bookbindery 6.0.0 → 7.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/bookbinder.gemspec +2 -2
  3. data/lib/bookbinder/commands/bind.rb +2 -1
  4. data/lib/bookbinder/commands/bind/directory_preparer.rb +3 -12
  5. data/lib/bookbinder/commands/bind/layout_preparer.rb +23 -0
  6. data/lib/bookbinder/commands/collection.rb +22 -8
  7. data/lib/bookbinder/commands/watch.rb +2 -1
  8. data/lib/bookbinder/config/checkers/subnavs_checker.rb +49 -0
  9. data/lib/bookbinder/config/checkers/topics_checker.rb +37 -0
  10. data/lib/bookbinder/config/configuration.rb +14 -1
  11. data/lib/bookbinder/config/fetcher.rb +27 -8
  12. data/lib/bookbinder/config/section_config.rb +4 -0
  13. data/lib/bookbinder/config/subnav_config.rb +41 -0
  14. data/lib/bookbinder/config/topic_config.rb +29 -0
  15. data/lib/bookbinder/config/validator.rb +5 -1
  16. data/lib/bookbinder/dita_command_creator.rb +2 -2
  17. data/lib/bookbinder/dita_html_to_middleman_formatter.rb +2 -2
  18. data/lib/bookbinder/ingest/section_repository.rb +1 -2
  19. data/lib/bookbinder/local_filesystem_accessor.rb +14 -0
  20. data/lib/bookbinder/preprocessing/dita_preprocessor.rb +2 -2
  21. data/lib/bookbinder/preprocessing/link_to_site_gen_dir.rb +15 -3
  22. data/lib/bookbinder/server_director.rb +1 -1
  23. data/lib/bookbinder/subnav/json_from_config.rb +77 -0
  24. data/lib/bookbinder/subnav/json_from_html.rb +38 -0
  25. data/lib/bookbinder/subnav/json_props_creator.rb +35 -0
  26. data/lib/bookbinder/subnav/pdf_config_creator.rb +52 -0
  27. data/lib/bookbinder/subnav/subnav_generator.rb +19 -0
  28. data/lib/bookbinder/subnav/subnav_generator_factory.rb +35 -0
  29. data/lib/bookbinder/subnav/template_creator.rb +41 -0
  30. data/lib/bookbinder/values/output_locations.rb +4 -0
  31. data/lib/bookbinder/values/section.rb +2 -1
  32. data/lib/bookbinder/values/{subnav.rb → subnav_template.rb} +2 -2
  33. data/master_middleman/archive_drop_down_menu.rb +4 -0
  34. data/master_middleman/bookbinder_helpers.rb +4 -3
  35. data/master_middleman/compass_runner.rb +0 -0
  36. metadata +17 -5
  37. data/lib/bookbinder/subnav_formatter.rb +0 -37
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c73a7a9a4f263f7730ce38c6e09b98ae457a9571
4
- data.tar.gz: bbd94c2710ce6052e2ad401525b9e024a584245b
3
+ metadata.gz: 0fb5495aa9aaa1b4337177fd9659ff1770edb7d1
4
+ data.tar.gz: 6aaa6711faed7f97fb649b8f0a7d76b259fdbc6d
5
5
  SHA512:
6
- metadata.gz: e6dbef842e82feb7bb9e4ab4dd273cc25bee08d6fb0ce4119f041c304e461ecca957269b2a96b96677e8ab03e8bfb05579654a9b47f1ff4cebe3e82723d76e5a
7
- data.tar.gz: c6331e3c72ee6a989f26b879d1234f2e9ae1d809b10c50bc6e2e8e633157ee155d6ffa97b7243e1d9f7944b18d532d0ed1eaa517fb7876f3fae6377db8883e2f
6
+ metadata.gz: d9d3278fb5effbab9c2591a5700685a78af34d9095a28d16dd1eb95144188fa498607a920cbc30224347ae972ec7e870a6a88be060e7091e7961fd609762865b
7
+ data.tar.gz: ae8ebb4008aedf58c261ce94ab0569ea8bec4caacf367ce9c2b5f611523b5793cc9650b9f987579cb95b507fd64f20d5a5ed5b1d5b688e345c75a656ee6cd98b
data/bookbinder.gemspec CHANGED
@@ -2,7 +2,7 @@ require 'base64'
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'bookbindery'
5
- s.version = '6.0.0'
5
+ s.version = '7.0.0'
6
6
  s.summary = 'Markdown to Rackup application documentation generator'
7
7
  s.description = 'A command line utility to be run in Book repositories to stitch together their constituent Markdown repos into a static-HTML-serving application'
8
8
  s.authors = ['Mike Grafton', 'Lucas Marks', 'Gavin Morgan', 'Nikhil Gajwani', 'Dan Wendorf', 'Brenda Chan', 'Matthew Boedicker', 'Frank Kotsianas', 'Elena Sharma', 'Christa Hartsock']
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
14
14
  s.bindir = 'install_bin'
15
15
  s.executable = 'bookbinder'
16
16
 
17
- s.required_ruby_version = '~> 2.0'
17
+ s.required_ruby_version = '>= 2.0'
18
18
  s.add_runtime_dependency 'fog-aws', ['~> 0.7.1']
19
19
  s.add_runtime_dependency 'ansi', ['~> 1.4']
20
20
  s.add_runtime_dependency 'unf', ['~> 0.1']
@@ -68,7 +68,8 @@ module Bookbinder
68
68
  sections,
69
69
  output_locations,
70
70
  options: bind_options.options,
71
- output_streams: bind_options.streams
71
+ output_streams: bind_options.streams,
72
+ config: bind_config
72
73
  )
73
74
  if file_system_accessor.file_exist?('redirects.rb')
