|
20 | 20 | import re |
21 | 21 | from typing import Optional, Dict, List, Tuple, Any |
22 | 22 |
|
| 23 | +from metomi.isodatetime.parsers import TimePointParser |
| 24 | +from metomi.isodatetime.exceptions import ISO8601SyntaxError |
| 25 | + |
23 | 26 | from cylc.flow import LOG |
24 | 27 | from cylc.flow.exceptions import ( |
25 | 28 | InputError, |
|
28 | 31 | from cylc.flow.id import ( |
29 | 32 | Tokens, |
30 | 33 | contains_multiple_workflows, |
| 34 | + tokenise, |
31 | 35 | upgrade_legacy_ids, |
32 | 36 | ) |
33 | 37 | from cylc.flow.pathutil import EXPLICIT_RELATIVE_PATH_REGEX |
|
43 | 47 |
|
44 | 48 |
|
45 | 49 | FN_CHARS = re.compile(r'[\*\?\[\]\!]') |
| 50 | +TP_PARSER = TimePointParser() |
| 51 | + |
| 52 | + |
| 53 | +def cli_tokenise(id_: str) -> Tokens: |
| 54 | + """Tokenise with support for long-format datetimes. |
| 55 | +
|
| 56 | + If a cycle selector is present, it could be part of a long-format |
| 57 | + ISO 8601 datetime that was erroneously split. Re-attach it if it |
| 58 | + results in a valid datetime. |
| 59 | +
|
| 60 | + Examples: |
| 61 | + >>> f = lambda t: {k: v for k, v in t.items() if v is not None} |
| 62 | + >>> f(cli_tokenise('foo//2021-01-01T00:00Z')) |
| 63 | + {'workflow': 'foo', 'cycle': '2021-01-01T00:00Z'} |
| 64 | + >>> f(cli_tokenise('foo//2021-01-01T00:horse')) |
| 65 | + {'workflow': 'foo', 'cycle': '2021-01-01T00', 'cycle_sel': 'horse'} |
| 66 | + """ |
| 67 | + tokens = tokenise(id_) |
| 68 | + cycle = tokens['cycle'] |
| 69 | + cycle_sel = tokens['cycle_sel'] |
| 70 | + if not (cycle and cycle_sel) or '-' not in cycle: |
| 71 | + return tokens |
| 72 | + cycle = f'{cycle}:{cycle_sel}' |
| 73 | + try: |
| 74 | + TP_PARSER.parse(cycle) |
| 75 | + except ISO8601SyntaxError: |
| 76 | + return tokens |
| 77 | + dict.__setitem__(tokens, 'cycle', cycle) |
| 78 | + del tokens['cycle_sel'] |
| 79 | + return tokens |
46 | 80 |
|
47 | 81 |
|
48 | 82 | def _parse_cli(*ids: str) -> List[Tokens]: |
@@ -124,14 +158,14 @@ def _parse_cli(*ids: str) -> List[Tokens]: |
124 | 158 | tokens_list: List[Tokens] = [] |
125 | 159 | for id_ in ids: |
126 | 160 | try: |
127 | | - tokens = Tokens(id_) |
| 161 | + tokens = cli_tokenise(id_) |
128 | 162 | except ValueError: |
129 | 163 | if id_.endswith('/') and not id_.endswith('//'): # noqa: SIM106 |
130 | 164 | # tolerate IDs that end in a single slash on the CLI |
131 | 165 | # (e.g. CLI auto completion) |
132 | 166 | try: |
133 | 167 | # this ID is invalid with or without the trailing slash |
134 | | - tokens = Tokens(id_[:-1]) |
| 168 | + tokens = cli_tokenise(id_[:-1]) |
135 | 169 | except ValueError: |
136 | 170 | raise InputError(f'Invalid ID: {id_}') |
137 | 171 | else: |
|
0 commit comments