Skip to content

Commit 7bf2fa9

Browse files
mikekgfbmalfet
authored andcommitted
Improve updown parsing (#683)
* improve updown parser, and use in README.md execution * cut/paste errors * typo: true -> false * we scan each partial line, so need to suppress at partial line level :( * make it twice as nice * improved updown parsing * special handling for lines w/o option * typo
1 parent 8d68984 commit 7bf2fa9

File tree

1 file changed

+207
-100
lines changed

1 file changed

+207
-100
lines changed

scripts/updown.py

Lines changed: 207 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
import re
44

55

6+
###############################################################################################
7+
###
8+
### print, with the ability to replace strings, or suppress lines
9+
###
10+
11+
612
def output(*args, **kwargs):
713
"""
814
Prints the given arguments after performing replacements and suppressions.
@@ -41,21 +47,142 @@ def output(*args, **kwargs):
4147
print(*str_args, file=file, end=end)
4248

4349

44-
def command_regexp(command):
45-
"""
46-
Processes a file based on the given predicates, replacements, and suppressions.
47-
Args:
48-
filename (str): The name of the file to process.
49-
predicate_list (list): A list of predicates to match in the file.
50-
replace_list (list): A list of tuples where the first element of each tuple is replaced with the second element in the output.
51-
suppress_list (list): A list of strings. If any string in this list is found in the output, the line is not printed.
52-
Returns:
53-
None
54-
"""
55-
return rf"^\[\s*{command}\s+(\w+)\s*\]\s*:\s*(.*)"
50+
###############################################################################################
51+
###
52+
### processing logic for optional argument in lines
53+
###
54+
55+
56+
def remove_text_between_brackets(text):
57+
return re.sub(r"\[.*?\]", "", text)
58+
59+
60+
def extract_text_between_brackets(text):
61+
return re.findall(r"\[(.*?)\]", text)
62+
63+
64+
def specialize_option(text, replacement):
65+
return re.sub(r"\[.*?\]", replacement, text)
66+
67+
68+
###############################################################################################
69+
###
70+
### process a line either suppressing or expanding options
71+
###
72+
73+
74+
def updown_process_line(
75+
line, lineno, filename, replace_list, suppress_list, expand_options
76+
):
77+
if not expand_options:
78+
output(
79+
remove_text_between_brackets(line),
80+
replace_list=replace_list,
81+
suppress_list=suppress_list,
82+
)
83+
else:
84+
options = extract_text_between_brackets(line)
85+
if len(options) == 0:
86+
output(
87+
line,
88+
replace_list=replace_list,
89+
suppress_list=suppress_list,
90+
)
91+
return
92+
if len(options) > 1:
93+
output(
94+
"echo 'cross product of options not yet supported anot line {line} of {filename}'\nexit 1",
95+
suppress_list=None,
96+
replace_list=None,
97+
)
98+
exit(1)
99+
for option in options[0].split("|"):
100+
output(
101+
specialize_option(line, option),
102+
replace_list=replace_list,
103+
suppress_list=suppress_list,
104+
)
105+
56106

107+
###############################################################################################
108+
###
109+
### process an updown command
110+
###
57111

58-
def updown_processor(filename, predicate_list, replace_list, suppress_list):
112+
113+
def process_command(
114+
line, lineno, filename, predicate_list, replace_list, suppress_list
115+
) -> bool:
116+
117+
command = r"^\[\s*(\w+)\s+(\w+)\s*\]\s*:\s*(.*)"
118+
match = re.search(command, line)
119+
120+
if not match:
121+
# We have not processed this line as a command
122+
return False
123+
124+
keyword = match.group(1)
125+
predicate = match.group(2)
126+
trailing_command = match.group(3)
127+
128+
if predicate not in predicate_list:
129+
# We have processed this line as a command
130+
return True
131+
132+
if keyword == "shell":
133+
output(
134+
trailing_command,
135+
replace_list=replace_list,
136+
suppress_list=suppress_list,
137+
)
138+
elif keyword == "prefix":
139+
output(
140+
trailing_command[:-1],
141+
end="",
142+
replace_list=replace_list,
143+
suppress_list=suppress_list,
144+
)
145+
elif keyword == "skip":
146+
if trailing_command == "begin":
147+
output(
148+
"if false; then",
149+
replace_list=replace_list,
150+
suppress_list=suppress_list,
151+
)
152+
elif trailing_command == "end":
153+
output(
154+
"fi",
155+
replace_list=replace_list,
156+
suppress_list=suppress_list,
157+
)
158+
else:
159+
output(
160+
f"echo 'error in line {lineno} of {filename}'\nexit 1;",
161+
suppress_list=None,
162+
replace_list=None,
163+
)
164+
exit(1)
165+
elif keyword == "end":
166+
output(
167+
"exit 0",
168+
replace_list=replace_list,
169+
suppress_list=suppress_list,
170+
)
171+
exit(0)
172+
173+
# We have processed this line as a command
174+
return True
175+
176+
177+
###############################################################################################
178+
###
179+
### updown processing of the input file
180+
###
181+
182+
183+
def updown_processor(
184+
filename, predicate_list, replace_list, suppress_list, expand_options
185+
):
59186
"""
60187
Processes a file based on the given predicates, replacements, and suppressions.
61188
Args:
@@ -69,72 +196,34 @@ def updown_processor(filename, predicate_list, replace_list, suppress_list):
69196
with open(filename, "r") as file:
70197
lines = file.readlines()
71198
print_flag = False
199+
72200
output("set -eou pipefail", replace_list=None, suppress_list=None)
73-
for i, line in enumerate(lines):
74-
shell = command_regexp("shell")
75-
prefix = command_regexp("prefix")
76-
skip = command_regexp("skip")
77-
end = command_regexp("end")
78-
if match := re.search(shell, line):
79-
# Extract the matched groups
80-
predicate = match.group(1)
81-
trailing_command = match.group(2)
82-
if predicate in predicate_list:
83-
output(
84-
trailing_command,
85-
replace_list=replace_list,
86-
suppress_list=suppress_list,
87-
)
88-
elif match := re.search(prefix, line):
89-
# Extract the matched groups
90-
predicate = match.group(1)
91-
trailing_command = match.group(2)
92-
if predicate in predicate_list:
93-
output(
94-
trailing_command,
95-
end="",
96-
replace_list=replace_list,
97-
suppress_list=suppress_list,
98-
)
99-
elif match := re.search(skip, line):
100-
# Extract the matched groups
101-
predicate = match.group(1)
102-
trailing_command = match.group(2)
103-
if predicate in predicate_list:
104-
if trailing_command == "begin":
105-
output(
106-
"if false; then",
107-
replace_list=replace_list,
108-
suppress_list=suppress_list,
109-
)
110-
elif trailing_command == "end":
111-
output(
112-
"fi",
113-
replace_list=replace_list,
114-
suppress_list=suppress_list,
115-
)
116-
else:
117-
output(
118-
f"echo 'error in line {i} of {filename}'\nexit 1;",
119-
suppress_list=None,
120-
replace_list=None,
121-
)
122-
exit(1)
123-
elif match := re.search(end, line):
124-
# Extract the matched groups
125-
predicate = match.group(1)
126-
trailing_command = match.group(2)
127-
if predicate in predicate_list:
128-
output(
129-
"exit 0",
130-
replace_list=replace_list,
131-
suppress_list=suppress_list,
132-
)
133-
return
201+
202+
for lineno, line in enumerate(lines):
203+
# clip trailing newline
204+
if line.endswith("\n"):
205+
line = line[:-1]
206+
207+
if process_command(
208+
line=line,
209+
lineno=lineno,
210+
filename=filename,
211+
predicate_list=predicate_list,
212+
replace_list=replace_list,
213+
suppress_list=suppress_list,
214+
):
215+
pass
134216
elif line.startswith("```"):
135217
print_flag = not print_flag
136218
elif print_flag:
137-
output(line, end="", replace_list=replace_list, suppress_list=suppress_list)
219+
updown_process_line(
220+
line=line,
221+
lineno=lineno,
222+
filename=filename,
223+
expand_options=expand_options,
224+
replace_list=replace_list,
225+
suppress_list=suppress_list,
226+
)
138227

139228
output(
140229
"echo 'reached end of file without exit command'\nexit 1;",
@@ -143,28 +232,46 @@ def updown_processor(filename, predicate_list, replace_list, suppress_list):
143232
)
144233

145234

146-
# Initialize the ArgumentParser object
147-
parser = argparse.ArgumentParser()
148-
# Add arguments
149-
parser.add_argument("-f", "--filename", help="Input filename", default="README.md")
150-
parser.add_argument("-p", "--predicate", help="Input predicates", default="")
151-
parser.add_argument("-r", "--replace", help="Input replace pairs", default="")
152-
parser.add_argument("-s", "--suppress", help="Input suppress strings", default="")
153-
args = parser.parse_args()
154-
# Get filename
155-
filename = args.filename
156-
# Check if file exists
157-
if not os.path.isfile(filename):
158-
output(f"echo 'File {filename} does not exist.'\n exit 1;")
159-
exit(1)
160-
# Get predicates, split by comma, and add "default"
161-
predicate_list = args.predicate.split(",") if args.predicate else []
162-
predicate_list.append("default")
163-
# Get replace pairs, split by comma, and turn into a list of tuples
164-
replace_list = (
165-
[tuple(pair.split(":")) for pair in args.replace.split(",")] if args.replace else []
166-
)
167-
# Get suppress strings, split by comma
168-
suppress_list = args.suppress.split(",") if args.suppress else []
169-
# Call updown_processor function
170-
updown_processor(filename, predicate_list, replace_list, suppress_list)
235+
###############################################################################################
236+
###
237+
### updown processing of the input file
238+
###
239+
240+
241+
def main():
242+
# Initialize the ArgumentParser object
243+
parser = argparse.ArgumentParser()
244+
# Add arguments
245+
parser.add_argument("-f", "--filename", help="Input filename", default="README.md")
246+
parser.add_argument("-p", "--predicate", help="Input predicates", default="")
247+
parser.add_argument("-r", "--replace", help="Input replace pairs", default="")
248+
parser.add_argument("-s", "--suppress", help="Input suppress strings", default="")
249+
parser.add_argument(
250+
"-e", "--expand-options", action="store_true", help="Expand options flag"
251+
)
252+
args = parser.parse_args()
253+
# Get filename
254+
filename = args.filename
255+
# Check if file exists
256+
if not os.path.isfile(filename):
257+
output(f"echo 'File {filename} does not exist.'\n exit 1;")
258+
exit(1)
259+
# Get predicates, split by comma, and add "default"
260+
predicate_list = args.predicate.split(",") if args.predicate else []
261+
predicate_list.append("default")
262+
# Get replace pairs, split by comma, and turn into a list of tuples
263+
replace_list = (
264+
[tuple(pair.split(":")) for pair in args.replace.split(",")]
265+
if args.replace
266+
else []
267+
)
268+
# Get suppress strings, split by comma
269+
suppress_list = args.suppress.split(",") if args.suppress else []
270+
# Call updown_processor function
271+
updown_processor(
272+
filename, predicate_list, replace_list, suppress_list, args.expand_options
273+
)
274+
275+
276+
if __name__ == "__main__":
277+
main()

0 commit comments

Comments
 (0)