pdk 1.9.0 → 3.2.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.
Files changed (163) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +744 -711
  3. data/README.md +23 -21
  4. data/lib/pdk/answer_file.rb +3 -112
  5. data/lib/pdk/bolt.rb +20 -0
  6. data/lib/pdk/cli/build.rb +51 -54
  7. data/lib/pdk/cli/bundle.rb +33 -29
  8. data/lib/pdk/cli/console.rb +148 -0
  9. data/lib/pdk/cli/convert.rb +46 -37
  10. data/lib/pdk/cli/env.rb +51 -0
  11. data/lib/pdk/cli/errors.rb +4 -3
  12. data/lib/pdk/cli/exec/command.rb +285 -0
  13. data/lib/pdk/cli/exec/interactive_command.rb +109 -0
  14. data/lib/pdk/cli/exec.rb +32 -201
  15. data/lib/pdk/cli/exec_group.rb +79 -43
  16. data/lib/pdk/cli/get/config.rb +26 -0
  17. data/lib/pdk/cli/get.rb +22 -0
  18. data/lib/pdk/cli/new/class.rb +20 -22
  19. data/lib/pdk/cli/new/defined_type.rb +21 -21
  20. data/lib/pdk/cli/new/fact.rb +27 -0
  21. data/lib/pdk/cli/new/function.rb +27 -0
  22. data/lib/pdk/cli/new/module.rb +40 -29
  23. data/lib/pdk/cli/new/provider.rb +18 -18
  24. data/lib/pdk/cli/new/task.rb +23 -22
  25. data/lib/pdk/cli/new/test.rb +52 -0
  26. data/lib/pdk/cli/new/transport.rb +27 -0
  27. data/lib/pdk/cli/new.rb +15 -9
  28. data/lib/pdk/cli/release/prep.rb +39 -0
  29. data/lib/pdk/cli/release/publish.rb +46 -0
  30. data/lib/pdk/cli/release.rb +185 -0
  31. data/lib/pdk/cli/remove/config.rb +83 -0
  32. data/lib/pdk/cli/remove.rb +22 -0
  33. data/lib/pdk/cli/set/config.rb +121 -0
  34. data/lib/pdk/cli/set.rb +22 -0
  35. data/lib/pdk/cli/test/unit.rb +71 -69
  36. data/lib/pdk/cli/test.rb +9 -8
  37. data/lib/pdk/cli/update.rb +38 -21
  38. data/lib/pdk/cli/util/command_redirector.rb +13 -3
  39. data/lib/pdk/cli/util/interview.rb +25 -9
  40. data/lib/pdk/cli/util/option_normalizer.rb +6 -6
  41. data/lib/pdk/cli/util/option_validator.rb +19 -9
  42. data/lib/pdk/cli/util/spinner.rb +13 -0
  43. data/lib/pdk/cli/util/update_manager_printer.rb +82 -0
  44. data/lib/pdk/cli/util.rb +105 -48
  45. data/lib/pdk/cli/validate.rb +96 -111
  46. data/lib/pdk/cli.rb +134 -87
  47. data/lib/pdk/config/errors.rb +5 -0
  48. data/lib/pdk/config/ini_file.rb +184 -0
  49. data/lib/pdk/config/ini_file_setting.rb +35 -0
  50. data/lib/pdk/config/json.rb +35 -0
  51. data/lib/pdk/config/json_schema_namespace.rb +137 -0
  52. data/lib/pdk/config/json_schema_setting.rb +51 -0
  53. data/lib/pdk/config/json_with_schema.rb +47 -0
  54. data/lib/pdk/config/namespace.rb +362 -0
  55. data/lib/pdk/config/setting.rb +134 -0
  56. data/lib/pdk/config/task_schema.json +116 -0
  57. data/lib/pdk/config/validator.rb +31 -0
  58. data/lib/pdk/config/yaml.rb +41 -0
  59. data/lib/pdk/config/yaml_with_schema.rb +51 -0
  60. data/lib/pdk/config.rb +304 -0
  61. data/lib/pdk/context/control_repo.rb +61 -0
  62. data/lib/pdk/context/module.rb +28 -0
  63. data/lib/pdk/context/none.rb +22 -0
  64. data/lib/pdk/context.rb +98 -0
  65. data/lib/pdk/control_repo.rb +89 -0
  66. data/lib/pdk/generate/defined_type.rb +27 -33
  67. data/lib/pdk/generate/fact.rb +26 -0
  68. data/lib/pdk/generate/function.rb +49 -0
  69. data/lib/pdk/generate/module.rb +160 -153
  70. data/lib/pdk/generate/provider.rb +16 -69
  71. data/lib/pdk/generate/puppet_class.rb +27 -32
  72. data/lib/pdk/generate/puppet_object.rb +100 -159
  73. data/lib/pdk/generate/task.rb +34 -51
  74. data/lib/pdk/generate/transport.rb +34 -0
  75. data/lib/pdk/generate.rb +21 -8
  76. data/lib/pdk/logger.rb +24 -6
  77. data/lib/pdk/module/build.rb +125 -37
  78. data/lib/pdk/module/convert.rb +146 -65
  79. data/lib/pdk/module/metadata.rb +72 -71
  80. data/lib/pdk/module/release.rb +255 -0
  81. data/lib/pdk/module/update.rb +48 -37
  82. data/lib/pdk/module/update_manager.rb +75 -39
  83. data/lib/pdk/module.rb +10 -2
  84. data/lib/pdk/monkey_patches.rb +268 -0
  85. data/lib/pdk/report/event.rb +36 -48
  86. data/lib/pdk/report.rb +35 -22
  87. data/lib/pdk/template/fetcher/git.rb +84 -0
  88. data/lib/pdk/template/fetcher/local.rb +29 -0
  89. data/lib/pdk/template/fetcher.rb +100 -0
  90. data/lib/pdk/template/renderer/v1/legacy_template_dir.rb +108 -0
  91. data/lib/pdk/template/renderer/v1/renderer.rb +131 -0
  92. data/lib/pdk/template/renderer/v1/template_file.rb +100 -0
  93. data/lib/pdk/template/renderer/v1.rb +25 -0
  94. data/lib/pdk/template/renderer.rb +97 -0
  95. data/lib/pdk/template/template_dir.rb +67 -0
  96. data/lib/pdk/template.rb +52 -0
  97. data/lib/pdk/tests/unit.rb +101 -51
  98. data/lib/pdk/util/bundler.rb +44 -42
  99. data/lib/pdk/util/changelog_generator.rb +138 -0
  100. data/lib/pdk/util/env.rb +48 -0
  101. data/lib/pdk/util/filesystem.rb +139 -2
  102. data/lib/pdk/util/git.rb +108 -8
  103. data/lib/pdk/util/json_finder.rb +86 -0
  104. data/lib/pdk/util/puppet_strings.rb +125 -0
  105. data/lib/pdk/util/puppet_version.rb +71 -87
  106. data/lib/pdk/util/ruby_version.rb +49 -25
  107. data/lib/pdk/util/template_uri.rb +283 -0
  108. data/lib/pdk/util/vendored_file.rb +34 -42
  109. data/lib/pdk/util/version.rb +11 -10
  110. data/lib/pdk/util/windows/api_types.rb +74 -44
  111. data/lib/pdk/util/windows/file.rb +31 -27
  112. data/lib/pdk/util/windows/process.rb +74 -0
  113. data/lib/pdk/util/windows/string.rb +19 -12
  114. data/lib/pdk/util/windows.rb +2 -0
  115. data/lib/pdk/util.rb +111 -124
  116. data/lib/pdk/validate/control_repo/control_repo_validator_group.rb +23 -0
  117. data/lib/pdk/validate/control_repo/environment_conf_validator.rb +98 -0
  118. data/lib/pdk/validate/external_command_validator.rb +213 -0
  119. data/lib/pdk/validate/internal_ruby_validator.rb +101 -0
  120. data/lib/pdk/validate/invokable_validator.rb +238 -0
  121. data/lib/pdk/validate/metadata/metadata_json_lint_validator.rb +84 -0
  122. data/lib/pdk/validate/metadata/metadata_syntax_validator.rb +76 -0
  123. data/lib/pdk/validate/metadata/metadata_validator_group.rb +20 -0
  124. data/lib/pdk/validate/puppet/puppet_epp_validator.rb +131 -0
  125. data/lib/pdk/validate/puppet/puppet_lint_validator.rb +66 -0
  126. data/lib/pdk/validate/puppet/puppet_plan_syntax_validator.rb +38 -0
  127. data/lib/pdk/validate/puppet/puppet_syntax_validator.rb +135 -0
  128. data/lib/pdk/validate/puppet/puppet_validator_group.rb +22 -0
  129. data/lib/pdk/validate/ruby/ruby_rubocop_validator.rb +79 -0
  130. data/lib/pdk/validate/ruby/ruby_validator_group.rb +19 -0
  131. data/lib/pdk/validate/tasks/tasks_metadata_lint_validator.rb +83 -0
  132. data/lib/pdk/validate/tasks/tasks_name_validator.rb +45 -0
  133. data/lib/pdk/validate/tasks/tasks_validator_group.rb +20 -0
  134. data/lib/pdk/validate/validator.rb +120 -0
  135. data/lib/pdk/validate/validator_group.rb +107 -0
  136. data/lib/pdk/validate/yaml/yaml_syntax_validator.rb +86 -0
  137. data/lib/pdk/validate/yaml/yaml_validator_group.rb +19 -0
  138. data/lib/pdk/validate.rb +86 -12
  139. data/lib/pdk/version.rb +2 -2
  140. data/lib/pdk.rb +60 -10
  141. metadata +138 -100
  142. data/lib/pdk/cli/module/build.rb +0 -14
  143. data/lib/pdk/cli/module/generate.rb +0 -45
  144. data/lib/pdk/cli/module.rb +0 -14
  145. data/lib/pdk/i18n.rb +0 -4
  146. data/lib/pdk/module/templatedir.rb +0 -321
  147. data/lib/pdk/template_file.rb +0 -95
  148. data/lib/pdk/validate/base_validator.rb +0 -215
  149. data/lib/pdk/validate/metadata/metadata_json_lint.rb +0 -86
  150. data/lib/pdk/validate/metadata/metadata_syntax.rb +0 -109
  151. data/lib/pdk/validate/metadata_validator.rb +0 -30
  152. data/lib/pdk/validate/puppet/puppet_lint.rb +0 -67
  153. data/lib/pdk/validate/puppet/puppet_syntax.rb +0 -112
  154. data/lib/pdk/validate/puppet_validator.rb +0 -30
  155. data/lib/pdk/validate/ruby/rubocop.rb +0 -77
  156. data/lib/pdk/validate/ruby_validator.rb +0 -29
  157. data/lib/pdk/validate/tasks/metadata_lint.rb +0 -126
  158. data/lib/pdk/validate/tasks/name.rb +0 -88
  159. data/lib/pdk/validate/tasks_validator.rb +0 -33
  160. data/lib/pdk/validate/yaml/syntax.rb +0 -109
  161. data/lib/pdk/validate/yaml_validator.rb +0 -31
  162. data/locales/config.yaml +0 -21
  163. data/locales/pdk.pot +0 -1291
