bookbindery 8.0.0 → 8.1.0

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