bookbindery 8.0.0 → 8.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1a0d33f4425ffc7d20e10ef7d4db181383434597
4
- data.tar.gz: 9e46a1a8c6a91b3c204b337c11db14b49b4297d6
3
+ metadata.gz: 6cb870420c8d260fe02e262c3d8db416a668a3fa
4
+ data.tar.gz: c2b4a7842bcdbcba9006096c7f5b4e08c04eff33
5
5
  SHA512:
6
- metadata.gz: 64b3f89db50a12afb1d050d5a993c630061a46c158113a318508546e825da51e1b66d0e96bf3da7c92f9276d781a2e69655f1b2adcad8cf61c98d638e50d9ccf
7
- data.tar.gz: a62cd292cbaa71a015a02d39f6cc88cdeab30f5f3837c72e05e874720b87c38dbc7b8f972e784680ab1b111b635cb8df042ccb0648afca9528ee41751d1e802a
6
+ metadata.gz: a45300da2301b2e37ea278f03d221d52592f9a6ad42429b349fe7169f75ae9b15aade134740f9c17a0effd101e807f00476f73970a8a4573ffea1a3ce0210e50
7
+ data.tar.gz: 1b3fa518acaa00628b2115d4c166df2272d08ca564416565a36e3d60aa69c82404d8dd97de84f2ed939eb2a04b608b11d8e4dfce8eadcb37f0d042f0a607c8e6
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 = '8.0.0'
5
+ s.version = '8.1.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', 'Michael Trestman']
@@ -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.3.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']
@@ -30,6 +30,7 @@ Gem::Specification.new do |s|
30
30
  s.add_runtime_dependency 'therubyracer'
31
31
  s.add_runtime_dependency 'git', '~> 1.2.8'
32
32
  s.add_runtime_dependency 'nokogiri', ['1.6.7.2']
33
+ s.add_runtime_dependency 'thor'
33
34
 
34
35
  s.add_development_dependency 'license_finder'
35
36
  s.add_development_dependency 'pry-byebug'
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require_relative '../lib/bookbinder/cli'
4
- require_relative '../lib/bookbinder/ingest/git_accessor'
5
4
 
6
- return_code = Bookbinder::Cli.new(Bookbinder::Ingest::GitAccessor.new).run ARGV
7
- exit return_code.to_i
5
+ Bookbinder::CLI.start
@@ -1,69 +1,98 @@
1
- require_relative 'command_runner'
2
- require_relative 'command_validator'
3
- require_relative 'commands/collection'
4
- require_relative 'config/cf_credentials'
5
- require_relative 'streams/colorized_stream'
6
- require_relative 'terminal'
1
+ require 'thor'
2
+
3
+ require_relative 'ingest/git_accessor'
4
+ require_relative 'legacy/cli'
7
5
 
8
6
  module Bookbinder
9
- class Cli
10
- def initialize(version_control_system)
11
- @version_control_system = version_control_system
7
+ class CLI < Thor
8
+ map '--version' => :version
9
+ map '--help' => :help
10
+
11
+ desc '--version', 'Print the version of bookbinder'
12
+ def version
13
+ gemspec = File.expand_path('../../../bookbinder.gemspec', __FILE__)
14
+ say "bookbinder #{Gem::Specification::load(gemspec).version}"
15
+ end
16
+
17
+ desc '--help', 'Print this message'
18
+ def help
19
+ super
12
20
  end
13
21
 
