pdk 1.10.0 → 1.11.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 +5 -5
- data/CHANGELOG.md +50 -1
- data/lib/pdk.rb +16 -1
- data/lib/pdk/analytics.rb +28 -0
- data/lib/pdk/analytics/client/google_analytics.rb +138 -0
- data/lib/pdk/analytics/client/noop.rb +23 -0
- data/lib/pdk/analytics/util.rb +17 -0
- data/lib/pdk/cli.rb +37 -0
- data/lib/pdk/cli/build.rb +2 -0
- data/lib/pdk/cli/bundle.rb +2 -1
- data/lib/pdk/cli/convert.rb +2 -0
- data/lib/pdk/cli/exec.rb +28 -1
- data/lib/pdk/cli/new/class.rb +2 -0
- data/lib/pdk/cli/new/defined_type.rb +2 -0
- data/lib/pdk/cli/new/module.rb +2 -0
- data/lib/pdk/cli/new/provider.rb +2 -0
- data/lib/pdk/cli/new/task.rb +2 -0
- data/lib/pdk/cli/test.rb +0 -1
- data/lib/pdk/cli/test/unit.rb +13 -10
- data/lib/pdk/cli/update.rb +21 -0
- data/lib/pdk/cli/util.rb +35 -0
- data/lib/pdk/cli/util/interview.rb +7 -1
- data/lib/pdk/cli/validate.rb +9 -2
- data/lib/pdk/config.rb +94 -0
- data/lib/pdk/config/errors.rb +5 -0
- data/lib/pdk/config/json.rb +23 -0
- data/lib/pdk/config/namespace.rb +273 -0
- data/lib/pdk/config/validator.rb +31 -0
- data/lib/pdk/config/value.rb +94 -0
- data/lib/pdk/config/yaml.rb +31 -0
- data/lib/pdk/generate/module.rb +3 -2
- data/lib/pdk/logger.rb +21 -1
- data/lib/pdk/module/build.rb +58 -0
- data/lib/pdk/module/convert.rb +1 -1
- data/lib/pdk/module/metadata.rb +1 -0
- data/lib/pdk/module/templatedir.rb +24 -5
- data/lib/pdk/module/update_manager.rb +2 -2
- data/lib/pdk/report/event.rb +3 -3
- data/lib/pdk/template_file.rb +1 -1
- data/lib/pdk/tests/unit.rb +10 -12
- data/lib/pdk/util.rb +9 -0
- data/lib/pdk/util/bundler.rb +5 -9
- data/lib/pdk/util/filesystem.rb +37 -0
- data/lib/pdk/util/puppet_version.rb +1 -1
- data/lib/pdk/util/ruby_version.rb +16 -6
- data/lib/pdk/util/template_uri.rb +72 -43
- data/lib/pdk/util/version.rb +1 -1
- data/lib/pdk/util/windows.rb +1 -0
- data/lib/pdk/util/windows/api_types.rb +0 -7
- data/lib/pdk/util/windows/file.rb +1 -1
- data/lib/pdk/util/windows/string.rb +1 -1
- data/lib/pdk/validate/base_validator.rb +8 -6
- data/lib/pdk/validate/puppet/puppet_syntax.rb +1 -1
- data/lib/pdk/validate/ruby/rubocop.rb +1 -1
- data/lib/pdk/version.rb +1 -1
- data/locales/pdk.pot +223 -114
- metadata +103 -50
data/lib/pdk/cli/new/class.rb
CHANGED
@@ -24,6 +24,8 @@ module PDK::CLI
|
|
24
24
|
raise PDK::CLI::ExitWithError, _("'%{name}' is not a valid class name") % { name: class_name }
|
25
25
|
end
|
26
26
|
|
27
|
+
PDK::CLI::Util.analytics_screen_view('new_class', opts)
|
28
|
+
|
27
29
|
PDK::Generate::PuppetClass.new(module_dir, class_name, opts).run
|
28
30
|
end
|
29
31
|
end
|
@@ -22,6 +22,8 @@ module PDK::CLI
|
|
22
22
|
raise PDK::CLI::ExitWithError, _("'%{name}' is not a valid defined type name") % { name: defined_type_name }
|
23
23
|
end
|
24
24
|
|
25
|
+
PDK::CLI::Util.analytics_screen_view('new_defined_type', opts)
|
26
|
+
|
25
27
|
PDK::Generate::DefinedType.new(module_dir, defined_type_name, opts).run
|
26
28
|
end
|
27
29
|
end
|
data/lib/pdk/cli/new/module.rb
CHANGED
@@ -21,6 +21,8 @@ module PDK::CLI
|
|
21
21
|
|
22
22
|
PDK::CLI::Util.validate_template_opts(opts)
|
23
23
|
|
24
|
+
PDK::CLI::Util.analytics_screen_view('new_module', opts)
|
25
|
+
|
24
26
|
if opts[:'skip-interview'] && opts[:'full-interview']
|
25
27
|
PDK.logger.info _('Ignoring --full-interview and continuing with --skip-interview.')
|
26
28
|
opts[:'full-interview'] = false
|
data/lib/pdk/cli/new/provider.rb
CHANGED
@@ -19,6 +19,8 @@ module PDK::CLI
|
|
19
19
|
raise PDK::CLI::ExitWithError, _("'%{name}' is not a valid provider name") % { name: provider_name }
|
20
20
|
end
|
21
21
|
|
22
|
+
PDK::CLI::Util.analytics_screen_view('new_provider', opts)
|
23
|
+
|
22
24
|
PDK::Generate::Provider.new(module_dir, provider_name, opts).run
|
23
25
|
end
|
24
26
|
end
|
data/lib/pdk/cli/new/task.rb
CHANGED
data/lib/pdk/cli/test.rb
CHANGED
data/lib/pdk/cli/test/unit.rb
CHANGED
@@ -33,10 +33,22 @@ module PDK::CLI
|
|
33
33
|
|
34
34
|
PDK::CLI::Util.module_version_check
|
35
35
|
|
36
|
+
PDK::CLI::Util.analytics_screen_view('test_unit', opts)
|
37
|
+
|
38
|
+
# Ensure that the bundled gems are up to date and correct Ruby is activated before running or listing tests.
|
39
|
+
puppet_env = PDK::CLI::Util.puppet_from_opts_or_env(opts)
|
40
|
+
PDK::Util::PuppetVersion.fetch_puppet_dev if opts[:'puppet-dev']
|
41
|
+
PDK::Util::RubyVersion.use(puppet_env[:ruby_version])
|
42
|
+
|
43
|
+
opts.merge!(puppet_env[:gemset])
|
44
|
+
|
45
|
+
PDK::Util::Bundler.ensure_bundle!(puppet_env[:gemset])
|
46
|
+
|
36
47
|
report = nil
|
37
48
|
|
38
49
|
if opts[:list]
|
39
|
-
examples = PDK::Test::Unit.list
|
50
|
+
examples = PDK::Test::Unit.list(opts)
|
51
|
+
|
40
52
|
if examples.empty?
|
41
53
|
puts _('No unit test files with examples were found.')
|
42
54
|
else
|
@@ -66,15 +78,6 @@ module PDK::CLI
|
|
66
78
|
}]
|
67
79
|
end
|
68
80
|
|
69
|
-
# Ensure that the bundled gems are up to date and correct Ruby is activated before running tests.
|
70
|
-
puppet_env = PDK::CLI::Util.puppet_from_opts_or_env(opts)
|
71
|
-
PDK::Util::PuppetVersion.fetch_puppet_dev if opts.key?(:'puppet-dev')
|
72
|
-
PDK::Util::RubyVersion.use(puppet_env[:ruby_version])
|
73
|
-
|
74
|
-
opts.merge!(puppet_env[:gemset])
|
75
|
-
|
76
|
-
PDK::Util::Bundler.ensure_bundle!(puppet_env[:gemset])
|
77
|
-
|
78
81
|
exit_code = PDK::Test::Unit.invoke(report, opts)
|
79
82
|
|
80
83
|
report_formats.each do |format|
|
data/lib/pdk/cli/update.rb
CHANGED
@@ -26,6 +26,27 @@ module PDK::CLI
|
|
26
26
|
raise PDK::CLI::ExitWithError, _('You can not specify --noop and --force when updating a module')
|
27
27
|
end
|
28
28
|
|
29
|
+
if Gem::Version.new(PDK::VERSION) < Gem::Version.new(PDK::Util.module_pdk_version)
|
30
|
+
PDK.logger.warn _(
|
31
|
+
'This module has been updated to PDK %{module_pdk_version} which ' \
|
32
|
+
'is newer than your PDK version (%{user_pdk_version}), proceed ' \
|
33
|
+
'with caution!',
|
34
|
+
) % {
|
35
|
+
module_pdk_version: PDK::Util.module_pdk_version,
|
36
|
+
user_pdk_version: PDK::VERSION,
|
37
|
+
}
|
38
|
+
|
39
|
+
unless opts[:force]
|
40
|
+
raise PDK::CLI::ExitWithError, _(
|
41
|
+
'Please update your PDK installation and try again. ' \
|
42
|
+
'You may also use the --force flag to override this and ' \
|
43
|
+
'continue at your own risk.',
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
PDK::CLI::Util.analytics_screen_view('update', opts)
|
49
|
+
|
29
50
|
updater = PDK::Module::Update.new(opts)
|
30
51
|
|
31
52
|
updater.run
|
data/lib/pdk/cli/util.rb
CHANGED
@@ -198,6 +198,41 @@ module PDK
|
|
198
198
|
raise PDK::CLI::ExitWithError, _('--template-url may not be used to specify paths containing #\'s.')
|
199
199
|
end
|
200
200
|
module_function :validate_template_opts
|
201
|
+
|
202
|
+
def analytics_screen_view(screen_name, opts = {})
|
203
|
+
dimensions = {
|
204
|
+
ruby_version: RUBY_VERSION,
|
205
|
+
}
|
206
|
+
|
207
|
+
cmd_opts = opts.dup.reject do |_, v|
|
208
|
+
v.nil? || (v.respond_to?(:empty?) && v.empty?)
|
209
|
+
end
|
210
|
+
|
211
|
+
if (format_args = cmd_opts.delete(:format))
|
212
|
+
formats = PDK::CLI::Util::OptionNormalizer.report_formats(format_args)
|
213
|
+
dimensions[:output_format] = formats.map { |r| r[:method].to_s.gsub(%r{\Awrite_}, '') }.sort.uniq.join(',')
|
214
|
+
else
|
215
|
+
dimensions[:output_format] = 'default'
|
216
|
+
end
|
217
|
+
|
218
|
+
safe_opts = [:'puppet-version', :'pe-version']
|
219
|
+
redacted_opts = cmd_opts.map do |k, v|
|
220
|
+
value = if [true, false].include?(v) || safe_opts.include?(k)
|
221
|
+
v
|
222
|
+
else
|
223
|
+
'redacted'
|
224
|
+
end
|
225
|
+
"#{k}=#{value}"
|
226
|
+
end
|
227
|
+
dimensions[:cli_options] = redacted_opts.join(',') unless redacted_opts.empty?
|
228
|
+
|
229
|
+
ignored_env_vars = %w[PDK_ANALYTICS_CONFIG PDK_DISABLE_ANALYTICS]
|
230
|
+
env_vars = ENV.select { |k, _| k.start_with?('PDK_') && !ignored_env_vars.include?(k) }.map { |k, v| "#{k}=#{v}" }
|
231
|
+
dimensions[:env_vars] = env_vars.join(',') unless env_vars.empty?
|
232
|
+
|
233
|
+
PDK.analytics.screen_view(screen_name, dimensions)
|
234
|
+
end
|
235
|
+
module_function :analytics_screen_view
|
201
236
|
end
|
202
237
|
end
|
203
238
|
end
|
@@ -30,7 +30,13 @@ module PDK
|
|
30
30
|
@prompt.print pastel.bold(_('[Q %{current_number}/%{questions_total}]') % { current_number: i, questions_total: num_questions }) + ' '
|
31
31
|
@prompt.puts pastel.bold(question[:question])
|
32
32
|
@prompt.puts question[:help] if question.key?(:help)
|
33
|
-
|
33
|
+
|
34
|
+
case question[:type]
|
35
|
+
when :yes
|
36
|
+
yes?(_('-->')) do |q|
|
37
|
+
q.default(question[:default]) if question.key?(:default)
|
38
|
+
end
|
39
|
+
when :multi_select
|
34
40
|
multi_select(_('-->'), per_page: question[:choices].count) do |q|
|
35
41
|
q.enum ')'
|
36
42
|
q.default(*question[:default]) if question.key?(:default)
|
data/lib/pdk/cli/validate.rb
CHANGED
@@ -31,6 +31,7 @@ module PDK::CLI
|
|
31
31
|
targets = []
|
32
32
|
|
33
33
|
if opts[:list]
|
34
|
+
PDK::CLI::Util.analytics_screen_view('validate', opts)
|
34
35
|
PDK.logger.info(_('Available validators: %{validator_names}') % { validator_names: validator_names.join(', ') })
|
35
36
|
exit 0
|
36
37
|
end
|
@@ -71,6 +72,12 @@ module PDK::CLI
|
|
71
72
|
PDK.logger.info(_('Running all available validators...'))
|
72
73
|
end
|
73
74
|
|
75
|
+
if validators == PDK::Validate.validators
|
76
|
+
PDK::CLI::Util.analytics_screen_view('validate', opts)
|
77
|
+
else
|
78
|
+
PDK::CLI::Util.analytics_screen_view(['validate', validators.map(&:name).sort].flatten.join('_'), opts)
|
79
|
+
end
|
80
|
+
|
74
81
|
# Subsequent arguments are targets.
|
75
82
|
targets.concat(args.to_a[1..-1]) if args.length > 1
|
76
83
|
|
@@ -85,11 +92,11 @@ module PDK::CLI
|
|
85
92
|
end
|
86
93
|
|
87
94
|
options = targets.empty? ? {} : { targets: targets }
|
88
|
-
options[:auto_correct] = true if opts
|
95
|
+
options[:auto_correct] = true if opts[:'auto-correct']
|
89
96
|
|
90
97
|
# Ensure that the bundled gems are up to date and correct Ruby is activated before running any validations.
|
91
98
|
puppet_env = PDK::CLI::Util.puppet_from_opts_or_env(opts)
|
92
|
-
PDK::Util::PuppetVersion.fetch_puppet_dev if opts
|
99
|
+
PDK::Util::PuppetVersion.fetch_puppet_dev if opts[:'puppet-dev']
|
93
100
|
PDK::Util::RubyVersion.use(puppet_env[:ruby_version])
|
94
101
|
|
95
102
|
options.merge!(puppet_env[:gemset])
|
data/lib/pdk/config.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'pdk/config/errors'
|
2
|
+
require 'pdk/config/json'
|
3
|
+
require 'pdk/config/namespace'
|
4
|
+
require 'pdk/config/validator'
|
5
|
+
require 'pdk/config/value'
|
6
|
+
require 'pdk/config/yaml'
|
7
|
+
|
8
|
+
module PDK
|
9
|
+
def self.config
|
10
|
+
@config ||= PDK::Config.new
|
11
|
+
end
|
12
|
+
|
13
|
+
class Config
|
14
|
+
def user
|
15
|
+
@user ||= PDK::Config::JSON.new('user', file: PDK::Config.user_config_path) do
|
16
|
+
mount :module_defaults, PDK::Config::JSON.new(file: PDK.answers.answer_file_path)
|
17
|
+
|
18
|
+
mount :analytics, PDK::Config::YAML.new(file: PDK::Config.analytics_config_path) do
|
19
|
+
value :disabled do
|
20
|
+
validate PDK::Config::Validator.boolean
|
21
|
+
default_to { PDK::Config.bolt_analytics_config.fetch('disabled', true) }
|
22
|
+
end
|
23
|
+
|
24
|
+
value 'user-id' do
|
25
|
+
validate PDK::Config::Validator.uuid
|
26
|
+
default_to do
|
27
|
+
require 'securerandom'
|
28
|
+
|
29
|
+
PDK::Config.bolt_analytics_config.fetch('user-id', SecureRandom.uuid)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.bolt_analytics_config
|
37
|
+
PDK::Config::YAML.new(file: File.expand_path('~/.puppetlabs/bolt/analytics.yaml'))
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.analytics_config_path
|
41
|
+
ENV['PDK_ANALYTICS_CONFIG'] || File.join(File.dirname(PDK::Util.configdir), 'puppet', 'analytics.yml')
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.user_config_path
|
45
|
+
File.join(PDK::Util.configdir, 'user_config.json')
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.analytics_config_exist?
|
49
|
+
PDK::Util::Filesystem.file?(analytics_config_path)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.analytics_config_interview!
|
53
|
+
return unless PDK::CLI::Util.interactive?
|
54
|
+
|
55
|
+
pre_message = _(
|
56
|
+
'PDK collects anonymous usage information to help us understand how ' \
|
57
|
+
'it is being used and make decisions on how to improve it. You can ' \
|
58
|
+
'find out more about what data we collect and how it is used in the ' \
|
59
|
+
"PDK documentation at %{url}.\n",
|
60
|
+
) % { url: 'https://puppet.com/docs/pdk/latest/pdk_install.html' }
|
61
|
+
post_message = _(
|
62
|
+
'You can opt in or out of the usage data collection at any time by ' \
|
63
|
+
'editing the analytics configuration file at %{path} and changing ' \
|
64
|
+
"the '%{key}' value.",
|
65
|
+
) % {
|
66
|
+
path: PDK::Config.analytics_config_path,
|
67
|
+
key: 'disabled',
|
68
|
+
}
|
69
|
+
|
70
|
+
questions = [
|
71
|
+
{
|
72
|
+
name: 'enabled',
|
73
|
+
question: _('Do you consent to the collection of anonymous PDK usage information?'),
|
74
|
+
type: :yes,
|
75
|
+
},
|
76
|
+
]
|
77
|
+
|
78
|
+
PDK.logger.info(text: pre_message, wrap: true)
|
79
|
+
prompt = TTY::Prompt.new(help_color: :cyan)
|
80
|
+
interview = PDK::CLI::Util::Interview.new(prompt)
|
81
|
+
interview.add_questions(questions)
|
82
|
+
answers = interview.run
|
83
|
+
|
84
|
+
if answers.nil?
|
85
|
+
PDK.logger.info _('No answer given, opting out of analytics collection.')
|
86
|
+
PDK.config.user['analytics']['disabled'] = true
|
87
|
+
else
|
88
|
+
PDK.config.user['analytics']['disabled'] = !answers['enabled']
|
89
|
+
end
|
90
|
+
|
91
|
+
PDK.logger.info(text: post_message, wrap: true)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'pdk/config/namespace'
|
2
|
+
|
3
|
+
module PDK
|
4
|
+
class Config
|
5
|
+
class JSON < Namespace
|
6
|
+
def parse_data(data, _filename)
|
7
|
+
return {} if data.nil? || data.empty?
|
8
|
+
|
9
|
+
require 'json'
|
10
|
+
|
11
|
+
::JSON.parse(data)
|
12
|
+
rescue ::JSON::ParserError => e
|
13
|
+
raise PDK::Config::LoadError, e.message
|
14
|
+
end
|
15
|
+
|
16
|
+
def serialize_data(data)
|
17
|
+
require 'json'
|
18
|
+
|
19
|
+
::JSON.pretty_generate(data)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,273 @@
|
|
1
|
+
module PDK
|
2
|
+
class Config
|
3
|
+
class Namespace
|
4
|
+
# @param value [String] the new name of this namespace.
|
5
|
+
attr_writer :name
|
6
|
+
|
7
|
+
# @return [String] the path to the file associated with the contents of
|
8
|
+
# this namespace.
|
9
|
+
attr_reader :file
|
10
|
+
|
11
|
+
# @return [self] the parent namespace of this namespace.
|
12
|
+
attr_accessor :parent
|
13
|
+
|
14
|
+
# Initialises the PDK::Config::Namespace object.
|
15
|
+
#
|
16
|
+
# @param name [String] the name of the namespace (defaults to nil).
|
17
|
+
# @param params [Hash{Symbol => Object}] keyword parameters for the
|
18
|
+
# method.
|
19
|
+
# @option params [String] :file the path to the file associated with the
|
20
|
+
# contents of the namespace (defaults to nil).
|
21
|
+
# @option params [self] :parent the parent {self} that this namespace is
|
22
|
+
# a child of (defaults to nil).
|
23
|
+
# @param block [Proc] a block that is evaluated within the new instance.
|
24
|
+
def initialize(name = nil, file: nil, parent: nil, &block)
|
25
|
+
@file = File.expand_path(file) unless file.nil?
|
26
|
+
@values = {}
|
27
|
+
@name = name.to_s
|
28
|
+
@parent = parent
|
29
|
+
|
30
|
+
instance_eval(&block) if block_given?
|
31
|
+
end
|
32
|
+
|
33
|
+
# Pre-configure a value in the namespace.
|
34
|
+
#
|
35
|
+
# Allows you to specify validators and a default value for value in the
|
36
|
+
# namespace (see PDK::Config::Value#initialize).
|
37
|
+
#
|
38
|
+
# @param key [String,Symbol] the name of the value.
|
39
|
+
# @param block [Proc] a block that is evaluated within the new [self].
|
40
|
+
#
|
41
|
+
# @return [nil]
|
42
|
+
def value(key, &block)
|
43
|
+
@values[key.to_s] ||= PDK::Config::Value.new(key.to_s)
|
44
|
+
@values[key.to_s].instance_eval(&block) if block_given?
|
45
|
+
end
|
46
|
+
|
47
|
+
# Mount a provided [self] (or subclass) into the namespace.
|
48
|
+
#
|
49
|
+
# @param key [String,Symbol] the name of the namespace to be mounted.
|
50
|
+
# @param obj [self] the namespace to be mounted.
|
51
|
+
# @param block [Proc] a block to be evaluated within the instance of the
|
52
|
+
# newly mounted namespace.
|
53
|
+
#
|
54
|
+
# @raise [ArgumentError] if the object to be mounted is not a {self} or
|
55
|
+
# subclass thereof.
|
56
|
+
#
|
57
|
+
# @return [self] the mounted namespace.
|
58
|
+
def mount(key, obj, &block)
|
59
|
+
raise ArgumentError, _('Only PDK::Config::Namespace objects can be mounted into a namespace') unless obj.is_a?(PDK::Config::Namespace)
|
60
|
+
obj.parent = self
|
61
|
+
obj.name = key.to_s
|
62
|
+
obj.instance_eval(&block) if block_given?
|
63
|
+
data[key.to_s] = obj
|
64
|
+
end
|
65
|
+
|
66
|
+
# Create and mount a new child namespace.
|
67
|
+
#
|
68
|
+
# @param name [String,Symbol] the name of the new namespace.
|
69
|
+
# @param block [Proc]
|
70
|
+
def namespace(name, &block)
|
71
|
+
mount(name, PDK::Config::Namespace.new, &block)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Get the value of the named key.
|
75
|
+
#
|
76
|
+
# If there is a value for that key, return it. If not, follow the logic
|
77
|
+
# described in {#default_config_value} to determine the default value to
|
78
|
+
# return.
|
79
|
+
#
|
80
|
+
# @note Unlike a Ruby Hash, this will not return `nil` in the event that
|
81
|
+
# the key does not exist (see #fetch).
|
82
|
+
#
|
83
|
+
# @param key [String,Symbol] the name of the value to retrieve.
|
84
|
+
#
|
85
|
+
# @return [Object] the requested value.
|
86
|
+
def [](key)
|
87
|
+
data[key.to_s]
|
88
|
+
end
|
89
|
+
|
90
|
+
# Get the value of the named key or the provided default value if not
|
91
|
+
# present.
|
92
|
+
#
|
93
|
+
# This differs from {#[]} in an important way in that it allows you to
|
94
|
+
# return a default value, which is not possible using `[] || default` as
|
95
|
+
# non-existent values when accessed normally via {#[]} will be defaulted
|
96
|
+
# to a new Hash.
|
97
|
+
#
|
98
|
+
# @param key [String,Symbol] the name of the value to fetch.
|
99
|
+
# @param default_value [Object] the value to return if the namespace does
|
100
|
+
# not contain the requested value.
|
101
|
+
#
|
102
|
+
# @return [Object] the requested value.
|
103
|
+
def fetch(key, default_value)
|
104
|
+
data.fetch(key.to_s, default_value)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Set the value of the named key.
|
108
|
+
#
|
109
|
+
# If the key has been pre-configured with {#value}, then the value of the
|
110
|
+
# key will be validated against any validators that have been configured.
|
111
|
+
#
|
112
|
+
# After the value has been set in memory, the value will then be
|
113
|
+
# persisted to disk.
|
114
|
+
#
|
115
|
+
# @param key [String,Symbol] the name of the configuration value.
|
116
|
+
# @param value [Object] the value of the configuration value.
|
117
|
+
#
|
118
|
+
# @return [nil]
|
119
|
+
def []=(key, value)
|
120
|
+
@values[key.to_s].validate!([name, key.to_s].join('.'), value) if @values.key?(key.to_s)
|
121
|
+
|
122
|
+
data[key.to_s] = value
|
123
|
+
save_data
|
124
|
+
end
|
125
|
+
|
126
|
+
# Convert the namespace into a Hash of values, suitable for serialising
|
127
|
+
# and persisting to disk.
|
128
|
+
#
|
129
|
+
# Child namespaces that are associated with their own files are excluded
|
130
|
+
# from the Hash (as their values will be persisted to their own files)
|
131
|
+
# and nil values are removed from the Hash.
|
132
|
+
#
|
133
|
+
# @return [Hash{String => Object}] the values from the namespace that
|
134
|
+
# should be persisted to disk.
|
135
|
+
def to_h
|
136
|
+
data.inject({}) do |new_hash, (key, value)|
|
137
|
+
new_hash[key] = if value.is_a?(PDK::Config::Namespace)
|
138
|
+
value.include_in_parent? ? value.to_h : nil
|
139
|
+
else
|
140
|
+
value
|
141
|
+
end
|
142
|
+
new_hash.delete_if { |_, v| v.nil? }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# @return [Boolean] true if the namespace has a parent, otherwise false.
|
147
|
+
def child_namespace?
|
148
|
+
!parent.nil?
|
149
|
+
end
|
150
|
+
|
151
|
+
# Determines the fully qualified name of the namespace.
|
152
|
+
#
|
153
|
+
# If this is a child namespace, then fully qualified name for the
|
154
|
+
# namespace will be "<parent>.<child>".
|
155
|
+
#
|
156
|
+
# @return [String] the fully qualifed name of the namespace.
|
157
|
+
def name
|
158
|
+
child_namespace? ? [parent.name, @name].join('.') : @name
|
159
|
+
end
|
160
|
+
|
161
|
+
# Determines if the contents of the namespace should be included in the
|
162
|
+
# parent namespace when persisting to disk.
|
163
|
+
#
|
164
|
+
# If the namespace has been mounted into a parent namespace and is not
|
165
|
+
# associated with its own file on disk, then the values in the namespace
|
166
|
+
# should be included in the parent namespace when persisting to disk.
|
167
|
+
#
|
168
|
+
# @return [Boolean] true if the values should be included in the parent
|
169
|
+
# namespace.
|
170
|
+
def include_in_parent?
|
171
|
+
child_namespace? && file.nil?
|
172
|
+
end
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
# @abstract Subclass and override {#parse_data} to implement parsing logic
|
177
|
+
# for a particular config file format.
|
178
|
+
#
|
179
|
+
# @param data [String] The content of the file to be parsed.
|
180
|
+
# @param filename [String] The path to the file to be parsed.
|
181
|
+
#
|
182
|
+
# @return [Hash{String => Object}] the data to be loaded into the
|
183
|
+
# namespace.
|
184
|
+
def parse_data(_data, _filename)
|
185
|
+
{}
|
186
|
+
end
|
187
|
+
|
188
|
+
# Read the file associated with the namespace.
|
189
|
+
#
|
190
|
+
# @raise [PDK::Config::LoadError] if the file is removed during read.
|
191
|
+
# @raise [PDK::Config::LoadError] if the user doesn't have the
|
192
|
+
# permissions needed to read the file.
|
193
|
+
# @return [String,nil] the contents of the file or nil if the file does
|
194
|
+
# not exist.
|
195
|
+
def load_data
|
196
|
+
return if file.nil?
|
197
|
+
return unless PDK::Util::Filesystem.file?(file)
|
198
|
+
|
199
|
+
PDK::Util::Filesystem.read_file(file)
|
200
|
+
rescue Errno::ENOENT => e
|
201
|
+
raise PDK::Config::LoadError, e.message
|
202
|
+
rescue Errno::EACCES
|
203
|
+
raise PDK::Config::LoadError, _('Unable to open %{file} for reading') % {
|
204
|
+
file: file,
|
205
|
+
}
|
206
|
+
end
|
207
|
+
|
208
|
+
# @abstract Subclass and override {#save_data} to implement generating
|
209
|
+
# logic for a particular config file format.
|
210
|
+
#
|
211
|
+
# @param data [Hash{String => Object}] the data stored in the namespace
|
212
|
+
#
|
213
|
+
# @return [String] the serialized contents of the namespace suitable for
|
214
|
+
# writing to disk.
|
215
|
+
def serialize_data(_data); end
|
216
|
+
|
217
|
+
# Persist the contents of the namespace to disk.
|
218
|
+
#
|
219
|
+
# Directories will be automatically created and the contents of the
|
220
|
+
# namespace will be serialized automatically with {#serialize_data}.
|
221
|
+
#
|
222
|
+
# @raise [PDK::Config::LoadError] if one of the intermediary path components
|
223
|
+
# exist but is not a directory.
|
224
|
+
# @raise [PDK::Config::LoadError] if the user does not have the
|
225
|
+
# permissions needed to write the file.
|
226
|
+
#
|
227
|
+
# @return [nil]
|
228
|
+
def save_data
|
229
|
+
return if file.nil?
|
230
|
+
|
231
|
+
FileUtils.mkdir_p(File.dirname(file))
|
232
|
+
|
233
|
+
PDK::Util::Filesystem.write_file(file, serialize_data(to_h))
|
234
|
+
rescue Errno::EACCES
|
235
|
+
raise PDK::Config::LoadError, _('Unable to open %{file} for writing') % {
|
236
|
+
file: file,
|
237
|
+
}
|
238
|
+
rescue SystemCallError => e
|
239
|
+
raise PDK::Config::LoadError, e.message
|
240
|
+
end
|
241
|
+
|
242
|
+
# Memoised accessor for the loaded data.
|
243
|
+
#
|
244
|
+
# @return [Hash<String => Object>] the contents of the namespace.
|
245
|
+
def data
|
246
|
+
@data ||= parse_data(load_data, file).tap do |h|
|
247
|
+
h.default_proc = default_config_value
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# The default behaviour of the namespace when the requested value does
|
252
|
+
# not exist.
|
253
|
+
#
|
254
|
+
# If the value has been pre-configured with {#value} to have a default
|
255
|
+
# value, resolve the default value and set it in the namespace
|
256
|
+
# (triggering a call to {#save_data}. Otherwise, set the value to a new
|
257
|
+
# Hash to allow for arbitrary level of nested values.
|
258
|
+
#
|
259
|
+
# @return [Proc] suitable for use by {Hash#default_proc}.
|
260
|
+
def default_config_value
|
261
|
+
->(hash, key) do
|
262
|
+
if @values.key?(key) && @values[key].default?
|
263
|
+
self[key] = @values[key].default
|
264
|
+
else
|
265
|
+
hash[key] = {}.tap do |h|
|
266
|
+
h.default_proc = default_config_value
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|