Skip to content

Commit a731a40

Browse files
authored
Merge pull request #60 from matejak/manpages
Enable generation of manpages
2 parents 3c80fc3 + 2e2ec04 commit a731a40

20 files changed

+432
-107
lines changed

ChangeLog

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ New features:
66
* Allow argbash and argbash-init to be run from symbolic links.
77
* Allow scripts generated by argbash-init with complete separation (`-s -s`) to be run from a symbolic link.
88
* Double quotes in help messages are escaped (fixes #61).
9+
* Introduced the `ARG_VERSION_AUTO` macro.
10+
* Enabled manpage output consumable by the `rst2man` utility.
911

1012

1113
2.7.1 (2018-08-15)

bin/argbash

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,22 @@
44
# SC2016: Expressions don't expand in single quotes, use double quotes for that.
55
# SC2059 Don't use variables in the printf format string.
66

7-
version=2.7.1
8-
97

108
# DEFINE_SCRIPT_DIR()
119
# ARG_POSITIONAL_SINGLE([input],[The input template file (pass '-' for stdin)])
1210
# ARG_OPTIONAL_SINGLE([output],[o],[Name of the output file (pass '-' for stdout)],[-])
1311
# ARG_OPTIONAL_SINGLE([type],[t],[Output type to generate],[bash-script])
14-
# ARG_OPTIONAL_BOOLEAN([library],[],[Whether the input file if the pure parsing library.])
15-
# ARG_OPTIONAL_SINGLE([strip],[],[Determines what to have in the output.],[none])
12+
# ARG_OPTIONAL_BOOLEAN([library],[],[Whether the input file if the pure parsing library])
13+
# ARG_OPTIONAL_SINGLE([strip],[],[Determines what to have in the output],[none])
1614
# ARG_OPTIONAL_BOOLEAN([check-typos],[],[Whether to check for possible argbash macro typos],[on])
1715
# ARG_OPTIONAL_BOOLEAN([commented],[c],[Commented mode - include explanatory comments with the parsing code],[off])
1816
# ARG_OPTIONAL_REPEATED([search],[I],[Directories to search for the wrapped scripts (directory of the template will be added to the end of the list)],["."])
1917
# ARG_OPTIONAL_SINGLE([debug],[],[(developer option) Tell autom4te to trace a macro])
2018
# ARG_TYPE_GROUP_SET([content],[content],[strip],[none,user-content,all])
21-
# ARG_TYPE_GROUP_SET([type],[type],[type],[bash-script,posix-script,completion,docopt])
19+
# ARG_TYPE_GROUP_SET([type],[type],[type],[bash-script,posix-script,manpage,manpage-defs,completion,docopt])
2220
# ARG_DEFAULTS_POS([])
23-
# ARG_VERSION([echo "argbash v$version"])
2421
# ARG_HELP([Argbash is an argument parser generator for Bash.])
22+
# ARG_VERSION_AUTO([_ARGBASH_VERSION])
2523

2624
# ARGBASH_GO()
2725
# needed because of Argbash --> m4_ignore([
@@ -54,18 +52,18 @@ content()
5452

5553
type()
5654
{
57-
local _allowed=("bash-script" "posix-script" "completion" "docopt") _seeking="$1"
55+
local _allowed=("bash-script" "posix-script" "manpage" "manpage-defs" "completion" "docopt") _seeking="$1"
5856
for element in "${_allowed[@]}"
5957
do
6058
test "$element" = "$_seeking" && echo "$element" && return 0
6159
done
62-
die "Value '$_seeking' (of argument '$2') doesn't match the list of allowed values: 'bash-script', 'posix-script', 'completion' and 'docopt'" 4
60+
die "Value '$_seeking' (of argument '$2') doesn't match the list of allowed values: 'bash-script', 'posix-script', 'manpage', 'manpage-defs', 'completion' and 'docopt'" 4
6361
}
6462

6563

6664
begins_with_short_option()
6765
{
68-
local first_option all_short_options='otcIvh'
66+
local first_option all_short_options='otcIhv'
6967
first_option="${1:0:1}"
7068
test "$all_short_options" = "${all_short_options/$first_option/}" && return 1 || return 0
7169
}
@@ -87,20 +85,20 @@ _arg_debug=
8785
print_help()
8886
{
8987
printf '%s\n' "Argbash is an argument parser generator for Bash."
90-
printf 'Usage: %s [-o|--output <arg>] [-t|--type <type>] [--(no-)library] [--strip <content>] [--(no-)check-typos] [-c|--(no-)commented] [-I|--search <arg>] [--debug <arg>] [-v|--version] [-h|--help] <input>\n' "$0"
88+
printf 'Usage: %s [-o|--output <arg>] [-t|--type <type>] [--(no-)library] [--strip <content>] [--(no-)check-typos] [-c|--(no-)commented] [-I|--search <arg>] [--debug <arg>] [-h|--help] [-v|--version] <input>\n' "$0"
9189
printf '\t%s\n' "<input>: The input template file (pass '-' for stdin)"
9290
printf '\t%s\n' "-o, --output: Name of the output file (pass '-' for stdout) (default: '-')"
9391
printf '\t%s\n' "-t, --type: Output type to generate (default: 'bash-script')"
94-
printf '\t%s\n' "--library, --no-library: Whether the input file if the pure parsing library. (off by default)"
95-
printf '\t%s\n' "--strip: Determines what to have in the output. (default: 'none')"
92+
printf '\t%s\n' "--library, --no-library: Whether the input file if the pure parsing library (off by default)"
93+
printf '\t%s\n' "--strip: Determines what to have in the output (default: 'none')"
9694
printf '\t%s\n' "--check-typos, --no-check-typos: Whether to check for possible argbash macro typos (on by default)"
9795
printf '\t%s\n' "-c, --commented, --no-commented: Commented mode - include explanatory comments with the parsing code (off by default)"
9896
printf '\t%s' "-I, --search: Directories to search for the wrapped scripts (directory of the template will be added to the end of the list) (default array elements:"
9997
printf " '%s'" "."
10098
printf ')\n'
10199
printf '\t%s\n' "--debug: (developer option) Tell autom4te to trace a macro (no default)"
102-
printf '\t%s\n' "-v, --version: Prints version"
103100
printf '\t%s\n' "-h, --help: Prints help"
101+
printf '\t%s\n' "-v, --version: Prints version"
104102
}
105103

106104

@@ -180,14 +178,6 @@ parse_commandline()
180178
--debug=*)
181179
_arg_debug="${_key##--debug=}"
182180
;;
183-
-v|--version)
184-
echo "argbash v$version"
185-
exit 0
186-
;;
187-
-v*)
188-
echo "argbash v$version"
189-
exit 0
190-
;;
191181
-h|--help)
192182
print_help
193183
exit 0
@@ -196,6 +186,14 @@ parse_commandline()
196186
print_help
197187
exit 0
198188
;;
189+
-v|--version)
190+
printf '%s %s\n\n%s\n' "argbash" "2.7.1" 'Argbash is an argument parser generator for Bash.'
191+
exit 0
192+
;;
193+
-v*)
194+
printf '%s %s\n\n%s\n' "argbash" "2.7.1" 'Argbash is an argument parser generator for Bash.'
195+
exit 0
196+
;;
199197
*)
200198
_last_positional="$1"
201199
_positionals+=("$_last_positional")
@@ -295,14 +293,15 @@ interpret_error()
295293
# $2: The original intended output file
296294
define_file_metadata()
297295
{
298-
local _defines='' _input_dirname _output_dirname
296+
local _defines='' _intended_destination="$ARGBASH_INTENDED_DESTINATION" _input_dirname _output_dirname
297+
test -n "$_intended_destination" || _intended_destination="$2"
299298

300299
_input_dirname="$(dirname "$1")"
301300
test "$1" != '-' && _defines="${_defines}m4_define([INPUT_BASENAME], [[$(basename "$1")]])"
302301
_defines="${_defines}m4_define([INPUT_ABS_DIRNAME], [[$(cd "$_input_dirname" && pwd)]])"
303302

304-
_output_dirname="$(dirname "$2")"
305-
test "$2" != '-' && _defines="${_defines}m4_define([OUTPUT_BASENAME], [[$(basename "$2")]])"
303+
_output_dirname="$(dirname "$_intended_destination")"
304+
test "$_intended_destination" != '-' && _defines="${_defines}m4_define([OUTPUT_BASENAME], [[$(basename "$_intended_destination" "$SPURIONS_OUTPUT_SUFFIX")]])"
306305
_defines="${_defines}m4_define([OUTPUT_ABS_DIRNAME], [[$(cd "$_output_dirname" && pwd)]])"
307306
printf "%s" "$_defines"
308307
}
@@ -461,7 +460,7 @@ then
461460
# match against suspicious, then inverse match against correct stuff:
462461
# #<optional whitespace>\(allowed\|another allowed\|...\)<optional whitespace><opening bracket <or> end of line>
463462
# Then, extract all matches (assumed to be alnum chars + '_') from grep and put them in the error msg.
464-
grep_output="$(printf "%s" "$output" | grep '^#\s*\(ARG_\|ARGBASH\)' | grep -v '^#\s*\(ARGBASH_SET_INDENT\|ARG_OPTIONAL_SINGLE\|ARG_VERSION\|ARG_HELP\|ARG_OPTIONAL_INCREMENTAL\|ARG_OPTIONAL_REPEATED\|ARG_VERBOSE\|ARG_OPTIONAL_BOOLEAN\|ARG_OPTIONAL_ACTION\|ARG_POSITIONAL_SINGLE\|ARG_POSITIONAL_INF\|ARG_POSITIONAL_MULTI\|ARG_POSITIONAL_DOUBLEDASH\|ARG_OPTION_STACKING\|ARG_RESTRICT_VALUES\|ARG_DEFAULTS_POS\|ARG_LEFTOVERS\|ARGBASH_WRAP\|INCLUDE_PARSING_CODE\|DEFINE_SCRIPT_DIR\|ARGBASH_SET_DELIM\|ARGBASH_GO\|ARGBASH_PREPARE\|ARG_TYPE_GROUP\|ARG_TYPE_GROUP_SET\|ARG_USE_ENV\|ARG_USE_PROG\)\s*\((\|$\)' | sed -e 's/#\s*\([[:alnum:]_]*\).*/\1 /' | tr -d '\n\r')"
463+
grep_output="$(printf "%s" "$output" | grep '^#\s*\(ARG_\|ARGBASH\)' | grep -v '^#\s*\(ARGBASH_SET_INDENT\|ARG_OPTIONAL_SINGLE\|ARG_VERSION\|ARG_VERSION_AUTO\|ARG_HELP\|ARG_OPTIONAL_INCREMENTAL\|ARG_OPTIONAL_REPEATED\|ARG_VERBOSE\|ARG_OPTIONAL_BOOLEAN\|ARG_OPTIONAL_ACTION\|ARG_POSITIONAL_SINGLE\|ARG_POSITIONAL_INF\|ARG_POSITIONAL_MULTI\|ARG_POSITIONAL_DOUBLEDASH\|ARG_OPTION_STACKING\|ARG_RESTRICT_VALUES\|ARG_DEFAULTS_POS\|ARG_LEFTOVERS\|ARGBASH_WRAP\|INCLUDE_PARSING_CODE\|DEFINE_SCRIPT_DIR\|ARGBASH_SET_DELIM\|ARGBASH_GO\|ARGBASH_PREPARE\|ARG_TYPE_GROUP\|ARG_TYPE_GROUP_SET\|ARG_USE_ENV\|ARG_USE_PROG\)\s*\((\|$\)' | sed -e 's/#\s*\([[:alnum:]_]*\).*/\1 /' | tr -d '\n\r')"
465464
test -n "$grep_output" && die "Your script contains possible misspelled Argbash macros: $grep_output" 1
466465
fi
467466
if test "$outfname" != '-'

