Skip to content

Commit 7f6541d

Browse files
authored
Autogenerate bash/zsh completion files (truckersmp-cli#174)
1 parent 92f9849 commit 7f6541d

File tree

6 files changed

+363
-94
lines changed

6 files changed

+363
-94
lines changed

Diff for: .github/workflows/check_gen_completions.yaml

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Completions generator
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
- dev
8+
paths:
9+
- .github/workflows/check_gen_completions.yaml
10+
- gen_completions
11+
pull_request:
12+
branches:
13+
- master
14+
- dev
15+
paths:
16+
- .github/workflows/check_gen_completions.yaml
17+
- gen_completions
18+
19+
jobs:
20+
check_by_flake8:
21+
name: Check completions generator by flake8
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v2
25+
- uses: actions/setup-python@v1
26+
with:
27+
python-version: '3.x'
28+
- name: Install flake8, flake8-docstrings, and flake8-import-order
29+
run: pip install flake8 flake8-docstrings flake8-import-order
30+
- name: Check Python script by flake8
31+
run: flake8 --max-line-length=90 --show-source --statistics gen_completions
32+
check_by_pylint:
33+
name: Check main scripts by pylint
34+
runs-on: ubuntu-latest
35+
steps:
36+
- uses: actions/checkout@v2
37+
- uses: actions/setup-python@v1
38+
with:
39+
python-version: '3.x'
40+
- name: Install pylint
41+
run: pip install pylint
42+
- name: Check Python scripts by pylint
43+
run: pylint -j2 gen_completions

Diff for: Makefile

+4-8
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,11 @@ all: truckersmp_cli/truckersmp-cli.exe _truckersmp-cli truckersmp-cli.bash
55
truckersmp_cli/truckersmp-cli.exe: truckersmp-cli.c
66
$(CC) $< -o $@
77

8-
_truckersmp-cli:
9-
if command -v genzshcomp > /dev/null; then \
10-
./truckersmp-cli --help | genzshcomp -f zsh > _truckersmp-cli; \
11-
fi
8+
_truckersmp-cli: gen_completions truckersmp_cli/args.py truckersmp_cli/variables.py truckersmp_cli/proton.json
9+
./gen_completions --zsh-completion $@
1210

13-
truckersmp-cli.bash:
14-
if command -v genzshcomp > /dev/null; then \
15-
./truckersmp-cli --help | genzshcomp -f bash > truckersmp-cli.bash; \
16-
fi
11+
truckersmp-cli.bash: gen_completions truckersmp_cli/args.py truckersmp_cli/variables.py truckersmp_cli/proton.json
12+
./gen_completions --bash-completion $@
1713

1814
clean:
1915
rm -f truckersmp_cli/truckersmp-cli.exe _truckersmp-cli truckersmp-cli.bash

Diff for: README.md

+2-4
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ Version|AppID
124124
## Build
125125

126126
1. Clone or download this repository
127-
1. Run `make` in the main folder to build the injector executable. Bash/zsh completion files will also be generated if [genzshcomp][python:genzshcomp] is available.
127+
1. Run `make` in the main folder to build the injector executable. Bash/zsh completion files will also be generated.
128128
1. Optional run [`setup.py`][setuptools:command-reference] to manually start the installation process.
129129

130130
### Buildtime dependencies
@@ -136,13 +136,12 @@ Version|AppID
136136

137137
#### Optional
138138

139-
* [`genzshcomp`][python:genzshcomp] to generate bash/zsh completions
140139
* [`git`][repology:git] to clone this repo and help developing
141140
* [`setuptools`][repology:python-setuptools] to run `setup.py`
142141

143142
### bash/zsh completion
144143

145-
If [`genzshcomp`][python:genzshcomp] is installed, `make` generates shell completion files for bash (bash-completion) and zsh. They enable tab-completion of available command-line options.
144+
`make` generates shell completion files for bash (bash-completion) and zsh. They enable tab-completion of available positional arguments and command-line options.
146145

147146
#### System-wide installation
148147

@@ -310,7 +309,6 @@ and TheUnknownNO's unofficial [TruckersMP-Launcher][github:truckersmp-launcher].
310309
[github:proton]: https://github.com/ValveSoftware/Proton
311310
[github:release-page]: https://github.com/truckersmp-cli/truckersmp-cli/releases
312311
[github:truckersmp-launcher]: https://github.com/TheUnknownNO/TruckersMP-Launcher
313-
[python:genzshcomp]: https://github.com/hhatto/genzshcomp
314312
[python:vdf]: https://github.com/ValvePython/vdf
315313
[repology:gcc-mingw-w64]: https://repology.org/project/gcc-mingw-w64/versions
316314
[repology:git]: https://repology.org/project/git/versions

Diff for: gen_completions

+216
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
truckersmp-cli completions generator.
5+
6+
This script generates completions for bash and zsh.
7+
"""
8+
9+
import argparse
10+
import json
11+
import locale
12+
import sys
13+
from string import Template
14+
15+
from truckersmp_cli.args import ACTIONS, GAMES, create_arg_parser
16+
from truckersmp_cli.variables import AppId, File
17+
18+
19+
class CompletionTemplate(Template):
20+
"""Template class that uses "%" as delimiter."""
21+
22+
delimiter = "%"
23+
24+
25+
TMPL_BASH = CompletionTemplate(
26+
"""# bash completion for truckersmp-cli -*- shell-script -*-
27+
28+
_truckersmp_cli() {
29+
local cur prev
30+
_get_comp_words_by_ref -n : cur prev
31+
32+
if [[ "${cur}" == -* ]]; then
33+
COMPREPLY=( $(compgen -W "%{tmpl_bash_options}" -- "${cur}") )
34+
return
35+
fi
36+
37+
local diropts="%{tmpl_dir_options_regex}"
38+
if [[ "${prev}" =~ ${diropts} ]]; then
39+
local IFS=$'\\n'
40+
compopt -o filenames
41+
COMPREPLY=( $(compgen -d -- "${cur}") )
42+
return
43+
fi
44+
local fileopts="%{tmpl_file_options_regex}"
45+
if [[ "${prev}" =~ ${fileopts} ]]; then
46+
local IFS=$'\\n'
47+
compopt -o filenames
48+
COMPREPLY=( $(compgen -f -- "${cur}") )
49+
return
50+
fi
51+
52+
local games="%{tmpl_games_regex}"
53+
for ((i=1; i < ${COMP_CWORD}; i++)); do
54+
if [[ "${COMP_WORDS[i]}" =~ ${games} ]]; then
55+
COMPREPLY=()
56+
return
57+
fi
58+
done
59+
60+
local acts="%{tmpl_actions_regex}"
61+
for ((i=1; i < ${COMP_CWORD}; i++)); do
62+
if [[ "${COMP_WORDS[i]}" =~ ${acts} ]]; then
63+
COMPREPLY=( $(compgen -W "%{tmpl_games}" -- "${cur}") )
64+
return
65+
fi
66+
done
67+
68+
COMPREPLY=( $(compgen -W "%{tmpl_actions}" -- "${cur}") )
69+
} &&
70+
complete -F _truckersmp_cli truckersmp-cli
71+
""")
72+
73+
TMPL_ZSH = CompletionTemplate(
74+
"""#compdef truckersmp-cli -*- shell-script -*-
75+
# zsh completion for truckersmp-cli
76+
77+
__truckersmp_cli() {
78+
typeset -A opt_args
79+
local prev="${words[CURRENT-1]}"
80+
local diropts="%{tmpl_dir_options_regex}"
81+
local fileopts="%{tmpl_file_options_regex}"
82+
83+
if [[ "${prev}" =~ ${diropts} ]]; then
84+
_path_files -/
85+
return
86+
elif [[ "${prev}" =~ ${fileopts} ]]; then
87+
_path_files
88+
return
89+
fi
90+
91+
_arguments -s -S "1: :->action" "2: :->game" %{tmpl_zsh_options}
92+
93+
case "${state}" in
94+
action)
95+
_values action '%{tmpl_zsh_actions}'
96+
;;
97+
game)
98+
_values game '%{tmpl_zsh_games}'
99+
;;
100+
esac
101+
}
102+
103+
__truckersmp_cli
104+
""")
105+
106+
107+
def write_shell_completion_file(shellname, path, content):
108+
"""
109+
Write given content to specified completion file.
110+
111+
If path is None, this function does nothing and returns True.
112+
If it successfully writes data to the file,
113+
this function returns True.
114+
If it fails to write, it prints an error message
115+
and returns False.
116+
117+
shellname: Shell name, used in messages
118+
path: Path to output completion file
119+
content: Content of completion file
120+
"""
121+
if path is None:
122+
return True
123+
124+
try:
125+
with open(path, "w") as f_out:
126+
f_out.write(content)
127+
except OSError as ex:
128+
print(
129+
"Failed to write {} completion file {}: {}".format(
130+
shellname, path, ex),
131+
file=sys.stderr)
132+
return False
133+
134+
print("Wrote {} completion file {}".format(shellname, path))
135+
return True
136+
137+
138+
def main():
139+
"""Generate completions for bash and zsh."""
140+
try:
141+
with open(File.proton_json) as f_in:
142+
AppId.proton = json.load(f_in)
143+
except (OSError, ValueError) as ex:
144+
sys.exit("Failed to load proton.json: {}".format(ex))
145+
146+
parser = argparse.ArgumentParser(
147+
description="Shell completions generator")
148+
parser.add_argument(
149+
"-b", "--bash-completion", metavar="FILE",
150+
help="write bash completion file.")
151+
parser.add_argument(
152+
"-z", "--zsh-completion", metavar="FILE",
153+
help="write zsh completion file.")
154+
config = parser.parse_args()
155+
156+
if config.bash_completion is None and config.zsh_completion is None:
157+
return "At least --bash-completion(-b) or --zsh-completion(-z) option needed."
158+
159+
comp_data = dict(
160+
action_names=[act[0] for act in ACTIONS],
161+
bash_options=["-h --help"],
162+
dir_options=[],
163+
file_options=[],
164+
game_names=[game[0] for game in GAMES],
165+
zsh_actions=[],
166+
zsh_games=[],
167+
zsh_options=['{-h,--help}"[show help message and exit]:"', ],
168+
)
169+
for act in create_arg_parser()[1]:
170+
if act.help.startswith("**DEPRECATED** "):
171+
continue
172+
if len(act.option_strings) > 1:
173+
zsh_arg_optnames = "{" + ",".join(act.option_strings) + "}"
174+
else:
175+
zsh_arg_optnames = act.option_strings[0]
176+
for opt in act.option_strings:
177+
comp_data["bash_options"].append(opt)
178+
if opt.endswith("dir"):
179+
comp_data["dir_options"].append(opt)
180+
elif opt.endswith("file"):
181+
comp_data["file_options"].append(opt)
182+
desc = act.help.replace("[", "\\[").replace("]", "\\]").replace('"', '\\"')
183+
comp_data["zsh_options"].append(
184+
'{}"[{}]"'.format(zsh_arg_optnames, " ".join(desc.split())))
185+
for name, desc in ACTIONS:
186+
comp_data["zsh_actions"].append("{}[{}]".format(name, desc))
187+
for name, desc in GAMES:
188+
comp_data["zsh_games"].append("{}[{}]".format(name, desc))
189+
190+
return (
191+
write_shell_completion_file(
192+
"bash", config.bash_completion, TMPL_BASH.substitute(
193+
tmpl_actions=" ".join(comp_data["action_names"]),
194+
tmpl_actions_regex="|".join(comp_data["action_names"]),
195+
tmpl_bash_options=" ".join(comp_data["bash_options"]),
196+
tmpl_dir_options_regex="|".join(comp_data["dir_options"]),
197+
tmpl_file_options_regex="|".join(comp_data["file_options"]),
198+
tmpl_games=" ".join(comp_data["game_names"]),
199+
tmpl_games_regex="|".join(comp_data["game_names"]),
200+
)
201+
) is False or write_shell_completion_file(
202+
"zsh", config.zsh_completion, TMPL_ZSH.substitute(
203+
tmpl_dir_options_regex="|".join(comp_data["dir_options"]),
204+
tmpl_file_options_regex="|".join(comp_data["file_options"]),
205+
tmpl_zsh_actions="' '".join(comp_data["zsh_actions"]),
206+
tmpl_zsh_games="' '".join(comp_data["zsh_games"]),
207+
tmpl_zsh_options=" ".join(comp_data["zsh_options"]),
208+
)
209+
) is False
210+
)
211+
212+
213+
if __name__ == "__main__":
214+
locale.setlocale(locale.LC_MESSAGES, "")
215+
locale.setlocale(locale.LC_TIME, "C")
216+
sys.exit(main())

0 commit comments

Comments
 (0)