Class: Clio::Commandline

Inherits:
Object
  • Object
show all
Defined in:
lib/clio/commandline.rb

Overview

Commandline

Clio’s Commandline class is a very versitile command line parser. A Command can be used either declaritively, defining usage and help information upfront; or lazily, whereby information about usage is built-up as the commandline actually gets use in one’s program; or you can use a mixture of the two.

Underlying Notation

As you might expect the fluent notation can be broken down into block notation.

cli = Clio::Command.new
cli.usage do
  option(:verbose, :v) do
    help('verbose output')
  end
  option(:quiet, :q) do
    help('run silently')
    xor(:V)
  end
  command(:document) do
    help('generate documentation')
    option(:output, :o) do
      type('FILE')
      help('output directory')
    end
    argument('files') do
      multiple
    end
  end
end

Clearly block notation is DRY and easier to read, but fluent notation is important to have because it allows the Commandline object to be passed around as an argument and modified easily.

Method Notation

This notation is very elegant, but slightly more limited in scope. For instance, subcommands that use non-letter characters, such as ‘:’, can not be described with this notation.

cli.usage.document('*files', '--output=FILE -o')
cli.usage('--verbose -V','--quiet -q')

cli.usage.help(
  'document'     , 'generate documentation',
  'validate'     , 'run tests or specifications',
  '--verbose'    , 'verbose output',
  '--quiet'      , 'run siltently'
)

cli.usage.document.help(
  '--output', 'output directory'
  'file*',    'files to document'
)

This notation is slightly more limited in scope… so…

cli.usage.command(:document, '--output=FILE -o', 'files*')

Bracket Shorthand Notation

The core notation can be somewhat verbose. As a further convenience commandline usage can be defined with a brief bracket shorthand. This is especailly useful when the usage is simple and statically defined.

cli.usage['document']['--output=FILE -o']['FILE*']

Using a little creativity to improve readabilty we can convert the whole example from above using this notation.

cli.usage['--verbose -V',        'verbose output'       ] \
         ['--quiet -q',          'run silently'         ] \
         ['document',            'generate documention' ] \
         [  '--output=FILE -o',  'output directory'     ] \
         [  'FILE*',             'files to document'    ]

Alternately the help information can be left out and defined in a seprate set of usage calls.

cli.usage['--verbose -V']['--quiet -q'] \
         ['document']['--output=FILE -o']['FILE*']

cli.usage.help(
  'document'  , 'generate documentation',
  'validate'  , 'run tests or specifications',
  '--verbose' , 'verbose output',
  '--quiet'   , 'run siltently'
)

cli.usage['document'].help(
  '--output', 'output directory'
  'FILE',     'files to docment'
)

A little more verbose, but a bit more intutive.

Combining Notations

Since the various notations all translate to same underlying structures, they can be mixed and matched as suites ones taste. For example we could mix Method Notation and Bracket Notation.

cli.usage.document['--output=FILE -o']['file*']
cli.usage['--verbose -V']['--quiet -q']

The important thing to keep in mind when doing this is what is returned by each type of usage call.

Commandline Parsing

With usage in place, call the parse method to process the actual commandline.

cli.parse

If no command arguments are passed to parse, ARGV is used.

Passive Parsing

The Command class allows you to declare as little or as much of the commandline interface upfront as is suitable to your application. When using the commandline object, if not already defined, options will be lazily created. For example:

cli = Clio::Commandline.new('--force')
cli.force?  #=> true

Commandline sees that you expect a ‘–force’ flag to be an acceptable option. So it will call cli.usage.option(‘force’) behind the scenes before trying to determine the actual value per the content of the command line. You can add aliases as parameters to this call as well.

cli = Clio::Commandline.new('-f')
cli.force?(:f)  #=> true

Once set, you do not need to specify the alias again:

cli.force?      #=> true

With the exception of help information, this means you can generally just use a commandline as needed without having to declare anything upfront. ++

Usage Cache

Lastly, Commandline provides a simple means to cache usage information to a configuration file, which then can be used again the next time the same command is used. This allows Commandline to provide high-performane tab completion.

Coming Soon

In the future Commandline will be able to generate Manpage templates.

TODO: Allow option setter methods (?) TODO: Allow a hash as argument to initialize (?) ++

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(argv = nil, opts = {}, &block) ⇒ Commandline

New Command.



242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/clio/commandline.rb', line 242

def initialize(argv=nil, opts={}, &block)
  argv_set(argv || ARGV)
  #if opts[:usage]
  #  @usage = opts[:usage]
  #else
  #  #@usage = load_cache
  #end
  if self.class == Commandline
    @usage = Usage.new
  else
    @usage = self.class.usage #|| Usage.new
  end
  @usage.instance_eval(&block) if block
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(s, *a) ⇒ Object

Method missing provide passive usage and parsing.

TODO: This reparses the commandline after every query.

Need only parse if usage has change.


369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
# File 'lib/clio/commandline.rb', line 369