doc/Makefile.pieces

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ STATICDIR = _static
1818
NUL =
1919

2020
PIECES = \
21+
$(STATICDIR)/argbash-version.txt \
2122
$(STATICDIR)/index_script-create.txt \
2223
$(STATICDIR)/index_script-help.txt \
2324
$(STATICDIR)/index_script-output.txt \
@@ -46,7 +47,11 @@ define CMD_OUT_LOCAL
4647
./$< $(FLAGS) >> $@
4748
endef
4849

50+
$(STATICDIR)/argbash-version.txt:
51+
$(AB) --version > "$@"
52+
4953
A_INIT_ARGS = --pos positional-arg --opt option --opt-bool print
54+
5055
$(STATICDIR)/minimal_init-create.txt: $(AB_INIT)
5156
$(eval FLAGS := $(A_INIT_ARGS) $(MINIMAL_INIT_CREATE))
5257
$(CMD_OUT)

doc/_static/argbash-version.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
argbash 2.7.1
2+
3+
Argbash is an argument parser generator for Bash.

doc/_static/wrapper-output-action.txt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,24 @@ Contents of '../src' matching '*.m4':
66
argbash-lib.m4: 0 kiB
77
argbash.m4: 10 kiB
88
argument_value_types.m4: 5 kiB
9-
collectors.m4: 18 kiB
9+
collectors.m4: 20 kiB
1010
constants.m4: 0 kiB
1111
default_settings.m4: 0 kiB
12+
docopt.m4: 2 kiB
1213
env_vars.m4: 1 kiB
1314
function_generators.m4: 7 kiB
1415
list.m4: 5 kiB
1516
output-bash-script.m4: 1 kiB
1617
output-completion.m4: 4 kiB
17-
output-docopt.m4: 2 kiB
18+
output-docopt.m4: 0 kiB
19+
output-manpage.m4: 1 kiB
1820
output-posix-script.m4: 3 kiB
1921
output-strip-all.m4: 0 kiB
2022
output-strip-none.m4: 0 kiB
2123
output-strip-user-content.m4: 0 kiB
2224
progs.m4: 2 kiB
2325
stuff.m4: 44 kiB
24-
utilities.m4: 9 kiB
26+
utilities.m4: 10 kiB
2527
value_validators.m4: 5 kiB
2628
Contents of '../resources/examples' matching '*.m4':
2729
minimal.m4: 0 kiB

