ark-cli 0.3.3.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|