14
- def run(args)
15
- command_name, *command_arguments = args
16
-
17
- logger = DeprecatedLogger.new
18
- commands = Commands::Collection.new(
19
- logger,
20
- colorized_streams,
21
- version_control_system
22
- )
23
-
24
- command_validator = CommandValidator.new(commands, commands.help.usage_message)
25
- command_runner = CommandRunner.new(logger, commands)
26
- command_name = command_name ? command_name : '--help'
27
-
28
- colorizer = Colorizer.new
29
- terminal = Terminal.new(colorizer)
30
-
31
- user_message = command_validator.validate(command_name)
32
- terminal.update(user_message)
33
-
34
- if user_message.error?
35
- return 1
36
- end
37
-
38
- begin
39
- command_runner.run(command_name, command_arguments)
40
-
41
- rescue Config::CfCredentials::CredentialKeyError => e
42
- colorized_streams[:err].puts "#{e.message}, in credentials.yml"
43
- 1
44
- rescue KeyError => e
45
- colorized_streams[:err].puts "#{e.message} from your configuration."
46
- 1
47
- rescue CliError::UnknownCommand => e
48
- colorized_streams[:out].puts e.message
49
- 1
50
- rescue RuntimeError => e
51
- colorized_streams[:err].puts e.message
52
- 1
53
- end
22
+ desc 'generate <book_name>', 'Generate a skeleton book that can be bound with "bookbinder bind"'
23
+ def generate(book_name)
24
+ run_legacy_cli('generate', book_name)
25
+ end
26
+
27
+ desc 'build_and_push_tarball', 'Create a tarball from the final_app directory and push to the S3 bucket specified in your credentials.yml'
28
+ def build_and_push_tarball
29
+ run_legacy_cli('build_and_push_tarball')
30
+ end
31
+
32
+ desc 'bind <local|remote> [--verbose] [--dita-flags=\"<dita-option>=<value>\"]', 'Bind the sections specified in config.yml from <local> or <remote> into the final_app directory'
33
+ option :verbose, type: :boolean
34
+ option 'dita-flags'
35
+ def bind(source)
36
+ args = ['bind', source]
37
+ args << '--verbose' if options[:verbose]
38
+ args << "--dita-flags=\\\"#{options['dita-flags']}\\\""
39
+ run_legacy_cli(*args)
40
+ end
41
+
42
+ desc 'push_local_to <environment>', 'Push the contents of final_app to the specified environment using the credentials.yml'
43
+ def push_local_to(environment)
44
+ run_legacy_cli('push_local_to', environment)
45
+ end
46
+
47
+ desc 'push_to_prod [build_#]', 'Push latest or <build_#> from your S3 bucket to the production host specified in credentials.yml'
48
+ def push_to_prod(build_num=nil)
49
+ args = ['push_to_prod', build_num].compact
50
+ run_legacy_cli(*args)
51
+ end
52
+
53
+ desc 'run_publish_ci', 'Run publish, push_local_to staging, and build_and_push_tarball for CI purposes'
54
+ def run_publish_ci
55
+ run_legacy_cli('run_publish_ci')
56
+ end
57
+
58
+ desc 'punch <git tag>', 'Apply the specified <git tag> to your book, sections, and layout repo'
59
+ def punch(git_tag)
60
+ run_legacy_cli('punch', git_tag)
61
+ end
62
+
63
+ desc 'update_local_doc_repos', 'Run `git pull` on all sections that exist at the same directory level as your book directory'
64
+ def update_local_doc_repos
65
+ run_legacy_cli('update_local_doc_repos')
66
+ end
67
+
68
+ desc 'watch', 'Bind and serve a local book, watching for changes'
69
+ def watch
70
+ run_legacy_cli('watch')
71
+ end
72
+
73
+ desc 'imprint <local|remote> [--verbose] [--dita-flags=\"<dita-option>=<value>\"]', 'Generate a PDF for a given book'
74
+ option :verbose, type: :boolean
75
+ option 'dita-flags'
76
+ def imprint(source)
77
+ args = ['imprint', source]
78
+ args << '--verbose' if options[:verbose]
79
+ args << "--dita-flags=\\\"#{options['dita-flags']}\\\""
80
+ run_legacy_cli(*args)
54
81
  end
55
82
 
56
83
  private
57
84
 
58
- attr_reader :version_control_system
85
+ attr_reader :legacy_cli
86
+
87
+ def initialize(*)
88
+ super
89
+
90
+ @legacy_cli = Legacy::Cli.new(Ingest::GitAccessor.new)
91
+ end
59
92
 
60
- def colorized_streams
61
- {
62
- err: Streams::ColorizedStream.new(Colorizer::Colors.red, $stderr),
63
- out: $stdout,
64
- success: Streams::ColorizedStream.new(Colorizer::Colors.green, $stdout),
65
- warn: Streams::ColorizedStream.new(Colorizer::Colors.yellow, $stdout),
66
- }
93
+ def run_legacy_cli(*args)
94
+ status = legacy_cli.run(args)
95
+ exit status unless status.zero?
67
96
  end
68
97
  end
69
98
  end
@@ -14,7 +14,7 @@ module Bookbinder
14
14
  config_decorator: nil,
15
15
  file_system_accessor: nil,
16
16
  middleman_runner: nil,
17
- sitemap_writer: nil,
17
+ broken_links_checker: nil,
18
18
  preprocessor: nil,
19
19
  cloner_factory: nil,
20
20
  section_repository: nil,
@@ -26,7 +26,7 @@ module Bookbinder
26
26
  @config_decorator = config_decorator
27
27
  @file_system_accessor = file_system_accessor
28
28
  @middleman_runner = middleman_runner
29
- @sitemap_writer = sitemap_writer
29
+ @broken_links_checker = broken_links_checker
30
30
  @preprocessor = preprocessor
31
31
  @cloner_factory = cloner_factory
32
32
  @section_repository = section_repository
@@ -85,15 +85,13 @@ module Bookbinder
85
85
  )
86
86
  if generation_result.success?
87
87
  file_system_accessor.copy(output_locations.build_dir, output_locations.public_dir)