74
75
  file_system_accessor.copy('redirects.rb', output_locations.final_app_dir)
@@ -1,3 +1,5 @@
1
+ require_relative 'layout_preparer'
2
+
1
3
  module Bookbinder
2
4
  module Commands
3
5
  module BindComponents
@@ -13,8 +15,7 @@ module Bookbinder
13
15
  copy_directory_from_gem(gem_root, 'template_app', output_locations.final_app_dir)
14
16
  copy_directory_from_gem(gem_root, 'master_middleman', output_locations.site_generator_home)
15
17
 
16
- layout_repo_path = fetch_layout_repo(config, cloner)
17
- fs.copy_contents(layout_repo_path, output_locations.site_generator_home)
18
+ LayoutPreparer.new(fs).prepare(output_locations, cloner, config)
18
19
  end
19
20
 
20
21
  private
@@ -24,16 +25,6 @@ module Bookbinder
24
25
  def copy_directory_from_gem(gem_root, dir, output_dir)
25
26
  fs.copy_contents(File.join(gem_root, dir), output_dir)
26
27
  end
27
-
28
- def fetch_layout_repo(config, cloner)
29
- if config.has_option?('layout_repo')
30
- cloned_repo = cloner.call(source_repo_name: config.layout_repo,
31
- destination_parent_dir: Dir.mktmpdir)
32
- cloned_repo.path
33
- else
34
- File.absolute_path('master_middleman')
35
- end
36
- end
37
28
  end
38
29
  end
39
30
  end
@@ -0,0 +1,23 @@
1
+ module Bookbinder
2
+ module Commands
3
+ module BindComponents
4
+ class LayoutPreparer
5
+ def initialize(fs)
6
+ @fs = fs
7
+ end
8
+
9
+ attr_reader :fs
10
+
11
+ def prepare(output_locations, cloner, config)
12
+ if config.has_option?('layout_repo')
13
+ cloned_repo = cloner.call(source_repo_name: config.layout_repo,
14
+ destination_parent_dir: Dir.mktmpdir)
15
+
16
+ fs.copy_contents(cloned_repo.path, output_locations.site_generator_home)
17
+ end
18
+ fs.copy_contents(File.absolute_path('master_middleman'), output_locations.site_generator_home)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -19,8 +19,9 @@ require_relative '../postprocessing/sitemap_writer'
19
19
  require_relative '../preprocessing/dita_preprocessor'
20
20
  require_relative '../preprocessing/link_to_site_gen_dir'
21
21
  require_relative '../preprocessing/preprocessor'
22
+ require_relative '../subnav/subnav_generator_factory'
22
23
  require_relative '../sheller'
23
- require_relative '../subnav_formatter'
24
+ require_relative '../subnav/json_from_html'
24
25
  require_relative '../values/output_locations'
25
26
 
26
27
  module Bookbinder
@@ -87,7 +88,7 @@ module Bookbinder
87
88
  def bind
