Skip to content

Commit d37a3f3

Browse files
committed
fix(param_reorder): Correct update old_filenames fields
1 parent 0c99800 commit d37a3f3

1 file changed

Lines changed: 97 additions & 61 deletions

File tree

param_reorder.py

Lines changed: 97 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import json
1818
import logging
1919
import os
20-
import re
2120
import shutil
2221
import subprocess
2322
from typing import Optional
@@ -113,7 +112,7 @@ def reorder_param_files(steps: dict) -> dict[str, str]:
113112
return renames
114113

115114

116-
def loop_relevant_files(renames: dict[str, str], steps: dict) -> list[str]:
115+
def loop_relevant_files(renames: dict[str, str]) -> list[str]:
117116
param_dirs = ["."]
118117
# Search all *.py, *.json and *.md files in the current directory
119118
# and replace all occurrences of the old names with the new names
@@ -125,76 +124,109 @@ def loop_relevant_files(renames: dict[str, str], steps: dict) -> list[str]:
125124
continue
126125
if file == "vehicle_components.json":
127126
continue
128-
if file == SEQUENCE_FILENAME:
129-
uplate_old_filenames(renames, steps)
130127
if file in PYTHON_FILES or file.endswith((".md", ".json")):
131-
update_file_contents(renames, root, file, steps)
128+
update_file_contents(renames, root, file)
132129
return param_dirs
133130

134131

135-
def uplate_old_filenames(renames: dict[str, str], steps: dict) -> None:
136-
for new_name, old_name in renames.items():
137-
if old_name != new_name:
138-
if "old_filenames" in steps[old_name]:
139-
if old_name not in steps[old_name]["old_filenames"]:
140-
steps[old_name]["old_filenames"].append(old_name)
141-
else:
142-
steps[old_name]["old_filenames"] = [old_name]
143-
144-
145-
def update_file_contents(renames: dict[str, str], root: str, file: str, steps: dict) -> None:
132+
def update_file_contents(renames: dict[str, str], root: str, file: str) -> None:
146133
with open(os.path.join(root, file), encoding="utf-8", newline="") as handle:
147134
file_content = handle.read()
148-
if file.startswith("TUNING_GUIDE_") and file.endswith(".md"):
149-
for old_filename in renames.values():
150-
if old_filename not in file_content:
151-
msg = f"The intermediate parameter file '{old_filename}' is not mentioned in the {file} file"
152-
logging.error(msg)
153-
for new_name, old_name in renames.items():
154-
if "configuration_steps" in file and file.endswith(".json"):
155-
file_content = update_configuration_steps_json_file_contents(steps, file_content, new_name, old_name)
156-
else:
135+
if "configuration_steps" in file and file.endswith(".json"):
136+
# Use renames (current JSON key -> new key) so that historical names already
137+
# stored in old_filenames are never accidentally overwritten.
138+
for new_name, old_name in renames.items():
139+
file_content = file_content.replace(old_name, new_name)
140+
else:
141+
if file.startswith("TUNING_GUIDE_") and file.endswith(".md"):
142+
for old_filename in file_renames:
143+
if old_filename not in file_content:
144+
msg = f"The intermediate parameter file '{old_filename}' is not mentioned in the {file} file"
145+
logging.error(msg)
146+
for old_name, new_name in file_renames.items():
157147
file_content = file_content.replace(old_name, new_name)
158148
with open(os.path.join(root, file), "w", encoding="utf-8", newline="") as handle:
159149
handle.write(file_content)
160150

161151

162-
def update_configuration_steps_json_file_contents(steps: dict, file_content: str, new_name: str, old_name: str) -> str:
163-
new_file_content = ""
164-
curr_filename = ""
165-
in_steps_block = False
152+
def _parse_old_filenames_from_line(line: str) -> list[str]:
153+
"""Extract filename strings from an old_filenames JSON line."""
154+
bracket_start = line.index("[")
155+
bracket_end = line.rindex("]")
156+
text = line[bracket_start + 1 : bracket_end].strip()
157+
return [v.strip().strip('"') for v in text.split(",")] if text else []
166158

167-
for line in file_content.splitlines(keepends=True):
168-
# Track when we enter/exit the "steps" block
169-
if '"steps": {' in line:
170-
in_steps_block = True
171-
new_file_content += line
172-
continue
173159

174-
# Only process filenames when inside steps block
175-
if in_steps_block:
176-
re_search = re.search(r"^ \"(\w.+)\"", line)
177-
if re_search:
178-
curr_filename = re_search.group(1)
179-
180-
if "old_filenames" in line:
181-
if curr_filename in steps and "old_filenames" in steps[curr_filename]:
182-
# WARNING!!! old_filenames can only used once, so we remove it after using it
183-
old_filenames = str(steps[curr_filename].pop("old_filenames")).replace("'", '"')
184-
new_file_content += f' "old_filenames": {old_filenames}'
185-
if line.endswith(",\n"):
186-
new_file_content += ","
187-
new_file_content += "\n"
188-
else:
189-
new_file_content += line
190-
else:
191-
new_file_content += line.replace(old_name, new_name)
160+
def _format_old_filenames_line(indent: str, values: list[str], trailing_comma: bool) -> str:
161+
"""Format an old_filenames JSON line."""
162+
values_str = ", ".join(f'"{v}"' for v in values)
163+
return f'{indent}"old_filenames": [{values_str}]{"," if trailing_comma else ""}\n'
192164

