power_stencil 0.5.1 → 0.7.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab-ci.yml +2 -0
  3. data/README.md +3 -7
  4. data/doc/builds.md +10 -1
  5. data/doc/entities.md +59 -50
  6. data/doc/images/power-stencil-entity-creation.svg +247 -114
  7. data/doc/plugins.md +49 -1
  8. data/doc/templates.md +23 -18
  9. data/etc/base_commands_definition.yml +28 -1
  10. data/etc/meta_templates/plugin_seed/psplugin_{entity}.gemspec +3 -1
  11. data/etc/power_stencil.yaml +7 -1
  12. data/etc/templates/plugin_definition/psplugin_{entity}.gemspec +3 -1
  13. data/etc/templates/project/.zzzgitignore.erb +8 -3
  14. data/lib/power_stencil.rb +1 -0
  15. data/lib/power_stencil/command_processors/build.rb +16 -3
  16. data/lib/power_stencil/command_processors/check.rb +12 -0
  17. data/lib/power_stencil/command_processors/create.rb +6 -5
  18. data/lib/power_stencil/command_processors/delete.rb +23 -15
  19. data/lib/power_stencil/command_processors/edit.rb +4 -2
  20. data/lib/power_stencil/command_processors/init.rb +2 -2
  21. data/lib/power_stencil/command_processors/plugin.rb +20 -6
  22. data/lib/power_stencil/command_processors/root.rb +1 -1
  23. data/lib/power_stencil/command_processors/shell.rb +10 -3
  24. data/lib/power_stencil/dsl/entities.rb +0 -16
  25. data/lib/power_stencil/engine/build_handling.rb +1 -2
  26. data/lib/power_stencil/engine/directory_processor.rb +6 -1
  27. data/lib/power_stencil/engine/entities_definitions.rb +16 -7
  28. data/lib/power_stencil/engine/entities_handling.rb +1 -1
  29. data/lib/power_stencil/engine/project_engine.rb +6 -10
  30. data/lib/power_stencil/initializer.rb +4 -7
  31. data/lib/power_stencil/plugins/base.rb +18 -9
  32. data/lib/power_stencil/plugins/capabilities.rb +10 -8
  33. data/lib/power_stencil/plugins/command_line.rb +1 -4
  34. data/lib/power_stencil/plugins/config.rb +1 -5
  35. data/lib/power_stencil/plugins/entity_definitions.rb +15 -0
  36. data/lib/power_stencil/plugins/gem.rb +14 -35
  37. data/lib/power_stencil/plugins/paths.rb +38 -0
  38. data/lib/power_stencil/plugins/require.rb +10 -16
  39. data/lib/power_stencil/plugins/templates.rb +3 -3
  40. data/lib/power_stencil/project/base.rb +24 -9
  41. data/lib/power_stencil/project/config.rb +3 -2
  42. data/lib/power_stencil/project/create.rb +23 -2
  43. data/lib/power_stencil/project/git.rb +75 -0
  44. data/lib/power_stencil/project/info.rb +14 -1
  45. data/lib/power_stencil/project/paths.rb +22 -26
  46. data/lib/power_stencil/project/plugins.rb +18 -23
  47. data/lib/power_stencil/project/templates.rb +15 -22
  48. data/lib/power_stencil/system_entity_definitions/all.rb +2 -1
  49. data/lib/power_stencil/system_entity_definitions/buildable.rb +1 -1
  50. data/lib/power_stencil/system_entity_definitions/entity_override.rb +6 -0
  51. data/lib/power_stencil/system_entity_definitions/entity_project_common.rb +13 -5
  52. data/lib/power_stencil/system_entity_definitions/{has_associated_files.rb → entity_templates.rb} +2 -2
  53. data/lib/power_stencil/system_entity_definitions/project_config.rb +1 -1
  54. data/lib/power_stencil/system_entity_definitions/project_entity.rb +7 -0
  55. data/lib/power_stencil/system_entity_definitions/simple_exec.rb +3 -3
  56. data/lib/power_stencil/system_entity_definitions/source_provider.rb +15 -0
  57. data/lib/power_stencil/utils/gem_utils.rb +22 -2
  58. data/lib/power_stencil/version.rb +1 -1
  59. data/power_stencil.gemspec +2 -1
  60. metadata +23 -6
