Module: Commandable

Included in:
AppController
Defined in:
lib/commandable/version.rb,
lib/commandable/coloring.rb,
lib/commandable/help_text.rb,
lib/commandable/controller.rb,
lib/commandable/exceptions.rb,
lib/commandable/app_controller.rb,
lib/commandable/command_parser.rb,
lib/commandable/argument_parser.rb,
lib/commandable/execution_controller.rb

Overview

Extending your class with this module allows you to use the #command method above your method. This makes them executable from the command line.

Defined Under Namespace

Modules: VERSION Classes: AccessorError, AppController, ConfigurationError, ExclusiveMethodClashError, MissingRequiredCommandError, MissingRequiredParameterError, SyntaxError, UnknownCommandError

Constant Summary collapse

HELP_COMMAND =

Default command that always gets added to end of the command list

{:help => {:description => "you're looking at it now", :argument_list => "", :class=>"Commandable", :class_method=>true}}

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.app_exeObject

Used when building the usage line, e.g. Usage: app_exe [command] [parameters]



12
13
14
# File 'lib/commandable/help_text.rb', line 12

def app_exe
  @app_exe
end

.app_infoObject

Describes your application, printed at the top of help/usage messages



9
10
11
# File 'lib/commandable/help_text.rb', line 9

def app_info
  @app_info
end

.clear_screenObject

If the screen should be cleared before printing help



31
32
33
# File 'lib/commandable/coloring.rb', line 31

def clear_screen
  @clear_screen
end

.clear_screen_codeObject

What escape code will be used to clear the screen



34
35
36
# File 'lib/commandable/coloring.rb', line 34

def clear_screen_code
  @clear_screen_code
end

.color_app_exeObject

What color the app_exe will be in the usage line in the help message



13
14
15
# File 'lib/commandable/coloring.rb', line 13

def color_app_exe
  @color_app_exe
end

.color_app_infoObject

What color the app_info text will be in the help message



11
12
13
# File 'lib/commandable/coloring.rb', line 11

def color_app_info
  @color_app_info
end

.color_commandObject

What color the word “command” and the commands themselves will be in the help message



15
16
17
# File 'lib/commandable/coloring.rb', line 15

def color_command
  @color_command
end

.color_descriptionObject

What color the description column header and text will be in the help message



17
18
19
# File 'lib/commandable/coloring.rb', line 17

def color_description
  @color_description
end

.color_error_descriptionObject

What color the error description will be in error messages



28
29
30
# File 'lib/commandable/coloring.rb', line 28

def color_error_description
  @color_error_description
end

.color_error_nameObject

What color the friendly name of the error will be in error messages



26
27
28
# File 'lib/commandable/coloring.rb', line 26

def color_error_name
  @color_error_name
end

.color_error_wordObject

What color the word “Error:” text will be in error messages



24
25
26
# File 'lib/commandable/coloring.rb', line 24

def color_error_word
  @color_error_word
end

.color_outputObject

If the output will be colorized or not



8
9
10
# File 'lib/commandable/coloring.rb', line 8

def color_output
  @color_output
end

.color_parameterObject

What color the word “parameter” and the parameters themselves will be in the help message



19
20
21
# File 'lib/commandable/coloring.rb', line 19

def color_parameter
  @color_parameter
end

.color_usageObject

What color the word “Usage:” will be in the help message



21
22
23
# File 'lib/commandable/coloring.rb', line 21

def color_usage
  @color_usage
end

.verbose_parametersObject

If optional parameters show default values, true by default



15
16
17
# File 'lib/commandable/help_text.rb', line 15

def verbose_parameters
  @verbose_parameters
end

Class Method Details

.[](index) ⇒ Object

Access the command array using the method name (symbol or string)

Raises:



19
20
21
22
# File 'lib/commandable/command_parser.rb', line 19

def [](index)
  raise AccessorError unless index.is_a? String or index.is_a? Symbol
  @@commands[index.to_sym]
end

.class_cacheObject

A hash of instances created when calling instance methods It’s keyed using the class name: “ClassName”=>#<ClassName:0x00000100b1f188>



14
15
16
# File 'lib/commandable/command_parser.rb', line 14

def class_cache
  @@class_cache
end

.clear_commandsObject

Clears all methods from the list of available commands This is mostly useful for testing.



26
27
28
29
# File 'lib/commandable/command_parser.rb', line 26

def clear_commands
  @@command_options = nil
  @@commands = HELP_COMMAND.dup
end

.commandsObject

An array of methods that can be executed from the command line



8
9
10
# File 'lib/commandable/command_parser.rb', line 8

def commands
  @@commands.dup
end

.each(&block) ⇒ Object

Convenience method to iterate over the array of commands using the Commandable module



32
33
34
35
36
# File 'lib/commandable/command_parser.rb', line 32

def each(&block)
  @@commands.each do |key, value|
    yield key => value
  end