def method_missing(s, *a)
  begin
    s = s.to_s
    case s
    when /[=]$/
      n = s.chomp('=')
      usage.option(n).type(*a)
      parse
      res = @cli.options[n.to_sym]
    when /[!]$/
      n = s.chomp('!')
      cmd = usage.commands[n.to_sym] || usage.command(n, *a)
      res = parse
    when /[?]$/
      n = s.chomp('?')
      u = usage.option(n, *a)
      parse
      res = @cli.options[u.key]
    else
      usage.option(s, *a)
      parse
      res = @cli.options[s.to_sym]
    end
  rescue Usage::ParseError => e
    res = nil
  end
  return res
end

Class Method Details

.argument(*n_type, &block) ⇒ Object



225
226
227
# File 'lib/clio/commandline.rb', line 225

def argument(*n_type, &block)
  usage.argument(*n_type, &block)
end

.help(string = nil) ⇒ Object



230
231
232
# File 'lib/clio/commandline.rb', line 230

def help(string=nil)
  usage.help(string)
end

.opt(label, help, &block) ⇒ Object Also known as: swt



219
220
221
# File 'lib/clio/commandline.rb', line 219

def opt(label, help, &block)
  usage.opt(label, help, &block)
end

.option(name, *aliases, &block) ⇒ Object Also known as: switch



213
214
215
# File 'lib/clio/commandline.rb', line 213

def option(name, *aliases, &block)
  usage.option(name, *aliases, &block)
end

.subcommand(name, help = nil, &block) ⇒ Object Also known as: command, cmd



206
207
208
# File 'lib/clio/commandline.rb', line 206

def subcommand(name, help=nil, &block)
  usage.subcommand(name, help, &block)
end

.usageObject

Command usage.



190
191
192
193
194
195
196
197
198
# File 'lib/clio/commandline.rb', line 190

def usage
  @usage ||= (
    if ancestors[1] < Commandline
      ancestors[1].usage.dup
    else
      Usage.new
    end
  )
end

.usage=(u) ⇒ Object

Raises:

  • (ArgumentError)


200
201
202
203
# File 'lib/clio/commandline.rb', line 200

def usage=(u)
  raise ArgumentError unless u <= Usage
  @usage = u
end

Instance Method Details

#[](i) ⇒ Object



311
312
313
# File 'lib/clio/commandline.rb', line 311

def [](i)
  @cli[i]
end

#argumentsObject



322
# File 'lib/clio/commandline.rb', line 322

def arguments  ; cli.arguments  ; end

#argv_set(argv) ⇒ Object



258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/clio/commandline.rb', line 258

def argv_set(argv)
  # reset parser
  @parser = nil
  # convert to array if string
  if String===argv
    argv = Shellwords.shellwords(argv)
  end
  # remove anything subsequent to '--'
  if index = argv.index('--')
    argv = argv[0...index]
  end
  @argv = argv
end

#cliObject



273
274
275
276
# File 'lib/clio/commandline.rb', line 273

def cli
  #parse unless @cli
  @cli
end

#commandObject



316
# File 'lib/clio/commandline.rb', line 316

def command    ; cli.command    ; end

#commandsObject



319
# File 'lib/clio/commandline.rb', line 319

def commands   ; cli.commands   ; end

#completion(argv = nil) ⇒ Object

TODO: adding ‘-’ is best idea?



347
348
349
350
351
352
353
354
355
356
# File 'lib/clio/commandline.rb', line 347

def completion(argv=nil)
  argv_set(argv) if argv
  @argv << "\t"
  parse
  @argv.pop
  parser.errors[0][1].completion.collect{ |s| s.to_s }
  #@argv.pop if @argv.last == '?'
  #load_cache
  #parse
end

#parametersObject

Parameters



332
# File 'lib/clio/commandline.rb', line 332

def parameters ; cli.parameters ; end

#parse(argv = nil) ⇒ Object



300
301
302
303
# File 'lib/clio/commandline.rb', line 300

def parse(argv=nil)
  argv_set(argv) if argv
  @cli = parser.parse
end

#parserObject



306
307
308
# File 'lib/clio/commandline.rb', line 306

def parser
  @parser ||= Usage::Parser.new(usage, @argv)
end

#switchesObject Also known as: options



325
# File 'lib/clio/commandline.rb', line 325

def switches   ; cli.options    ; end

#to_aObject



335
336
337
# File 'lib/clio/commandline.rb', line 335

def to_a
  cli.to_a
end

#to_sObject



290
291
292
# File 'lib/clio/commandline.rb', line 290

def to_s
  usage.to_s
end

#to_s_helpObject



295
296
297
# File 'lib/clio/commandline.rb', line 295

def to_s_help
  usage.to_s_help
end

#usageObject

def usage(name=nil, &block)

@usage ||= Usage.new(name)
@usage.instance_eval(&block) if block
@usage

end



285
286
287
# File 'lib/clio/commandline.rb', line 285

def usage
  @usage
end

#valid?Boolean

Commandline fully valid?

Returns:

  • (Boolean)


341
342
343
# File 'lib/clio/commandline.rb', line 341

def valid?
  @cli.valid?
end