Skip to content

Commit bf2e3c2

Browse files
cli: migrate from Bash to Python
* Convert the main Rose script from Bash to Python. * Register Python commands as entry points, hardcode Bash commands in the main Rose script. * Update centralised option help text to the more up to date help text in the sub commands.
1 parent 8c7c64f commit bf2e3c2

33 files changed

+1824
-605
lines changed

ACKNOWLEDGEMENT.md

+4
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ metomi/rosie/lib/html/static/js/livestamp.min.js
3636
metomi/rosie/lib/html/static/js/moment.min.js
3737
* Unmodified external software library released under the MIT license.
3838
See <http://momentjs.com/>
39+
40+
metomi/rose/opt_parse.py
41+
* Contains modification of the Python 3.7 optparse source released under
42+
the PSF license.

bin/rose-mpi-launch

+4
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ ROSE_COMMAND=
125125
ROSE_COMMAND_FILE=
126126
while (($# > 0)); do
127127
case $1 in
128+
--help)
129+
rose_help
130+
exit
131+
:;;
128132
--command-file=*)
129133
ROSE_COMMAND_FILE=${1#*=}
130134
shift 1

bin/rose-tutorial

+8-1
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,16 @@
2828
#
2929
# DESCRIPTION
3030
# Make a copy of one of the tutorial SUITE in the cylc-run directory.
31-
#
3231
#-------------------------------------------------------------------------------
3332
set -eu
33+
34+
if [[ $1 == '--help' ]]; then
35+
# shellcheck source=metomi/rose/etc/lib/bash/rose_usage
36+
. "$(rose resource lib/bash/rose_usage)"
37+
rose_help
38+
exit 0
39+
fi
40+
3441
mkdir -p "$HOME/cylc-run"
3542

3643
usage () {

metomi/rose/app_run.py

+36-2
Original file line numberDiff line numberDiff line change
@@ -511,10 +511,44 @@ def _command(self, conf_tree, opts, args):
511511

512512
def main():
513513
"""Launcher for the CLI."""
514-
opt_parser = RoseOptionParser()
514+
opt_parser = RoseOptionParser(
515+
usage='%prog [OPTIONS] [--] [COMMAND ...]',
516+
description='''
517+
Run an application according to its configuration.
518+
519+
May run a builtin application (if the `mode` setting in the configuration
520+
specifies the name of a builtin application) or a command.
521+
522+
Determine the command to run in this order:
523+
524+
1. If `COMMAND` is specified, invoke the command.
525+
2. If the `--command-key=KEY` option is defined, invoke the command
526+
specified in `[command]KEY`.
527+
3. If the `ROSE_APP_COMMAND_KEY` environment variable is set, the command
528+
specified in the `[command]KEY` setting in the application
529+
configuration whose `KEY` matches it is used.
530+
4. If the environment variable `ROSE_TASK_NAME` is defined and a setting
531+
in the `[command]` section has a key matching the value of the
532+
environment variable, then the value of the setting is used as the
533+
command.
534+
5. Invoke the command specified in `[command]default`.
535+
''',
536+
epilog='''
537+
ENVIRONMENT VARIABLES
538+
optional ROSE_APP_COMMAND_KEY
539+
Switch to a particular command specified in `[command]KEY`.
540+
optional ROSE_APP_MODE
541+
Specifies a builtin application to run.
542+
optional ROSE_APP_OPT_CONF_KEYS
543+
Each `KEY` in this space delimited list switches on an optional
544+
configuration. The configurations are applied first-to-last.
545+
optional ROSE_FILE_INSTALL_ROOT
546+
If specified, change to the specified directory to install files.
547+
'''
548+
)
515549
option_keys = AppRunner.OPTIONS
516550
opt_parser.add_my_options(*option_keys)
517-
opts, args = opt_parser.parse_args(sys.argv[1:])
551+
opts, args = opt_parser.parse_args()
518552
event_handler = Reporter(opts.verbosity - opts.quietness)
519553
runner = AppRunner(event_handler)
520554
try:

metomi/rose/config_cli.py

+59-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,56 @@ def get_meta_path(root_node, rel_path=None, meta_key=False):
6161

6262
def main():
6363
"""Implement the "rose config" command."""
64-
opt_parser = RoseOptionParser()
64+
opt_parser = RoseOptionParser(
65+
description='''
66+
Parse and print rose configuration files.
67+
68+
With no option and no argument, print the rose site + user configuration.
69+
70+
EXAMPLES
71+
# Print the value of OPTION in SECTION.
72+
rose config SECTION OPTION
73+
74+
# Print the value of OPTION in SECTION in FILE.
75+
rose config --file=FILE SECTION OPTION
76+
77+
# Print the value of OPTION in SECTION if exists, or VALUE otherwise.
78+
rose config --default=VALUE SECTION OPTION
79+
80+
# Print the OPTION=VALUE pairs in SECTION.
81+
rose config SECTION
82+
83+
# Print the value of a top level OPTION.
84+
rose config OPTION
85+
86+
# Print the OPTION keys in SECTION.
87+
rose config --keys SECTION
88+
89+
# Print the SECTION keys.
90+
rose config --keys
91+
92+
# Exit with 0 if OPTION exists in SECTION, or 1 otherwise.
93+
rose config -q SECTION OPTION
94+
95+
# Exit with 0 if SECTION exists, or 1 otherwise.
96+
rose config -q SECTION
97+
98+
# Combine the configurations in FILE1 and FILE2, and dump the result.
99+
rose config --file=FILE1 --file=FILE2
100+
101+
# Print the value of OPTION in SECTION of the metadata associated with
102+
# the specified config FILE
103+
rose config --file=FILE --meta SECTION OPTION
104+
105+
# Print the value of a specified metadata KEY
106+
rose config --meta-key=KEY
107+
''',
108+
epilog='''
109+
ENVIRONMENT VARIABLES
110+
optional ROSE_META_PATH
111+
Prepend `$ROSE_META_PATH` to the metadata search path.
112+
'''
113+
)
65114
opt_parser.add_my_options(
66115
"default",
67116
"env_var_process_mode",
@@ -73,9 +122,17 @@ def main():
73122
"no_opts",
74123
"print_conf_mode",
75124
)
125+
# the quietness argument is non-standard for this command
126+
opt_parser.modify_option(
127+
'quietness',
128+
help=(
129+
"Exit with 0 if the specified `SECTION` and/or `OPTION` exist in"
130+
" the configuration, or 1 otherwise."
131+
),
132+
)
133+
76134
opts, args = opt_parser.parse_args()
77135
report = Reporter(opts.verbosity - opts.quietness)
78-
79136
metomi.rose.macro.add_meta_paths()
80137

81138
if opts.meta_key:

metomi/rose/config_diff.py

+67-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,73 @@ def format_metadata_as_text(metadata, only_these_options=None):
135135

136136
def main():
137137
"""Implement the "rose config-diff" command."""
138-
opt_parser = metomi.rose.opt_parse.RoseOptionParser()
138+
opt_parser = metomi.rose.opt_parse.RoseOptionParser(
139+
description='''
140+
Display the metadata-annotated difference between two Rose config files.
141+
142+
EXAMPLES
143+
# Display the metadata-annotated diff between two Rose config files.
144+
rose config-diff FILE1 FILE2
145+
146+
# Display the metadata-annotated diff between two Rose config dirs.
147+
rose config-diff DIR1 DIR2
148+
149+
# Display the diff, ignoring particular setting patterns
150+
rose config-diff --ignore=namelist:foo FILE1 FILE2
151+
152+
# Display the diff with a particular diff tool
153+
rose config-diff --diff-tool=kdiff3 FILE1 FILE2
154+
155+
# Display the diff with some diff tool specific options/arguments
156+
rose config-diff FILE1 FILE2 -- [DIFF_OPTIONS] [DIFF_ARGUMENTS]
157+
''',
158+
epilog='''
159+
ENVIRONMENT VARIABLES
160+
optional ROSE_META_PATH
161+
Prepend `$ROSE_META_PATH` to the metadata search path.
162+
163+
ARGUMENTS
164+
PATH1, PATH2
165+
Two Rose configuration files or directories to compare.
166+
If the path is a directory, look underneath for a Rose configuration
167+
file. '-' for `PATH1` or `PATH2` denotes read in from standard input.
168+
--
169+
Options and arguments after a `--` token are passed directly to the
170+
diff tool.
171+
172+
CONFIGURATION
173+
[external]diff-tool, [external]gdiff-tool
174+
You can override the default non-graphical and graphical diff tools
175+
by setting e.g::
176+
177+
[external]
178+
diff-tool=diff3
179+
gdiff-tool=kompare
180+
181+
in your site or user Rose configuration (`rose.conf`).
182+
183+
[rose-config-diff]properties, [rose-config-diff]ignore{...}
184+
You can override the default metadata properties to display by
185+
setting e.g::
186+
187+
[rose-config-diff]
188+
properties=title,ns,description,help
189+
190+
in your site or user Rose configuration (`rose.conf`).
191+
You can also set shorthand ignore patterns by setting e.g.::
192+
193+
[rose-config-diff]
194+
ignore{foo}=namelist:bar,namelist:baz
195+
196+
in the same location. This will allow you to run::
197+
198+
rose config-diff --ignore=foo ...
199+
200+
instead of::
201+
202+
rose config-diff --ignore=namelist:bar --ignore=namelist:baz ...
203+
'''
204+
)
139205
opt_parser.add_my_options(
140206
"diff_tool",
141207
"graphical",

metomi/rose/config_dump.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,22 @@ def __str__(self):
3939

4040
def main():
4141
"""Implement the "rose config-dump" command."""
42-
opt_parser = RoseOptionParser()
42+
opt_parser = RoseOptionParser(
43+
description='''
44+
Re-dump Rose configuration files in the common format.
45+
46+
Load and dump `"rose-*.conf"` files in place. Apply format-specific
47+
pretty-printing.
48+
49+
By default, it recursively loads and dumps all `rose-*.conf` files in the
50+
current working directory.
51+
52+
EXAMPLES
53+
rose config-dump
54+
rose config-dump -C /path/to/conf/dir
55+
rose config-dump -f /path/to/file1 -f /path/to/file2
56+
'''
57+
)
4358
opt_parser.add_my_options("conf_dir", "files", "no_pretty_mode")
4459
opts = opt_parser.parse_args()[0]
4560
verbosity = opts.verbosity - opts.quietness

bin/rose-date metomi/rose/date_cli.py

+7-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
#!/usr/bin/env python3
2-
# -----------------------------------------------------------------------------
31
# Copyright (C) British Crown (Met Office) & Contributors.
4-
#
52
# This file is part of Rose, a framework for meteorological suites.
63
#
74
# Rose is free software: you can redistribute it and/or modify
@@ -187,6 +184,13 @@ def main():
187184
file=sys.stderr
188185
)
189186

187+
if '--help' in sys.argv:
188+
print('\n' + __doc__)
189+
sys.exit()
190+
191+
# strip the rose end of the CLI args
192+
sys.argv = sys.argv[2:]
193+
190194
# Handle Legacy Rose-date -c functionality
191195
if '-c' in sys.argv or '--use-task-cycle-time' in sys.argv:
192196
if os.getenv('ROSE_TASK_CYCLE_TIME'):
@@ -206,10 +210,3 @@ def main():
206210
os.environ['ISODATETIMECALENDAR'] = os.getenv('ROSE_CYCLING_MODE')
207211

208212
sys.exit(iso_main())
209-
210-
211-
if __name__ == '__main__':
212-
if '--help' in sys.argv:
213-
print(__doc__)
214-
else:
215-
main()

metomi/rose/env_cat.py

+27-1
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,34 @@
2525

2626
def main():
2727
"""Implement "rose env-cat"."""
28-
opt_parser = RoseOptionParser()
28+
opt_parser = RoseOptionParser(
29+
usage='rose env-cat [OPTIONS] [FILE ...]',
30+
description=r'''
31+
Substitute environment variables in input files and print.
32+
33+
If no argument is specified, read from STDIN. One `FILE` argument may be
34+
`-`, which means read from STDIN.
35+
36+
In `match-mode=default`, the command will look for `$NAME` or `${NAME}`
37+
syntax and substitute them with the value of the environment variable
38+
`NAME`. A backslash in front of the syntax, e.g. `\$NAME` or `\${NAME}`
39+
will escape the substitution.
40+
41+
In `match-mode=brace`, the command will look for `${NAME}` syntax only.
42+
43+
EXAMPLES
44+
rose env-cat [OPTIONS] [FILE ...]
45+
'''
46+
)
2947
opt_parser.add_my_options("match_mode", "output_file", "unbound")
48+
opt_parser.modify_option(
49+
'output_file',
50+
help=(
51+
"Specify an output file."
52+
"\nIf no output file is specified or if `FILE`"
53+
"is `-`, write output to STDOUT."
54+
),
55+
)
3056
opts, args = opt_parser.parse_args()
3157
if not args:
3258
args = ["-"]

metomi/rose/etc/lib/bash/rose_usage

+14
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@
2929
# formatted like this one. If CODE is specified, exits the current shell
3030
# with it. If CODE is 0, prints the usage to STDOUT. If code is not 0,
3131
# prints the usage to STDERR.
32+
#
33+
# NAME
34+
# rose_help
35+
#
36+
# SYNOPSIS
37+
# rose_help
38+
#
39+
# DESCRIPTION
40+
# Prints help information extracted from the header of $0.
41+
# Suitable for use with the --help option.
3242
#-------------------------------------------------------------------------------
3343
function rose_usage() {
3444
local CODE=${1:-}
@@ -50,3 +60,7 @@ function rose_usage() {
5060
exit "$CODE"
5161
fi
5262
}
63+
64+
function rose_help() {
65+
sed -n 's/^# //; s/^#//; /NAME/,/------/p' "$0" | head -n -1
66+
}

0 commit comments

Comments
 (0)