doc/guide.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,23 @@ Special arguments
255255

256256
ARG_VERSION([code to execute when specified])
257257

258+
* Enhanced version argument (a special case of an action argument):
259+
::
260+
261+
ARG_VERSION_AUTO([version number or macro containing it])
262+
263+
The macro will take it's first argument, expands it, and treats it as a version number.
264+
This allows you to use a quoted macro containing the version number as the first argument.
265+
Then, it attempts to detect the basename of the generated script and outputs a version message out of those two.
266+
267+
If the ``ARG_HELP([MSG], ...)`` macro has been used before, it also outputs the ``MSG`` below the program name --- version pair.
268+
269+
For example, for argbashm, it yields
270+
271+
.. literalinclude:: _static/argbash-version.txt
272+
:language: text
273+
274+
258275
* Verbose argument (a special case of a repeated argument):
259276
::
260277

doc/usage.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@ POSIX script posix-script none
246246
POSIX script parsing section posix-script user-content
247247
Bash completion completion all
248248
docopt help message docopt all
249+
manpage template manpage all
250+
manpage template definitions manpage-defs all
249251
============================ ======================= ==========================
250252

251253

@@ -296,6 +298,7 @@ Typically, you generate bash completion ``my-script.sh`` from the generated scri
296298
297299
and you move the created completion file ``my-script.sh`` to ``/etc/bash_completion.d/`` directory.
298300