end

.execute(argv = ARGV.clone, silent = false) ⇒ Object

A wrapper for the execution_queue that runs the queue and traps errors. If an error occurs inside this method it will print out a complete list of availavle methods with usage instructions and exit gracefully.

If you do not want the output from your methods to be printed out automatically run the execute command with silent set to anything other than false or nil.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/commandable/execution_controller.rb', line 16

def execute(argv=ARGV.clone, silent=false)
  begin
    command_queue = execution_queue(argv)
    command_queue.each do |com|
      return_value = com[:proc].call
      puts return_value if !silent || com[:method] == :help
    end
  rescue SystemExit => kernel_exit
    Kernel.exit kernel_exit.status
  rescue Exception => exception
    if exception.respond_to?(:friendly_name)
      set_colors
      puts help("\n  #{@c_error_word}Error:#{@c_reset} #{@c_error_name}#{exception.friendly_name}#{@c_reset}\n  #{@c_error_description}#{exception.message}#{@c_reset}\n\n")
    else
      puts exception.inspect
      puts exception.backtrace.collect{|line| " #{line}"}
    end
  end
end

.execution_queue(argv) ⇒ Object

Returns an array of executable procs based on the given array of commands and parameters Normally this would come from the command line parameters in the ARGV array.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/commandable/execution_controller.rb', line 38

def execution_queue(argv)
  arguments = argv.dup
  method_hash = {}
  last_method = nil
  
  if arguments.empty?
    arguments << @@default_method.keys[0] if @@default_method and !@@default_method.values[0][:parameters].flatten.include?(:req)
  end
  arguments << "help" if arguments.empty?
  
  # Parse the command line into methods and their parameters
  
  arguments.each do |arg|
    if Commandable[arg]
      last_method = arg.to_sym
      method_hash.merge!(last_method=>[])
    else
      unless last_method
        default = find_by_subkey(:default)

        # Raise an error if there is no default method and the first item isn't a method
        raise UnknownCommandError, arguments.first if default.empty?
        
        # Raise an error if there is a default method but it doesn't take any parameters
        raise UnknownCommandError, (arguments.first) if default.values[0][:argument_list] == ""
        
        last_method = default.keys.first
        method_hash.merge!(last_method=>[])
      end
      method_hash[last_method] << arg
    end
    # Test for missing required switches
    @@commands.select do |key, value|
      if value[:required] and method_hash[key].nil?
        # If the required switch is also a default have the error be a missing parameter instead of a missing command
        if value[:default]
          method_hash.merge!(key=>[])
        else
          raise MissingRequiredCommandError, key 
        end
      end
    end
  end
  
  # Build an array of procs to be called for each method and its given parameters
  proc_array = []
  method_hash.each do |meth, params|
    command = @@commands[meth]

    if command[:parameters] && !command[:parameters].empty?
      
      #Change the method name for attr_writers
      meth = "#{meth}=" if command[:parameters][0][0] == :writer
    
      # Get a list of required parameters and make sure all of them were provided
      required = command[:parameters].select{|param| [:req, :writer].include?(param[0])}
      required.shift(params.count)
      raise MissingRequiredParameterError, {:method=>meth, :parameters=>required.collect!{|meth| meth[1]}.to_s[1...-1].gsub(":",""), :default=>command[:default]} unless required.empty?
    end
    
    # Test for duplicate XORs
    proc_array.select{|x| x[:xor] and x[:xor]==command[:xor] }.each {|bad| raise ExclusiveMethodClashError, "#{meth}, #{bad[:method]}"}

    klass = Object
    command[:class].split(/::/).each { |name| klass = klass.const_get(name) }
    ## Look for class in class cache
    unless command[:class_method]
      klass = (@@class_cache[klass.name] ||= klass.new)
    end
    proc_array << {:method=>meth, :xor=>command[:xor], :parameters=>params, :priority=>command[:priority], :proc=>lambda{klass.send(meth, *params)}}
  end
  proc_array.sort{|a,b| a[:priority] <=> b[:priority]}.reverse

end

.help(additional_info = nil) ⇒ Object

Generates an array of the available commands with a list of their parameters and the method’s description. This includes the applicaiton info and app name if given. It’s meant to be printed to the command line.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/commandable/help_text.rb', line 21

