|
| 1 | +from typing import Optional, Sequence |
| 2 | +from cedarscript_ast_parser import CaseStatement, CaseWhen, CaseAction, LoopControl |
| 3 | + |
| 4 | + |
| 5 | +# <dt>case_stmt: CASE WHEN (EMPTY | REGEX r"<string>" | PREFIX "<string>" | SUFFIX "<string>" | INDENT LEVEL <integer> | LINE NUMBER <integer> ) \ |
| 6 | +# THEN (CONTINUE | BREAK | REMOVE [BREAK] | INDENT <integer> [BREAK] | REPLACE r"<string>" [BREAK] | <content_literal> [BREAK] | <content_from_segment> [BREAK])</dt> |
| 7 | +# <dd>This is the versatile `WHEN..THEN` content filter. Only used in conjunction with <replace_region_clause>. \ |
| 8 | +# Filters each line of the region according to `WHEN/THEN` pairs:</dd> |
| 9 | +# <dd>WHEN: Allows you to choose which *matcher* to use:</dd> |
| 10 | +# <dd>EMPTY: Matches an empty line</dd> |
| 11 | +# <dd>REGEX: Regex matcher. Allows using capture groups in the `REPLACE` action</dd> |
| 12 | +# <dd>PREFIX: Matches by line prefix</dd> |
| 13 | +# <dd>SUFFIX: Matches by line suffix</dd> |
| 14 | +# <dd>INDENT LEVEL: Matches lines with specific indent level</dd> |
| 15 | +# <dd>LINE NUMBER: Matches by line number</dd> |
| 16 | +# <dd>THEN: Allows you to choose which *action* to take for its matched line:</dd> |
| 17 | +# <dd>CONTINUE: Leaves the line as is and goes to the next</dd> |
| 18 | +# <dd>BREAK: Stops processing the lines, leaving the rest of the lines untouched</dd> |
| 19 | +# <dd>REMOVE: Removes the line</dd> |
| 20 | +# <dd>INDENT: Increases or decreases indent level. Only positive or negative integers</dd> |
| 21 | +# <dd>REPLACE: Replace with text (regex capture groups enabled: \\1, \\2, etc)</dd> |
| 22 | +# <dd><content_literal> or <content_from_segment>: Replace with text (can't use regex capture groups)</dd> |
| 23 | +# <dt> |
| 24 | + |
| 25 | + |
| 26 | +def process_case_statement(content: Sequence[str], case_statement: CaseStatement) -> list[str]: |
| 27 | + """Process content lines according to CASE statement rules. |
| 28 | + |
| 29 | + Args: |
| 30 | + content: Sequence of strings to process |
| 31 | + case_statement: CaseStatement containing when/then rules |
| 32 | + |
| 33 | + Returns: |
| 34 | + List of processed strings |
| 35 | + """ |
| 36 | + result = [] |
| 37 | + |
| 38 | + for line_num, line in enumerate(content, start=1): |
| 39 | + indent_level = (len(line) - len(line.lstrip())) // 4 |
| 40 | + matched = False |
| 41 | + |
| 42 | + # Process each when/then pair |
| 43 | + for when, action in case_statement.cases: |
| 44 | + if _matches_when(line, when, indent_level, line_num): |
| 45 | + matched = True |
| 46 | + processed = _apply_action(line, action, indent_level, when) |
| 47 | + |
| 48 | + if processed is None: # REMOVE action |
| 49 | + break |
| 50 | + if isinstance(processed, LoopControl): |
| 51 | + if processed == LoopControl.BREAK: |
| 52 | + result.append(line) |
| 53 | + result.extend(content[line_num:]) |
| 54 | + return result |
| 55 | + elif processed == LoopControl.CONTINUE: |
| 56 | + result.append(line) |
| 57 | + break |
| 58 | + else: |
| 59 | + result.append(processed) |
| 60 | + break |
| 61 | + |
| 62 | + # If no when conditions matched, use else action if present |
| 63 | + if not matched and case_statement.else_action is not None: |
| 64 | + processed = _apply_action(line, case_statement.else_action, indent_level, None) |
| 65 | + if processed is not None and not isinstance(processed, LoopControl): |
| 66 | + result.append(processed) |
| 67 | + elif not matched: |
| 68 | + result.append(line) |
| 69 | + |
| 70 | + return result |
| 71 | + |
| 72 | +def _matches_when(line: str, when: CaseWhen, indent_level: int, line_num: int) -> bool: |
| 73 | + """Check if a line matches the given when condition.""" |
| 74 | + stripped = line.strip() |
| 75 | + if when.empty and not stripped: |
| 76 | + return True |
| 77 | + if when.regex and when.regex.search(stripped): |
| 78 | + return True |
| 79 | + if when.prefix and stripped.startswith(when.prefix): |
| 80 | + return True |
| 81 | + if when.suffix and stripped.endswith(when.suffix): |
| 82 | + return True |
| 83 | + if when.indent_level is not None and indent_level == when.indent_level: |
| 84 | + return True |
| 85 | + if when.line_number is not None and line_num == when.line_number: |
| 86 | + return True |
| 87 | + return False |
| 88 | + |
| 89 | + |
| 90 | +def _apply_action(line: str, action: CaseAction, current_indent: int, when: CaseWhen) -> Optional[str | LoopControl]: |
| 91 | + """Apply the given action to a line. |
| 92 | + |
| 93 | + Returns: |
| 94 | + - None for REMOVE action |
| 95 | + - LoopControl enum for BREAK/CONTINUE |
| 96 | + - Modified string for other actions |
| 97 | + """ |
| 98 | + if action.loop_control: |
| 99 | + return action.loop_control |
| 100 | + if action.remove: |
| 101 | + return None |
| 102 | + if action.indent is not None: |
| 103 | + new_indent = current_indent + action.indent |
| 104 | + if new_indent < 0: |
| 105 | + new_indent = 0 |
| 106 | + return " " * (new_indent * 4) + line.lstrip() |
| 107 | + if action.sub_pattern is not None: |
| 108 | + line = action.sub_pattern.sub(action.sub_repl, line) |
| 109 | + if action.content is not None: |
| 110 | + if isinstance(action.content, str): |
| 111 | + # TODO |
| 112 | + return " " * (current_indent * 4) + action.content |
| 113 | + else: |
| 114 | + region, indent = action.content |
| 115 | + # TODO Handle region content replacement - would need region processing logic |
| 116 | + return line |
| 117 | + return line |
0 commit comments