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)



30
31
32
33
# File 'lib/consoler/application.rb', line 30

def initialize(options = {})
  @description = options[:description]
  @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)


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

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:



196
197
198
199
200
201
202
203
204
205
206
# File 'lib/consoler/application.rb', line 196

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:



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/consoler/application.rb', line 165

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



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/consoler/application.rb', line 212

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



239
240
241
242
243
244
245
246
247
# File 'lib/consoler/application.rb', line 239

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



113
114
115
116
117
118
119
120
121
122
123
124
125
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
# File 'lib/consoler/application.rb', line 113

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)


41
42
43
# File 'lib/consoler/application.rb', line 41

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



85
86
87
88
89
90
91
92
93
94
95
# File 'lib/consoler/application.rb', line 85

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
end

#usageObject

Show the usage message

Contains all commands and options, including subapps



100
101
102
103
104
105
# File 'lib/consoler/application.rb', line 100

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

  _commands_usage $PROGRAM_NAME
end