Skip to content

Commit

Permalink
Merge pull request #4809 from MetRonnie/opt-parse-refactor
Browse files Browse the repository at this point in the history
Tidy & homogenise usage of `CylcOptionParser`
  • Loading branch information
hjoliver authored Apr 20, 2022
2 parents 982a206 + 5e73a64 commit 6fc565a
Show file tree
Hide file tree
Showing 46 changed files with 320 additions and 171 deletions.
59 changes: 37 additions & 22 deletions cylc/flow/option_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
setup_segregated_log_streams,
)

WORKFLOW_ID_ARG_DOC = ('WORKFLOW', 'Workflow ID')
WORKFLOW_ID_MULTI_ARG_DOC = ('WORKFLOW ...', 'Workflow ID(s)')
WORKFLOW_ID_OR_PATH_ARG_DOC = ('WORKFLOW | PATH', 'Workflow ID or path')
ID_MULTI_ARG_DOC = ('ID ...', 'Workflow/Cycle/Family/Task ID(s)')
FULL_ID_MULTI_ARG_DOC = ('ID ...', 'Cycle/Family/Task ID(s)')

icp_option = Option(
"--initial-cycle-point", "--icp",
Expand Down Expand Up @@ -227,21 +232,29 @@ def __init__(
jset: bool = False,
multitask: bool = False,
multiworkflow: bool = False,
prep: bool = False,
auto_add: bool = True,
color: bool = True,
segregated_log: bool = False
) -> None:

"""
Args:
usage: Usage instructions. Typically this will be the __doc__ of
the script module.
argdoc: The args for the command, to be inserted into the usage
instructions. Optional list of tuples of (name, description).
comms: If True, allow the --comms-timeout option.
jset: If True, allow the Jinja2 --set option.
multitask: If True, insert the multitask text into the
usage instructions.
multiworkflow: If True, insert the multiworkflow text into the
usage instructions.
auto_add: If True, allow the standard options.
color: If True, allow the --color option.
segregated_log: If False, write all logging entries to stderr.
If True, write entries at level < WARNING to stdout and
entries at level >= WARNING to stderr.
"""
self.auto_add = auto_add
if argdoc is None:
if prep:

# TODO

argdoc = [('WORKFLOW | PATH', 'Workflow ID or path')]
else:
argdoc = [('WORKFLOW', 'Workflow ID')]

if multiworkflow:
usage += self.MULTIWORKFLOW_USAGE
Expand All @@ -255,31 +268,26 @@ def __init__(
self.unlimited_args = False
self.comms = comms
self.jset = jset
self.prep = prep
self.color = color
# Whether to log messages that are below warning level to stdout
# instead of stderr:
self.segregated_log = segregated_log

maxlen = 0
for arg in argdoc:
if len(arg[0]) > maxlen:
maxlen = len(arg[0])

if argdoc:
maxlen = max(len(arg) for arg, _ in argdoc)
usage += "\n\nArguments:"
for arg in argdoc:
if arg[0].startswith('['):
for arg, descr in argdoc:
if arg.startswith('['):
self.n_optional_args += 1
else:
self.n_compulsory_args += 1
if arg[0].endswith('...]'):
if arg.rstrip(']').endswith('...'):
self.unlimited_args = True

args += arg[0] + " "
args += arg + " "

pad = (maxlen - len(arg[0])) * ' ' + ' '
usage += "\n " + arg[0] + pad + arg[1]
pad = (maxlen - len(arg)) * ' ' + ' '
usage += "\n " + arg + pad + descr
usage = usage.replace('ARGS', args)

OptionParser.__init__(
Expand Down Expand Up @@ -479,6 +487,13 @@ def parse_args(self, api_args, remove_opts=None):

return (options, args)

@staticmethod
def optional(arg: Tuple[str, str]) -> Tuple[str, str]:
"""Make an argdoc tuple display as an optional arg with
square brackets."""
name, doc = arg
return (f'[{name}]', doc)


class Options:
"""Wrapper to allow Python API access to optparse CLI functionality.
Expand Down
8 changes: 4 additions & 4 deletions cylc/flow/scheduler_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
)
from cylc.flow.network.client import WorkflowRuntimeClient
from cylc.flow.option_parsers import (
WORKFLOW_ID_ARG_DOC,
CylcOptionParser as COP,
Options,
icp_option,
Expand Down Expand Up @@ -95,8 +96,6 @@
"""


WORKFLOW_NAME_ARG_DOC = ("WORKFLOW", "Workflow name or ID")

RESUME_MUTATION = '''
mutation (
$wFlows: [WorkflowID]!
Expand All @@ -111,13 +110,14 @@


@lru_cache()
def get_option_parser(add_std_opts=False):
def get_option_parser(add_std_opts: bool = False) -> COP:
"""Parse CLI for "cylc play"."""
parser = COP(
PLAY_DOC,
jset=True,
comms=True,
argdoc=[WORKFLOW_NAME_ARG_DOC])
argdoc=[WORKFLOW_ID_ARG_DOC]
)

parser.add_option(
"-n", "--no-detach", "--non-daemon",
Expand Down
9 changes: 6 additions & 3 deletions cylc/flow/scripts/broadcast.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@
from cylc.flow.exceptions import InputError
from cylc.flow.network.client_factory import get_client
from cylc.flow.network.multi import call_multi
from cylc.flow.option_parsers import CylcOptionParser as COP
from cylc.flow.option_parsers import (
WORKFLOW_ID_MULTI_ARG_DOC,
CylcOptionParser as COP,
)
from cylc.flow.parsec.config import ParsecConfig
from cylc.flow.parsec.validate import cylc_config_validate
from cylc.flow.print_tree import get_tree
Expand Down Expand Up @@ -225,13 +228,13 @@ def report_bad_options(bad_options, is_set=False):
return bad_opts


def get_option_parser():
def get_option_parser() -> COP:
"""CLI for "cylc broadcast"."""
parser = COP(
__doc__,
comms=True,
multiworkflow=True,
argdoc=[('WORKFLOW_ID [WORKFLOW_ID ...]', 'Workflow ID(s)')],
argdoc=[WORKFLOW_ID_MULTI_ARG_DOC],
)

parser.add_option(
Expand Down
5 changes: 3 additions & 2 deletions cylc/flow/scripts/cat_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
from cylc.flow.hostuserutil import is_remote_platform
from cylc.flow.id_cli import parse_id
from cylc.flow.option_parsers import (
ID_MULTI_ARG_DOC,
CylcOptionParser as COP,
verbosity_to_opts,
)
Expand Down Expand Up @@ -219,12 +220,12 @@ def view_log(logpath, mode, tailer_tmpl, batchview_cmd=None, remote=False,
return proc.wait()


def get_option_parser():
def get_option_parser() -> COP:
"""Set up the CLI option parser."""
parser = COP(
__doc__,
argdoc=[
("ID [...]", "Workflow/Cycle/Task ID"),
ID_MULTI_ARG_DOC,
]
)

Expand Down
18 changes: 12 additions & 6 deletions cylc/flow/scripts/check_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@
from typing import TYPE_CHECKING

import cylc.flow.flags
from cylc.flow.option_parsers import CylcOptionParser as COP
from cylc.flow.option_parsers import (
WORKFLOW_ID_OR_PATH_ARG_DOC,
CylcOptionParser as COP,
)
from cylc.flow.cylc_subproc import procopen, PIPE, DEVNULL
from cylc.flow import __version__ as CYLC_VERSION
from cylc.flow.config import WorkflowConfig
Expand All @@ -54,15 +57,18 @@
def get_option_parser():
parser = COP(
__doc__,
prep=True,
jset=True,
argdoc=[('WORKFLOW_ID', 'Workflow ID or path to source')],
argdoc=[WORKFLOW_ID_OR_PATH_ARG_DOC],
)

parser.add_option(
"-e", "--error", help="Exit with error status "
"if " + CYLC_VERSION + " is not available on all remote platforms.",
action="store_true", default=False, dest="error")
"-e", "--error",
help=(
f"Exit with error status if {CYLC_VERSION} is not available "
"on all remote platforms."
),
action="store_true", default=False, dest="error"
)

return parser

Expand Down
8 changes: 6 additions & 2 deletions cylc/flow/scripts/clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@
import cylc.flow.flags
from cylc.flow.id_cli import parse_ids_async
from cylc.flow.loggingutil import disable_timestamps
from cylc.flow.option_parsers import CylcOptionParser as COP, Options
from cylc.flow.option_parsers import (
WORKFLOW_ID_MULTI_ARG_DOC,
CylcOptionParser as COP,
Options,
)
from cylc.flow.terminal import cli_function, is_terminal
from cylc.flow.workflow_files import init_clean, get_contained_workflows

Expand All @@ -78,7 +82,7 @@ def get_option_parser():
parser = COP(
__doc__,
multiworkflow=True,
argdoc=[('WORKFLOW_ID [WORKFLOW_ID ...]', 'Workflow IDs')],
argdoc=[WORKFLOW_ID_MULTI_ARG_DOC],
segregated_log=True,
)

Expand Down
15 changes: 11 additions & 4 deletions cylc/flow/scripts/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@
from typing import TYPE_CHECKING

from cylc.flow.id_cli import parse_id
from cylc.flow.option_parsers import CylcOptionParser as COP
from cylc.flow.option_parsers import (
WORKFLOW_ID_ARG_DOC,
CylcOptionParser as COP,
)
from cylc.flow.network.client import WorkflowRuntimeClient
from cylc.flow.network.server import PB_METHOD_MAP
from cylc.flow.terminal import cli_function
Expand All @@ -43,9 +46,13 @@


def get_option_parser():
parser = COP(__doc__, comms=True, argdoc=[
('WORKFLOW_ID', 'Workflow ID'),
('METHOD', 'Network API function name')])
parser = COP(
__doc__, comms=True,
argdoc=[
WORKFLOW_ID_ARG_DOC,
('METHOD', 'Network API function name')
]
)

parser.add_option(
'-n', '--no-input',
Expand Down
10 changes: 7 additions & 3 deletions cylc/flow/scripts/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@
from cylc.flow.config import WorkflowConfig
from cylc.flow.id_cli import parse_id
from cylc.flow.exceptions import InputError
from cylc.flow.option_parsers import CylcOptionParser as COP, icp_option
from cylc.flow.option_parsers import (
WORKFLOW_ID_OR_PATH_ARG_DOC,
CylcOptionParser as COP,
icp_option,
)
from cylc.flow.pathutil import get_workflow_run_dir
from cylc.flow.templatevars import get_template_vars
from cylc.flow.terminal import cli_function
Expand All @@ -66,10 +70,10 @@
from optparse import Values


def get_option_parser():
def get_option_parser() -> COP:
parser = COP(
__doc__,
argdoc=[("[WORKFLOW_ID]", "Workflow ID or path to source")],
argdoc=[COP.optional(WORKFLOW_ID_OR_PATH_ARG_DOC)],
jset=True,
)

Expand Down
8 changes: 6 additions & 2 deletions cylc/flow/scripts/cycle_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,16 @@
from metomi.isodatetime.exceptions import IsodatetimeError


def get_option_parser():
def get_option_parser() -> COP:
parser = COP(
__doc__,
color=False,
argdoc=[
('[POINT]', 'ISO8601 date-time, default=$CYLC_TASK_CYCLE_POINT')])
COP.optional(
('POINT', 'ISO8601 date-time, default=$CYLC_TASK_CYCLE_POINT')
)
]
)

parser.add_option(
"--offset-hours", metavar="HOURS",
Expand Down
15 changes: 10 additions & 5 deletions cylc/flow/scripts/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@
from typing import TYPE_CHECKING

from cylc.flow.id_cli import parse_id
from cylc.flow.option_parsers import CylcOptionParser as COP, icp_option
from cylc.flow.option_parsers import (
WORKFLOW_ID_OR_PATH_ARG_DOC,
CylcOptionParser as COP,
icp_option,
)
from cylc.flow.config import WorkflowConfig
from cylc.flow.templatevars import load_template_vars
from cylc.flow.terminal import cli_function
Expand Down Expand Up @@ -114,12 +118,13 @@ def prdict(dct, arrow='<', section='', level=0, diff=False, nested=False):
print(' ' + arrow + ' ', key, '=', dct[key])


def get_option_parser():
def get_option_parser() -> COP:
parser = COP(
__doc__, jset=True, prep=True,
__doc__,
jset=True,
argdoc=[
('WORKFLOW_ID_1', 'Workflow ID or path to source'),
('WORKFLOW_ID_2', 'Workflow ID or path to source')
(f'WORKFLOW_{n}', WORKFLOW_ID_OR_PATH_ARG_DOC[1])
for n in (1, 2)
]
)

Expand Down
7 changes: 5 additions & 2 deletions cylc/flow/scripts/dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@

from cylc.flow.exceptions import CylcError
from cylc.flow.id_cli import parse_id
from cylc.flow.option_parsers import CylcOptionParser as COP
from cylc.flow.option_parsers import (
WORKFLOW_ID_ARG_DOC,
CylcOptionParser as COP,
)
from cylc.flow.network.client_factory import get_client
from cylc.flow.terminal import cli_function

Expand Down Expand Up @@ -147,7 +150,7 @@ def get_option_parser():
parser = COP(
__doc__,
comms=True,
argdoc=[('WORKFLOW_ID', 'Workflow ID')],
argdoc=[WORKFLOW_ID_ARG_DOC],
)
parser.add_option(
"-g", "--global", help="Global information only.",
Expand Down
Loading

0 comments on commit 6fc565a

Please sign in to comment.