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.



249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/clio/commandline.rb', line 249

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 #.deep_copy
  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.


376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
# File 'lib/clio/commandline.rb', line 376

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



232
233
234
# File 'lib/clio/commandline.rb', line 232

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

.help(string = nil) ⇒ Object



237
238
239
# File 'lib/clio/commandline.rb', line 237

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

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



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

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

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



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

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

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



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

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

.usageObject

Command usage.



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

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

.usage=(u) ⇒ Object

Raises:

  • (ArgumentError)


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

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

Instance Method Details

#[](i) ⇒ Object



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

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

#argumentsObject



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

def arguments  ; cli.arguments  ; end

#argv_set(argv) ⇒ Object



265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/clio/commandline.rb', line 265

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



280
281
282
283
# File 'lib/clio/commandline.rb', line 280

def cli
  #parse unless @cli
  @cli
end

#commandObject



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

def command    ; cli.command    ; end

#commandsObject



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

def commands   ; cli.commands   ; end

#completion(argv = nil) ⇒ Object

TODO: adding ‘-’ is best idea?



354
355
356
357
358
359
360
361
362
363
# File 'lib/clio/commandline.rb', line 354

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



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

def parameters ; cli.parameters ; end

#parse(argv = nil) ⇒ Object



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

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

#parserObject



313
314
315
# File 'lib/clio/commandline.rb', line 313

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

#switchesObject Also known as: options



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

def switches   ; cli.options    ; end

#to_aObject



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

def to_a
  cli.to_a
end

#to_sObject



297
298
299
# File 'lib/clio/commandline.rb', line 297

def to_s
  usage.to_s
end

#to_s_helpObject



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

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



292
293
294
# File 'lib/clio/commandline.rb', line 292

def usage
  @usage
end

#valid?Boolean

Commandline fully valid?

Returns:

  • (Boolean)


348
349
350
# File 'lib/clio/commandline.rb', line 348

def valid?
  @cli.valid?
end