bookbindery 1.0.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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