@@ -0,0 +1,38 @@
1
+ module PowerStencil
2
+ module Plugins
3
+
4
+ module Paths
5
+
6
+ def plugin_path
7
+ case self.type
8
+ when :local
9
+ project.project_local_plugin_path self.name
10
+ when :gem
11
+ gem_spec.gem_dir
12
+ end
13
+ end
14
+
15
+ def plugin_command_line_definition_file
16
+ File.join plugin_path, 'etc', 'command_line.yaml'
17
+ end
18
+
19
+ def plugin_capabilities_definition_file
20
+ File.join plugin_path, 'etc', 'plugin_capabilities.yaml'
21
+ end
22
+
23
+ def plugin_config_specific_file
24
+ File.join plugin_path, 'etc', 'plugin_config.yaml'
25
+ end
26
+
27
+ def plugin_processors_dir
28
+ File.join plugin_path, 'lib', plugin_name, 'processors'
29
+ end
30
+
31
+ def plugin_entities_definitions_dir
32
+ File.join plugin_path, 'etc', plugin_name, 'entities_definitions'
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+ end
@@ -6,7 +6,6 @@ module PowerStencil
6
6
  POST_BUILD_HOOK = :post_build_hook
7
7
 
8
8
  include PowerStencil::Utils::SecureRequire
9
- include PowerStencil::Utils::GemUtils
10
9
 
11
10
  private
12
11
 
@@ -15,21 +14,16 @@ module PowerStencil
15
14
  end
16
15
 
17
16
  def require_entry_point
18
- if is_available_gem? name
19
- logger.debug "Plugin '#{name}' is actually a Ruby Gem."
20
- raise "Plugin (#{name}) provided as a Ruby gem is not yet supported !"
21
- else
22
- @entry_point_path = File.join project.project_plugin_path(name), 'lib', "#{name.underscore}.rb"
23
- logger.debug "Plugin '#{name}' is provided locally: '#{entry_point_path}'"
24
- plugin_root_path = File.dirname(entry_point_path)
25
- begin
26
- $LOAD_PATH << plugin_root_path
27
- securely_require entry_point_path unless plugin_definition[:plugin_module].nil?
28
- rescue LoadError => e
29
- logger.warn "As plugin '#{name}' code is invalid, removing '#{plugin_root_path}' from LOAD_PATH"
30
- $LOAD_PATH.delete plugin_root_path
31
- end
32
-
17
+ @entry_point_path = File.join plugin_path, 'lib', "#{name.underscore}.rb"
18
+ logger.debug "Plugin '#{name}' entry point: '#{entry_point_path}'"
19
+ plugin_root_path = File.dirname(entry_point_path)
20
+ begin
21
+ $LOAD_PATH << plugin_root_path
22
+ securely_require entry_point_path unless plugin_definition[:plugin_module].nil?
23
+ rescue LoadError => e
24
+ @entry_point_path = nil
25
+ logger.warn "As plugin '#{name}' code is invalid, removing '#{plugin_root_path}' from LOAD_PATH"
26
+ $LOAD_PATH.delete plugin_root_path
33
27
  end
34
28
  end
35
29
 
@@ -3,15 +3,15 @@ module PowerStencil
3
3
 
4
4
  module Templates
5
5
 
6
- def register_plugin_templates
6
+ def register_plugin_templates_templates
7
7
  return unless capabilities[:templates]
8
8
  logger.info "Loading '#{self.name}' plugin templates..."
9
9
  plugin_definition[:templates].each do |templates_path|
10
- plugin_templates_path = File.join self.path, templates_path
10
+ plugin_templates_path = File.join self.plugin_path, templates_path
11
11
  Dir.entries(plugin_templates_path).reject { |e| %w(. ..).include? e }.each do |entry|
12
12
  template_path = File.join(plugin_templates_path, entry)
13
13
  if Dir.exist? template_path
14
- project.register_template_path_for_type entry.to_sym, template_path
14
+ project.register_template_template_path_for_type entry.to_sym, template_path
15
15
  end
16
16
  end
17
17
  end
@@ -5,6 +5,7 @@ require 'power_stencil/project/versioning'
5
5
  require 'power_stencil/project/info'
6
6
  require 'power_stencil/project/templates'