data/lib/pdk/cli.rb CHANGED
@@ -1,113 +1,160 @@
1
1
  require 'cri'
2
2
 
3
+ require 'pdk'
3
4
  require 'pdk/cli/errors'
4
- require 'pdk/cli/util'
5
- require 'pdk/cli/util/command_redirector'
6
- require 'pdk/cli/util/option_normalizer'
7
- require 'pdk/cli/util/option_validator'
8
- require 'pdk/cli/exec_group'
9
- require 'pdk/generate/module'
10
- require 'pdk/i18n'
11
- require 'pdk/logger'
12
- require 'pdk/report'
13
- require 'pdk/util/version'
14
- require 'pdk/util/puppet_version'
15
-
16
- module PDK::CLI
17
- def self.run(args)
18
- @base_cmd.run(args)
19
- rescue PDK::CLI::ExitWithError => e
20
- PDK.logger.send(e.log_level, e.message)
21
-
22
- exit e.exit_code
23
- rescue PDK::CLI::FatalError => e
24
- PDK.logger.fatal(e.message) if e.message
25
-
26
- # If FatalError was raised as the result of another exception, send the
27
- # details of that exception to the debug log. If there was no cause
28
- # (FatalError raised on its own outside a rescue block), send the details
29
- # of the FatalError exception to the debug log.
30
- cause = e.cause
31
- if cause.nil?
32
- e.backtrace.each { |line| PDK.logger.debug(line) }
33
- else
34
- PDK.logger.debug("#{cause.class}: #{cause.message}")
35
- cause.backtrace.each { |line| PDK.logger.debug(line) }
36
- end
37
-
38
- exit e.exit_code
39
- end
40
5
 
