shellopts 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +28 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/README.md +352 -0
- data/Rakefile +6 -0
- data/TODO +97 -0
- data/bin/console +14 -0
- data/bin/mkdoc +10 -0
- data/bin/setup +8 -0
- data/lib/ext/array.rb +9 -0
- data/lib/shellopts/ast/command.rb +41 -0
- data/lib/shellopts/ast/node.rb +37 -0
- data/lib/shellopts/ast/option.rb +21 -0
- data/lib/shellopts/ast/program.rb +14 -0
- data/lib/shellopts/compiler.rb +130 -0
- data/lib/shellopts/grammar/command.rb +64 -0
- data/lib/shellopts/grammar/node.rb +25 -0
- data/lib/shellopts/grammar/option.rb +55 -0
- data/lib/shellopts/grammar/program.rb +65 -0
- data/lib/shellopts/parser.rb +106 -0
- data/lib/shellopts/version.rb +3 -0
- data/lib/shellopts.rb +195 -0
- data/shellopts.gemspec +40 -0
- metadata +144 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f97bd1dadffdc1a8e5eab5635bdb186ddb925bafc2fe60b478c84d3b9590fb63
|
4
|
+
data.tar.gz: 9909d8069a937593e7a583d6fd8db1989f7eb8e321d9c655e2d0f376caeffd24
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 161c50d8cb2dc9abaa91164c3898f57942a6d51fcaadcb83bfd52cb96115548772be5d244d7e620f71751b523fdd5db0ab8a8a8bc76624be40a94d0341ac53c3
|
7
|
+
data.tar.gz: abaa5c58dfc5aaa98a553b4cb582c738a4bcd3649e5681c034a32b550bb583d106e4b0240cb0c5225dc0c8f58b3a32af2bd314d5894209b39b72e26446c6f4e3
|
data/.gitignore
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
/.bundle/
|
2
|
+
/.yardoc
|
3
|
+
/_yardoc/
|
4
|
+
/doc/
|
5
|
+
/pkg/
|
6
|
+
/spec/reports/
|
7
|
+
/tmp/
|
8
|
+
|
9
|
+
# rspec failure tracking
|
10
|
+
.rspec_status
|
11
|
+
|
12
|
+
# simplecov
|
13
|
+
/coverage/
|
14
|
+
|
15
|
+
# Ignore Gemfile.lock. See https://stackoverflow.com/questions/4151495/should-gemfile-lock-be-included-in-gitignore
|
16
|
+
/Gemfile.lock
|
17
|
+
|
18
|
+
# Ignore vim files
|
19
|
+
.*.swp
|
20
|
+
|
21
|
+
# Ignore t.* files
|
22
|
+
t
|
23
|
+
t.*
|
24
|
+
tt
|
25
|
+
tt.*
|
26
|
+
s
|
27
|
+
s.*
|
28
|
+
|
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.5.1
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,352 @@
|
|
1
|
+
# Shellopts
|
2
|
+
|
3
|
+
`ShellOpts` is a simple command line parsing libray that covers most modern use
|
4
|
+
cases incl. sub-commands. Options and commands are specified using a
|
5
|
+
getopt(1)-like string that is interpreted by the library to process the command
|
6
|
+
line
|
7
|
+
|
8
|
+
## Usage
|
9
|
+
|
10
|
+
The following program accepts `-a` and `--all` that are aliases
|
11
|
+
for the same option, `--count` that may be given an integer argument but
|
12
|
+
defaults to 42, `--file` that has a mandatory argument, and `-v` and
|
13
|
+
`--verbose` that can be repeated to increase the verbosity level
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
require 'shellopts'
|
17
|
+
|
18
|
+
# Define options
|
19
|
+
USAGE = "a,all count=#? file= +v,verbose -- FILE..."
|
20
|
+
|
21
|
+
# Define default values
|
22
|
+
all = false
|
23
|
+
count = nil
|
24
|
+
file = nil
|
25
|
+
verbosity_level = 0
|
26
|
+
|
27
|
+
# Process command line and return remaining non-option arguments
|
28
|
+
args = ShellOpts.process(USAGE, ARGV) do |opt, arg|
|
29
|
+
case opt
|
30
|
+
when '-a', '--all'; all = true
|
31
|
+
when '--count'; count = arg || 42
|
32
|
+
when '--file'; file = arg # never nil
|
33
|
+
when '-v, '--verbose'; verbosity_level += 1
|
34
|
+
else
|
35
|
+
fail "Internal Error: Unmatched option: '#{opt}'"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Process remaining arguments
|
40
|
+
args.each { |arg| ... }
|
41
|
+
```
|
42
|
+
|
43
|
+
Note that the `else` clause catches legal but unhandled options; it is not an
|
44
|
+
user error. It typically happens because of a missing or misspelled option name
|
45
|
+
in the `when` clauses
|
46
|
+
|
47
|
+
If there is an error in the command line options, the program will exit with
|
48
|
+
status 1 and print an error message and a short usage description on standard
|
49
|
+
error
|
50
|
+
|
51
|
+
## Processing
|
52
|
+
|
53
|
+
`ShellOpts.process` compiles a usage definition string into a grammar and use that to
|
54
|
+
parse the command line. If given a block, the block is called with a name/value
|
55
|
+
pair for each option or command and return a list of the remaining non-option
|
56
|
+
arguments
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
args = ShellOpts.process(USAGE, ARGV) do |opt, arg|
|
60
|
+
case opt
|
61
|
+
when ...
|
62
|
+
end
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
This calls the block for each option in the same order as on the command line
|
67
|
+
and return the remaining non-option args. It also sets up the `ShellOpts.error` and
|
68
|
+
`ShellOpts.fail` methods. Please note that you need to call `ShellOpts.reset`
|
69
|
+
if you want to process another command line
|
70
|
+
|
71
|
+
If `ShellOpts.process` is called without a block it returns a
|
72
|
+
`ShellOpts::ShellOpts` object. It can be used to process more than one command
|
73
|
+
line at a time and to inspect the grammar and AST
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
shellopts = ShellOpts.process(USAGE, ARGV) # Returns a ShellOpts::ShellOpts object
|
77
|
+
shellopts.each { |opt, val| ... } # Access options
|
78
|
+
args = shellopts.args # Access remaining arguments
|
79
|
+
shellopts.error "Something went wrong" # Emit an error message and exit
|
80
|
+
```
|
81
|
+
|
82
|
+
## Usage string
|
83
|
+
|
84
|
+
A usage string, typically named `USAGE`, is a list of option and command
|
85
|
+
definitions separated by whitespace. It can span multiple lines. A double
|
86
|
+
dash (`--`) marks the end of the definition, anything after that is not
|
87
|
+
interpreted but copied verbatim in error messages
|
88
|
+
|
89
|
+
The general [syntax](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form) is
|
90
|
+
|
91
|
+
```EBNF
|
92
|
+
options { command options } [ "--" anything ]
|
93
|
+
```
|
94
|
+
|
95
|
+
## Options
|
96
|
+
|
97
|
+
An option is defined by a list of comma-separated names optionally prefixed by a
|
98
|
+
`+` and/or followed by a `=` and a set of flags. The syntax is
|
99
|
+
|
100
|
+
```EBNF
|
101
|
+
[ "+" ] name-list [ "=" [ "#" | "$" ] [ label ] [ "?" ] ]
|
102
|
+
```
|
103
|
+
|
104
|
+
#### Repeated options
|
105
|
+
|
106
|
+
Options are unique by default and the user will get an error if an option is
|
107
|
+
used more than once. You can tell the parser to allow several instances of the
|
108
|
+
same option by prefixing the option names with a `+`. A typical use case is to
|
109
|
+
let the user repeat a 'verbose' option to increase verbosity: `+v,verbose`
|
110
|
+
allows `-vvv` or `--verbose --verbose --verbose`. `ShellOpts::process` yields
|
111
|
+
an entry for each usage so should handle repeated options like this
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
verbosity_level = 0
|
115
|
+
|
116
|
+
args = ShellOpts.process(USAGE, ARGV) do |opt, arg|
|
117
|
+
case opt
|
118
|
+
when '-v', '--verbose'; verbosity_level += 1
|
119
|
+
# other options
|
120
|
+
end
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
#### Option names
|
125
|
+
|
126
|
+
Option names are a comma-separated list of names. Names can consist of one or more
|
127
|
+
ASCII letters (a-zA-Z), digits, underscores ('\_') and dashes ('-'). A name
|
128
|
+
can't start with a dash, though
|
129
|
+
|
130
|
+
Names that are one character long are considered 'short options' and are
|
131
|
+
prefixed with a single dash on the command line (eg. '-a'). Names with two or
|
132
|
+
more characters are 'long options' and are used with two dashes (eg. '--all').
|
133
|
+
Note that short and long names handles arguments differently
|
134
|
+
|
135
|
+
Examples:
|
136
|
+
```
|
137
|
+
a # -a
|
138
|
+
all # --all
|
139
|
+
a,all # -a or --all
|
140
|
+
r,R,recursive # -r, -R, or --recursive
|
141
|
+
```
|
142
|
+
|
143
|
+
#### Option argument
|
144
|
+
|
145
|
+
An option that takes an an argument is declared with a `=` after the name list.
|
146
|
+
By default the type of an option is a `String` but a integer argument can be
|
147
|
+
specified by the `#` flag and a float argument by the `$` flag.
|
148
|
+
|
149
|
+
You can label a option value that will be used in help texts and error
|
150
|
+
messages. A usage string like `file=FILE` will be displayed as `--file=FILE`
|
151
|
+
and `file=FILE?` like `--file[=FILE]`. If no label is given, `INT` will be used
|
152
|
+
for integer arguments, `FLOAT` for floating point, and else `ARG`
|
153
|
+
|
154
|
+
Arguments are mandatory by default but can be made optional by suffixing a `?`
|
155
|
+
|
156
|
+
## Commands
|
157
|
+
|
158
|
+
Sub-commands (like `git clone`) are defined by a name (or a dot-separated list
|
159
|
+
of names) followed by an exclamation mark. All options following a command are
|
160
|
+
local to that command. It is not possible to 'reset' this behaviour so global
|
161
|
+
options should always come before the first command. Nested commands are
|
162
|
+
specified using a dot-separated "path" to the nested sub-command
|
163
|
+
|
164
|
+
Examples
|
165
|
+
```
|
166
|
+
g,global clone! t,template=
|
167
|
+
g,global clone! t,template= clone.list! v,verbose
|
168
|
+
```
|
169
|
+
|
170
|
+
The last example could be called like `program -g clone list -v`. You may split
|
171
|
+
the usage string to improve readability:
|
172
|
+
|
173
|
+
```
|
174
|
+
g,global
|
175
|
+
clone! t,template=
|
176
|
+
clone.list! v,verbose
|
177
|
+
```
|
178
|
+
|
179
|
+
#### Command processing
|
180
|
+
|
181
|
+
Commands are treated like options but with a value that is an array of options (and
|
182
|
+
sub-commands) to the command:
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
USAGE = "a cmd! b c"
|
186
|
+
|
187
|
+
args = ShellOpts.process(USAGE, ARGV) { |opt,val|
|
188
|
+
case opt
|
189
|
+
when '-a'; # Handle -a
|
190
|
+
when 'cmd'
|
191
|
+
opt.each { |opt, val|
|
192
|
+
case opt
|
193
|
+
when '-b'; # Handle -b
|
194
|
+
when '-c'; # Handle -c
|
195
|
+
end
|
196
|
+
}
|
197
|
+
end
|
198
|
+
}
|
199
|
+
```
|
200
|
+
|
201
|
+
## Parsing
|
202
|
+
|
203
|
+
Parsing of the command line follows the UNIX traditions for short and long
|
204
|
+
options. Short options are one letter long and prefixed by a `-`. Short options
|
205
|
+
can be grouped so that `-abc` is the same as `-a -b -c`. Long options are
|
206
|
+
prefixed with a `--` and can't be grouped
|
207
|
+
|
208
|
+
Mandatory arguments to short options can be separated by a whitespace (`-f
|
209
|
+
/path/to/file`) but optional arguments needs to come immediately after the
|
210
|
+
option: `-f/path/to/file`. Long options also allow a space separator for
|
211
|
+
mandatory arguments but use `=` to separate the option from optional arguments:
|
212
|
+
`--file=/path/to/file`
|
213
|
+
|
214
|
+
Examples
|
215
|
+
```
|
216
|
+
f= # -farg or -f arg
|
217
|
+
f=? # -farg
|
218
|
+
|
219
|
+
file= # --file=arg or --file arg
|
220
|
+
file=? # --file=arg
|
221
|
+
```
|
222
|
+
|
223
|
+
#### Error handling
|
224
|
+
|
225
|
+
If the command line is invalid, it's a user error and the program exits with
|
226
|
+
status 1 and prints an error message on STDERR
|
227
|
+
|
228
|
+
If there is an error in the usage string, ShellOpts raises a
|
229
|
+
`ShellOpts::CompileError`. Note that this exception signals an error by the
|
230
|
+
application developer and shouldn't be catched. If there is an internal error
|
231
|
+
in the library, a ShellOpts::InternalError is raised and you should look for a
|
232
|
+
newer version of `ShellOpts` or file a bug-report
|
233
|
+
|
234
|
+
All ShellOpt exceptions derive from ShellOpt::Error
|
235
|
+
|
236
|
+
#### Error handling methods
|
237
|
+
|
238
|
+
ShellOpts provides two methods that can be used by the application to
|
239
|
+
generate error messages in the style of ShellOpts: `ShellOpts.error` and
|
240
|
+
`ShellOpts.fail`. Both write an error message on STDERR and terminates the
|
241
|
+
program with status 1.
|
242
|
+
|
243
|
+
`error` is intended to respond to user errors (like giving a file name that
|
244
|
+
doesn't exist) and prints a short usage summary to remind the user:
|
245
|
+
|
246
|
+
```
|
247
|
+
<PROGRAM>: <MESSAGE>
|
248
|
+
Usage: <PROGRAM> <USAGE>
|
249
|
+
```
|
250
|
+
The usage string is a prettyfied version of the usage definition given to
|
251
|
+
ShellOpts
|
252
|
+
|
253
|
+
`fail` is used to report that something is wrong with the assumptions about the
|
254
|
+
system (eg. disk full) and omits the usage summary
|
255
|
+
```
|
256
|
+
<PROGRAM>: <MESSAGE>
|
257
|
+
```
|
258
|
+
|
259
|
+
The methods are defined as instance methods on `ShellOpts::ShellOpts` and as
|
260
|
+
class methods on `ShellOpts`. The class methods stores program name and usage
|
261
|
+
string in global variables that are reset by `ShellOpts.reset`
|
262
|
+
|
263
|
+
## Example
|
264
|
+
|
265
|
+
The rm(1) command could be implemented like this
|
266
|
+
```ruby
|
267
|
+
|
268
|
+
require 'shellopts'
|
269
|
+
|
270
|
+
# Define options
|
271
|
+
USAGE = %{
|
272
|
+
f,force i I interactive=WHEN? r,R,recusive d,dir
|
273
|
+
one-file-system no-preserve-root preserve-root
|
274
|
+
v,verbose help version
|
275
|
+
}
|
276
|
+
|
277
|
+
# Define defaults
|
278
|
+
force = false
|
279
|
+
prompt = false
|
280
|
+
prompt_once = false
|
281
|
+
interactive = false
|
282
|
+
interactive_when = nil
|
283
|
+
recursive = false
|
284
|
+
remove_empty_dirs = false
|
285
|
+
one_file_system = false
|
286
|
+
preserve_root = true
|
287
|
+
verbose = false
|
288
|
+
|
289
|
+
# Process command line
|
290
|
+
args = ShellOpts.process(USAGE, ARGV) { |opt, val|
|
291
|
+
case opt
|
292
|
+
when '-f', '--force'; force = true
|
293
|
+
when '-i'; prompt = true
|
294
|
+
when '-I'; prompt_once = true
|
295
|
+
when '--interactive'; interactive = true; interactive_when = val
|
296
|
+
when '-r', '-R', '--recursive'; recursive = true
|
297
|
+
when '-d', '--dir'; remove_empty_dirs = true
|
298
|
+
when '--one-file-system'; one_file_system = true
|
299
|
+
when '--preserve-root'; preserve_root = true
|
300
|
+
when '--no-preserve-root'; preserve_root = false
|
301
|
+
when '--verbose'; verbose = true
|
302
|
+
when '--help'; print_help; exit
|
303
|
+
when '--version'; puts VERSION; exit
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# Remaining arguments are files or directories
|
308
|
+
files = args
|
309
|
+
```
|
310
|
+
|
311
|
+
|
312
|
+
## See also
|
313
|
+
|
314
|
+
* [Command Line Options: How To Parse In Bash Using “getopt”](http://www.bahmanm.com/blogs/command-line-options-how-to-parse-in-bash-using-getopt)
|
315
|
+
|
316
|
+
## Installation
|
317
|
+
|
318
|
+
To install in your gem repository:
|
319
|
+
|
320
|
+
```
|
321
|
+
$ gem install shellopts
|
322
|
+
```
|
323
|
+
|
324
|
+
To add it as a dependency for an executable add this line to your application's
|
325
|
+
`Gemfile`. Use exact version match as ShellOpts is still in development:
|
326
|
+
|
327
|
+
```ruby
|
328
|
+
gem 'shellopts', 'x.y.z'
|
329
|
+
```
|
330
|
+
|
331
|
+
If you're developing a library, you should add the dependency to the `*.gemfile` instead:
|
332
|
+
|
333
|
+
```ruby
|
334
|
+
spec.add_dependency 'shellopts', 'x.y.z'
|
335
|
+
```
|
336
|
+
|
337
|
+
## Development
|
338
|
+
|
339
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
340
|
+
`rake spec` to run the tests. You can also run `bin/console` for an interactive
|
341
|
+
prompt that will allow you to experiment.
|
342
|
+
|
343
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
344
|
+
release a new version, update the version number in `version.rb`, and then run
|
345
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
346
|
+
git commits and tags, and push the `.gem` file to
|
347
|
+
[rubygems.org](https://rubygems.org).
|
348
|
+
|
349
|
+
## Contributing
|
350
|
+
|
351
|
+
Bug reports and pull requests are welcome on GitHub at
|
352
|
+
https://github.com/[USERNAME]/shellopts.
|
data/Rakefile
ADDED
data/TODO
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
|
2
|
+
TODO
|
3
|
+
o Add a option flag for solitary options (--help)
|
4
|
+
o Make a #to_yaml
|
5
|
+
o Make an official dump method for debug
|
6
|
+
o Make a note that all options are processed at once and not as-you-go
|
7
|
+
o Test that arguments with spaces work
|
8
|
+
o Long version usage strings
|
9
|
+
o Doc: Example of processing of sub-commands and sub-sub-commands
|
10
|
+
|
11
|
+
+ More tests
|
12
|
+
+ More doc
|
13
|
+
+ Implement value-name-before-flags rule
|
14
|
+
+ Kill option array values
|
15
|
+
+ Kill key forms
|
16
|
+
+ Rename Option#opt to Option#name
|
17
|
+
+ Have all Ast objects to be on [key, name, value] form
|
18
|
+
+ Change #=>i, $=>f and introduce b (boolean)
|
19
|
+
+ Unshift program name to usage definition string before compiling
|
20
|
+
+ Rename to UsageCompiler and ArgvParser
|
21
|
+
+ Make usage-string handle commands
|
22
|
+
+ Change !cmd to cmd!
|
23
|
+
+ Clean-up terminology: Option-name is used for names with and without the prefixed dashes
|
24
|
+
+ Rename Option#has_argument? and #optional? to something else
|
25
|
+
+ Fix location reporting of compiler errors
|
26
|
+
+ Allow '--' in usage so that everything after can be used as USAGE in error messages
|
27
|
+
+ Handle pretty-printing of usage string in handling of ParserError
|
28
|
+
+ Compiler.new.compile(usage), Parser.new(compiled_program).parse(argv)
|
29
|
+
+ Check for duplicate option in the parser
|
30
|
+
+ Handle CompilerError
|
31
|
+
+ Use nil value as the name of the top 'command'
|
32
|
+
+ Refactor compilation to avoid having the Command objects throw CompilerErrors
|
33
|
+
+ Change to 'parser.parse' / 'parser.parse3'
|
34
|
+
+ Use first long option as symbolic key
|
35
|
+
+ Use full option names everywhere (eg. '--all' instead of 'all')
|
36
|
+
|
37
|
+
- Revert change from '#' -> 'i'
|
38
|
+
- Guard against reserved 'object_id' name in OpenStruct
|
39
|
+
- Default value ('=' -> ':')
|
40
|
+
Default values are better handled in the calling program
|
41
|
+
|
42
|
+
? More specialized exceptions: "MissingArgumentError" etc.
|
43
|
+
|
44
|
+
LATER
|
45
|
+
o Allow '-a' and '--aa' in usage
|
46
|
+
o Allow single-line comments
|
47
|
+
o Allow multi-line comments
|
48
|
+
o Regex as option value spec
|
49
|
+
o "FILE", "DIR", "NEWFILE", "NEWDIR" as keyword in option value spec
|
50
|
+
RFILE, RDIR
|
51
|
+
WFILE, WDIR
|
52
|
+
EFILE, EDIR
|
53
|
+
o Octal and hexadecimal integers
|
54
|
+
o Escape of separator in lists
|
55
|
+
o Handle output of subcommand usage like "cmd1 cmd1.cmd2 cmd2"
|
56
|
+
o Command-specific arguments: clone! o,opt ++ ARG1 ARG2...
|
57
|
+
|
58
|
+
ON TO_H BRANCH
|
59
|
+
ShellOpts.process(usage, argv) { |opt,val| ... } => args
|
60
|
+
ShellOpts.process(usage, argv) { |key,opt,val| ... } => args
|
61
|
+
|
62
|
+
opts = ShellOpts.new(usage, argv, defaults = {})
|
63
|
+
opts = ShellOpts.new(usage, argv, defaults = OpenStruct.new)
|
64
|
+
|
65
|
+
opts.args
|
66
|
+
opts.to_a
|
67
|
+
opts.to_h
|
68
|
+
opts.to_openstruct
|
69
|
+
|
70
|
+
opts.each { |opt,val| ... }
|
71
|
+
opts.each { |key,opt,val| ... }
|
72
|
+
|
73
|
+
LONG FORMAT
|
74
|
+
|
75
|
+
PROGRAM = File.basename(ARGV.first)
|
76
|
+
USAGE = "-a -f FILE -lvh FILE..."
|
77
|
+
DESCR = %(
|
78
|
+
Short description
|
79
|
+
|
80
|
+
Longer description
|
81
|
+
)
|
82
|
+
OPTIONS = %(
|
83
|
+
-a,--all
|
84
|
+
Process all files
|
85
|
+
|
86
|
+
-f, --file=FILE
|
87
|
+
Process file
|
88
|
+
|
89
|
+
!command
|
90
|
+
This is a command
|
91
|
+
|
92
|
+
--this-is-a-command-option
|
93
|
+
Options for commands are nested
|
94
|
+
|
95
|
+
...
|
96
|
+
)
|
97
|
+
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "shellopts"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/mkdoc
ADDED
data/bin/setup
ADDED
data/lib/ext/array.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module ShellOpts
|
2
|
+
module Ast
|
3
|
+
class Command < Node
|
4
|
+
# Array of options (Ast::Option). Initially empty but filled out by the
|
5
|
+
# parser
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
# Optional sub-command (Ast::Command). Initially nil but assigned by the
|
9
|
+
# parser
|
10
|
+
attr_accessor :command
|
11
|
+
|
12
|
+
def initialize(grammar, name)
|
13
|
+
super(grammar, name)
|
14
|
+
@options = []
|
15
|
+
@command = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# Array of option or command tuples
|
19
|
+
def values
|
20
|
+
(options + (Array(command || []))).map { |node| node.to_tuple }
|
21
|
+
end
|
22
|
+
|
23
|
+
# :nocov:
|
24
|
+
def dump(&block)
|
25
|
+
super {
|
26
|
+
yield if block_given?
|
27
|
+
puts "options:"
|
28
|
+
indent { options.each { |opt| opt.dump } }
|
29
|
+
print "command:"
|
30
|
+
if command
|
31
|
+
puts
|
32
|
+
indent { command.dump }
|
33
|
+
else
|
34
|
+
puts "nil"
|
35
|
+
end
|
36
|
+
}
|
37
|
+
end
|
38
|
+
# :nocov:
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ShellOpts
|
2
|
+
module Ast
|
3
|
+
class Node
|
4
|
+
# The associated Grammar::Node object
|
5
|
+
attr_reader :grammar
|
6
|
+
|
7
|
+
# Key of node. Shorthand for grammar.key
|
8
|
+
def key() @grammar.key end
|
9
|
+
|
10
|
+
# Name of node (either program, command, or option name)
|
11
|
+
attr_reader :name
|
12
|
+
|
13
|
+
# Initialize an +Ast::Node+ object. +grammar+ is the corresponding
|
14
|
+
# grammar object (+Grammar::Node+) and +name+ is the name of the option
|
15
|
+
# or sub-command
|
16
|
+
def initialize(grammar, name)
|
17
|
+
@grammar, @name = grammar, name
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return a name/value pair
|
21
|
+
def to_tuple
|
22
|
+
[name, values]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Return either a value (option value), an array of values (command), or
|
26
|
+
# nil (option without a value). Should be defined in sub-classes
|
27
|
+
def values() raise end
|
28
|
+
|
29
|
+
# :nocov:
|
30
|
+
def dump(&block)
|
31
|
+
puts key.inspect
|
32
|
+
indent { yield } if block_given?
|
33
|
+
end
|
34
|
+
# :nocov:
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ShellOpts
|
2
|
+
module Ast
|
3
|
+
class Option < Node
|
4
|
+
# Optional value. Can be a String, Integer, or Float
|
5
|
+
attr_reader :value
|
6
|
+
|
7
|
+
def initialize(grammar, name, value)
|
8
|
+
super(grammar, name)
|
9
|
+
@value = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def values() value end
|
13
|
+
|
14
|
+
# :nocov:
|
15
|
+
def dump
|
16
|
+
super { puts "values: #{values.inspect}" }
|
17
|
+
end
|
18
|
+
# :nocov:
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ShellOpts
|
2
|
+
module Ast
|
3
|
+
class Program < Command
|
4
|
+
# Command line arguments. Initially nil but assigned by the parser. This array
|
5
|
+
# is the same as the argument array returned by Ast.parse
|
6
|
+
attr_accessor :arguments
|
7
|
+
|
8
|
+
def initialize(grammar)
|
9
|
+
super(grammar, grammar.name)
|
10
|
+
@arguments = nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|