def help(additional_info=nil)
  
  set_colors
  set_screen_clear
  
  cmd_length = "Command".length
  parm_length = "Parameters".length
  max_command = [(@@commands.keys.max_by{|key| key.to_s.length }).to_s.length, cmd_length].max
  max_parameter = @@commands[@@commands.keys.max_by{|key| @@commands[key][:argument_list].length }][:argument_list].length
  max_parameter = [parm_length, max_parameter].max if max_parameter > 0

  usage_text = "  #{@c_usage}Usage:#{@c_reset} "

  if Commandable.app_exe      
    cmd_text = "<#{@c_command + @c_bold}command#{@c_reset}>"
    parm_text = " [#{@c_parameter + @c_bold}parameters#{@c_reset}]" if max_parameter > 0
    usage_text += "#{@c_app_exe + app_exe + @c_reset} #{cmd_text}#{parm_text} [#{cmd_text}#{parm_text}...]"
  end

  array =  [usage_text, ""]
  
  array.unshift additional_info if additional_info
  array.unshift (@c_app_info + Commandable.app_info + @c_reset) if Commandable.app_info
  array.unshift @s_clear_screen_code
  
  header_text = " #{" "*(max_command-cmd_length)}#{@c_command + @c_bold}Command#{@c_reset} "
  header_text += "#{@c_parameter + @c_bold}Parameters #{@c_reset}#{" "*(max_parameter-parm_length)}" if max_parameter > 0
  header_text += "#{@c_description + @c_bold}Description#{@c_reset}"
  
  array << header_text

  array += @@commands.keys.collect do |key|
    is_default = (@@default_method and key == @@default_method.keys[0])
    default_color =  is_default ? @c_bold : ""

    help_line  = " #{" "*(max_command-key.length)}#{@c_command + default_color + key.to_s + @c_reset}"+
                 " #{default_color + @c_parameter + @@commands[key][:argument_list] + @c_reset}"
    help_line += "#{" "*(max_parameter-@@commands[key][:argument_list].length)} " if max_parameter > 0
    
    # indent new lines
    description = @@commands[key][:description].gsub("\n", "\n" + (" "*(max_command + max_parameter + (max_parameter > 0 ? 1 : 0) + 4)))
    
    help_line += ": #{default_color + @c_description}#{"<#{@@commands[key][:xor]}> " if @@commands[key][:xor]}" +
                 "#{description}" +
                 "#{" (default)" if is_default}#{@c_reset}" 
  end
  array << nil
end

.reset_allObject

Resets the class to default values clearing any commands and setting the colors back to their default values.



7
8
9
10
11
12
13
14
15
16
17
# File 'lib/commandable/controller.rb', line 7

def reset_all
  clear_commands
  reset_colors
  reset_screen_clearing
  
  @app_info = nil
  @app_exe = nil
  @verbose_parameters = true
  @@default_method = nil
  @@class_cache = {}
end

.reset_colorsObject

Resets colors to their default values and disables color output



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/commandable/coloring.rb', line 37

def reset_colors
  @color_output = false

  #Term::ANSIColor.coloring = true
  c = Term::ANSIColor
  @color_app_info           = c.intense_white  + c.bold
  @color_app_exe            = c.intense_green  + c.bold
  @color_command            = c.intense_yellow
  @color_description        = c.intense_white
  @color_parameter          = c.intense_cyan
  @color_usage              = c.intense_black   + c.bold

  @color_error_word         = c.intense_black   + c.bold
  @color_error_name         = c.intense_red     + c.bold
  @color_error_description  = c.intense_white   + c.bold

  @color_bold               = c.bold
  @color_reset              = c.reset
end

.reset_screen_clearingObject

Resets the escape code used for screen clearing and disables screen clearing.



58
59
60
61
# File 'lib/commandable/coloring.rb', line 58

def reset_screen_clearing
  @clear_screen = false
  @clear_screen_code = "\e[H\e[2J"
end

Instance Method Details

#parse_arguments(parameters) ⇒ Object

Parse a method’s parameters building the argument list for printing help/usage



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/commandable/argument_parser.rb', line 4

def parse_arguments(parameters)
  parameter_string = ""
  method_definition = nil
  parameters.each do |parameter|
    arg_type = parameter[0]
    arg = parameter[1]
    case arg_type
      when :req
        parameter_string += " #{arg}"
      when :opt
        if Commandable.verbose_parameters
          # figure out what the default value is
          method_definition ||= readline(@@method_file, @@method_line)
          default = parse_optional(method_definition, arg)
          parameter_string += " [#{arg}=#{default}]"
        else
          parameter_string += " [#{arg}]"
        end
      when :rest
        parameter_string += " *#{arg}"
      when :block
        parameter_string += " &#{arg}"
    end
  end
  parameter_string.strip
end

#parse_optional(method_def, argument) ⇒ Object

Parses a method defition for the optional values of given argument.



41
42
43
# File 'lib/commandable/argument_parser.rb', line 41

def parse_optional(method_def, argument)
  method_def.scan(/#{argument}\s*=\s*("[^"\r\n]*"|'[^'\r\n]*'|[0-9]*)/)[0][0]
end

#readline(file, line_number) ⇒ Object

Reads a line from a source code file.



32
33
34
35
36
37
38
# File 'lib/commandable/argument_parser.rb', line 32

def readline(file, line_number)
  current_line = 0
  File.open(file).each do |line_text|
    current_line += 1
    return line_text.strip if current_line == line_number
  end
end