7
7
  require 'power_stencil/project/plugins'
8
+ require 'power_stencil/project/git'
8
9
 
9
10
  require 'power_stencil/engine/project_engine'
10
11
  require 'power_stencil/engine/entity_engine'
@@ -30,40 +31,54 @@ module PowerStencil
30
31
  include PowerStencil::Project::Templates
31
32
  include PowerStencil::Project::Plugins
32
33
  include PowerStencil::Project::Info
34
+ include PowerStencil::Project::Git
33
35
  extend PowerStencil::Project::Create
34
36
 
35
37
  attr_reader :engine, :entity_engine
36
38
 
39
+ def name
40
+ File.dirname project_config_root
41
+ end
42
+
37
43
  def initialize(search_from_path: Dir.pwd)
38
44
  initialize_paths search_from_path
39
45
  load_project_specific_config
40
46
  check_project_version
41
47
  bootstrap_plugins
42
48
  build_engines
43
- register_system_templates
44
- register_project_templates
49
+ register_system_templates_templates
50
+ register_plugins_templates_templates
51
+ register_project_templates_templates
52
+ setup_git_tracking
45
53
  end
46
54
 
47
55
  private
48
56
 
49
- def register_project_templates
50
- dir = project_templates_path
57
+ def register_plugins_templates_templates
58
+ plugins.each do |_, plugin|
59
+ plugin.register_plugin_templates_templates
60
+ end
61
+ end
62
+
63
+
64
+ def register_project_templates_templates
65
+ dir = project_templates_templates_path
51
66
  if Dir.exist? dir and File.readable? dir
52
67
  logger.info 'Registering project specific templates.'
53
68
  Dir.entries(dir).each do |potential_entity_type|
54
69
  next if potential_entity_type.match /^\./
55
70
  template_dir = File.join(dir, potential_entity_type)
56
71
  next unless File.directory? template_dir
57
- register_template_path_for_type potential_entity_type.to_sym, template_dir
72
+ register_template_template_path_for_type potential_entity_type.to_sym, template_dir
58
73
  end
59
74
  end
60
75
  end
61
76
 
62
- def register_system_templates
77
+ def register_system_templates_templates
63
78
  logger.debug 'Registering system templates'
64
79
  %i(plugin_definition simple_exec).each do |template_name|
65
- template_path = template_path template_name
66
- register_template_path_for_type template_name, template_path
80
+ template_path = system_template_template_path template_name
81
+ register_template_template_path_for_type template_name, template_path
67
82
  end
68
83
  end
69
84
 
@@ -76,4 +91,4 @@ module PowerStencil
76
91
  end
77
92
 
78
93
  end
79
- end
94
+ end
@@ -21,8 +21,9 @@ module PowerStencil
21
21
  end
22
22
 
23
23
 
24
- def add_plugin_config(plugin_name)
25
- yaml_file = plugin_config_specific_file plugin_name
24
+ def add_plugin_config(plugin)
25
+ plugin_name = plugin.name
26
+ yaml_file = plugin.plugin_config_specific_file
26
27
  priority = if priority.nil?
27
28
  PLUGIN_CONFIG_PRIORITY_MIN
28
29
  else
@@ -5,6 +5,8 @@ module PowerStencil
5
5
 
6
6
  module Create
7
7
 
8
+ INITIAL_REPOSITORY_COMMIT_MESSAGE = 'Initial commit for project "%s".'
9
+
8
10
  include Climatic::Proxy
9
11
 
10
12
  def create_project_tree(path, config_directory_name = PowerStencil.config[:default_config_directory_name])
@@ -14,14 +16,33 @@ module PowerStencil
14
16
  raise PowerStencil::Error, "The directory '#{path}' already contains a PowerStencil project !" unless config[:force]
15
17
  end
16
18
  logger.info "Creating project in '#{path}'..."
17
- render_project_template_in(path)
19
+ render_project_template_in path
20
+ initialize_git_repository path
18
21
  end
19
22
 
20
23
  private
21
24
 