41
- def self.template_url_option(dsl)
42
- desc = _('Specifies the URL to the template to use when creating new modules or classes. (default: %{default_url})') % { default_url: PDK::Util.default_template_url }
6
+ module TTY
7
+ autoload :Prompt, 'tty/prompt'
43
8
 
44
- dsl.option nil, 'template-url', desc, argument: :required
9
+ class Prompt
10
+ autoload :Test, 'tty/prompt/test'
45
11
  end
12
+ end
46
13
 
47
- def self.skip_interview_option(dsl)
48
- dsl.option nil, 'skip-interview', _('When specified, skips interactive querying of metadata.')
14
+ module Cri
15
+ class Command
16
+ class CriExitException
17
+ def initialize(is_error:)
18
+ @is_error = is_error
19
+ end
20
+ end
49
21
  end
22
+ end
50
23
 
51
- def self.full_interview_option(dsl)
52
- dsl.option nil, 'full-interview', _('When specified, interactive querying of metadata will include all optional questions.')
53
- end
24
+ module PDK
25
+ module CLI
26
+ autoload :Util, 'pdk/cli/util'
27
+
28
+ # Attempt to anonymise the raw ARGV array if the command parsing failed.
29
+ #
30
+ # If an item does not start with '-' but is preceeded by an item that does
31
+ # start with '-', assume that these items are an option/value pair and redact
32
+ # the value. Any additional values that do not start with '-' that follow an
33
+ # option/value pair are assumed to be arguments (rather than subcommand
34
+ # names) and are also redacted.
35
+ #
36
+ # @example
37
+ # # Where PDK::CLI.args => ['new', 'plan', '--some', 'value', 'plan_name']
38
+ #
39
+ # PDK::CLI.anonymised_args
40
+ # => ['new', 'plan', '--some', 'redacted', 'redacted']
41
+ #
42
+ # @return Array[String] the command arguments with any identifying values
43
+ # redacted.
44
+ def self.anonymised_args
45
+ in_args = false
46
+ @args.map do |arg|
47
+ if arg.start_with?('-')
48
+ in_args = true
49
+ arg
50
+ else
51
+ in_args ? 'redacted' : arg
52
+ end
53
+ end
54
+ end
54
55
 
