Skip to content

Commit 42ef2ad

Browse files
committed
dev: First working version of the refactor script
1 parent a6784ed commit 42ef2ad

File tree

1 file changed

+247
-15
lines changed

1 file changed

+247
-15
lines changed

development/scripts/misc/refactor_headers.py

100644100755
Lines changed: 247 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
import re
77
import argparse
8+
import subprocess
9+
10+
from pathlib import Path
811

912
# Walk in a directory
1013
# Find all xxx.hpp
@@ -56,6 +59,13 @@
5659
// ...
5760
#endif // ifndef _pinocchio_python_spatial_se3i_hpp__"""
5861

62+
HPP_MODULE_TPL_GUARD = """
63+
/// Explicit template instantiation
64+
#if PINOCCHIO_ENABLE_TEMPLATE_INSTANTIATION
65+
#include "{module_tpl}"
66+
#endif // PINOCCHIO_ENABLE_TEMPLATE_INSTANTIATION
67+
"""
68+
5969
HPP_MODULE = """//
6070
// Copyright (c) 2025 INRIA
6171
//
@@ -69,6 +79,8 @@
6979
// Module headers
7080
{module_includes}
7181
82+
{module_tpl_include}
83+
7284
#endif // ifndef {guard}
7385
"""
7486

@@ -138,36 +150,43 @@ def remove_includes(content: str, module_header: str) -> (str, list[str]):
138150
#ifndef __pinocchio_python_spatial_se3_hpp__
139151
#define __pinocchio_python_spatial_se3_hpp__
140152
<BLANKLINE>
141-
<BLANKLINE>
142153
#ifdef PINOCCHIO_LSP
143154
#include "pinocchio/bindings/python/spatial/se3.hpp"
144155
#endif // PINOCCHIO_LSP
156+
<BLANKLINE>
145157
// ...
146158
#endif // ifndef __pinocchio_python_spatial_se3_hpp__
147159
>>> print(res[1])
148160
['<eigenpy/eigenpy.hpp>', '<boost/python/tuple.hpp>', '"pinocchio/spatial/se3.hpp"', '"pinocchio/spatial/explog.hpp"']
149161
"""
150-
# TODO: warning if 0 includes, LSP_GUARD will not be added
151162
includes = INCLUDE_PATTERN.findall(content)
152-
new_content = INCLUDE_PATTERN.sub("", content, count=len(includes) - 1)
153-
new_content2 = INCLUDE_PATTERN.sub(
154-
LSP_GUARD.format(module_hpp=module_header), new_content
163+
if len(includes) == 0:
164+
print("\tNo include, PINOCCHIO_LSP will not be added")
165+
tmp_pattern = "$$$$$ BLBLBBLLB #####"
166+
new_content = INCLUDE_PATTERN.sub(tmp_pattern, content)
167+
new_content2 = new_content.replace(
168+
tmp_pattern, LSP_GUARD.format(module_hpp=module_header), 1
155169
)
156-
# (new_content2, _) = INCLUDE_PATTERN.subn("", new_content)
157-
return new_content2, includes
170+
new_content3 = new_content2.replace(tmp_pattern, "")
171+
return new_content3, includes
158172

159173

160174
def create_hpp_module(
161-
guard: str, dependencies_includes: list[str], module_includes: list[str]
175+
guard: str,
176+
dependencies_includes: list[str],
177+
module_includes: list[str],
178+
module_tpl_include: str | None,
162179
) -> str:
163180
"""Create a module content.
164181
:ivar guard: Guard name.
165182
:ivar dependencies_includes: Module dependencies include paths (path with < or ").
166-
:ivar module_includes: Module internal include paths (path with < or ").
183+
:ivar module_includes: Module internal include paths (path without < or ").
184+
:ivar module_tpl: Module explicit template instantiation (path without < or ").
167185
:return: Module content.
168186
>>> res = create_hpp_module("__pinocchio_python_spatial_se3_hpp__",\
169187
['<eigenpy/eigenpy.hpp>', '<boost/python/tuple.hpp>', '"pinocchio/spatial/se3.hpp"', '"pinocchio/spatial/explog.hpp"'],\
170-
['"pinocchio/bindings/python/spatial/se3_decl.hxx"', '"pinocchio/bindings/python/spatial/se3_def.hxx"'])
188+
['pinocchio/bindings/python/spatial/se3_decl.hxx', 'pinocchio/bindings/python/spatial/se3_def.hxx'],\
189+
None)
171190
>>> print(res)
172191
//
173192
// Copyright (c) 2025 INRIA
@@ -186,25 +205,238 @@ def create_hpp_module(
186205
#include "pinocchio/bindings/python/spatial/se3_decl.hxx"
187206
#include "pinocchio/bindings/python/spatial/se3_def.hxx"
188207
<BLANKLINE>
208+
<BLANKLINE>
209+
<BLANKLINE>
210+
#endif // ifndef __pinocchio_python_spatial_se3_hpp__
211+
<BLANKLINE>
212+
>>> res = create_hpp_module("__pinocchio_python_spatial_se3_hpp__",\
213+
['<eigenpy/eigenpy.hpp>', '<boost/python/tuple.hpp>', '"pinocchio/spatial/se3.hpp"', '"pinocchio/spatial/explog.hpp"'],\
214+
['pinocchio/bindings/python/spatial/se3_decl.hxx', 'pinocchio/bindings/python/spatial/se3_def.hxx'],\
215+
"pinocchio/bindings/python/spatial/se3_tpl.hxx")
216+
>>> print(res)
217+
//
218+
// Copyright (c) 2025 INRIA
219+
//
220+
<BLANKLINE>
221+
#ifndef __pinocchio_python_spatial_se3_hpp__
222+
#define __pinocchio_python_spatial_se3_hpp__
223+
<BLANKLINE>
224+
// Module dependencies
225+
#include <eigenpy/eigenpy.hpp>
226+
#include <boost/python/tuple.hpp>
227+
#include "pinocchio/spatial/se3.hpp"
228+
#include "pinocchio/spatial/explog.hpp"
229+
<BLANKLINE>
230+
// Module headers
231+
#include "pinocchio/bindings/python/spatial/se3_decl.hxx"
232+
#include "pinocchio/bindings/python/spatial/se3_def.hxx"
233+
<BLANKLINE>
234+
<BLANKLINE>
235+
/// Explicit template instantiation
236+
#if PINOCCHIO_ENABLE_TEMPLATE_INSTANTIATION
237+
#include "pinocchio/bindings/python/spatial/se3_tpl.hxx"
238+
#endif // PINOCCHIO_ENABLE_TEMPLATE_INSTANTIATION
239+
<BLANKLINE>
240+
<BLANKLINE>
189241
#endif // ifndef __pinocchio_python_spatial_se3_hpp__
190242
<BLANKLINE>
191243
"""
192244
deps = "\n".join([f"#include {d}" for d in dependencies_includes])
193-
modules = "\n".join([f"#include {d}" for d in module_includes])
194-
return HPP_MODULE.format(guard=guard, dep_includes=deps, module_includes=modules)
245+
modules = "\n".join([f'#include "{d}"' for d in module_includes])
246+
module_tpl = ""
247+
if module_tpl_include is not None:
248+
module_tpl = HPP_MODULE_TPL_GUARD.format(module_tpl=module_tpl_include)
249+
return HPP_MODULE.format(
250+
guard=guard,
251+
dep_includes=deps,
252+
module_includes=modules,
253+
module_tpl_include=module_tpl,
254+
)
255+
256+
257+
def guard_from_path(path: Path) -> str:
258+
"""Compute the guard from the relative path
259+
>>> guard_from_path(Path("pinocchio/algorithm/kinematics.hxx"))
260+
'pinocchio_algorithm_kinematics_hxx'
261+
"""
262+
guard_content = "_".join(
263+
map(lambda x: x.replace(".", "_").replace("-", "_"), path.parts)
264+
)
265+
return f"{guard_content}"
266+
267+
268+
def update_content(
269+
path: Path,
270+
module_path: Path,
271+
new_guard: str,
272+
dry: bool = False,
273+
) -> list[str]:
274+
"""Update path content with the following rule:
275+
:ivar path: File to update.
276+
:ivar module_path: Module path relative to root directory.
277+
:ivar new_guard: New guard.
278+
:ivar dry: Don modify the file.
279+
- Update guards
280+
- Remove includes
281+
- Add clangd_hack
282+
"""
283+
content = ""
284+
with path.open() as path_desc:
285+
content = path_desc.read()
286+
old_guard = find_guard(content)
287+
print(f"\tNew guard: {new_guard}")
288+
content = update_guard(content, old_guard, new_guard)
289+
content, includes = remove_includes(content, str(module_path))
290+
if not dry:
291+
with path.open("w") as path:
292+
path.write(content)
293+
return includes, old_guard
294+
295+
296+
def update_module(module_path: Path, include_root_path: Path, dry: bool = False):
297+
"""Apply new convention to a module
298+
:ivar module_path: Path to the hpp (module entry point) to refactor. Must be an absolute path.
299+
:ivar include_root_path: Path to the include directory. Me be an absolute path.
300+
"""
301+
assert module_path.is_absolute()
302+
assert include_root_path.is_absolute()
303+
304+
print(f"Update {module_path}")
305+
306+
module_path_rel = module_path.relative_to(include_root_path)
307+
module_old_def = module_path.parent / Path(f"{module_path.stem}.hxx")
308+
module_old_tpl = module_path.parent / Path(f"{module_path.stem}.txx")
309+
module_decl = module_path.parent / Path(f"{module_path.stem}-decl.hxx")
310+
module_def = module_path.parent / Path(f"{module_path.stem}-def.hxx")
311+
module_tpl = module_path.parent / Path(f"{module_path.stem}-tpl.hxx")
312+
313+
print(f"\tConvert {module_path} into {module_decl}")
314+
dependency_includes = []
315+
module_includes = [str(module_decl.relative_to(include_root_path))]
316+
module_tpl_include = None
317+
318+
decl_includes, module_guard = update_content(
319+
module_path,
320+
module_path_rel,
321+
guard_from_path(module_decl.relative_to(include_root_path)),
322+
dry,
323+
)
324+
dependency_includes += decl_includes
325+
if not dry:
326+
subprocess.run(
327+
[
328+
"git",
329+
"-C",
330+
str(include_root_path),
331+
"mv",
332+
str(module_path),
333+
str(module_decl),
334+
]
335+
)
336+
337+
if module_old_def.exists():
338+
print(f"\tConvert {module_old_def} into {module_def}")
339+
def_includes, _ = update_content(
340+
module_old_def,
341+
module_path_rel,
342+
guard_from_path(module_def.relative_to(include_root_path)),
343+
dry,
344+
)
345+
dependency_includes += def_includes
346+
if not dry:
347+
subprocess.run(
348+
[
349+
"git",
350+
"-C",
351+
str(include_root_path),
352+
"mv",
353+
str(module_old_def),
354+
str(module_def),
355+
]
356+
)
357+
module_includes.append(str(module_def.relative_to(include_root_path)))
358+
359+
if module_old_tpl.exists():
360+
print(f"\tConvert {module_old_tpl} into {module_tpl}")
361+
tpl_includes, _ = update_content(
362+
module_old_tpl,
363+
module_path_rel,
364+
guard_from_path(module_tpl.relative_to(include_root_path)),
365+
dry,
366+
)
367+
dependency_includes += tpl_includes
368+
if not dry:
369+
subprocess.run(
370+
[
371+
"git",
372+
"-C",
373+
str(include_root_path),
374+
"mv",
375+
str(module_old_tpl),
376+
str(module_tpl),
377+
]
378+
)
379+
module_tpl_include = str(module_tpl.relative_to(include_root_path))
380+
381+
print(f"\tCreate new module {module_path}")
382+
print(f"\tNew module guard: {module_guard}")
383+
print(f"\tNew module dependencies: {', '.join(dependency_includes)}")
384+
print(f"\tNew module includes: {', '.join(module_includes)}")
385+
print(f"\tNew module tpl: {module_tpl_include}")
386+
module_content = create_hpp_module(
387+
module_guard, dependency_includes, module_includes, module_tpl_include
388+
)
389+
if not dry:
390+
with module_path.open("w") as module_path_desc:
391+
module_path_desc.write(module_content)
392+
393+
394+
def is_dir(dir: str) -> Path:
395+
p = Path(dir)
396+
if not p.is_dir():
397+
raise argparse.ArgumentTypeError(f"{dir} is not a directory")
398+
return p
399+
400+
401+
def is_file(file: str) -> Path:
402+
p = Path(file)
403+
if not p.is_file():
404+
raise argparse.ArgumentTypeError(f"{file} is not a file")
405+
return p
195406

196407

197408
def argument_parser() -> argparse.ArgumentParser:
198409
parser = argparse.ArgumentParser(
199410
description="Refactor headers in a Pinocchio subdirectory"
200411
)
201-
412+
parser.add_argument("include_directory", help="Include directory", type=is_dir)
413+
parser.add_argument("--dry", help="Dry run", action="store_true")
414+
group = parser.add_mutually_exclusive_group(required=True)
415+
group.add_argument(
416+
"-m", "--module", help="Module to convert", type=is_file, default=None
417+
)
418+
group.add_argument(
419+
"-d", "--dir", help="Directory to convert", type=is_dir, default=None
420+
)
202421
return parser
203422

204423

205424
def main(args: list[str]):
206-
pass
425+
parser = argument_parser()
426+
args = parser.parse_args(args)
427+
428+
if args.module is not None:
429+
update_module(
430+
args.module.absolute(), args.include_directory.absolute(), args.dry
431+
)
432+
elif args.dir is not None:
433+
for m in args.dir.glob("*.hpp"):
434+
update_module(m.absolute(), args.include_directory.absolute(), args.dry)
435+
else:
436+
raise RuntimeError("module or dir argument must be provided")
207437

208438

209439
if __name__ == "__main__":
210-
main()
440+
import sys
441+
442+
main(sys.argv[1:])

0 commit comments

Comments
 (0)