25
+ def initialize_git_repository(repo_path)
26
+ if config[:'no-git']
27
+ puts_and_logs 'Do not initialize project git repository as per config request.', logs_as: :debug
28
+ return
29
+ end
30
+ if Dir.exists? File.join(repo_path, '.git')
31
+ puts_and_logs 'Git repository already exists. Skipping.', logs_as: :debug, check_verbose: false
32
+ return
33
+ end
34
+ puts_and_logs 'Initializing git repository...', logs_as: :debug
35
+ git = ::Git.init repo_path
36
+ logger.debug 'Adding all files.'
37
+ git.add repo_path
38
+ logger.debug 'Committing initial status'
39
+ commit_msg = INITIAL_REPOSITORY_COMMIT_MESSAGE % [File.basename(repo_path)]
40
+ git.commit_all commit_msg
41
+ end
42
+
22
43
  def render_project_template_in(new_project_config_path)
23
44
  engine = PowerStencil::Engine::InitEngine.new
24
- engine.render_source PowerStencil::Project::Paths.project_system_template_path, new_project_config_path
45
+ engine.render_source PowerStencil::Project::Paths.project_system_template_template_path, new_project_config_path
25
46
  end
26
47
 
27
48
  end
@@ -0,0 +1,75 @@
1
+ module PowerStencil
2
+ module Project
3
+
4
+ module Git
5
+
6
+ include Climatic::Utils::Input
7
+
8
+ def track_action_with_git(action_message,
9
+ user_validation_required: false,
10
+ validation_message: 'Commit changes ?',
11
+ show_files_to_commit: false,
12
+ &block)
13
+ return yield if git.nil?
14
+
15
+ status_before_action = git.status
16
+ yield
17
+ status_after_action = git.status
18
+
19
+ files_introduced_by_action = status_after_action.untracked.reject { |f| status_before_action.untracked.keys.include? f}
20
+ files_deleted_by_action = status_after_action.deleted.reject { |f| status_before_action.deleted.keys.include? f}
21
+ files_modified_by_action = status_after_action.changed.reject { |f| status_before_action.changed.keys.include? f}
22
+ files_to_commit = [files_introduced_by_action, files_deleted_by_action, files_modified_by_action].map(&:keys).flatten.sort.uniq
23
+
24
+ if files_to_commit.empty?
25
+ puts_and_logs 'Nothing to commit.'
26
+ return
27
+ end
28
+
29
+ if show_files_to_commit
30
+ header = 'Following file'
31
+ header << 's' if files_to_commit.count > 1
32
+ header << ' will be committed:'
33
+ puts header
34
+ puts files_to_commit.map { |filename| " - '#{filename}'" }
35
+ end
36
+
37
+ if user_validation_required
38
+ unless get_user_confirmation(default_choice: 'Yes', prompt: validation_message)
39
+ puts_and_logs 'Commit cancelled by user.', check_verbose: false
40
+ return
41
+ end
42
+ end
43
+
44
+ files_to_commit.each { |filename| git.add filename }
45
+
46
+ # Verify files to be committed really are
47
+ files_really_to_be_committed = git.diff.stats[:files].keys.select { |filename| files_to_commit.include? filename }
48
+ return if files_really_to_be_committed.empty?
49
+ files_really_to_be_committed.each { |filename| logger.info "File '#{filename}' will be committed" }
50
+
51
+ git.commit action_message
52
+ puts_and_logs 'All changes introduced have been committed.'
53
+ end
54
+
55
+ private
56
+
57
+ attr_reader :git
58
+
59
+ def setup_git_tracking
60
+ @git = nil
61
+ if config[:'no-git']
62
+ logger.debug "Won't track any changes with git as per config request!"
63
+ return
64
+ end
65
+ @git = ::Git.open(self.project_root, :log => PowerStencil.logger)
66
+ logger.debug 'Following project changes with git'
67
+ rescue => e
68
+ logger.debug PowerStencil::Error.report_error(e)
69
+ logger.warn 'This project is not managed by git'
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+ end
@@ -60,7 +60,20 @@ module PowerStencil
60
60
  .sort{ |a,b| a.first <=> b.first }
61
61
  .each do |type, klass|
62
62
  msg = "Type '#{type}' --> #{klass}"
