Class: Consoler::Application

Inherits:
Object
  • Object
show all
Defined in:
lib/consoler/application.rb

Overview

Consoler application

Examples:

A simple application

# create a application
app = Consoler::Application.new description: 'A simple app'

# define a command
app.build 'target [--clean]' do |target, clean|
  # clean contains a boolean
  clean_up if clean

  # target contains a string
  build_project target
end
app.run(['build', 'production', '--clean'])

# this does not match, nothing is executed and the usage message is printed
app.run(['deploy', 'production'])

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Application

Create a consoler application

Parameters:

  • options (Hash) (defaults to: {})

    Options for the application

Options Hash (options):

  • :description (String)

    The description for the application (optional)

  • :rescue_errors (bool)

    Should the application catch errors (optional)



31
32
33
34
35
36
37
38
39
# File 'lib/consoler/application.rb', line 31

def initialize(options = {})
  @description = options[:description]
  @rescue_errors = if !options[:rescue_errors].nil? then
                    options[:rescue_errors]
                   else
                     true
                   end
  @commands = []
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(command_name, input = nil) {|...| ... } ⇒ nil

Register a command for this app

Parameters:

  • command_name (Symbol)

    Name of the command

  • input (String, Consoler::Application) (defaults to: nil)

    Options definition or a complete subapp

Yields:

  • (...)

    Executed when the action is matched with parameters based on your options

Returns:

  • (nil)


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
# File 'lib/consoler/application.rb', line 57

def method_missing(command_name, input = nil, &block)
  action = nil
  options_def = ''

  unless block.nil?
    action = block
    options_def = input

    if !options_def.nil? && !options_def.instance_of?(String)
      raise 'Invalid options'
    end
  end

  if input.instance_of? Consoler::Application
    action = input
    options_def = ''
  end

  if action.nil?
    raise 'Invalid subapp/block'
  end

  command = command_name.to_s

  _add_command(command, options_def, action)

  nil
end

Instance Method Details

#_add_command(command, options_def, action) ⇒ Consoler::Application (private)

Add a command

Parameters:

  • command (String)

    Command name

  • options_def (String)

    Definition of options

  • action (Proc, Consoler::Application)

    Action or subapp

Returns:



209
210
211
212
213
214
215
216
217
218
219
# File 'lib/consoler/application.rb', line 209

def _add_command(command, options_def, action)
  @commands.push(
    Consoler::Command.new(
      command: command,
      options: Consoler::Options.new(options_def),
      action: action,
    )
  )

  self
end

#_commands_usage(prefix = '') ⇒ Consoler::Application (protected)

Print the usage message for this command

Parameters:

  • prefix (String) (defaults to: '')

    A prefix for the command from a parent app

Returns:



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/consoler/application.rb', line 178

def _commands_usage(prefix = '')
  @commands.each do |command|
    # print the usage message of a subapp with a prefix from the current command
    if command.action.instance_of?(Consoler::Application)
      command.action._commands_usage "#{prefix} #{command.command}"
    else
      print "  #{prefix} #{command.command}"

      if command.options.size
        print " #{command.options.to_definition}"
      end

      unless command.options.description.nil?
        print "  -- #{command.options.description}"
      end

      print "\n"
    end
  end

  self
end

#_dispatch(action, match) ⇒ Object (private)

Execute an action with argument match info

Parameters:

  • action (Proc)

    Action

  • match (Hash)

    Argument match information



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/consoler/application.rb', line 225

def _dispatch(action, match)
  # match parameter names to indices of match information
  arguments = action.parameters.map do |parameter|
    parameter_name = parameter[1].to_s

    if match.key? parameter_name
      match[parameter_name]
    else
      # check for the normalized name of every match to see
      # if it fits the parameter name
      match.each do |name, value|
        normalized_name = _normalize name

        if parameter_name == normalized_name
          break value
        end
      end
    end
  end

  action.call(*arguments)
end

#_normalize(name) ⇒ String (private)

Normalize a name to be used as a variable name

Parameters:

  • name (String)

    Name

Returns:

  • (String)

    Normalized name



252
253
254
255
256
257
258
259
260
# File 'lib/consoler/application.rb', line 252

def _normalize(name)
  # maybe do something more, maybe not.. ruby does allow for
  # some weird stuff to be used as a variable name. the user
  # should use some common sense. and, other things might
  # also be an syntax error, like starting with a number.
  # this normalization is more of a comvenience than anything
  # else
  name.tr('-', '_')
end

#_run(args) ⇒ (mixed, Boolean) (protected)

Run the app

Parameters:

  • args (Array<String>)

    Arguments

Returns:

  • ((mixed, Boolean))

    Result of the command, and, did the args match a command at all



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/consoler/application.rb', line 126

def _run(args)
  arg = args.shift

  return [nil, false] if arg.nil?

  arguments = Consoler::Arguments.new args
  exact_matches = []
  partial_matches = []

  @commands.each do |command|
    if command.command == arg
      exact_matches.push command
    elsif command.command.start_with? arg
      partial_matches.push command
    end
  end

  # we only allow a single partial match to prevent ambiguity
  partial_match = if partial_matches.size == 1
                    partial_matches[0]
                  end

  unless exact_matches.empty? && partial_match.nil?
    matches = exact_matches
    matches.push partial_match unless partial_match.nil?

    matches.each do |command|
      # the matched command contains a subapp, run subapp with the same
      # arguments (excluding the arg that matched this command)
      if command.action.instance_of?(Consoler::Application)
        result, matched = command.action._run(args)

        if matched
          return result, true
        end
      else
        match = arguments.match command.options

        next if match.nil?

        return _dispatch(command.action, match), true
      end
    end
  end

  [nil, false]
end

#respond_to_missing?(_method_name, _include_private = false) ⇒ Boolean

Accept all method_missing call

We use the name as a command name, thus we accept all names

Parameters:

  • _method_name (String)

    Name of the method

  • _include_private (bool) (defaults to: false)

    Name of the method

Returns:

  • (Boolean)


47
48
49
# File 'lib/consoler/application.rb', line 47

def respond_to_missing?(_method_name, _include_private = false)
  true
end

#run(args = ARGV, disable_usage_message = false) ⇒ mixed

Run the application with a list of arguments

Parameters:

  • args (Array) (defaults to: ARGV)

    Arguments

  • disable_usage_message (Boolean) (defaults to: false)

    Disable the usage message when nothing it matched

Returns:

  • (mixed)

    Result of your matched command, nil otherwise



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/consoler/application.rb', line 91

def run(args = ARGV, disable_usage_message = false)
  # TODO: signal handling of some kind?

  result, matched = _run(args.dup)

  if !matched && !disable_usage_message
    usage
  end

  result
rescue RuntimeError => e
  if @rescue_errors
    $stderr.puts "A runtime error occured: #{e.message.strip}"
    nil
  else
    raise e
  end
end

#usageObject

Show the usage message

Contains all commands and options, including subapps



113
114
115
116
117
118
# File 'lib/consoler/application.rb', line 113

def usage
  puts "#{@description}\n\n" unless @description.nil?
  puts 'Usage:'

  _commands_usage $PROGRAM_NAME
end