-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathlines
executable file
·95 lines (76 loc) · 2.7 KB
/
lines
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/usr/bin/env python3
# "Lines Is Not Exactly Sed"
# Sed-inspired line processor using modern Python regex and format syntax
import re
import sys
### Main ###
def main(args):
# For now, the only args are command strings
operations = [parse_command(c) for c in args[1:]]
# print(f"{operations = }") # TODO: some sort of debug flag/mode
parse_lines(operations, sys.stdin)
def parse_lines(processors, lines):
for n,l in enumerate(lines, start=1):
context = {"Line": l, "LineNum": n}
for p in processors:
l, context = p(l, context)
if l is None:
break
else:
print(l, end="") # REM: consider yielding lines, so `main` is the one dealing with files and output
#####
### Command Parsing ###
CMD_BASE_RE = re.compile(r"(?P<cmd>[a-z]+)(?P<sep>[^a-z])(?P<rest>.*)")
def parse_command(cmd_str):
m = CMD_BASE_RE.match(cmd_str)
if not m:
raise ValueError(f"Could not parse command '{cmd_str}'")
cmd, sep, rest = (m.group("cmd"), m.group("sep"), m.group("rest"))
if cmd not in CMD_MAP:
raise ValueError(f"Unrecognized command '{cmd}'")
split_rest = rest.split(sep)
args = split_rest[:-1]
opts = split_rest[-1]
# print(f"{cmd = }, {sep = }, {args = }, {opts = }")
Op = CMD_MAP[cmd]
return Op(*args, opts)
#####
### Operation Classes ###
class Filter:
def __init__(self, pattern, options):
# TODO: Add options like invert and re.IGNORECASE
self.pattern = re.compile(pattern)
def __call__(self, line, context):
m = self.pattern.match(line)
if m:
# TODO: mark context as matched (or something?)
context.update(m.groupdict()) # Match groups can be used in later transformers
return line, context
return None, context
def __repr__(self):
return f"<Filter({self.pattern}) at {id(self)}>"
class Transformer:
def __init__(self, pattern, fmt, options):
# TODO: Add options like pass/drop and re.IGNORECASE
self.pattern = re.compile(pattern)
self.fmt = fmt
def __call__(self, line, context):
m = self.pattern.match(line)
if m:
# TODO: mark context as matched (or something?)
context.update(m.groupdict())
return self.fmt.format(**context) + "\n", context # FIXME: use subn to replace all matches
return None, context
def __repr__(self):
return f"<Transformer({self.pattern}, {self.fmt}) at {id(self)}>"
CMD_MAP = {
"f": Filter,
"filter": Filter,
"t": Transformer,
"trans": Transformer,
"transform": Transformer,
}
#####
if __name__ == '__main__':
_xit = main(sys.argv)
sys.exit(_xit)