88
- result = sitemap_writer.write(
89
- bind_config.public_host,
90
- bind_options.streams,
91
- bind_config.broken_link_exclusions
92
- )
88
+
89
+ broken_links_checker.check!(bind_config.broken_link_exclusions)
90
+ broken_links_checker.announce(bind_options.streams)
93
91
 
94
92
  bind_options.streams[:success].puts "Bookbinder bound your book into #{output_locations.final_app_dir}"
95
93
 
96
- result.has_broken_links? ? 1 : 0
94
+ broken_links_checker.has_broken_links? ? 1 : 0
97
95
  else
98
96
  bind_options.streams[:err].puts "Your bind failed. Rerun with --verbose to troubleshoot."
99
97
  1
@@ -113,7 +111,7 @@ module Bookbinder
113
111
  :output_locations,
114
112
  :preprocessor,
115
113
  :section_repository,
116
- :sitemap_writer,
114
+ :broken_links_checker,
117
115
  :middleman_runner,
118
116
  )
119
117
 
@@ -18,7 +18,7 @@ require_relative '../ingest/cloner_factory'
18
18
  require_relative '../ingest/section_repository'
19
19
  require_relative '../local_filesystem_accessor'
20
20
  require_relative '../middleman_runner'
21
- require_relative '../postprocessing/sitemap_writer'
21
+ require_relative '../postprocessing/broken_links_checker'
22
22
  require_relative '../preprocessing/dita_html_preprocessor'
23
23
  require_relative '../preprocessing/dita_pdf_preprocessor'
24
24
  require_relative '../preprocessing/link_to_site_gen_dir'
@@ -97,7 +97,7 @@ module Bookbinder
97
97
  config_decorator: Config::ConfigurationDecorator.new(loader: config_loader, config_filename: 'bookbinder.yml'),
98
98
  file_system_accessor: local_filesystem_accessor,
99
99
  middleman_runner: runner,