88
89
  @bind ||= Commands::Bind.new(
89
90
  streams,
90
- OutputLocations.new(final_app_dir: final_app_directory, context_dir: File.absolute_path('.')),
91
+ output_locations,
91
92
  configuration_fetcher,
92
93
  Config::ArchiveMenuConfiguration.new(loader: config_loader, config_filename: 'bookbinder.yml'),
93
94
  local_filesystem_accessor,
@@ -95,12 +96,12 @@ module Bookbinder
95
96
  Postprocessing::SitemapWriter.build(logger, final_app_directory, sitemap_port),
96
97
  Preprocessing::Preprocessor.new(
97
98
  Preprocessing::DitaPreprocessor.new(
98
- DitaHtmlToMiddlemanFormatter.new(local_filesystem_accessor, subnav_formatter, html_document_manipulator),
99
+ DitaHtmlToMiddlemanFormatter.new(local_filesystem_accessor, dita_json_generator, html_document_manipulator),
99
100
  local_filesystem_accessor,
100
101
  DitaCommandCreator.new(ENV['PATH_TO_DITA_OT_LIBRARY']),
101
102
  sheller
102
103
  ),
103
- Preprocessing::LinkToSiteGenDir.new(local_filesystem_accessor),
104
+ Preprocessing::LinkToSiteGenDir.new(local_filesystem_accessor, subnav_generator_factory)
104
105
  ),
105
106
  Ingest::ClonerFactory.new(streams, local_filesystem_accessor, version_control_system),
106
107
  Ingest::SectionRepository.new,
@@ -112,11 +113,11 @@ module Bookbinder
112
113
  @watch ||= Commands::Watch.new(
113
114
  streams,
114
115
  middleman_runner: runner,
115
- output_locations: OutputLocations.new(final_app_dir: final_app_directory, context_dir: File.absolute_path('.')),
116
+ output_locations: output_locations,
116
117
  config_fetcher: configuration_fetcher,
117
118
  config_decorator: Config::ArchiveMenuConfiguration.new(loader: config_loader, config_filename: 'bookbinder.yml'),
118
119
  file_system_accessor: local_filesystem_accessor,
119
- preprocessor: Preprocessing::Preprocessor.new(Preprocessing::LinkToSiteGenDir.new(local_filesystem_accessor)),
120
+ preprocessor: Preprocessing::Preprocessor.new(Preprocessing::LinkToSiteGenDir.new(local_filesystem_accessor, subnav_generator_factory)),
120
121
  cloner: local_file_system_cloner,
121
122
  section_repository: Ingest::SectionRepository.new,
122
123
  directory_preparer: directory_preparer
@@ -146,6 +147,7 @@ module Bookbinder
146
147
  Config::RemoteYamlCredentialProvider.new(logger, version_control_system)
147
148
  ).tap do |fetcher|
148
149
  fetcher.set_config_file_path './config.yml'
150
+ fetcher.set_config_dir_path './config/'
149
151
  end
150
152
  end
151
153
 
@@ -153,16 +155,28 @@ module Bookbinder
153
155
  @config_loader ||= Config::YAMLLoader.new
154
156
  end
155
157
 
158
+ def subnav_generator_factory
159
+ Subnav::SubnavGeneratorFactory.new(local_filesystem_accessor, output_locations)
160
+ end
161
+
162
+ def json_generator
163
+ Subnav::JsonFromConfig.new
164
+ end
165
+
156
166
  def directory_preparer
157
167
  Commands::BindComponents::DirectoryPreparer.new(local_filesystem_accessor)
158
168
  end
159
169
 
170
+ def output_locations
171
+ OutputLocations.new(final_app_dir: final_app_directory, context_dir: File.absolute_path('.'))
172
+ end
173
+
160
174
  def final_app_directory
161
175
  @final_app_directory ||= File.absolute_path('final_app')
162
176
  end
163
177
 
164
- def subnav_formatter
165
- @subnav_formatter ||= SubnavFormatter.new
178
+ def dita_json_generator
179
+ Subnav::JsonFromHtml.new
166
180
  end
167
181
 
168
182
  def html_document_manipulator
@@ -51,7 +51,8 @@ module Bookbinder
51
51
  preprocessor.preprocess(
52
52
  sections,
53
53
  output_locations,
54
- output_streams: streams
54
+ output_streams: streams,
55
+ config: watch_config
55
56
  )
56
57
  if file_system_accessor.file_exist?('redirects.rb')
57
58
  file_system_accessor.copy('redirects.rb', output_locations.final_app_dir)
@@ -0,0 +1,49 @@
1
+ module Bookbinder
2
+ module Config
3
+ module Checkers
4
+ class SubnavsChecker
5
+ MissingRequiredKeyError = Class.new(RuntimeError)
6
+ MissingSubnavsKeyError = Class.new(RuntimeError)
7
+ MissingSubnavNameError = Class.new(RuntimeError)
8
+
9
+ def check(config)
10
+ @config = config
11
+
12
+ if section_subnav_names.count > 0
13
+ if config.subnavs.empty?
14
+ MissingSubnavsKeyError.new("You must specify at least one subnav under the subnavs key in config.yml")
15
+ elsif missing_subnavs.count != 0
16
+ MissingSubnavNameError.new("Your config.yml is missing required subnav names under the subnavs key. Required subnav names are #{missing_subnavs.join(", ")}.")
17
+ elsif invalid_subnavs.any?
18
+ MissingRequiredKeyError.new("Your config.yml is missing required key(s) for subnavs #{invalid_subnav_names}. Required keys are #{required_subnav_keys.join(", ")}.")
19
+ end
20
+ end
21
+ end
22
+
23
+ attr_reader :config
24
+
25
+ private
26
+
27
+ def invalid_subnavs
28
+ config.subnavs.map {|subnav_config| subnav_config unless subnav_config.valid? }
29
+ end
30
+
31
+ def invalid_subnav_names
32
+ invalid_subnavs.map(&:name).join(", ")
33
+ end
34
+
35
+ def missing_subnavs
36
+ section_subnav_names - config.subnavs.map(&:name)
37
+ end
38
+
39
+ def required_subnav_keys
40
+ Bookbinder::Config::SubnavConfig::CONFIG_REQUIRED_KEYS
41
+ end
42
+
43
+ def section_subnav_names
44
+ config.sections.map(&:subnav_name).compact.uniq
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,37 @@
1
+ module Bookbinder
2
+ module Config
3
+ module Checkers
4
+ class TopicsChecker
5
+ MissingRequiredKeyError = Class.new(RuntimeError)
6
+
7
+ def check(config)
8
+ @config = config
9
+
10
+ if invalid_subnavs.any?
11
+ MissingRequiredKeyError.new("Your config.yml is missing required key(s) for subnav(s) #{invalid_subnav_names}. Required keys are #{required_topic_keys.join(", ")}.")
12
+ end
13
+ end
14
+
15
+ attr_reader :config
16
+
17
+ private
18
+
19
+ def invalid_subnavs
20
+ config.subnavs.select { |subnav_config| invalid_topics(subnav_config.topics).any? }
21
+ end
22
+
23
+ def invalid_topics(topics)
24
+ topics.map {|topic| topic unless topic.valid? }
25
+ end
26
+
27
+ def invalid_subnav_names
28
+ invalid_subnavs.map(&:name).join(', ')
29
+ end
30
+
31
+ def required_topic_keys
32
+ Bookbinder::Config::TopicConfig::CONFIG_REQUIRED_KEYS
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,6 +1,7 @@
1
1
  require_relative '../ingest/destination_directory'
2
2
  require_relative '../ingest/repo_identifier'
3
3
  require_relative 'section_config'
4
+ require_relative 'subnav_config'
4
5
 
5
6
  module Bookbinder
6
7
  module Config
@@ -65,10 +66,11 @@ module Bookbinder
65
66
 
66
67
  def initialize(config)
67
68
  @config = config
69
+ @subnavs = assemble_subnavs || []
68
70
  end
69
71
 
70
72
  CONFIG_REQUIRED_KEYS = %w(book_repo public_host)
71
- CONFIG_OPTIONAL_KEYS = %w(archive_menu book_repo_url cred_repo cred_repo_url layout_repo layout_repo_url sections)
73
+ CONFIG_OPTIONAL_KEYS = %w(archive_menu book_repo_url cred_repo cred_repo_url layout_repo layout_repo_url sections subnav_exclusions)
72
74
 
73
75
  CONFIG_REQUIRED_KEYS.each do |method_name|
74
76
  define_method(method_name) do
@@ -108,8 +110,19 @@ module Bookbinder
108
110
 
109
111
  alias_method :eql?, :==
110
112
 
113
+ attr_reader :subnavs
114
+
111
115
  private
112
116
 
117
+ def assemble_subnavs
118
+ if config[:subnavs]
119
+ config[:subnavs].map do |subnav|
120
+ subnav.merge!({'subnav_exclusions' => subnav_exclusions})
121
+ Config::SubnavConfig.new(subnav)
122
+ end
123
+ end
124
+ end
125
+
113
126
  attr_reader :config
114
127
  end
115
128
  end
@@ -13,7 +13,10 @@ module Bookbinder
13
13
  end
14
14
 
15
15
  def fetch_config
16
- @config ||= validate(read_config_file)
16
+ @base_config ||= read_config_file
17
+ @optional_configs ||= read_optional_configs
18
+
19
+ @config ||= validate(@base_config, @optional_configs)
17
20
  end
18
21
 
19
22
  def fetch_credentials(environment = 'null-environment')
@@ -29,13 +32,17 @@ module Bookbinder
29
32
  }
