pdk 1.9.0 → 3.2.0

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