ark-cli 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8f81a9ef56e28f05713802328520747a8cd55f28
4
- data.tar.gz: c3c8d30d31f3f89fbf32eeb1f2de9082a45b494b
3
+ metadata.gz: 40d1e78d63af56864533187b51ab27bdf514cdcd
4
+ data.tar.gz: 7cc02dba83f021e537314dfccc03bc77592e39c4
5
5
  SHA512:
6
- metadata.gz: a971ddca64327ee1f4931fa09bf6597c3e43d6cb0b8f70b119d644f5814d57ce794c2a8b47ca7638027792491ccb503f653ce1bfbeef12e4866cc5d49c6b9b75
7
- data.tar.gz: 3a15696e5b260bb06537e05f078d954784dad19e9621dc4871e0f5905a65be77cca3e50d178127d6b17a66ade5f2227041203730fa54c85f568c821cf3990e62
6
+ metadata.gz: b80df1a67863e975e5d46633e9128a6fd6392a2bd1233e0b905e09d5b1158c347185ec12987561a6cc16229a8fa947dbacf1b7e3fd7a501e191e248280d7329b
7
+ data.tar.gz: c710c95aed57880c6a3f5cf01331f859ab766ef10850950466567725b1a14c36718ba0226ed08b30479fb86f78367df3b59ce75be726d265190e16906d92eccf
@@ -0,0 +1,180 @@
1
+ # ark-cli
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/ark-cli.svg)](http://badge.fury.io/rb/ark-cli)
4
+
5
+ __ark-cli__ is a Ruby library for handling command line options and
6
+ arguments. See below for basic usage. Full documentation can be found at
7
+ http://www.rubydoc.info/github/grimheart/ark-cli
8
+
9
+
10
+
11
+ ## Declaring an interface
12
+
13
+ The usual way to use ark-cli is to call `Ark::CLI.report`. This convenience
14
+ method builds a new interface and returns a `Report` object for inspection.
15
+
16
+ ```ruby
17
+ require 'ark/cli'
18
+
19
+ # Declare an interface and parse the command line
20
+ # Returns a Report instance for inspection as `r`
21
+ r = Ark::CLI.report do |s|
22
+ s.name 'example'
23
+ s.args 'host', 'path'
24
+ s.desc "An example demonstrating usage of ark-cli"
25
+
26
+ s.opt :verbose, :v,
27
+ desc: "Increase verbosity"
28
+
29
+ s.opt :port, :p,
30
+ args: 'number',
31
+ desc: "Specify an alternate port number"
32
+ end
33
+
34
+ r.args # Get all arguments received, including trailing args
35
+ r.arg[:host] # Get the value of the `host` argument
36
+ r.opt[:port] # Get the value of the `port` option
37
+ r.count[:v] # Get the number of times an option was toggled
38
+ ```
39
+
40
+ The `CLI.report` method yields a `Spec` instance, which we'll call `s`. Calls to
41
+ this instance will define how the command line is parsed. The online usage
42
+ information will also be generated from this spec.
43
+
44
+ `s` has four important methods:
45
+
46
+ ### Name and description
47
+
48
+ The `name` method sets the name of the program to use in usage information. It
49
+ expects a string. This should probably be the same as the name of the
50
+ executable.
51
+
52
+ The `desc` method sets a description of the program for generating usage
53
+ information. It expects a string.
54
+
55
+ ```ruby
56
+ s.name 'example'
57
+ s.desc "An example demonstrating usage of ark-cli"
58
+ ```
59
+
60
+ ### Declaring arguments
61
+
62
+ The `args` method defines what arguments the program will expect.
63
+
64
+ Declaring two named, expected arguments:
65
+
66
+ ```ruby
67
+ s.args 'host', 'port'
68
+ ```
69
+
70
+ Declare defaults with a colon after the argument name, like `'host:localhost'`.
71
+ Arguments with default values are optional - no error will be raised if they
72
+ aren't given.
73
+
74
+ ```ruby
75
+ s.args 'file:~/.dcf'
76
+ s.args 'host', 'port:22', "user:#{ENV['USER']}"
77
+ ```
78
+
79
+ Declaring a variadic interface with a glob, `'dest...'`
80
+
81
+ ```ruby
82
+ s.args 'target', 'dest...'
83
+ ```
84
+
85
+ ### Declaring options
86
+
87
+ The `opt` method defines options.
88
+
89
+ Flags are simple options without arguments:
90
+
91
+ ```ruby
92
+ s.opt :verbose, :v,
93
+ desc: "Increase verbosity"
94
+ ```
95
+
96
+ Flags are false by default and set true when given on the command line. A count
97
+ is kept of the number of times the flag was specified, and stored in the report
98
+ as `r.count`. The `desc` field is used to generate usage information.
99
+
100
+ Options can also take arguments:
101
+
102
+ ```ruby
103
+ s.opt :port, :p,
104
+ args: 'number',
105
+ desc: "Specify an alternate port number"
106
+ ```
107
+
108
+
109
+
110
+ ## Inspecting the `Report` object
111
+
112
+ The `Ark::CLI#report` method returns a `Report` instance which we can inspect
113
+ for information parsed from the command line.
114
+
115
+ Supposing `r` is a returned `Report` instance, we can get all arguments with the
116
+ `r.args` method. This will include any trailing arguments.
117
+
118
+ ```ruby
119
+ host, path = r.args
120
+ ```
121
+
122
+ Get the value of a named argument with the `r.arg` method:
123
+
124
+ ```ruby
125
+ host = r.arg[:host]
126
+ path = r.arg[:path]
127
+ ```
128
+
129
+ Inspect the value of an option with `r.opt`:
130
+
131
+ ```ruby
132
+ verbose = r.opt[:v]
133
+ port = r.opt[:port]
134
+ ```
135
+
136
+ Get a count of the number of times a flag was specified with `r.count`:
137
+
138
+ ```ruby
139
+ verbosity = r.count[:verbose]
140
+ ```
141
+
142
+ Get an array of trailing arguments with `r.trailing`:
143
+
144
+ ```ruby
145
+ path1, path2 = r.trailing
146
+ ```
147
+
148
+
149
+
150
+ ## Usage information
151
+
152
+ A help option is defined for all interfaces with the `-h` and `--help` flags.
153
+ When the help option is given, the program will print usage information and exit
154
+ immediately. Usage information is constructed from the `Spec` built during the
155
+ CLI declaration.
156
+
157
+ Usage information for the above declaration would look like this:
158
+
159
+ example [OPTION...] HOST PATH
160
+
161
+ An example demonstrating usage of ark-cli
162
+
163
+ OPTIONS:
164
+
165
+ -h --help
166
+ Print usage information
167
+
168
+ -v --verbose
169
+ Increase verbosity
170
+
171
+ -p --port NUMBER
172
+ Specify an alternate port number
173
+
174
+
175
+
176
+ ## Example script
177
+
178
+ A working example script with comments can be found at `example/hello.rb` --
179
+ invoke the script with the `--help` option for usage information.
180
+
@@ -1,4 +1,4 @@
1
- # ark-cli - a simple command line interface for Ruby
1
+ # ark-cli - a command line interface library for Ruby
2
2
  # Copyright 2015 Macquarie Sharpless <macquarie.sharpless@gmail.com>
3
3
  #
4
4
  # This program is free software: you can redistribute it and/or modify
@@ -18,6 +18,11 @@
18
18
  require 'ark/utility'
19
19
  include Ark::Log
20
20
 
21
+ require_relative 'cli/interface.rb'
22
+ require_relative 'cli/option.rb'
23
+ require_relative 'cli/spec.rb'
24
+ require_relative 'cli/report.rb'
25
+
21
26
 
22
27
  module Ark # :nodoc:
23
28
 
@@ -25,499 +30,16 @@ module Ark # :nodoc:
25
30
  #
26
31
  # Call #begin to define a new interface and parse the command line. See
27
32
  # +README.md+ or +example/hello.rb+ for more information.
28
-
29
- # Represents an option and stores the option's current state, as well as
30
- # usage information.
31
- class Option
32
- # Initialize a new Option instance
33
- # [+keys+] A list of names this option will be identified by
34
- # [+args+] A list of argument named this option will expect
35
- # [+desc+] A short description of this option
36
- def initialize(long, short=nil, args=nil, desc=nil)
37
- @long = long
38
- @short = short
39
- @args = args || []
40
- @vals = []
41
- @flag = false
42
- @count = 0
43
- @desc = desc || ''
44
- end
45
- # A count of how many times this option has been given on the command line.
46
- # Useful for flags that might be specified repeatedly, like +-vvv+ to raise
47
- # verbosity three times.
48
- attr_reader :count
49
- # A short description of the option, if given
50
- attr_reader :desc
51
- # Long name for this option
52
- attr_reader :long
53
- # Short name for this option
54
- attr_reader :short
55
-
56
- # Return the number of arguments this option expects
57
- def arity()
58
- return @args.length
59
- end
60
-
61
- # Return a count of how many arguments this option still expects
62
- def vals_needed()
63
- if self.flag?
64
- return 0
65
- else
66
- return @args.length - @vals.length
67
- end
68
- end
69
-
70
- # True if this option has received all the arguments it expects, or if this
71
- # option expects no arguments
72
- def full?
73
- return self.vals_needed == 0
74
- end
75
-
76
- # True if this option expects no arguments; opposite of #has_args?
77
- def flag?
78
- return @args.empty?
79
- end
80
-
81
- # Toggle this option to the true state and increment the toggle count. Only
82
- # valid for options which expect no argument (flags). Attempting to toggle
83
- # a option with expected arguments will raise an error.
84
- def toggle()
85
- if self.flag?
86
- @count += 1
87
- @flag = true
88
- else
89
- raise StandardError, "Tried to toggle an option which expects an argument"
90
- end
91
- end
92
-
93
- # Pass an argument +arg+ to this option
94
- def push(arg)
95
- @vals << arg
96
- end
97
-
98
- # Return the current value of this option
99
- def value()
100
- if self.flag?
101
- return @flag
102
- else
103
- if self.full? && @vals.length == 1
104
- return @vals[0]
105
- elsif self.full?
106
- return @vals
107
- else
108
- return nil
109
- end
110
- end
111
- end
112
-
113
- # True if this option expects an argument. Opposite of #flag?
114
- def has_args?
115
- return @args.length > 0
116
- end
117
-
118
- # Return basic usage information: the option's names and arguments
119
- def header()
120
- if self.flag?
121
- args = ''
122
- else
123
- args = ' ' + @args.join(', ').upcase
124
- end
125
- short = @short ? "-#{@short} " : ''
126
- return "#{short}--#{@long}#{args}"
127
- end
128
-
129
- # Represent this option as a string
130
- def to_s()
131
- return "(#{self.header})"
132
- end
133
- end
134
-
135
-
136
- class CLI
137
-
138
- # Raised when the command line is malformed
139
- class SyntaxError < ArgumentError
140
- end
141
-
33
+ module CLI
142
34
  # Convenience method for interface declarations. Yields the CLI instance and
143
35
  # then returns it after parsing. Equivalent to instantiating a CLI instance
144
36
  # with #new, modifying it, and then calling #parse
145
37
  #
146
38
  # +args+ is an array of strings, which defaults to ARGV
147
39
  def self.report(args=ARGV, &block)
148
- cli = self.new(args, &block)
149
- return cli.report
150
- end
151
-
152
- # Initialize a CLI instance.
153
- #
154
- # +args+ must be an array of strings, like ARGV
155
- def initialize(args, &block)
156
- self.rebuild(args, &block)
157
- end
158
-
159
- attr_reader :report
160
-
161
- def rebuild(input=ARGV, &block)
162
- @input = input
163
- @spec = Spec.new
164
- yield @spec
165
- @spec.opt :help, :h, desc: "Print usage information"
166
- self.parse
167
- end
168
-
169
- # Parse the command line
170
- def parse()
171
- taking_options = true
172
- last_opt = nil
173
- refargs = @spec.get_args.clone
174
-
175
- @report = Report.new()
176
-
177
- @input.each do |word|
178
- dbg "Parsing '#{word}'"
179
- if last_opt && last_opt.has_args? && !last_opt.full?
180
- dbg "Got argument '#{word}' for '#{last_opt}'", 1
181
- last_opt.push(word)
182
- else
183
- if word[/^-/] && taking_options
184
- if word[/^-[^-]/]
185
- dbg "Identified short option(s)", 1
186
- shorts = word[/[^-]+$/].split('')
187
- shorts.each_with_index do |short, i|
188
- last_short = i == (shorts.length - 1)
189
- opt = @spec.get_opt(short)
190
- last_opt = opt
191
- if opt.has_args? && shorts.length > 1 && !last_short
192
- raise SyntaxError, "Error: -#{short} in compound option '#{word}' expects an argument"
193
- elsif opt.flag?
194
- opt.toggle()
195
- dbg "Toggled flag '#{opt}'", 1
196
- end
197
- end
198
- elsif word[/^--/]
199
- dbg "Identified long option", 1
200
- key = word[/[^-]+$/]
201
- opt = @spec.get_opt(key)
202
- last_opt = opt
203
- if opt.flag?
204
- opt.toggle()
205
- dbg "Toggled #{opt}", 1
206
- end
207
- end
208
- else
209
- dbg "Parsed output arg", 1
210
- taking_options = false
211
- @report.args << word
212
- key = refargs.shift
213
- if key
214
- if key == @spec.get_variad
215
- @report.arg[key] = []
216
- @report.arg[key] << word
217
- else
218
- @report.arg[key] = word
219
- end
220
- elsif @spec.is_variadic?
221
- @report.arg[@spec.get_variad] << word
222
- else
223
- @report.trailing << word
224
- end
225
- end
226
- end
227
- end
228
- @spec.get_opts.each do |name, opt|
229
- @report.opt[name] = opt.value
230
- @report.count[name] = opt.count
231
- end
232
- @spec.get_args.each do |name|
233
- if @report.arg[name].nil?
234
- if @spec.has_default?(name)
235
- @report.arg[name] = @spec.get_default(name)
236
- @report.args << @report.arg[name]
237
- end
238
- end
239
- end
240
- if @report.opt[:help]
241
- self.print_usage()
242
- end
243
- end
244
-
245
- # Construct usage information
246
- def usage()
247
- tb = TextBuilder.new()
248
-
249
- tb.next 'USAGE:'
250
- tb.push @spec.get_name if @spec.get_name
251
-
252
- tb.push '[OPTION'
253
- tb.add '...' if @spec.has_options?
254
- tb.add ']'
255
-
256
- if @spec.has_args?
257
- if @spec.is_variadic?
258
- singles = @spec.get_args[0..-2].map do |a|
259
- if @spec.has_default?(a)
260
- a = "[#{a}]"
261
- end
262
- a.upcase
263
- end
264
- tb.push singles
265
- v = @spec.get_args.last.upcase
266
- tb.push "[#{v}1 #{v}2...]"
267
- else
268
- argmap = @spec.get_args.map do |a|
269
- if @spec.has_default?(a)
270
- a = "[#{a}]"
271
- end
272
- a.upcase
273
- end
274
- tb.push argmap
275
- end
276
- end
277
-
278
- if @spec.get_desc
279
- tb.skip @spec.get_desc
280
- tb.wrap(indent: 4)
281
- end
282
-
283
- tb.skip 'OPTIONS:'
284
- tb.skip
285
-
286
- @spec.get_opts.values.uniq.each do |opt|
287
- tb.indent 4
288
- tb.push opt.header
289
- if opt.desc
290
- tb.next
291
- tb.indent 8
292
- tb.push opt.desc
293
- end
294
- tb.skip
295
- end
296
-
297
- return tb.print
298
- end
299
-
300
- # Print usage information and exit
301
- def print_usage()
302
- puts self.usage
303
- exit 0
304
- end
305
- end
306
-
307
- class Spec
308
-
309
- # Raised when a nonexistent option is received
310
- class NoSuchOptionError < ArgumentError
311
- end
312
-
313
- # Raised when there is a syntax error in the args declaration
314
- class ArgumentSyntaxError < ArgumentError
315
- end
316
-
317
- # Initialize a bare interface spec
318
- def initialize()
319
- @args = []
320
- @options = {}
321
- @variadic = false
322
- @option_listing = false
323
- @trailing_error = false
324
- end
325
- # If true, the full option list will always be displayed in the usage info
326
- # header
327
- attr_reader :option_listing
328
- # If true, an error will be raised if trailing arguments are given
329
- attr_reader :trailing_error
330
-
331
- private
332
-
333
- def strip(arg)
334
- return arg[/^(.+?)(\.\.\.$|_$|$)/, 1]
335
- end
336
-
337
- def optional?(arg)
338
- return !arg[/_$/].nil?
339
- end
340
-
341
- def variadic?(arg)
342
- return !arg[/\.\.\.$/].nil?
343
- end
344
-
345
- def parse_arg(arg, default: nil, last: false)
346
- stripped = strip(arg)
347
- @args << stripped
348
- if optional?(arg)
349
- @optional << stripped
350
- end
351
- unless default.nil?
352
- @defaults[stripped] = default
353
- end
354
- if variadic?(arg)
355
- if last
356
- @variadic = true
357
- @variad = stripped
358
- else
359
- raise ArgumentSyntaxError,
360
- "Variadic arguments must come last. Offending variad is '#{arg}'"
361
- end
362
- end
363
- end
364
-
365
- public
366
-
367
- def get_name
368
- return @name
369
- end
370
-
371
- def get_desc
372
- return @desc
373
- end
374
-
375
- def get_args
376
- return @args
377
- end
378
-
379
- def get_opts
380
- return @options
381
- end
382
-
383
- def is_variadic?
384
- return @variadic
385
- end
386
-
387
- def get_variad
388
- return @variad
389
- end
390
-
391
- def get_defaults
392
- return @defaults
393
- end
394
-
395
- # Get an Option object for the given option +name+
396
- def get_opt(name)
397
- name = name.to_sym
398
- if !@options.keys.member?(name)
399
- raise NoSuchOptionError, "Error, no such option: '#{name}'"
400
- end
401
- return @options[name]
402
- end
403
-
404
- def is_optional?(arg)
405
- @optional.member?(arg.to_s)
406
- end
407
-
408
- def has_default?(arg)
409
- @defaults.key?(arg.to_s)
410
- end
411
-
412
- def get_default(arg)
413
- @defaults[arg.to_s]
40
+ i = Interface.new(args, &block)
41
+ return i.report
414
42
  end
415
-
416
- def has_args?
417
- @args.length > 0
418
- end
419
-
420
- def has_options?
421
- @options.values.uniq.length > 1
422
- end
423
-
424
- # Specify general information about the program
425
- # [+name+] Name of the program
426
- # [+desc+] Short description of the program
427
- # [+args+] A list of named arguments
428
- def header(name: nil, desc: nil, args: [])
429
- self.name(name)
430
- self.desc(desc)
431
- self.args(args)
432
- end
433
-
434
- # Set the name of the program to +str+
435
- def name(str)
436
- @name = str.to_s if str
437
- end
438
-
439
- # Set the description of the program to +str+
440
- def desc(str)
441
- @desc = str.to_s if str
442
- end
443
-
444
- # Define what arguments the program will accept
445
- def args(*input)
446
- @args = []
447
- @optional = []
448
- @defaults = {}
449
-
450
- input.flatten.each_with_index do |item, i|
451
- list_last = (input.length - (i + 1)) == 0
452
- if item.is_a?(Hash)
453
- item.each_with_index do |pair,ii|
454
- k = pair[0].to_s
455
- v = pair[1]
456
- hash_last = (item.length - (ii + 1)) == 0
457
- last = hash_last && list_last
458
- parse_arg(k, default: v, last: last)
459
- end
460
- else
461
- parse_arg(item, last: list_last)
462
- end
463
- end
464
-
465
- @refargs = @args.clone
466
- end
467
-
468
- # Define an Option
469
- # [+keys+] A list of names for this option
470
- # [+args+] A list of arguments the option expects
471
- # [+desc+] A short description of the option, used to provide usage info
472
- def opt(long, short=nil, args: nil, desc: nil)
473
- long = long.to_sym
474
- short = short.to_sym if short
475
- args = [args] if args.is_a?(String)
476
- args.map!(&:to_sym) if args
477
- o = Option.new(long, short, args, desc)
478
- @options[long] = o
479
- @options[short] = o if short
480
- end
481
-
482
- # Force the full option list display in the usage info, no matter how many
483
- # options the program has
484
- def force_option_list()
485
- @option_list = true
486
- end
487
-
488
- # The parser will raise an error on finding trailing arguments (default
489
- # behavior is to ignore and stuff the trailing args into Report.trailing_args)
490
- def raise_on_trailing()
491
- @trailing_error = true
492
- end
493
-
494
- end
495
-
496
- class Report
497
- def initialize()
498
- @args = []
499
- @named_args = {}
500
- @trailing_args = []
501
- @counts = {}
502
- @options = {}
503
- end
504
-
505
- def args
506
- return @args
507
- end
508
- def arg
509
- return @named_args
510
- end
511
- def trailing
512
- return @trailing_args
513
- end
514
- def opt
515
- return @options
516
- end
517
- def count
518
- return @counts
519
- end
520
-
521
43
  end
522
44
 
523
45
  end # module Ark
@@ -0,0 +1,173 @@
1
+ module Ark
2
+ module CLI
3
+
4
+ class Interface
5
+
6
+ # Raised when the command line is malformed
7
+ class SyntaxError < ArgumentError
8
+ end
9
+
10
+ # Initialize an Interface instance.
11
+ #
12
+ # +args+ must be an array of strings, like ARGV
13
+ def initialize(args, &block)
14
+ self.rebuild(args, &block)
15
+ end
16
+
17
+ attr_reader :report
18
+
19
+ def rebuild(input=ARGV, &block)
20
+ @input = input
21
+ @spec = Spec.new
22
+ yield @spec
23
+ @spec.opt :help, :h, desc: "Print usage information"
24
+ self.parse
25
+ end
26
+
27
+ # Parse the command line
28
+ def parse()
29
+ taking_options = true
30
+ last_opt = nil
31
+ refargs = @spec.get_args.clone
32
+
33
+ @report = Report.new()
34
+
35
+ @input.each do |word|
36
+ dbg "Parsing '#{word}'"
37
+ if last_opt && last_opt.has_args? && !last_opt.full?
38
+ dbg "Got argument '#{word}' for '#{last_opt}'", 1
39
+ last_opt.push(word)
40
+ else
41
+ if word[/^-/] && taking_options
42
+ if word[/^-[^-]/]
43
+ dbg "Identified short option(s)", 1
44
+ shorts = word[/[^-]+$/].split('')
45
+ shorts.each_with_index do |short, i|
46
+ last_short = i == (shorts.length - 1)
47
+ opt = @spec.get_opt(short)
48
+ last_opt = opt
49
+ if opt.has_args? && shorts.length > 1 && !last_short
50
+ raise SyntaxError, "Error: -#{short} in compound option '#{word}' expects an argument"
51
+ elsif opt.flag?
52
+ opt.toggle()
53
+ dbg "Toggled flag '#{opt}'", 1
54
+ end
55
+ end
56
+ elsif word[/^--/]
57
+ dbg "Identified long option", 1
58
+ key = word[/[^-]+$/]
59
+ opt = @spec.get_opt(key)
60
+ last_opt = opt
61
+ if opt.flag?
62
+ opt.toggle()
63
+ dbg "Toggled #{opt}", 1
64
+ end
65
+ end
66
+ else
67
+ dbg "Parsed output arg", 1
68
+ taking_options = false
69
+ @report.args << word
70
+ key = refargs.shift
71
+ if key
72
+ if key == @spec.get_variad
73
+ @report.arg[key] = []
74
+ @report.arg[key] << word
75
+ else
76
+ @report.arg[key] = word
77
+ end
78
+ elsif @spec.is_variadic?
79
+ @report.arg[@spec.get_variad] << word
80
+ else
81
+ @report.trailing << word
82
+ end
83
+ end
84
+ end
85
+ end
86
+ @spec.get_opts.each do |name, opt|
87
+ @report.opt[name] = opt.value
88
+ @report.count[name] = opt.count
89
+ end
90
+ @spec.get_args.each do |name|
91
+ if @report.arg[name].nil?
92
+ if @spec.has_default?(name)
93
+ @report.arg[name] = @spec.get_default(name)
94
+ @report.args << @report.arg[name]
95
+ end
96
+ end
97
+ end
98
+ if @report.opt[:help]
99
+ self.print_usage()
100
+ end
101
+ end
102
+
103
+ # Construct usage information
104
+ def usage()
105
+ tb = TextBuilder.new()
106
+
107
+ tb.next 'USAGE:'
108
+ tb.push @spec.get_name if @spec.get_name
109
+
110
+ if @spec.get_opts.values.uniq.length < 5 || @spec.option_listing
111
+ @spec.get_opts.values.uniq.each do |opt|
112
+ tb.push "[#{opt.header}]"
113
+ end
114
+ else
115
+ tb.push '[OPTION...]'
116
+ end
117
+
118
+ if @spec.has_args?
119
+ if @spec.is_variadic?
120
+ singles = @spec.get_args[0..-2].map do |a|
121
+ if @spec.has_default?(a)
122
+ a = "[#{a}]"
123
+ end
124
+ a.upcase
125
+ end
126
+ tb.push singles
127
+ v = @spec.get_args.last.upcase
128
+ tb.push "[#{v}1 #{v}2...]"
129
+ else
130
+ argmap = @spec.get_args.map do |a|
131
+ if @spec.has_default?(a)
132
+ a = "[#{a}]"
133
+ end
134
+ a.upcase
135
+ end
136
+ tb.push argmap
137
+ end
138
+ end
139
+
140
+ tb.wrap indent: 7, indent_after: true, segments: true
141
+
142
+ if @spec.get_desc
143
+ tb.skip @spec.get_desc
144
+ tb.wrap(indent: 4)
145
+ end
146
+
147
+ tb.skip 'OPTIONS:'
148
+ tb.skip
149
+
150
+ @spec.get_opts.values.uniq.each do |opt|
151
+ tb.indent 4
152
+ tb.push opt.header
153
+ if opt.desc
154
+ tb.next
155
+ tb.indent 8
156
+ tb.push opt.desc
157
+ end
158
+ tb.skip
159
+ end
160
+
161
+ return tb.print
162
+ end
163
+
164
+ # Print usage information and exit
165
+ def print_usage()
166
+ puts self.usage
167
+ exit 0
168
+ end
169
+ end
170
+
171
+ end # module CLI
172
+ end # module Ark
173
+
@@ -0,0 +1,118 @@
1
+ module Ark
2
+ module CLI
3
+
4
+ # Represents an option and stores the option's current state, as well as
5
+ # usage information.
6
+ class Option
7
+
8
+ # Initialize a new Option instance
9
+ # [+keys+] A list of names this option will be identified by
10
+ # [+args+] A list of argument named this option will expect
11
+ # [+desc+] A short description of this option
12
+ def initialize(long, short=nil, args=nil, desc=nil)
13
+ @long = long
14
+ @short = short
15
+ @args = args || []
16
+ @vals = []
17
+ @flag = false
18
+ @count = 0
19
+ @desc = desc || ''
20
+ end
21
+
22
+ # A count of how many times this option has been given on the command line.
23
+ # Useful for flags that might be specified repeatedly, like +-vvv+ to raise
24
+ # verbosity three times.
25
+ attr_reader :count
26
+
27
+ # A short description of the option, if given
28
+ attr_reader :desc
29
+
30
+ # Long name for this option
31
+ attr_reader :long
32
+
33
+ # Short name for this option
34
+ attr_reader :short
35
+
36
+ # Return the number of arguments this option expects
37
+ def arity()
38
+ return @args.length
39
+ end
40
+
41
+ # Return a count of how many arguments this option still expects
42
+ def vals_needed()
43
+ if self.flag?
44
+ return 0
45
+ else
46
+ return @args.length - @vals.length
47
+ end
48
+ end
49
+
50
+ # True if this option has received all the arguments it expects, or if this
51
+ # option expects no arguments
52
+ def full?
53
+ return self.vals_needed == 0
54
+ end
55
+
56
+ # True if this option expects no arguments; opposite of #has_args?
57
+ def flag?
58
+ return @args.empty?
59
+ end
60
+
61
+ # Toggle this option to the true state and increment the toggle count. Only
62
+ # valid for options which expect no argument (flags). Attempting to toggle
63
+ # a option with expected arguments will raise an error.
64
+ def toggle()
65
+ if self.flag?
66
+ @count += 1
67
+ @flag = true
68
+ else
69
+ raise StandardError, "Tried to toggle an option which expects an argument"
70
+ end
71
+ end
72
+
73
+ # Pass an argument +arg+ to this option
74
+ def push(arg)
75
+ @vals << arg
76
+ end
77
+
78
+ # Return the current value of this option
79
+ def value()
80
+ if self.flag?
81
+ return @flag
82
+ else
83
+ if self.full? && @vals.length == 1
84
+ return @vals[0]
85
+ elsif self.full?
86
+ return @vals
87
+ else
88
+ return nil
89
+ end
90
+ end
91
+ end
92
+
93
+ # True if this option expects an argument. Opposite of #flag?
94
+ def has_args?
95
+ return @args.length > 0
96
+ end
97
+
98
+ # Return basic usage information: the option's names and arguments
99
+ def header()
100
+ if self.flag?
101
+ args = ''
102
+ else
103
+ args = ' ' + @args.join(', ').upcase
104
+ end
105
+ short = @short ? "-#{@short} " : ''
106
+ return "#{short}--#{@long}#{args}"
107
+ end
108
+
109
+ # Represent this option as a string
110
+ def to_s()
111
+ return "(#{self.header})"
112
+ end
113
+ end
114
+
115
+
116
+ end # module CLI
117
+ end # module Ark
118
+
@@ -0,0 +1,32 @@
1
+ module Ark
2
+ module CLI
3
+
4
+ class Report
5
+ def initialize()
6
+ @args = []
7
+ @named_args = {}
8
+ @trailing_args = []
9
+ @counts = {}
10
+ @options = {}
11
+ end
12
+
13
+ def args
14
+ return @args
15
+ end
16
+ def arg
17
+ return @named_args
18
+ end
19
+ def trailing
20
+ return @trailing_args
21
+ end
22
+ def opt
23
+ return @options
24
+ end
25
+ def count
26
+ return @counts
27
+ end
28
+ end
29
+
30
+ end # module CLI
31
+ end # module Ark
32
+
@@ -0,0 +1,183 @@
1
+ module Ark
2
+ module CLI
3
+
4
+ class Spec
5
+
6
+ # Raised when a nonexistent option is received
7
+ class NoSuchOptionError < ArgumentError
8
+ end
9
+
10
+ # Raised when there is a syntax error in the args declaration
11
+ class ArgumentSyntaxError < ArgumentError
12
+ end
13
+
14
+ # Initialize a bare interface spec
15
+ def initialize()
16
+ @args = []
17
+ @options = {}
18
+ @variadic = false
19
+ @option_listing = false
20
+ @trailing_error = false
21
+ end
22
+
23
+ # If true, the full option list will always be displayed in the usage info
24
+ # header
25
+ attr_reader :option_listing
26
+
27
+ # If true, an error will be raised if trailing arguments are given
28
+ attr_reader :trailing_error
29
+
30
+ private
31
+
32
+ def strip(arg)
33
+ return arg[/^(\S+?)(:|\.\.\.|$)/, 1]
34
+ end
35
+
36
+ def defaulted?(arg)
37
+ return !arg[/^\S+?:.+/].nil?
38
+ end
39
+
40
+ def parse_default(arg)
41
+ return arg[/^.+?:(.+)/, 1]
42
+ end
43
+
44
+ def variadic?(arg)
45
+ return !arg[/\.\.\.$/].nil?
46
+ end
47
+
48
+ def parse_arg(arg, default: nil, last: false)
49
+ stripped = strip(arg)
50
+ @args << stripped
51
+ if defaulted?(arg)
52
+ @defaults[stripped] = parse_default(arg)
53
+ end
54
+ if variadic?(arg)
55
+ if last
56
+ @variadic = true
57
+ @variad = stripped
58
+ else
59
+ raise ArgumentSyntaxError,
60
+ "Variadic arguments must come last. Offending variad is '#{arg}'"
61
+ end
62
+ end
63
+ end
64
+
65
+ public
66
+
67
+ def get_name
68
+ return @name
69
+ end
70
+
71
+ def get_desc
72
+ return @desc
73
+ end
74
+
75
+ def get_args
76
+ return @args
77
+ end
78
+
79
+ def has_options?
80
+ @options.values.uniq.length > 1
81
+ end
82
+
83
+ def get_opts
84
+ return @options
85
+ end
86
+
87
+ # Get an Option object for the given option +name+
88
+ def get_opt(name)
89
+ name = name.to_sym
90
+ if !@options.keys.member?(name)
91
+ raise NoSuchOptionError, "Error, no such option: '#{name}'"
92
+ end
93
+ return @options[name]
94
+ end
95
+
96
+ def is_variadic?
97
+ return @variadic
98
+ end
99
+
100
+ def get_variad
101
+ return @variad
102
+ end
103
+
104
+ def has_default?(arg)
105
+ @defaults.key?(arg.to_s)
106
+ end
107
+
108
+ def get_defaults
109
+ return @defaults
110
+ end
111
+
112
+ def get_default(arg)
113
+ @defaults[arg.to_s]
114
+ end
115
+
116
+ def has_args?
117
+ @args.length > 0
118
+ end
119
+
120
+ # Specify general information about the program
121
+ # [+name+] Name of the program
122
+ # [+desc+] Short description of the program
123
+ # [+args+] A list of named arguments
124
+ def header(name: nil, desc: nil, args: [])
125
+ self.name(name)
126
+ self.desc(desc)
127
+ self.args(args)
128
+ end
129
+
130
+ # Set the name of the program to +str+
131
+ def name(str)
132
+ @name = str.to_s if str
133
+ end
134
+
135
+ # Set the description of the program to +str+
136
+ def desc(str)
137
+ @desc = str.to_s if str
138
+ end
139
+
140
+ # Define what arguments the program will accept
141
+ def args(*input)
142
+ @args = []
143
+ @defaults = {}
144
+
145
+ input.flatten.each_with_index do |item, i|
146
+ last = (input.length - (i + 1)) == 0
147
+ parse_arg(item, last: last)
148
+ end
149
+
150
+ @refargs = @args.clone
151
+ end
152
+
153
+ # Define an Option
154
+ # [+keys+] A list of names for this option
155
+ # [+args+] A list of arguments the option expects
156
+ # [+desc+] A short description of the option, used to provide usage info
157
+ def opt(long, short=nil, args: nil, desc: nil)
158
+ long = long.to_sym
159
+ short = short.to_sym if short
160
+ args = [args] if args.is_a?(String)
161
+ args.map!(&:to_sym) if args
162
+ o = Option.new(long, short, args, desc)
163
+ @options[long] = o
164
+ @options[short] = o if short
165
+ end
166
+
167
+ # Force the full option list display in the usage info, no matter how many
168
+ # options the program has
169
+ def force_option_list()
170
+ @option_list = true
171
+ end
172
+
173
+ # The parser will raise an error on finding trailing arguments (default
174
+ # behavior is to ignore and stuff the trailing args into Report.trailing_args)
175
+ def raise_on_trailing()
176
+ @trailing_error = true
177
+ end
178
+
179
+ end
180
+
181
+ end # module CLI
182
+ end # module Ark
183
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ark-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Macquarie Sharpless
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-06 00:00:00.000000000 Z
11
+ date: 2015-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ark-util
@@ -16,31 +16,36 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.2'
19
+ version: '0.3'
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: 0.2.0
22
+ version: 0.3.0
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - "~>"
28
28
  - !ruby/object:Gem::Version
29
- version: '0.2'
29
+ version: '0.3'
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 0.2.0
32
+ version: 0.3.0
33
33
  description: |
34
- A simple library for parsing options and arguments from the commandline. Parses
35
- ARGV and returns an object holding information about what options were set on
36
- the commandline, and what arguments were given.
34
+ A library for parsing options and arguments from the commandline. Parses ARGV
35
+ and returns an object holding information about what options were set on the
36
+ commandline, and what arguments were given.
37
37
  email:
38
38
  - macquarie.sharpless@gmail.com
39
39
  executables: []
40
40
  extensions: []
41
41
  extra_rdoc_files: []
42
42
  files:
43
+ - README.md
43
44
  - lib/ark/cli.rb
45
+ - lib/ark/cli/interface.rb
46
+ - lib/ark/cli/option.rb
47
+ - lib/ark/cli/report.rb
48
+ - lib/ark/cli/spec.rb
44
49
  homepage: https://github.com/grimheart/ark-cli
45
50
  licenses:
46
51
  - GPL-3.0
@@ -64,5 +69,5 @@ rubyforge_project:
64
69
  rubygems_version: 2.4.5
65
70
  signing_key:
66
71
  specification_version: 4
67
- summary: Simple commandline interface
72
+ summary: Command line interface library
68
73
  test_files: []