30
33
  end
31
34
 
32
- def set_config_file_path config_file_path
33
- @config_file_path = File.expand_path config_file_path
35
+ def set_config_dir_path(config_dir_path)
36
+ @config_dir_path = File.expand_path(config_dir_path)
37
+ end
38
+
39
+ def set_config_file_path(config_file_path)
40
+ @config_file_path = File.expand_path(config_file_path)
34
41
  end
35
42
 
36
43
  private
37
44
 
38
- attr_reader(:loader, :configuration_validator, :config, :config_file_path,
45
+ attr_reader(:loader, :configuration_validator, :config, :config_file_path, :config_dir_path,
39
46
  :credentials_provider)
40
47
 
41
48
  def read_config_file
@@ -44,17 +51,29 @@ module Bookbinder
44
51
  rescue FileNotFoundError => e
45
52
  raise "The configuration file specified does not exist. Please create a config #{e} file at #{config_file_path} and try again."
46
53
  rescue InvalidSyntaxError => e
47
- raise "There is a syntax error in your config file: \n #{e}"
54
+ raise syntax_error(e)
55
+ end
56
+
57
+ def read_optional_configs
58
+ Dir["#{config_dir_path}/*.yml"].map do |config_file|
59
+ loader.load(File.expand_path(config_file)) || {}
60
+ end.reduce({}, :merge)
61
+ rescue InvalidSyntaxError => e
62
+ raise syntax_error(e)
48
63
  end
49
64
 
50
- def validate(config_hash)
51
- raise 'Your config.yml appears to be empty. Please check and try again.' unless config_hash
65
+ def validate(base_hash, optional_hash)
66
+ raise 'Your config.yml appears to be empty. Please check and try again.' unless base_hash
52
67
 
53
- Configuration.parse(config_hash).tap do |config|
68
+ Configuration.parse(base_hash.merge(optional_hash)).tap do |config|
54
69
  errors = configuration_validator.exceptions(config)
55
70
  raise errors.first if errors.any?
56
71
  end
57
72
  end
73
+
74
+ def syntax_error(e)
75
+ "There is a syntax error in your config file: \n #{e}"
76
+ end
58
77
  end
59
78
  end
60
79
  end
@@ -11,6 +11,10 @@ module Bookbinder
11
11
  config['subnav_template']
12
12
  end
13
13
 
14
+ def subnav_name
15
+ config['subnav_name']
16
+ end
17
+
14
18
  def desired_directory_name
15
19
  config['directory']
16
20
  end
@@ -0,0 +1,41 @@
1
+ require_relative '../config/topic_config'
2
+
3
+ module Bookbinder
4
+ module Config
5
+ class SubnavConfig
6
+ def initialize(config)
7
+ @config = config
8
+ @topics = assemble_topics || []
9
+ end
10
+
11
+ def name
12
+ config['name']
13
+ end
14
+
15
+ def pdf_config
16
+ config['pdf_config']
17
+ end
18
+
19
+ def subnav_exclusions
20
+ config['subnav_exclusions'] || []
21
+ end
22
+
23
+ def valid?
24
+ (CONFIG_REQUIRED_KEYS - config.keys).empty?
25
+ end
26
+
27
+ CONFIG_REQUIRED_KEYS = %w(name topics)
28
+
29
+ attr_reader :topics
30
+
31
+ private
32
+
33
+ attr_reader :config
34
+
35
+ def assemble_topics
36
+ config['topics'].map{|topic| Config::TopicConfig.new(topic)} if config['topics']
37
+ end
38
+ end
39
+ end
40
+ end
41
+
@@ -0,0 +1,29 @@
1
+ module Bookbinder
2
+ module Config
3
+ class TopicConfig
4
+ def initialize(config)
5
+ @config = config
6
+ end
7
+
8
+ def title
9
+ config['title']
10
+ end
11
+
12
+ def toc_path
13
+ Pathname(config['toc_path'])
14
+ end
15
+
16
+ def toc_nav_name
17
+ config['toc_nav_name'] || title
18
+ end
19
+
20
+ def valid?
21
+ (CONFIG_REQUIRED_KEYS - config.keys).empty?
22
+ end
23
+
24
+ CONFIG_REQUIRED_KEYS = %w(title toc_path)
25
+
26
+ attr_reader :config
27
+ end
28
+ end
29
+ end
@@ -3,6 +3,8 @@ require_relative 'checkers/archive_menu_checker'
3
3
  require_relative 'checkers/required_keys_checker'
4
4
  require_relative 'checkers/repository_name_presence_checker'
5
5
  require_relative 'checkers/dita_section_checker'
6
+ require_relative 'checkers/subnavs_checker'
7
+ require_relative 'checkers/topics_checker'
6
8
 
7
9
  module Bookbinder
8
10
  module Config
@@ -17,7 +19,9 @@ module Bookbinder
17
19
  Checkers::DuplicateSectionNameChecker.new,
18
20
  Checkers::RepositoryNamePresenceChecker.new,
19
21
  Checkers::DitaSectionChecker.new,
20
- Checkers::ArchiveMenuChecker.new(@file_system_accessor)
22
+ Checkers::ArchiveMenuChecker.new(@file_system_accessor),
23
+ Checkers::SubnavsChecker.new,
24
+ Checkers::TopicsChecker.new
21
25
  ].map do |checker|