301+
299302
.. _docopt_output:
300303

301304
Docopt help message
@@ -315,6 +318,27 @@ Typically, you generate docopt output to the standard output from the generated
315318
$ argbash my-script --type docopt --strip all
316319
317320
321+
Manpage output
322+
++++++++++++++
323+
324+
Argbash can generate source for the manual page for your script.
325+
There are two files in the process --- the template, and definitions.
326+
Those two files are in the `reStructuredText <http://docutils.sourceforge.net/rst.html>`_ format, and the template is supposed to be processed by the `rst2man <http://docutils.sourceforge.net/sandbox/manpage-writer/rst2man.txt>`_ utility.
327+
328+
The manpage template is supposed to be generated as script's metadata change, definitions are required to be maintained manually, as they are supposed to contain content that is not present in the script.
329+
You can regenerate the template using the ``manpage`` output, while you are probably going to use the ``manpage-defs`` once to get you kickstarted and then continue to maintain it manually.
330+
331+
So given a argbash-powered script or m4 file, your manpage workflow will typically look like this:
332+
333+
.. code-block:: shell-session
334+
335+
$ argbash my-script --type manpage-defs --strip all -o my-script-defs.rst
336+
$ argbash my-script --type manpage --strip all -o my-script.rst
337+
$ vim my-script-defs.rst # Edit the definitions file
338+
$ rst2man my-script.rst > my-script.1
339+
$ man ./my-script.1
340+
341+
318342
.. _api_change:
319343