100
- sitemap_writer: Postprocessing::SitemapWriter.build(logger, final_app_directory, sitemap_port),
100
+ broken_links_checker: Postprocessing::BrokenLinksChecker.build(final_app_directory, sitemap_port),
101
101
  preprocessor: Preprocessing::Preprocessor.new(
102
102
  Preprocessing::DitaHTMLPreprocessor.new(
103
103
  local_filesystem_accessor,
@@ -2,7 +2,6 @@ module Bookbinder
2
2
  module Config
3
3
  module Checkers
4
4
  class ProductsChecker
5
- MissingRequiredKeyError = Class.new(RuntimeError)
6
5
  MissingProductsKeyError = Class.new(RuntimeError)
7
6
  MissingProductIdError = Class.new(RuntimeError)
8
7
 
@@ -11,11 +10,9 @@ module Bookbinder
11
10
 
12
11
  if section_product_ids.count > 0
13
12
  if config.products.empty?
14
- MissingProductsKeyError.new('You must specify at least one product under the products key in config.yml')
13
+ MissingProductsKeyError.new('You must specify at least one product under the products key in config.yml')
15
14
  elsif missing_products.count != 0
16
15
  MissingProductIdError.new("Your config.yml is missing required product id under the products key. Required product ids are #{missing_products.join(", ")}.")
17
- elsif invalid_products.any?
18
- MissingRequiredKeyError.new("Your config.yml is missing required key(s) for products #{invalid_product_ids}. Required keys are #{required_product_keys.join(", ")}.")
19
16
  end
20
17
  end
21
18
  end
@@ -24,22 +21,10 @@ module Bookbinder
24
21
 
25
22
  private
26
23
 
27
- def invalid_products
28
- config.products.map {|product_config| product_config unless product_config.valid? }
29
- end
30
-
31
- def invalid_product_ids
32
- invalid_products.map(&:id).join(', ')
33
- end
34
-
35
24
  def missing_products
36
25
  section_product_ids - config.products.map(&:id)
37
26
  end
38
27
 
39
- def required_product_keys
40
- Bookbinder::Config::ProductConfig::CONFIG_REQUIRED_KEYS
41
- end
42
-
43
28
  def section_product_ids
44
29
  config.sections.map(&:product_id).compact.uniq
45
30
  end
@@ -57,7 +57,7 @@ module Bookbinder
57
57
  end
58
58
 
59
59
  CONFIG_REQUIRED_KEYS = %w(book_repo public_host)
60
- CONFIG_OPTIONAL_KEYS = %w(archive_menu book_repo_url cred_repo cred_repo_url repo_link_enabled repo_links feedback_enabled layout_repo layout_repo_ref layout_repo_url sections subnav_exclusions)
60
+ CONFIG_OPTIONAL_KEYS = %w(archive_menu book_repo_url cred_repo cred_repo_url repo_link_enabled repo_links feedback_enabled layout_repo layout_repo_ref layout_repo_url sections)
61
61
 
62
62
  CONFIG_REQUIRED_KEYS.each do |method_name|
63
63
  define_method(method_name) do
@@ -104,7 +104,6 @@ module Bookbinder
104
104
  def assemble_products
105
105
  if config[:products]
106
106
  config[:products].map do |product|
107
- product.merge!({'subnav_exclusions' => subnav_exclusions})
108
107
  Config::ProductConfig.new(product)
109
108
  end
110
109
  end
@@ -1,11 +1,8 @@
1
- require_relative '../config/topic_config'
2
-
3
1
  module Bookbinder
4
2
  module Config
5
3
  class ProductConfig
6
4
  def initialize(config)
7
5
  @config = config
8
- @subnav_topics = assemble_topics || []
9
6
  end
10
7
 
11
8
  def id
@@ -16,30 +13,21 @@ module Bookbinder
16
13
  config['pdf_config']
17
14
  end
18
15
 
19
- def subnav_exclusions
20
- config['subnav_exclusions'] || []
21
- end
22
-
23
- def specifies_subnav?
24
- !subnav_topics.empty?
16
+ def subnav_root
17
+ config['subnav_root']
25
18
  end
26
19
 
27
20
  def valid?
28
21
  (CONFIG_REQUIRED_KEYS - config.keys).empty?
29
22
  end
30
23
 
31
- CONFIG_REQUIRED_KEYS = %w(id subnav_topics)
24
+ CONFIG_REQUIRED_KEYS = %w(id)
32
25
 
33
- attr_reader :subnav_topics
34
26
  alias_method :subnav_name, :id
35
27
 
36
28
  private
37
29
 
38
30
  attr_reader :config
39
-
40
- def assemble_topics
41
- config['subnav_topics'].map{|topic| Config::TopicConfig.new(topic)} if config['subnav_topics']
42
- end
43
31
  end
44
32
  end
45
33
  end
@@ -5,7 +5,6 @@ require_relative 'checkers/repository_name_presence_checker'
5
5
  require_relative 'checkers/ditamap_presence_checker'
6
6
  require_relative 'checkers/section_presence_checker'
7
7
  require_relative 'checkers/products_checker'
8
- require_relative 'checkers/subnav_topics_checker'
9
8
 
10
9
  module Bookbinder
11
10
  module Config
@@ -22,8 +21,7 @@ module Bookbinder
22
21
  Checkers::SectionPresenceChecker.new,
23
22
  Checkers::DitamapPresenceChecker.new,
24
23
  Checkers::ArchiveMenuChecker.new(@file_system_accessor),
25
- Checkers::ProductsChecker.new,
26
- Checkers::SubnavTopicsChecker.new
24
+ Checkers::ProductsChecker.new
27
25
  ].map do |checker|
28
26
  checker.check(config)
29
27
  end
@@ -12,13 +12,6 @@ module Bookbinder
12
12
  doc.to_html
13
13
  end
14
14
 
15
- def add_class(document: nil, selector: nil, classname: nil)
16
- doc = Nokogiri::HTML.fragment(document)
17
- node_set = doc.css(selector)
18
- node_set.add_class(classname)
19
- doc.to_html
20
- end
21
-
22
15
  def read_html_in_tag(document: nil, tag: nil)
23
16
  doc = Nokogiri::HTML(document)
24
17
  doc.css(tag).inner_html
@@ -0,0 +1,71 @@
1
+ require_relative '../command_runner'
2
+ require_relative '../command_validator'
3
+ require_relative '../commands/collection'
4
+ require_relative '../config/cf_credentials'
5
+ require_relative '../streams/colorized_stream'
6
+ require_relative '../terminal'
7
+
8
+ module Bookbinder
9
+ module Legacy
10
+ class Cli
11
+ def initialize(version_control_system)
12
+ @version_control_system = version_control_system
13
+ end
14
+
15
+ def run(args)
16
+ command_name, *command_arguments = args
17
+
18
+ logger = DeprecatedLogger.new
19
+ commands = Commands::Collection.new(
20
+ logger,
21
+ colorized_streams,
22
+ version_control_system
23
+ )
24
+
25
+ command_validator = CommandValidator.new(commands, commands.help.usage_message)
26
+ command_runner = CommandRunner.new(logger, commands)
27
+ command_name = command_name ? command_name : '--help'
28
+
29
+ colorizer = Colorizer.new
30
+ terminal = Terminal.new(colorizer)
31
+
32
+ user_message = command_validator.validate(command_name)
33
+ terminal.update(user_message)
34
+
35
+ if user_message.error?
36
+ return 1
37
+ end
38
+
39
+ begin
40
+ command_runner.run(command_name, command_arguments)
41
+
42
+ rescue Config::CfCredentials::CredentialKeyError => e
43
+ colorized_streams[:err].puts "#{e.message}, in credentials.yml"
44
+ 1
45
+ rescue KeyError => e
46
+ colorized_streams[:err].puts "#{e.message} from your configuration."
47
+ 1
48
+ rescue CliError::UnknownCommand => e
49
+ colorized_streams[:out].puts e.message
50
+ 1
51
+ rescue RuntimeError => e
52
+ colorized_streams[:err].puts e.message
53
+ 1
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ attr_reader :version_control_system
60
+
61
+ def colorized_streams
62
+ {
63
+ err: Streams::ColorizedStream.new(Colorizer::Colors.red, $stderr),
64
+ out: $stdout,
65
+ success: Streams::ColorizedStream.new(Colorizer::Colors.green, $stdout),
66
+ warn: Streams::ColorizedStream.new(Colorizer::Colors.yellow, $stdout),
67
+ }
68
+ end
69
+ end
70
+ end
71
+ end
@@ -4,8 +4,8 @@ require_relative '../../../template_app/rack_app'
4
4
 
5
5
  module Bookbinder
6
6
  module Postprocessing
7
- class SitemapWriter
8
- def self.build(logger, final_app_directory, port)
7
+ class BrokenLinksChecker
8
+ def self.build(final_app_directory, port)
9
9
  new(
10
10
  Spider.new(app_dir: final_app_directory),
11
11
  ServerDirector.new(
@@ -21,17 +21,18 @@ module Bookbinder
21
21
  @server_director = server_director
22
22
  end
23
23
 
24
- def write(host_for_sitemap, streams, broken_link_exclusions)
24
+ def check!(broken_link_exclusions)
25
25
  server_director.use_server { |port|
26
- spider.generate_sitemap(
27
- host_for_sitemap,
28
- port,
29
- streams,
30
- broken_link_exclusions: broken_link_exclusions
31
- )
32
- }.tap do |sitemap|
33
- File.write(sitemap.to_path, sitemap.to_xml)
34
- end
26
+ @result = spider.find_broken_links(port, broken_link_exclusions: broken_link_exclusions)
27
+ }
28
+ end
29
+
30
+ def announce(streams)
31
+ @result.announce_broken_links(streams)
32
+ end
33
+
34
+ def has_broken_links?
35
+ @result.has_broken_links?
35
36
  end
36
37
 
37
38
  private
@@ -3,27 +3,31 @@ require 'pty'
3
3
  require_relative 'css_link_checker'
4
4
  require_relative 'sieve'
5
5
  require_relative 'stabilimentum'
6
- require_relative 'sitemap_generator'
7
6
 
8
7
  module Bookbinder
9
8
  class Spider
10
9
  class Result
11
- def initialize(broken_links, sitemap, app_dir)
10
+ def initialize(broken_links)
12
11
  @broken_links = broken_links
13
- @sitemap = sitemap
14
- @app_dir = app_dir
15
12
  end
16
13
 
17
14
  def has_broken_links?
18
15
  @broken_links.any?
19
16
  end
20
17
 
21
- def to_xml
22
- @sitemap
23
- end
18
+ def announce_broken_links(streams)
19
+ if @broken_links.none?
20
+ streams[:out].puts "\nNo broken links!"
21
+ else
22
+ streams[:err].puts(<<-MESSAGE)
23
+
24
+ Found #{@broken_links.count} broken links!
24
25
 
25
- def to_path
26
- Pathname(@app_dir).join('public/sitemap.xml')
26
+ #{@broken_links.sort.join("\n")}
27
+
28
+ Found #{@broken_links.count} broken links!
29
+ MESSAGE
30
+ end
27
31
  end
28
32
  end
29
33
 
@@ -35,41 +39,20 @@ module Bookbinder
35
39
  @app_dir = app_dir || raise('Spiders must be initialized with an app directory.')
36
40
  end
37
41
 
38
- def generate_sitemap(target_host, port, streams,
39
- broken_link_exclusions: /(?!.*)/)
42
+ def find_broken_links(port, broken_link_exclusions: /(?!.*)/)
40
43
  temp_host = "localhost:#{port}"
41
-
42
44
  sieve = Sieve.new domain: "http://#{temp_host}"
43
45
  links = crawl_from "http://#{temp_host}/index.html", sieve
44
- broken_links, working_links = links
45
- sitemap_links = substitute_hostname(temp_host, target_host, working_links)
46
+ broken_links = links.first
46
47
  public_broken_links = broken_links.reject {|l| l.match(broken_link_exclusions)}
47
- announce_broken_links(public_broken_links, streams)
48
- Result.new(
49
- public_broken_links,
50
- SitemapGenerator.new.generate(sitemap_links), app_dir
51
- )
48
+
49
+ Result.new(public_broken_links)
52
50
  end
53
51
 
54
52
  private
55
53
 
56
54
  attr_reader :app_dir
57
55
 
58
- def announce_broken_links(broken_links, streams)
59
- if broken_links.none?
60
- streams[:out].puts "\nNo broken links!"
61
- else
62
- streams[:err].puts(<<-MESSAGE)
63
-
64
- Found #{broken_links.count} broken links!
65
-
66
- #{broken_links.sort.join("\n")}
67
-
68
- Found #{broken_links.count} broken links!
69
- MESSAGE
70
- end
71
- end
72
-
73
56
  def crawl_from(url, sieve)
74
57
  broken_links = []
75
58
  sitemap = [url]
@@ -79,7 +62,7 @@ Found #{broken_links.count} broken links!
79
62
  Anemone.crawl(url) do |anemone|
80
63
  dont_visit_fragments(anemone)
81
64
  anemone.on_every_page do |page|
82
- broken, working = sieve.links_from Stabilimentum.new(page), is_first_pass
65
+ broken, working = sieve.links_from(Stabilimentum.new(page), is_first_pass)
83
66
  broken_links.concat broken
84
67
  sitemap.concat working
85
68
  end
@@ -93,9 +76,5 @@ Found #{broken_links.count} broken links!
93
76
  def dont_visit_fragments(anemone)
94
77
  anemone.focus_crawl { |page| page.links.reject { |link| link.to_s.match(/%23/) } }
95
78
  end
96
-
97
- def substitute_hostname(temp_host, target_host, links)
98
- links.map { |l| l.gsub(/#{Regexp.escape(temp_host)}/, target_host) }
99
- end
100
79
  end
101
80
  end
@@ -5,72 +5,79 @@ require 'redcarpet'
5
5
  module Bookbinder
6
6
  module Subnav
7
7
  class JsonFromMarkdownToc
8
+ SubnavDuplicateLinkError = Class.new(RuntimeError)
9
+ SubnavBrokenLinkError = Class.new(RuntimeError)
10
+ SubnavRootMissingError = Class.new(RuntimeError)
11
+
8
12
  def initialize(fs)
9
13
  @fs = fs
10
14
  @renderer = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new)
11
15
  end
12
16
 
13
- def get_links(subnav_config, output_locations)
17
+ def get_links(product_config, output_locations)
14
18
  @source_for_site_gen = output_locations.source_for_site_generator
15
- @config = subnav_config
19
+ @config = product_config
16
20
 
17
- { links: get_links_and_headers }.to_json
18
- end
21
+ root = absolute_source_from_path(Pathname(config.subnav_root))
19
22
 
20
- attr_reader :fs, :source_for_site_gen, :renderer, :config
21
-
22
- private
23
+ raise SubnavRootMissingError.new('Subnav root not found at: ' + config.subnav_root) if root.nil?
23
24
 
24
- def get_links_and_headers
25
- menu_items = []
25
+ @parsed_files = { Pathname(root) => '(root)'}
26
26
 
27
- config.subnav_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
27
+ {links: gather_urls_and_texts(root)}.to_json
36
28
  end
37
29
 
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)
30
+ attr_reader :fs, :source_for_site_gen, :renderer, :config
43
31
 
44
- gather_urls_and_texts(toc_html.css('html'), topic)
45
- end
32
+ private
46
33
 
47
34
  def get_html(md)
48
35
  Nokogiri::HTML(renderer.render(md))
49
36
  end
50
37
 
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
38
+ def absolute_source_from_path(path)
39
+ full_sources = fs.find_files_extension_agnostically(path, source_for_site_gen)
40
+ full_sources.first
62
41
  end
63
42
 
64
- def nav_items(base_node)
65
- base_node.css('h2, ul') - base_node.css(*exclusions)
66
- end
43
+ # href: ./cat/index.html
44
+ # expanded href: my/cat/index.html
45
+ # full source: my/cat/index.html.md.erb
46
+ def gather_urls_and_texts(source)
47
+ toc_md = fs.read(source)
48
+ base_node = get_html(toc_md).css('html')
67
49
 
68
- def exclusions
69
- @exclusions ||= config.subnav_exclusions << '.nav-exclude'
50
+ nav_items(base_node).map do |element|
51
+ href = element['href']
52
+ expanded_href = (source.dirname + href).relative_path_from(source_for_site_gen)
53
+ next_source = absolute_source_from_path(expanded_href)
54
+
55
+ raise SubnavBrokenLinkError.new(<<-ERROR) unless next_source
56
+ Broken link found in subnav for product_id: #{config.id}
57
+
58
+ Link: #{expanded_href}
59
+ Source file: #{source}
60
+ ERROR
61
+ raise SubnavDuplicateLinkError.new(<<-ERROR) if @parsed_files.has_key?(next_source)
62
+ )
63
+ Duplicate link found in subnav for product_id: #{config.id}
64
+
65
+ Link: #{expanded_href}
66
+ Original file: #{@parsed_files[next_source]}
67
+ Current file: #{source}
68
+ ERROR
69
+
70
+ @parsed_files[next_source] = source
71
+ nested_urls_and_texts = gather_urls_and_texts(next_source)
72
+ nested_links = {}
73
+ nested_links.merge!(nestedLinks: nested_urls_and_texts) unless nested_urls_and_texts.empty?
74
+
75
+ {url: '/' + expanded_href.to_s, text: element.inner_text}.merge(nested_links)
76
+ end
70
77
  end
71
78
 
72
- def frontmatter_header?(element)
73
- element.inner_html.include?('title: ')
79
+ def nav_items(base_node)
80
+ base_node.css('a.subnav')
74
81
  end
75
82
  end
76
83
  end
@@ -18,21 +18,13 @@ module Bookbinder
18
18
  attribute: 'data-props-location',
19
19
  value: props_filename)