22
26
  checker.check(config)
23
27
  end
@@ -24,13 +24,13 @@ module Bookbinder
24
24
  write_to: write_to,
25
25
  dita_flags: dita_flags,
26
26
  ditamap_path: dita_section.path_to_preprocessor_attribute('ditamap_location'),
27
- ditaval_path: dita_section.path_to_preprocessor_attribute('ditaval_location'),
27
+ ditaval_path: dita_section.path_to_preprocessor_attribute('ditaval_location')
28
28
  )
29
29
  end
30
30
 
31
31
  private
32
32
 
33
- def unduplicated_flags(write_to: nil, ditamap_path: nil, ditaval_path: nil, dita_flags: dita_flags)
33
+ def unduplicated_flags(write_to: nil, ditamap_path: nil, ditaval_path: nil, dita_flags: nil)
34
34
  arg_flags = {
35
35
  'output.dir' => write_to,
36
36
  'args.input' => ditamap_path,
@@ -1,4 +1,4 @@
1
- require_relative 'values/subnav'
1
+ require_relative 'values/subnav_template'
2
2
 
3
3
  module Bookbinder
4
4
 
@@ -40,7 +40,7 @@ module Bookbinder
40
40
  selector: 'div.nav-content',
41
41
  attribute: 'data-props-location',
42
42
  value: json_props_location)
43
- Subnav.new(formatted_json_links, nav_text)
43
+ SubnavTemplate.new(formatted_json_links, nav_text)
44
44
  end
45
45
 
46
46
  private
@@ -1,5 +1,3 @@
1
- require 'tmpdir'
2
- require_relative '../deprecated_logger'
3
1
  require_relative '../values/section'
4
2
 
5
3
  module Bookbinder
@@ -21,6 +19,7 @@ module Bookbinder
21
19
  working_copy.full_name,
22
20
  section_config.desired_directory_name,
23
21
  section_config.subnav_template,
22
+ section_config.subnav_name,
24
23
  section_config.preprocessor_config
25
24
  )
26
25
  end
@@ -20,6 +20,11 @@ module Bookbinder
20
20
  to
21
21
  end
22
22
 
23
+ def overwrite(to: nil, text: nil)
24
+ File.delete(to) if file_exist?(to)
25
+ write(to: to, text: text)
26
+ end
27
+
23
28
  def read(path)
24
29
  File.read(path)
25
30
  end
@@ -80,5 +85,14 @@ module Bookbinder
80
85
  reject {|p| p.to_s.match %r{/\.}}.
81
86
  reject(&:directory?)
82
87
  end
88
+
89
+ def find_files_extension_agnostically(pattern, directory='.')
90
+ extensionless_pattern = pattern.to_s.split('.').first
91
+
92
+ `find -L #{directory} -path '*/#{extensionless_pattern}.*'`.
93
+ lines.
94
+ map(&:chomp).
95
+ map(&Pathname.method(:new))
96
+ end
83
97
  end
84
98
  end
@@ -1,4 +1,4 @@
1
- require_relative '../values/subnav'
1
+ require_relative '../values/subnav_template'
2
2
 
3
3
  module Bookbinder
4
4
  module Preprocessing
@@ -18,7 +18,7 @@ module Bookbinder
18
18
  section.subnav_template.include?('dita_subnav') if section.subnav_template
19
19
  end
20
20
 
21
- def preprocess(dita_sections, output_locations, options: [], output_streams: nil)
21
+ def preprocess(dita_sections, output_locations, options: [], output_streams: nil, **_)
22
22
  dita_options = dita_flags(options)
23
23
  dita_sections.each do |dita_section|
24
24
  if dita_section.path_to_preprocessor_attribute('ditamap_location')
@@ -1,26 +1,38 @@
1
+ require_relative '../subnav/subnav_generator'
2
+ require_relative '../subnav/json_from_config'
3
+
1
4
  module Bookbinder
2
5
  module Preprocessing
3
6
  class LinkToSiteGenDir
4
- def initialize(filesystem)
7
+ def initialize(filesystem, subnav_generator_factory)
5
8
  @filesystem = filesystem
9
+ @subnav_generator_factory = subnav_generator_factory
6
10
  end
7
11
 
8
12
  def applicable_to?(section)
9
13
  filesystem.file_exist?(section.path_to_repository)
10
14
  end
11
15
 
12
- def preprocess(sections, output_locations, *_)
16
+ def preprocess(sections, output_locations, config: nil, **_)
13
17
  sections.each do |section|
14
18
  filesystem.link_creating_intermediate_dirs(
15
19
  section.path_to_repository,
16
20
  output_locations.source_for_site_generator.join(section.destination_directory)
17
21
  )
18
22
  end
23
+
24
+ config.subnavs.each do |subnav|
25
+ subnav_generator.generate(subnav)
26
+ end
19
27
  end
20
28
 
21
29
  private
22
30
 
