From 354a2feb5cf17344883cffccd8089c1e44db3e7d Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Tue, 30 Jul 2024 04:23:44 +0100 Subject: [PATCH] Begin to use pathlib --- breathe/apidoc.py | 32 +++++++++++------------------- breathe/directives/setup.py | 14 ++++++------- breathe/file_state_cache.py | 11 ++++++---- breathe/parser/__init__.py | 14 +++++++------ breathe/path_handler.py | 9 ++++----- breathe/process.py | 10 ++++++---- breathe/project.py | 5 +++-- breathe/renderer/filter.py | 2 +- breathe/renderer/sphinxrenderer.py | 13 +++++------- 9 files changed, 53 insertions(+), 57 deletions(-) diff --git a/breathe/apidoc.py b/breathe/apidoc.py index 198966805..fdb1eb330 100644 --- a/breathe/apidoc.py +++ b/breathe/apidoc.py @@ -17,8 +17,8 @@ import os import sys import argparse -import errno import xml.etree.ElementTree +from pathlib import Path from breathe import __version__ @@ -46,32 +46,24 @@ def print_info(msg, args): def write_file(name, text, args): """Write the output file for module/package .""" - fname = os.path.join(args.destdir, "%s.%s" % (name, args.suffix)) + fname = Path(args.destdir, f"{name}.{args.suffix}") if args.dryrun: print_info("Would create file %s." % fname, args) return - if not args.force and os.path.isfile(fname): + if not args.force and fname.is_file(): print_info("File %s already exists, skipping." % fname, args) else: print_info("Creating file %s." % fname, args) - if not os.path.exists(os.path.dirname(fname)): - try: - os.makedirs(os.path.dirname(fname)) - except OSError as exc: # Guard against race condition - if exc.errno != errno.EEXIST: - raise + fname.parent.mkdir(parents=True, exist_ok=True) try: - with open(fname) as target: - orig = target.read() - if orig == text: - print_info("File %s up to date, skipping." % fname, args) - return + orig = fname.read_text() + if orig == text: + print_info("File %s up to date, skipping." % fname, args) + return except FileNotFoundError: # Don't mind if it isn't there pass - - with open(fname, "w") as target: - target.write(text) + fname.write_text(text) def format_heading(level, text): @@ -100,12 +92,12 @@ def create_package_file(package, package_type, package_id, args): text = format_heading(1, "%s %s" % (TYPEDICT[package_type], package)) text += format_directive(package_type, package, args) - write_file(os.path.join(package_type, package_id), text, args) + write_file(Path(package_type, package_id), text, args) def create_modules_toc_file(key, value, args): """Create the module's index.""" - if not os.path.isdir(os.path.join(args.destdir, key)): + if not Path(args.destdir, key).is_dir(): return text = format_heading(1, "%s list" % value) text += ".. toctree::\n" @@ -120,7 +112,7 @@ def recurse_tree(args): Look for every file in the directory tree and create the corresponding ReST files. """ - index = xml.etree.ElementTree.parse(os.path.join(args.rootpath, "index.xml")) + index = xml.etree.ElementTree.parse(Path(args.rootpath, "index.xml")) # Assuming this is a valid Doxygen XML for compound in index.getroot(): diff --git a/breathe/directives/setup.py b/breathe/directives/setup.py index 4a4ee2667..52b9e07e0 100644 --- a/breathe/directives/setup.py +++ b/breathe/directives/setup.py @@ -1,3 +1,5 @@ +from pathlib import Path + from breathe.directives.class_like import ( DoxygenStructDirective, DoxygenClassDirective, @@ -26,7 +28,6 @@ from sphinx.application import Sphinx -import os import subprocess @@ -94,17 +95,16 @@ def set_temp_data( app.add_config_value("breathe_separate_member_pages", False, "env") breathe_css = "breathe.css" - if os.path.exists(os.path.join(app.confdir, "_static", breathe_css)): + if Path(app.confdir, "_static", breathe_css).exists(): app.add_css_file(breathe_css) def write_file(directory, filename, content): - # Check the directory exists - if not os.path.exists(directory): - os.makedirs(directory) + # Ensure that the directory exists + directory = Path(directory) + directory.mkdir(parents=True, exist_ok=True) # Write the file with the provided contents - with open(os.path.join(directory, filename), "w") as f: - f.write(content) + (directory / filename).write_text(content) doxygen_handle = AutoDoxygenProcessHandle( subprocess.check_call, write_file, project_info_factory diff --git a/breathe/file_state_cache.py b/breathe/file_state_cache.py index da302606c..2f340ed04 100644 --- a/breathe/file_state_cache.py +++ b/breathe/file_state_cache.py @@ -1,3 +1,5 @@ +from pathlib import Path + from sphinx.application import Sphinx from sphinx.environment import BuildEnvironment @@ -27,19 +29,20 @@ def _getmtime(filename: str): raise MTimeError("Cannot find file: %s" % os.path.realpath(filename)) -def update(app: Sphinx, source_file: str) -> None: +def update(app: Sphinx, source_file: str | os.PathLike[str]) -> None: if not hasattr(app.env, "breathe_file_state"): app.env.breathe_file_state = {} # type: ignore - new_mtime = _getmtime(source_file) + norm_source_file = Path(source_file).resolve().as_posix() + new_mtime = _getmtime(norm_source_file) mtime, docnames = app.env.breathe_file_state.setdefault( # type: ignore - source_file, (new_mtime, set()) + norm_source_file, (new_mtime, set()) ) assert app.env is not None docnames.add(app.env.docname) - app.env.breathe_file_state[source_file] = (new_mtime, docnames) # type: ignore + app.env.breathe_file_state[norm_source_file] = (new_mtime, docnames) # type: ignore def _get_outdated( diff --git a/breathe/parser/__init__.py b/breathe/parser/__init__.py index 9a656cce9..cde8750d4 100644 --- a/breathe/parser/__init__.py +++ b/breathe/parser/__init__.py @@ -1,7 +1,9 @@ +from pathlib import Path + from . import index from . import compound -from breathe import file_state_cache, path_handler +from breathe import file_state_cache from breathe.project import ProjectInfo from sphinx.application import Sphinx @@ -34,7 +36,7 @@ def __init__(self, app: Sphinx, cache): class DoxygenIndexParser(Parser): def parse(self, project_info: ProjectInfo): - filename = path_handler.resolve_path(self.app, project_info.project_path(), "index.xml") + filename = Path(self.app.confdir, project_info.project_path(), "index.xml").resolve() file_state_cache.update(self.app, filename) try: @@ -60,11 +62,11 @@ def __init__(self, app: Sphinx, cache, self.project_info = project_info def parse(self, refid: str): - filename = path_handler.resolve_path( - self.app, + filename = Path( + self.app.confdir, self.project_info.project_path(), - "%s.xml" % refid - ) + f"{refid}.xml" + ).resolve() file_state_cache.update(self.app, filename) diff --git a/breathe/path_handler.py b/breathe/path_handler.py index 1c2bb138f..104a5704d 100644 --- a/breathe/path_handler.py +++ b/breathe/path_handler.py @@ -1,12 +1,12 @@ -from sphinx.application import Sphinx +from pathlib import Path -import os +from sphinx.application import Sphinx def includes_directory(file_path: str): # Check for backslash or forward slash as we don't know what platform we're on and sometimes # the doxygen paths will have forward slash even on Windows. - return bool(file_path.count("\\")) or bool(file_path.count("/")) + return bool(str(file_path).count("\\")) or bool(str(file_path).count("/")) def resolve_path(app: Sphinx, directory: str, filename: str): @@ -14,5 +14,4 @@ def resolve_path(app: Sphinx, directory: str, filename: str): path is relative, then it is relative to the conf.py directory. """ - # os.path.join does the appropriate handling if _project_path is an absolute path - return os.path.join(app.confdir, directory, filename) + return Path(app.confdir, directory, filename).resolve() diff --git a/breathe/process.py b/breathe/process.py index 66a19b50a..7c5f7990b 100644 --- a/breathe/process.py +++ b/breathe/process.py @@ -1,3 +1,5 @@ +from pathlib import Path + from breathe.project import AutoProjectInfo, ProjectInfoFactory import os @@ -38,7 +40,7 @@ class AutoDoxygenProcessHandle: def __init__( self, run_process: Callable, - write_file: Callable[[str, str, str], None], + write_file: Callable[[str | os.PathLike[str], str, str], None], project_info_factory: ProjectInfoFactory, ) -> None: self.run_process = run_process @@ -92,13 +94,13 @@ def process( extra=f"{options}\n{aliases}", ) - build_dir = os.path.join(auto_project_info.build_dir(), "breathe", "doxygen") - cfgfile = "%s.cfg" % name + build_dir = Path(auto_project_info.build_dir(), "breathe", "doxygen") + cfgfile = f"{name}.cfg" self.write_file(build_dir, cfgfile, cfg) # Shell-escape the cfg file name to try to avoid any issue where the name might include # malicious shell character - We have to use the shell=True option to make it work on # Windows. See issue #271 - self.run_process("doxygen %s" % quote(cfgfile), cwd=build_dir, shell=True) + self.run_process(f"doxygen {quote(cfgfile)}", cwd=build_dir, shell=True) return os.path.join(build_dir, name, "xml") diff --git a/breathe/project.py b/breathe/project.py index 5c2b65d36..eb73f2e3d 100644 --- a/breathe/project.py +++ b/breathe/project.py @@ -1,3 +1,5 @@ +from pathlib import Path + from .exception import BreatheError from sphinx.application import Sphinx @@ -40,8 +42,7 @@ def abs_path_to_source_file(self, file_): projects conf.py directory as specified in the breathe_projects_source config variable. """ - # os.path.join does the appropriate handling if _source_path is an absolute path - return os.path.join(self.app.confdir, self._source_path, file_) + return Path(self.app.confdir, self._source_path, file_).resolve() def create_project_info(self, project_path): """Creates a proper ProjectInfo object based on the information in this AutoProjectInfo""" diff --git a/breathe/renderer/filter.py b/breathe/renderer/filter.py index 3860b2f97..8ab0c78ea 100644 --- a/breathe/renderer/filter.py +++ b/breathe/renderer/filter.py @@ -453,7 +453,7 @@ def allow(self, node_stack) -> bool: # If the target_file contains directory separators then # match against the same length at the end of the location # - location_match = location[-len(self.target_file) :] + location_match = location[-len(self.target_file):] return location_match == self.target_file else: # If there are no separators, match against the whole filename diff --git a/breathe/renderer/sphinxrenderer.py b/breathe/renderer/sphinxrenderer.py index 4160b7772..0531b5331 100644 --- a/breathe/renderer/sphinxrenderer.py +++ b/breathe/renderer/sphinxrenderer.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path from breathe.parser import compound, compoundsuper, DoxygenCompoundParser from breathe.project import ProjectInfo @@ -2348,21 +2348,18 @@ def visit_docdot(self, node) -> List[Node]: def visit_docdotfile(self, node) -> List[Node]: """Translate node from doxygen's dotfile command to sphinx's graphviz directive.""" dotcode = "" - dot_file_path = node.name # type: str + dot_file_path = Path(node.name) # Doxygen v1.9.3+ uses a relative path to specify the dot file. # Previously, Doxygen used an absolute path. # This relative path is with respect to the XML_OUTPUT path. # Furthermore, Doxygen v1.9.3+ will copy the dot file into the XML_OUTPUT - if not os.path.isabs(dot_file_path): + if not dot_file_path.is_absolute(): # Use self.project_info.project_path as the XML_OUTPUT path, and # make it absolute with consideration to the conf.py path project_path = self.project_info.project_path() - dot_file_path = os.path.abspath( - os.path.join(self.app.confdir, project_path, dot_file_path) - ) + dot_file_path = Path(self.app.confdir, project_path, dot_file_path).resolve() try: - with open(dot_file_path, encoding="utf-8") as fp: - dotcode = fp.read() + dotcode = dot_file_path.read_text(encoding="utf-8") if not dotcode.rstrip("\n"): raise RuntimeError("%s found but without any content" % dot_file_path) except OSError as exc: