ark-cli 0.3.3.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/ark/cli.rb +344 -0
  3. metadata +68 -0
@@ -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
@@ -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: []