23
- attr_reader :filesystem
31
+ def subnav_generator
32
+ @subnav_generator ||= subnav_generator_factory.produce(Subnav::JsonFromConfig.new(filesystem))
33
+ end
34
+
35
+ attr_reader :filesystem, :subnav_generator_factory
24
36
  end
25
37
  end
26
38
  end
@@ -2,7 +2,7 @@ require 'puma'
2
2
 
3
3
  module Bookbinder
4
4
  class ServerDirector
5
- def initialize(app: app, directory: nil, port: 41722)
5
+ def initialize(app: nil, directory: nil, port: 41722)
6
6
  @app = app
7
7
  @directory = directory
8
8
  @port = port
@@ -0,0 +1,77 @@
1
+ require 'json'
2
+ require 'nokogiri'
3
+ require 'redcarpet'
4
+
5
+ module Bookbinder
6
+ module Subnav
7
+ class JsonFromConfig
8
+ def initialize(fs)
9
+ @fs = fs
10
+ @renderer = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new)
11
+ end
12
+
13
+ def get_links(subnav_config, source_for_site_gen)
14
+ @source_for_site_gen = source_for_site_gen
15
+ @config = subnav_config
16
+
17
+ { links: get_links_and_headers }.to_json
18
+ end
19
+
20
+ attr_reader :fs, :source_for_site_gen, :renderer, :config
21
+
22
+ private
23
+
24
+ def get_links_and_headers
25
+ menu_items = []
26
+
27
+ config.topics.map do |topic|
28
+ menu_items << { text: topic.title, title: true }
29
+ menu_items << { url: "/#{topic.toc_path}.html", text: topic.toc_nav_name }
30
+
31
+ links_from_toc_page = parse_toc_file(topic)
32
+ links_from_toc_page.each {|link| menu_items << link}
33
+ end
34
+
35
+ menu_items
36
+ end
37
+
38
+ def parse_toc_file(topic)
39
+ toc_files = fs.find_files_extension_agnostically(topic.toc_path, source_for_site_gen)
40
+ toc_md = fs.read(toc_files.first)
41
+
42
+ toc_html = get_html(toc_md)
43
+
44
+ gather_urls_and_texts(toc_html.css('html'), topic)
45
+ end
46
+
47
+ def get_html(md)
48
+ Nokogiri::HTML(renderer.render(md))
49
+ end
50
+
51
+ def gather_urls_and_texts(base_node, topic)
52
+ nav_items(base_node).map do |element|
53
+ if element.name == 'h2' && !frontmatter_header?(element)
54
+ {text: element.inner_html}
55
+ else
56
+ list_elements = element.css('li > a', 'li > p > a')
57
+ list_elements.map do |li|
58
+ { url: "/#{topic.toc_path.dirname.join(li['href'])}", text: li.inner_text }
59
+ end
60
+ end
61
+ end.flatten
62
+ end
63
+
64
+ def nav_items(base_node)
65
+ base_node.css('h2, ul') - base_node.css(*exclusions)
66
+ end
67
+
68
+ def exclusions
69
+ @exclusions ||= config.subnav_exclusions << '.nav-exclude'
70
+ end
71
+
72
+ def frontmatter_header?(element)
73
+ element.inner_html.include?('title: ')
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,38 @@
1
+ require 'nokogiri'
2
+ require 'active_support/all'
3
+
4
+ module Bookbinder
5
+ module Subnav
6
+ class JsonFromHtml
7
+ def get_links_as_json(raw_subnav_text, base_dirname)
8
+ doc = Nokogiri::XML(raw_subnav_text)
9
+
10
+ all_anchors = doc.css('a')
11
+ all_anchors.each do |anchor|
12
+ anchor['href'] = "/#{base_dirname}/#{anchor['href']}"
13
+ end
14
+
15
+ {
16
+ links: gather_urls_and_texts(doc.css('body > ul'))
17
+ }.to_json
18
+ end
19
+
20
+ private
21
+
22
+ def gather_urls_and_texts(base_node)
23
+ top_level_li = base_node.css("> li")
24
+ top_level_li.map do |li|
25
+ anchor = li.css('a')[0]
26
+ href = anchor['href']
27
+ text = anchor.inner_text
28
+ ul = li.css('> ul')
29
+ if ul.size > 0
30
+ {url: href, text: text, nestedLinks: gather_urls_and_texts(ul)}
31
+ else
32
+ {url: href, text: text}
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,35 @@
1
+ module Bookbinder
2
+ module Subnav
3
+ class JsonPropsCreator
4
+ def initialize(fs, output_locations, json_generator)
5
+ @fs = fs
6
+ @output_locations = output_locations
7
+ @json_generator = json_generator
8
+ end
9
+
10
+ def create(subnav_config)
11
+ json_links = json_generator.get_links(subnav_config, output_locations.source_for_site_generator)
12
+
13
+ fs.write(text: json_links, to: props_path(subnav_config.name))
14
+
15
+ filename(subnav_config.name)
16
+ end
17
+
18
+ attr_reader :fs, :output_locations, :json_generator
19
+
20
+ private
21
+
22
+ def filename(name)
23
+ "#{name}-props.json"
24
+ end
25
+
26
+ def subnavs_path
27
+ output_locations.subnavs_for_layout_dir
28
+ end
29
+
30
+ def props_path(name)
31
+ subnavs_path.join(filename(name))
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,52 @@
1
+ require 'yaml'
2
+ require "json"
3
+
4
+ module Bookbinder
5
+ module Subnav
6
+ class PdfConfigCreator
7
+ def initialize(fs, output_locations)
8
+ @fs = fs
9
+ @output_locations = output_locations
10
+ end
11
+
12
+ def create(props_filename, subnav_config)
13
+ json = JSON.parse(fs.read(props_location(props_filename)))
14
+ @links = format_links(json['links'])
15
+
16
+ fs.overwrite(to: output_locations.pdf_config_dir.join(subnav_config.pdf_config),
17
+ text: config_content)
18
+ end
19
+
20
+ attr_reader :fs, :output_locations
21
+
22
+ private
23
+
24
+ def props_location(filename)
25
+ output_locations.subnavs_for_layout_dir.join(filename)
26
+ end
27
+
28
+ def format_links(links)
29
+ links.map{|item| item['url'] }.compact.map{|link| link.sub(/^\//, '')}
30
+ end
31
+
32
+ def config_content
33
+ config_keys.inject({}) do |hash, key|
34
+ hash[key] = content_for(key)
35
+ hash
36
+ end.to_yaml
37
+ end
38
+
39
+ def config_keys
40
+ %w{copyright_notice header executable pages}
41
+ end
42
+
43
+ def content_for(key)
44
+ key == 'pages' ? @links : default_content
45
+ end
46
+
47
+ def default_content
48
+ 'REPLACE ME'
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,19 @@
1
+ module Bookbinder
2
+ module Subnav
3
+ class SubnavGenerator
4
+ def initialize(json_props_creator, template_creator, pdf_config_creator)
5
+ @json_props_creator = json_props_creator
6
+ @template_creator = template_creator
7
+ @pdf_config_creator = pdf_config_creator
8
+ end
9
+
10
+ def generate(subnav_config)
11
+ filename = json_props_creator.create(subnav_config)
12
+ template_creator.create(filename, subnav_config)
13
+ pdf_config_creator.create(filename, subnav_config) if subnav_config.pdf_config
14
+ end
15
+
16
+ attr_reader :json_props_creator, :template_creator, :pdf_config_creator
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+ require_relative 'json_props_creator'
2
+ require_relative 'template_creator'
3
+ require_relative 'pdf_config_creator'
4
+ require_relative '../html_document_manipulator'
5
+
6
+ module Bookbinder
7
+ module Subnav
8
+ class SubnavGeneratorFactory
9
+ def initialize(fs, output_locations)
10
+ @fs = fs
11
+ @output_locations = output_locations
12
+ end
13
+
14
+ def produce(json_generator)
15
+ SubnavGenerator.new(json_props_creator(json_generator), template_creator, pdf_config_creator)
16
+ end
17
+
18
+ attr_reader :fs, :output_locations
19
+
20
+ private
21
+
22
+ def json_props_creator(json_generator)
23
+ @json_props_creator ||= JsonPropsCreator.new(fs, output_locations, json_generator)
24
+ end
25
+
26
+ def template_creator
27
+ @template_creator ||= TemplateCreator.new(fs, output_locations, HtmlDocumentManipulator.new)
28
+ end
29
+
30
+ def pdf_config_creator
31
+ @pdf_config_creator ||= PdfConfigCreator.new(fs, output_locations)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,41 @@
1
+ module Bookbinder
2
+ module Subnav
3
+ class TemplateCreator
4
+ def initialize(fs, output_locations, html_doc_manipulator)
5
+ @fs = fs
6
+ @output_locations = output_locations
7
+ @html_doc_manipulator = html_doc_manipulator
8
+ end
9
+
10
+ def create(props_filename, subnav_config)
11
+ template_content = fs.read(template_path)
12
+ nav_content = html_doc_manipulator.set_attribute(document: template_content,
13
+ selector: 'div.nav-content',
14
+ attribute: 'data-props-location',
15
+ value: props_filename)
16
+
17
+ fs.write(text: nav_content, to: subnav_destination(subnav_config.name))
18
+ end
19
+
20
+ attr_reader :fs, :output_locations, :html_doc_manipulator
21
+
22
+ private
23
+
24
+ def subnavs_path
25
+ output_locations.subnavs_for_layout_dir
26
+ end
27
+
28
+ def filename(name)
29
+ "#{name}.erb"
30
+ end
31
+
32
+ def template_path
33
+ subnavs_path.join('subnav_template.erb')
34
+ end
35
+
36
+ def subnav_destination(name)
37
+ subnavs_path.join(filename(name))
38
+ end
39
+ end
40
+ end
41
+ end
@@ -62,6 +62,10 @@ module Bookbinder
62
62
  source_for_site_generator.join('subnavs')
63
63
  end
64
64
 
65
+ def pdf_config_dir
66
+ context_dir
67
+ end
68
+
65
69
  private
66
70
 
67
71
  def context_dir
@@ -6,6 +6,7 @@ module Bookbinder
6
6
  :full_name,
7
7
  :desired_directory_name,
8
8
  :subnav_templ,
9
+ :subnav_name,
9
10
  :preprocessor_config) do
10
11
  def path_to_repository
11
12
  Pathname(self[:path_to_repository].to_s)
@@ -21,7 +22,7 @@ module Bookbinder
21
22
 
22
23
  def subnav
23
24
  namespace = destination_directory.to_s.gsub('/', '_')
24
- template = subnav_template || 'default'
25
+ template = subnav_template || subnav_name || 'default'
25
26
  {namespace => template}
26
27
  end
27
28
 
@@ -1,4 +1,4 @@
1
1
  module Bookbinder
2
- Subnav = Struct.new(:json_links,
2
+ SubnavTemplate = Struct.new(:json_links,
3
3
  :text)
4
- end
4
+ end
@@ -15,6 +15,10 @@ module Bookbinder
15
15
  }
16
16
  end
17
17
 
18
+ def empty?
19
+ title.nil? && dropdown_links.empty?
20
+ end
21
+
18
22
  private
19
23
 
20
24
  attr_reader :config, :current_path
@@ -48,9 +48,10 @@ module Bookbinder
48
48
  config[:archive_menu],
49
49
  current_path: current_page.path
50
50
  )
