completely 0.7.6 → 0.8.0.rc3
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 +206 -139
- data/lib/completely/commands/base.rb +3 -3
- data/lib/completely/commands/generate.rb +1 -1
- data/lib/completely/commands/init.rb +20 -7
- data/lib/completely/commands/test.rb +2 -2
- data/lib/completely/completions.rb +89 -3
- data/lib/completely/config.rb +9 -50
- data/lib/completely/flat_config.rb +56 -0
- data/lib/completely/pattern_config.rb +201 -0
- data/lib/completely/templates/pattern-config/sample.yaml +21 -0
- data/lib/completely/templates/pattern-config/template.erb +149 -0
- data/lib/completely/tester.rb +1 -1
- data/lib/completely/version.rb +1 -1
- data/lib/completely.rb +2 -0
- metadata +9 -5
- /data/lib/completely/templates/{sample-nested.yaml → flat-config/sample-nested.yaml} +0 -0
- /data/lib/completely/templates/{sample.yaml → flat-config/sample.yaml} +0 -0
- /data/lib/completely/templates/{template.erb → flat-config/template.erb} +0 -0
- /data/lib/completely/templates/{tester-template.erb → tester.bash.erb} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c08af2e45d441174aa7ba877f55ce3ab49ed37c066abba5b522f404226c4fb09
|
|
4
|
+
data.tar.gz: 4096cfb3dd365eece6ad5801cde77ca2127fe0500404839949d8e4b6a3c0ec85
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b8d65f92118b3700b77803005d936a3b1ddee313936207387496d7685bd6361ffcb3fbe6c28bd8c3b5b7fb7d4d1bd1b259e1e898da1b9739df07b99e17e89c1f
|
|
7
|
+
data.tar.gz: 398311bc0f57a4722a10d37776aad1a4e0651986a35832fc54a087618fcc3995543a21e35880deaa8ada9b88bfbc91183201c0a8bdf174ebb69ccd34e6233006
|
data/README.md
CHANGED
|
@@ -39,20 +39,15 @@ $ alias completely='docker run --rm -it --user $(id -u):$(id -g) --volume "$PWD:
|
|
|
39
39
|
|
|
40
40
|
## Configuration syntax
|
|
41
41
|
|
|
42
|
-
Completely works with a
|
|
43
|
-
|
|
42
|
+
Completely works with a YAML configuration file as input, and generates a bash
|
|
43
|
+
completions script as output.
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
There are three configuration formats:
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
- command
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Each pattern contains an array of words (or functions) that will be suggested
|
|
55
|
-
for the auto complete process.
|
|
47
|
+
- **Pattern config**: Recommended for new projects. It describes command shapes,
|
|
48
|
+
option groups, and value sources explicitly.
|
|
49
|
+
- **Flat config**: The original simple pattern-to-suggestions format.
|
|
50
|
+
- **Nested config**: A nested spelling of the flat config format.
|
|
56
51
|
|
|
57
52
|
You can save a sample YAML file by running:
|
|
58
53
|
|
|
@@ -60,165 +55,162 @@ You can save a sample YAML file by running:
|
|
|
60
55
|
$ completely init
|
|
61
56
|
```
|
|
62
57
|
|
|
63
|
-
This
|
|
64
|
-
|
|
65
|
-
```yaml
|
|
66
|
-
mygit:
|
|
67
|
-
- -h
|
|
68
|
-
- -v
|
|
69
|
-
- --help
|
|
70
|
-
- --version
|
|
71
|
-
- init
|
|
72
|
-
- status
|
|
73
|
-
|
|
74
|
-
mygit init:
|
|
75
|
-
- --bare
|
|
76
|
-
- <directory>
|
|
77
|
-
|
|
78
|
-
mygit status:
|
|
79
|
-
- --help
|
|
80
|
-
- --verbose
|
|
81
|
-
- --branch
|
|
82
|
-
- -b
|
|
83
|
-
|
|
84
|
-
mygit status*--branch: &branches
|
|
85
|
-
- $(git branch --format='%(refname:short)' 2>/dev/null)
|
|
58
|
+
This creates a `completely.yaml` file using the recommended pattern config
|
|
59
|
+
format. You can also choose a format explicitly:
|
|
86
60
|
|
|
87
|
-
|
|
61
|
+
```bash
|
|
62
|
+
$ completely init --format pattern
|
|
63
|
+
$ completely init --format flat
|
|
64
|
+
$ completely init --format nested
|
|
88
65
|
```
|
|
89
66
|
|
|
90
|
-
|
|
91
|
-
input, and if the input matches the pattern, the list that follows it will be
|
|
92
|
-
suggested as completions.
|
|
93
|
-
|
|
94
|
-
Note that the suggested completions will not show flags (strings that start with
|
|
95
|
-
a hyphen `-`) unless the input ends with a hyphen.
|
|
96
|
-
|
|
97
|
-
To generate the bash script, simply run:
|
|
67
|
+
To generate the bash script, run:
|
|
98
68
|
|
|
99
69
|
```bash
|
|
100
70
|
$ completely generate
|
|
101
71
|
|
|
102
|
-
# or, to
|
|
72
|
+
# or, to preview it without saving:
|
|
103
73
|
$ completely preview
|
|
104
74
|
```
|
|
105
75
|
|
|
106
|
-
For more options
|
|
76
|
+
For more options, run:
|
|
107
77
|
|
|
108
78
|
```bash
|
|
109
79
|
$ completely --help
|
|
110
80
|
```
|
|
111
81
|
|
|
112
|
-
###
|
|
82
|
+
### Pattern config
|
|
113
83
|
|
|
114
|
-
|
|
115
|
-
the special syntax `<..>` to suggest more advanced functions.
|
|
84
|
+
Pattern config is the recommended format for new completion files.
|
|
116
85
|
|
|
117
86
|
```yaml
|
|
118
|
-
|
|
119
|
-
-
|
|
120
|
-
- <directory>
|
|
87
|
+
patterns:
|
|
88
|
+
- mygit [root options]
|
|
89
|
+
- mygit init [init options] <directory>
|
|
90
|
+
- mygit status [status options]
|
|
91
|
+
|
|
92
|
+
options:
|
|
93
|
+
root:
|
|
94
|
+
- -h|--help
|
|
95
|
+
- -v|--version
|
|
96
|
+
init:
|
|
97
|
+
- --bare
|
|
98
|
+
status:
|
|
99
|
+
- --help
|
|
100
|
+
- --branch|-b <branch>
|
|
101
|
+
- --format <format>
|
|
102
|
+
- --verbose (repeatable)
|
|
103
|
+
|
|
104
|
+
tokens:
|
|
105
|
+
directory: +directory
|
|
106
|
+
branch: $(git branch --format='%(refname:short)' 2>/dev/null)
|
|
107
|
+
format: [short, long]
|
|
121
108
|
```
|
|
122
109
|
|
|
123
|
-
|
|
124
|
-
(when `<file>` is used) or just directories (when `<directory>` is used) to
|
|
125
|
-
the list of suggestions.
|
|
110
|
+
The `patterns` section describes valid command shapes:
|
|
126
111
|
|
|
127
|
-
|
|
112
|
+
- Plain words are command words, for example `mygit`, `init`, and `status`.
|
|
113
|
+
- Command aliases can be written with `|`, for example `status|st`.
|
|
114
|
+
- `[name options]` references `options.name`. `[name]` is also accepted.
|
|
115
|
+
- `<token>` references `tokens.token`.
|
|
116
|
+
- `<token>...` marks the final positional as repeatable.
|
|
128
117
|
|
|
129
|
-
|
|
130
|
-
|---------------|---------------------
|
|
131
|
-
| `<alias>` | Alias names
|
|
132
|
-
| `<arrayvar>` | Array variable names
|
|
133
|
-
| `<binding>` | Readline key binding names
|
|
134
|
-
| `<builtin>` | Names of shell builtin commands
|
|
135
|
-
| `<command>` | Command names
|
|
136
|
-
| `<directory>` | Directory names
|
|
137
|
-
| `<disabled>` | Names of disabled shell builtins
|
|
138
|
-
| `<enabled>` | Names of enabled shell builtins
|
|
139
|
-
| `<export>` | Names of exported shell variables
|
|
140
|
-
| `<file>` | File names
|
|
141
|
-
| `<function>` | Names of shell functions
|
|
142
|
-
| `<group>` | Group names
|
|
143
|
-
| `<helptopic>` | Help topics as accepted by the help builtin
|
|
144
|
-
| `<hostname>` | Hostnames, as taken from the file specified by the HOSTFILE shell variable
|
|
145
|
-
| `<job>` | Job names
|
|
146
|
-
| `<keyword>` | Shell reserved words
|
|
147
|
-
| `<running>` | Names of running jobs
|
|
148
|
-
| `<service>` | Service names
|
|
149
|
-
| `<signal>` | Signal names
|
|
150
|
-
| `<stopped>` | Names of stopped jobs
|
|
151
|
-
| `<user>` | User names
|
|
152
|
-
| `<variable>` | Names of all shell variables
|
|
153
|
-
|
|
154
|
-
For those interested in the technical details, any word between `<...>` will
|
|
155
|
-
simply be added using the [`compgen -A action`][compgen] function, so you can
|
|
156
|
-
in fact use any of its supported arguments.
|
|
157
|
-
|
|
158
|
-
### Suggesting custom dynamic suggestions
|
|
159
|
-
|
|
160
|
-
You can also use any command that outputs a whitespace-delimited list as a
|
|
161
|
-
suggestions list, by wrapping it in `$(..)`. For example, in order to add git
|
|
162
|
-
branches to your suggestions, use the following:
|
|
118
|
+
The `options` section defines option groups:
|
|
163
119
|
|
|
164
120
|
```yaml
|
|
165
|
-
|
|
166
|
-
|
|
121
|
+
options:
|
|
122
|
+
status:
|
|
123
|
+
- --help
|
|
124
|
+
- --branch|-b <branch>
|
|
125
|
+
- --verbose (repeatable)
|
|
167
126
|
```
|
|
168
127
|
|
|
169
|
-
|
|
170
|
-
|
|
128
|
+
An option can be a plain flag, aliases separated with `|`, or a flag that
|
|
129
|
+
expects a value token.
|
|
171
130
|
|
|
172
|
-
|
|
131
|
+
Options are unique by default. If an option should be suggested again after it
|
|
132
|
+
was already used, add `(repeatable)`:
|
|
173
133
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
-
|
|
178
|
-
|
|
179
|
-
### Suggesting flag arguments
|
|
134
|
+
```yaml
|
|
135
|
+
options:
|
|
136
|
+
status:
|
|
137
|
+
- --tag <tag> (repeatable)
|
|
138
|
+
```
|
|
180
139
|
|
|
181
|
-
|
|
182
|
-
arguments for flags. For example:
|
|
140
|
+
The final positional in a pattern can be repeatable:
|
|
183
141
|
|
|
184
142
|
```yaml
|
|
185
|
-
|
|
186
|
-
-
|
|
187
|
-
|
|
143
|
+
patterns:
|
|
144
|
+
- mygit upload <file>...
|
|
145
|
+
```
|
|
188
146
|
|
|
189
|
-
|
|
190
|
-
- $(git branch --format='%(refname:short)' 2>/dev/null)
|
|
147
|
+
Only the final positional may be repeatable.
|
|
191
148
|
|
|
192
|
-
|
|
193
|
-
|
|
149
|
+
The `tokens` section defines completion sources. Each token value can be one of
|
|
150
|
+
these forms:
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
tokens:
|
|
154
|
+
source: ~
|
|
155
|
+
directory: +directory
|
|
156
|
+
branch: $(git branch --format='%(refname:short)' 2>/dev/null)
|
|
157
|
+
format: [short, long]
|
|
158
|
+
target: [+file, +directory, README.md, $(git branch --format='%(refname:short)' 2>/dev/null)]
|
|
159
|
+
literal: ++file
|
|
194
160
|
```
|
|
195
161
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
162
|
+
- A null value such as `~` defines a token without completion suggestions.
|
|
163
|
+
- A value starting with `+`, such as `+directory`, uses a bash built-in completion action.
|
|
164
|
+
- A value starting with `++`, such as `++file`, provides the literal completion word `+file`.
|
|
165
|
+
- Plain strings, including `$(...)` command substitutions, are added to the completion word list.
|
|
166
|
+
- An array combines multiple source items.
|
|
167
|
+
|
|
168
|
+
Every `[name]` option group and every `<token>` used by patterns or options must
|
|
169
|
+
be defined. This keeps typos from generating broken completion scripts.
|
|
170
|
+
|
|
171
|
+
### Flat config
|
|
172
|
+
|
|
173
|
+
Flat config is the original Completely format. It is simpler, and remains
|
|
174
|
+
supported.
|
|
199
175
|
|
|
200
176
|
```yaml
|
|
201
|
-
mygit
|
|
177
|
+
mygit:
|
|
178
|
+
- -h
|
|
179
|
+
- -v
|
|
180
|
+
- --help
|
|
181
|
+
- --version
|
|
182
|
+
- init
|
|
183
|
+
- status
|
|
184
|
+
|
|
185
|
+
mygit init:
|
|
186
|
+
- --bare
|
|
187
|
+
- <directory>
|
|
188
|
+
|
|
189
|
+
mygit status:
|
|
190
|
+
- --help
|
|
191
|
+
- --verbose
|
|
202
192
|
- --branch
|
|
203
193
|
- -b
|
|
204
194
|
|
|
205
|
-
mygit
|
|
195
|
+
mygit status*--branch: &branches
|
|
206
196
|
- $(git branch --format='%(refname:short)' 2>/dev/null)
|
|
207
197
|
|
|
208
|
-
mygit
|
|
198
|
+
mygit status*-b: *branches
|
|
209
199
|
```
|
|
210
200
|
|
|
211
|
-
|
|
201
|
+
Each pattern is checked against the user's input. If the input matches the
|
|
202
|
+
pattern, the list that follows it is suggested as completions.
|
|
212
203
|
|
|
213
|
-
|
|
214
|
-
|
|
204
|
+
Suggested completions do not show flags (strings that start with a hyphen `-`)
|
|
205
|
+
unless the input ends with a hyphen.
|
|
215
206
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
207
|
+
Adding a `*` wildcard in the middle of a pattern can be used for suggesting flag
|
|
208
|
+
arguments. In the example above, branches are suggested after `--branch` or `-b`.
|
|
209
|
+
|
|
210
|
+
### Nested config
|
|
219
211
|
|
|
220
|
-
|
|
221
|
-
|
|
212
|
+
Nested config is an alternate spelling of flat config. It generates the same
|
|
213
|
+
completion behavior as the flat example above.
|
|
222
214
|
|
|
223
215
|
```yaml
|
|
224
216
|
mygit:
|
|
@@ -237,18 +229,74 @@ mygit:
|
|
|
237
229
|
- +-b: *branches
|
|
238
230
|
```
|
|
239
231
|
|
|
240
|
-
The rules
|
|
232
|
+
The rules are:
|
|
233
|
+
|
|
234
|
+
- Each pattern can have a mixed array of strings and hashes.
|
|
235
|
+
- Strings and hash keys are used as completion strings for that pattern.
|
|
236
|
+
- Hashes can contain a nested mixed array of the same structure.
|
|
237
|
+
- Hash keys are appended to the parent prefix. In the example above, the `init`
|
|
238
|
+
hash creates the pattern `mygit init`.
|
|
239
|
+
- To provide a wildcard such as `mygit status*--branch`, prefix the hash key with
|
|
240
|
+
`+` or `*`, for example `+--branch` or `"*--branch"`. When using `*`, quote the
|
|
241
|
+
key because asterisks have special meaning in YAML.
|
|
242
|
+
|
|
243
|
+
### Completion sources
|
|
244
|
+
|
|
245
|
+
Pattern config and the original flat/nested formats use the same underlying bash
|
|
246
|
+
completion sources, but they spell built-ins differently.
|
|
247
|
+
|
|
248
|
+
Pattern config uses named tokens:
|
|
249
|
+
|
|
250
|
+
```yaml
|
|
251
|
+
tokens:
|
|
252
|
+
file: +file
|
|
253
|
+
directory: +directory
|
|
254
|
+
branch: $(git branch --format='%(refname:short)' 2>/dev/null)
|
|
255
|
+
format: [short, long]
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Flat and nested configs use completion words directly:
|
|
241
259
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
-
|
|
246
|
-
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
260
|
+
```yaml
|
|
261
|
+
mygit init:
|
|
262
|
+
- <file>
|
|
263
|
+
- <directory>
|
|
264
|
+
- $(git branch --format='%(refname:short)' 2>/dev/null)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The built-in names map to `compgen -A` actions:
|
|
268
|
+
|
|
269
|
+
| Built-in | Meaning
|
|
270
|
+
|---------------|---------------------
|
|
271
|
+
| `alias` | Alias names
|
|
272
|
+
| `arrayvar` | Array variable names
|
|
273
|
+
| `binding` | Readline key binding names
|
|
274
|
+
| `builtin` | Names of shell builtin commands
|
|
275
|
+
| `command` | Command names
|
|
276
|
+
| `directory` | Directory names
|
|
277
|
+
| `disabled` | Names of disabled shell builtins
|
|
278
|
+
| `enabled` | Names of enabled shell builtins
|
|
279
|
+
| `export` | Names of exported shell variables
|
|
280
|
+
| `file` | File names
|
|
281
|
+
| `function` | Names of shell functions
|
|
282
|
+
| `group` | Group names
|
|
283
|
+
| `helptopic` | Help topics as accepted by the help builtin
|
|
284
|
+
| `hostname` | Hostnames, as taken from the file specified by the HOSTFILE shell variable
|
|
285
|
+
| `job` | Job names
|
|
286
|
+
| `keyword` | Shell reserved words
|
|
287
|
+
| `running` | Names of running jobs
|
|
288
|
+
| `service` | Service names
|
|
289
|
+
| `signal` | Signal names
|
|
290
|
+
| `stopped` | Names of stopped jobs
|
|
291
|
+
| `user` | User names
|
|
292
|
+
| `variable` | Names of all shell variables
|
|
293
|
+
|
|
294
|
+
### Completion scope and limitations
|
|
295
|
+
|
|
296
|
+
- Completion words are treated as whitespace-delimited tokens.
|
|
297
|
+
- Literal completion phrases that contain spaces are not supported as a single completion item.
|
|
298
|
+
- Quotes and other special shell characters in literal completion words are not escaped automatically.
|
|
299
|
+
- Dynamic `$(...)` completion commands should output plain whitespace-delimited words.
|
|
252
300
|
|
|
253
301
|
|
|
254
302
|
## Using the generated completion scripts
|
|
@@ -293,9 +341,26 @@ require 'completely'
|
|
|
293
341
|
# Load from file
|
|
294
342
|
completions = Completely::Completions.load "input.yaml"
|
|
295
343
|
|
|
296
|
-
# Or, from a hash
|
|
344
|
+
# Or, from a pattern config hash
|
|
345
|
+
input = {
|
|
346
|
+
"patterns" => [
|
|
347
|
+
"mygit init [init options] <directory>",
|
|
348
|
+
"mygit status|st [status options]"
|
|
349
|
+
],
|
|
350
|
+
"options" => {
|
|
351
|
+
"init" => ["--bare"],
|
|
352
|
+
"status" => ["--verbose|-v", "--branch|-b <branch>"]
|
|
353
|
+
},
|
|
354
|
+
"tokens" => {
|
|
355
|
+
"directory" => "directory",
|
|
356
|
+
"branch" => "$(git branch --format='%(refname:short)' 2>/dev/null)"
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
completions = Completely::Completions.new input
|
|
360
|
+
|
|
361
|
+
# Flat and nested config hashes are also supported by the same API.
|
|
297
362
|
input = {
|
|
298
|
-
"mygit" => %w[--help --version status init
|
|
363
|
+
"mygit" => %w[--help --version status init],
|
|
299
364
|
"mygit status" => %w[--help --verbose --branch]
|
|
300
365
|
}
|
|
301
366
|
completions = Completely::Completions.new input
|
|
@@ -327,8 +392,10 @@ autoload -Uz +X bashcompinit && bashcompinit
|
|
|
327
392
|
## Customizing the `complete` command
|
|
328
393
|
|
|
329
394
|
In case you wish to customize the `complete` command call in the generated
|
|
330
|
-
script, you can do so by adding any additional flags to the
|
|
331
|
-
configuration file using the special `completely_options`
|
|
395
|
+
script, you can do so by adding any additional flags to the
|
|
396
|
+
`completely.yaml` configuration file using the special `completely_options`
|
|
397
|
+
key. Completely passes these options to Bash's `complete` command as is. For
|
|
398
|
+
example:
|
|
332
399
|
|
|
333
400
|
```yaml
|
|
334
401
|
completely_options:
|
|
@@ -6,7 +6,7 @@ module Completely
|
|
|
6
6
|
class << self
|
|
7
7
|
def param_config_path
|
|
8
8
|
param 'CONFIG_PATH', <<~USAGE
|
|
9
|
-
Path to the YAML configuration file [default: completely.yaml].
|
|
9
|
+
Path to the Completely YAML configuration file (pattern, flat, or nested) [default: completely.yaml].
|
|
10
10
|
Can also be set by an environment variable.
|
|
11
11
|
USAGE
|
|
12
12
|
end
|
|
@@ -18,7 +18,7 @@ module Completely
|
|
|
18
18
|
|
|
19
19
|
def environment_config_path
|
|
20
20
|
environment 'COMPLETELY_CONFIG_PATH',
|
|
21
|
-
'Path to a
|
|
21
|
+
'Path to a Completely YAML configuration file [default: completely.yaml].'
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def environment_debug
|
|
@@ -62,7 +62,7 @@ module Completely
|
|
|
62
62
|
|
|
63
63
|
def syntax_warning
|
|
64
64
|
say! "\nr`WARNING:`\nr`Your configuration is invalid.`"
|
|
65
|
-
say! 'r`All patterns must
|
|
65
|
+
say! 'r`All completion patterns must use the same command name.`'
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
end
|
|
@@ -16,7 +16,7 @@ module Completely
|
|
|
16
16
|
option '-i --install PROGRAM', 'Install the generated script as completions for PROGRAM.'
|
|
17
17
|
|
|
18
18
|
param 'CONFIG_PATH', <<~USAGE
|
|
19
|
-
Path to the YAML configuration file [default: completely.yaml].
|
|
19
|
+
Path to the Completely YAML configuration file (pattern, flat, or nested) [default: completely.yaml].
|
|
20
20
|
Use '-' to read from stdin.
|
|
21
21
|
|
|
22
22
|
Can also be set by an environment variable.
|
|
@@ -3,12 +3,12 @@ require 'completely/commands/base'
|
|
|
3
3
|
module Completely
|
|
4
4
|
module Commands
|
|
5
5
|
class Init < Base
|
|
6
|
-
help 'Create a new sample YAML configuration file'
|
|
6
|
+
help 'Create a new sample Completely YAML configuration file'
|
|
7
7
|
|
|
8
|
-
usage 'completely init [--
|
|
8
|
+
usage 'completely init [--format FORMAT] [CONFIG_PATH]'
|
|
9
9
|
usage 'completely init (-h|--help)'
|
|
10
10
|
|
|
11
|
-
option '-
|
|
11
|
+
option '-f --format FORMAT', 'Sample format: pattern, flat, or nested [default: pattern]'
|
|
12
12
|
|
|
13
13
|
param_config_path
|
|
14
14
|
environment_config_path
|
|
@@ -26,16 +26,29 @@ module Completely
|
|
|
26
26
|
@sample ||= File.read sample_path
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def
|
|
30
|
-
args['--
|
|
29
|
+
def format
|
|
30
|
+
@format ||= args['--format'] || 'pattern'
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def sample_path
|
|
34
34
|
@sample_path ||= begin
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
raise Error, "Invalid format: #{format}" unless sample_filenames.key? format
|
|
36
|
+
|
|
37
|
+
File.expand_path "../templates/#{sample_filename}", __dir__
|
|
37
38
|
end
|
|
38
39
|
end
|
|
40
|
+
|
|
41
|
+
def sample_filename
|
|
42
|
+
sample_filenames.fetch format
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def sample_filenames
|
|
46
|
+
@sample_filenames ||= {
|
|
47
|
+
'flat' => 'flat-config/sample.yaml',
|
|
48
|
+
'nested' => 'flat-config/sample-nested.yaml',
|
|
49
|
+
'pattern' => 'pattern-config/sample.yaml',
|
|
50
|
+
}
|
|
51
|
+
end
|
|
39
52
|
end
|
|
40
53
|
end
|
|
41
54
|
end
|
|
@@ -6,8 +6,8 @@ module Completely
|
|
|
6
6
|
summary 'Test completions'
|
|
7
7
|
|
|
8
8
|
help 'This command can be used to test that your completions script responds with ' \
|
|
9
|
-
'the right completions. It works by reading
|
|
10
|
-
'a completions script, and generating a temporary testing script.'
|
|
9
|
+
'the right completions. It works by reading a Completely YAML configuration file, ' \
|
|
10
|
+
'generating a completions script, and generating a temporary testing script.'
|
|
11
11
|
|
|
12
12
|
usage 'completely test [--keep] COMPLINE...'
|
|
13
13
|
usage 'completely test (-h|--help)'
|
|
@@ -16,7 +16,7 @@ module Completely
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def initialize(config, function_name: nil)
|
|
19
|
-
@config =
|
|
19
|
+
@config = normalize_config config
|
|
20
20
|
@function_name = function_name
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -29,6 +29,8 @@ module Completely
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def valid?
|
|
32
|
+
return pattern_programs.uniq.one? if pattern_config?
|
|
33
|
+
|
|
32
34
|
pattern_prefixes.uniq.one?
|
|
33
35
|
end
|
|
34
36
|
|
|
@@ -62,7 +64,10 @@ module Completely
|
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
def template_path
|
|
65
|
-
@template_path ||=
|
|
67
|
+
@template_path ||= begin
|
|
68
|
+
template = pattern_config? ? 'pattern-config/template.erb' : 'flat-config/template.erb'
|
|
69
|
+
File.expand_path("templates/#{template}", __dir__)
|
|
70
|
+
end
|
|
66
71
|
end
|
|
67
72
|
|
|
68
73
|
def template
|
|
@@ -70,7 +75,7 @@ module Completely
|
|
|
70
75
|
end
|
|
71
76
|
|
|
72
77
|
def command
|
|
73
|
-
@command ||= flat_config.keys.first.split.first
|
|
78
|
+
@command ||= pattern_config? ? config.model[:program] : flat_config.keys.first.split.first
|
|
74
79
|
end
|
|
75
80
|
|
|
76
81
|
def function_name
|
|
@@ -91,5 +96,86 @@ module Completely
|
|
|
91
96
|
|
|
92
97
|
"#{options} "
|
|
93
98
|
end
|
|
99
|
+
|
|
100
|
+
def pattern_config?
|
|
101
|
+
config.is_a? PatternConfig
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def pattern_routes
|
|
105
|
+
config.model[:routes]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def pattern_programs
|
|
109
|
+
pattern_routes.map { |route| route.dig(:words, 0, :name) }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def pattern_root_words
|
|
113
|
+
pattern_routes.flat_map do |route|
|
|
114
|
+
word = route[:words][1]
|
|
115
|
+
word ? [word[:name], *word[:aliases]] : []
|
|
116
|
+
end.uniq
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def pattern_route_id(route)
|
|
120
|
+
pattern_routes.index route
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def pattern_route_conditions(route)
|
|
124
|
+
route[:words][1..].map.with_index do |word, index|
|
|
125
|
+
names = [word[:name], *word[:aliases]]
|
|
126
|
+
names.map { |name| %["${non_options[#{index}]}" == "#{bash_escape name}"] }.join(' || ')
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def pattern_route_word_count(route)
|
|
131
|
+
route[:words].size - 1
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def pattern_route_options(route)
|
|
135
|
+
route[:option_groups].flat_map do |name|
|
|
136
|
+
config.model[:options][name] || []
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def pattern_options_with_values
|
|
141
|
+
config.model[:options].values.flatten.select { |option| option[:value] }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def pattern_source_empty?(source)
|
|
145
|
+
source[:items].empty?
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def pattern_source_compgen(source)
|
|
149
|
+
wordlist = source[:items]
|
|
150
|
+
.select { |item| item[:type] == :value }
|
|
151
|
+
.map { |item| item[:value] }
|
|
152
|
+
.join(' ')
|
|
153
|
+
|
|
154
|
+
builtins = source[:items]
|
|
155
|
+
.select { |item| item[:type] == :builtin }
|
|
156
|
+
.map { |item| "-A #{bash_escape item[:value]}" }
|
|
157
|
+
|
|
158
|
+
parts = []
|
|
159
|
+
parts << %[-W "#{bash_double_quote_escape wordlist}"] unless wordlist.empty?
|
|
160
|
+
parts.concat builtins
|
|
161
|
+
parts.join(' ')
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def bash_escape(value)
|
|
165
|
+
value.to_s.gsub('\\', '\\\\\\').gsub('"', '\\"')
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def bash_double_quote_escape(value)
|
|
169
|
+
value.to_s.gsub('\\', '\\\\\\').gsub('"', '\\"')
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def normalize_config(config)
|
|
173
|
+
case config
|
|
174
|
+
when FlatConfig, PatternConfig
|
|
175
|
+
config
|
|
176
|
+
else
|
|
177
|
+
Config.build config
|
|
178
|
+
end
|
|
179
|
+
end
|
|
94
180
|
end
|
|
95
181
|
end
|
data/lib/completely/config.rb
CHANGED
|
@@ -1,67 +1,26 @@
|
|
|
1
1
|
module Completely
|
|
2
2
|
class Config
|
|
3
|
-
attr_reader :config, :options
|
|
4
|
-
|
|
5
3
|
class << self
|
|
6
4
|
def parse(str)
|
|
7
|
-
|
|
5
|
+
build YAML.load(str, aliases: true)
|
|
8
6
|
rescue Psych::Exception => e
|
|
9
7
|
raise ParseError, "Invalid YAML: #{e.message}"
|
|
10
8
|
end
|
|
11
9
|
|
|
12
10
|
def load(path) = parse(File.read(path))
|
|
13
11
|
def read(io) = parse(io.read)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def initialize(config)
|
|
17
|
-
@options = config.delete('completely_options')&.transform_keys(&:to_sym) || {}
|
|
18
|
-
@config = config
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def flat_config
|
|
22
|
-
result = {}
|
|
23
|
-
|
|
24
|
-
config.each do |root_key, root_list|
|
|
25
|
-
result.merge! process_key(root_key, root_list)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
result
|
|
29
|
-
end
|
|
30
12
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
result
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def collect_immediate_children(list)
|
|
41
|
-
list.map do |item|
|
|
42
|
-
x = item.is_a?(Hash) ? item.keys.first : item
|
|
43
|
-
x.gsub(/^[*+]/, '')
|
|
13
|
+
def build(config)
|
|
14
|
+
if pattern_config? config
|
|
15
|
+
PatternConfig.new config
|
|
16
|
+
else
|
|
17
|
+
FlatConfig.new config
|
|
18
|
+
end
|
|
44
19
|
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def process_nested_items(prefix, list)
|
|
48
|
-
result = {}
|
|
49
20
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
nested_prefix = generate_nested_prefix(prefix, item)
|
|
54
|
-
nested_list = item.values.first
|
|
55
|
-
result.merge!(process_key(nested_prefix, nested_list))
|
|
21
|
+
def pattern_config?(config)
|
|
22
|
+
config.is_a?(Hash) && config.has_key?('patterns')
|
|
56
23
|
end
|
|
57
|
-
|
|
58
|
-
result
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def generate_nested_prefix(prefix, item)
|
|
62
|
-
appended_prefix = item.keys.first.gsub(/^\+/, '*')
|
|
63
|
-
appended_prefix = " #{appended_prefix}" unless appended_prefix.start_with? '*'
|
|
64
|
-
"#{prefix}#{appended_prefix}"
|
|
65
24
|
end
|
|
66
25
|
end
|
|
67
26
|
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Completely
|
|
2
|
+
class FlatConfig
|
|
3
|
+
attr_reader :config, :options
|
|
4
|
+
|
|
5
|
+
def initialize(config)
|
|
6
|
+
@options = config.delete('completely_options')&.transform_keys(&:to_sym) || {}
|
|
7
|
+
@config = config
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def flat_config
|
|
11
|
+
result = {}
|
|
12
|
+
|
|
13
|
+
config.each do |root_key, root_list|
|
|
14
|
+
result.merge! process_key(root_key, root_list)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
result
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def process_key(prefix, list)
|
|
23
|
+
result = {}
|
|
24
|
+
result[prefix] = collect_immediate_children list
|
|
25
|
+
result.merge! process_nested_items(prefix, list)
|
|
26
|
+
result
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def collect_immediate_children(list)
|
|
30
|
+
list.map do |item|
|
|
31
|
+
x = item.is_a?(Hash) ? item.keys.first : item
|
|
32
|
+
x.gsub(/^[*+]/, '')
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def process_nested_items(prefix, list)
|
|
37
|
+
result = {}
|
|
38
|
+
|
|
39
|
+
list.each do |item|
|
|
40
|
+
next unless item.is_a? Hash
|
|
41
|
+
|
|
42
|
+
nested_prefix = generate_nested_prefix(prefix, item)
|
|
43
|
+
nested_list = item.values.first
|
|
44
|
+
result.merge!(process_key(nested_prefix, nested_list))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
result
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def generate_nested_prefix(prefix, item)
|
|
51
|
+
appended_prefix = item.keys.first.gsub(/^\+/, '*')
|
|
52
|
+
appended_prefix = " #{appended_prefix}" unless appended_prefix.start_with? '*'
|
|
53
|
+
"#{prefix}#{appended_prefix}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
module Completely
|
|
2
|
+
class PatternConfig
|
|
3
|
+
attr_reader :config, :options
|
|
4
|
+
|
|
5
|
+
def initialize(config)
|
|
6
|
+
@options = config.delete('completely_options')&.transform_keys(&:to_sym) || {}
|
|
7
|
+
@config = config
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def model
|
|
11
|
+
validate!
|
|
12
|
+
|
|
13
|
+
@model ||= {
|
|
14
|
+
program: program,
|
|
15
|
+
routes: routes,
|
|
16
|
+
options: parsed_options,
|
|
17
|
+
tokens: tokens,
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def flat_config
|
|
22
|
+
raise Error, 'Pattern config cannot be converted to flat config'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def patterns
|
|
28
|
+
@patterns ||= Array config['patterns']
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def option_groups
|
|
32
|
+
@option_groups ||= config['options'] || {}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def token_sources
|
|
36
|
+
@token_sources ||= config['tokens'] || {}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def program
|
|
40
|
+
routes.first.dig(:words, 0, :name)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def routes
|
|
44
|
+
@routes ||= patterns.map { |pattern| parse_pattern pattern }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def parsed_options
|
|
48
|
+
@parsed_options ||= option_groups.to_h do |name, entries|
|
|
49
|
+
[name, Array(entries).map { |entry| parse_option entry }]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def tokens
|
|
54
|
+
@tokens ||= token_sources.to_h do |name, source|
|
|
55
|
+
[name, parse_source(name, source)]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def validate!
|
|
60
|
+
missing_options = referenced_options - option_groups.keys
|
|
61
|
+
missing_tokens = referenced_tokens - token_sources.keys
|
|
62
|
+
|
|
63
|
+
errors = []
|
|
64
|
+
errors << "Unknown option group: #{missing_options.join ', '}" if missing_options.any?
|
|
65
|
+
errors << "Unknown token: #{missing_tokens.join ', '}" if missing_tokens.any?
|
|
66
|
+
errors.concat repeatable_positional_errors
|
|
67
|
+
raise ParseError, errors.join("\n") if errors.any?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def repeatable_positional_errors
|
|
71
|
+
patterns.filter_map do |pattern|
|
|
72
|
+
positionals = pattern_parts(pattern).select { |part| token? part }
|
|
73
|
+
next unless positionals[0...-1].any? { |part| repeatable_token? part }
|
|
74
|
+
|
|
75
|
+
"Repeatable positional must be the last positional in pattern: #{pattern}"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def referenced_options
|
|
80
|
+
patterns.flat_map do |pattern|
|
|
81
|
+
pattern_parts(pattern).filter_map { |part| option_group_name(part) if option_group?(part) }
|
|
82
|
+
end.uniq
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def referenced_tokens
|
|
86
|
+
pattern_tokens + option_tokens
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def pattern_tokens
|
|
90
|
+
patterns.flat_map do |pattern|
|
|
91
|
+
pattern_parts(pattern).filter_map { |part| token_name(part) if token?(part) }
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def option_tokens
|
|
96
|
+
option_groups.values.flatten.filter_map do |entry|
|
|
97
|
+
value_part = option_parts(entry).find { |part| token? part }
|
|
98
|
+
token_name(value_part) if value_part
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def parse_pattern(pattern)
|
|
103
|
+
result = { words: [], option_groups: [], positionals: [] }
|
|
104
|
+
|
|
105
|
+
pattern_parts(pattern).each do |part|
|
|
106
|
+
if option_group?(part)
|
|
107
|
+
result[:option_groups] << option_group_name(part)
|
|
108
|
+
elsif token?(part)
|
|
109
|
+
result[:positionals] << parse_token(part)
|
|
110
|
+
else
|
|
111
|
+
result[:words] << parse_word(part)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
result
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def parse_word(part)
|
|
119
|
+
names = part.split('|')
|
|
120
|
+
{ name: names.first, aliases: names[1..] || [] }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def pattern_parts(pattern)
|
|
124
|
+
pattern.scan(/\[[^\]]+\]|<[^>]+>\.\.\.|<[^>]+>|\S+/)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def parse_option(entry)
|
|
128
|
+
flag_part, *parts = option_parts entry
|
|
129
|
+
value_part = parts.find { |part| token? part }
|
|
130
|
+
metadata_parts = parts.select { |part| metadata? part }
|
|
131
|
+
unknown_parts = parts - [value_part] - metadata_parts
|
|
132
|
+
raise ParseError, "Invalid option syntax: #{entry}" if unknown_parts.any?
|
|
133
|
+
|
|
134
|
+
names = flag_part.split('|')
|
|
135
|
+
|
|
136
|
+
result = { names: names, repeatable: false }
|
|
137
|
+
result[:value] = parse_token(value_part) if value_part
|
|
138
|
+
metadata_parts.each { |part| apply_option_metadata result, part }
|
|
139
|
+
result
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def option_parts(entry)
|
|
143
|
+
entry.scan(/<[^>]+>|\([^)]+\)|\S+/)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def apply_option_metadata(result, part)
|
|
147
|
+
case part
|
|
148
|
+
when '(repeatable)'
|
|
149
|
+
result[:repeatable] = true
|
|
150
|
+
else
|
|
151
|
+
raise ParseError, "Unknown option metadata: #{part}"
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def metadata?(part)
|
|
156
|
+
part.start_with?('(') && part.end_with?(')')
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def parse_token(part)
|
|
160
|
+
repeatable = repeatable_token? part
|
|
161
|
+
token_part = repeatable ? part.delete_suffix('...') : part
|
|
162
|
+
name = token_name token_part
|
|
163
|
+
result = { name: name, source: parse_source(name, token_sources[name]) }
|
|
164
|
+
result[:repeatable] = true if repeatable
|
|
165
|
+
result
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def parse_source(_name, source)
|
|
169
|
+
source_items = source.is_a?(Array) ? source : [source]
|
|
170
|
+
items = source_items.compact.map { |item| parse_source_item item }
|
|
171
|
+
{ items: items }
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def parse_source_item(item)
|
|
175
|
+
return { type: :value, value: item.to_s[1..] } if item.to_s.start_with? '++'
|
|
176
|
+
return { type: :builtin, value: item.to_s[1..] } if item.to_s.start_with? '+'
|
|
177
|
+
|
|
178
|
+
{ type: :value, value: item.to_s }
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def option_group?(part)
|
|
182
|
+
part.start_with?('[') && part.end_with?(']')
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def option_group_name(part)
|
|
186
|
+
part[1..-2].sub(/\s+options\z/, '')
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def token?(part)
|
|
190
|
+
part.match?(/\A<[^>]+>(?:\.\.\.)?\z/)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def repeatable_token?(part)
|
|
194
|
+
part.end_with? '...'
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def token_name(part)
|
|
198
|
+
part.delete_suffix('...')[/\A<(.+)>\z/, 1]
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
patterns:
|
|
2
|
+
- mygit [root options]
|
|
3
|
+
- mygit init [init options] <directory>
|
|
4
|
+
- mygit status [status options]
|
|
5
|
+
|
|
6
|
+
options:
|
|
7
|
+
root:
|
|
8
|
+
- -h|--help
|
|
9
|
+
- -v|--version
|
|
10
|
+
init:
|
|
11
|
+
- --bare
|
|
12
|
+
status:
|
|
13
|
+
- --help
|
|
14
|
+
- --branch|-b <branch>
|
|
15
|
+
- --format <format>
|
|
16
|
+
- --verbose (repeatable)
|
|
17
|
+
|
|
18
|
+
tokens:
|
|
19
|
+
directory: +directory
|
|
20
|
+
branch: $(git branch --format='%(refname:short)' 2>/dev/null)
|
|
21
|
+
format: [short, long]
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# <%= "#{command} completion".ljust 56 %> -*- shell-script -*-
|
|
2
|
+
|
|
3
|
+
# This bash completions script was generated by
|
|
4
|
+
# completely (https://github.com/bashly-framework/completely)
|
|
5
|
+
# Modifying it manually is not recommended
|
|
6
|
+
|
|
7
|
+
<%= function_name %>_flag_expects_value() {
|
|
8
|
+
case "$1" in
|
|
9
|
+
% pattern_options_with_values.each do |option|
|
|
10
|
+
<%= option[:names].map { |name| bash_escape name }.join('|') %>) return 0 ;;
|
|
11
|
+
% end
|
|
12
|
+
esac
|
|
13
|
+
|
|
14
|
+
return 1
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
<%= function_name %>() {
|
|
18
|
+
local cur=${COMP_WORDS[COMP_CWORD]}
|
|
19
|
+
local prev=
|
|
20
|
+
if ((COMP_CWORD > 0)); then
|
|
21
|
+
prev=${COMP_WORDS[$((COMP_CWORD - 1))]}
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
local completed=()
|
|
25
|
+
if ((COMP_CWORD > 1)); then
|
|
26
|
+
completed=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}")
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
local non_options=()
|
|
30
|
+
local completed_options=()
|
|
31
|
+
local skip_next=0
|
|
32
|
+
for word in "${completed[@]}"; do
|
|
33
|
+
if ((skip_next)); then
|
|
34
|
+
skip_next=0
|
|
35
|
+
continue
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
if [[ "${word:0:1}" == "-" ]]; then
|
|
39
|
+
completed_options+=("$word")
|
|
40
|
+
if <%= function_name %>_flag_expects_value "$word"; then
|
|
41
|
+
skip_next=1
|
|
42
|
+
fi
|
|
43
|
+
continue
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
non_options+=("$word")
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
local route_id=
|
|
50
|
+
local route_word_count=-1
|
|
51
|
+
local route_has_positionals=0
|
|
52
|
+
local positional_index=0
|
|
53
|
+
% pattern_routes.each do |route|
|
|
54
|
+
% conditions = pattern_route_conditions(route)
|
|
55
|
+
if (( ${#non_options[@]} >= <%= pattern_route_word_count route %> )) &&
|
|
56
|
+
(( <%= pattern_route_word_count route %> > route_word_count ))<%= conditions.empty? ? '' : ' &&' %>
|
|
57
|
+
% conditions.each_with_index do |condition, index|
|
|
58
|
+
[[ <%= condition %> ]]<%= index == conditions.size - 1 ? '' : ' &&' %>
|
|
59
|
+
% end
|
|
60
|
+
then
|
|
61
|
+
route_id=<%= pattern_route_id route %>
|
|
62
|
+
route_word_count=<%= pattern_route_word_count route %>
|
|
63
|
+
route_has_positionals=<%= route[:positionals].empty? ? 0 : 1 %>
|
|
64
|
+
positional_index=$((${#non_options[@]} - <%= pattern_route_word_count route %>))
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
% end
|
|
68
|
+
COMPREPLY=()
|
|
69
|
+
|
|
70
|
+
if [[ -z "$route_id" ]] || { (( route_word_count == 0 )) && (( !route_has_positionals )) && [[ "${cur:0:1}" != "-" ]]; }; then
|
|
71
|
+
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "<%= bash_double_quote_escape pattern_root_words.join(' ') %>" -- "$cur")
|
|
72
|
+
return
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
case "$route_id:$prev" in
|
|
76
|
+
% pattern_routes.each do |route|
|
|
77
|
+
% pattern_route_options(route).select { |option| option[:value] }.each do |option|
|
|
78
|
+
<%= pattern_route_id route %>:<%= option[:names].map { |name| bash_escape name }.join("|#{pattern_route_id route}:") %>)
|
|
79
|
+
% if pattern_source_empty? option[:value][:source]
|
|
80
|
+
return
|
|
81
|
+
% else
|
|
82
|
+
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen <%= pattern_source_compgen option[:value][:source] %> -- "$cur")
|
|
83
|
+
return
|
|
84
|
+
% end
|
|
85
|
+
;;
|
|
86
|
+
% end
|
|
87
|
+
% end
|
|
88
|
+
esac
|
|
89
|
+
|
|
90
|
+
if [[ "${cur:0:1}" == "-" ]]; then
|
|
91
|
+
case "$route_id" in
|
|
92
|
+
% pattern_routes.each do |route|
|
|
93
|
+
<%= pattern_route_id route %>)
|
|
94
|
+
local words=()
|
|
95
|
+
% pattern_route_options(route).each do |option|
|
|
96
|
+
% if option[:repeatable]
|
|
97
|
+
words+=(<%= option[:names].map { |name| %["#{bash_escape name}"] }.join(' ') %>)
|
|
98
|
+
% else
|
|
99
|
+
local option_seen=0
|
|
100
|
+
for completed_option in "${completed_options[@]}"; do
|
|
101
|
+
case "$completed_option" in
|
|
102
|
+
<%= option[:names].map { |name| bash_escape name }.join('|') %>) option_seen=1 ;;
|
|
103
|
+
esac
|
|
104
|
+
done
|
|
105
|
+
if ((!option_seen)); then
|
|
106
|
+
words+=(<%= option[:names].map { |name| %["#{bash_escape name}"] }.join(' ') %>)
|
|
107
|
+
fi
|
|
108
|
+
% end
|
|
109
|
+
% end
|
|
110
|
+
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "${words[*]}" -- "$cur")
|
|
111
|
+
return
|
|
112
|
+
;;
|
|
113
|
+
% end
|
|
114
|
+
esac
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
% pattern_routes.each do |route|
|
|
118
|
+
% route[:positionals].each_with_index do |positional, index|
|
|
119
|
+
% next unless positional[:repeatable]
|
|
120
|
+
if [[ "$route_id" == "<%= pattern_route_id route %>" ]] && (( positional_index >= <%= index %> )); then
|
|
121
|
+
% if pattern_source_empty? positional[:source]
|
|
122
|
+
return
|
|
123
|
+
% else
|
|
124
|
+
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen <%= pattern_source_compgen positional[:source] %> -- "$cur")
|
|
125
|
+
return
|
|
126
|
+
% end
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
% end
|
|
130
|
+
% end
|
|
131
|
+
case "$route_id:$positional_index" in
|
|
132
|
+
% pattern_routes.each do |route|
|
|
133
|
+
% route[:positionals].each_with_index do |positional, index|
|
|
134
|
+
% next if positional[:repeatable]
|
|
135
|
+
<%= pattern_route_id route %>:<%= index %>)
|
|
136
|
+
% if pattern_source_empty? positional[:source]
|
|
137
|
+
return
|
|
138
|
+
% else
|
|
139
|
+
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen <%= pattern_source_compgen positional[:source] %> -- "$cur")
|
|
140
|
+
return
|
|
141
|
+
% end
|
|
142
|
+
;;
|
|
143
|
+
% end
|
|
144
|
+
% end
|
|
145
|
+
esac
|
|
146
|
+
} &&
|
|
147
|
+
complete <%= complete_options_line %>-F <%= function_name %> <%= command %>
|
|
148
|
+
|
|
149
|
+
# ex: filetype=sh
|
data/lib/completely/tester.rb
CHANGED
data/lib/completely/version.rb
CHANGED
data/lib/completely.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: completely
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0.rc3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Danny Ben Shitrit
|
|
@@ -72,12 +72,16 @@ files:
|
|
|
72
72
|
- lib/completely/completions.rb
|
|
73
73
|
- lib/completely/config.rb
|
|
74
74
|
- lib/completely/exceptions.rb
|
|
75
|
+
- lib/completely/flat_config.rb
|
|
75
76
|
- lib/completely/installer.rb
|
|
76
77
|
- lib/completely/pattern.rb
|
|
77
|
-
- lib/completely/
|
|
78
|
-
- lib/completely/templates/sample.yaml
|
|
79
|
-
- lib/completely/templates/
|
|
80
|
-
- lib/completely/templates/
|
|
78
|
+
- lib/completely/pattern_config.rb
|
|
79
|
+
- lib/completely/templates/flat-config/sample-nested.yaml
|
|
80
|
+
- lib/completely/templates/flat-config/sample.yaml
|
|
81
|
+
- lib/completely/templates/flat-config/template.erb
|
|
82
|
+
- lib/completely/templates/pattern-config/sample.yaml
|
|
83
|
+
- lib/completely/templates/pattern-config/template.erb
|
|
84
|
+
- lib/completely/templates/tester.bash.erb
|
|
81
85
|
- lib/completely/tester.rb
|
|
82
86
|
- lib/completely/version.rb
|
|
83
87
|
homepage: https://github.com/bashly-framework/completely
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|