ark-cli 0.4.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []