Skip to content

Commit bf0c143

Browse files
authored
Merge pull request #76 from DannyBen/add/nested-config-support
Add support for nested configuration
2 parents 9230b1a + 039628b commit bf0c143

File tree

20 files changed

+288
-82
lines changed

20 files changed

+288
-82
lines changed

README.md

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ or with Docker:
4040
$ alias completely='docker run --rm -it --user $(id -u):$(id -g) --volume "$PWD:/app" dannyben/completely'
4141
```
4242

43-
## Using the `completely` command line
43+
## Configuration syntax
4444

45-
The `completely` command line works with a simple YAML configuration file as
46-
input, and generates a bash completions script as output.
45+
Completely works with a simple YAML configuration file as input, and generates
46+
a bash completions script as output.
4747

4848
The configuration file is built of blocks that look like this:
4949

@@ -59,45 +59,42 @@ for the auto complete process.
5959
6060
You can save a sample YAML file by running:
6161
62-
```
62+
```bash
6363
$ completely init
6464
```
6565

6666
This will generate a file named `completely.yaml` with this content:
6767

6868
```yaml
6969
mygit:
70+
- -h
71+
- -v
7072
- --help
7173
- --version
72-
- status
7374
- init
74-
- commit
75+
- status
76+
77+
mygit init:
78+
- --bare
79+
- <directory>
7580

7681
mygit status:
7782
- --help
7883
- --verbose
7984
- --branch
80-
- $(git branch --format='%(refname:short)' 2>/dev/null)
85+
- -b
8186

82-
mygit init:
83-
- --bare
84-
- <directory>
87+
mygit status*--branch: &branches
88+
- $(git branch --format='%(refname:short)' 2>/dev/null)
8589

86-
mygit commit:
87-
- <file>
88-
- --help
89-
- --message
90-
- --all
91-
- -a
92-
- --quiet
93-
- -q
90+
mygit status*-b: *branches
9491
```
9592
9693
Each pattern in this configuration file will be checked against the user's
97-
input, and if the input **starts with** a matching pattern, the list that
98-
follows it will be suggested as completions.
94+
input, and if the input matches the pattern, the list that follows it will be
95+
suggested as completions.
9996
100-
Note that the suggested completions will not show flags (string that start with
97+
Note that the suggested completions will not show flags (strings that start with
10198
a hyphen `-`) unless the input ends with a hyphen.
10299

103100
To generate the bash script, simply run:
@@ -207,6 +204,48 @@ mygit checkout*--branch: &branches
207204
mygit checkout*-b: *branches
208205
```
209206

207+
### Alternative nested syntax
208+
209+
Completely also supports an alternative nested syntax. You can generate this
210+
example by running:
211+
212+
```bash
213+
$ completely init --nested
214+
```
215+
216+
The example configuration below will generate the exact same script as the one
217+
shown at the beginning of this document.
218+
219+
```yaml
220+
mygit:
221+
- -h
222+
- -v
223+
- --help
224+
- --version
225+
- init:
226+
- --bare
227+
- <directory>
228+
- status:
229+
- --help
230+
- --verbose
231+
- +--branch: &branches
232+
- $(git branch --format='%(refname:short)' 2>/dev/null)
233+
- +-b: *branches
234+
```
235+
236+
The rules here are as follows:
237+
238+
- Each pattern (e.g., `mygit`) can have a mixed array of strings and hashes.
239+
- Strings and hash keys (e.e., `--help` and `init` respectively) will be used
240+
as completion strings for that pattern.
241+
- Hashes may contain a nested mixed array of the same structure.
242+
- When a hash is provided, the hash key will be appended to the parent prefix.
243+
In the example above, the `init` hash will create the pattern `mygit init`.
244+
- In order to provide a wildcard (like `mygit status*--branch` in the standard
245+
configuration syntax), you can provide either a `*` or a `+` prefix to the
246+
hash key (e.g., `+--branch` or `"*--branch"`). Note that when using a `*`,
247+
the hash key must be quoted since asterisks have special meaning in YAML.
248+
210249
## Using the generated completion scripts
211250

212251
In order to enable the completions, simply source the generated script:

completely.gemspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ Gem::Specification.new do |s|
1616

1717
s.required_ruby_version = '>= 3.0'
1818

19-
s.add_runtime_dependency 'colsole', '>= 0.8.1', '< 2'
20-
s.add_runtime_dependency 'mister_bin', '~> 0.7'
19+
s.add_dependency 'colsole', '>= 0.8.1', '< 2'
20+
s.add_dependency 'mister_bin', '~> 0.7'
2121

2222
s.metadata = {
2323
'bug_tracker_uri' => 'https://github.com/DannyBen/completely/issues',

lib/completely.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require 'completely/exceptions'
2+
require 'completely/config'
23
require 'completely/pattern'
34
require 'completely/completions'
45
require 'completely/tester'

lib/completely/commands/init.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ module Commands
55
class Init < Base
66
help 'Create a new sample YAML configuration file'
77

8-
usage 'completely init [CONFIG_PATH]'
8+
usage 'completely init [--nested] [CONFIG_PATH]'
99
usage 'completely init (-h|--help)'
1010

11+
option '-n --nested', 'Generate a nested configuration'
12+
1113
param_config_path
1214
environment_config_path
1315

@@ -24,8 +26,15 @@ def sample
2426
@sample ||= File.read sample_path
2527
end
2628

29+
def nested?
30+
args['--nested']
31+
end
32+
2733
def sample_path
28-
@sample_path ||= File.expand_path '../templates/sample.yaml', __dir__
34+
@sample_path ||= begin
35+
sample_name = nested? ? 'sample-nested' : 'sample'
36+
File.expand_path "../templates/#{sample_name}.yaml", __dir__
37+
end
2938
end
3039
end
3140
end

lib/completely/completions.rb

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,20 @@ class Completions
77

88
class << self
99
def load(config_path, function_name: nil)
10-
begin
11-
data = YAML.load_file config_path, aliases: true
12-
rescue ArgumentError
13-
# :nocov:
14-
data = YAML.load_file config_path
15-
# :nocov:
16-
end
17-
18-
new data, function_name: function_name
10+
config = Config.load config_path
11+
new config, function_name: function_name
1912
end
2013
end
2114

2215
def initialize(config, function_name: nil)
23-
@config = config
16+
@config = config.is_a?(Config) ? config : Config.new(config)
2417
@function_name = function_name
2518
end
2619

20+
def flat_config
21+
@flat_config ||= config.flat_config
22+
end
23+
2724
def patterns
2825
@patterns ||= patterns!
2926
end
@@ -54,7 +51,7 @@ def tester
5451
private
5552

5653
def patterns!
57-
result = config.map do |text, completions|
54+
result = flat_config.map do |text, completions|
5855
Pattern.new text, completions, pattern_function_name
5956
end
6057

@@ -70,7 +67,7 @@ def template
7067
end
7168

7269
def command
73-
@command ||= config.keys.first.split.first
70+
@command ||= flat_config.keys.first.split.first
7471
end
7572

7673
def function_name

lib/completely/config.rb

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
module Completely
2+
class Config
3+
attr_reader :config
4+
5+
class << self
6+
def load(config_path)
7+
begin
8+
config = YAML.load_file config_path, aliases: true
9+
rescue ArgumentError
10+
# :nocov:
11+
config = YAML.load_file config_path
12+
# :nocov:
13+
end
14+
15+
new config
16+
end
17+
end
18+
19+
def initialize(config)
20+
@config = config
21+
end
22+
23+
def flat_config
24+
result = {}
25+
26+
config.each do |root_key, root_list|
27+
result.merge! process_key(root_key, root_list)
28+
end
29+
30+
result
31+
end
32+
33+
private
34+
35+
def process_key(prefix, list)
36+
result = {}
37+
result[prefix] = collect_immediate_children list
38+
result.merge! process_nested_items(prefix, list)
39+
result
40+
end
41+
42+
def collect_immediate_children(list)
43+
list.map do |item|
44+
x = item.is_a?(Hash) ? item.keys.first : item
45+
x.gsub(/^[*+]/, '')
46+
end
47+
end
48+
49+
def process_nested_items(prefix, list)
50+
result = {}
51+
52+
list.each do |item|
53+
next unless item.is_a? Hash
54+
55+
nested_prefix = generate_nested_prefix(prefix, item)
56+
nested_list = item.values.first
57+
result.merge!(process_key(nested_prefix, nested_list))
58+
end
59+
60+
result
61+
end
62+
63+
def generate_nested_prefix(prefix, item)
64+
appended_prefix = item.keys.first.gsub(/^\+/, '*')
65+
appended_prefix = " #{appended_prefix}" unless appended_prefix.start_with? '*'
66+
"#{prefix}#{appended_prefix}"
67+
end
68+
end
69+
end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
mygit:
2+
- -h
3+
- -v
4+
- --help
5+
- --version
6+
- init:
7+
- --bare
8+
- <directory>
9+
- status:
10+
- --help
11+
- --verbose
12+
- +--branch: &branches
13+
- $(git branch --format='%(refname:short)' 2>/dev/null)
14+
- +-b: *branches
Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
11
mygit:
2+
- -h
3+
- -v
24
- --help
35
- --version
4-
- status
56
- init
6-
- commit
7+
- status
8+
9+
mygit init:
10+
- --bare
11+
- <directory>
712

813
mygit status:
914
- --help
1015
- --verbose
1116
- --branch
12-
- $(git branch --format='%(refname:short)' 2>/dev/null)
17+
- -b
1318

14-
mygit init:
15-
- --bare
16-
- <directory>
19+
mygit status*--branch: &branches
20+
- $(git branch --format='%(refname:short)' 2>/dev/null)
1721

18-
mygit commit:
19-
- <file>
20-
- --help
21-
- --message
22-
- --all
23-
- -a
24-
- --quiet
25-
- -q
22+
mygit status*-b: *branches

spec/approvals/cli/generated-script

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,24 @@ _mygit_completions() {
2828
local compline="${compwords[*]}"
2929

3030
case "$compline" in
31-
'status'*)
32-
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help --verbose --branch $(git branch --format='%(refname:short)' 2>/dev/null)")" -- "$cur")
31+
'status'*'--branch')
32+
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "$(git branch --format='%(refname:short)' 2>/dev/null)")" -- "$cur")
33+
;;
34+
35+
'status'*'-b')
36+
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "$(git branch --format='%(refname:short)' 2>/dev/null)")" -- "$cur")
3337
;;
3438

35-
'commit'*)
36-
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A file -W "$(_mygit_completions_filter "--help --message --all -a --quiet -q")" -- "$cur")
39+
'status'*)
40+
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help --verbose --branch -b")" -- "$cur")
3741
;;
3842

3943
'init'*)
4044
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_mygit_completions_filter "--bare")" -- "$cur")
4145
;;
4246

4347
*)
44-
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help --version status init commit")" -- "$cur")
48+
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h -v --help --version init status")" -- "$cur")
4549
;;
4650

4751
esac

0 commit comments

Comments
 (0)