ark-cli 0.3.3.pre
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 +7 -0
- data/lib/ark/cli.rb +344 -0
- metadata +68 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 72720815ec557828109dd2b41d70c7696d2af6d1
|
4
|
+
data.tar.gz: cb7c8672236156022779ef6efd10611bbc072791
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4cc0f8a933c6d7aae990c0ca27f400553715063cdbeceba75eebef89f987ab037e906990df1ab7e725022ca8d776dca33aa42053bc2b4ca9d8aaa77f4f4b1f09
|
7
|
+
data.tar.gz: 7fe81ef9f2a608955236343baf419394fa2bb726f7b9fdc3e6c4de91066891a2c08485a7f42105ce8f18e4cea27c7dc2c69acca6164b3c23e04bc41e5d254a27
|
data/lib/ark/cli.rb
ADDED
@@ -0,0 +1,344 @@
|
|
1
|
+
# ark-cli - a simple command line interface for Ruby
|
2
|
+
# Copyright 2015 Macquarie Sharpless <macquarie.sharpless@gmail.com>
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
|
18
|
+
require 'ark/utility'
|
19
|
+
include Ark::Log
|
20
|
+
|
21
|
+
|
22
|
+
module Ark # :nodoc:
|
23
|
+
|
24
|
+
# A library for handling options and arguments from the command line.
|
25
|
+
#
|
26
|
+
# Call #begin to define a new interface and parse the command line. See
|
27
|
+
# +README.md+ or +example/hello.rb+ for more information.
|
28
|
+
class CLI
|
29
|
+
|
30
|
+
# Raised when a nonexistent option is received
|
31
|
+
class NoSuchOptionError < ArgumentError
|
32
|
+
end
|
33
|
+
|
34
|
+
# Raised when the command line is malformed
|
35
|
+
class SyntaxError < ArgumentError
|
36
|
+
end
|
37
|
+
|
38
|
+
# Represents an option and stores the option's current state, as well as
|
39
|
+
# usage information.
|
40
|
+
class Option
|
41
|
+
# Initialize a new Option instance
|
42
|
+
# [+keys+] A list of names this option will be identified by
|
43
|
+
# [+args+] A list of argument named this option will expect
|
44
|
+
# [+desc+] A short description of this option
|
45
|
+
def initialize(keys, args=nil, desc=nil)
|
46
|
+
@keys = keys
|
47
|
+
@args = args || []
|
48
|
+
@vals = []
|
49
|
+
@flag = false
|
50
|
+
@count = 0
|
51
|
+
@desc = desc || ''
|
52
|
+
end
|
53
|
+
# A count of how many times this option has been given on the command line.
|
54
|
+
# Useful for flags that might be specified repeatedly, like +-vvv+ to raise
|
55
|
+
# verbosity three times.
|
56
|
+
attr_reader :count
|
57
|
+
# A short description of the option, if given
|
58
|
+
attr_reader :desc
|
59
|
+
|
60
|
+
# Return the number of arguments this option expects
|
61
|
+
def arity()
|
62
|
+
return @args.length
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return a count of how many arguments this option still expects
|
66
|
+
def vals_needed()
|
67
|
+
if self.flag?
|
68
|
+
return 0
|
69
|
+
else
|
70
|
+
return @args.length - @vals.length
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# True if this option has received all the arguments it expects, or if this
|
75
|
+
# option expects no arguments
|
76
|
+
def full?
|
77
|
+
return self.vals_needed == 0
|
78
|
+
end
|
79
|
+
|
80
|
+
# True if this option expects no arguments; opposite of #has_args?
|
81
|
+
def flag?
|
82
|
+
return @args.empty?
|
83
|
+
end
|
84
|
+
|
85
|
+
# Toggle this option to the true state and increment the toggle count. Only
|
86
|
+
# valid for options which expect no argument (flags). Attempting to toggle
|
87
|
+
# a option with expected arguments will raise an error.
|
88
|
+
def toggle()
|
89
|
+
if self.flag?
|
90
|
+
@count += 1
|
91
|
+
@flag = true
|
92
|
+
else
|
93
|
+
raise StandardError, "Tried to toggle an option which expects an argument"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Pass an argument +arg+ to this option
|
98
|
+
def push(arg)
|
99
|
+
@vals << arg
|
100
|
+
end
|
101
|
+
|
102
|
+
# Return the current value of this option
|
103
|
+
def value()
|
104
|
+
if self.flag?
|
105
|
+
return @flag
|
106
|
+
else
|
107
|
+
if self.full? && @vals.length == 1
|
108
|
+
return @vals[0]
|
109
|
+
elsif self.full?
|
110
|
+
return @vals
|
111
|
+
else
|
112
|
+
return nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# True if this option expects an argument. Opposite of #flag?
|
118
|
+
def has_args?
|
119
|
+
return @args.length > 0
|
120
|
+
end
|
121
|
+
|
122
|
+
# Return basic usage information: the option's names and arguments
|
123
|
+
def header()
|
124
|
+
if self.flag?
|
125
|
+
args = ''
|
126
|
+
else
|
127
|
+
args = ' ' + @args.join(', ').upcase
|
128
|
+
end
|
129
|
+
keys = @keys.sort {|a,b| a.length <=> b.length }
|
130
|
+
keys = keys.map {|k| k.length > 1 ? "--#{k}" : "-#{k}" }
|
131
|
+
keys = keys.join(' ')
|
132
|
+
return keys + args
|
133
|
+
end
|
134
|
+
|
135
|
+
# Represent this option as a string
|
136
|
+
def to_s()
|
137
|
+
return "(#{self.header})"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Convenience method for interface declarations. Yields the CLI instance and
|
142
|
+
# then returns it after parsing. Equivalent to instantiating a CLI instance
|
143
|
+
# with #new, modifying it, and then calling #parse
|
144
|
+
#
|
145
|
+
# +args+ is an array of strings, which defaults to ARGV
|
146
|
+
def self.begin(args=ARGV, &block)
|
147
|
+
cli = self.new(args)
|
148
|
+
yield cli
|
149
|
+
cli.parse()
|
150
|
+
if cli[:help]
|
151
|
+
cli.print_help()
|
152
|
+
exit 0
|
153
|
+
else
|
154
|
+
return cli
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Initialize a CLI instance.
|
159
|
+
#
|
160
|
+
# +args+ must be an array of strings, like ARGV
|
161
|
+
def initialize(args)
|
162
|
+
@args = args
|
163
|
+
@output_args = []
|
164
|
+
@scriptargs = []
|
165
|
+
@refargs = []
|
166
|
+
@named_args = {}
|
167
|
+
@options = {}
|
168
|
+
@variadic = false
|
169
|
+
@variad = nil
|
170
|
+
@scriptname = nil
|
171
|
+
@desc = nil
|
172
|
+
|
173
|
+
self.opt :help, :h, desc: "Print usage information"
|
174
|
+
end
|
175
|
+
|
176
|
+
# Parse the command line
|
177
|
+
def parse()
|
178
|
+
taking_options = true
|
179
|
+
last_opt = nil
|
180
|
+
|
181
|
+
@args.each do |word|
|
182
|
+
dbg "Parsing '#{word}'"
|
183
|
+
if last_opt && last_opt.has_args? && !last_opt.full?
|
184
|
+
dbg "Got argument '#{word}' for '#{last_opt}'", 1
|
185
|
+
last_opt.push(word)
|
186
|
+
else
|
187
|
+
if word[/^-/] && taking_options
|
188
|
+
if word[/^-[^-]/]
|
189
|
+
dbg "Identified short option(s)", 1
|
190
|
+
shorts = word[/[^-]+$/].split('')
|
191
|
+
shorts.each_with_index do |short, i|
|
192
|
+
last_short = i == (shorts.length - 1)
|
193
|
+
opt = self.get_opt(short)
|
194
|
+
last_opt = opt
|
195
|
+
if opt.has_args? && shorts.length > 1 && !last_short
|
196
|
+
raise SyntaxError, "Error: compound option '#{word}' expected an argument"
|
197
|
+
elsif opt.flag?
|
198
|
+
opt.toggle()
|
199
|
+
dbg "Toggled flag '#{opt}'", 1
|
200
|
+
end
|
201
|
+
end
|
202
|
+
elsif word[/^--/]
|
203
|
+
dbg "Identified long option", 1
|
204
|
+
key = word[/[^-]+$/]
|
205
|
+
opt = self.get_opt(key)
|
206
|
+
last_opt = opt
|
207
|
+
if opt.flag?
|
208
|
+
opt.toggle()
|
209
|
+
dbg "Toggled #{opt}", 1
|
210
|
+
end
|
211
|
+
end
|
212
|
+
else
|
213
|
+
dbg "Parsed output arg", 1
|
214
|
+
taking_options = false
|
215
|
+
@output_args << word
|
216
|
+
key = @scriptargs.shift
|
217
|
+
if key
|
218
|
+
if key == @variad
|
219
|
+
@named_args[key] = []
|
220
|
+
@named_args[key] << word
|
221
|
+
else
|
222
|
+
@named_args[key] = word
|
223
|
+
end
|
224
|
+
elsif @variadic
|
225
|
+
@named_args[@variad] << word
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
# Define an Option
|
234
|
+
# [+keys+] A list of names for this option
|
235
|
+
# [+args+] A list of arguments the option expects
|
236
|
+
# [+desc+] A short description of the option, used to provide usage info
|
237
|
+
def opt(*keys, args: nil, desc: nil)
|
238
|
+
raise ArgumentError, "An option must have at least one name" if keys.empty?
|
239
|
+
keys.map!(&:to_sym)
|
240
|
+
args.map!(&:to_sym) if args
|
241
|
+
o = Option.new(keys, args, desc)
|
242
|
+
keys.each {|k| @options[k] = o }
|
243
|
+
end
|
244
|
+
|
245
|
+
# Return all command line arguments
|
246
|
+
def args()
|
247
|
+
return @output_args
|
248
|
+
end
|
249
|
+
|
250
|
+
# Return the value of the named argument +name+
|
251
|
+
def arg(name)
|
252
|
+
return @named_args[name.to_sym]
|
253
|
+
end
|
254
|
+
|
255
|
+
# Get an Option object for the given option +name+
|
256
|
+
def get_opt(name)
|
257
|
+
name = name.to_sym
|
258
|
+
if !@options.keys.member?(name)
|
259
|
+
raise NoSuchOptionError, "Error, no such option: '#{name}'"
|
260
|
+
end
|
261
|
+
return @options[name]
|
262
|
+
end
|
263
|
+
|
264
|
+
# Get the value of a given option by +name+
|
265
|
+
def [](name)
|
266
|
+
return self.get_opt(name).value
|
267
|
+
end
|
268
|
+
|
269
|
+
# Get the toggle count of a flag by +name+
|
270
|
+
def count(name)
|
271
|
+
return self.get_opt(name).count
|
272
|
+
end
|
273
|
+
|
274
|
+
# Specify general information about the program
|
275
|
+
# [+name+] Name of the program
|
276
|
+
# [+desc+] Short description of the program
|
277
|
+
# [+args+] A list of named arguments
|
278
|
+
def header(name: nil, desc: nil, args: [])
|
279
|
+
@scriptname = name
|
280
|
+
@desc = desc
|
281
|
+
@scriptargs = args.map(&:to_sym)
|
282
|
+
if @scriptargs.last == :*
|
283
|
+
if @scriptargs.length > 1
|
284
|
+
@variadic = true
|
285
|
+
@scriptargs.pop
|
286
|
+
@refargs = @scriptargs.clone
|
287
|
+
@variad = @scriptargs.last
|
288
|
+
else
|
289
|
+
@scriptargs = []
|
290
|
+
end
|
291
|
+
else
|
292
|
+
@refargs = @scriptargs.clone
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# Print usage information
|
297
|
+
def print_help()
|
298
|
+
tb = TextBuilder.new()
|
299
|
+
|
300
|
+
tb.push @scriptname || 'Usage:'
|
301
|
+
|
302
|
+
if @options.length > 0
|
303
|
+
tb.push '[OPTION'
|
304
|
+
tb.add '...' if @options.values.uniq.length > 1
|
305
|
+
tb.add ']'
|
306
|
+
end
|
307
|
+
|
308
|
+
if !@refargs.empty?
|
309
|
+
if @variadic
|
310
|
+
singles = @refargs[0..-2].map(&:upcase)
|
311
|
+
tb.push singles
|
312
|
+
v = @variad.upcase
|
313
|
+
tb.push "#{v}1 #{v}2..."
|
314
|
+
else
|
315
|
+
tb.push @refargs.map(&:upcase)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
if @desc
|
320
|
+
tb.next @desc
|
321
|
+
tb.wrap(indent: 4)
|
322
|
+
end
|
323
|
+
|
324
|
+
tb.skip 'OPTIONS:'
|
325
|
+
tb.skip
|
326
|
+
|
327
|
+
@options.values.uniq.each do |opt|
|
328
|
+
tb.indent 4
|
329
|
+
tb.push opt.header
|
330
|
+
if opt.desc
|
331
|
+
tb.next
|
332
|
+
tb.indent 8
|
333
|
+
tb.push opt.desc
|
334
|
+
end
|
335
|
+
tb.skip
|
336
|
+
end
|
337
|
+
|
338
|
+
puts tb.print
|
339
|
+
end
|
340
|
+
|
341
|
+
end
|
342
|
+
|
343
|
+
end
|
344
|
+
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ark-cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.3.pre
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Macquarie Sharpless
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ark-util
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.2'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.2.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.2'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.2.0
|
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.
|
37
|
+
email:
|
38
|
+
- macquarie.sharpless@gmail.com
|
39
|
+
executables: []
|
40
|
+
extensions: []
|
41
|
+
extra_rdoc_files: []
|
42
|
+
files:
|
43
|
+
- lib/ark/cli.rb
|
44
|
+
homepage: https://github.com/grimheart/ark-cli
|
45
|
+
licenses:
|
46
|
+
- GPL-3.0
|
47
|
+
metadata: {}
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.3.1
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 2.4.5.1
|
65
|
+
signing_key:
|
66
|
+
specification_version: 4
|
67
|
+
summary: Simple commandline interface
|
68
|
+
test_files: []
|