55
- def self.puppet_version_options(dsl)
56
- dsl.option nil, 'puppet-version', _('Puppet version to run tests or validations against.'), argument: :required
57
- dsl.option nil, 'pe-version', _('Puppet Enterprise version to run tests or validations against.'), argument: :required
58
- end
56
+ def self.run(args)
57
+ @args = args
58
+ @base_cmd.run(args)
59
+ rescue PDK::CLI::ExitWithError => e
60
+ PDK.logger.send(e.log_level, e.message)
61
+
62
+ exit e.exit_code
63
+ rescue PDK::CLI::FatalError => e
64
+ PDK.logger.fatal(e.message) if e.message
65
+
66
+ # If FatalError was raised as the result of another exception, send the
67
+ # details of that exception to the debug log. If there was no cause
68
+ # (FatalError raised on its own outside a rescue block), send the details
69
+ # of the FatalError exception to the debug log.
70
+ cause = e.cause
71
+ if cause.nil?
72
+ e.backtrace.each { |line| PDK.logger.debug(line) }
73
+ else
74
+ PDK.logger.debug("#{cause.class}: #{cause.message}")
75
+ cause.backtrace.each { |line| PDK.logger.debug(line) }
76
+ end
77
+
78
+ exit e.exit_code
79
+ end
59
80
 
60
- def self.puppet_dev_option(dsl)
61
- dsl.option nil,
62
- 'puppet-dev',
63
- _('When specified, PDK will validate or test against the current Puppet source from github.com. To use this option, you must have network access to https://github.com.')
64
- end
81
+ def self.template_url_option(dsl)
82
+ require 'pdk/util/template_uri'
65
83
 
66
- @base_cmd = Cri::Command.define do
67
- name 'pdk'
68
- usage _('pdk command [options]')
69
- summary _('Puppet Development Kit')
70
- description _('The shortest path to better modules.')
71
- default_subcommand 'help'
84
+ desc = format('Specifies the URL to the template to use when creating new modules or classes. (default: %{default_url})', default_url: PDK::Util::TemplateURI.default_template_uri)
72
85
 
73
- flag nil, :version, _('Show version of pdk.') do |_, _|
74
- puts PDK::Util::Version.version_string
75
- exit 0
86
+ dsl.option nil, 'template-url', desc, argument: :required
76
87
  end
77
88
 
78
- flag :h, :help, _('Show help for this command.') do |_, c|
79
- puts c.help
80
- exit 0
89
+ def self.template_ref_option(dsl)
90
+ dsl.option nil, 'template-ref', 'Specifies the template git branch or tag to use when creating new modules or classes.', argument: :required
81
91
  end
82
92
 