20
20
 
21
- nav_content = html_doc_manipulator.add_class(document: nav_with_props,
22
- selector: 'div.nav-content',
23
- classname: nav_type(subnav_spec))
24
-
25
- fs.write(text: nav_content, to: subnav_destination(subnav_spec.subnav_name))
21
+ fs.write(text: nav_with_props, to: subnav_destination(subnav_spec.subnav_name))
26
22
  end
27
23
 
28
24
  attr_reader :fs, :output_locations, :html_doc_manipulator
29
25
 
30
26
  private
31
27
 
32
- def nav_type(subnav_spec)
33
- subnav_spec.subnav_name.include?('dita') ? 'deepnav-content' : 'shallownav-content'
34
- end
35
-
36
28
  def subnavs_path
37
29
  output_locations.subnavs_for_layout_dir
38
30
  end
@@ -110,6 +110,13 @@ module Bookbinder
110
110
  Redcarpet::Markdown.new(quicklinks_renderer).render(page_src)
111
111
  end
112
112
 
113
+ def owners
114
+ html_resources = sitemap.resources.select { |r| r.path.end_with?('.html') }
115
+ html_resources.each.with_object({}) { |resource, owners|
116
+ owners[resource.path] = Array(resource.data['owner'])
117
+ }
118
+ end
119
+
113
120
  private