63
- msg << " (template-template path: '#{entity_type_templates[type]}')" unless entity_type_templates[type].nil?
63
+
64
+ source_provider = klass.entity_type_source_provider
65
+ source_provider_display = if source_provider == PowerStencil
66
+ "'#{source_provider.name}'"
67
+ elsif source_provider == self
68
+ "project '#{source_provider.name}'"
69
+ elsif source_provider.is_a? PowerStencil::Plugins::Base
70
+ "plugin '#{source_provider.name}'"
71
+ else
72
+ raise PowerStencil::Error, "Unidentified source provider for #{klass} !"
73
+ end
74
+
75
+ msg << " (provided by #{source_provider_display})"
76
+ msg << " template-template path: '#{entity_type_templates_templates[type]}'" unless entity_type_templates_templates[type].nil?
64
77
  report << msg
65
78
  end
66
79
  report
@@ -7,12 +7,12 @@ module PowerStencil
7
7
 
8
8
  attr_reader :project_config_root, :started_from
9
9
 
10
- def self.system_templates_path
10
+ def self.system_templates_templates_path
11
11
  File.expand_path File.join('..', '..', '..', '..', 'etc', 'templates'), __FILE__
12
12
  end
13
13
 
14
- def self.project_system_template_path
15
- File.join system_templates_path, 'project'
14
+ def self.project_system_template_template_path
15
+ File.join system_templates_templates_path, 'project'
16
16
  end
17
17
 
18
18
  def build_run_path(seed)
@@ -31,44 +31,40 @@ module PowerStencil
31
31
  File.join project_root, PowerStencil.config[:project_build_root_directory_name]
32
32
  end
33
33
 
34
- def template_path(entity_type)
35
- File.join PowerStencil::Project::Paths.system_templates_path, entity_type.to_s
34
+ def system_template_template_path(entity_type)
35
+ File.join PowerStencil::Project::Paths.system_templates_templates_path, entity_type.to_s
36
36
  end
37
37
 
38
- def project_plugins_path
38
+ def project_local_plugins_path
39
39
  File.join project_config_root, PowerStencil.config[:project_plugins_directory_name]
40
40
  end
41
41
 
42
- def project_templates_path
43
- File.join project_config_root, PowerStencil.config[:project_templates_directory_name]
44
- end
45
-
46
- def project_entity_definitions_path
47
- File.join project_config_root, PowerStencil.config[:project_entity_definitions_directory_name]
42
+ def project_local_plugin_path(plugin_name)
43
+ File.join project_local_plugins_path, plugin_name
48
44
  end
49
45
 
50
- def project_plugin_path(plugin_name)
51
- File.join project_plugins_path, plugin_name
52
- end
53
-
54
- def plugin_capabilities_definition_file(plugin_name)
55
- File.join project_plugin_path(plugin_name), 'etc', 'plugin_capabilities.yaml'
46
+ def project_templates_templates_path
47
+ File.join project_config_root, PowerStencil.config[:project_templates_directory_name]
56
48
  end
57
49
 
58
- def plugin_commands_line_definition_file(plugin_name)
59
- File.join project_plugin_path(plugin_name), 'etc', 'command_line.yaml'
50
+ def entities_template_path
51
+ File.join project_root, PowerStencil.config[:versioned_entities_templates_directory_name]
60
52
  end
61
53
 
62
- def plugin_config_specific_file(plugin_name)
63
- File.join project_plugin_path(plugin_name), 'etc', 'plugin_config.yaml'
54
+ def user_entities_template_path
55
+ File.join project_root, PowerStencil.config[:unversioned_user_entities_templates_directory_name]
64
56
  end
65
57
 
66
- def plugin_processors_dir(plugin_name)
67
- File.join project_plugin_path(plugin_name), 'lib', plugin_name, 'processors'
58
+ def entity_template_path(entity)
59
+ if entity.is_versioned_entity?
60
+ File.join entities_template_path, entity.type.to_s, entity.name
61
+ else
62
+ File.join user_entities_template_path, entity.type.to_s, entity.name
63
+ end
68
64
  end
69
65
 
70
- def plugin_entities_definitions_dir(plugin_name)
71
- File.join project_plugin_path(plugin_name), 'etc', plugin_name, 'entities_definitions'
66
+ def project_entity_definitions_path
67
+ File.join project_config_root, PowerStencil.config[:project_entity_definitions_directory_name]
72
68
  end
73
69
 
74
70
  def project_entity_path(entity)
@@ -7,11 +7,11 @@ module PowerStencil
7
7
  @plugins ||= {}
