From 0d472b67539e2645c583240d3137bc15acab42fb Mon Sep 17 00:00:00 2001 From: Eirikr Hinngart <151315375+Oichkatzelesfrettschen@users.noreply.github.com> Date: Fri, 30 May 2025 08:44:07 -0700 Subject: [PATCH] Add script to enumerate C++23 upgrades --- tools/cpp23_enumerate.py | 113 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 tools/cpp23_enumerate.py diff --git a/tools/cpp23_enumerate.py b/tools/cpp23_enumerate.py new file mode 100644 index 000000000..45193c7fd --- /dev/null +++ b/tools/cpp23_enumerate.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +"""Enumerate repository files and guess which are C++23 upgraded. + +The script walks the directory tree rooted at the repository and prints a +summary of file types. It also inspects C++ source files and uses simple +heuristics to determine whether they likely use C++23 features. +""" + +from __future__ import annotations + +import argparse +import os +import mimetypes +from dataclasses import dataclass +from typing import Iterator, List + + +CXX_EXTENSIONS = {".cpp", ".cc", ".cxx", ".C", ".hpp", ".hh", ".hxx"} + +CXX23_MARKERS = [ + "consteval", # keyword introduced in C++20, relevant for 23 + "std::expected", # library feature from C++23 + "std::print", # print facility in C++23 + "std::format", # improved in C++23 + "std::ranges", # ranges library + "co_await", # coroutine keyword + "#if __cplusplus >= 202302L", # direct version check +] + + +@dataclass +class FileInfo: + """Simple container for file information.""" + + path: str + file_type: str + upgraded_cpp23: bool = False + + +def guess_file_type(path: str) -> str: + """Return a best-effort string describing the file type.""" + mime, _ = mimetypes.guess_type(path) + return mime or "unknown" + + +def walk_files(root: str) -> Iterator[str]: + """Yield all file paths under *root*.""" + for current, _, files in os.walk(root): + for name in files: + yield os.path.join(current, name) + + +def inspect_file(path: str) -> FileInfo: + """Inspect *path* and return collected information.""" + ext = os.path.splitext(path)[1] + file_type = guess_file_type(path) + upgraded = False + if ext in CXX_EXTENSIONS: + try: + with open(path, "r", encoding="utf-8", errors="ignore") as source: + data = source.read() + except OSError: + data = "" + upgraded = any(marker in data for marker in CXX23_MARKERS) + return FileInfo(path, file_type, upgraded) + + +def analyze(root: str) -> tuple[list[FileInfo], list[str], list[str]]: + """Analyze the directory tree starting at *root*.""" + tree: List[FileInfo] = [] + upgraded: List[str] = [] + not_upgraded: List[str] = [] + for path in walk_files(root): + info = inspect_file(path) + tree.append(info) + if os.path.splitext(path)[1] in CXX_EXTENSIONS: + (upgraded if info.upgraded_cpp23 else not_upgraded).append(path) + return tree, upgraded, not_upgraded + + +def print_summary( + tree: list[FileInfo], upgraded: list[str], not_upgraded: list[str] +) -> None: + """Print the collected summary to stdout.""" + print("File tree and types:\n") + for info in tree: + print(f"{info.path}: {info.file_type}") + print("\nC++23 upgraded files:") + for path in upgraded: + print(path) + print("\nFiles not upgraded to C++23:") + for path in not_upgraded: + print(path) + + +def parse_args() -> argparse.Namespace: + """Parse command line arguments.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "root", nargs="?", default=".", help="Root directory to scan" + ) + return parser.parse_args() + + +def main() -> None: + """Entry point for command line execution.""" + args = parse_args() + tree, upgraded, not_upgraded = analyze(args.root) + print_summary(tree, upgraded, not_upgraded) + + +if __name__ == "__main__": + main()