bookbindery 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/bookbinder +6 -0
- data/lib/bookbinder.rb +59 -0
- data/lib/bookbinder/app_fetcher.rb +40 -0
- data/lib/bookbinder/archive.rb +95 -0
- data/lib/bookbinder/artifact_namer.rb +22 -0
- data/lib/bookbinder/blue_green_app.rb +27 -0
- data/lib/bookbinder/book.rb +54 -0
- data/lib/bookbinder/bookbinder_logger.rb +33 -0
- data/lib/bookbinder/cf_command_runner.rb +114 -0
- data/lib/bookbinder/cf_routes.rb +19 -0
- data/lib/bookbinder/cli.rb +68 -0
- data/lib/bookbinder/cli_error.rb +6 -0
- data/lib/bookbinder/code_example.rb +40 -0
- data/lib/bookbinder/command_runner.rb +32 -0
- data/lib/bookbinder/command_validator.rb +24 -0
- data/lib/bookbinder/commands/bookbinder_command.rb +18 -0
- data/lib/bookbinder/commands/build_and_push_tarball.rb +31 -0
- data/lib/bookbinder/commands/generate_pdf.rb +140 -0
- data/lib/bookbinder/commands/help.rb +31 -0
- data/lib/bookbinder/commands/naming.rb +9 -0
- data/lib/bookbinder/commands/publish.rb +138 -0
- data/lib/bookbinder/commands/push_local_to_staging.rb +35 -0
- data/lib/bookbinder/commands/push_to_prod.rb +35 -0
- data/lib/bookbinder/commands/run_publish_ci.rb +42 -0
- data/lib/bookbinder/commands/tag.rb +31 -0
- data/lib/bookbinder/commands/update_local_doc_repos.rb +27 -0
- data/lib/bookbinder/commands/version.rb +25 -0
- data/lib/bookbinder/configuration.rb +163 -0
- data/lib/bookbinder/configuration_fetcher.rb +55 -0
- data/lib/bookbinder/configuration_validator.rb +162 -0
- data/lib/bookbinder/css_link_checker.rb +64 -0
- data/lib/bookbinder/directory_helpers.rb +15 -0
- data/lib/bookbinder/distributor.rb +69 -0
- data/lib/bookbinder/git_client.rb +63 -0
- data/lib/bookbinder/git_hub_repository.rb +151 -0
- data/lib/bookbinder/local_file_system_accessor.rb +9 -0
- data/lib/bookbinder/middleman_runner.rb +86 -0
- data/lib/bookbinder/pdf_generator.rb +73 -0
- data/lib/bookbinder/publisher.rb +125 -0
- data/lib/bookbinder/pusher.rb +34 -0
- data/lib/bookbinder/remote_yaml_credential_provider.rb +21 -0
- data/lib/bookbinder/section.rb +78 -0
- data/lib/bookbinder/server_director.rb +53 -0
- data/lib/bookbinder/shell_out.rb +19 -0
- data/lib/bookbinder/sieve.rb +62 -0
- data/lib/bookbinder/sitemap_generator.rb +19 -0
- data/lib/bookbinder/spider.rb +91 -0
- data/lib/bookbinder/stabilimentum.rb +59 -0
- data/lib/bookbinder/usage_messenger.rb +33 -0
- data/lib/bookbinder/yaml_loader.rb +22 -0
- data/master_middleman/bookbinder_helpers.rb +133 -0
- data/master_middleman/config.rb +23 -0
- data/master_middleman/quicklinks_renderer.rb +78 -0
- data/master_middleman/submodule_aware_assets.rb +45 -0
- data/template_app/Gemfile +7 -0
- data/template_app/Gemfile.lock +20 -0
- data/template_app/app.rb +3 -0
- data/template_app/config.ru +9 -0
- data/template_app/lib/rack_static.rb +19 -0
- data/template_app/lib/vienna_application.rb +26 -0
- metadata +462 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module Bookbinder
|
4
|
+
class PdfGenerator
|
5
|
+
class MissingSource < StandardError
|
6
|
+
def initialize(required_file)
|
7
|
+
super "Could not find file #{required_file}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(logger)
|
12
|
+
@logger = logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate(sources, target, header, left_footer=nil)
|
16
|
+
sources.each { |s| check_destination_exists s }
|
17
|
+
check_destination_exists header
|
18
|
+
|
19
|
+
left_footer ||= " © Copyright 2013-#{Time.now.year}, Pivotal"
|
20
|
+
|
21
|
+
toc_xslt_path = File.expand_path('../../../toc.xslt', __FILE__)
|
22
|
+
|
23
|
+
command = <<-CMD
|
24
|
+
wkhtmltopdf \
|
25
|
+
--disable-external-links \
|
26
|
+
--disable-javascript \
|
27
|
+
--load-error-handling ignore \
|
28
|
+
--margin-top 26mm \
|
29
|
+
--margin-bottom 13mm \
|
30
|
+
--header-spacing 10 \
|
31
|
+
--header-html #{header} \
|
32
|
+
--footer-spacing 5 \
|
33
|
+
--footer-font-size 10 \
|
34
|
+
--footer-left '#{left_footer}' \
|
35
|
+
--footer-center '[page] of [toPage]' \
|
36
|
+
--print-media-type \
|
37
|
+
toc --xsl-style-sheet #{toc_xslt_path} \
|
38
|
+
#{sources.join(' ')} \
|
39
|
+
#{target}
|
40
|
+
CMD
|
41
|
+
|
42
|
+
`#{command}`
|
43
|
+
|
44
|
+
raise "'wkhtmltopdf' appears to have failed" unless $?.success? && File.exist?(target)
|
45
|
+
|
46
|
+
@logger.log "\nYour PDF file was generated to #{target.green}"
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
def check_file_exists(required_file)
|
51
|
+
unless File.exist? required_file
|
52
|
+
@logger.error "\nPDF Generation failed (could not find file)!"
|
53
|
+
raise MissingSource, required_file
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def check_destination_exists(url)
|
58
|
+
uri = URI(url)
|
59
|
+
if uri.class == URI::Generic
|
60
|
+
check_file_exists url
|
61
|
+
elsif uri.class == URI::HTTP
|
62
|
+
check_url_exists url
|
63
|
+
else
|
64
|
+
@logger.error "Malformed destination provided for PDF generation source: #{url}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def check_url_exists(url)
|
69
|
+
res = Net::HTTP.get_response(URI(url))
|
70
|
+
raise MissingSource, url if res.code == '404'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'middleman-syntax'
|
2
|
+
require_relative 'bookbinder_logger'
|
3
|
+
require_relative 'directory_helpers'
|
4
|
+
require_relative 'section'
|
5
|
+
require_relative 'server_director'
|
6
|
+
|
7
|
+
module Bookbinder
|
8
|
+
class Publisher
|
9
|
+
include DirectoryHelperMethods
|
10
|
+
|
11
|
+
def initialize(logger, spider, static_site_generator)
|
12
|
+
@gem_root = File.expand_path('../../../', __FILE__)
|
13
|
+
@logger = logger
|
14
|
+
@spider = spider
|
15
|
+
@static_site_generator = static_site_generator
|
16
|
+
end
|
17
|
+
|
18
|
+
def publish(cli_options, output_paths, publish_config, git_accessor)
|
19
|
+
intermediate_directory = output_paths.fetch(:output_dir)
|
20
|
+
final_app_dir = output_paths.fetch(:final_app_dir)
|
21
|
+
master_middleman_dir = output_paths.fetch(:master_middleman_dir)
|
22
|
+
master_dir = File.join intermediate_directory, 'master_middleman'
|
23
|
+
workspace_dir = File.join master_dir, 'source'
|
24
|
+
build_directory = File.join master_dir, 'build/.'
|
25
|
+
public_directory = File.join final_app_dir, 'public'
|
26
|
+
|
27
|
+
@versions = publish_config.fetch(:versions, [])
|
28
|
+
@book_repo = publish_config[:book_repo]
|
29
|
+
prepare_directories final_app_dir, intermediate_directory, workspace_dir, master_middleman_dir, master_dir, git_accessor
|
30
|
+
FileUtils.cp 'redirects.rb', final_app_dir if File.exists?('redirects.rb')
|
31
|
+
|
32
|
+
target_tag = cli_options[:target_tag]
|
33
|
+
sections = gather_sections(workspace_dir, publish_config, output_paths, target_tag, git_accessor)
|
34
|
+
book = Book.new(logger: @logger,
|
35
|
+
full_name: @book_repo,
|
36
|
+
sections: publish_config.fetch(:sections))
|
37
|
+
host_for_sitemap = publish_config.fetch(:host_for_sitemap)
|
38
|
+
|
39
|
+
generate_site(cli_options, output_paths, publish_config, master_dir, book, sections, build_directory, public_directory, git_accessor)
|
40
|
+
generate_sitemap(final_app_dir, host_for_sitemap, @spider)
|
41
|
+
|
42
|
+
@logger.log "Bookbinder bound your book into #{final_app_dir.to_s.green}"
|
43
|
+
|
44
|
+
!@spider.has_broken_links?
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def generate_sitemap(final_app_dir, host_for_sitemap, spider)
|
50
|
+
server_director = ServerDirector.new(@logger, directory: final_app_dir)
|
51
|
+
raise "Your public host must be a single String." unless host_for_sitemap.is_a?(String)
|
52
|
+
|
53
|
+
server_director.use_server { |port| spider.generate_sitemap host_for_sitemap, port }
|
54
|
+
end
|
55
|
+
|
56
|
+
def generate_site(cli_options, output_paths, publish_config, middleman_dir, book, sections, build_dir, public_dir, git_accessor)
|
57
|
+
@static_site_generator.run(middleman_dir,
|
58
|
+
publish_config.fetch(:template_variables, {}),
|
59
|
+
output_paths[:local_repo_dir],
|
60
|
+
cli_options[:verbose],
|
61
|
+
book,
|
62
|
+
sections,
|
63
|
+
publish_config[:host_for_sitemap],
|
64
|
+
publish_config[:archive_menu],
|
65
|
+
git_accessor
|
66
|
+
)
|
67
|
+
FileUtils.cp_r build_dir, public_dir
|
68
|
+
end
|
69
|
+
|
70
|
+
def gather_sections(workspace, publish_config, output_paths, target_tag, git_accessor)
|
71
|
+
section_data = publish_config.fetch(:sections)
|
72
|
+
section_data.map do |attributes|
|
73
|
+
section = Section.get_instance(@logger,
|
74
|
+
section_hash: attributes,
|
75
|
+
destination_dir: workspace,
|
76
|
+
local_repo_dir: output_paths[:local_repo_dir],
|
77
|
+
target_tag: target_tag,
|
78
|
+
git_accessor: git_accessor)
|
79
|
+
section
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def prepare_directories(final_app, middleman_scratch_space, middleman_source, master_middleman_dir, middleman_dir, git_accessor)
|
84
|
+
forget_sections(middleman_scratch_space)
|
85
|
+
FileUtils.rm_rf File.join final_app, '.'
|
86
|
+
FileUtils.mkdir_p middleman_scratch_space
|
87
|
+
FileUtils.mkdir_p File.join final_app, 'public'
|
88
|
+
FileUtils.mkdir_p middleman_source
|
89
|
+
|
90
|
+
copy_directory_from_gem 'template_app', final_app
|
91
|
+
copy_directory_from_gem 'master_middleman', middleman_dir
|
92
|
+
FileUtils.cp_r File.join(master_middleman_dir, '.'), middleman_dir
|
93
|
+
|
94
|
+
copy_version_master_middleman(middleman_source, git_accessor)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Copy the index file from each version into the version's directory. Because version
|
98
|
+
# subdirectories are sections, this is the only way they get content from their master
|
99
|
+
# middleman directory.
|
100
|
+
def copy_version_master_middleman(dest_dir, git_accessor)
|
101
|
+
@versions.each do |version|
|
102
|
+
Dir.mktmpdir(version) do |tmpdir|
|
103
|
+
book = Book.from_remote(logger: @logger, full_name: @book_repo,
|
104
|
+
destination_dir: tmpdir, ref: version, git_accessor: git_accessor)
|
105
|
+
index_source_dir = File.join(tmpdir, book.directory, 'master_middleman', source_dir_name)
|
106
|
+
index_dest_dir = File.join(dest_dir, version)
|
107
|
+
FileUtils.mkdir_p(index_dest_dir)
|
108
|
+
|
109
|
+
Dir.glob(File.join(index_source_dir, 'index.*')) do |f|
|
110
|
+
FileUtils.cp(File.expand_path(f), index_dest_dir)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def forget_sections(middleman_scratch)
|
117
|
+
Section.store.clear
|
118
|
+
FileUtils.rm_rf File.join middleman_scratch, '.'
|
119
|
+
end
|
120
|
+
|
121
|
+
def copy_directory_from_gem(dir, output_dir)
|
122
|
+
FileUtils.cp_r File.join(@gem_root, "#{dir}/."), output_dir
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative 'app_fetcher'
|
2
|
+
|
3
|
+
module Bookbinder
|
4
|
+
class Pusher
|
5
|
+
def initialize(cf_cli, app_fetcher)
|
6
|
+
@cf_cli = cf_cli
|
7
|
+
@app_fetcher = app_fetcher
|
8
|
+
end
|
9
|
+
|
10
|
+
def push(app_dir)
|
11
|
+
Dir.chdir(app_dir) do
|
12
|
+
cf_cli.login
|
13
|
+
|
14
|
+
old_app = app_fetcher.fetch_current_app
|
15
|
+
|
16
|
+
if old_app
|
17
|
+
new_app = old_app.with_flipped_name
|
18
|
+
cf_cli.start(new_app)
|
19
|
+
cf_cli.push(new_app)
|
20
|
+
cf_cli.map_routes(new_app)
|
21
|
+
cf_cli.takedown_old_target_app(old_app)
|
22
|
+
else
|
23
|
+
new_app = cf_cli.new_app
|
24
|
+
cf_cli.push(new_app)
|
25
|
+
cf_cli.map_routes(new_app)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :cf_cli, :app_fetcher
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
module Bookbinder
|
5
|
+
class RemoteYamlCredentialProvider
|
6
|
+
def initialize(logger, repository, git_accessor = Git)
|
7
|
+
@logger = logger
|
8
|
+
@repository = repository
|
9
|
+
@git_accessor = git_accessor
|
10
|
+
end
|
11
|
+
|
12
|
+
def credentials
|
13
|
+
@logger.log "Processing #{@repository.full_name.cyan}"
|
14
|
+
Dir.mktmpdir do |destination_dir|
|
15
|
+
@repository.copy_from_remote(destination_dir, @git_accessor)
|
16
|
+
cred_file_yaml = File.join(destination_dir, @repository.short_name, 'credentials.yml')
|
17
|
+
YAML.load_file(cred_file_yaml)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'bookbinder/directory_helpers'
|
2
|
+
|
3
|
+
module Bookbinder
|
4
|
+
class Section
|
5
|
+
|
6
|
+
def self.store
|
7
|
+
@@store ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.get_instance(logger,
|
11
|
+
section_hash: {},
|
12
|
+
local_repo_dir: nil,
|
13
|
+
destination_dir: Dir.mktmpdir,
|
14
|
+
target_tag: nil,
|
15
|
+
git_accessor: Git)
|
16
|
+
@git_accessor = git_accessor
|
17
|
+
store.fetch([section_hash, local_repo_dir]) { acquire(logger, section_hash, local_repo_dir, destination_dir, target_tag, git_accessor) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(logger, repository, subnav_template)
|
21
|
+
@logger = logger
|
22
|
+
@subnav_template = subnav_template
|
23
|
+
@repository = repository
|
24
|
+
@git_accessor = Git
|
25
|
+
end
|
26
|
+
|
27
|
+
def subnav_template
|
28
|
+
@subnav_template.gsub(/^_/, '').gsub(/\.erb$/, '') if @subnav_template
|
29
|
+
end
|
30
|
+
|
31
|
+
def directory
|
32
|
+
@repository.directory
|
33
|
+
end
|
34
|
+
|
35
|
+
def full_name
|
36
|
+
@repository.full_name
|
37
|
+
end
|
38
|
+
|
39
|
+
def copied?
|
40
|
+
@repository.copied?
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_modification_date_for(file: nil, full_path: nil)
|
44
|
+
unless @repository.has_git_object?
|
45
|
+
begin
|
46
|
+
git_base_object = @git_accessor.open(@repository.path_to_local_repo)
|
47
|
+
rescue => e
|
48
|
+
raise "Invalid git repository! Cannot get modification date for section: #{@repository.path_to_local_repo}."
|
49
|
+
end
|
50
|
+
end
|
51
|
+
@repository.get_modification_date_for(file: file, git: git_base_object)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def self.acquire(logger, section_hash, local_repo_dir, destination, target_tag, git_accessor)
|
57
|
+
repository = section_hash['repository']
|
58
|
+
raise "section repository '#{repository}' is not a hash" unless repository.is_a?(Hash)
|
59
|
+
raise "section repository '#{repository}' missing name key" unless repository['name']
|
60
|
+
logger.log "Gathering #{repository['name'].cyan}"
|
61
|
+
|
62
|
+
repository = build_repository(logger, destination, local_repo_dir, section_hash, target_tag, git_accessor)
|
63
|
+
section = new(logger, repository, section_hash['subnav_template'])
|
64
|
+
|
65
|
+
store[[section_hash, local_repo_dir]] = section
|
66
|
+
end
|
67
|
+
private_class_method :acquire
|
68
|
+
|
69
|
+
def self.build_repository(logger, destination, local_repo_dir, repo_hash, target_tag, git_accessor)
|
70
|
+
if local_repo_dir
|
71
|
+
GitHubRepository.build_from_local(logger, repo_hash, local_repo_dir, destination)
|
72
|
+
else
|
73
|
+
GitHubRepository.build_from_remote(logger, repo_hash, destination, target_tag, git_accessor)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
private_class_method :build_repository
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'popen4'
|
2
|
+
|
3
|
+
module Bookbinder
|
4
|
+
class ServerDirector
|
5
|
+
def initialize(logger, directory: nil, port: 41722)
|
6
|
+
@logger = logger
|
7
|
+
@directory = directory
|
8
|
+
@port = port
|
9
|
+
end
|
10
|
+
|
11
|
+
def use_server
|
12
|
+
Dir.chdir(@directory) do
|
13
|
+
POpen4::popen4("puma -p #{@port}") do |stdout, stderr, stdin, pid|
|
14
|
+
begin
|
15
|
+
wait_for_server(stdout)
|
16
|
+
consume_stream_in_separate_thread(stdout)
|
17
|
+
consume_stream_in_separate_thread(stderr)
|
18
|
+
yield @port
|
19
|
+
ensure
|
20
|
+
stop_server(pid)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def wait_for_server(io)
|
29
|
+
Kernel.sleep(1)
|
30
|
+
begin
|
31
|
+
line = io.gets
|
32
|
+
raise 'Puma could not start' if line.nil?
|
33
|
+
|
34
|
+
@logger.log "Vienna says, #{line}"
|
35
|
+
end until line.include?('Listening on')
|
36
|
+
|
37
|
+
@logger.log 'Vienna is lovely this time of year.'
|
38
|
+
end
|
39
|
+
|
40
|
+
def stop_server(pid)
|
41
|
+
Process.kill 'KILL', pid
|
42
|
+
end
|
43
|
+
|
44
|
+
# avoids deadlocks by ensuring rack doesn't hang waiting to write to stderr
|
45
|
+
def consume_stream_in_separate_thread(stream)
|
46
|
+
Thread.new do
|
47
|
+
s = nil
|
48
|
+
while stream.read(1024, s)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module Bookbinder
|
4
|
+
module ShellOut
|
5
|
+
def shell_out(command, failure_okay = false)
|
6
|
+
Open3.popen3(command) do |input, stdout, stderr, wait_thr|
|
7
|
+
command_failed = (wait_thr.value != 0)
|
8
|
+
announce_failure(failure_okay, stderr, stdout) if command_failed
|
9
|
+
stdout.read
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def announce_failure(failure_okay, stderr, stdout)
|
14
|
+
contents_of_stderr = stderr.read
|
15
|
+
error_message = contents_of_stderr.empty? ? stdout.read : contents_of_stderr
|
16
|
+
raise "\n#{error_message}" unless failure_okay
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Bookbinder
|
2
|
+
class Sieve
|
3
|
+
def initialize(domain: ->(){ raise 'You must supply a domain parameter.' }.call)
|
4
|
+
@unverified_fragments_by_url = {}
|
5
|
+
@domain = domain
|
6
|
+
end
|
7
|
+
|
8
|
+
def links_from(page, is_first_pass)
|
9
|
+
if page.not_found?
|
10
|
+
working = []
|
11
|
+
broken = [Spider.prepend_location(page.referer, page.url)]
|
12
|
+
else
|
13
|
+
working = [page.url.to_s]
|
14
|
+
broken = broken_fragments_targeting(page, is_first_pass)
|
15
|
+
store_unverified_fragments_from(page) if is_first_pass
|
16
|
+
end
|
17
|
+
|
18
|
+
return broken, working
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def store_unverified_fragments_from(page)
|
24
|
+
@unverified_fragments_by_url.merge! fragments_targeting_other_pages_from page
|
25
|
+
end
|
26
|
+
|
27
|
+
def broken_fragments_targeting(page, first_pass)
|
28
|
+
first_pass ? local_fragments_missing_from(page) : remote_fragments_missing_from(page)
|
29
|
+
end
|
30
|
+
|
31
|
+
def local_fragments_missing_from(page)
|
32
|
+
local_fragments = page.fragment_identifiers targeting_locally: true
|
33
|
+
local_fragments.reject { |uri| page.has_target_for?(uri) }.map { |uri| Spider.prepend_location(page.url, uri) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def remote_fragments_missing_from(page)
|
37
|
+
@unverified_fragments_by_url.fetch(page.url, []).reject { |localized_identifier| page.has_target_for? URI(strip_location(localized_identifier)) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def fragments_targeting_other_pages_from(page)
|
41
|
+
uris_with_fragments = page.fragment_identifiers(targeting_locally: false)
|
42
|
+
uris_with_fragments.reduce({}) { |dict, uri| merge_uris_under_targets(dict, page, uri) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def merge_uris_under_targets(dict, page, uri)
|
46
|
+
target_url = URI::join @domain, uri.path
|
47
|
+
localized_identifier = Spider.prepend_location(page.url, "##{uri.fragment}")
|
48
|
+
|
49
|
+
if dict.has_key? target_url
|
50
|
+
dict[target_url] << localized_identifier
|
51
|
+
else
|
52
|
+
dict[target_url] = [localized_identifier]
|
53
|
+
end
|
54
|
+
|
55
|
+
dict
|
56
|
+
end
|
57
|
+
|
58
|
+
def strip_location(id)
|
59
|
+
id.split('=> ').last
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|