320344
API changes support

resources/Makefile

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ M4_SRC = \
1414
$(NUL)
1515

1616
M4_SRC += \
17+
../src/output-bash-script.m4 \
1718
../src/output-completion.m4 \
1819
../src/output-docopt.m4 \
19-
../src/output-bash-script.m4 \
20+
../src/output-manpage.m4 \
21+
../src/output-manpage-defs.m4 \
2022
../src/output-posix-script.m4 \
2123
$(NUL)
2224

@@ -28,6 +30,7 @@ M4_SRC += \
2830
../src/collectors.m4 \
2931
../src/constants.m4 \
3032
../src/default_settings.m4 \
33+
../src/docopt.m4 \
3134
../src/env_vars.m4 \
3235
../src/function_generators.m4 \
3336
../src/list.m4 \
@@ -56,9 +59,10 @@ EXAMPLES = \
5659
$(NUL)
5760

5861
ROOT_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
62+
MANPAGE = argbash.1.gz
5963

6064
$(GENPARSE): ../src/argbash.m4 $(M4_SRC)
61-
bash $(GENPARSE) $< -o argbash.temp && mv argbash.temp $@
65+
ARGBASH_INTENDED_DESTINATION="$@" bash $(GENPARSE) $< -o argbash.temp && mv argbash.temp $@
6266

6367
$(A_INIT): ../src/argbash-init.m4 $(GENPARSE)
6468
$(GENPARSE) $< -o $@
@@ -143,7 +147,15 @@ define-version:
143147
$(eval VERSION_MAJOR := $(shell cut -f 1 -d . ../src/version))
144148
$(eval VERSION_SUFFIX := -$(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH))
145149

146-
install: $(GENPARSE) $(A_INIT) $(ARGBASH_TO) $(COMPLETION)
150+
argbash.rst: ../src/argbash.m4 $(ARGBASH_EXEC)
151+
$(ARGBASH_EXEC) --type manpage --strip all -o $@ $<
152+
153+
man: $(MANPAGE)
154+
155+
$(MANPAGE): argbash.rst argbash-defs.rst
156+
rst2man $< | gzip > $@
157+
158+
install: $(GENPARSE) $(A_INIT) $(ARGBASH_TO) $(COMPLETION) $(MANPAGE)
147159
@echo Installing to prefix "'/$(PREFIX)' of root $(ROOT)"
148160
mkdir -p "$(ROOT)/$(PREFIX)/bin"
149161
touch "$(ROOT)/$(PREFIX)/bin/argbash$(VERSION_SUFFIX)"
@@ -155,6 +167,8 @@ install: $(GENPARSE) $(A_INIT) $(ARGBASH_TO) $(COMPLETION)
155167
cp -p $(A_INIT) "$(ROOT)/$(PREFIX)/bin/argbash-init$(VERSION_SUFFIX)" && chmod a+x "$(ROOT)/$(PREFIX)"/bin/argbash-init$(VERSION_SUFFIX)
156168
chmod a+x "$(ROOT)/$(PREFIX)/bin/argbash$(VERSION_SUFFIX)"
157169
test "$(INSTALL_COMPLETION)" = "no" || (mkdir -p "$(ROOT)/$(BASH_COMPLETION_DIRECTORY)" && mv "$(COMPLETION)" "$(ROOT)/$(BASH_COMPLETION_DIRECTORY)/")
170+
mkdir -p "$(ROOT)/$(PREFIX)/share/man/man1/"
171+
cp -p $(MANPAGE) "$(ROOT)/$(PREFIX)/share/man/man1/"
158172

