bookbindery 1.0.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/{bin → install_bin}/bookbinder +0 -0
  3. data/lib/bookbinder.rb +2 -9
  4. data/lib/bookbinder/archive.rb +1 -1
  5. data/lib/bookbinder/archive_menu_configuration.rb +34 -0
  6. data/lib/bookbinder/book.rb +17 -17
  7. data/lib/bookbinder/cli.rb +25 -36
  8. data/lib/bookbinder/code_example.rb +5 -38
  9. data/lib/bookbinder/code_example_reader.rb +40 -0
  10. data/lib/bookbinder/colorizer.rb +14 -0
  11. data/lib/bookbinder/command_runner.rb +9 -16
  12. data/lib/bookbinder/command_validator.rb +14 -7
  13. data/lib/bookbinder/commands/bind.rb +321 -0
  14. data/lib/bookbinder/commands/build_and_push_tarball.rb +7 -6
  15. data/lib/bookbinder/commands/chain.rb +11 -0
  16. data/lib/bookbinder/commands/generate_pdf.rb +4 -3
  17. data/lib/bookbinder/commands/help.rb +49 -10
  18. data/lib/bookbinder/commands/naming.rb +9 -1
  19. data/lib/bookbinder/commands/push_local_to_staging.rb +4 -3
  20. data/lib/bookbinder/commands/push_to_prod.rb +36 -4
  21. data/lib/bookbinder/commands/run_publish_ci.rb +21 -24
  22. data/lib/bookbinder/commands/tag.rb +3 -3
  23. data/lib/bookbinder/commands/update_local_doc_repos.rb +5 -4
  24. data/lib/bookbinder/commands/version.rb +11 -8
  25. data/lib/bookbinder/configuration.rb +8 -3
  26. data/lib/bookbinder/configuration_fetcher.rb +7 -25
  27. data/lib/bookbinder/configuration_validator.rb +21 -0
  28. data/lib/bookbinder/distributor.rb +1 -1
  29. data/lib/bookbinder/dita_html_to_middleman_formatter.rb +37 -0
  30. data/lib/bookbinder/dita_section.rb +7 -0
  31. data/lib/bookbinder/dita_section_gatherer.rb +28 -0
  32. data/lib/bookbinder/git_accessor.rb +17 -0
  33. data/lib/bookbinder/git_client.rb +10 -7
  34. data/lib/bookbinder/git_hub_repository.rb +46 -41
  35. data/lib/bookbinder/local_dita_preprocessor.rb +27 -0
  36. data/lib/bookbinder/local_dita_to_html_converter.rb +49 -0
  37. data/lib/bookbinder/local_file_system_accessor.rb +68 -0
  38. data/lib/bookbinder/middleman_runner.rb +30 -17
  39. data/lib/bookbinder/publisher.rb +16 -80
  40. data/lib/bookbinder/remote_yaml_credential_provider.rb +2 -3
  41. data/lib/bookbinder/repositories/command_repository.rb +156 -0
  42. data/lib/bookbinder/repositories/section_repository.rb +31 -0
  43. data/lib/bookbinder/section.rb +5 -67
  44. data/lib/bookbinder/shell_out.rb +1 -0
  45. data/lib/bookbinder/sheller.rb +19 -0
  46. data/lib/bookbinder/sieve.rb +6 -1
  47. data/lib/bookbinder/terminal.rb +10 -0
  48. data/lib/bookbinder/user_message.rb +6 -0
  49. data/lib/bookbinder/user_message_presenter.rb +21 -0
  50. data/lib/bookbinder/yaml_loader.rb +18 -7
  51. data/master_middleman/archive_drop_down_menu.rb +46 -0
  52. data/master_middleman/bookbinder_helpers.rb +47 -40
  53. metadata +33 -87
  54. data/lib/bookbinder/commands/publish.rb +0 -138
  55. data/lib/bookbinder/usage_messenger.rb +0 -33
@@ -127,11 +127,31 @@ module Bookbinder
127
127
  end
128
128
  end
129
129
 
130
+ class RequiredKeysChecker
131
+ def check(config)
132
+ missing_keys = []
133
+
134
+ Configuration::CONFIG_REQUIRED_KEYS.map do |required_key|
135
+ config_keys = config.keys
136
+ unless config_keys.include?(required_key)
137
+ missing_keys.push(required_key)
138
+ end
139
+ end
140
+
141
+ if missing_keys.length > 0
142
+ raise ConfigurationValidator::MissingRequiredKeyError.new(
143
+ "Your config.yml is missing required key(s). Required keys are #{missing_keys.join(", ")}."
144
+ )
145
+ end
146
+ end
147
+ end
148
+
130
149
  class ConfigurationValidator