114
121
 
115
122
  def subnav_template_name
@@ -23,6 +23,8 @@ set :images_dir, 'images'
23
23
 
24
24
  set :relative_links, false
25
25
 
26
+ page '/owners.json', :layout => false
27
+
26
28
  activate :subdirectory_aware_assets
27
29
 
28
30
  activate :navigation
data/template_app/Gemfile CHANGED
@@ -1,6 +1,6 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
- ruby '2.0.0'
3
+ ruby '2.3.0'
4
4
 
5
5
  gem 'vienna'
6
6
  gem 'rack-rewrite'
@@ -15,7 +15,10 @@ module Bookbinder
15
15
  urls: Dir.glob("#{root}/**/*").map { |fn| fn.gsub(/^#{root}/, '')},
16
16
  root: root,
17
17
  index: 'index.html',
18
- header_rules: [[:all, {'Cache-Control' => 'public, max-age=3600'}]]
18
+ header_rules: [[:all, {
19
+ 'Cache-Control' => 'public, max-age=3600',
20
+ 'Access-Control-Allow-Origin' => '*'
21
+ }]]
19
22
  }
20
23
  run NotFound.new("#{root}/404.html")
21
24
  end
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: 8.0.0
4
+ version: 8.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Grafton
@@ -18,7 +18,7 @@ authors:
18
18
  autorequire:
19
19
  bindir: install_bin
20
20
  cert_chain: []
21
- date: 2016-02-23 00:00:00.000000000 Z
21
+ date: 2016-03-08 00:00:00.000000000 Z
22
22
  dependencies:
23
23
  - !ruby/object:Gem::Dependency
24
24
  name: fog-aws
@@ -230,6 +230,20 @@ dependencies:
230
230
  - - '='
231
231
  - !ruby/object:Gem::Version
232
232
  version: 1.6.7.2
233
+ - !ruby/object:Gem::Dependency
234
+ name: thor
235
+ requirement: !ruby/object:Gem::Requirement
236
+ requirements:
237
+ - - ">="
238
+ - !ruby/object:Gem::Version
239
+ version: '0'
240
+ type: :runtime
241
+ prerelease: false
242
+ version_requirements: !ruby/object:Gem::Requirement
243
+ requirements:
244
+ - - ">="
245
+ - !ruby/object:Gem::Version
246
+ version: '0'
233
247
  - !ruby/object:Gem::Dependency
234
248
  name: license_finder
235
249
  requirement: !ruby/object:Gem::Requirement
@@ -344,7 +358,6 @@ files:
344
358
  - lib/bookbinder/config/checkers/repository_name_presence_checker.rb
345
359
  - lib/bookbinder/config/checkers/required_keys_checker.rb
346
360
  - lib/bookbinder/config/checkers/section_presence_checker.rb
347
- - lib/bookbinder/config/checkers/subnav_topics_checker.rb
348
361
  - lib/bookbinder/config/configuration.rb
349
362
  - lib/bookbinder/config/configuration_decorator.rb
350
363
  - lib/bookbinder/config/dita_config_generator.rb
@@ -353,7 +366,6 @@ files:
353
366
  - lib/bookbinder/config/product_config.rb
354
367
  - lib/bookbinder/config/remote_yaml_credential_provider.rb
355
368
  - lib/bookbinder/config/section_config.rb
356
- - lib/bookbinder/config/topic_config.rb
357
369
  - lib/bookbinder/config/validator.rb
358
370
  - lib/bookbinder/config/yaml_loader.rb
359
371
  - lib/bookbinder/css_link_checker.rb
@@ -385,9 +397,10 @@ files:
385
397
  - lib/bookbinder/ingest/update_failure.rb
386
398
  - lib/bookbinder/ingest/update_success.rb
387
399
  - lib/bookbinder/ingest/working_copy.rb
400
+ - lib/bookbinder/legacy/cli.rb
388
401
  - lib/bookbinder/local_filesystem_accessor.rb
389
402
  - lib/bookbinder/middleman_runner.rb
390
- - lib/bookbinder/postprocessing/sitemap_writer.rb
403
+ - lib/bookbinder/postprocessing/broken_links_checker.rb
391
404
  - lib/bookbinder/preprocessing/dita_html_preprocessor.rb
392
405
  - lib/bookbinder/preprocessing/dita_pdf_preprocessor.rb
393
406
  - lib/bookbinder/preprocessing/dita_preprocessor.rb
@@ -396,7 +409,6 @@ files:
396
409
  - lib/bookbinder/server_director.rb
397
410
  - lib/bookbinder/sheller.rb
398
411
  - lib/bookbinder/sieve.rb
399
- - lib/bookbinder/sitemap_generator.rb
400
412
  - lib/bookbinder/spider.rb
401
413
  - lib/bookbinder/stabilimentum.rb
402
414
  - lib/bookbinder/streams/colorized_stream.rb
@@ -435,9 +447,9 @@ require_paths:
435
447
  - lib
436
448
  required_ruby_version: !ruby/object:Gem::Requirement
437
449
  requirements:
438
- - - ">="
450
+ - - "~>"
439
451
  - !ruby/object:Gem::Version
440
- version: '2.0'
452
+ version: 2.3.0
441
453
  required_rubygems_version: !ruby/object:Gem::Requirement
442
454
  requirements:
443
455
  - - ">="
@@ -445,7 +457,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
445
457
  version: '0'
446
458
  requirements: []
447
459
  rubyforge_project:
448
- rubygems_version: 2.4.5
460
+ rubygems_version: 2.5.1
449
461
  signing_key:
450
462
  specification_version: 4
451
463
  summary: Markdown to Rackup application documentation generator
@@ -1,37 +0,0 @@
1
- module Bookbinder
2
- module Config
3
- module Checkers
4
- class SubnavTopicsChecker
5
- MissingRequiredKeyError = Class.new(RuntimeError)
6
-
7
- def check(config)
8
- @config = config
9
-
10
- if invalid_products.any?
11
- MissingRequiredKeyError.new("Your config.yml is missing required key(s) for subnav_topics in product id(s) #{invalid_product_ids}. Required keys are #{required_topic_keys.join(", ")}.")
12
- end
13
- end
14
-
15
- attr_reader :config
16
-
17
- private
18
-
19
- def invalid_products
20
- config.products.select { |product_config| invalid_topics(product_config.subnav_topics).any? }
21
- end
22
-
23
- def invalid_topics(topics)
24
- topics.map {|topic| topic unless topic.valid? }
25
- end
26
-
27
- def invalid_product_ids
28
- invalid_products.map(&:id).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,29 +0,0 @@
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
@@ -1,18 +0,0 @@
1
- require 'nokogiri'
2
-
3
- module Bookbinder
4
- class SitemapGenerator
5
- def generate(links)
6
- Nokogiri::XML::Builder.new(encoding: 'UTF-8') { |xml|
7
- xml.urlset('xmlns' => 'http://www.sitemaps.org/schemas/sitemap/0.9') {
8
- links.each do |link|
9
- xml.url {
10
- xml.loc link
11
- xml.changefreq 'daily'
12
- }
13
- end
14
- }
15
- }.to_xml
16
- end
17
- end
18
- end