|
23 | 23 | Dict,
|
24 | 24 | List,
|
25 | 25 | Tuple,
|
26 |
| - Optional |
| 26 | + Optional, |
| 27 | + Union |
27 | 28 | )
|
28 | 29 |
|
29 | 30 | import cylc.flow.flags
|
@@ -85,10 +86,10 @@ class GraphParser:
|
85 | 86 | store dependencies for the whole workflow (call parse_graph multiple times
|
86 | 87 | and key results by graph section).
|
87 | 88 |
|
88 |
| - The general form of a dependency is "EXPRESSION => NODE", where: |
89 |
| - * On the right, NODE is a task or family name |
| 89 | + The general form of a dependency is "LHS => RHS", where: |
90 | 90 | * On the left, an EXPRESSION of nodes involving parentheses, and
|
91 | 91 | logical operators '&' (AND), and '|' (OR).
|
| 92 | + * On the right, an EXPRESSION of nodes NOT involving '|' |
92 | 93 | * Node names may be parameterized (any number of parameters):
|
93 | 94 | NODE<i,j,k>
|
94 | 95 | NODE<i=0,j,k> # specific parameter value
|
@@ -517,32 +518,33 @@ def _proc_dep_pair(
|
517 | 518 | "Suicide markers must be"
|
518 | 519 | f" on the right of a trigger: {left}")
|
519 | 520 |
|
| 521 | + # Check that parentheses match. |
| 522 | + mismatch_msg = 'Mismatched parentheses in: "{}"' |
| 523 | + if left and left.count("(") != left.count(")"): |
| 524 | + raise GraphParseError(mismatch_msg.format(left)) |
| 525 | + if right.count("(") != right.count(")"): |
| 526 | + raise GraphParseError(mismatch_msg.format(right)) |
| 527 | + |
520 | 528 | # Ignore cycle point offsets on the right side.
|
521 | 529 | # (Note we can't ban this; all nodes get process as left and right.)
|
522 | 530 | if '[' in right:
|
523 | 531 | return
|
524 | 532 |
|
525 |
| - # Check that parentheses match. |
526 |
| - if left and left.count("(") != left.count(")"): |
527 |
| - raise GraphParseError( |
528 |
| - "Mismatched parentheses in: \"" + left + "\"") |
529 |
| - |
530 | 533 | # Split right side on AND.
|
531 | 534 | rights = right.split(self.__class__.OP_AND)
|
532 | 535 | if '' in rights or right and not all(rights):
|
533 | 536 | raise GraphParseError(
|
534 | 537 | f"Null task name in graph: {left} => {right}")
|
535 | 538 |
|
| 539 | + lefts: Union[List[str], List[Optional[str]]] |
536 | 540 | if not left or (self.__class__.OP_OR in left or '(' in left):
|
537 |
| - # Treat conditional or bracketed expressions as a single entity. |
| 541 | + # Treat conditional or parenthesised expressions as a single entity |
538 | 542 | # Can get [None] or [""] here
|
539 |
| - lefts: List[Optional[str]] = [left] |
| 543 | + lefts = [left] |
540 | 544 | else:
|
541 | 545 | # Split non-conditional left-side expressions on AND.
|
542 | 546 | # Can get [""] here too
|
543 |
| - # TODO figure out how to handle this wih mypy: |
544 |
| - # assign List[str] to List[Optional[str]] |
545 |
| - lefts = left.split(self.__class__.OP_AND) # type: ignore |
| 547 | + lefts = left.split(self.__class__.OP_AND) |
546 | 548 | if '' in lefts or left and not all(lefts):
|
547 | 549 | raise GraphParseError(
|
548 | 550 | f"Null task name in graph: {left} => {right}")
|
@@ -847,9 +849,14 @@ def _compute_triggers(
|
847 | 849 | trigs += [f"{name}{offset}:{trigger}"]
|
848 | 850 |
|
849 | 851 | for right in rights:
|
| 852 | + right = right.strip('()') # parentheses don't matter |
850 | 853 | m = self.__class__.REC_RHS_NODE.match(right)
|
851 |
| - # This will match, bad nodes are detected earlier (type ignore): |
852 |
| - suicide_char, name, output, opt_char = m.groups() # type: ignore |
| 854 | + if not m: |
| 855 | + # Bad nodes should have been detected earlier; fail loudly |
| 856 | + raise ValueError( # pragma: no cover |
| 857 | + f"Unexpected graph expression: '{right}'" |
| 858 | + ) |
| 859 | + suicide_char, name, output, opt_char = m.groups() |
853 | 860 | suicide = (suicide_char == self.__class__.SUICIDE)
|
854 | 861 | optional = (opt_char == self.__class__.OPTIONAL)
|
855 | 862 | if output:
|
|
0 commit comments