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,35 @@
|
|
1
|
+
require_relative '../distributor'
|
2
|
+
require_relative 'bookbinder_command'
|
3
|
+
require_relative 'naming'
|
4
|
+
|
5
|
+
module Bookbinder
|
6
|
+
module Commands
|
7
|
+
class PushLocalToStaging < BookbinderCommand
|
8
|
+
extend Commands::Naming
|
9
|
+
|
10
|
+
def self.usage
|
11
|
+
"push_local_to_staging \t \t \t Push the contents of final_app to the staging host specified in credentials.yml"
|
12
|
+
end
|
13
|
+
|
14
|
+
def run(_)
|
15
|
+
Distributor.build(@logger, options).distribute
|
16
|
+
0
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def options
|
22
|
+
{
|
23
|
+
app_dir: './final_app',
|
24
|
+
build_number: ENV['BUILD_NUMBER'],
|
25
|
+
|
26
|
+
aws_credentials: config.aws_credentials,
|
27
|
+
cf_credentials: config.cf_staging_credentials,
|
28
|
+
|
29
|
+
book_repo: config.book_repo,
|
30
|
+
production: false
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative '../distributor'
|
2
|
+
require_relative 'bookbinder_command'
|
3
|
+
require_relative 'naming'
|
4
|
+
|
5
|
+
module Bookbinder
|
6
|
+
module Commands
|
7
|
+
class PushToProd < BookbinderCommand
|
8
|
+
extend Commands::Naming
|
9
|
+
|
10
|
+
def self.usage
|
11
|
+
"push_to_prod [build_#] \t \t \t Push latest or <build_#> from your S3 bucket to the production host specified in credentials.yml"
|
12
|
+
end
|
13
|
+
|
14
|
+
def run(arguments)
|
15
|
+
Distributor.build(@logger, options(arguments)).distribute
|
16
|
+
0
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def options(arguments)
|
22
|
+
{
|
23
|
+
app_dir: Dir.mktmpdir,
|
24
|
+
build_number: arguments[0],
|
25
|
+
|
26
|
+
aws_credentials: config.aws_credentials,
|
27
|
+
cf_credentials: config.cf_production_credentials,
|
28
|
+
|
29
|
+
book_repo: config.book_repo,
|
30
|
+
production: true
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative 'bookbinder_command'
|
2
|
+
require_relative 'naming'
|
3
|
+
require_relative 'publish'
|
4
|
+
require_relative 'push_local_to_staging'
|
5
|
+
require_relative 'build_and_push_tarball'
|
6
|
+
|
7
|
+
module Bookbinder
|
8
|
+
module Commands
|
9
|
+
class RunPublishCI < BookbinderCommand
|
10
|
+
extend Commands::Naming
|
11
|
+
|
12
|
+
def self.usage
|
13
|
+
"run_publish_ci \t \t \t \t Run publish, push_local_to_staging, and build_and_push_tarball for CI purposes"
|
14
|
+
end
|
15
|
+
|
16
|
+
def run(cli_args)
|
17
|
+
check_params
|
18
|
+
all_successfully_ran = publish(cli_args) == 0 && push_to_staging == 0 && push_tarball == 0
|
19
|
+
all_successfully_ran ? 0 : 1
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def check_params
|
25
|
+
raise BuildAndPushTarball::MissingBuildNumber unless ENV['BUILD_NUMBER']
|
26
|
+
config.book_repo
|
27
|
+
end
|
28
|
+
|
29
|
+
def publish(cli_args)
|
30
|
+
Publish.new(@logger, @configuration_fetcher).run(['github'] + cli_args)
|
31
|
+
end
|
32
|
+
|
33
|
+
def push_to_staging
|
34
|
+
PushLocalToStaging.new(@logger, @configuration_fetcher).run []
|
35
|
+
end
|
36
|
+
|
37
|
+
def push_tarball
|
38
|
+
BuildAndPushTarball.new(@logger, @configuration_fetcher).run []
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative '../book'
|
2
|
+
require_relative '../bookbinder_logger'
|
3
|
+
require_relative '../cli_error'
|
4
|
+
|
5
|
+
require_relative 'bookbinder_command'
|
6
|
+
require_relative 'naming'
|
7
|
+
|
8
|
+
module Bookbinder
|
9
|
+
module Commands
|
10
|
+
class Tag < BookbinderCommand
|
11
|
+
extend Commands::Naming
|
12
|
+
|
13
|
+
def self.usage
|
14
|
+
"tag <git tag> \t \t \t \t Apply the specified <git tag> to your book and all sections of your book"
|
15
|
+
end
|
16
|
+
|
17
|
+
def run(params)
|
18
|
+
tag = params.first
|
19
|
+
raise CliError::InvalidArguments unless tag
|
20
|
+
|
21
|
+
book = Book.new(logger: @logger, full_name: config.book_repo, sections: config.sections)
|
22
|
+
|
23
|
+
book.tag_self_and_sections_with tag
|
24
|
+
|
25
|
+
@logger.log 'Success!'.green
|
26
|
+
@logger.log " #{book.full_name.yellow} and its sections were tagged with #{tag.blue}"
|
27
|
+
0
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative 'bookbinder_command'
|
2
|
+
require_relative 'naming'
|
3
|
+
|
4
|
+
module Bookbinder
|
5
|
+
module Commands
|
6
|
+
class UpdateLocalDocRepos < BookbinderCommand
|
7
|
+
extend Commands::Naming
|
8
|
+
|
9
|
+
def self.usage
|
10
|
+
"update_local_doc_repos \t \t \t Run `git pull` on all sections that exist at the same directory level as your book directory"
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(_)
|
14
|
+
config.sections.map { |conf| repo_for(conf) }.each(&:update_local_copy)
|
15
|
+
0
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def repo_for(section_config)
|
21
|
+
local_repo_dir = File.absolute_path('../')
|
22
|
+
GitHubRepository.new(logger: @logger, full_name: section_config['repository']['name'],
|
23
|
+
local_repo_dir: local_repo_dir)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative 'bookbinder_command'
|
2
|
+
|
3
|
+
module Bookbinder
|
4
|
+
module Commands
|
5
|
+
class Version < BookbinderCommand
|
6
|
+
def self.to_s
|
7
|
+
'version'
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.command_name
|
11
|
+
'--version'
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.usage
|
15
|
+
"--version \t \t \t \t Print the version of bookbinder"
|
16
|
+
end
|
17
|
+
|
18
|
+
def run(*)
|
19
|
+
@logger.log "bookbinder #{Gem::Specification::load(File.join GEM_ROOT, "bookbinder.gemspec").version}"
|
20
|
+
0
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require_relative 'remote_yaml_credential_provider'
|
2
|
+
require_relative 'git_hub_repository'
|
3
|
+
|
4
|
+
module Bookbinder
|
5
|
+
class Configuration
|
6
|
+
|
7
|
+
CURRENT_SCHEMA_VERSION = '1.0.0'
|
8
|
+
STARTING_SCHEMA_VERSION = '1.0.0'
|
9
|
+
|
10
|
+
class CredentialKeyError < StandardError;
|
11
|
+
end
|
12
|
+
|
13
|
+
class ConfigSchemaUnsupportedError < StandardError;
|
14
|
+
end
|
15
|
+
|
16
|
+
class AwsCredentials
|
17
|
+
REQUIRED_KEYS = %w(access_key secret_key green_builds_bucket).freeze
|
18
|
+
|
19
|
+
def initialize(cred_hash)
|
20
|
+
@creds = cred_hash
|
21
|
+
end
|
22
|
+
|
23
|
+
REQUIRED_KEYS.each do |method_name|
|
24
|
+
define_method(method_name) do
|
25
|
+
begin
|
26
|
+
creds.fetch(method_name)
|
27
|
+
rescue KeyError => e
|
28
|
+
raise CredentialKeyError, e
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :creds
|
36
|
+
end
|
37
|
+
|
38
|
+
class CfCredentials
|
39
|
+
REQUIRED_KEYS = %w(api_endpoint organization app_name).freeze
|
40
|
+
OPTIONAL_KEYS = %w(username password production_space production_host staging_space staging_host).freeze
|
41
|
+
|
42
|
+
def initialize(cred_hash, is_production)
|
43
|
+
@creds = cred_hash
|
44
|
+
@is_production = is_production
|
45
|
+
end
|
46
|
+
|
47
|
+
REQUIRED_KEYS.each do |method_name|
|
48
|
+
define_method(method_name) do
|
49
|
+
fetch(method_name)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
OPTIONAL_KEYS.each do |method_name|
|
54
|
+
define_method(method_name) do
|
55
|
+
creds.fetch(method_name, nil)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def routes
|
60
|
+
key = is_production ? 'production_host' : 'staging_host'
|
61
|
+
fetch(key) if correctly_formatted_domain_and_routes?(key)
|
62
|
+
end
|
63
|
+
|
64
|
+
def flat_routes
|
65
|
+
routes.reduce([]) do |all_routes, domain_apps|
|
66
|
+
domain, apps = domain_apps
|
67
|
+
all_routes + apps.map { |app| [domain, app] }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def space
|
72
|
+
key = is_production ? 'production_space' : 'staging_space'
|
73
|
+
fetch(key)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
attr_reader :creds, :is_production
|
79
|
+
|
80
|
+
def fetch(key)
|
81
|
+
creds.fetch(key)
|
82
|
+
rescue KeyError => e
|
83
|
+
raise CredentialKeyError, e
|
84
|
+
end
|
85
|
+
|
86
|
+
def correctly_formatted_domain_and_routes?(deploy_environment)
|
87
|
+
routes_hash = fetch(deploy_environment)
|
88
|
+
domains = routes_hash.keys
|
89
|
+
domains.each { |domain| correctly_formatted_domain?(domain, routes_hash) }
|
90
|
+
end
|
91
|
+
|
92
|
+
def correctly_formatted_domain?(domain, routes_hash)
|
93
|
+
raise 'Each domain in credentials must be a single string.' unless domain.is_a? String
|
94
|
+
raise "Domain #{domain} in credentials must contain a web extension, e.g. '.com'." unless domain.include?('.')
|
95
|
+
raise "Did you mean to add a list of hosts for domain #{domain}? Check your credentials.yml." unless routes_hash[domain]
|
96
|
+
raise "Hosts in credentials must be nested as an array under the desired domain #{domain}." unless routes_hash[domain].is_a? Array
|
97
|
+
raise "Did you mean to provide a hostname for the domain #{domain}? Check your credentials.yml." if routes_hash[domain].any?(&:nil?)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
attr_reader :schema_version, :schema_major_version, :schema_minor_version, :schema_patch_version
|
102
|
+
|
103
|
+
def initialize(logger, config_hash)
|
104
|
+
@logger = logger
|
105
|
+
@config = config_hash
|
106
|
+
end
|
107
|
+
|
108
|
+
CONFIG_REQUIRED_KEYS = %w(book_repo layout_repo cred_repo sections public_host pdf pdf_index versions)
|
109
|
+
CONFIG_OPTIONAL_KEYS = %w(archive_menu)
|
110
|
+
|
111
|
+
CONFIG_REQUIRED_KEYS.each do |method_name|
|
112
|
+
define_method(method_name) do
|
113
|
+
config.fetch(method_name)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
CONFIG_OPTIONAL_KEYS.each do |method_name|
|
118
|
+
define_method(method_name) do
|
119
|
+
config[method_name]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def has_option?(key)
|
124
|
+
@config.has_key?(key)
|
125
|
+
end
|
126
|
+
|
127
|
+
def template_variables
|
128
|
+
config.fetch('template_variables', {})
|
129
|
+
end
|
130
|
+
|
131
|
+
def aws_credentials
|
132
|
+
@aws_creds ||= AwsCredentials.new(credentials.fetch('aws'))
|
133
|
+
end
|
134
|
+
|
135
|
+
def cf_staging_credentials
|
136
|
+
@cf_staging_creds ||= CfCredentials.new(credentials.fetch('cloud_foundry'), false)
|
137
|
+
end
|
138
|
+
|
139
|
+
def cf_production_credentials
|
140
|
+
@cf_prod_creds ||= CfCredentials.new(credentials.fetch('cloud_foundry'), true)
|
141
|
+
end
|
142
|
+
|
143
|
+
def ==(o)
|
144
|
+
(o.class == self.class) && (o.config == self.config)
|
145
|
+
end
|
146
|
+
|
147
|
+
alias_method :eql?, :==
|
148
|
+
|
149
|
+
protected
|
150
|
+
|
151
|
+
attr_reader :config
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def credentials
|
156
|
+
@credentials ||= RemoteYamlCredentialProvider.new(@logger, credentials_repository).credentials
|
157
|
+
end
|
158
|
+
|
159
|
+
def credentials_repository
|
160
|
+
@credentials_repository ||= GitHubRepository.new(logger: @logger, full_name: cred_repo)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative 'yaml_loader'
|
2
|
+
|
3
|
+
module Bookbinder
|
4
|
+
|
5
|
+
class ConfigurationFetcher
|
6
|
+
attr_reader :logger,
|
7
|
+
:configuration_validator,
|
8
|
+
:config,
|
9
|
+
:config_file_path
|
10
|
+
|
11
|
+
def initialize(logger, configuration_validator, loader)
|
12
|
+
@loader = loader
|
13
|
+
@logger = logger
|
14
|
+
@configuration_validator = configuration_validator
|
15
|
+
end
|
16
|
+
|
17
|
+
def fetch_config
|
18
|
+
@config ||= validate(read_config_file)
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_config_file_path config_file_path
|
22
|
+
@config_file_path = config_file_path
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :loader
|
29
|
+
|
30
|
+
def read_config_file
|
31
|
+
|
32
|
+
begin
|
33
|
+
config_hash = loader.load(config_file_path)
|
34
|
+
rescue FileNotFoundError => e
|
35
|
+
raise "The configuration file specified does not exist. Please create a config #{e} file at #{config_file_path} and try again."
|
36
|
+
rescue InvalidSyntaxError => e
|
37
|
+
raise "There is a syntax error in your config file: \n #{e}"
|
38
|
+
end
|
39
|
+
|
40
|
+
if config_hash
|
41
|
+
if File.exists?('./pdf_index.yml')
|
42
|
+
config_hash['pdf_index'] = loader.load(config_file_path)
|
43
|
+
else
|
44
|
+
config_hash['pdf_index'] = nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
config_hash
|
49
|
+
end
|
50
|
+
|
51
|
+
def validate(config_hash)
|
52
|
+
Configuration.new(logger, config_hash) if configuration_validator.valid?(config_hash, Configuration::CURRENT_SCHEMA_VERSION, Configuration::STARTING_SCHEMA_VERSION)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
module Bookbinder
|
2
|
+
|
3
|
+
class DuplicateSectionNameChecker
|
4
|
+
def check(config)
|
5
|
+
if duplicate_section_names?(config)
|
6
|
+
ConfigurationValidator::DuplicateSectionNameError
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def duplicate_section_names?(config)
|
13
|
+
directory_names = config['sections'].map {|section| section['directory']}
|
14
|
+
directory_names.length != directory_names.uniq.length
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class ArchiveMenuChecker
|
20
|
+
def initialize(file_system_accessor)
|
21
|
+
@file_system_accessor = file_system_accessor
|
22
|
+
end
|
23
|
+
|
24
|
+
def check(config)
|
25
|
+
partial_location = './master_middleman/source/archive_menus/_default.erb'
|
26
|
+
if config.has_key?("archive_menu") && config["archive_menu"].nil?
|
27
|
+
ConfigurationValidator::ArchiveMenuNotDefinedError.new 'Did you mean to provide an archive menu value to display? If you use the archive_menu key, you must provide at least one value.'
|
28
|
+
elsif archive_items(config).include?(nil)
|
29
|
+
ConfigurationValidator::EmptyArchiveItemsError.new 'Did you forget to add a value to the archive_menu?'
|
30
|
+
elsif config.has_key?("archive_menu") && !@file_system_accessor.file_exist?(partial_location)
|
31
|
+
ConfigurationValidator::MissingArchiveMenuPartialError.new "You must provide a template partial named at #{partial_location}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def archive_items(config)
|
38
|
+
config.fetch('archive_menu', [])
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
Version = Struct.new(:major, :minor, :patch) do
|
44
|
+
class << self
|
45
|
+
def parse(raw_version)
|
46
|
+
if raw_version
|
47
|
+
new(*raw_version.split('.'))
|
48
|
+
else
|
49
|
+
new(nil, nil, nil)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def valid?
|
55
|
+
[major, minor, patch].all?(&:present?)
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
[major, minor, patch].compact.join('.')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class VersionCheckerMessages
|
64
|
+
def initialize(user_schema_version, bookbinder_schema_version)
|
65
|
+
@user_schema_version = user_schema_version
|
66
|
+
@bookbinder_schema_version = bookbinder_schema_version
|
67
|
+
end
|
68
|
+
|
69
|
+
def schema_now_required_message
|
70
|
+
"[ERROR] Bookbinder now requires a certain schema. Please see README " +
|
71
|
+
"and provide a schema version."
|
72
|
+
end
|
73
|
+
|
74
|
+
def incompatible_schema_message
|
75
|
+
"[ERROR] Your config.yml format, schema version #{user_schema_version}, " +
|
76
|
+
"is older than this version of Bookbinder can support. Please update " +
|
77
|
+
"your config.yml keys and format to version #{bookbinder_schema_version} " +
|
78
|
+
"and try again."
|
79
|
+
end
|
80
|
+
|
81
|
+
def unrecognized_schema_version_message
|
82
|
+
"[ERROR] The config schema version #{user_schema_version} is " +
|
83
|
+
"unrecognized by this version of Bookbinder. The latest schema version " +
|
84
|
+
"is #{bookbinder_schema_version}."
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
attr_reader :user_schema_version, :bookbinder_schema_version
|
90
|
+
end
|
91
|
+
|
92
|
+
class ConfigVersionChecker
|
93
|
+
def initialize(bookbinder_schema_version, starting_schema_version, messages, logger)
|
94
|
+
@bookbinder_schema_version = bookbinder_schema_version
|
95
|
+
@starting_schema_version = starting_schema_version
|
96
|
+
@messages = messages
|
97
|
+
@logger = logger
|
98
|
+
end
|
99
|
+
|
100
|
+
def check(config)
|
101
|
+
user_schema_version = Version.parse(config['schema_version'])
|
102
|
+
if user_schema_version.valid?
|
103
|
+
if user_schema_version.major > bookbinder_schema_version.major
|
104
|
+
raise Configuration::ConfigSchemaUnsupportedError.new messages.unrecognized_schema_version_message
|
105
|
+
elsif user_schema_version.minor > bookbinder_schema_version.minor
|
106
|
+
raise Configuration::ConfigSchemaUnsupportedError.new messages.unrecognized_schema_version_message
|
107
|
+
elsif user_schema_version.patch > bookbinder_schema_version.patch
|
108
|
+
raise Configuration::ConfigSchemaUnsupportedError.new messages.unrecognized_schema_version_message
|
109
|
+
elsif user_schema_version.major < bookbinder_schema_version.major
|
110
|
+
raise Configuration::ConfigSchemaUnsupportedError.new messages.incompatible_schema_message
|
111
|
+
elsif user_schema_version.minor < bookbinder_schema_version.minor
|
112
|
+
@logger.warn nonbreaking_schema_message_for("minor")
|
113
|
+
elsif user_schema_version.patch < bookbinder_schema_version.patch
|
114
|
+
@logger.warn nonbreaking_schema_message_for("patch")
|
115
|
+
end
|
116
|
+
elsif bookbinder_schema_version != starting_schema_version
|
117
|
+
raise Configuration::ConfigSchemaUnsupportedError.new messages.schema_now_required_message
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
attr_reader :bookbinder_schema_version, :starting_schema_version, :messages
|
124
|
+
|
125
|
+
def nonbreaking_schema_message_for(version_level)
|
126
|
+
"[WARNING] Your schema is valid, but there exists a new #{version_level} version. Consider updating your config.yml."
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class ConfigurationValidator
|
131
|
+
DuplicateSectionNameError = Class.new(RuntimeError)
|
132
|
+
MissingArchiveMenuPartialError = Class.new(RuntimeError)
|
133
|
+
EmptyArchiveItemsError = Class.new(RuntimeError)
|
134
|
+
ArchiveMenuNotDefinedError = Class.new(RuntimeError)
|
135
|
+
|
136
|
+
def initialize(logger, file_system_accessor)
|
137
|
+
@logger = logger
|
138
|
+
@file_system_accessor = file_system_accessor
|
139
|
+
end
|
140
|
+
|
141
|
+
def valid?(config_hash, bookbinder_schema_version, starting_schema_version)
|
142
|
+
raise 'Your config.yml appears to be empty. Please check and try again.' unless config_hash
|
143
|
+
|
144
|
+
user_config_schema_version = config_hash['schema_version']
|
145
|
+
exceptions = [
|
146
|
+
ConfigVersionChecker.new(Version.parse(bookbinder_schema_version),
|
147
|
+
Version.parse(starting_schema_version),
|
148
|
+
VersionCheckerMessages.new(Version.parse(user_config_schema_version),
|
149
|
+
bookbinder_schema_version),
|
150
|
+
@logger),
|
151
|
+
DuplicateSectionNameChecker.new,
|
152
|
+
ArchiveMenuChecker.new(@file_system_accessor)
|
153
|
+
].map do |checker|
|
154
|
+
checker.check(config_hash)
|
155
|
+
end
|
156
|
+
exception = exceptions.compact.first
|
157
|
+
raise exception if exception
|
158
|
+
|
159
|
+
true
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|