8
8
  end
9
9
 
10
- def create_plugin_tree(plugin_name, new_plugin_path, overwrite_files: false)
10
+ def create_new_local_plugin_tree(plugin_name, new_plugin_path, overwrite_files: false)
11
11
  raise PowerStencil::Error, "Plugin '#{plugin_name}' already exists !" if plugins.keys.include? plugin_name
12
12
  raise PowerStencil::Error, "Invalid plugin name '#{plugin_name}'" if (plugin_name.underscore =~ /^[_[:lower:]][_[:alnum:]]*$/).nil?
13
13
  entity_engine.dsl = PowerStencil::Dsl::PluginGeneration
14
- entity_engine.render_source entity_type_templates[:plugin_definition],
14
+ entity_engine.render_source entity_type_templates_templates[:plugin_definition],
15
15
  new_plugin_path,
16
16
  overwrite_files: overwrite_files,
17
17
  main_entry_point: plugin_name
@@ -21,6 +21,7 @@ module PowerStencil
21
21
  private
22
22
 
23
23
  def bootstrap_plugins
24
+ @plugins = {}
24
25
  initialize_gem_plugins
25
26
  initialize_local_plugins
26
27
  command_line_manager.definition_hash_to_commands
@@ -31,45 +32,39 @@ module PowerStencil
31
32
  end
32
33
  end
33
34
 
34
-
35
35
  def initialize_gem_plugins
36
- # PowerStencil::logger.warn 'Gem plugins not yet supported ! Skipping...'
37
36
  if config[:project_plugins].empty?
38
- PowerStencil.logger.info "No gem plugin found in '#{project_plugins_path}'"
37
+ PowerStencil.logger.info 'No gem plugin found in project'
39
38
  return
40
39
  end
41
40
  config[:project_plugins].each do |plugin_definition|
42
- plugin_name, plugin_requirements = case plugin_definition
43
- when String
44
- [plugin_definition, '']
45
- when Hash
46
- [plugin_definition.keys.first, plugin_definition.values.first]
47
- end
48
- unless PowerStencil::Plugins::Base.gem_locally_installed? plugin_name, plugin_requirements
49
- PowerStencil::Plugins::Base.install_gem plugin_name, plugin_requirements
50
- end
41
+
42
+ (gem_name, gem_req) = PowerStencil::Plugins::Base.plugin_definition_to_name_and_req plugin_definition
43
+ raise PowerStencil::Error, "Plugin '#{gem_name}' already exists !" unless plugins[gem_name].nil?
44
+
45
+ plugins[gem_name] = PowerStencil::Plugins::Base.new(gem_name, self, type: :gem, gem_req: gem_req)
51
46
  end
52
47
  end
53
48
 
54
49
  def initialize_local_plugins
55
- unless File.directory? project_plugins_path
56
- PowerStencil.logger.info "No local plugin found in '#{project_plugins_path}'"
50
+ unless File.directory? project_local_plugins_path
51
+ PowerStencil.logger.info "No local plugin found in '#{project_local_plugins_path}'"
57
52
  return
58
53
  end
59
54
 
60
- candidates = Dir.entries(project_plugins_path)
61
- .select { |e| File.directory? File.join(project_plugins_path, e) }
62
- .reject { |d| %w(. ..).include? d }
63
- @plugins = {}
64
- candidates.each do |candidate|
55
+ Dir.entries(project_local_plugins_path)
56
+ .select { |e| File.directory? File.join(project_local_plugins_path, e) }
57
+ .reject { |d| %w(. ..).include? d }
58
+ .each do |candidate|
65
59
  begin
66
60
  raise PowerStencil::Error, "Plugin '#{candidate}' already exists !" unless plugins[candidate].nil?
67
61
  plugins[candidate] = PowerStencil::Plugins::Base.new(candidate, self)
68
62
  rescue PowerStencil::Error => pse
69
- PowerStencil.logger.debug pse.message
70
- PowerStencil.logger.error "Discarding invalid plugin '#{candidate}'."
63
+ logger.puts_and_logs pse.message, logs_as: :error
64
+ logger.puts_and_logs "Discarding invalid plugin '#{candidate}'.", logs_as: :error
71
65
  end
72
66
  end
67
+
73
68
  end
74
69
 
75
70
  end