pdk 1.16.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +167 -11
  3. data/README.md +1 -1
  4. data/lib/pdk.rb +26 -19
  5. data/lib/pdk/answer_file.rb +2 -93
  6. data/lib/pdk/cli.rb +8 -6
  7. data/lib/pdk/cli/config.rb +3 -1
  8. data/lib/pdk/cli/config/get.rb +3 -1
  9. data/lib/pdk/cli/convert.rb +7 -9
  10. data/lib/pdk/cli/env.rb +52 -0
  11. data/lib/pdk/cli/exec/command.rb +13 -0
  12. data/lib/pdk/cli/exec_group.rb +78 -43
  13. data/lib/pdk/cli/get.rb +20 -0
  14. data/lib/pdk/cli/get/config.rb +24 -0
  15. data/lib/pdk/cli/new.rb +2 -0
  16. data/lib/pdk/cli/new/class.rb +2 -1
  17. data/lib/pdk/cli/new/defined_type.rb +2 -1
  18. data/lib/pdk/cli/new/fact.rb +29 -0
  19. data/lib/pdk/cli/new/function.rb +29 -0
  20. data/lib/pdk/cli/new/provider.rb +2 -1
  21. data/lib/pdk/cli/new/task.rb +2 -1
  22. data/lib/pdk/cli/new/test.rb +2 -1
  23. data/lib/pdk/cli/new/transport.rb +2 -1
  24. data/lib/pdk/cli/release/publish.rb +11 -1
  25. data/lib/pdk/cli/remove.rb +20 -0
  26. data/lib/pdk/cli/remove/config.rb +80 -0
  27. data/lib/pdk/cli/set.rb +20 -0
  28. data/lib/pdk/cli/set/config.rb +119 -0
  29. data/lib/pdk/cli/update.rb +6 -8
  30. data/lib/pdk/cli/util.rb +7 -3
  31. data/lib/pdk/cli/util/option_validator.rb +6 -0
  32. data/lib/pdk/cli/util/update_manager_printer.rb +82 -0
  33. data/lib/pdk/cli/validate.rb +26 -44
  34. data/lib/pdk/config.rb +264 -7
  35. data/lib/pdk/config/ini_file.rb +183 -0
  36. data/lib/pdk/config/ini_file_setting.rb +39 -0
  37. data/lib/pdk/config/namespace.rb +25 -5
  38. data/lib/pdk/config/setting.rb +3 -2
  39. data/lib/pdk/context.rb +99 -0
  40. data/lib/pdk/context/control_repo.rb +60 -0
  41. data/lib/pdk/context/module.rb +28 -0
  42. data/lib/pdk/context/none.rb +22 -0
  43. data/lib/pdk/control_repo.rb +40 -0
  44. data/lib/pdk/generate/defined_type.rb +25 -32
  45. data/lib/pdk/generate/fact.rb +25 -0
  46. data/lib/pdk/generate/function.rb +48 -0
  47. data/lib/pdk/generate/module.rb +14 -17
  48. data/lib/pdk/generate/provider.rb +15 -64
  49. data/lib/pdk/generate/puppet_class.rb +25 -31
  50. data/lib/pdk/generate/puppet_object.rb +83 -187
  51. data/lib/pdk/generate/task.rb +28 -46
  52. data/lib/pdk/generate/transport.rb +20 -74
  53. data/lib/pdk/module.rb +1 -1
  54. data/lib/pdk/module/convert.rb +43 -23
  55. data/lib/pdk/module/metadata.rb +6 -2
  56. data/lib/pdk/module/release.rb +3 -9
  57. data/lib/pdk/module/update.rb +7 -11
  58. data/lib/pdk/module/update_manager.rb +7 -0
  59. data/lib/pdk/report.rb +3 -3
  60. data/lib/pdk/report/event.rb +8 -2
  61. data/lib/pdk/template.rb +59 -0
  62. data/lib/pdk/template/fetcher.rb +98 -0
  63. data/lib/pdk/template/fetcher/git.rb +85 -0
  64. data/lib/pdk/template/fetcher/local.rb +28 -0
  65. data/lib/pdk/template/renderer.rb +96 -0
  66. data/lib/pdk/template/renderer/v1.rb +25 -0
  67. data/lib/pdk/template/renderer/v1/legacy_template_dir.rb +116 -0
  68. data/lib/pdk/template/renderer/v1/renderer.rb +132 -0
  69. data/lib/pdk/template/renderer/v1/template_file.rb +102 -0
  70. data/lib/pdk/template/template_dir.rb +67 -0
  71. data/lib/pdk/tests/unit.rb +8 -1
  72. data/lib/pdk/util.rb +38 -39
  73. data/lib/pdk/util/bundler.rb +2 -1
  74. data/lib/pdk/util/changelog_generator.rb +11 -2
  75. data/lib/pdk/util/json_finder.rb +84 -0
  76. data/lib/pdk/util/puppet_strings.rb +3 -3
  77. data/lib/pdk/util/puppet_version.rb +2 -2
  78. data/lib/pdk/util/ruby_version.rb +5 -1
  79. data/lib/pdk/util/template_uri.rb +13 -14
  80. data/lib/pdk/util/vendored_file.rb +1 -2
  81. data/lib/pdk/validate.rb +79 -25
  82. data/lib/pdk/validate/control_repo/control_repo_validator_group.rb +23 -0
  83. data/lib/pdk/validate/control_repo/environment_conf_validator.rb +98 -0
  84. data/lib/pdk/validate/external_command_validator.rb +208 -0
  85. data/lib/pdk/validate/internal_ruby_validator.rb +100 -0
  86. data/lib/pdk/validate/invokable_validator.rb +220 -0
  87. data/lib/pdk/validate/metadata/metadata_json_lint_validator.rb +86 -0
  88. data/lib/pdk/validate/metadata/metadata_syntax_validator.rb +78 -0
  89. data/lib/pdk/validate/metadata/metadata_validator_group.rb +20 -0
  90. data/lib/pdk/validate/puppet/puppet_epp_validator.rb +133 -0
  91. data/lib/pdk/validate/puppet/puppet_lint_validator.rb +66 -0
  92. data/lib/pdk/validate/puppet/puppet_syntax_validator.rb +137 -0
  93. data/lib/pdk/validate/puppet/puppet_validator_group.rb +21 -0
  94. data/lib/pdk/validate/ruby/ruby_rubocop_validator.rb +80 -0
  95. data/lib/pdk/validate/ruby/ruby_validator_group.rb +19 -0
  96. data/lib/pdk/validate/tasks/tasks_metadata_lint_validator.rb +88 -0
  97. data/lib/pdk/validate/tasks/tasks_name_validator.rb +50 -0
  98. data/lib/pdk/validate/tasks/tasks_validator_group.rb +20 -0
  99. data/lib/pdk/validate/validator.rb +118 -0
  100. data/lib/pdk/validate/validator_group.rb +104 -0
  101. data/lib/pdk/validate/yaml/yaml_syntax_validator.rb +95 -0
  102. data/lib/pdk/validate/yaml/yaml_validator_group.rb +19 -0
  103. data/lib/pdk/version.rb +1 -1
  104. data/locales/pdk.pot +477 -313
  105. metadata +77 -35
  106. data/lib/pdk/module/template_dir.rb +0 -115
  107. data/lib/pdk/module/template_dir/base.rb +0 -268
  108. data/lib/pdk/module/template_dir/git.rb +0 -91
  109. data/lib/pdk/module/template_dir/local.rb +0 -21
  110. data/lib/pdk/template_file.rb +0 -96
  111. data/lib/pdk/validate/base_validator.rb +0 -215
  112. data/lib/pdk/validate/metadata/metadata_json_lint.rb +0 -82
  113. data/lib/pdk/validate/metadata/metadata_syntax.rb +0 -111
  114. data/lib/pdk/validate/metadata_validator.rb +0 -26
  115. data/lib/pdk/validate/puppet/puppet_epp.rb +0 -135
  116. data/lib/pdk/validate/puppet/puppet_lint.rb +0 -64
  117. data/lib/pdk/validate/puppet/puppet_syntax.rb +0 -135
  118. data/lib/pdk/validate/puppet_validator.rb +0 -26
  119. data/lib/pdk/validate/ruby/rubocop.rb +0 -72
  120. data/lib/pdk/validate/ruby_validator.rb +0 -26
  121. data/lib/pdk/validate/tasks/metadata_lint.rb +0 -130
  122. data/lib/pdk/validate/tasks/name.rb +0 -90
  123. data/lib/pdk/validate/tasks_validator.rb +0 -29
  124. data/lib/pdk/validate/yaml/syntax.rb +0 -125
  125. data/lib/pdk/validate/yaml_validator.rb +0 -28
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # pdk [![Build Status](https://travis-ci.org/puppetlabs/pdk.svg?branch=master)](https://travis-ci.org/puppetlabs/pdk) [![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/x70e2fqllbaootpd?svg=true)](https://ci.appveyor.com/project/puppetlabs/pdk) [![Coverage Status](https://coveralls.io/repos/github/puppetlabs/pdk/badge.svg?branch=master)](https://coveralls.io/github/puppetlabs/pdk?branch=master)
1
+ # pdk [![Build Status](https://travis-ci.org/puppetlabs/pdk.svg?branch=main)](https://travis-ci.org/puppetlabs/pdk) [![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/x70e2fqllbaootpd?svg=true)](https://ci.appveyor.com/project/puppetlabs/pdk) [![Coverage Status](https://coveralls.io/repos/github/puppetlabs/pdk/badge.svg?branch=main)](https://coveralls.io/github/puppetlabs/pdk?branch=main)
2
2
 
