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.
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: []