From ebc45d0626bb51a1d327828e729a8a9e3a458a2f Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Tue, 16 Apr 2024 07:54:16 +0800 Subject: [PATCH 1/3] Formatted tools, and added a new script new_component.py. Signed-off-by: Andy Zhang --- tools/add_copyright_header.py | 45 +++-- tools/new_component.py | 337 ++++++++++++++++++++++++++++++++ tools/patch_compile_commands.py | 28 ++- 3 files changed, 393 insertions(+), 17 deletions(-) create mode 100644 tools/new_component.py diff --git a/tools/add_copyright_header.py b/tools/add_copyright_header.py index 0ce9c57d3..13040df4e 100644 --- a/tools/add_copyright_header.py +++ b/tools/add_copyright_header.py @@ -50,7 +50,8 @@ **************************************************************************************************/\ """ -REGEX_CPP = re.compile(r"""/\*+ +REGEX_CPP = re.compile( + r"""/\*+ \* Copyright \(c\) 2023-(....) NWSOFT {65}\* \* {96}\* \* Permission is hereby granted, free of charge, to any person obtaining a copy {19}\* @@ -71,7 +72,9 @@ \* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE {18}\* \* SOFTWARE\. {86}\* \*+/ -""", flags=re.MULTILINE) +""", + flags=re.MULTILINE, +) COPYRIGHT_PY_CMAKE = """\ ##################################################################################################### @@ -97,7 +100,8 @@ #####################################################################################################\ """ -REGEX_PY_CMAKE = re.compile(r"""#{101} +REGEX_PY_CMAKE = re.compile( + r"""#{101} # {2}Copyright \(c\) 2023-(....) NWSOFT {67}# # {99}# # {2}Permission is hereby granted, free of charge, to any person obtaining a copy {21}# @@ -118,7 +122,9 @@ # {2}OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE {20}# # {2}SOFTWARE\. {88}# #{101} -""", flags=re.MULTILINE) +""", + flags=re.MULTILINE, +) def count_lines(text: str) -> int: @@ -131,13 +137,21 @@ def first_n_lines(text: str, n: int) -> str: def process(file: Path): - if file.suffix in [".cpp", ".hpp"] or file.name == "cpp.hint": # C++ Source / Header + if ( + file.suffix in [".cpp", ".hpp"] or file.name == "cpp.hint" + ): # C++ Source / Header with open(file, "r") as f: contents = f.read() - results = re.match(REGEX_CPP, first_n_lines(contents, count_lines(COPYRIGHT_CPP) + 1)) + results = re.match( + REGEX_CPP, first_n_lines(contents, count_lines(COPYRIGHT_CPP) + 1) + ) year = results.group(1) if results is not None else None if results is None: - contents = COPYRIGHT_CPP.format(year=datetime.datetime.now().year) + "\n\n" + contents + contents = ( + COPYRIGHT_CPP.format(year=datetime.datetime.now().year) + + "\n\n" + + contents + ) print(f"Added header to {file}") elif year != str(datetime.datetime.now().year): header = COPYRIGHT_CPP.format(year=datetime.datetime.now().year) @@ -146,13 +160,22 @@ def process(file: Path): with open(file, "w") as f: f.write(contents) - elif file.suffix == ".py" or file.name == "CMakeLists.txt": # Python File or CMake file + elif ( + file.suffix == ".py" or file.name == "CMakeLists.txt" + ): # Python File or CMake file with open(file, "r") as f: contents = f.read() - results = re.match(REGEX_PY_CMAKE, first_n_lines(contents, count_lines(COPYRIGHT_PY_CMAKE) + 1)) + results = re.match( + REGEX_PY_CMAKE, + first_n_lines(contents, count_lines(COPYRIGHT_PY_CMAKE) + 1), + ) year = results.group(1) if results is not None else None if results is None: - contents = COPYRIGHT_PY_CMAKE.format(year=datetime.datetime.now().year) + "\n\n" + contents + contents = ( + COPYRIGHT_PY_CMAKE.format(year=datetime.datetime.now().year) + + "\n\n" + + contents + ) print(f"Added header to {file}") elif year != str(datetime.datetime.now().year): header = COPYRIGHT_PY_CMAKE.format(year=datetime.datetime.now().year) @@ -169,7 +192,7 @@ def walk_into_directory(path: Path): process(subpath) -if __name__ == '__main__': +if __name__ == "__main__": process(PROJECT_PATH / "CMakeLists.txt") process(PROJECT_PATH / "setup.py") process(PROJECT_PATH / "cpp.hint") diff --git a/tools/new_component.py b/tools/new_component.py new file mode 100644 index 000000000..b0dab9ed8 --- /dev/null +++ b/tools/new_component.py @@ -0,0 +1,337 @@ +##################################################################################################### +# Copyright (c) 2023-2024 NWSOFT # +# # +# Permission is hereby granted, free of charge, to any person obtaining a copy # +# of this software and associated documentation files (the "Software"), to deal # +# in the Software without restriction, including without limitation the rights # +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # +# copies of the Software, and to permit persons to whom the Software is # +# furnished to do so, subject to the following conditions: # +# # +# The above copyright notice and this permission notice shall be included in all # +# copies or substantial portions of the Software. # +# # +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # +# SOFTWARE. # +##################################################################################################### + +from pathlib import Path +import sys +import datetime + + +class _Getch: + """Gets a single character from standard input. Does not echo to the + screen.""" + + def __init__(self): + if not hasattr(sys.stdin, "fileno"): + self.impl = input + return + try: + self.impl = _GetchWindows() + except ImportError: + self.impl = _GetchUnix() + + def __call__(self): + return self.impl() + + +class _GetchUnix: + def __init__(self): + import tty + + def __call__(self) -> str: + import tty, termios + + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(sys.stdin.fileno()) + ch = sys.stdin.read(1) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + return ch + + +class _GetchWindows: + def __init__(self): + import msvcrt + + def __call__(self) -> str: + import msvcrt + + return msvcrt.getch() + + +getch = _Getch() + +WELCOME_STRING = """\ +WELCOME TO STEPPABLE! +===================== +Thank you for contributing to this project! This program is a guided wizard that creates a new component in Steppable. +Please select your option by pressing on the corresponding letter: + +A - Add a new component +H - Help on new components, e.g., naming conventions etc. (**RECOMMENDED** for new contributors) +Q - Exit the program. +""" + +HELP = [ + """\ +HELP ON CONTRIBUTING +==================== +Thank you for contributing to this project! This part documents how you may contribute to the project. + +1. Naming Conventions for components +------------------------------------ +Due to the problems of CMake with handling spaces, the names of components cannot include a space. Also, it cannot be a +restricted character in any operating system, for example, * " / \ < > : | ? on Windows. The names should be in the +camelCase format. + +Be descriptive with the name, and try to include similar functions in a single component. For example, instead of using +sin, cos, and tan in a single component, it is much better to combine them in a single component named "trigonometry". +""", + """\ +2. C++ Code Standard +-------------------- +In this project, there are several things to bear in mind while developing. + +For easier typing, camelCase naming is applied on all non-constant variables, functions and namespaces. Classes, enums, +structs need to apply the UpperCamelCase naming convention. The code should be formatted using the clang-format utility, +which is provided with LLVM, before the changed are committed. + +The output should be constructed in the report file, instead of the main file. The latter is responsible for performing +calculations, while the report file is responsible for creating the output that can be shown to the user. + +Examples: ++----------------------------------------------------------+-----------------------------------------------------------+ +| BAD | GOOD | ++----------------------------------------------------------+-----------------------------------------------------------+ +| #include | #include // Formatting issues | +| std::string Func(std::string A, std::string B) | std::string func(std::string A, std::string B) | +| | // Naming issues | +| { | { | +| return "A + B" + " is " + A + B; | return reportFunc(A, B); // The output should be | +| | // constructed in the report | +| | // function. | +| } | } | ++----------------------------------------------------------+-----------------------------------------------------------+ +""", + """\ +3. Questions? +------------- +Steppable has a discussion forum on GitHub, https://github.com/ZCG-coder/Steppable/discussions/categories/q-a. You may +raise any concers on contributing, using and more there. +""", +] + +SRC_PATH = Path(__file__).parent.parent / "src" + +BRACE = "{" +END_BRACE = "}" + + +def show_help() -> None: + for idx, part in enumerate(HELP): + print(part) + if idx != (len(HELP) - 1): + print(f"Part {idx + 1}/{len(HELP)} | [Q]uit | [ANY KEY] next") + char = getch().upper() + if char == "Q": + return + elif char == "\n": + continue + else: + print("END | [ANY KEY] exit") + char = getch() + + +def validate_name(name: str) -> bool: + if ( + "*" in name + or '"' in name + or "/" in name + or "\\" in name + or "<" in name + or ">" in name + or ":" in name + or "|" in name + or "?" in name + or " " in name + ): + return False + return True + + +def make_dir(name: str, date: str, author: str) -> None: + path: Path = SRC_PATH / name + + if not path.is_dir(): + path.mkdir(exist_ok=False) + else: + print( + "COMPONENT EXISTS. Maybe you want to add your code to that component, or choose another name." + ) + return + + with open(path / f"{name}.cpp", "w") as f: + f.write( + f"""\ +/************************************************************************************************** + * Copyright (c) 2023-2024 NWSOFT * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **************************************************************************************************/ + +/** + * @file {name}.cpp + * @brief Desciption + * + * @author {author} + * @date {date} + */ + +#include "{name}Report.hpp" +#include + +std::string {name}(/* Arguments... */) +{BRACE} + // Your code here... +{END_BRACE} +""" + ) + + with open(path / f"{name}Report.cpp", "w") as f: + f.write( + f"""\ +/************************************************************************************************** + * Copyright (c) 2023-2024 NWSOFT * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **************************************************************************************************/ + +/** + * @file {name}Report.cpp + * @brief Desciption + * + * @author {author} + * @date {date} + */ + +#include "{name}Report.hpp" +#include + +std::string report{name.capitalize()}() +{BRACE} + // Your code here... +{END_BRACE} +""" + ) + + with open(path / f"{name}Report.hpp", "w") as f: + f.write( + f"""\ +/************************************************************************************************** + * Copyright (c) 2023-2024 NWSOFT * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **************************************************************************************************/ + +/** + * @file {name}Report.hpp + * @brief Desciption + * + * @author {author} + * @date {date} + */ + +#include + +std::string report{name.capitalize()}(); +""" + ) + + +def ordinal(n: int) -> str: + """ + Get the ordinal numeral for a given number n + """ + return f"{n:d}{'tsnrhtdd'[(n//10%10!=1)*(n%10<4)*n%10::4]}" + + +today = datetime.date.today() +today_ordinal = ordinal(today.day) +today_string = today.strftime(f"{today_ordinal} %B %Y") + + +def main(): + print(WELCOME_STRING) + selection = getch().upper() + + if selection == "H": + return show_help() + elif selection == "A": + name = input("Name of your component: ") + author = input("Your name: ") + if not validate_name(name): + print(f"INVALID NAME: {name}") + return + make_dir(name, today_string, author) + + +if __name__ == "__main__": + main() diff --git a/tools/patch_compile_commands.py b/tools/patch_compile_commands.py index a4c35b871..5efcc9a39 100644 --- a/tools/patch_compile_commands.py +++ b/tools/patch_compile_commands.py @@ -39,7 +39,9 @@ PROJECT_PATH = Path(__file__).parent.parent NO_MAIN_PATTERN = re.compile(r"\s-DNO_MAIN\s") -I_SYS_ROOT_PATTERN = re.compile(r"\s/Library/Developer/CommandLineTools/SDKs/MacOSX\d\d\.\d\.sdk\s") +I_SYS_ROOT_PATTERN = re.compile( + r"\s/Library/Developer/CommandLineTools/SDKs/MacOSX\d\d\.\d\.sdk\s" +) def get_compile_commands() -> Path: @@ -49,11 +51,21 @@ def get_compile_commands() -> Path: :raises RuntimeError: if the build directory cannot be found. """ - if (compile_commands_file := PROJECT_PATH / "build" / "compile_commands.json").is_file: + if ( + compile_commands_file := PROJECT_PATH / "build" / "compile_commands.json" + ).is_file: return compile_commands_file - elif (compile_commands_file := PROJECT_PATH / "cmake-build-debug" / "compile_commands.json").is_file: + elif ( + compile_commands_file := PROJECT_PATH + / "cmake-build-debug" + / "compile_commands.json" + ).is_file: return compile_commands_file - elif (compile_commands_file := PROJECT_PATH / "cmake-build-release" / "compile_commands.json").is_file: + elif ( + compile_commands_file := PROJECT_PATH + / "cmake-build-release" + / "compile_commands.json" + ).is_file: return compile_commands_file raise RuntimeError("Cannot get the CMake build directory") @@ -70,7 +82,11 @@ def patch(): for obj in objs: command: str = obj["command"] command = re.sub(NO_MAIN_PATTERN, " ", command) - command = re.sub(I_SYS_ROOT_PATTERN, " /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk ", command) + command = re.sub( + I_SYS_ROOT_PATTERN, + " /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk ", + command, + ) obj["command"] = command modified_objs.append(obj) @@ -78,5 +94,5 @@ def patch(): json.dump(modified_objs, f, indent=4) -if __name__ == '__main__': +if __name__ == "__main__": patch() From 102fa7556b93478c2aaf894203f9fe1cdb3655eb Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Tue, 16 Apr 2024 07:54:16 +0800 Subject: [PATCH 2/3] Formatted tools, and added a new script new_component.py. Signed-off-by: Andy Zhang From 506904177bfd0f6f7b6e5973c583c7e6118a5256 Mon Sep 17 00:00:00 2001 From: Andy Zhang Date: Wed, 17 Apr 2024 09:25:40 +0800 Subject: [PATCH 3/3] Updated new_component.py (#10) new_component.py will now automatically add the component to CMakeLists.txt. Signed-off-by: Andy Zhang --- CMakeLists.txt | 12 ++++++++ src/CMakeLists.txt | 5 --- tools/new_component.py | 69 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 78 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 91c247218..7d1d5a623 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,14 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(STP_BASE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} CACHE STRING "The source directory of Steppable") + +execute_process(COMMAND uname -o COMMAND tr -d '\n' OUTPUT_VARIABLE OPERATING_SYSTEM) +if (${OPERATING_SYSTEM} MATCHES "Android") + set(ANDROID 1) + message(STATUS "Building for Android.") + set(LINUX CACHE INTERNAL "Is Linux or Android" 1) +endif () + if (WIN32) add_compile_definitions(WINDOWS) if (MSVC) # MSVC Has no UTF-8 support by default @@ -62,7 +70,11 @@ function(capitalize IN OUT) ${CAPITALIZED} PARENT_SCOPE) endfunction() + set(COMPONENTS abs add baseConvert subtract multiply decimalConvert comparison power division) +# NEW_COMPONENT: PATCH +# Do NOT remove the previous comment. + set(TARGETS ${COMPONENTS} util) set(TEST_TARGETS_TEMP util fraction number ${COMPONENTS}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e01173217..57c535b5d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -72,8 +72,3 @@ target_link_libraries(steppable util) target_link_libraries(calc steppable) target_compile_definitions(calc PRIVATE NO_MAIN) -execute_process(COMMAND uname -o COMMAND tr -d '\n' OUTPUT_VARIABLE OPERATING_SYSTEM) -if (${OPERATING_SYSTEM} MATCHES "Android") - set(ANDROID 1) - message(STATUS "Building for Android.") -endif () \ No newline at end of file diff --git a/tools/new_component.py b/tools/new_component.py index b0dab9ed8..e0132539b 100644 --- a/tools/new_component.py +++ b/tools/new_component.py @@ -50,12 +50,17 @@ def __call__(self) -> str: import tty, termios fd = sys.stdin.fileno() - old_settings = termios.tcgetattr(fd) try: + old_settings = termios.tcgetattr(fd) tty.setraw(sys.stdin.fileno()) ch = sys.stdin.read(1) + error = False + except termios.error: + ch = input() + error = True finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + if not error: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) return ch @@ -131,10 +136,13 @@ def __call__(self) -> str: """, ] -SRC_PATH = Path(__file__).parent.parent / "src" +ROOT_DIR = Path(__file__).parent.parent +SRC_PATH = ROOT_DIR / "src" +TESTS_PATH = ROOT_DIR / "tests" BRACE = "{" END_BRACE = "}" +PATCH_COMMENT = "# NEW_COMPONENT: PATCH" def show_help() -> None: @@ -222,6 +230,7 @@ def make_dir(name: str, date: str, author: str) -> None: {END_BRACE} """ ) + print(f"- Added {name}.cpp") with open(path / f"{name}Report.cpp", "w") as f: f.write( @@ -265,6 +274,7 @@ def make_dir(name: str, date: str, author: str) -> None: {END_BRACE} """ ) + print(f"- Added {name}Report.cpp") with open(path / f"{name}Report.hpp", "w") as f: f.write( @@ -304,6 +314,59 @@ def make_dir(name: str, date: str, author: str) -> None: std::string report{name.capitalize()}(); """ ) + print(f"- Added {name}Report.hpp") + + with open(TESTS_PATH / f"test{name.capitalize()}.cpp", "w") as f: + f.write( + """\ +/************************************************************************************************** + * Copyright (c) 2023-2024 NWSOFT * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **************************************************************************************************/ + +#include "colors.hpp" +#include "output.hpp" +#include "testing.hpp" +#include "util.hpp" + +#include +#include + +TEST_START() +TEST_END() +""" + ) + print(f"- Added test{name.capitalize()}.cpp") + + patch_cmakelists(name) + + +def patch_cmakelists(name: str) -> None: + with open(ROOT_DIR / "CMakeLists.txt") as f: + contents = f.read() + + pos = contents.find(PATCH_COMMENT) + contents = contents[:pos - 2] + f" {name}" + contents[pos - 2:] + with open(ROOT_DIR / "CMakeLists.txt", "w") as f: + f.write(contents) + print(f"- Added {name} to CMakeLists.txt.") def ordinal(n: int) -> str: