Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cylc Message Help Contains two patterns of use #5921

Draft
wants to merge 1 commit into
base: 8.3.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 40 additions & 16 deletions cylc/flow/option_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,8 @@ class CylcOptionParser(OptionParser):
)
]

LINEBREAK = ('\n', '')

def __init__(
self,
usage: str,
Expand Down Expand Up @@ -407,7 +409,6 @@ def __init__(
if multitask:
usage += self.MULTITASK_USAGE

args = ""
self.n_compulsory_args = 0
self.n_optional_args = 0
self.unlimited_args = False
Expand All @@ -419,21 +420,7 @@ def __init__(
self.segregated_log = segregated_log

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

args += arg + " "

pad = (maxlen - len(arg)) * ' ' + ' '
usage += "\n " + arg + pad + descr
usage = usage.replace('ARGS', args)
usage = self.argdoc_parser(argdoc, usage)

OptionParser.__init__(
self,
Expand All @@ -442,6 +429,43 @@ def __init__(
formatter=CylcHelpFormatter()
)

def argdoc_parser(self, argdoc, usage) -> str:
"""Convert argdoc list into a string to be passed to
OptionParser.__init__ as usage argument.
"""
args = ""
maxlen = max(len(arg) for arg, _ in argdoc)
usage += "\n\nArguments:"
if 'ARGS' in usage:
script_name, usage = usage.split('ARGS', 1)
else:
return usage
used_args = [self.LINEBREAK]
if self.LINEBREAK in argdoc:
args += ' One of:\n'
args += script_name
for arg, descr in argdoc:
if arg.startswith('['):
self.n_optional_args += 1
else:
self.n_compulsory_args += 1
if arg.rstrip(']').endswith('...'):
self.unlimited_args = True

if arg == '\n':
args += arg
args += script_name
else:
args += arg + " "

if (arg, descr) not in used_args:
pad = (maxlen - len(arg)) * ' ' + ' '
usage += "\n " + arg + pad + descr
used_args.append((arg, descr))

usage = args + usage
return usage

def get_std_options(self):
"""Get a data-structure of standard options"""
opts = []
Expand Down
11 changes: 6 additions & 5 deletions cylc/flow/scripts/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,14 @@ def get_option_parser() -> COP:
__doc__,
comms=True,
argdoc=[
COP.optional(WORKFLOW_ID_ARG_DOC),
WORKFLOW_ID_ARG_DOC,
('JOB', 'Job ID - CYCLE/TASK_NAME/SUBMIT_NUM'),
COP.optional(
('JOB', 'Job ID - CYCLE/TASK_NAME/SUBMIT_NUM')
('[SEVERITY:]MESSAGE ...', 'Severity Level:Message')
),
COP.optional(
('[SEVERITY:]MESSAGE ...', 'Messages')
)
COP.LINEBREAK,
COP.optional(('[SEVERITY:]MESSAGE', 'Severity Level:Message')),
COP.optional(('[SEVERITY:]MESSAGE', 'Severity Level:Message')),
]
)

Expand Down
71 changes: 69 additions & 2 deletions tests/unit/test_option_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,20 @@
)


USAGE_WITH_COMMENT = "usage \n # comment"
USAGE_WITH_COMMENT = "ARGS usage \n # comment"
ARGS = 'args'
KWARGS = 'kwargs'
SOURCES = 'sources'
USEIF = 'useif'


class MockCylcOptionParser(COP):
"""A stub subclass to allow unit-like testing of methods."""
def __init__(self, **kwargs):
for key, value in kwargs.items():
self.__dict__[key] = value


@pytest.fixture(scope='module')
def parser():
return COP(
Expand Down Expand Up @@ -94,7 +101,9 @@ def test_help_nocolor(monkeypatch: pytest.MonkeyPatch, parser: COP):
f = io.StringIO()
with redirect_stdout(f):
parser.print_help()
assert (f.getvalue()).startswith("Usage: " + USAGE_WITH_COMMENT)
str_ = f.getvalue()
assert str_.startswith(
"Usage: " + USAGE_WITH_COMMENT.replace('ARGS', 'SOME_ARG '))


def test_Options_std_opts():
Expand Down Expand Up @@ -580,3 +589,61 @@ def test__in_list():
second = OptionSettings(['--foo'])
third = OptionSettings(['--bar'])
assert first._in_list([second, third]) is True

@pytest.mark.parametrize(
'argdoc, usage, expect',
(
param(
[('WORKFLOW', 'Workflow ID')],
'cylc play [OPTIONS] ARGS\n\nStart, resume...',
enumerate(('cylc play [OPTIONS] WORKFLOW',)),
id='basic'
),
param(
[
('WORKFLOW', 'Workflow ID'),
COP.LINEBREAK,
('[TASK]', 'Task ID'),
],
'cylc total-perspective [OPTIONS] ARGS\n\nThe most savage...',
enumerate([
'One of:',
'cylc total-perspective [OPTIONS] WORKFLOW',
'cylc total-perspective [OPTIONS] [TASK]'
]),
id='has-line-break'
),
param(
[('[TASK]', 'Task ID')],
'cylc infinite-improbability [OPTIONS] ARGS\n\nEvery point...',
enumerate(['cylc infinite-improbability [OPTIONS] [TASK]',]),
id='startswith-sq-bracket',
),
param(
[
('WORKFLOW', 'Workflow ID'),
COP.LINEBREAK,
('WORKFLOW', 'Workflow ID'),
],
'cylc skin-cat [OPTIONS] ARGS\n\nThere are many ways.',
{
-2: 'Arguments:',
-1: 'WORKFLOW Workflow ID'
}.items(),
id='no-repeated-arg-descriptions'
)
)
)
def test_argdoc_parser(self, argdoc, usage, expect):
"""Tests for standalone argdoc_parser method when argdoc
offers multiple ways to call a command.
"""
parser = MockCylcOptionParser(
n_compulsory_args=0,
n_optional_args=0,
unlimited_args=False)

result = parser.argdoc_parser(argdoc, usage)
results = [r for r in result.split('\n') if r]
for i, line in expect:
assert results[i].strip() == line
Loading