typedargs 0.2.0 → 0.3.0
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 +4 -4
- data/README.md +79 -270
- data/mrblib/alias.rb +29 -10
- data/mrblib/impl.rb +36 -52
- data/mrblib/lexer.rb +2 -2
- data/mrblib/typedargs.rb +5 -0
- data/mrblib/utf8.rb +0 -6
- data/mrblib/value_parser.rb +1 -1
- data/mrblib/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9b1ff45511cabd1ddd347f7bf662753fefb1c1aa53a9dbb06ea258a94617c24b
|
|
4
|
+
data.tar.gz: 3b6a22c83f792a6a3c87416479c529aa09a48d21523c84140f32960a2bc01964
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2747b98351471ec61b2669ff3f7165ca8c96b4d36c31ab964be8e2781bc08584a9aca7824bee4faf301dbc74c891d5d58a124dfa063ff71e5459bcdf5d0f615b
|
|
7
|
+
data.tar.gz: b4b65d4e167065a4948fd0f3f577f57371c71012a24d2c949f38a5bd455e8d765e112b8723f97908653bfa3ce1fa9cafae55198a48a21ef720748c4b2f7f3d22
|
data/README.md
CHANGED
|
@@ -1,324 +1,133 @@
|
|
|
1
|
-
# TypedArgs
|
|
2
|
-
*A tiny operator‑typed CLI language for structured data.*
|
|
1
|
+
# TypedArgs
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
It is a **mini‑language** for expressing structured data on the command line — scalars, arrays, hashes, and arrays of hashes — using a small set of explicit, shell‑safe operators.
|
|
6
|
-
|
|
7
|
-
It runs anywhere MRuby runs: embedded systems, containers, CI runners, Windows, macOS, Linux, BusyBox, Alpine, and fully sandboxed MRuby VMs. No dependencies. No shell tricks. No heuristics. No guessing.
|
|
8
|
-
|
|
9
|
-
TypedArgs behaves the same everywhere.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Why TypedArgs Exists
|
|
14
|
-
|
|
15
|
-
Most CLI parsers try to *guess* what the user meant. TypedArgs refuses.
|
|
16
|
-
Shells are inconsistent. Quoting rules differ. JSON on the command line is painful.
|
|
17
|
-
Suffix‑typed flags collide with shells. YAML is too heavy.
|
|
18
|
-
Users deserve a grammar that is:
|
|
19
|
-
|
|
20
|
-
- **Explicit** — the operator defines the shape
|
|
21
|
-
- **Portable** — works in every shell without quoting
|
|
22
|
-
- **Deterministic** — same input, same output, always
|
|
23
|
-
- **Minimal** — four operators, one mental model
|
|
24
|
-
- **Structured** — arrays and hashes are first‑class citizens
|
|
25
|
-
|
|
26
|
-
TypedArgs is the answer: a tiny algebra of flags.
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
# The Operator Model
|
|
31
|
-
|
|
32
|
-
TypedArgs is built on four operators.
|
|
33
|
-
They define the shape of the value — nothing else is needed.
|
|
34
|
-
|
|
35
|
-
| Operator | Meaning |
|
|
36
|
-
|----------|---------|
|
|
37
|
-
| `=` | scalar assignment |
|
|
38
|
-
| `+=` | append scalar to array |
|
|
39
|
-
| `:fields:=` | assign hash tuple |
|
|
40
|
-
| `+:fields:=` | append hash tuple to array |
|
|
41
|
-
|
|
42
|
-
This is the entire language.
|
|
43
|
-
|
|
44
|
-
No suffixes.
|
|
45
|
-
No brackets.
|
|
46
|
-
No type inference.
|
|
47
|
-
No shell‑sensitive characters.
|
|
48
|
-
Just operators.
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
# Installation
|
|
53
|
-
|
|
54
|
-
TypedArgs is pure Ruby and MRuby‑core‑friendly.
|
|
55
|
-
Drop the Ruby files into your MRuby build or load them into your VM.
|
|
56
|
-
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
# Basic Usage
|
|
60
|
-
|
|
61
|
-
```ruby
|
|
62
|
-
args = TypedArgs.opts("--mode=fast", "--debug=true")
|
|
63
|
-
|
|
64
|
-
args["mode"] # => "fast"
|
|
65
|
-
args["debug"] # => true
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
If no arguments are passed, `TypedArgs.opts` defaults to `ARGV`.
|
|
69
|
-
You must supply that array yourself in MRuby; see `tools/typedargs_test/test.c` for an example.
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
# Grammar Overview
|
|
74
|
-
|
|
75
|
-
TypedArgs defines a small, explicit grammar for keys and values.
|
|
76
|
-
Everything is driven by operators.
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
## Scalars
|
|
3
|
+
Structured CLI arguments for mruby and CRuby. Lets you pass arrays, hashes, and arrays of records on the command line without quoting JSON or writing a config file.
|
|
81
4
|
|
|
82
5
|
```
|
|
83
6
|
--mode=fast
|
|
84
|
-
--
|
|
85
|
-
--debug=true
|
|
86
|
-
--foo=nil
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Values may be:
|
|
90
|
-
|
|
91
|
-
- strings
|
|
92
|
-
- integers
|
|
93
|
-
- floats
|
|
94
|
-
- booleans (`true` / `false`)
|
|
95
|
-
- `nil`
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## Dotted Keys
|
|
100
|
-
|
|
101
|
-
```
|
|
102
|
-
--db.user=root
|
|
103
|
-
--cache.redis.host=localhost
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Keys may contain:
|
|
107
|
-
|
|
108
|
-
- letters
|
|
109
|
-
- digits
|
|
110
|
-
- underscore
|
|
111
|
-
- dash
|
|
112
|
-
- dot
|
|
113
|
-
|
|
114
|
-
Keys may **not** start with a digit or dash.
|
|
115
|
-
Dotted keys are treated as **flat strings**, not nested hashes.
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## Arrays (`+=`)
|
|
120
|
-
|
|
121
|
-
```
|
|
122
|
-
--item+=a
|
|
123
|
-
--item+=b
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
Result:
|
|
127
|
-
|
|
128
|
-
```ruby
|
|
129
|
-
{ "item" => ["a", "b"] }
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
`+=` always appends.
|
|
133
|
-
If the key didn’t exist, an array is created.
|
|
134
|
-
|
|
135
|
-
---
|
|
136
|
-
|
|
137
|
-
## Hash Tuples (`:fields:=`)
|
|
138
|
-
|
|
139
|
-
```
|
|
7
|
+
--item+=apple --item+=banana
|
|
140
8
|
--range:min,max:=5,10
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
Result:
|
|
144
|
-
|
|
145
|
-
```ruby
|
|
146
|
-
{ "range" => { "min" => 5, "max" => 10 } }
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
Arity is enforced:
|
|
150
|
-
If you declare two fields, you must supply two values.
|
|
151
|
-
|
|
152
|
-
---
|
|
153
|
-
|
|
154
|
-
## Arrays of Hashes (`+:fields:=`)
|
|
155
|
-
|
|
156
|
-
```
|
|
157
9
|
--servers+:name,port:=alpha,80
|
|
158
10
|
--servers+:name,port:=beta,443
|
|
159
11
|
```
|
|
160
12
|
|
|
161
|
-
|
|
13
|
+
becomes
|
|
162
14
|
|
|
163
15
|
```ruby
|
|
164
16
|
{
|
|
17
|
+
"mode" => "fast",
|
|
18
|
+
"item" => ["apple", "banana"],
|
|
19
|
+
"range" => {"min" => 5, "max" => 10},
|
|
165
20
|
"servers" => [
|
|
166
|
-
{
|
|
167
|
-
{
|
|
21
|
+
{"name" => "alpha", "port" => 80},
|
|
22
|
+
{"name" => "beta", "port" => 443}
|
|
168
23
|
]
|
|
169
24
|
}
|
|
170
25
|
```
|
|
171
26
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
---
|
|
27
|
+
It's a small, dependency-free parser that runs anywhere mruby runs (embedded, BusyBox, Alpine, sandboxed VMs) and on CRuby ≥ 2.5.
|
|
175
28
|
|
|
176
|
-
##
|
|
29
|
+
## When you'd want this
|
|
177
30
|
|
|
178
|
-
|
|
179
|
-
TypedArgs.alias("-v", "--verbose")
|
|
180
|
-
TypedArgs.opts("-v")
|
|
181
|
-
# => { "verbose" => true }
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
Aliases expand before parsing.
|
|
185
|
-
They can target dotted keys and any operator form.
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
# Error Reporting
|
|
190
|
-
|
|
191
|
-
TypedArgs provides compiler‑style diagnostics with caret indicators.
|
|
192
|
-
|
|
193
|
-
Example:
|
|
194
|
-
|
|
195
|
-
```
|
|
196
|
-
--range:min,max:=5
|
|
197
|
-
^
|
|
198
|
-
Syntax error: Arity mismatch: expected 2, got 1
|
|
199
|
-
```
|
|
31
|
+
You have a CLI that takes input that's genuinely structured — a list of servers, a set of feature toggles, a hash of coordinates — and the alternatives don't fit:
|
|
200
32
|
|
|
201
|
-
|
|
33
|
+
- JSON on argv means escaping `'{"servers":[{"name":"alpha"}]}'` past the shell.
|
|
34
|
+
- Repeating a flag with an ad-hoc convention (`--server alpha:80 --server beta:443`) means inventing a mini-syntax per tool.
|
|
35
|
+
- A config file is heavy when you only have three values to pass.
|
|
202
36
|
|
|
203
|
-
|
|
204
|
-
- a caret pointing to the exact byte
|
|
205
|
-
- a clear error class
|
|
37
|
+
If your CLI takes scalars and the occasional list, a normal option parser is probably a better fit. TypedArgs earns its place when structure is the point.
|
|
206
38
|
|
|
207
|
-
|
|
39
|
+
## The four operators
|
|
208
40
|
|
|
209
|
-
|
|
41
|
+
| Form | Meaning |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `--key=value` | scalar |
|
|
44
|
+
| `--key+=value` | append to array |
|
|
45
|
+
| `--key:f1,f2:=v1,v2` | hash with named fields |
|
|
46
|
+
| `--key+:f1,f2:=v1,v2` | append a hash to an array |
|
|
210
47
|
|
|
211
|
-
|
|
48
|
+
Scalar values are auto-typed: integers, floats, `true`, `false`, `nil`, and otherwise strings. Tuple arity is enforced — declaring two fields means you must supply two values.
|
|
212
49
|
|
|
213
|
-
|
|
214
|
-
Later flags overwrite earlier ones unless using accumulation operators.
|
|
50
|
+
## Usage
|
|
215
51
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
## Scalar Assignment (`=`)
|
|
219
|
-
|
|
220
|
-
| Syntax | Meaning |
|
|
221
|
-
|--------|---------|
|
|
222
|
-
| `--key=value` | assign scalar |
|
|
223
|
-
|
|
224
|
-
Overwrites previous value.
|
|
225
|
-
|
|
226
|
-
---
|
|
227
|
-
|
|
228
|
-
## Scalar Accumulation (`+=`)
|
|
52
|
+
```ruby
|
|
53
|
+
require "typedargs"
|
|
229
54
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
55
|
+
args = TypedArgs.opts("--mode=fast", "--debug=true")
|
|
56
|
+
args["mode"] # => "fast"
|
|
57
|
+
args["debug"] # => true
|
|
58
|
+
```
|
|
233
59
|
|
|
234
|
-
|
|
235
|
-
Overwrites previous non‑array values.
|
|
60
|
+
Calling `TypedArgs.opts` with no arguments reads from `ARGV`. In mruby you provide `ARGV` yourself; see `tools/typedargs_test/test.c`.
|
|
236
61
|
|
|
237
|
-
|
|
62
|
+
## Keys
|
|
238
63
|
|
|
239
|
-
|
|
64
|
+
Keys are letters, digits, underscore, dash, and dot. They can't start with a digit or dash. Dotted keys (`--db.host=localhost`) are stored as **flat strings**, not nested hashes — `args["db.host"]`, not `args["db"]["host"]`. Auto-nesting opens ambiguities the grammar avoids.
|
|
240
65
|
|
|
241
|
-
|
|
242
|
-
|--------|---------|
|
|
243
|
-
| `--key:field1,field2:=v1,v2` | assign hash |
|
|
66
|
+
## Short-flag aliases
|
|
244
67
|
|
|
245
|
-
|
|
68
|
+
```ruby
|
|
69
|
+
TypedArgs.alias("-v", "--verbose")
|
|
70
|
+
TypedArgs.opts("-v") # => {"verbose" => true}
|
|
71
|
+
TypedArgs.opts("-vfoo") # => {"verbose" => "foo"}
|
|
72
|
+
```
|
|
246
73
|
|
|
247
|
-
|
|
74
|
+
The alias target can be any long-flag form, including operators:
|
|
248
75
|
|
|
249
|
-
|
|
76
|
+
```ruby
|
|
77
|
+
TypedArgs.alias("-r", "--range:min,max:=")
|
|
78
|
+
TypedArgs.opts("-r5,10") # => {"range" => {"min" => 5, "max" => 10}}
|
|
250
79
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
80
|
+
TypedArgs.alias("-S", "--servers+:name,port:=")
|
|
81
|
+
TypedArgs.opts("-Salpha,80", "-Sbeta,443")
|
|
82
|
+
# => {"servers" => [{"name"=>"alpha","port"=>80}, {"name"=>"beta","port"=>443}]}
|
|
83
|
+
```
|
|
254
84
|
|
|
255
|
-
|
|
85
|
+
Aliases are textual rewrites performed before parsing. `TypedArgs.reset_aliases!` clears the alias map (useful in tests).
|
|
256
86
|
|
|
257
|
-
|
|
87
|
+
## Override rules
|
|
258
88
|
|
|
259
|
-
|
|
89
|
+
Flags apply in argv order. Later flags overwrite earlier ones unless they're accumulating.
|
|
260
90
|
|
|
261
91
|
| Sequence | Result |
|
|
262
|
-
|
|
263
|
-
| `--foo=1`
|
|
264
|
-
| `--foo+=1`
|
|
265
|
-
| `--foo:
|
|
266
|
-
| `--foo+:
|
|
267
|
-
| `--foo=1
|
|
268
|
-
|
|
269
|
-
TypedArgs is explicit:
|
|
270
|
-
the operator determines the shape.
|
|
271
|
-
|
|
272
|
-
---
|
|
92
|
+
|---|---|
|
|
93
|
+
| `--foo=1` then `--foo+=2` | `[2]` |
|
|
94
|
+
| `--foo+=1` then `--foo+=2` | `[1, 2]` |
|
|
95
|
+
| `--foo:a,b:=1,2` then `--foo:a,b:=3,4` | `{"a"=>3, "b"=>4}` |
|
|
96
|
+
| `--foo+:a,b:=1,2` then `--foo+:a,b:=3,4` | `[{"a"=>1,"b"=>2}, {"a"=>3,"b"=>4}]` |
|
|
97
|
+
| `--foo=1`, `--foo+=2`, `--foo:n:=x`, `--foo=bar` | `"bar"` |
|
|
273
98
|
|
|
274
|
-
|
|
99
|
+
The operator on each flag determines the shape of the value at that key.
|
|
275
100
|
|
|
276
|
-
|
|
101
|
+
## Errors
|
|
277
102
|
|
|
278
|
-
|
|
279
|
-
- arrays
|
|
280
|
-
- hashes
|
|
281
|
-
- arrays of hashes
|
|
282
|
-
- dotted keys
|
|
283
|
-
- alias expansion
|
|
284
|
-
- invalid characters
|
|
285
|
-
- invalid suffix placement
|
|
286
|
-
- invalid field lists
|
|
287
|
-
- tuple arity
|
|
288
|
-
- invalid numbers
|
|
289
|
-
- unterminated strings
|
|
290
|
-
- invalid short flags
|
|
291
|
-
- invalid dotted paths
|
|
292
|
-
- empty keys
|
|
293
|
-
- alias expansion to invalid keys
|
|
103
|
+
Invalid input raises a `TypedArgs::SyntaxError` subclass with a caret pointing at the offending byte:
|
|
294
104
|
|
|
295
|
-
|
|
296
|
-
|
|
105
|
+
```
|
|
106
|
+
--range:min,max:=5
|
|
107
|
+
^
|
|
108
|
+
Syntax error: Arity mismatch: expected 2, got 1
|
|
109
|
+
```
|
|
297
110
|
|
|
298
|
-
|
|
111
|
+
The exception classes are `InvalidCharacterError`, `InvalidKeyStartError`, `UnterminatedStringError`, `ArityMismatchError`, `UnexpectedTokenError`, `InvalidSuffixPositionError`, `InvalidFieldListError`, and `InvalidNumberError`.
|
|
299
112
|
|
|
300
|
-
|
|
113
|
+
## Caveats
|
|
301
114
|
|
|
302
|
-
TypedArgs is
|
|
115
|
+
The shell still owns word-splitting and metacharacter handling — `--secret=p$ssw0rd` will need quoting in bash regardless of what TypedArgs does after argv arrives. Strictness around scalars (e.g. `12.34.56` raises rather than parsing as a string) is intentional but stricter than most option parsers.
|
|
303
116
|
|
|
304
|
-
|
|
305
|
-
- **Portable** — no shell dependencies
|
|
306
|
-
- **Minimal** — four operators, one grammar
|
|
307
|
-
- **Deterministic** — predictable and stable
|
|
308
|
-
- **Structured** — arrays and hashes are first‑class
|
|
117
|
+
## Installation
|
|
309
118
|
|
|
310
|
-
|
|
119
|
+
For CRuby:
|
|
311
120
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
- Bash‑only features
|
|
121
|
+
```
|
|
122
|
+
gem install typedargs
|
|
123
|
+
```
|
|
316
124
|
|
|
317
|
-
|
|
318
|
-
TypedArgs does everything else.
|
|
125
|
+
For mruby, add to your `build_config.rb`:
|
|
319
126
|
|
|
320
|
-
|
|
127
|
+
```ruby
|
|
128
|
+
conf.gem mgem: 'typedargs'
|
|
129
|
+
```
|
|
321
130
|
|
|
322
|
-
|
|
131
|
+
## License
|
|
323
132
|
|
|
324
|
-
Apache
|
|
133
|
+
Apache-2.0.
|
data/mrblib/alias.rb
CHANGED
|
@@ -7,19 +7,38 @@ module TypedArgs
|
|
|
7
7
|
@alias_map[short] = long
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def
|
|
11
|
-
|
|
12
|
-
mapped ? strip_leading_dashes(mapped) : strip_leading_dashes(raw)
|
|
10
|
+
def reset_aliases!
|
|
11
|
+
@alias_map = {}
|
|
13
12
|
end
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
# If `arg` is a short flag whose first 2 characters are registered as an
|
|
15
|
+
# alias, return the textually-expanded long-flag form. Otherwise return
|
|
16
|
+
# nil.
|
|
17
|
+
#
|
|
18
|
+
# Expansion rule:
|
|
19
|
+
# "-X" alone → alias_target
|
|
20
|
+
# "-X<payload>" → alias_target + payload (if target ends in "=")
|
|
21
|
+
# alias_target + "=" + payload (otherwise)
|
|
22
|
+
#
|
|
23
|
+
# This makes the README's claim literally true: aliases are textual
|
|
24
|
+
# substitutions performed before parsing, and they can target dotted
|
|
25
|
+
# keys and any operator form (`=`, `+=`, `:fields:=`, `+:fields:=`).
|
|
26
|
+
def expand_short_alias(arg)
|
|
27
|
+
return nil if arg.nil? || arg.length < 2
|
|
28
|
+
short_key = arg[0, 2]
|
|
29
|
+
target = @alias_map[short_key]
|
|
30
|
+
return nil if target.nil?
|
|
31
|
+
|
|
32
|
+
if arg.length > 2
|
|
33
|
+
payload = arg[2, arg.length - 2]
|
|
34
|
+
if target.length > 0 && target[target.length - 1, 1] == "="
|
|
35
|
+
target + payload
|
|
36
|
+
else
|
|
37
|
+
target + "=" + payload
|
|
38
|
+
end
|
|
39
|
+
else
|
|
40
|
+
target
|
|
21
41
|
end
|
|
22
|
-
str[i, n - i]
|
|
23
42
|
end
|
|
24
43
|
end
|
|
25
44
|
end
|
data/mrblib/impl.rb
CHANGED
|
@@ -12,7 +12,21 @@ module TypedArgs
|
|
|
12
12
|
if long_flag?(a)
|
|
13
13
|
parse_long(out, a)
|
|
14
14
|
elsif short_flag?(a)
|
|
15
|
-
|
|
15
|
+
# Alias expansion happens BEFORE parsing, per the README.
|
|
16
|
+
# If a 2-char short prefix is aliased, we textually rewrite
|
|
17
|
+
# the entire arg into a long-flag form and dispatch there.
|
|
18
|
+
expanded = Internal.expand_short_alias(a)
|
|
19
|
+
if expanded
|
|
20
|
+
if long_flag?(expanded)
|
|
21
|
+
parse_long(out, expanded)
|
|
22
|
+
else
|
|
23
|
+
# Alias target wasn't a long-flag form; treat the original
|
|
24
|
+
# arg as a short flag (best-effort fallback).
|
|
25
|
+
parse_short(out, a)
|
|
26
|
+
end
|
|
27
|
+
else
|
|
28
|
+
parse_short(out, a)
|
|
29
|
+
end
|
|
16
30
|
end
|
|
17
31
|
end
|
|
18
32
|
i += 1
|
|
@@ -21,6 +35,7 @@ module TypedArgs
|
|
|
21
35
|
end
|
|
22
36
|
|
|
23
37
|
private
|
|
38
|
+
|
|
24
39
|
def long_flag?(arg)
|
|
25
40
|
arg.length >= 2 &&
|
|
26
41
|
arg[0,1] == "-" &&
|
|
@@ -33,35 +48,28 @@ module TypedArgs
|
|
|
33
48
|
!(arg.length >= 2 && arg[1,1] == "-")
|
|
34
49
|
end
|
|
35
50
|
|
|
36
|
-
|
|
37
|
-
# In impl.rb, replace parse_long or parse_long-like logic with:
|
|
38
|
-
|
|
39
51
|
def parse_long(out, arg)
|
|
40
|
-
|
|
41
|
-
body = arg[2, arg.length - 2] # "--" removed, character-based
|
|
52
|
+
body = arg[2, arg.length - 2] # strip "--"
|
|
42
53
|
|
|
43
|
-
# find '=' in character mode
|
|
44
54
|
eq_idx = body.index("=")
|
|
45
|
-
|
|
46
55
|
if eq_idx
|
|
47
|
-
key_str = body[0, eq_idx]
|
|
48
|
-
val_str = body[(eq_idx + 1), body.length - (eq_idx + 1)]
|
|
56
|
+
key_str = body[0, eq_idx]
|
|
57
|
+
val_str = body[(eq_idx + 1), body.length - (eq_idx + 1)]
|
|
49
58
|
else
|
|
50
59
|
key_str = body
|
|
51
60
|
val_str = nil
|
|
52
61
|
end
|
|
53
62
|
|
|
54
|
-
# parse key in character mode
|
|
55
63
|
key_lex = Lexer.new(key_str, 0, key_str.length, true)
|
|
56
64
|
key_ast = KeyParser.new(key_lex).parse
|
|
57
65
|
|
|
58
|
-
|
|
66
|
+
# Long-flag keys are taken at face value; the alias map applies
|
|
67
|
+
# only to short flags (per README).
|
|
68
|
+
name = key_ast[:name]
|
|
59
69
|
|
|
60
|
-
# script check (character indices)
|
|
61
70
|
ScriptCheck.validate_key(key_str)
|
|
62
71
|
|
|
63
72
|
if val_str
|
|
64
|
-
# parse value in character mode (value lexer now also character-based)
|
|
65
73
|
val_lex = Lexer.new(val_str, 0, val_str.length, false)
|
|
66
74
|
vp = ValueParser.new(val_lex)
|
|
67
75
|
|
|
@@ -79,55 +87,34 @@ module TypedArgs
|
|
|
79
87
|
assign(out, name, key_ast, value)
|
|
80
88
|
end
|
|
81
89
|
|
|
82
|
-
|
|
90
|
+
# parse_short handles short flags that DO NOT have an alias.
|
|
91
|
+
# Aliased short flags are rewritten and dispatched to parse_long.
|
|
83
92
|
def parse_short(out, arg)
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
# Strip the leading dash; everything after it is the bare name
|
|
94
|
+
# candidate (until any attached value).
|
|
95
|
+
name = arg[1, arg.length - 1]
|
|
86
96
|
|
|
87
97
|
if name.nil? || name.length == 0
|
|
88
98
|
raise InvalidKeyStartError.new(
|
|
89
|
-
"Invalid key start",
|
|
90
|
-
1,
|
|
91
|
-
arg
|
|
99
|
+
"Invalid key start", 1, arg
|
|
92
100
|
)
|
|
93
101
|
end
|
|
94
102
|
|
|
95
|
-
# first character
|
|
103
|
+
# Validate the first character of the short-flag name.
|
|
96
104
|
c0 = name[0,1]
|
|
97
105
|
unless c0 == "_" ||
|
|
98
106
|
(c0 >= "A" && c0 <= "Z") ||
|
|
99
107
|
(c0 >= "a" && c0 <= "z") ||
|
|
100
|
-
(c0 > "\u007F")
|
|
108
|
+
(c0 > "\u007F")
|
|
101
109
|
raise InvalidCharacterError.new(
|
|
102
|
-
"Illegal character in short flag",
|
|
103
|
-
1,
|
|
104
|
-
arg
|
|
110
|
+
"Illegal character in short flag", 1, arg
|
|
105
111
|
)
|
|
106
112
|
end
|
|
107
113
|
|
|
108
|
-
#
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
ch = name[j,1]
|
|
112
|
-
valid =
|
|
113
|
-
ch == "_" ||
|
|
114
|
-
(ch >= "A" && ch <= "Z") ||
|
|
115
|
-
(ch >= "a" && ch <= "z") ||
|
|
116
|
-
(ch >= "0" && ch <= "9") ||
|
|
117
|
-
ch == "-" ||
|
|
118
|
-
ch == "." ||
|
|
119
|
-
(ch > "\u007F") # allow non-ASCII letters
|
|
120
|
-
unless valid
|
|
121
|
-
raise InvalidCharacterError.new(
|
|
122
|
-
"Illegal character in short flag",
|
|
123
|
-
1,
|
|
124
|
-
arg
|
|
125
|
-
)
|
|
126
|
-
end
|
|
127
|
-
j += 1
|
|
128
|
-
end
|
|
114
|
+
# The bare name is just the first character. Everything else
|
|
115
|
+
# (if any) is the attached value.
|
|
116
|
+
name = c0
|
|
129
117
|
|
|
130
|
-
# attached value (character-mode)
|
|
131
118
|
if arg.length > 2
|
|
132
119
|
val_str = arg[2, arg.length - 2]
|
|
133
120
|
val_lex = Lexer.new(val_str, 0, val_str.length, false)
|
|
@@ -140,7 +127,6 @@ module TypedArgs
|
|
|
140
127
|
out[name] = value
|
|
141
128
|
end
|
|
142
129
|
|
|
143
|
-
|
|
144
130
|
def build_hash(fields, vals)
|
|
145
131
|
h = {}
|
|
146
132
|
i = 0
|
|
@@ -156,10 +142,8 @@ module TypedArgs
|
|
|
156
142
|
when :scalar
|
|
157
143
|
out[name] = value
|
|
158
144
|
when :hash
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
value.each { |k, v| h[k] = v }
|
|
162
|
-
out[name] = h
|
|
145
|
+
# Hash tuple assignment overwrites the previous value, per README.
|
|
146
|
+
out[name] = value
|
|
163
147
|
when :array_scalar
|
|
164
148
|
existing = out[name]
|
|
165
149
|
arr = existing.is_a?(Array) ? existing : []
|
data/mrblib/lexer.rb
CHANGED
|
@@ -43,7 +43,7 @@ module TypedArgs
|
|
|
43
43
|
start = i
|
|
44
44
|
i += 1
|
|
45
45
|
|
|
46
|
-
# scan UTF
|
|
46
|
+
# scan UTF-8 codepoints until closing quote
|
|
47
47
|
while i < end_i
|
|
48
48
|
cp, ni = Internal.utf8_next(s, i)
|
|
49
49
|
|
|
@@ -88,7 +88,7 @@ module TypedArgs
|
|
|
88
88
|
@i = j
|
|
89
89
|
return Token.new(:IDENT, val, i)
|
|
90
90
|
else
|
|
91
|
-
# raw value: scan until
|
|
91
|
+
# raw value: scan until comma (the only intra-arg terminator)
|
|
92
92
|
start = i
|
|
93
93
|
while i < end_i
|
|
94
94
|
cc = s[i,1]
|
data/mrblib/typedargs.rb
CHANGED
|
@@ -27,6 +27,11 @@ module TypedArgs
|
|
|
27
27
|
Internal.register_alias(short, long)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
# Clear all registered aliases. Useful for test isolation.
|
|
31
|
+
def reset_aliases!
|
|
32
|
+
Internal.reset_aliases!
|
|
33
|
+
end
|
|
34
|
+
|
|
30
35
|
def opts(*argv)
|
|
31
36
|
args = argv.empty? ? ::ARGV : argv
|
|
32
37
|
Internal::Impl.parse(args)
|
data/mrblib/utf8.rb
CHANGED
data/mrblib/value_parser.rb
CHANGED
|
@@ -61,7 +61,7 @@ module TypedArgs
|
|
|
61
61
|
if Object.const_defined?(:Float)
|
|
62
62
|
return s.to_f if float_string?(s)
|
|
63
63
|
end
|
|
64
|
-
raise InvalidNumberError.new("Invalid number", @tok.pos,
|
|
64
|
+
raise InvalidNumberError.new("Invalid number", @tok.pos, @lx.str)
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def integer_string?(s)
|
data/mrblib/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: typedargs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Asmod4n
|
|
@@ -47,7 +47,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
47
47
|
- !ruby/object:Gem::Version
|
|
48
48
|
version: '0'
|
|
49
49
|
requirements: []
|
|
50
|
-
rubygems_version:
|
|
50
|
+
rubygems_version: 3.6.9
|
|
51
51
|
specification_version: 4
|
|
52
52
|
summary: A tiny, deterministic operator-typed CLI language.
|
|
53
53
|
test_files: []
|