83
- format_desc = _(
84
- "Specify desired output format. Valid formats are '%{available_formats}'. " \
85
- 'You may also specify a file to which the formatted output is sent, ' \
86
- "for example: '--format=junit:report.xml'. This option may be specified " \
87
- 'multiple times if each option specifies a distinct target file.',
88
- ) % { available_formats: PDK::Report.formats.join("', '") }
93
+ def self.skip_interview_option(dsl)
94
+ dsl.option nil, 'skip-interview', 'When specified, skips interactive querying of metadata.'
95
+ end
89
96
 
90
- option :f, :format, format_desc, argument: :required, multiple: true do |values|
91
- PDK::CLI::Util::OptionNormalizer.report_formats(values.compact)
97
+ def self.full_interview_option(dsl)
98
+ dsl.option nil, 'full-interview', 'When specified, interactive querying of metadata will include all optional questions.'
92
99
  end
93
100
 
94
- flag :d, :debug, _('Enable debug output.') do |_, _|
95
- PDK.logger.enable_debug_output
101
+ def self.puppet_version_options(dsl)
102
+ dsl.option nil, 'puppet-version', 'Puppet version to run tests or validations against.', argument: :required
103
+ dsl.option nil, 'pe-version', '(Deprecated) Puppet Enterprise version to run tests or validations against.', argument: :required
96
104
  end
97
105
 
98
- option nil, 'answer-file', _('Path to an answer file.'), argument: :required, hidden: true do |value|
99
- PDK.answer_file = value
106
+ def self.puppet_dev_option(dsl)
107
+ dsl.option nil,
108
+ 'puppet-dev',
109
+ 'When specified, PDK will validate or test against the current Puppet source from github.com. To use this option, you must have network access to https://github.com.'
100
110
  end
101
- end
102
111
 
103
- require 'pdk/cli/bundle'
104
- require 'pdk/cli/build'
105
- require 'pdk/cli/convert'
106
- require 'pdk/cli/new'
107
- require 'pdk/cli/test'
108
- require 'pdk/cli/update'
109
- require 'pdk/cli/validate'
110
- require 'pdk/cli/module'
112
+ @base_cmd = Cri::Command.define do
113
+ name 'pdk'
114
+ usage 'pdk command [options]'
115
+ summary 'Puppet Development Kit'
116
+ description 'The shortest path to better modules.'
117
+ default_subcommand 'help'
118
+
119
+ flag nil, :version, 'Show version of pdk.' do |_, _|
120
+ puts PDK::Util::Version.version_string
121
+ exit 0
122
+ end
123
+
124
+ flag :h, :help, 'Show help for this command.' do |_, c|
125
+ puts c.help
126
+ exit 0
127
+ end
128
+
129
+ format_desc =
130
+ "Specify desired output format. Valid formats are '#{PDK::Report.formats.join("', '")}'. " \
131
+ 'You may also specify a file to which the formatted output is sent, ' \
132
+ "for example: '--format=junit:report.xml'. This option may be specified " \
133
+ 'multiple times if each option specifies a distinct target file.'
134
+
135
+ option :f, :format, format_desc, argument: :required, multiple: true do |values|
136
+ PDK::CLI::Util::OptionNormalizer.report_formats(values.compact)
137
+ end
138
+
139
+ flag :d, :debug, 'Enable debug output.' do |_, _|
140
+ PDK.logger.enable_debug_output
141
+ end
142
+ end
111
143
 
112
- @base_cmd.add_command Cri::Command.new_basic_help
144
+ require 'pdk/cli/bundle'
145
+ require 'pdk/cli/build'
146
+ require 'pdk/cli/convert'
147
+ require 'pdk/cli/env'
148
+ require 'pdk/cli/get'
149
+ require 'pdk/cli/new'
150
+ require 'pdk/cli/set'
151
+ require 'pdk/cli/test'
152
+ require 'pdk/cli/update'
153
+ require 'pdk/cli/validate'
154
+ # require 'pdk/cli/console' Temporarily disabled while we work on the puppet-debugger gem. It will be back soon! (CONT-1154)
155
+ require 'pdk/cli/release'
156
+ require 'pdk/cli/remove'
157
+
158
+ @base_cmd.add_command Cri::Command.new_basic_help
159
+ end
113
160
  end