3
3
  * [Installation](#installation)
4
4
  * [Basic usage](#basic-usage)
data/lib/pdk.rb CHANGED
@@ -5,12 +5,13 @@ module PDK
5
5
  autoload :AnswerFile, 'pdk/answer_file'
6
6
  autoload :Bolt, 'pdk/bolt'
7
7
  autoload :Config, 'pdk/config'
8
+ autoload :Context, 'pdk/context'
8
9
  autoload :ControlRepo, 'pdk/control_repo'
9
10
  autoload :Generate, 'pdk/generate'
10
11
  autoload :Logger, 'pdk/logger'
11
12
  autoload :Module, 'pdk/module'
12
13
  autoload :Report, 'pdk/report'
13
- autoload :TemplateFile, 'pdk/template_file'
14
+ autoload :Template, 'pdk/template'
14
15
  autoload :TEMPLATE_REF, 'pdk/version'
15
16
  autoload :Util, 'pdk/util'
16
17
  autoload :Validate, 'pdk/validate'
@@ -30,35 +31,41 @@ module PDK
30
31
  autoload :Unit, 'pdk/tests/unit'
31
32
  end
32
33
 
33
- # Singleton accessor to the current answer file being used by the PDK.
34
- #
35
- # @return [PDK::AnswerFile] The AnswerFile instance currently being used by
36
- # the PDK.
37
- def self.answers
38
- @answer_file ||= PDK::AnswerFile.new
34
+ def self.logger
35
+ @logger ||= PDK::Logger.new
39
36
  end
40
37
 
41
- # Specify the path to a custom answer file that the PDK should use.
42
- #
43
- # @param path [String] A path on disk to the file where the PDK should store
44
- # answers to interactive questions.
45
- def self.answer_file=(path)
46
- @answer_file = PDK::AnswerFile.new(path)
38
+ def self.config
39
+ return @config unless @config.nil?
40
+ options = {}
41
+ options['user.module_defaults.path'] = PDK::Util::Env['PDK_ANSWER_FILE'] unless PDK::Util::Env['PDK_ANSWER_FILE'].nil?
42
+ @config = PDK::Config.new(options)
47
43
  end
48
44
 
49
- def self.logger
50
- @logger ||= PDK::Logger.new
45
+ def self.context
46
+ @context ||= PDK::Context.create(Dir.pwd)
51
47
  end
52
48
 
53
- def self.config
54
- @config ||= PDK::Config.new
49
+ def self.available_feature_flags
50
+ @available_feature_flags ||= %w[
51
+ controlrepo
52
+ ].freeze
53
+ end
54
+
55
+ def self.requested_feature_flags
56
+ @requested_feature_flags ||= (PDK::Util::Env['PDK_FEATURE_FLAGS'] || '').split(',').map { |flag| flag.strip }
57
+ end
58
+
59
+ def self.feature_flag?(flagname)
60
+ return false unless available_feature_flags.include?(flagname)
61
+ requested_feature_flags.include?(flagname)
55
62
  end
56
63
 
57
64
  def self.analytics
58
65
  @analytics ||= PDK::Analytics.build_client(
59
66
  logger: PDK.logger,
60
- disabled: PDK::Util::Env['PDK_DISABLE_ANALYTICS'] || PDK.config.user['analytics']['disabled'],
61
- user_id: PDK.config.user['analytics']['user-id'],
67
+ disabled: PDK::Util::Env['PDK_DISABLE_ANALYTICS'] || PDK.config.get_within_scopes('analytics.disabled', %w[user system]),
68
+ user_id: PDK.config.get_within_scopes('analytics.user-id', %w[user system]),
62
69
  app_id: "UA-139917834-#{PDK::Util.development_mode? ? '2' : '1'}",
63
70
  client: :google_analytics,
64
71
  app_name: 'pdk',
@@ -1,103 +1,12 @@
1
1
  require 'pdk'
2
- autoload :JSON, 'json'
3
2
 
4
3
  module PDK
5
4
  class AnswerFile
6
- attr_reader :answers
7
- attr_reader :answer_file_path
8
-
9
- # Initialises the AnswerFile object, which stores the responses to certain
10
- # interactive questions.
11
- #
12
- # @param answer_file_path [String, nil] The path on disk to the file where
13
- # the answers will be stored and read from. If not specified (or `nil`),
14
- # the default path will be used (see #default_answer_file_path).
15
- #
16
- # @raise (see #read_from_disk)
17
- def initialize(answer_file_path = nil)
18
- @answer_file_path = answer_file_path || default_answer_file_path
19
- @answers = read_from_disk
20
- end
21
-
22
- # Retrieve the stored answer to a question.
23
- #
24
- # @param question [String] The question name/identifying string.
25
- #
26
- # @return [Object] The answer to the question, or `nil` if no answer found.
27
- def [](question)
28
- answers[question]
29
- end
30
-
31
- # Update the stored answers in memory and then save them to disk.
32
- #
33
- # @param new_answers [Hash{String => Object}] The new questions and answers
34
- # to be merged into the existing answers.
35
- #
36
- # @raise [PDK::CLI::FatalError] if the new answers are not provided as
37
- # a Hash.
38
- # @raise (see #save_to_disk)
39
- def update!(new_answers = {})
40
- unless new_answers.is_a?(Hash)
41
- raise PDK::CLI::FatalError, _('Answer file can be updated only with a Hash')
42
- end
43
-
44
- answers.merge!(new_answers)
45
-
46
- save_to_disk
47
- end
48
-
49
- private
50
-
51
5
  # Determine the default path to the answer file.
52
6
  #
53
7
  # @return [String] The path on disk to the default answer file.
54
- def default_answer_file_path
55
- File.join(PDK::Util.cachedir, 'answers.json')
56
- end
57
-
58
- # Read existing answers into memory from the answer file on disk.
59
- #
60
- # @raise [PDK::CLI::FatalError] If the answer file exists but can not be
61
- # read.
62
- #
63
- # @return [Hash{String => Object}] The existing questions and answers.
64
- def read_from_disk
65
- return {} if !PDK::Util::Filesystem.file?(answer_file_path) || PDK::Util::Filesystem.zero?(answer_file_path)
66
-
67
- unless PDK::Util::Filesystem.readable?(answer_file_path)
68
- raise PDK::CLI::FatalError, _("Unable to open '%{file}' for reading") % {
69
- file: answer_file_path,
70
- }
71
- end
72
-
73
- answers = JSON.parse(PDK::Util::Filesystem.read_file(answer_file_path))
74
- if answers.is_a?(Hash)
75
- answers
76
- else
77
- PDK.logger.warn _("Answer file '%{path}' did not contain a valid set of answers, recreating it") % {
78
- path: answer_file_path,
79
- }
80
- {}
81
- end
82
- rescue JSON::JSONError
83
- PDK.logger.warn _("Answer file '%{path}' did not contain valid JSON, recreating it") % {
84
- path: answer_file_path,
85
- }
86
- {}
87
- end
88
-
89
- # Save the in memory answer set to the answer file on disk.
90
- #
91
- # @raise [PDK::CLI::FatalError] if the answer file can not be written to.
92
- def save_to_disk
93
- PDK::Util::Filesystem.mkdir_p(File.dirname(answer_file_path))
94
-
95
- PDK::Util::Filesystem.write_file(answer_file_path, JSON.pretty_generate(answers))
96
- rescue SystemCallError, IOError => e
97
- raise PDK::CLI::FatalError, _("Unable to write '%{file}': %{msg}") % {
98
- file: answer_file_path,
99
- msg: e.message,
100
- }
8
+ def self.default_answer_file_path
9
+ PDK::Util::Filesystem.expand_path(File.join(PDK::Util.cachedir, 'answers.json'))
101
10
  end
102
11
  end
103
12
  end
data/lib/pdk/cli.rb CHANGED
@@ -5,7 +5,10 @@ require 'pdk/cli/errors'
5
5
 
6
6
  module TTY
7
7
  autoload :Prompt, 'tty/prompt'
8
- autoload :TestPrompt, 'tty/test_prompt'
8
+
9
+ class Prompt
10
+ autoload :Test, 'tty/prompt/test'
11
+ end
9
12
  end
10
13
 
11
14
  class Cri::Command::CriExitException
@@ -150,24 +153,23 @@ module PDK::CLI
150
153
  flag :d, :debug, _('Enable debug output.') do |_, _|
151
154
  PDK.logger.enable_debug_output
152
155
  end
153
-
154
- option nil, 'answer-file', _('Path to an answer file.'), argument: :required, hidden: true do |value|
155
- require 'pdk/answer_file'
156
- PDK.answer_file = value
157
- end
158
156
  end
159
157
 
160
158
  require 'pdk/cli/bundle'
161
159
  require 'pdk/cli/build'
162
160
  require 'pdk/cli/config'
163
161
  require 'pdk/cli/convert'
162
+ require 'pdk/cli/env'
163
+ require 'pdk/cli/get'
164
164
  require 'pdk/cli/new'
165
+ require 'pdk/cli/set'
165
166
  require 'pdk/cli/test'
166
167
  require 'pdk/cli/update'
167
168
  require 'pdk/cli/validate'
168
169
  require 'pdk/cli/module'
169
170
  require 'pdk/cli/console'
170
171
  require 'pdk/cli/release'
172
+ require 'pdk/cli/remove'
171
173
 
172
174
  @base_cmd.add_command Cri::Command.new_basic_help
173
175
  end
@@ -2,10 +2,12 @@ module PDK::CLI
2
2
  @config_cmd = @base_cmd.define_command do
3
3
  name 'config'
4
4
  usage _('config [subcommand] [options]')
5
- summary _('Configure the Puppet Development Kit.')
5
+ summary _('(Deprecated) Configure the Puppet Development Kit.')
6
6
  default_subcommand 'help'
7
7
 
8
8
  run do |_opts, args, _cmd|
9
+ PDK.logger.warn _('The \'pdk config\' command is deprecated, please use \'pdk get config\' and \'pdk set config\' instead.')
10
+
9
11
  if args == ['help']
10
12
  PDK::CLI.run(%w[config --help])
11
13
  exit 0
@@ -2,9 +2,11 @@ module PDK::CLI
2
2
  @config_get_cmd = @config_cmd.define_command do
3
3
  name 'get'
4
4
  usage _('config get [name]')
5
- summary _('Retrieve the configuration for <name>. If not specified, retrieve all configuration settings')
5
+ summary _('(Deprecated) Retrieve the configuration for <name>. If not specified, retrieve all configuration settings')
6
6
 
7
7
  run do |_opts, args, _cmd|
8
+ PDK.logger.warn _('The \'pdk config get\' command is deprecated, please use \'pdk get config\' instead.')
9
+
8
10
  item_name = args[0]
9
11
  resolved_config = PDK.config.resolve(item_name)
10
12
  # If the user wanted to know a setting but it doesn't exist, raise an error
@@ -14,14 +14,12 @@ module PDK::CLI
14
14
  flag nil, :'default-template', _('Convert the module to use the default PDK template.')
15
15
 
16
16
  run do |opts, _args, _cmd|
17
- require 'pdk/module/convert'
18
- require 'pdk/cli/util'
17
+ # Write the context information to the debug log
18
+ PDK.context.to_debug_log
19
19
 
20
- PDK::CLI::Util.ensure_in_module!(
21
- check_module_layout: true,
22
- message: _('`pdk convert` can only be run from inside a valid module directory.'),
23
- log_level: :info,
24
- )
20
+ unless PDK.context.is_a?(PDK::Context::Module)
21
+ raise PDK::CLI::ExitWithError, _('`pdk convert` can only be run from inside a valid module directory.')
22
+ end
25
23
 
26
24
  if opts[:noop] && opts[:force]
27
25
  raise PDK::CLI::ExitWithError, _('You can not specify --noop and --force when converting a module')
@@ -31,7 +29,7 @@ module PDK::CLI
31
29
  raise PDK::CLI::ExitWithError, _('You can not specify --template-url and --default-template.') if opts[:'template-url']
32
30
 
33
31
  opts[:'template-url'] = PDK::Util::TemplateURI.default_template_addressable_uri.to_s
34
- PDK.answers.update!('template-url' => nil)
32
+ PDK.config.set(%w[user module_defaults template-url], nil)
35
33
  end
36
34
 
37
35
  PDK::CLI::Util.validate_template_opts(opts)
@@ -48,7 +46,7 @@ module PDK::CLI
48
46
  opts[:'full-interview'] = false
49
47
  end
50
48
 
51
- PDK::Module::Convert.invoke(PDK::Util.module_root, opts)
49
+ PDK::Module::Convert.invoke(PDK.context.root_path, opts)
52
50
  end
53
51
  end
54
52
  end
@@ -0,0 +1,52 @@
1
+ module PDK::CLI
2
+ @env_cmd = @base_cmd.define_command do
3
+ name 'env'
4
+ usage _('env')
5
+ summary _('(Experimental) Output environment variables for specific Puppet context')
6
+ description _(<<-EOF
7
+ [experimental] Aids in setting a CLI context for a specified version of Puppet by outputting export commands for necessary environment variables.
8
+ EOF
9
+ )
10
+
11
+ PDK::CLI.puppet_version_options(self)
12
+ PDK::CLI.puppet_dev_option(self)
13
+
14
+ run do |opts, _args, _cmd|
15
+ require 'pdk/util'
16
+ require 'pdk/util/ruby_version'
17
+
18
+ PDK::CLI::Util.validate_puppet_version_opts(opts)
19
+
20
+ PDK::CLI::Util.analytics_screen_view('env')
21
+
22
+ # Ensure that the correct Ruby is activated before running command.
23
+ puppet_env = PDK::CLI::Util.puppet_from_opts_or_env(opts)
24
+ PDK::Util::RubyVersion.use(puppet_env[:ruby_version])
25
+
26
+ resolved_env = {
27
+ 'PDK_RESOLVED_PUPPET_VERSION' => puppet_env[:gemset][:puppet],
28
+ 'PDK_RESOLVED_RUBY_VERSION' => puppet_env[:ruby_version],
29
+ }
30
+
31
+ resolved_env['GEM_HOME'] = PDK::Util::RubyVersion.gem_home
32
+ gem_path = PDK::Util::RubyVersion.gem_path
33
+ resolved_env['GEM_PATH'] = gem_path.empty? ? resolved_env['GEM_HOME'] : gem_path
34
+
35
+ # Make sure invocation of Ruby prefers our private installation.
36
+ package_binpath = PDK::Util.package_install? ? File.join(PDK::Util.pdk_package_basedir, 'bin') : nil
37
+
38
+ resolved_env['PATH'] = [
39
+ PDK::Util::RubyVersion.bin_path,
40
+ File.join(resolved_env['GEM_HOME'], 'bin'),
41
+ PDK::Util::RubyVersion.gem_paths_raw.map { |gem_path_raw| File.join(gem_path_raw, 'bin') },
42
+ package_binpath,
43
+ PDK::Util::Env['PATH'],
44
+ ].compact.flatten.join(File::PATH_SEPARATOR)
45
+
46
+ resolved_env.each do |var, val|
47
+ puts "export #{var}=\"#{val}\""
48
+ end
49
+ exit 0
50
+ end
51
+ end
52
+ end
@@ -10,6 +10,14 @@ module PDK
10
10
  attr_accessor :environment
11
11
  attr_writer :exec_group
12
12
 
13
+ # The spinner for this command.
14
+ # This should only be used for testing
15
+ #
16
+ # @return [TTY::Spinner, nil]
17
+ #
18
+ # @api private
19
+ attr_reader :spinner
20
+
13
21
  TEMPFILE_MODE = File::RDWR | File::BINARY | File::CREAT | File::TRUNC
14
22
 
15
23
  def initialize(*argv)
@@ -71,6 +79,11 @@ module PDK
71
79
  @environment.merge!(additional_env)
72
80
  end
73
81
 
82
+ # @return [Hash[Symbol => Object]] The result from executing the command
83
+ # :stdout => String : The result of STDOUT
84
+ # :stderr => String : The result of STDERR
85
+ # :exit_code => Integer : The exit code from the command
86
+ # :duration => Float : Number seconds it took to execute
74
87
  def execute!
75
88
  # Start spinning if configured.
76
89
  @spinner.auto_spin if @spinner
@@ -3,66 +3,101 @@ require 'pdk'
3
3
  module PDK
4
4
  module CLI
5
5
  class ExecGroup
6
- def initialize(message, opts = {})
7
- require 'pdk/cli/util'
8
- @options = opts.merge(PDK::CLI::Util.spinner_opts_for_platform)
9
-
10
- if PDK::CLI::Util.interactive?
11
- require 'pdk/cli/util/spinner'
12
-
13
- @spinner = if parallel?
14
- TTY::Spinner::Multi.new("[:spinner] #{message}", @options)
15
- else
16
- TTY::Spinner.new("[:spinner] #{message}", @options)
17
- end
18
- @spinner.auto_spin
6
+ # Execution Group (ExecGroup) factory.
7
+ #
8
+ # @param message [String] A name or message for this group. Provided for backwards compatibility during refactor
9
+ #
10
+ # @param create_options [Hash] A hash options used during creation of the ExecGroup. This are not passed to the new object
11
+ # @option create_options :parallel [Boolean] Whether the group should be executed in Parallel (True) or Serial (False)
12
+ #
13
+ # @param group_opts [Hash] A hash of options used to configure the execution group. Provided for backwards compatibility during refactor
14
+ #
15
+ # @return [ExecGroup]
16
+ def self.create(message, create_options = {}, group_opts = {})
17
+ if create_options[:parallel]
18
+ ParallelExecGroup.new(message, group_opts)
19
+ else
20
+ SerialExecGroup.new(message, group_opts)
19
21
  end
20
-
21
- @threads_or_procs = []
22
- @exit_codes = []
23
22
  end
24
23
 
25
- def parallel?
26
- @options[:parallel].nil? ? true : @options[:parallel]
24
+ # Base class for an Exection Group
25
+ #
26
+ # @param message [String] A name or message for this group. Provided for backwards compatibility during refactor
27
+ #
28
+ # @param opts [Hash] A hash of options used to configure the execution group. Provided for backwards compatibility during refactor
29
+ #
30
+ # @api private
31
+ def initialize(_message, opts = {})
32
+ @options = opts
27
33
  end
28
34
 
29
- def register(&block)
35
+ # Register something to execute as a group
36
+ #
37
+ # @param block [Block] A block of ruby to execute
38
+ #
39
+ # @api private
40
+ def register(&_block)
30
41
  raise PDK::CLI::FatalError, _('No block registered') unless block_given?
42
+ end
43
+
44
+ # The return code of running all registered blocks
45
+ #
46
+ # @return [int] The highest exit code from the blocks
47
+ #
48
+ # @abstract
49
+ def exit_code; end
50
+ end
31
51
 
32
- @threads_or_procs << if parallel?
33
- Thread.new do
34
- GettextSetup.initialize(File.absolute_path('../../../locales', File.dirname(__FILE__)))
35
- GettextSetup.negotiate_locale!(GettextSetup.candidate_locales)
36
- @exit_codes << yield
37
- end
38
- else
39
- block
40
- end
52
+ # Executes registered blocks in serial
53
+ #
54
+ # @see PDK::CLI::ExecGroup
55
+ class SerialExecGroup < ExecGroup
56
+ def initialize(message, opts = {})
57
+ super(message, opts)
58
+ @procs = []
41
59
  end
42
60
 
43
- def add_spinner(message, opts = {})
44
- require 'pdk/cli/util'
61
+ def register(&block)
62
+ super(&block)
45
63
 
46
- return unless PDK::CLI::Util.interactive?
47
- @spinner.register("[:spinner] #{message}", @options.merge(opts).merge(PDK::CLI::Util.spinner_opts_for_platform))
64
+ @procs << block
48
65
  end
49
66
 
50
67
  def exit_code
51
- if parallel?
52
- @threads_or_procs.each(&:join)
53
- else
54
- @exit_codes = @threads_or_procs.map(&:call)
55
- end
68
+ exit_codes = @procs.map(&:call)
69
+ exit_codes.nil? ? 0 : exit_codes.max
70
+ end
71
+ end
56
72
 
57
- exit_code = @exit_codes.max
73
+ # Executes registered blocks in parallel using Ruby threads
74
+ #
75
+ # @see PDK::CLI::ExecGroup
76
+ class ParallelExecGroup < ExecGroup
77
+ def initialize(message, opts = {})
78
+ super(message, opts)
79
+ @threads = []
80
+ @exit_codes = []
81
+ end
82
+
83
+ def register(&block)
84
+ super(&block)
58
85
 
59
- if exit_code.zero? && @spinner
60
- @spinner.success
61
- elsif @spinner
62
- @spinner.error
86
+ # TODO: This executes the thread immediately, whereas the SerialExecGroup executes only when exit_code
87
+ # is called. Need to change this so it uses a kind of ThreadPool to limit to number on concurrent jobs
88
+ # and only starts on the call to exit_code
89
+ # e.g. max_threads = No. of CPUs
90
+ @threads << Thread.new do
91
+ GettextSetup.initialize(File.absolute_path('../../../locales', File.dirname(__FILE__)))
92
+ GettextSetup.negotiate_locale!(GettextSetup.candidate_locales)
93
+ @exit_codes << yield
63
94
  end
95
+ end
64
96
 
65
- exit_code
97
+ def exit_code
98
+ @threads.each(&:join)
99
+ return 0 if @exit_codes.empty?
100
+ @exit_codes.max
66
101
  end
67
102
  end
68
103
  end