File size: 1,900 Bytes
d7c4dd5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
"""
Free-form command parser for the CI/CD Doctor environment.
Converts raw command strings into structured ParsedCommand objects.
"""

import re
from dataclasses import dataclass
from typing import Optional


@dataclass
class ParsedCommand:
    type: str  # "cat" | "echo_append" | "sed" | "pipeline_run" | "pipeline_logs" | "pipeline_status" | "unknown"
    filename: Optional[str] = None
    content: Optional[str] = None    # for echo >>
    pattern: Optional[str] = None    # for sed: old value
    replacement: Optional[str] = None  # for sed: new value
    stage: Optional[str] = None


def parse_command(command: str) -> ParsedCommand:
    command = command.strip()

    for sep in ["&&", ";"]:
        if sep in command:
            command = command.split(sep)[0].strip()
            break

    m = re.match(r"cat\s+(.+)", command)
    if m:
        return ParsedCommand(type="cat", filename=m.group(1).strip())

    m = re.match(r'echo\s+([\'"])(.*?)\1\s*>>\s*(\S+)', command, re.DOTALL)
    if m:
        return ParsedCommand(
            type="echo_append",
            content=m.group(2),
            filename=m.group(3),
        )

    # Example supported: sed -i 's/old/new/g' file OR sed -i "s|old|new|" file
    m = re.match(
        r"sed\s+-i\s+([\'\"]?)s(.)(.+?)\2(.*?)\2([g]*)\1\s+(\S+)",
        command,
    )
    if m:
        return ParsedCommand(
            type="sed",
            pattern=m.group(3),
            replacement=m.group(4),
            filename=m.group(6),
        )

    if re.fullmatch(r"pipeline\s+run", command):
        return ParsedCommand(type="pipeline_run")

    m = re.match(r"pipeline\s+logs(?:\s+(\S+))?\s*$", command)
    if m:
        return ParsedCommand(type="pipeline_logs", stage=m.group(1))

    if re.fullmatch(r"pipeline\s+status", command):
        return ParsedCommand(type="pipeline_status")

    return ParsedCommand(type="unknown")