51
-
52
- partial 'archive_menus/default', locals: { menu_title: menu.title,
53
- dropdown_links: menu.dropdown_links }
51
+ unless menu.empty?
52
+ partial 'archive_menus/default', locals: { menu_title: menu.title,
53
+ dropdown_links: menu.dropdown_links }
54
+ end
54
55
  end
55
56
 
56
57
  def modified_date(format="%B %-d, %Y")
File without changes
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bookbindery
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.0
4
+ version: 7.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Grafton
@@ -17,7 +17,7 @@ authors:
17
17
  autorequire:
18
18
  bindir: install_bin
19
19
  cert_chain: []
20
- date: 2015-09-08 00:00:00.000000000 Z
20
+ date: 2015-10-10 00:00:00.000000000 Z
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
23
23
  name: fog-aws
@@ -301,6 +301,7 @@ files:
301
301
  - lib/bookbinder/command_validator.rb
302
302
  - lib/bookbinder/commands/bind/bind_options.rb
303
303
  - lib/bookbinder/commands/bind/directory_preparer.rb
304
+ - lib/bookbinder/commands/bind/layout_preparer.rb
304
305
  - lib/bookbinder/commands/bind.rb
305
306
  - lib/bookbinder/commands/build_and_push_tarball.rb
306
307
  - lib/bookbinder/commands/chain.rb
