shellopts 0.9.1
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/.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
|