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 +4 -4
- data/bookbinder.gemspec +3 -2
- data/install_bin/bookbinder +1 -3
- data/lib/bookbinder/cli.rb +86 -57
- data/lib/bookbinder/commands/bind.rb +7 -9
- data/lib/bookbinder/commands/collection.rb +2 -2
- data/lib/bookbinder/config/checkers/products_checker.rb +1 -16
- data/lib/bookbinder/config/configuration.rb +1 -2
- data/lib/bookbinder/config/product_config.rb +3 -15
- data/lib/bookbinder/config/validator.rb +1 -3
- data/lib/bookbinder/html_document_manipulator.rb +0 -7
- data/lib/bookbinder/legacy/cli.rb +71 -0
- data/lib/bookbinder/postprocessing/{sitemap_writer.rb → broken_links_checker.rb} +13 -12
- data/lib/bookbinder/spider.rb +18 -39
- data/lib/bookbinder/subnav/json_from_markdown_toc.rb +50 -43
- data/lib/bookbinder/subnav/template_creator.rb +1 -9
- data/master_middleman/bookbinder_helpers.rb +7 -0
- data/master_middleman/config.rb +2 -0
- data/template_app/Gemfile +1 -1
- data/template_app/lib/vienna_application.rb +4 -1
- metadata +21 -9
- data/lib/bookbinder/config/checkers/subnav_topics_checker.rb +0 -37
- data/lib/bookbinder/config/topic_config.rb +0 -29
- data/lib/bookbinder/sitemap_generator.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6cb870420c8d260fe02e262c3d8db416a668a3fa
|
4
|
+
data.tar.gz: c2b4a7842bcdbcba9006096c7f5b4e08c04eff33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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 = '
|
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'
|
data/install_bin/bookbinder
CHANGED
data/lib/bookbinder/cli.rb
CHANGED
@@ -1,69 +1,98 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require_relative '
|
4
|
-
require_relative '
|
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
|
10
|
-
|
11
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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 :
|
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
|
61
|
-
|
62
|
-
|
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
|
-
|
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
|
-
@
|
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
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
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
|
-
:
|
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/
|
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
|
-
|
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
|
-
|
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
|
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
|
20
|
-
config['
|
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
|
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
|
8
|
-
def self.build(
|
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
|
24
|
+
def check!(broken_link_exclusions)
|
25
25
|
server_director.use_server { |port|
|
26
|
-
spider.
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
data/lib/bookbinder/spider.rb
CHANGED
@@ -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
|
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
|
22
|
-
@
|
23
|
-
|
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
|
-
|
26
|
-
|
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
|
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
|
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
|
-
|
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
|
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(
|
17
|
+
def get_links(product_config, output_locations)
|
14
18
|
@source_for_site_gen = output_locations.source_for_site_generator
|
15
|
-
@config =
|
19
|
+
@config = product_config
|
16
20
|
|
17
|
-
|
18
|
-
end
|
21
|
+
root = absolute_source_from_path(Pathname(config.subnav_root))
|
19
22
|
|
20
|
-
|
21
|
-
|
22
|
-
private
|
23
|
+
raise SubnavRootMissingError.new('Subnav root not found at: ' + config.subnav_root) if root.nil?
|
23
24
|
|
24
|
-
|
25
|
-
menu_items = []
|
25
|
+
@parsed_files = { Pathname(root) => '(root)'}
|
26
26
|
|
27
|
-
|
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
|
-
|
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
|
-
|
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
|
52
|
-
|
53
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
69
|
-
|
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
|
73
|
-
|
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
|
-
|
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
|
data/master_middleman/config.rb
CHANGED
data/template_app/Gemfile
CHANGED
@@ -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, {
|
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.
|
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-
|
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/
|
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:
|
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.
|
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
|