193-
# Track end of steps block
194-
if in_steps_block and line.strip() == "}," and curr_filename:
195-
in_steps_block = False
196165

197-
return new_file_content
166+
def update_old_filenames_in_json_file(json_path: str) -> None:
167+
"""
168+
Update old_filenames fields in the JSON file using targeted text replacement.
169+
170+
Must be called after loop_relevant_files so the JSON already has the new step keys.
171+
Reads the file as text to avoid reformatting. Uses brace counting to correctly
172+
identify step block boundaries and deduplicates multiple old_filenames entries.
173+
"""
174+
with open(json_path, encoding="utf-8", newline="") as f:
175+
lines = f.readlines()
176+
177+
needs_update: dict[str, list[str]] = {}
178+
for old_name, new_name in file_renames.items():
179+
if old_name != new_name:
180+
needs_update.setdefault(new_name, []).append(old_name)
181+
182+
new_lines: list[str] = []
183+
i = 0
184+
while i < len(lines):
185+
line = lines[i]
186+
# Step keys have exactly 8 spaces of indentation inside "steps": { ... }
187+
if not (line.startswith(' "') and line.rstrip().endswith('": {')):
188+
new_lines.append(line)
189+
i += 1
190+
continue
191+
stripped = line.strip()
192+
step_key = stripped[1 : stripped.index('"', 1)]
193+
if step_key not in needs_update:
194+
new_lines.append(line)
195+
i += 1
196+
continue
197+
# Collect all lines for this step using brace counting
198+
step_lines: list[str] = [line]
199+
i += 1
200+
depth = 1
201+
while i < len(lines) and depth > 0:
202+
depth += lines[i].count("{") - lines[i].count("}")
203+
step_lines.append(lines[i])
204+
i += 1
205+
# Merge all old_filenames entries (existing + new names) into one
206+
of_indices = [k for k, sl in enumerate(step_lines) if sl.strip().startswith('"old_filenames"')]
207+
merged: list[str] = []
208+
for k in of_indices:
209+
for v in _parse_old_filenames_from_line(step_lines[k]):
210+
if v not in merged:
211+
merged.append(v)
212+
for name in needs_update[step_key]:
213+
if name not in merged:
214+
merged.append(name)
215+
if of_indices:
216+
first = of_indices[0]
217+
first_line = step_lines[first]
218+
indent = first_line[: len(first_line) - len(first_line.lstrip())]
219+
step_lines[first] = _format_old_filenames_line(indent, merged, first_line.rstrip().endswith(","))
220+
for k in reversed(of_indices[1:]):
221+
del step_lines[k]
222+
else:
223+
ref = step_lines[1] if len(step_lines) > 1 else ""
224+
indent = ref[: len(ref) - len(ref.lstrip())] if ref.strip() else " "
225+
step_lines.insert(1, _format_old_filenames_line(indent, merged, trailing_comma=True))
226+
new_lines.extend(step_lines)
227+
228+
with open(json_path, "w", encoding="utf-8", newline="") as f:
229+
f.writelines(new_lines)
198230

199231

200232
def _git_executable() -> Optional[str]:
@@ -238,18 +270,20 @@ def rename_file(old_name: str, new_name: str, param_dir: str) -> None:
238270
if git_executable and _is_git_tracked(old_name_path):
239271
try:
240272
subprocess.run( # noqa: S603
241-
[git_executable, "mv", "--", old_name_path, new_name_path],
273+
[git_executable, "mv", "-f", "--", old_name_path, new_name_path],
242274
check=True,
243275
capture_output=True,
244276
text=True,
245277
)
246278
return
247279
except subprocess.CalledProcessError as exc:
280+
stderr = exc.stderr.strip() if exc.stderr else ""
248281
logging.warning(
249-
"git mv failed for %s -> %s, falling back to os.rename: %s",
282+
"git mv failed for %s -> %s, falling back to os.rename: %s%s",
250283
old_name_path,
251284
new_name_path,
252285
exc,
286+
f"\n stderr: {stderr}" if stderr else "",
253287
)
254288
os.rename(old_name_path, new_name_path)
255289

@@ -285,12 +319,14 @@ def change_line_endings_for_md_files() -> None:
285319

286320
def main() -> None:
287321
logging.basicConfig(level="INFO", format="%(asctime)s - %(levelname)s - %(message)s")
288-
with open(os.path.join("ardupilot_methodic_configurator", SEQUENCE_FILENAME), encoding="utf-8") as f:
322+
json_path = os.path.join("ardupilot_methodic_configurator", SEQUENCE_FILENAME)
323+
with open(json_path, encoding="utf-8") as f:
289324
json_content = json.load(f)
290325
steps = json_content["steps"]
291326
renames = reorder_param_files(steps)
292-
param_dirs = loop_relevant_files(renames, steps)
327+
param_dirs = loop_relevant_files(renames)
293328
reorder_actual_files(renames, param_dirs)
329+
update_old_filenames_in_json_file(json_path)
294330
# change_line_endings_for_md_files()
295331

296332

0 commit comments

Comments
 (0)