bookbindery 3.1.0 → 3.1.2

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/install_bin/bookbinder +1 -1
  3. data/lib/bookbinder/cf_command_runner.rb +1 -1
  4. data/lib/bookbinder/cli.rb +5 -5
  5. data/lib/bookbinder/code_example_reader.rb +57 -19
  6. data/lib/bookbinder/colorizer.rb +5 -2
  7. data/lib/bookbinder/commands/bind.rb +32 -42
  8. data/lib/bookbinder/commands/bind/bind_options.rb +6 -4
  9. data/lib/bookbinder/commands/bind/directory_preparer.rb +8 -13
  10. data/lib/bookbinder/{repositories/command_repository.rb → commands/collection.rb} +19 -37
  11. data/lib/bookbinder/config/archive_menu_configuration.rb +38 -0
  12. data/lib/bookbinder/config/checkers/archive_menu_checker.rb +35 -0
  13. data/lib/bookbinder/config/checkers/dita_section_checker.rb +24 -0
  14. data/lib/bookbinder/config/checkers/duplicate_section_name_checker.rb +29 -0
  15. data/lib/bookbinder/config/checkers/repository_name_presence_checker.rb +37 -0
  16. data/lib/bookbinder/config/checkers/required_keys_checker.rb +47 -0
  17. data/lib/bookbinder/config/configuration.rb +99 -0
  18. data/lib/bookbinder/config/fetcher.rb +61 -0
  19. data/lib/bookbinder/config/remote_yaml_credential_provider.rb +22 -0
  20. data/lib/bookbinder/config/validator.rb +30 -0
  21. data/lib/bookbinder/config/yaml_loader.rb +34 -0
  22. data/lib/bookbinder/ingest/git_cloner.rb +0 -1
  23. data/lib/bookbinder/ingest/local_filesystem_cloner.rb +22 -17
  24. data/lib/bookbinder/ingest/missing_working_copy.rb +21 -0
  25. data/lib/bookbinder/{repositories → ingest}/section_repository.rb +3 -4
  26. data/lib/bookbinder/{repositories → ingest}/section_repository_factory.rb +1 -1
  27. data/lib/bookbinder/ingest/working_copy.rb +12 -16
  28. data/lib/bookbinder/preprocessing/copy_to_site_gen_dir.rb +2 -3
  29. data/lib/bookbinder/preprocessing/dita_preprocessor.rb +5 -5
  30. data/lib/bookbinder/preprocessing/preprocessor.rb +1 -1
  31. data/lib/bookbinder/sheller.rb +3 -0
  32. data/lib/bookbinder/streams/colorized_stream.rb +1 -1
  33. data/lib/bookbinder/values/cf_routes.rb +17 -6
  34. data/lib/bookbinder/values/output_locations.rb +1 -12
  35. data/lib/bookbinder/values/section.rb +2 -7
  36. data/master_middleman/bookbinder_helpers.rb +23 -18
  37. data/master_middleman/quicklinks_renderer.rb +1 -0
  38. data/template_app/lib/rack_static.rb +2 -0
  39. data/template_app/lib/vienna_application.rb +2 -0
  40. metadata +51 -52
  41. data/lib/bookbinder.rb +0 -44
  42. data/lib/bookbinder/archive_menu_configuration.rb +0 -34
  43. data/lib/bookbinder/configuration.rb +0 -97
  44. data/lib/bookbinder/configuration_fetcher.rb +0 -59
  45. data/lib/bookbinder/configuration_validator.rb +0 -28
  46. data/lib/bookbinder/remote_yaml_credential_provider.rb +0 -20
  47. data/lib/bookbinder/shell_out.rb +0 -20
  48. data/lib/bookbinder/validation_checkers/archive_menu_checker.rb +0 -31
  49. data/lib/bookbinder/validation_checkers/dita_section_checker.rb +0 -20
  50. data/lib/bookbinder/validation_checkers/duplicate_section_name_checker.rb +0 -25
  51. data/lib/bookbinder/validation_checkers/repository_name_presence_checker.rb +0 -33
  52. data/lib/bookbinder/validation_checkers/required_keys_checker.rb +0 -43
  53. data/lib/bookbinder/yaml_loader.rb +0 -33