@@ -0,0 +1,5 @@
1
+ module PDK
2
+ class Config
3
+ class LoadError < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,184 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ class Config
5
+ # Represents a configuration file using the INI file format
6
+ class IniFile < Namespace
7
+ # Ini Files have very strict valdiation rules which are set in the IniFileSetting class
8
+ # @see PDK::Config::Namespace.default_setting_class
9
+ def default_setting_class
10
+ PDK::Config::IniFileSetting
11
+ end
12
+
13
+ # Parses an IniFile document.
14
+ #
15
+ # @see PDK::Config::Namespace.parse_file
16
+ def parse_file(filename)
17
+ raise unless block_given?
18
+
19
+ data = load_data(filename)
20
+ return if data.nil? || data.empty?
21
+
22
+ ini_file = IniFileImpl.parse(data)
23
+ ini_file.to_hash.each do |name, value|
24
+ begin
25
+ new_setting = PDK::Config::IniFileSetting.new(name, self, value)
26
+ rescue StandardError
27
+ # We just ignore invalid initial settings
28
+ new_setting = PDK::Config::IniFileSetting.new(name, self, nil)
29
+ end
30
+
31
+ yield name, new_setting
32
+ end
33
+ end
34
+
35
+ # Serializes object data into an INI file string.
36
+ #
37
+ # @see PDK::Config::Namespace.serialize_data
38
+ def serialize_data(data)
39
+ default_lines = ''
40
+ lines = ''
41
+ data.each do |name, value|
42
+ next if value.nil?
43
+
44
+ if value.is_a?(Hash)
45
+ # Hashes are an INI section
46
+ lines += "\n[#{name}]\n"
47
+ value.each do |child_name, child_value|
48
+ next if child_value.nil?
49
+
50
+ lines += "#{child_name} = #{munge_serialized_value(child_value)}\n"
51
+ end
52
+ else
53
+ default_lines += "#{name} = #{munge_serialized_value(value)}\n"
54
+ end
55
+ end
56
+
57
+ default_lines + lines
58
+ end
59
+
60
+ private
61
+
62
+ def munge_serialized_value(value)
63
+ value = value.to_s unless value.is_a?(String)
64
+ # Add enclosing quotes if there's a space in the value
65
+ value = "\"#{value}\"" if value.include?(' ')
66
+ value
67
+ end
68
+
69
+ # Adapted from https://raw.githubusercontent.com/puppetlabs/puppet/6c257fc7827989c2af2901f974666f0f23611153/lib/puppet/settings/ini_file.rb
70
+ # rubocop:disable Style/PerlBackrefs
71
+ # rubocop:disable Style/RedundantSelf
72
+ # rubocop:disable Style/StringLiterals
73
+ class IniFileImpl
74
+ DEFAULT_SECTION_NAME = 'default_section_name'.freeze
75
+
76
+ def self.parse(config_fh)
77
+ config = new([DefaultSection.new])
78
+ config_fh.each_line do |line|
79
+ case line.chomp
80
+ when /^(\s*)\[([[:word:]]+)\](\s*)$/
81
+ config.append(SectionLine.new($1, $2, $3))
82
+ when /^(\s*)([[:word:]]+)(\s*=\s*)(.*?)(\s*)$/
83
+ config.append(SettingLine.new($1, $2, $3, $4, $5))
84
+ else
85
+ config.append(Line.new(line))
86
+ end
87
+ end
88
+
89
+ config
90
+ end
91
+
92
+ def initialize(lines = [])
93
+ @lines = lines
94
+ end
95
+
96
+ def to_hash
97
+ result = {}
98
+
99
+ current_section_name = nil
100
+ @lines.each do |line|
101
+ if line.instance_of?(SectionLine)
102
+ current_section_name = line.name
103
+ result[current_section_name] = {}
104
+ elsif line.instance_of?(SettingLine)
105
+ if current_section_name.nil?
106
+ result[line.name] = munge_value(line.value)
107
+ else
108
+ result[current_section_name][line.name] = munge_value(line.value)
109
+ end
110
+ end
111
+ end
112
+
113
+ result
114
+ end
115
+
116
+ def munge_value(value)
117
+ value = value.to_s unless value.is_a?(String)
118
+ # Strip enclosing quotes
119
+ value = value.slice(1...-1) if value.start_with?('"') && value.end_with?('"')
120
+ value
121
+ end
122
+
123
+ def append(line)
124
+ line.previous = @lines.last
125
+ @lines << line
126
+ end
127
+
128
+ module LineNumber
129
+ attr_accessor :previous
130
+
131
+ def line_number
132
+ line = 0
133
+ previous_line = previous
134
+ while previous_line
135
+ line += 1
136
+ previous_line = previous_line.previous
137
+ end
138
+ line
139
+ end
140
+ end
141
+
142
+ Line = Struct.new(:text) do
143
+ include LineNumber
144
+
145
+ def to_s
146
+ text
147
+ end
148
+ end
149
+
150
+ SettingLine = Struct.new(:prefix, :name, :infix, :value, :suffix) do
151
+ include LineNumber
152
+
153
+ def to_s
154
+ "#{prefix}#{name}#{infix}#{value}#{suffix}"
155
+ end
156
+
157
+ def ==(other)
158
+ super(other) && self.line_number == other.line_number
159
+ end
160
+ end
161
+
162
+ SectionLine = Struct.new(:prefix, :name, :suffix) do
163
+ include LineNumber
164
+
165
+ def to_s
166
+ "#{prefix}[#{name}]#{suffix}"
167
+ end
168
+ end
169
+
170
+ class DefaultSection < SectionLine
171
+ attr_accessor :write_sectionline
172
+
173
+ def initialize
174
+ @write_sectionline = false
175
+ super("", DEFAULT_SECTION_NAME, "")
176
+ end
177
+ end
178
+ end
179
+ # rubocop:enable Style/StringLiterals
180
+ # rubocop:enable Style/RedundantSelf
181
+ # rubocop:enable Style/PerlBackrefs
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,35 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ class Config
5
+ class IniFileSetting < PDK::Config::Setting
6
+ # Initialises the PDK::Config::JSONSchemaSetting object.
7
+ #
8
+ # @see PDK::Config::Setting.initialize
9
+ def initialize(_name, namespace, initial_value = nil)
10
+ raise 'The IniFileSetting object can only be created within the IniFile Namespace' unless namespace.is_a?(PDK::Config::IniFile)
11
+
12
+ super
13
+ validate!(initial_value) unless initial_value.nil?
14
+ end
15
+
16
+ # Verifies that the new setting value is valid in an Ini File
17
+ #
18
+ # @see PDK::Config::Setting.validate!
19
+ def validate!(value)
20
+ # We're very restrictive here. Realistically Ini files only have string types
21
+ return if value.nil? || value.is_a?(String) || value.is_a?(Integer)
22
+
23
+ # The only other valid-ish type is a Hash
24
+ raise ArgumentError, format('The setting %{key} may only be a String or Integer, not %{class}', key: qualified_name, class: value.class) unless value.is_a?(Hash)
25
+
26
+ # Any hashes can only have a single String/Integer value
27
+ value.each do |child_name, child_value|
28
+ next if child_value.nil? || child_value.is_a?(String) || child_value.is_a?(Integer)
29
+
30
+ raise ArgumentError, format('The setting %{key} may only be a String or Integer, not %{class}', key: "#{qualified_name}.#{child_name}", class: child_value.class)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ class Config
5
+ class JSON < Namespace
6
+ # Parses a JSON document.
7
+ #
8
+ # @see PDK::Config::Namespace.parse_file
9
+ def parse_file(filename)
10
+ raise unless block_given?
11
+
12
+ data = load_data(filename)
13
+ return if data.nil? || data.empty?
14
+
15
+ require 'json'
16
+
17
+ data = ::JSON.parse(data)
18
+ return if data.nil? || data.empty?
19
+
20
+ data.each { |k, v| yield k, PDK::Config::Setting.new(k, self, v) }
21
+ rescue ::JSON::ParserError => e
22
+ raise PDK::Config::LoadError, e.message
23
+ end
24
+
25
+ # Serializes object data into a JSON string.
26
+ #
27
+ # @see PDK::Config::Namespace.serialize_data
28
+ def serialize_data(data)
29
+ require 'json'
30
+
31
+ ::JSON.pretty_generate(data)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,137 @@
1
+ require 'pdk'
2
+
3
+ # Due to https://github.com/ruby-json-schema/json-schema/issues/439
4
+ # Windows file paths "appear" as uri's with no host and a schema of drive letter
5
+ # Also it is not possible to craft a URI with a Windows path due to the URI object
6
+ # always prepending the path with forward slash (`/`) so Windows paths end up looking
7
+ # like '/C:\schema.json', which can not be read.
8
+ # Instead we need to monkey patch the Schema Reader reader to remove the errant forward slash
9
+ require 'json-schema/schema/reader'
10
+ module JSON
11
+ class Schema
12
+ class Reader
13
+ alias original_read_file read_file
14
+
15
+ def read_file(pathname)
16
+ new_pathname = JSON::Util::URI.unescaped_path(pathname.to_s)
17
+ # Munge the path if it looks like a Windows path e.g. /C:/Windows ...
18
+ # Note that UNC style paths do not have the same issue (\\host\path)
19
+ new_pathname.slice!(0) if new_pathname.start_with?('/') && new_pathname[2] == ':'
20
+ original_read_file(Pathname.new(new_pathname))
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ require 'json-schema'
27
+
28
+ module PDK
29
+ class Config
30
+ class JSONSchemaNamespace < Namespace
31
+ # Initialises the PDK::Config::JSONSchemaNamespace object.
32
+ #
33
+ # @see PDK::Config::Namespace.initialize
34
+ #
35
+ # @option params [String] :schema_file Path to the JSON Schema document
36
+ def initialize(name = nil, file: nil, parent: nil, persistent_defaults: false, schema_file: nil, &block)
37
+ super(name, file: file, parent: parent, persistent_defaults: persistent_defaults, &block)
38
+ @schema_file = schema_file
39
+ @unmanaged_settings = {}
40
+ end
41
+
42
+ # The JSON Schema for the namespace
43
+ #
44
+ # @return [Hash]
45
+ def schema
46
+ document_schema.schema
47
+ end
48
+
49
+ # Whether the schema is valid but empty.
50
+ #
51
+ # @return [Boolean]
52
+ def empty_schema?
53
+ document_schema.schema.empty?
54
+ end
55
+
56
+ # Name of all the top level properties for the schema
57
+ #
58
+ # @return [String[]]
59
+ def schema_property_names
60
+ return [] if schema['properties'].nil?
61
+
62
+ schema['properties'].keys
63
+ end
64
+
65
+ # Extends the to_h namespace method to include unmanaged settings
66
+ #
67
+ # @see PDK::Config::Namespace.to_h
68
+ def to_h
69
+ # This may seem counter-intuitive but we need to call super first as the settings
70
+ # may not have been loaded yet, which means @unmanaged_settings will be empty.
71
+ # We call super first to force any file loading and then merge the unmanaged settings
72
+ settings_hash = super
73
+ @unmanaged_settings = {} if @unmanaged_settings.nil?
74
+ @unmanaged_settings.merge(settings_hash)
75
+ end
76
+
77
+ # Validates a document (Hash table) against the schema
78
+ #
79
+ # @return [Boolean]
80
+ def validate_document!(document)
81
+ ::JSON::Validator.validate!(schema, document)
82
+ end
83
+
84
+ protected
85
+
86
+ # @!attribute [w] unmanaged_settings
87
+ # Sets the list of unmanaged settings. For subclass use only
88
+ #
89
+ # @param unmanaged_settings [Hash<String, Object]] A hashtable of all unmanaged settings which will be persisted, but not visible
90
+ # @protected
91
+ attr_writer :unmanaged_settings
92
+
93
+ private
94
+
95
+ # Override the create_setting method to always fail. This is called
96
+ # to dyanmically add settings. However as we're using a schema, no
97
+ # new settings can be created
98
+ #
99
+ # @see PDK::Config::Namespace.create_missing_setting
100
+ #
101
+ # @private
102
+ def create_missing_setting(key, _initial_value = nil)
103
+ raise ArgumentError, "Setting '#{key}' does not exist'"
104
+ end
105
+
106
+ # Create a valid, but empty schema
107
+ #
108
+ # @return [JSON::Schema]
109
+ def create_empty_schema
110
+ ::JSON::Schema.new({}, 'http://json-schema.org/draft-06/schema#')
111
+ end
112
+
113
+ # Lazily retrieve the JSON schema from disk for this namespace
114
+ #
115
+ # @return [JSON::Schema]
116
+ def document_schema
117
+ return @document_schema unless @document_schema.nil?
118
+
119
+ # Create an empty schema by default.
120
+ @document_schema = create_empty_schema
121
+
122
+ return @document_schema if @schema_file.nil?
123
+
124
+ raise PDK::Config::LoadError, format('Unable to open %{file} for reading. File does not exist', file: @schema_file) unless PDK::Util::Filesystem.file?(@schema_file)
125
+
126
+ # The schema should not query external URI references, except for the meta-schema. Local files are allowed
127
+ schema_reader = ::JSON::Schema::Reader.new(
128
+ accept_file: true,
129
+ accept_uri: proc { |uri| uri.host.nil? || ['json-schema.org'].include?(uri.host) }
130
+ )
131
+ @document_schema = schema_reader.read(Addressable::URI.convert_path(@schema_file))
132
+ rescue ::JSON::Schema::JsonParseError => e
133
+ raise PDK::Config::LoadError, format('Unable to open %{file} for reading. JSON Error: %{msg}', file: @schema_file, msg: e.message)
134
+ end
135
+ end
136
+ end
137
+ end