159173
altpreclean: define-version
160174
$(RM) "$(ROOT)/$(PREFIX)/bin/argbash-$(VERSION_MAJOR).$(VERSION_MINOR)"
@@ -173,6 +187,7 @@ uninstall:
173187
$(RM) "$(ROOT)/$(PREFIX)"/bin/argbash-*
174188
rmdir "$(ROOT)/$(PREFIXED_LIBDIR)/argbash$(VERSION_SUFFIX)"
175189
$(RM) "$(ROOT)/$(BASH_COMPLETION_DIRECTORY)/$(COMPLETION)"
190+
$(RM) "$(ROOT)/$(PREFIX)/share/man/man1/$(MANPAGE)"
176191

177192
altuninstall: define-version uninstall
178193

@@ -196,4 +211,4 @@ tag:
196211

197212
# Update using
198213
# grep '^[-a-z]*:' Makefile | cut -f 1 -d ':' | sort | tr '\n' ' '
199-
.PHONY: altinstall altpreclean bootstrap check check-shellcheck define-version develop examples install release tag uninstall unittests version
214+
.PHONY: altinstall altpreclean bootstrap check check-shellcheck define-version develop examples install man release tag uninstall unittests version

resources/argbash-defs.rst

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
2+
.. |AUTHOR| replace:: Matěj Týč
3+
4+
.. |MAN_SECTION| replace:: 1
5+
6+
.. |DESCRIPTION| replace::
7+
Argbash is a code generator that typically generates a bash argument-parsing library tailor-made for your script.
8+
It lets you to describe arguments your script should take and then, you can generate the ``bash`` parsing code.
9+
It stays in your script by default, but you can have it generated to a separate file and let ``bash`` to include it in your script for you.
10+
``Argbash`` is very simple to use and the generated code is relatively nice to read.
11+
Moreover, argument definitions stay embedded in the script, so when you need to update the parsing logic, you just re-run the ``argbash`` script on the already generated script.
12+
13+
.. |OPTION_OUTPUT| replace:: \
14+
15+
.. |OPTION_TYPE| replace:: Check out the documentation to learn about all argbash capabilities that are supported.
16+
17+
.. |OPTION_LIBRARY| replace:: This option is deprecated and it is the same as ``--strip user-content``.
18+
19+
.. |OPTION_STRIP| replace::
20+
You can either strip ``none``, which is useful for scripts.
21+
If you strip ``user-content``, you keep the Argbash header.
22+
If you strip ``all``, you will have only the generated content in the result.
23+
24+
.. |OPTION_CHECK_TYPOS| replace:: \
25+
26+
.. |OPTION_COMMENTED| replace:: \
27+
28+
.. |OPTION_SEARCH| replace:: \
29+
30+
.. |OPTION_DEBUG| replace:: \
31+
32+
.. |OPTION_HELP| replace:: \
33+
34+
.. |OPTION_VERSION| replace:: \

src/argbash-lib.m4

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,8 @@ m4_include([collectors.m4])
1212
m4_include([stuff.m4])
1313
m4_include([default_settings.m4])
1414
INCLUDE_ACCORDING_TO_OUTPUT_TYPE(_OUTPUT_TYPE)
15+
16+
dnl These macros that are being undefined are not needed and they present a security threat when exposed during Argbash run
17+
m4_undefine([m4_esyscmd])
18+
m4_undefine([m4_esyscmd_s])
19+
m4_undefine([m4_syscmd])

0 commit comments

Comments
 (0)