@@ -0,0 +1,38 @@
1
+ require_relative 'configuration'
2
+
3
+ module Bookbinder
4
+ module Config
5
+ class ArchiveMenuConfiguration
6
+ def initialize(loader: nil, config_filename: nil)
7
+ @loader = loader
8
+ @config_filename = config_filename
9
+ end
10
+
11
+ def generate(base_config, sections)
12
+ base_config.merge(
13
+ Configuration.new(
14
+ archive_menu: root_config(base_config).merge(section_config(sections))))
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :loader, :config_filename
20
+
21
+ def root_config(base_config)
22
+ { '.' => base_config.archive_menu }
23
+ end
24
+
25
+ def section_config(sections)
26
+ sections.reduce({}) {|config, section|
27
+ config_path = section.path_to_repository.join(config_filename)
28
+ archive_config = loader.load_key(config_path, 'archive_menu')
29
+ if archive_config
30
+ config.merge(section.desired_directory_name => archive_config)
31
+ else
32
+ config
33
+ end
34
+ }
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,35 @@
1
+ module Bookbinder
2
+ module Config
3
+ module Checkers
4
+ class ArchiveMenuChecker
5
+ MissingArchiveMenuPartialError = Class.new(RuntimeError)
6
+ ArchiveMenuNotDefinedError = Class.new(RuntimeError)
7
+ EmptyArchiveItemsError = Class.new(RuntimeError)
8
+
9
+ def initialize(file_system_accessor)
10
+ @file_system_accessor = file_system_accessor
11
+ end
12
+
13
+ def check(config)
14
+ partial_location = './master_middleman/source/archive_menus/_default.erb'
15
+ if config.has_key?("archive_menu") && config["archive_menu"].nil?
16
+ ArchiveMenuNotDefinedError.new 'Did you mean to provide an archive menu value to display? If you use the archive_menu key, you must provide at least one value.'
17
+ elsif archive_items(config).include?(nil)
18
+ EmptyArchiveItemsError.new 'Did you forget to add a value to the archive_menu?'
19
+ elsif config.has_key?("archive_menu") && !file_system_accessor.file_exist?(partial_location)
20
+ MissingArchiveMenuPartialError.new "You must provide a template partial named at #{partial_location}"
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :file_system_accessor
27
+
28
+ def archive_items(config)
29
+ config.fetch('archive_menu', [])
30
+ end
31
+
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,24 @@
1
+ module Bookbinder
2
+ module Config
3
+ module Checkers
4
+ class DitaSectionChecker
5
+ DitamapLocationError = Class.new(RuntimeError)
6
+
7
+ def check(config)
8
+ dita_sections = config['dita_sections'].to_a
9
+
10
+ sum = 0
11
+ dita_sections.each do |section|
12
+ if section['ditamap_location']
13
+ sum += 1
14
+ end
15
+ end
16
+
17
+ if !dita_sections.empty? && (sum < 1)
18
+ DitamapLocationError.new "You must have at least one 'ditamap_location' key in dita_sections."
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ module Bookbinder
2
+ module Config
3
+ module Checkers
4
+ class DuplicateSectionNameChecker
5
+ DuplicateSectionNameError = Class.new(RuntimeError)
6
+
7
+ def check(config)
8
+ if duplicate_section_names?(config)
9
+ DuplicateSectionNameError.new error_message
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def duplicate_section_names?(config)
16
+ sections = config['sections'].to_a + config['dita_sections'].to_a
17
+ directory_names = sections.map {|section| section['directory']}
18
+ directory_names.length != directory_names.uniq.length
19
+ end
20
+
21
+ def error_message
22
+ <<-ERROR
23
+ Duplicate repository names are not allowed.
24
+ ERROR
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,37 @@
1
+ module Bookbinder
2
+ module Config
3
+ module Checkers
4
+ class RepositoryNamePresenceChecker
5
+ MissingRepositoryNameError = Class.new(RuntimeError)
6
+
7
+ def check(config)
8
+ all_sections = config['sections'].to_a + config['dita_sections'].to_a
9
+ failures = all_sections.map do |section|
10
+ if !section['repository'] || !section['repository']['name']
11
+ true
12
+ end
13
+ end
14
+
15
+ if failures.compact.empty?
16
+ nil
17
+ else
18
+ MissingRepositoryNameError.new error_message
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def error_message
25
+ <<-ERROR
26
+ Cannot locate a specific section.
27
+ All sections must provide the section 'name' key under the 'repository' key:
28
+
29
+ sections:
30
+ - repository:
31
+ name: 'your-org/your-repo'
32
+ ERROR
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,47 @@
1
+ require_relative '../configuration'
2
+
3
+ module Bookbinder
4
+ module Config
5
+ module Checkers
6
+ class RequiredKeysChecker
7
+ MissingRequiredKeyError = Class.new(RuntimeError)
8
+ SectionAbsenceError = Class.new(RuntimeError)
9
+
10
+ def check(config)
11
+ missing_keys = []
12
+
13
+ Config::Configuration::CONFIG_REQUIRED_KEYS.map do |required_key|
14
+ config_keys = config.keys
15
+ unless config_keys.include?(required_key)
16
+ missing_keys.push(required_key)
17
+ end
18
+ end
19
+
20
+ if missing_keys.length > 0
21
+ MissingRequiredKeyError.new("Your config.yml is missing required key(s). Required keys are #{missing_keys.join(", ")}.")
22
+ elsif !config['sections'] && !config['dita_sections']
23
+ SectionAbsenceError.new error_message
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def error_message
30
+ <<-ERROR
31
+ Cannot locate your sections.
32
+ Must specify at least one of 'sections' and/or 'dita_sections' in config.yml:
33
+
34
+ sections:
35
+ - repository:
36
+ name: 'your-org/your-repo'
37
+
38
+ dita_sections:
39
+ - repository:
40
+ name: 'dita-org/dita-repo'
41
+ ditamap_location: 'example.ditamap'
42
+ ERROR
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,99 @@
1
+ require_relative '../ingest/repo_identifier'
2
+ require_relative 'section_config'
3
+
4
+ module Bookbinder
5
+ module Config
6
+ class Configuration
7
+ class << self
8
+ def parse(input_config)
9
+ new(symbolize_keys(input_config).
10
+ merge(expand_repo_identifiers(input_config)).
11
+ merge(sections: combined_sections(input_config)))
12
+ end
13
+
14
+ private
15
+
16
+ def symbolize_keys(h)
17
+ h.reduce({}) {|acc, (k, v)| acc.merge(k.to_sym => v) }
18
+ end
19
+
20
+ def expand_repo_identifiers(input_config)
21
+ input_config.select {|k, _| k.match(/_repo$/)}.
22
+ reduce({}) {|h, (k, v)| h.merge(:"#{k}_url" => Ingest::RepoIdentifier.new(v))}
23
+ end
24
+
25
+ def combined_sections(input_config)
26
+ (regular_sections(input_config) + dita_sections(input_config)).
27
+ map { |section| Config::SectionConfig.new(section) }
28
+ end
29
+
30
+ def regular_sections(input_config)
31
+ input_config['sections'] || []
32
+ end
33
+
34
+ def dita_sections(input_config)
35
+ (input_config['dita_sections'] || []).map { |dita_section|
36
+ dita_section.merge(
37
+ 'preprocessor_config' => {
38
+ 'ditamap_location' => dita_section['ditamap_location'],
39
+ 'ditaval_location' => dita_section['ditaval_location']
40
+ },
41
+ 'subnav_template' => 'dita_subnav'
42
+ ).reject { |k, _|
43
+ %w(ditamap_location ditaval_location).include?(k)
44
+ }
45
+ }
46
+ end
47
+ end
48
+
49
+ def initialize(config)
50
+ @config = config
51
+ end
52
+
53
+ CONFIG_REQUIRED_KEYS = %w(book_repo public_host)
54
+ CONFIG_OPTIONAL_KEYS = %w(archive_menu book_repo_url cred_repo cred_repo_url layout_repo layout_repo_url sections)
55
+
56
+ CONFIG_REQUIRED_KEYS.each do |method_name|
57
+ define_method(method_name) do
58
+ config.fetch(method_name.to_sym)
59
+ end
60
+ end
61
+
62
+ CONFIG_OPTIONAL_KEYS.each do |method_name|
63
+ define_method(method_name) do
64
+ config[method_name.to_sym]
65
+ end
66
+ end
67
+
68
+ def template_variables
69
+ config.fetch(:template_variables, {})
70
+ end
71
+
72
+ def versions
73
+ config.fetch(:versions, [])
74
+ end
75
+
76
+ def merge(other_configuration)
77
+ Configuration.new(config.merge(other_configuration.instance_variable_get(:@config)))
78
+ end
79
+
80
+ def merge_sections(incoming_sections)
81
+ merge(Configuration.new(sections: sections + incoming_sections))
82
+ end
83
+
84
+ def has_option?(key)
85
+ !!config[key.to_sym]
86
+ end
87
+
88
+ def ==(o)
89
+ o.class == self.class && o.instance_variable_get(:@config) == @config
90
+ end
91
+
92
+ alias_method :eql?, :==
93
+
94
+ private
95
+
96
+ attr_reader :config
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,61 @@
1
+ require_relative 'aws_credentials'
2
+ require_relative 'cf_credentials'
3
+ require_relative 'configuration'
4
+ require_relative 'yaml_loader'
5
+
6
+ module Bookbinder
7
+ module Config
8
+ class Fetcher
9
+ def initialize(logger, configuration_validator, loader, credentials_provider)
10
+ @loader = loader
11
+ @logger = logger
12
+ @configuration_validator = configuration_validator
13
+ @credentials_provider = credentials_provider
14
+ end
15
+
16
+ def fetch_config
17
+ @config ||= validate(read_config_file)
18
+ end
19
+
20
+ def fetch_credentials(environment = 'null-environment')
21
+ @credentials ||= credentials_provider.credentials(fetch_config.cred_repo_url)
22
+ {
23
+ aws: Config::AwsCredentials.new(
24
+ @credentials.fetch('aws', {})
25
+ ),
26
+ cloud_foundry: Config::CfCredentials.new(
27
+ @credentials.fetch('cloud_foundry', {}),
28
+ environment
29
+ )
30
+ }
31
+ end
32
+
33
+ def set_config_file_path config_file_path
34
+ @config_file_path = File.expand_path config_file_path
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader(:loader, :logger, :configuration_validator, :config, :config_file_path,
40
+ :credentials_provider)
41
+
42
+ def read_config_file
43
+ loader.load(config_file_path)
44
+
45
+ rescue FileNotFoundError => e
46
+ raise "The configuration file specified does not exist. Please create a config #{e} file at #{config_file_path} and try again."
47
+ rescue InvalidSyntaxError => e
48
+ raise "There is a syntax error in your config file: \n #{e}"
49
+ end
50
+
51
+ def validate(config_hash)
52
+ raise 'Your config.yml appears to be empty. Please check and try again.' unless config_hash
53
+
54
+ errors = configuration_validator.exceptions(config_hash)
55
+ raise errors.first if errors.any?
56
+
57
+ Configuration.parse(config_hash)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,22 @@
1
+ require 'yaml'
2
+ require 'ansi/code'
3
+
4
+ module Bookbinder
5
+ module Config
6
+ class RemoteYamlCredentialProvider
7
+ def initialize(logger, version_control_system)
8
+ @logger = logger
9
+ @version_control_system = version_control_system
10
+ end
11
+
12
+ def credentials(repo_url)
13
+ logger.log "Processing #{ANSI.cyan{repo_url}}"
14
+ YAML.load(version_control_system.read_file("credentials.yml", from_repo: repo_url))
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :logger, :version_control_system
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ require_relative 'checkers/duplicate_section_name_checker'
2
+ require_relative 'checkers/archive_menu_checker'
3
+ require_relative 'checkers/required_keys_checker'
4
+ require_relative 'checkers/repository_name_presence_checker'
5
+ require_relative 'checkers/dita_section_checker'
6
+
7
+ module Bookbinder
8
+ module Config
9
+ class Validator
10
+ def initialize(logger, file_system_accessor)
11
+ @logger = logger
12
+ @file_system_accessor = file_system_accessor
13
+ end
14
+
15
+ def exceptions(config_hash)
16
+ exceptions = [
17
+ Checkers::RequiredKeysChecker.new,
18
+ Checkers::DuplicateSectionNameChecker.new,
19
+ Checkers::RepositoryNamePresenceChecker.new,
20
+ Checkers::DitaSectionChecker.new,
21
+ Checkers::ArchiveMenuChecker.new(@file_system_accessor)
22
+ ].map do |checker|
23
+ checker.check(config_hash)
24
+ end
25
+
26
+ exceptions.compact
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ require 'yaml'
2
+
3
+ module Bookbinder
4
+ module Config
5
+ FileNotFoundError = Class.new(RuntimeError)
6
+ InvalidSyntaxError = Class.new(RuntimeError)
7
+
8
+ class YAMLLoader
9
+ def load(path)
10
+ if File.exist?(path)
11
+ config(path)
12
+ else
13
+ raise FileNotFoundError.new, "YAML"
14
+ end
15
+ rescue Psych::SyntaxError => e
16
+ raise InvalidSyntaxError.new e
17
+ end
18
+
19
+ def load_key(path, key)
20
+ if File.exist?(path)
21
+ config(path)[key]
22
+ end
23
+ rescue Psych::SyntaxError => e
24
+ raise InvalidSyntaxError.new e
25
+ end
26
+
27
+ private
28
+
29
+ def config(path)
30
+ YAML.load_file(path) || {}
31
+ end
32
+ end
33
+ end
34
+ end