@@ -322,10 +323,14 @@ files:
322
323
  - lib/bookbinder/config/checkers/duplicate_section_name_checker.rb
323
324
  - lib/bookbinder/config/checkers/repository_name_presence_checker.rb
324
325
  - lib/bookbinder/config/checkers/required_keys_checker.rb
326
+ - lib/bookbinder/config/checkers/subnavs_checker.rb
327
+ - lib/bookbinder/config/checkers/topics_checker.rb
325
328
  - lib/bookbinder/config/configuration.rb
326
329
  - lib/bookbinder/config/fetcher.rb
327
330
  - lib/bookbinder/config/remote_yaml_credential_provider.rb
328
331
  - lib/bookbinder/config/section_config.rb
332
+ - lib/bookbinder/config/subnav_config.rb
333
+ - lib/bookbinder/config/topic_config.rb
329
334
  - lib/bookbinder/config/validator.rb
330
335
  - lib/bookbinder/config/yaml_loader.rb
331
336
  - lib/bookbinder/css_link_checker.rb
@@ -370,11 +375,17 @@ files:
370
375
  - lib/bookbinder/spider.rb
371
376
  - lib/bookbinder/stabilimentum.rb
372
377
  - lib/bookbinder/streams/colorized_stream.rb
373
- - lib/bookbinder/subnav_formatter.rb
378
+ - lib/bookbinder/subnav/json_from_config.rb
379
+ - lib/bookbinder/subnav/json_from_html.rb
380
+ - lib/bookbinder/subnav/json_props_creator.rb
381
+ - lib/bookbinder/subnav/pdf_config_creator.rb
382
+ - lib/bookbinder/subnav/subnav_generator.rb
383
+ - lib/bookbinder/subnav/subnav_generator_factory.rb
384
+ - lib/bookbinder/subnav/template_creator.rb
374
385
  - lib/bookbinder/terminal.rb
375
386
  - lib/bookbinder/values/output_locations.rb
376
387
  - lib/bookbinder/values/section.rb
377
- - lib/bookbinder/values/subnav.rb
388
+ - lib/bookbinder/values/subnav_template.rb
378
389
  - lib/bookbinder/values/user_message.rb
379
390
  - template_app/app.rb
380
391
  - template_app/config.ru
@@ -385,6 +396,7 @@ files:
385
396
  - template_app/rack_app.rb
386
397
  - master_middleman/archive_drop_down_menu.rb
387
398
  - master_middleman/bookbinder_helpers.rb
399
+ - master_middleman/compass_runner.rb
388
400
  - master_middleman/config.rb
389
401
  - master_middleman/quicklinks_renderer.rb
390
402
  - master_middleman/subdirectory_aware_assets.rb
@@ -400,7 +412,7 @@ require_paths:
400
412
  - lib
401
413
  required_ruby_version: !ruby/object:Gem::Requirement
402
414
  requirements:
403
- - - ~>
415
+ - - '>='
404
416
  - !ruby/object:Gem::Version
405
417
  version: '2.0'
406
418
  required_rubygems_version: !ruby/object:Gem::Requirement
@@ -1,37 +0,0 @@
1
- require 'nokogiri'
2
- require 'active_support/all'
3
-
4
- module Bookbinder
5
- class SubnavFormatter
6
-
7
- def get_links_as_json(raw_subnav_text, base_dirname)
8
- doc = Nokogiri::XML(raw_subnav_text)
9
-
10
- all_anchors = doc.css('a')
11
- all_anchors.each do |anchor|
12
- anchor['href'] = "/#{base_dirname}/#{anchor['href']}"
13
- end
14
-
15
- {
16
- links: gather_urls_and_texts(doc.css('body > ul'))
17
- }.to_json
18
- end
19
-
20
- private
21
-
22
- def gather_urls_and_texts(base_node)
23
- top_level_li = base_node.css("> li")
24
- top_level_li.map do |li|
25
- anchor = li.css('a')[0]
26
- href = anchor['href']
27
- text = anchor.inner_text
28
- ul = li.css('> ul')
29
- if ul.size > 0
30
- {url: href, text: text, nestedLinks: gather_urls_and_texts(ul)}
31
- else
32
- {url: href, text: text}
33
- end
34
- end
35
- end
36
- end
37
- end