Skip to content

Commit 83a8036

Browse files
Ellmendbieber
authored andcommittedMay 21, 2018
Fish completion support (google#122)
- Fish completion support (--completion fish)
1 parent 6912ccd commit 83a8036

9 files changed

+115
-18
lines changed
 

‎README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ Please see [The Python Fire Guide](docs/guide.md).
8282
| [Help](docs/using-cli.md#help-flag) | `command -- --help` |
8383
| [REPL](docs/using-cli.md#interactive-flag) | `command -- --interactive` | Enters interactive mode.
8484
| [Separator](docs/using-cli.md#separator-flag) | `command -- --separator=X` | This sets the separator to `X`. The default separator is `-`.
85-
| [Completion](docs/using-cli.md#completion-flag) | `command -- --completion` | Generate a completion script for the CLI.
85+
| [Completion](docs/using-cli.md#completion-flag) | `command -- --completion [shell]` | Generate a completion script for the CLI.
8686
| [Trace](docs/using-cli.md#trace-flag) | `command -- --trace` | Gets a Fire trace for the command.
8787
| [Verbose](docs/using-cli.md#verbose-flag) | `command -- --verbose` |
8888

‎docs/api.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
| [Help](using-cli.md#help-flag) | `command -- --help` |
1414
| [REPL](using-cli.md#interactive-flag) | `command -- --interactive` | Enters interactive mode.
1515
| [Separator](using-cli.md#separator-flag) | `command -- --separator=X` | This sets the separator to `X`. The default separator is `-`.
16-
| [Completion](using-cli.md#completion-flag) | `command -- --completion` | Generate a completion script for the CLI.
16+
| [Completion](using-cli.md#completion-flag) | `command -- --completion [shell]` | Generate a completion script for the CLI.
1717
| [Trace](using-cli.md#trace-flag) | `command -- --trace` | Gets a Fire trace for the command.
1818
| [Verbose](using-cli.md#verbose-flag) | `command -- --verbose` |
1919

‎docs/guide.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,7 @@ The complete set of flags available is shown below, in the reference section.
702702
| [Help](using-cli.md#help-flag) | `command -- --help` | Show help and usage information for the command.
703703
| [REPL](using-cli.md#interactive-flag) | `command -- --interactive` | Enter interactive mode.
704704
| [Separator](using-cli.md#separator-flag) | `command -- --separator=X` | This sets the separator to `X`. The default separator is `-`.
705-
| [Completion](using-cli.md#completion-flag) | `command -- --completion` | Generate a completion script for the CLI.
705+
| [Completion](using-cli.md#completion-flag) | `command -- --completion [shell]` | Generate a completion script for the CLI.
706706
| [Trace](using-cli.md#trace-flag) | `command -- --trace` | Gets a Fire trace for the command.
707707
| [Verbose](using-cli.md#verbose-flag) | `command -- --verbose` | Include private members in the output.
708708

‎docs/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ Please see [The Python Fire Guide](guide.md).
8282
| [Help](using-cli.md#help-flag) | `command -- --help` |
8383
| [REPL](using-cli.md#interactive-flag) | `command -- --interactive` | Enters interactive mode.
8484
| [Separator](using-cli.md#separator-flag) | `command -- --separator=X` | This sets the separator to `X`. The default separator is `-`.
85-
| [Completion](using-cli.md#completion-flag) | `command -- --completion` | Generate a completion script for the CLI.
85+
| [Completion](using-cli.md#completion-flag) | `command -- --completion [shell]` | Generate a completion script for the CLI.
8686
| [Trace](using-cli.md#trace-flag) | `command -- --trace` | Gets a Fire trace for the command.
8787
| [Verbose](using-cli.md#verbose-flag) | `command -- --verbose` |
8888

‎docs/using-cli.md

+3
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ Call `widget -- --completion` to generate a completion script for the Fire CLI
140140
run `widget -- --completion > ~/.widget-completion`. You should then source this
141141
file; to get permanent completion, source this file from your .bashrc file.
142142

143+
Call `widget -- --completion fish` to generate a completion script for the Fish
144+
shell. Source this file from your fish.config.
145+
143146
If the commands available in the Fire CLI change, you'll have to regenerate the
144147
completion script and source it again.
145148

‎fire/completion.py

+60-3
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626
import six
2727

2828

29-
def Script(name, component, default_options=None):
30-
return _Script(name, _Commands(component), default_options)
29+
def Script(name, component, default_options=None, shell='bash'):
30+
if shell == 'fish':
31+
return _FishScript(name, _Commands(component), default_options)
32+
return _BashScript(name, _Commands(component), default_options)
3133

3234

33-
def _Script(name, commands, default_options=None):
35+
def _BashScript(name, commands, default_options=None):
3436
"""Returns a Bash script registering a completion function for the commands.
3537
3638
Args:
@@ -97,6 +99,61 @@ def _Script(name, commands, default_options=None):
9799
)
98100

99101

102+
def _FishScript(name, commands, default_options=None):
103+
"""Returns a Fish script registering a completion function for the commands.
104+
105+
Args:
106+
name: The first token in the commands, also the name of the command.
107+
commands: A list of all possible commands that tab completion can complete
108+
to. Each command is a list or tuple of the string tokens that make up
109+
that command.
110+
default_options: A dict of options that can be used with any command. Use
111+
this if there are flags that can always be appended to a command.
112+
Returns:
113+
A string which is the Fish script. Source the fish script to enable tab
114+
completion in Fish.
115+
"""
116+
default_options = default_options or set()
117+
options_map = defaultdict(lambda: copy(default_options))
118+
for command in commands:
119+
start = (name + ' ' + ' '.join(command[:-1])).strip()
120+
completion = _FormatForCommand(command[-1])
121+
options_map[start].add(completion)
122+
options_map[start.replace('_', '-')].add(completion)
123+
fish_source = """function __fish_using_command
124+
set cmd (commandline -opc)
125+
if [ (count $cmd) -eq (count $argv) ]
126+
for i in (seq (count $argv))
127+
if [ $cmd[$i] != $argv[$i] ]
128+
return 1
129+
end
130+
end
131+
return 0
132+
end
133+
return 1
134+
end
135+
"""
136+
subcommand_template = "complete -c {name} -n " \
137+
"'__fish_using_command {start}' -f -a {subcommand}\n"
138+
flag_template = "complete -c {name} -n " \
139+
"'__fish_using_command {start}' -l {option}\n"
140+
for start in options_map:
141+
for option in sorted(options_map[start]):
142+
if option.startswith('--'):
143+
fish_source += flag_template.format(
144+
name=name,
145+
start=start,
146+
option=option[2:]
147+
)
148+
else:
149+
fish_source += subcommand_template.format(
150+
name=name,
151+
start=start,
152+
subcommand=option
153+
)
154+
return fish_source
155+
156+
100157
def _IncludeMember(name, verbose):
101158
if verbose:
102159
return True

‎fire/completion_test.py

+40-4
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,32 @@
2525

2626
class TabCompletionTest(testutils.BaseTestCase):
2727

28-
def testCompletionScript(self):
29-
# A sanity check test to make sure the completion script satisfies some
30-
# basic assumptions.
28+
def testCompletionBashScript(self):
29+
# A sanity check test to make sure the bash completion script satisfies
30+
# some basic assumptions.
3131
commands = [
3232
['run'],
3333
['halt'],
3434
['halt', '--now'],
3535
]
36-
script = completion._Script(name='command', commands=commands) # pylint: disable=protected-access
36+
script = completion._BashScript(name='command', commands=commands) # pylint: disable=protected-access
3737
self.assertIn('command', script)
3838
self.assertIn('halt', script)
3939
self.assertIn('"$start" == "command"', script)
4040

41+
def testCompletionFishScript(self):
42+
# A sanity check test to make sure the fish completion script satisfies
43+
# some basic assumptions.
44+
commands = [
45+
['run'],
46+
['halt'],
47+
['halt', '--now'],
48+
]
49+
script = completion._FishScript(name='command', commands=commands) # pylint: disable=protected-access
50+
self.assertIn('command', script)
51+
self.assertIn('halt', script)
52+
self.assertIn('-l now', script)
53+
4154
def testFnCompletions(self):
4255
def example(one, two, three):
4356
return one, two, three
@@ -113,6 +126,29 @@ def testClassScript(self):
113126
self.assertIn('--alpha', script)
114127
self.assertIn('--beta', script)
115128

129+
def testDeepDictFishScript(self):
130+
deepdict = {'level1': {'level2': {'level3': {'level4': {}}}}}
131+
script = completion.Script('deepdict', deepdict, shell='fish')
132+
self.assertIn('level1', script)
133+
self.assertIn('level2', script)
134+
self.assertIn('level3', script)
135+
self.assertNotIn('level4', script) # The default depth is 3.
136+
137+
def testFnFishScript(self):
138+
script = completion.Script('identity', tc.identity, shell='fish')
139+
self.assertIn('arg1', script)
140+
self.assertIn('arg2', script)
141+
self.assertIn('arg3', script)
142+
self.assertIn('arg4', script)
143+
144+
def testClassFishScript(self):
145+
script = completion.Script('', tc.MixedDefaults, shell='fish')
146+
self.assertIn('ten', script)
147+
self.assertIn('sum', script)
148+
self.assertIn('identity', script)
149+
self.assertIn('alpha', script)
150+
self.assertIn('beta', script)
151+
116152
def testNonStringDictCompletions(self):
117153
completions = completion.Completions({
118154
10: 'green',

‎fire/core.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def main(argv):
4444
-h --help: Provide help and usage information for the command.
4545
-i --interactive: Drop into a Python REPL after running the command.
4646
--completion: Write the Bash completion script for the tool to stdout.
47+
--completion fish: Write the Fish completion script for the tool to stdout.
4748
--separator SEPARATOR: Use SEPARATOR in place of the default separator, '-'.
4849
--trace: Get the Fire Trace for the command.
4950
"""
@@ -165,9 +166,9 @@ def Fire(component=None, command=None, name=None):
165166
return result
166167

167168

168-
def CompletionScript(name, component):
169-
"""Returns the text of the Bash completion script for a Fire CLI."""
170-
return completion.Script(name, component)
169+
def CompletionScript(name, component, shell):
170+
"""Returns the text of the completion script for a Fire CLI."""
171+
return completion.Script(name, component, shell=shell)
171172

172173

173174
class FireError(Exception):
@@ -338,7 +339,7 @@ def _Fire(component, args, context, name=None):
338339
initial_args = remaining_args
339340

340341
if not remaining_args and (show_help or interactive or show_trace
341-
or show_completion):
342+
or show_completion is not None):
342343
# Don't initialize the final class or call the final function unless
343344
# there's a separator after it, and instead process the current component.
344345
break
@@ -469,10 +470,10 @@ def _Fire(component, args, context, name=None):
469470
initial_args)
470471
return component_trace
471472

472-
if show_completion:
473+
if show_completion is not None:
473474
if name is None:
474475
raise ValueError('Cannot make completion script without command name')
475-
script = CompletionScript(name, initial_component)
476+
script = CompletionScript(name, initial_component, shell=show_completion)
476477
component_trace.AddCompletionScript(script)
477478

478479
if interactive:

‎fire/parser.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def CreateParser():
2727
parser.add_argument('--verbose', '-v', action='store_true')
2828
parser.add_argument('--interactive', '-i', action='store_true')
2929
parser.add_argument('--separator', default='-')
30-
parser.add_argument('--completion', action='store_true')
30+
parser.add_argument('--completion', nargs='?', const='bash', type=str)
3131
parser.add_argument('--help', '-h', action='store_true')
3232
parser.add_argument('--trace', '-t', action='store_true')
3333
# TODO: Consider allowing name to be passed as an argument.

0 commit comments

Comments
 (0)
Please sign in to comment.