131
150
  DuplicateSectionNameError = Class.new(RuntimeError)
132
151
  MissingArchiveMenuPartialError = Class.new(RuntimeError)
133
152
  EmptyArchiveItemsError = Class.new(RuntimeError)
134
153
  ArchiveMenuNotDefinedError = Class.new(RuntimeError)
154
+ MissingRequiredKeyError = Class.new(RuntimeError)
135
155
 
136
156
  def initialize(logger, file_system_accessor)
137
157
  @logger = logger
@@ -143,6 +163,7 @@ module Bookbinder
143
163
 
144
164
  user_config_schema_version = config_hash['schema_version']
145
165
  exceptions = [
166
+ RequiredKeysChecker.new,
146
167
  ConfigVersionChecker.new(Version.parse(bookbinder_schema_version),
147
168
  Version.parse(starting_schema_version),
148
169
  VersionCheckerMessages.new(Version.parse(user_config_schema_version),
@@ -5,7 +5,7 @@ module Bookbinder
5
5
  EXPIRATION_HOURS = 2
6
6
 
7
7
  def self.build(logger, options)
8
- namespace = GitHubRepository.new(logger: logger, full_name: options[:book_repo]).short_name
8
+ namespace = GitHubRepository.new(logger: logger, full_name: options[:book_repo], git_accessor: Git).short_name
9
9
  namer = ArtifactNamer.new(namespace, options[:build_number], 'log', '/tmp')
10
10
 
11
11
  archive = Archive.new(logger: logger, key: options[:aws_credentials].access_key, secret: options[:aws_credentials].secret_key)
@@ -0,0 +1,37 @@
1
+ module Bookbinder
2
+
3
+ class DitaHtmlToMiddlemanFormatter
4
+ def initialize(file_system_accessor)
5
+ @file_system_accessor = file_system_accessor
6
+ end
7
+
8
+ def format(src, dest)
9
+ all_files_with_ext = file_system_accessor.find_files_with_ext('.html', src)
10
+
11
+ all_files_with_ext.map do |filepath|
12
+ file_title_text = file_system_accessor.read_html_in_tag(path: filepath,
13
+ marker: 'title')
14
+
15
+ file_body_text = file_system_accessor.read_html_in_tag(path: filepath,
16
+ marker: 'body')
17
+
18
+ relative_path_to_file = file_system_accessor.relative_path_from(src, filepath)
19
+ new_filepath = File.join dest, "#{relative_path_to_file}.erb"
20
+
21
+ output_text = frontmatter(file_title_text) + file_body_text
22
+
23
+ file_system_accessor.write(to: new_filepath, text: output_text)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :file_system_accessor
30
+
31
+ def frontmatter(title)
32
+ sanitized_title = title.gsub('"', '\"')
33
+ "---\ntitle: \"#{sanitized_title}\"\ndita: true\n---\n"
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,7 @@
1
+ module Bookbinder
2
+ DitaSection = Struct.new(:path_to_local_repo,
3
+ :ditamap_location,
4
+ :full_name,
5
+ :target_ref,
6
+ :directory)
7
+ end
@@ -0,0 +1,28 @@
1
+ module Bookbinder
2
+ class DitaSectionGatherer
3
+ def initialize(version_control_system, view_updater)
4
+ @version_control_system = version_control_system
5
+ @view_updater = view_updater
6
+ end
7
+
8
+ def gather(dita_sections, to: nil)
9
+ dita_sections.map do |dita_section|
10
+ view_updater.log "Gathering " + "#{dita_section.full_name}".cyan
11
+ version_control_system.clone("git@github.com:#{dita_section.full_name}",
12
+ dita_section.directory,
13
+ path: to)
14
+
15
+ DitaSection.new(File.join(to, dita_section.directory),
16
+ dita_section.ditamap_location,
17
+ dita_section.full_name,
18
+ dita_section.target_ref,
19
+ dita_section.directory)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :version_control_system, :view_updater
26
+
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ require 'git'
2
+
3
+ module Bookbinder
4
+ class GitAccessor
5
+ def initialize
6
+ @cache = {}
7
+ end
8
+
9
+ def clone(url, name, path: nil)
10
+ cache[[url, name, path]] ||= Git.clone(url, name, path: path)
11
+ end
12
+
13
+ private
14
+
15
+ attr_reader :cache, :third_party
16
+ end
17
+ end
@@ -5,18 +5,21 @@ module Bookbinder
5
5
  class GitClient::TokenException < StandardError;
6
6
  end
7
7
 
8
- def initialize(logger, *args)
9
- @logger = logger
10
- super(*args)
11
- end
12
-
13
8
  def head_sha(full_name)
14
9
  commits(full_name).first.sha
15
10
  end
16
11
 
17
12
  def create_tag!(full_name, tagname, ref)
18
- @logger.log 'Tagging ' + full_name.cyan
19
- tag_result = create_tag(full_name, "tags/#{tagname}", 'Tagged by Bookbinder', ref, 'commit', 'Bookbinder', 'bookbinder@cloudfoundry.org', Time.now.iso8601)
13
+ tag_result = create_tag(
14
+ full_name,
15
+ "tags/#{tagname}",
16
+ 'Tagged by Bookbinder',
17
+ ref,
18
+ 'commit',
19
+ 'Bookbinder',
20
+ 'bookbinder@cloudfoundry.org',
21
+ Time.now.iso8601
22
+ )
20
23
  create_ref(full_name, "tags/#{tagname}", tag_result.sha)
21
24
  rescue Octokit::Unauthorized, Octokit::NotFound
22
25
  raise_error_with_context
@@ -1,52 +1,65 @@
1
- require 'ruby-progressbar'
2
1
  require 'bookbinder/shell_out'
3
2
  require 'git'
4
- require_relative 'git_client'
5
3
  require_relative 'bookbinder_logger'
4
+ require_relative 'git_client'
6
5
 
7
6
  module Bookbinder
8
7
  class GitHubRepository
9
- class RepositoryCloneError < StandardError
10
- def initialize(msg=nil)
11
- super
12
- end
13
- end
8
+ RepositoryCloneError = Class.new(StandardError)
14
9
 
15
10
  include Bookbinder::ShellOut
16
11
 
17
12
  attr_reader :full_name, :copied_to
18
13
 
19
- def self.build_from_remote(logger, section_hash, destination_dir, target_ref, git_accessor)
14
+ def self.build_from_remote(logger,
15
+ section_hash,
16
+ target_ref,
17
+ git_accessor)
20
18
  full_name = section_hash.fetch('repository', {}).fetch('name')
21
19
  target_ref = target_ref || section_hash.fetch('repository', {})['ref']
22
20
  directory = section_hash['directory']
23
- repository = new(logger: logger, full_name: full_name, target_ref: target_ref, github_token: ENV['GITHUB_API_TOKEN'], directory: directory)
24
- repository.copy_from_remote(destination_dir, git_accessor) if destination_dir
25
- repository
26
- end
27
-
28
- def self.build_from_local(logger, section_hash, local_repo_dir, destination_dir)
21
+ new(logger: logger,
22
+ full_name: full_name,
23
+ target_ref: target_ref,
24
+ github_token: ENV['GITHUB_API_TOKEN'],
25
+ directory: directory,
26
+ git_accessor: git_accessor)
27
+ end
28
+
29
+ def self.build_from_local(logger,
30
+ section_hash,
31
+ local_repo_dir,
32
+ git_accessor)
29
33
  full_name = section_hash.fetch('repository').fetch('name')
30
34
  directory = section_hash['directory']
31
35
 
32
- repository = new(logger: logger, full_name: full_name, directory: directory, local_repo_dir: local_repo_dir)
33
- repository.copy_from_local(destination_dir) if destination_dir
34
-
35
- repository
36
+ new(logger: logger,
37
+ full_name: full_name,
38
+ directory: directory,
39
+ local_repo_dir: local_repo_dir,
40
+ git_accessor: git_accessor)
36
41
  end
37
42
 
38
- def initialize(logger: nil, full_name: nil, target_ref: nil, github_token: nil, directory: nil, local_repo_dir: nil)
43
+ def initialize(logger: nil,
44
+ full_name: nil,
45
+ target_ref: nil,
46
+ github_token: nil,
47
+ directory: nil,
48
+ local_repo_dir: nil,
49
+ git_accessor: nil)
39
50
  @logger = logger
40
- #TODO better error message
41
51
  raise 'No full_name provided ' unless full_name
42
52
  @full_name = full_name
43
- @github = GitClient.new(logger, access_token: github_token || ENV['GITHUB_API_TOKEN'])
44
53
  @target_ref = target_ref
45
54
  @directory = directory
46
55
  @local_repo_dir = local_repo_dir
56
+
57
+ @github = GitClient.new(access_token: github_token || ENV['GITHUB_API_TOKEN'])
58
+ @git_accessor = git_accessor or raise ArgumentError.new("Must provide a git accessor")
47
59
  end
48
60
 
49
61
  def tag_with(tagname)
62
+ @logger.log 'Tagging ' + full_name.cyan
50
63
  @github.create_tag! full_name, tagname, head_sha
51
64
  end
52
65
 
@@ -62,9 +75,11 @@ module Bookbinder
62
75
  @directory || short_name
63
76
  end
64
77
 
65
- def copy_from_remote(destination_dir, git_accessor = Git)
78
+ def copy_from_remote(destination_dir)
66
79
  begin
67
- @git = git_accessor.clone("git@github.com:#{full_name}", directory, path: destination_dir)
80
+ @git_base_object = git_accessor.clone("git@github.com:#{full_name}",
81
+ directory,
82
+ path: destination_dir)
68
83
  rescue => e
69
84
  if e.message.include? "Permission denied (publickey)"
70
85
  raise RepositoryCloneError.new "Unable to access repository #{full_name}. You do not have the correct access rights. Please either add the key to your SSH agent, or set the GIT_SSH environment variable to override default SSH key usage. For more information run: `man git`."
@@ -75,8 +90,8 @@ module Bookbinder
75
90
  raise e
76
91
  end
77
92
  end
78
- @git.checkout(target_ref) unless target_ref == 'master'
79
- @copied_to = destination_dir
93
+ @git_base_object.checkout(target_ref) unless target_ref == 'master'
94
+ @copied_to = File.join(destination_dir, directory)
80
95
  end
81
96
 
82
97
  def copy_from_local(destination_dir)
@@ -116,30 +131,20 @@ module Bookbinder
116
131
  @logger.log ' skipping (not found) '.magenta + path_to_local_repo
117
132
  end
118
133
 
119
- def get_modification_date_for(file: nil, git: nil)
120
- @git ||= git
121
- raise "Unexpected Error: Git accessor unavailable." if @git.nil?
122
-
123
- irrelevant_path_component = directory+'/'
124
- repo_path = file.gsub(irrelevant_path_component, '')
125
-
126
- begin
127
- @git.log(1).object(repo_path).first.date
128
- rescue Git::GitExecuteError => e
129
- raise "This file does not exist or is not tracked by git! Cannot get last modified date for #{repo_path}."
130
- end
131
- end
132
-
133
134
  def path_to_local_repo
134
- File.join(@local_repo_dir, short_name)
135
+ if @local_repo_dir
136
+ File.join(@local_repo_dir, short_name)
137
+ end
135
138
  end
136
139
 
137
140
  def has_git_object?
138
- !!@git
141
+ !!@git_base_object
139
142
  end
140
143
 
141
144
  private
142
145
 
146
+ attr_reader :git_accessor
147
+
143
148
  def target_ref
144
149
  @target_ref ||= 'master'
145
150
  end
@@ -0,0 +1,27 @@
1
+ module Bookbinder
2
+ class LocalDitaPreprocessor
3
+
4
+ def initialize(dita_converter, dita_formatter, local_file_system_accessor)
5
+ @dita_converter = dita_converter
6
+ @dita_formatter = dita_formatter
7
+ @local_file_system_accessor = local_file_system_accessor
8
+ end
9
+
10
+ def preprocess(dita_sections, converted_dita_dir, formatted_dita_dir, workspace_dir)
11
+ dita_converter.convert dita_sections, to: converted_dita_dir
12
+
13
+ dita_formatter.format converted_dita_dir, formatted_dita_dir
14
+
15
+ local_file_system_accessor.copy_named_directory_with_path('images',
16
+ converted_dita_dir,
17
+ workspace_dir)
18
+ local_file_system_accessor.copy_contents(formatted_dita_dir, workspace_dir)
19
+ end
20
+
21
+
22
+ private
23
+
24
+ attr_reader :dita_converter, :dita_formatter, :local_file_system_accessor
25
+
26
+ end
27
+ end
@@ -0,0 +1,49 @@
1
+ require_relative '../bookbinder/dita_section'
2
+
3
+ module Bookbinder
4
+ class LocalDitaToHtmlConverter
5
+ DitaToHtmlLibraryFailure = Class.new(RuntimeError)
6
+
7
+ def initialize(sheller, path_to_dita_ot_library)
8
+ @sheller = sheller
9
+ @path_to_dita_ot_library = path_to_dita_ot_library
10
+ end
11
+
12
+ def convert(dita_sections, to: nil)
13
+ dita_sections.map do |dita_section|
14
+ absolute_path_to_ditamap = File.join dita_section.path_to_local_repo, dita_section.ditamap_location
15
+ classpath = "#{path_to_dita_ot_library}/lib/xercesImpl.jar:" +
16
+ "#{path_to_dita_ot_library}/lib/xml-apis.jar:" +
17
+ "#{path_to_dita_ot_library}/lib/resolver.jar:" +
18
+ "#{path_to_dita_ot_library}/lib/commons-codec-1.4.jar:" +
19
+ "#{path_to_dita_ot_library}/lib/icu4j.jar:" +
20
+ "#{path_to_dita_ot_library}/lib/saxon/saxon9-dom.jar:" +
21
+ "#{path_to_dita_ot_library}/lib/saxon/saxon9.jar:target/classes:" +
22
+ "#{path_to_dita_ot_library}:" +
23
+ "#{path_to_dita_ot_library}/lib/:" +
24
+ "#{path_to_dita_ot_library}/lib/dost.jar"
25
+ out_dir = File.join to, dita_section.directory
26
+ command = "export CLASSPATH=#{classpath}; " +
27
+ "ant -f #{path_to_dita_ot_library} " +
28
+ "-Dbasedir='/' " +
29
+ "-Doutput.dir=#{out_dir} " +
30
+ "-Dtranstype='tocjs' " +
31
+ "-Dargs.input=#{absolute_path_to_ditamap} "
32
+
33
+ begin
34
+ sheller.run_command(command)
35
+ rescue Sheller::ShelloutFailure
36
+ raise DitaToHtmlLibraryFailure.new 'The DITA-to-HTML conversion failed. ' +
37
+ 'Please check that you have specified the path to your DITA-OT library in the ENV, ' +
38
+ 'that your DITA-specific keys/values in config.yml are set, ' +
39
+ 'and that your DITA toolkit is correctly configured.'
40
+
41
+ end
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ attr_reader :sheller, :path_to_dita_ot_library
48
+ end
49
+ end
@@ -1,9 +1,77 @@
1
+ require 'find'
2
+ require 'pathname'
3
+ require 'nokogiri'
4
+
1
5
  module Bookbinder
2
6
 
3
7
  class LocalFileSystemAccessor
4
8
  def file_exist?(path)
5
9
  File.exist?(path)
6
10
  end
11
+
12
+ def write(to: nil, text: nil)
13
+ make_directory(File.dirname to)
14
+
15
+ File.open(to, 'a') do |f|
16
+ f.write(text)
17
+ end
18
+
19
+ to
20
+ end
21
+
22
+ def read(path)
23
+ File.read(path)
24
+ end
25
+
26
+ def read_html_in_tag(path: nil, marker: nil)
27
+ doc = Nokogiri::XML(File.open path)
28
+ doc.css(marker).inner_html
29
+ end
30
+
31
+ def remove_directory(path)
32
+ FileUtils.rm_rf(path)
33
+ end
34
+
35
+ def make_directory(path)
36
+ FileUtils.mkdir_p(path)
37
+ end
38
+
39
+ def copy(src, dest)
40
+ FileUtils.cp_r src, dest
41
+ end
42
+
43
+ def copy_contents(src, dest)
44
+ contents = Dir.glob File.join(src, '**')
45
+ contents.each do |dir|
46
+ FileUtils.cp_r dir, dest
47
+ end
48
+ end
49
+
50
+ def copy_named_directory_with_path(dir_name, src, dest)
51
+ contents = Dir.glob File.join(src, "**/#{dir_name}")
52
+ contents.each do |dir|
53
+ relative_path_to_dir = relative_path_from(src, dir)
54
+ extended_dest = File.join dest, relative_path_to_dir
55
+ FileUtils.mkdir_p extended_dest
56
+ copy_contents dir, extended_dest
57
+ end
58
+ end
59
+
60
+ def rename_file(path, new_name)
61
+ new_path = File.expand_path File.join path, '..', new_name
62
+ File.rename(path, new_path)
63
+ end
64
+
65
+ def find_files_with_ext(ext, path)
66
+ Dir[File.join path, '**/*'].select { |file| File.basename(file).match(ext) }
67
+ end
68
+
69
+ def relative_path_from(src, target)
70
+ target_path = Pathname(File.absolute_path target)
71
+ relative_path = target_path.relative_path_from(Pathname(File.absolute_path src))
72
+ relative_path.to_s
73
+ end
74
+
7
75
  end
8
76
 
9
77
  end