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