class Nucleon::Plugin::Action

Public Class Methods

action_help(action = nil, extended_help = false) click to toggle source
    # File lib/core/plugin/action.rb
627 def self.action_help(action = nil, extended_help = false)
628   action_index      = action_index(false).export
629   provider_index    = {}
630   processed_actions = {}
631 
632   last_namespace    = nil
633   last_group        = nil
634   multiple_found    = false
635 
636   command_width           = 0
637   namespace_command_width = {}
638 
639   output            = ''
640 
641   if action
642     if action.empty?
643       output << cyan(sprintf("\n%s\n", I18n.t('nucleon.core.exec.help.no_actions_found')))
644     else
645       multiple_found = true
646       output << cyan(sprintf("\n%s\n", I18n.t('nucleon.core.exec.help.multiple_actions_found')))
647 
648       action.each do |info|
649         provider_index[info[:provider]] = true
650       end
651     end
652   end
653 
654   action_index.each do |action_id, info|
655     if ! multiple_found || provider_index.has_key?(info[:provider])
656       action        = Nucleon.action(info[:provider], { :settings => {}, :quiet => true, :help => true })
657       command_text  = action.help
658 
659       command_size  = command_text.gsub(/\e\[(\d+)m/, '').size
660       command_width = [ command_width, command_size + 2 ].max
661 
662       namespace = info[:description][:namespace]
663 
664       namespace_command_width[namespace] = 0 unless namespace_command_width.has_key?(namespace)
665       namespace_command_width[namespace] = [ namespace_command_width[namespace], command_size + 2 ].max
666 
667       if extended_help
668         help_text = ''
669         info[:description][:help].split("\n").each do |line|
670           break if ! help_text.empty? && line.empty?
671           help_text << '           ' + line + "\n"
672         end
673       else
674         help_text = nil
675       end
676 
677       processed_actions[action_id] = {
678         :info    => info,
679         :command => command_text,
680         :help    => help_text
681       }
682     end
683   end
684 
685   processed_actions.each do |action_id, info|
686     command_text = info[:command]
687     help_text    = info[:help]
688     info         = info[:info]
689     namespace    = info[:description][:namespace]
690     group        = info[:description][:group]
691 
692     group_id = group.is_a?(Array) ? group.flatten.join('::') : group.to_s
693     group_id = '' unless group_id
694 
695     output << "\n" if group_id != last_group
696 
697     if namespace != last_namespace
698       output << "\n----------------------------------------------------\n" if help_text
699       output << sprintf("\n   %s:\n\n", I18n.t('nucleon.core.exec.help.action_group', { :namespace => purple(namespace) }))
700     end
701 
702     if help_text
703       output << "       " + render_colorized(command_text, namespace_command_width[namespace]) + "  --  " + blue(info[:description][:description]) + "\n"
704       output << "\n#{help_text}\n"
705     else
706       output << "       " + render_colorized(command_text, command_width) + "  --  " + blue(info[:description][:description]) + "\n"
707     end
708 
709     last_namespace = namespace
710     last_group     = group_id
711   end
712   output
713 end
action_index(tree = true) click to toggle source
    # File lib/core/plugin/action.rb
523 def self.action_index(tree = true)
524   action_config = Config.new
525   action_index  = Config.new
526 
527   generate_index = lambda do |info, parents = nil|
528     groups = info.keys - [ :_weight, :_weights ]
529     groups = groups.sort do |a, b|
530       info[b][:_weight] <=> info[a][:_weight]
531     end
532 
533     groups.each do |group|
534       data = info[group]
535 
536       if data.is_a?(Hash) && data.has_key?(:_weights)
537         sub_parents = parents.nil? ? [ group ] : [ parents, group ].flatten
538         generate_index.call(data, sub_parents)
539       else
540         keys = tree ? [ parents, group ] : [ parents, group ].flatten.join('::')
541         action_index.set(keys, data)
542       end
543     end
544   end
545 
546   Nucleon.loaded_plugins(:nucleon, :action).each do |provider, data|
547     description = data[:class].describe
548 
549     unless description.nil? || !description.is_a?(Hash)
550       data[:description] = description
551       data[:_weight]     = description[:weight]
552 
553       keys = [ description[:namespace], description[:group], description[:action] ].flatten.compact
554       action_config.set(keys, data)
555 
556       keys.pop
557 
558       while ! keys.empty?
559         group_config = action_config.get(keys)
560 
561         if group_config.has_key?(:_weights)
562           group_config[:_weights].push(description[:weight])
563         else
564           action_config.set([ keys, :_weights ], [ description[:weight] ])
565         end
566         action_config.set([ keys, :_weight ], group_config[:_weights].inject(0.0) { |sum, el| sum + el } / group_config[:_weights].size)
567         keys.pop
568       end
569     end
570   end
571 
572   generate_index.call(action_config.export)
573   action_index
574 end
components(search) click to toggle source
    # File lib/core/plugin/action.rb
511 def self.components(search)
512   components = []
513 
514   array(search).each do |element|
515     break if element.match(/^\-+/)
516     components << element
517   end
518   components
519 end
describe(group = nil, action = 'unknown', weight = -1000, description = nil, help = nil) click to toggle source
   # File lib/core/plugin/action.rb
15 def self.describe(group = nil, action = 'unknown', weight = -1000, description = nil, help = nil)
16   describe_base(group, action, weight, description, help)
17 end
describe_base(group = nil, action = 'unknown', weight = -1000, description = nil, help = nil, provider_override = nil) click to toggle source
   # File lib/core/plugin/action.rb
19 def self.describe_base(group = nil, action = 'unknown', weight = -1000, description = nil, help = nil, provider_override = nil)
20   if provider_override
21     provider_override = provider_override.to_s.gsub('_', '.')
22     description_id    = "#{namespace}.action.#{provider_override}.description"
23     help_id           = "#{namespace}.action.#{provider_override}.help"
24   else
25     if group
26       group_name     = Util::Data.array(group).join('.')
27       description_id = "#{namespace}.action.#{group_name}.#{action}.description"
28       help_id        = "#{namespace}.action.#{group_name}.#{action}.help"
29     else
30       description_id = "#{namespace}.action.#{action}.description"
31       help_id        = "#{namespace}.action.#{action}.help"
32     end
33   end
34 
35   {
36     :namespace   => namespace,
37     :weight      => weight,
38     :group       => group,
39     :action      => action,
40     :description => description ? description : I18n.t(description_id),
41     :help        => help ? help : I18n.t(help_id)
42   }
43 end
exec(provider, options, quiet = true, display_errors = true, state = nil) click to toggle source
    # File lib/core/plugin/action.rb
129 def self.exec(provider, options, quiet = true, display_errors = true, state = nil)
130   exec_safe(provider, { :settings => Config.ensure(options), :quiet => quiet }, display_errors, state)
131 end
exec_cli(provider, args, quiet = false, name = :nucleon, display_errors = true, state = nil) click to toggle source
    # File lib/core/plugin/action.rb
133 def self.exec_cli(provider, args, quiet = false, name = :nucleon, display_errors = true, state = nil)
134   exec_safe(provider, { :args => args, :quiet => quiet, :executable => name }, display_errors, state)
135 end
exec_safe(provider, options, display_errors = true, state = nil) click to toggle source
    # File lib/core/plugin/action.rb
 99 def self.exec_safe(provider, options, display_errors = true, state = nil)
100   begin
101     state  = State.new(Nucleon.code.unknown_status) unless state
102     logger = Nucleon.logger
103 
104     logger.info("Running nucleon action #{provider} with #{options.inspect}")
105 
106     state.action = Nucleon.action(provider, options)
107     state.action.execute
108 
109     state.status = state.action.status if state.action.status.is_a?(Integer)
110     state.result = state.action.result
111 
112   rescue => error # This does NOT catch interrupts
113     state.error = error
114 
115     if display_errors
116       logger.error("Nucleon action #{provider} experienced an error:")
117       logger.error(state.error.inspect)
118       logger.error(state.error.message)
119       logger.error(Nucleon::Util::Data.to_yaml(state.error.backtrace))
120 
121       Nucleon.ui.error(state.error.message, { :prefix => false }) if state.error.message
122     end
123 
124     state.action.finalize_execution(false) if state.action
125   end
126   state
127 end
namespace() click to toggle source
   # File lib/core/plugin/action.rb
47 def self.namespace
48   :nucleon
49 end
render_colorized(text, length = 0) click to toggle source
    # File lib/core/plugin/action.rb
717 def self.render_colorized(text, length = 0)
718   command_size  = text.gsub(/\e\[(\d+)m/, '').size
719   remaining     = [ length - command_size, 0 ].max
720   text + sprintf("%#{remaining}s", ' ')
721 end
search_actions(search_components) click to toggle source
    # File lib/core/plugin/action.rb
578 def self.search_actions(search_components)
579   action_components = components(search_components)
580   action_index      = action_index(false).export
581   actions_found     = []
582   final_components  = []
583 
584   search_action = lambda do |components|
585     unless components.empty?
586       index             = action_index
587       action_id         = components.is_a?(Array) ? components.flatten.join('::') : components
588       action_id_pattern = action_id.gsub('::', ':.*:')
589 
590       # Check for exact matches
591       index.each do |loaded_action_id, loaded_action_info|
592         if loaded_action_id.match(/^[^\:]+\:\:#{action_id.gsub(/\-/, '\-')}$/)
593           loaded_action_info[:action_id] = loaded_action_id
594           actions_found << loaded_action_info
595           break
596         end
597       end
598 
599       if actions_found.empty?
600         # Check for similarly named actions
601         index.each do |loaded_action_id, loaded_action_info|
602           if loaded_action_id.match(/(^|\:)#{action_id_pattern.gsub(/\-/, '\-')}(\:|$)/)
603             loaded_action_info[:action_id] = loaded_action_id
604             actions_found << loaded_action_info
605           end
606         end
607       end
608     end
609     if components.is_a?(Array) && ! components.empty? && actions_found.empty?
610       components.pop
611       final_components = components
612       search_action.call(components)
613     else
614       final_components = components
615     end
616   end
617 
618   search_action.call(action_components) unless action_components.empty?
619 
620   { :actions    => actions_found.size == 1 ? actions_found[0] : actions_found,
621     :components => final_components
622   }
623 end

Public Instance Methods

arguments() click to toggle source
    # File lib/core/plugin/action.rb
263 def arguments
264   []
265 end
cleanup() { || ... } click to toggle source
    # File lib/core/plugin/action.rb
491 def cleanup
492   logger.info("Running cleanup for action #{plugin_provider}")
493 
494   yield if block_given?
495 
496   # Nothing to do right now
497   extension(:cleanup)
498 end
config() click to toggle source
    # File lib/core/plugin/action.rb
209 def config
210   get(:config)
211 end
config_subset(names) click to toggle source
    # File lib/core/plugin/action.rb
215 def config_subset(names)
216   Util::Data.subset(config, names)
217 end
configure() { |action_info| ... } click to toggle source
    # File lib/core/plugin/action.rb
269 def configure
270   action_info = index_config
271 
272   yield(action_info) if block_given?
273 
274   group  = array(action_info[:description][:group])
275   action = cyan(action_info[:description][:action])
276 
277   if ! group.empty?
278     group = green(group.join(' ').strip)
279     usage = "#{group} #{action} "
280   else
281     usage = "#{action} "
282   end
283 
284   arguments.each do |arg|
285     arg_config = config[arg.to_sym]
286 
287     arg_prefix = arg_config.default ? '[' : ''
288     arg_suffix = arg_config.default ? ']' : ''
289 
290     if arg_config.type == :array
291       usage << "#{arg_prefix}<#{arg}> ...#{arg_suffix}"
292     else
293       usage << "#{arg_prefix}<#{arg}>#{arg_suffix} "
294     end
295   end
296   myself.usage = yellow(usage)
297   myself
298 end
execute(skip_validate = false, skip_hooks = false) { || ... } click to toggle source
    # File lib/core/plugin/action.rb
441 def execute(skip_validate = false, skip_hooks = false)
442   logger.info("Executing action #{plugin_provider}")
443 
444   myself.status = code.success
445   myself.result = nil
446 
447   if processed?
448     if skip_validate || validate
449       yield if block_given? && ( skip_hooks || extension_check(:exec_init) )
450     else
451       puts "\n" + I18n.t('nucleon.core.exec.help.usage') + ': ' + help + "\n" unless quiet?
452       myself.status = code.validation_failed
453       skip_hooks    = true
454     end
455     finalize_execution(skip_hooks)
456   else
457     if @parser.options[:help]
458       myself.status = code.help_wanted
459     else
460       myself.status = code.action_unprocessed
461     end
462     finalize_execution(true)
463   end
464 end
finalize_execution(skip_hooks = false) click to toggle source
    # File lib/core/plugin/action.rb
468 def finalize_execution(skip_hooks = false)
469   begin
470     myself.status = extension_set(:exec_exit, status) unless skip_hooks
471   ensure
472     cleanup
473   end
474 
475   myself.status = code.unknown_status unless myself.status.is_a?(Integer)
476 
477   if processed? && myself.status != code.success
478     logger.warn("Execution failed for #{plugin_provider} with status #{status}")
479     warn(Codes.render_status(status), { :i18n => false })
480   end
481 end
help() click to toggle source
    # File lib/core/plugin/action.rb
312 def help
313   return @parser.help if @parser
314   usage
315 end
ignore() click to toggle source
    # File lib/core/plugin/action.rb
255 def ignore
256   []
257 end
index_config() click to toggle source
    # File lib/core/plugin/action.rb
190 def index_config
191   action_info = nil
192   self.class.action_index(false).export.each do |action_id, info|
193     if info[:provider] == plugin_provider
194       action_info = info
195       break
196     end
197   end
198   Config.ensure(action_info)
199 end
namespace() click to toggle source
    # File lib/core/plugin/action.rb
203 def namespace
204   self.class.namespace
205 end
normalize(reload) { || ... } click to toggle source
    # File lib/core/plugin/action.rb
139 def normalize(reload)
140   args = array(delete(:args, []))
141   help = delete(:help, false)
142 
143   unless reload
144     @action_interface = Util::Liquid.new do |method, method_args|
145       options = {}
146       options = method_args[0] if method_args.length > 0
147 
148       quiet   = true
149       quiet   = method_args[1] if method_args.length > 1
150 
151       myself.class.exec(method, options, quiet)
152     end
153 
154     set(:config, Config.new)
155 
156     if get(:settings, nil)
157       # Internal processing
158       configure
159       set(:processed, true)
160       set(:settings, Config.ensure(get(:settings)))
161 
162       Nucleon.log_level = settings[:log_level] if settings.has_key?(:log_level)
163     else
164       # External processing
165       set(:settings, Config.new)
166       configure
167       parse_base(args)
168     end
169 
170     yield if block_given? && ! help
171   end
172 end
options() click to toggle source
    # File lib/core/plugin/action.rb
259 def options
260   config.keys - arguments - ignore
261 end
parse(parser) click to toggle source
    # File lib/core/plugin/action.rb
373 def parse(parser)
374 
375   generate = lambda do |format, name|
376     formats = [ :option, :arg ]
377     types   = parse_types
378     name    = name.to_sym
379 
380     if config.export.has_key?(name) && formats.include?(format.to_sym)
381       option_config = config[name]
382       type          = option_config.type
383       default       = option_config.default
384       locale        = option_config.locale
385 
386       if types.include?(type.to_sym)
387         value_label = "#{type.to_s.upcase}"
388 
389         if type == :bool
390           parser.send("option_#{type}", name, default, "--[no-]#{name}", locale)
391         elsif format == :arg
392           parser.send("#{format}_#{type}", name, default, locale)
393         else
394           if type == :array
395             parser.send("option_#{type}", name, default, "--#{name} #{value_label},...", locale)
396           else
397             parser.send("option_#{type}", name, default, "--#{name} #{value_label}", locale)
398           end
399         end
400       end
401     end
402   end
403 
404   #---
405 
406   options.each do |name|
407     generate.call(:option, name)
408   end
409 
410   arguments.each do |name|
411     generate.call(:arg, name)
412   end
413 end
parse_base(args) click to toggle source
    # File lib/core/plugin/action.rb
330 def parse_base(args)
331   logger.info("Parsing action #{plugin_provider} with: #{args.inspect}")
332 
333   action_info = index_config
334 
335   help_text = ''
336   action_info[:description][:help].split("\n").each do |line|
337     help_text << '     ' + green(line) + "\n"
338   end
339 
340   @parser = Util::CLI::Parser.new(args, usage, "\n#{help_text}\n") do |parser|
341     parser.strict = strict?
342 
343     parse(parser)
344     extension(:parse, { :parser => parser, :config => config })
345   end
346 
347   if @parser
348     if @parser.processed
349       set(:processed, true)
350       settings.import(Util::Data.merge([ @parser.extra, @parser.options, @parser.arguments ], true))
351       logger.debug("Parse successful")
352 
353     elsif @parser.options[:help] && ! quiet?
354       executable = delete(:executable, '')
355       puts I18n.t('nucleon.core.exec.help.usage') + ": " + executable.to_s + ' ' + help + "\n"
356 
357     else
358       if @parser.options[:help]
359         logger.debug("Help wanted but running in silent mode")
360       else
361         logger.warn("Parse failed for unknown reasons")
362       end
363     end
364   end
365 end
parse_types() click to toggle source
    # File lib/core/plugin/action.rb
369 def parse_types
370   [ :bool, :int, :float, :str, :array ]
371 end
processed?() click to toggle source
    # File lib/core/plugin/action.rb
183 def processed?
184   get(:processed, false)
185 end
register(name, type, default = nil, locale = nil, &code) click to toggle source
    # File lib/core/plugin/action.rb
231 def register(name, type, default = nil, locale = nil, &code)
232   name = name.to_sym
233 
234   if code
235     option = Option.new(namespace, registration_provider, name, type, default, locale) do |value, success|
236       code.call(value, success)
237     end
238   else
239     option = Option.new(namespace, registration_provider, name, type, default, locale)
240   end
241 
242   config[name]   = option
243   settings[name] = option.default if settings[name].nil?
244 end
registration_provider() click to toggle source
    # File lib/core/plugin/action.rb
227 def registration_provider
228   plugin_provider
229 end
remove(names) click to toggle source
    # File lib/core/plugin/action.rb
248 def remove(names)
249   Util::Data.rm_keys(config, names)
250   Util::Data.rm_keys(settings, names)
251 end
render_options() click to toggle source
Calls superclass method
    # File lib/core/plugin/action.rb
503 def render_options
504   options = super
505   options.merge(settings.export)
506 end
result() click to toggle source
    # File lib/core/plugin/action.rb
323 def result
324   get(:result, nil)
325 end
result=(result) click to toggle source
    # File lib/core/plugin/action.rb
319 def result=result
320   set(:result, result)
321 end
run() click to toggle source
    # File lib/core/plugin/action.rb
485 def run
486   @action_interface
487 end
settings() click to toggle source
    # File lib/core/plugin/action.rb
221 def settings
222   get(:settings)
223 end
strict?() click to toggle source
    # File lib/core/plugin/action.rb
177 def strict?
178   true # Override in providers if needed (allow extra options if false)
179 end
usage() click to toggle source
    # File lib/core/plugin/action.rb
306 def usage
307   get(:usage, '')
308 end
usage=(usage) click to toggle source
    # File lib/core/plugin/action.rb
302 def usage=usage
303   set(:usage, usage)
304 end
validate(*args) click to toggle source
    # File lib/core/plugin/action.rb
417 def validate(*args)
418   # TODO: Add extension hooks and logging
419 
420   # Validate all of the configurations
421   success = true
422   config.export.each do |name, option|
423     unless ignore.include?(name)
424       success = false unless option.validate(settings[name], *args)
425     end
426   end
427   if success
428     # Check for missing arguments (in case of internal execution mode)
429     arguments.each do |name|
430       if settings[name.to_sym].nil?
431         warn('nucleon.core.exec.errors.missing_argument', { :name => name })
432         success = false
433       end
434     end
435   end
436   success
437 end