55
66import re
77import argparse
8+ import subprocess
9+
10+ from pathlib import Path
811
912# Walk in a directory
1013# Find all xxx.hpp
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+
5969HPP_MODULE = """//
6070// Copyright (c) 2025 INRIA
6171//
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 ("\t No 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
160174def 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"\t New 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"\t Convert { 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"\t Convert { 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"\t Convert { 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"\t Create new module { module_path } " )
382+ print (f"\t New module guard: { module_guard } " )
383+ print (f"\t New module dependencies: { ', ' .join (dependency_includes )} " )
384+ print (f"\t New module includes: { ', ' .join (module_includes )} " )
385+ print (f"\t New 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
197408def 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
205424def 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
209439if __name__ == "__main__" :
210- main ()
440+ import sys
441+
442+ main (sys .argv [1 :])
0 commit comments