|
| 1 | +#! /usr/bin/env python3 |
| 2 | +# Refactor headers according to |
| 3 | +# https://github.com/stack-of-tasks/pinocchio/pull/2572 |
| 4 | +# Run test with python -m doctest -v refactor_headers.py |
| 5 | + |
| 6 | +import re |
| 7 | +import argparse |
| 8 | + |
| 9 | +# Walk in a directory |
| 10 | +# Find all xxx.hpp |
| 11 | +# Rename it into xxx-def.hxx |
| 12 | +# Update guards |
| 13 | +# Remove includes |
| 14 | +# Add clangd_hack |
| 15 | +# if xxx.hxx associated |
| 16 | +# Rename into xxx-decl.hxx |
| 17 | +# Update guards |
| 18 | +# Remove includes |
| 19 | +# Add clangd_hack |
| 20 | +# if xxx.txx associated |
| 21 | +# Rename into xxx-tpl.hxx |
| 22 | +# Update guards |
| 23 | +# Remove includes |
| 24 | +# Add clangd_hack |
| 25 | +# Create xxx.hpp |
| 26 | +# Add guards |
| 27 | +# Add includes form def, decl and tpl |
| 28 | +# include def, decl and tpl |
| 29 | + |
| 30 | +GUARD_PATTERN = re.compile("^#ifndef __(.*)__$", re.MULTILINE) |
| 31 | +INCLUDE_PATTERN = re.compile(r"^#include ([\"<].*[\">])\n", re.MULTILINE) |
| 32 | + |
| 33 | +TEST_CPP_CONTENT_GOOD_GUARD = """// |
| 34 | +// Copyright (c) 2015-2024 CNRS INRIA |
| 35 | +// Copyright (c) 2016 Wandercraft, 86 rue de Paris 91400 Orsay, France. |
| 36 | +// |
| 37 | +
|
| 38 | +#ifndef __pinocchio_python_spatial_se3_hpp__ |
| 39 | +#define __pinocchio_python_spatial_se3_hpp__ |
| 40 | +
|
| 41 | +#include <eigenpy/eigenpy.hpp> |
| 42 | +#include <boost/python/tuple.hpp> |
| 43 | +
|
| 44 | +#include "pinocchio/spatial/se3.hpp" |
| 45 | +#include "pinocchio/spatial/explog.hpp" |
| 46 | +// ... |
| 47 | +#endif // ifndef __pinocchio_python_spatial_se3_hpp__""" |
| 48 | + |
| 49 | +TEST_CPP_CONTENT_BAD_GUARD = """// |
| 50 | +// Copyright (c) 2015-2024 CNRS INRIA |
| 51 | +// Copyright (c) 2016 Wandercraft, 86 rue de Paris 91400 Orsay, France. |
| 52 | +// |
| 53 | +
|
| 54 | +#ifndef _pinocchio_python_spatial_se3_hpp__ |
| 55 | +#define _pinocchio_python_spatial_se3_hpp__ |
| 56 | +// ... |
| 57 | +#endif // ifndef _pinocchio_python_spatial_se3i_hpp__""" |
| 58 | + |
| 59 | +HPP_MODULE = """// |
| 60 | +// Copyright (c) 2025 INRIA |
| 61 | +// |
| 62 | +
|
| 63 | +#ifndef {guard} |
| 64 | +#define {guard} |
| 65 | +
|
| 66 | +// Module dependencies |
| 67 | +{dep_includes} |
| 68 | +
|
| 69 | +// Module headers |
| 70 | +{module_includes} |
| 71 | +
|
| 72 | +#endif // ifndef {guard} |
| 73 | +""" |
| 74 | + |
| 75 | +LSP_GUARD = """#ifdef PINOCCHIO_LSP |
| 76 | +#include "{module_hpp}" |
| 77 | +#endif // PINOCCHIO_LSP |
| 78 | +""" |
| 79 | + |
| 80 | + |
| 81 | +def find_guard(content: str) -> None | str: |
| 82 | + """Find guard in a C++ header file. |
| 83 | + :ivar content: File content to parse. |
| 84 | + :return: Guard name if found (without __), None if no guard is found. |
| 85 | + >>> find_guard(TEST_CPP_CONTENT_GOOD_GUARD) |
| 86 | + 'pinocchio_python_spatial_se3_hpp' |
| 87 | + >>> find_guard(TEST_CPP_CONTENT_BAD_GUARD) |
| 88 | + """ |
| 89 | + match = GUARD_PATTERN.search(content) |
| 90 | + if match: |
| 91 | + return match.group(1) |
| 92 | + return None |
| 93 | + |
| 94 | + |
| 95 | +def update_guard(content: str, old_guard_name, new_guard_name: str) -> None | str: |
| 96 | + """Replace guards in a C++ header file. |
| 97 | + :ivar content: File content to parse. |
| 98 | + :ivar old_guard_name: Guard to replace. |
| 99 | + :ivar new_guard_name: New guard name. |
| 100 | + :return: New content if the 3 guards are changed, None otherwise. |
| 101 | + >>> res = update_guard(TEST_CPP_CONTENT_GOOD_GUARD, "pinocchio_python_spatial_se3_hpp", "pinocchio_python_spatial_se3_def_hpp") |
| 102 | + >>> print(res) |
| 103 | + // |
| 104 | + // Copyright (c) 2015-2024 CNRS INRIA |
| 105 | + // Copyright (c) 2016 Wandercraft, 86 rue de Paris 91400 Orsay, France. |
| 106 | + // |
| 107 | + <BLANKLINE> |
| 108 | + #ifndef __pinocchio_python_spatial_se3_def_hpp__ |
| 109 | + #define __pinocchio_python_spatial_se3_def_hpp__ |
| 110 | + <BLANKLINE> |
| 111 | + #include <eigenpy/eigenpy.hpp> |
| 112 | + #include <boost/python/tuple.hpp> |
| 113 | + <BLANKLINE> |
| 114 | + #include "pinocchio/spatial/se3.hpp" |
| 115 | + #include "pinocchio/spatial/explog.hpp" |
| 116 | + // ... |
| 117 | + #endif // ifndef __pinocchio_python_spatial_se3_def_hpp__ |
| 118 | + >>> update_guard(TEST_CPP_CONTENT_BAD_GUARD, "pinocchio_python_spatial_se3_hpp", "pinocchio_python_spatial_se3_def_hpp") |
| 119 | + """ |
| 120 | + (new_content, nr_sub) = re.subn(f"{old_guard_name}", new_guard_name, content) |
| 121 | + if nr_sub == 3: |
| 122 | + return new_content |
| 123 | + return None |
| 124 | + |
| 125 | + |
| 126 | +def remove_includes(content: str, module_header: str) -> (str, list[str]): |
| 127 | + """Remove includes, add LSP guard instead and return them. |
| 128 | + :ivar content: File content to parse. |
| 129 | + :ivar module_header: Module header path (without < or ") |
| 130 | + :return: New content and list of removed includes (path with < or ") |
| 131 | + >>> res = remove_includes(TEST_CPP_CONTENT_GOOD_GUARD, "pinocchio/bindings/python/spatial/se3.hpp") |
| 132 | + >>> print(res[0]) |
| 133 | + // |
| 134 | + // Copyright (c) 2015-2024 CNRS INRIA |
| 135 | + // Copyright (c) 2016 Wandercraft, 86 rue de Paris 91400 Orsay, France. |
| 136 | + // |
| 137 | + <BLANKLINE> |
| 138 | + #ifndef __pinocchio_python_spatial_se3_hpp__ |
| 139 | + #define __pinocchio_python_spatial_se3_hpp__ |
| 140 | + <BLANKLINE> |
| 141 | + <BLANKLINE> |
| 142 | + #ifdef PINOCCHIO_LSP |
| 143 | + #include "pinocchio/bindings/python/spatial/se3.hpp" |
| 144 | + #endif // PINOCCHIO_LSP |
| 145 | + // ... |
| 146 | + #endif // ifndef __pinocchio_python_spatial_se3_hpp__ |
| 147 | + >>> print(res[1]) |
| 148 | + ['<eigenpy/eigenpy.hpp>', '<boost/python/tuple.hpp>', '"pinocchio/spatial/se3.hpp"', '"pinocchio/spatial/explog.hpp"'] |
| 149 | + """ |
| 150 | + # TODO: warning if 0 includes, LSP_GUARD will not be added |
| 151 | + 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 |
| 155 | + ) |
| 156 | + # (new_content2, _) = INCLUDE_PATTERN.subn("", new_content) |
| 157 | + return new_content2, includes |
| 158 | + |
| 159 | + |
| 160 | +def create_hpp_module( |
| 161 | + guard: str, dependencies_includes: list[str], module_includes: list[str] |
| 162 | +) -> str: |
| 163 | + """Create a module content. |
| 164 | + :ivar guard: Guard name. |
| 165 | + :ivar dependencies_includes: Module dependencies include paths (path with < or "). |
| 166 | + :ivar module_includes: Module internal include paths (path with < or "). |
| 167 | + :return: Module content. |
| 168 | + >>> res = create_hpp_module("__pinocchio_python_spatial_se3_hpp__",\ |
| 169 | + ['<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"']) |
| 171 | + >>> print(res) |
| 172 | + // |
| 173 | + // Copyright (c) 2025 INRIA |
| 174 | + // |
| 175 | + <BLANKLINE> |
| 176 | + #ifndef __pinocchio_python_spatial_se3_hpp__ |
| 177 | + #define __pinocchio_python_spatial_se3_hpp__ |
| 178 | + <BLANKLINE> |
| 179 | + // Module dependencies |
| 180 | + #include <eigenpy/eigenpy.hpp> |
| 181 | + #include <boost/python/tuple.hpp> |
| 182 | + #include "pinocchio/spatial/se3.hpp" |
| 183 | + #include "pinocchio/spatial/explog.hpp" |
| 184 | + <BLANKLINE> |
| 185 | + // Module headers |
| 186 | + #include "pinocchio/bindings/python/spatial/se3_decl.hxx" |
| 187 | + #include "pinocchio/bindings/python/spatial/se3_def.hxx" |
| 188 | + <BLANKLINE> |
| 189 | + #endif // ifndef __pinocchio_python_spatial_se3_hpp__ |
| 190 | + <BLANKLINE> |
| 191 | + """ |
| 192 | + 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) |
| 195 | + |
| 196 | + |
| 197 | +def argument_parser() -> argparse.ArgumentParser: |
| 198 | + parser = argparse.ArgumentParser( |
| 199 | + description="Refactor headers in a Pinocchio subdirectory" |
| 200 | + ) |
| 201 | + |
| 202 | + return parser |
| 203 | + |
| 204 | + |
| 205 | +def main(args: list[str]): |
| 206 | + pass |
| 207 | + |
| 208 | + |
| 209 | +if __name__ == "__main__": |
| 210 | + main() |
0 commit comments