diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2a4f7e67..6a45ee3c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -122,7 +122,7 @@ jobs: compat-test-python3-ubuntu: strategy: matrix: - python3-version: ['8', '9', '10', '11', '12', '13'] + python3-version: ['10', '11', '12', '13'] runs-on: ubuntu-latest container: ghcr.io/opencyphal/toxic:tx22.4.3 needs: test diff --git a/setup.cfg b/setup.cfg index 3eefd88f..59327c3b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,8 +16,6 @@ classifiers = License :: OSI Approved :: MIT License Programming Language :: Python Programming Language :: Python :: 3 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 @@ -46,7 +44,7 @@ config = zip_safe = False -python_requires >= 3.8 +python_requires >= 3.10 [options.entry_points] console_scripts = diff --git a/setup.py b/setup.py index 289316f7..fdfc8b5f 100755 --- a/setup.py +++ b/setup.py @@ -33,18 +33,8 @@ pydsdl_version_specifier = f"pydsdl {match.group(1)} {match.group(2)}" package_data = {"": ["*.j2", "**/*.css", "**/*.js", "*.ini", "*.json", "*.hpp", "*.h"]} -if sys.version_info < (3, 9): - # For version 3.8 we need to add importlib_resources as a dependency. This seems to blow away the values - # in setup.cfg so we need to specify them here. - setuptools.setup( - version=version["__version__"], - package_data=package_data, - install_requires=["importlib_resources", pydsdl_version_specifier], - ) -else: - # For version 3.9 and later we don't need to add importlib_resources as a dependency. - setuptools.setup( - version=version["__version__"], - package_data=package_data, - install_requires=[pydsdl_version_specifier], - ) +setuptools.setup( + version=version["__version__"], + package_data=package_data, + install_requires=[pydsdl_version_specifier], +) diff --git a/src/nunavut/__init__.py b/src/nunavut/__init__.py index d7683876..6b7ce234 100644 --- a/src/nunavut/__init__.py +++ b/src/nunavut/__init__.py @@ -15,36 +15,26 @@ invoking the ``nunavut.generate_all`` method. """ + import sys as _sys -from ._generators import AbstractGenerator -from ._generators import generate_all -from ._generators import generate_all_for_language -from ._generators import generate_all_from_namespace -from ._generators import generate_all_from_namespace_with_generators -from ._generators import basic_language_context_builder_from_args +from ._generators import ( + AbstractGenerator, + basic_language_context_builder_from_args, + generate_all, + generate_all_for_language, + generate_all_from_namespace, + generate_all_from_namespace_with_generators, +) from ._namespace import Namespace -from ._utilities import TEMPLATE_SUFFIX -from ._utilities import DefaultValue -from ._utilities import ResourceType -from ._utilities import ResourceSearchPolicy -from ._utilities import YesNoDefault -from ._version import __author__ -from ._version import __copyright__ -from ._version import __email__ -from ._version import __license__ -from ._version import __version__ -from .jinja import CodeGenerator -from .jinja import DSDLCodeGenerator -from .jinja import SupportGenerator -from .lang import Language -from .lang import LanguageContext -from .lang import LanguageContextBuilder -from .lang import UnsupportedLanguageError +from ._utilities import TEMPLATE_SUFFIX, DefaultValue, ResourceSearchPolicy, ResourceType, YesNoDefault +from ._version import __author__, __copyright__, __email__, __license__, __version__ +from .jinja import CodeGenerator, DSDLCodeGenerator, SupportGenerator +from .lang import Language, LanguageContext, LanguageContextBuilder, UnsupportedLanguageError from .lang._config import LanguageConfig -if _sys.version_info[:2] < (3, 8): # pragma: no cover - print("A newer version of Python is required", file=_sys.stderr) +if _sys.version_info[:2] < (3, 10): # pragma: no cover + print("Python 3.10 or newer is required", file=_sys.stderr) _sys.exit(1) __version_info__ = tuple(map(int, __version__.split(".")[:3])) diff --git a/src/nunavut/__main__.py b/src/nunavut/__main__.py index cac2951f..614587db 100644 --- a/src/nunavut/__main__.py +++ b/src/nunavut/__main__.py @@ -11,5 +11,4 @@ from .cli.runners import main as cli_main - sys.exit(cli_main()) diff --git a/src/nunavut/_namespace.py b/src/nunavut/_namespace.py index 086fc07a..76d73733 100644 --- a/src/nunavut/_namespace.py +++ b/src/nunavut/_namespace.py @@ -66,11 +66,9 @@ Iterator, KeysView, List, - Optional, Protocol, Tuple, TypeVar, - Union, cast, ) @@ -79,15 +77,6 @@ from .lang import Language, LanguageContext from .lang._common import IncludeGenerator -if sys.version_info < (3, 9): - # Python 3.8 has a bug. This is a workaround per https://stackoverflow.com/a/66518591/310659 - def _register(self, cls, method=None): # type: ignore - if hasattr(cls, "__func__"): - setattr(cls, "__annotations__", cls.__func__.__annotations__) - return self.dispatcher.register(cls, func=method) - - singledispatchmethod.register = _register # type: ignore - # +--------------------------------------------------------------------------------------------------------------------+ @@ -96,7 +85,7 @@ class AsyncResultProtocol(Protocol): Defines the protocol for a duck-type compatible with multiprocessing.pool.AsyncResult. """ - def get(self, timeout: Optional[Any] = None) -> Any: + def get(self, timeout: Any | None = None) -> Any: """ See multiprocessing.pool.AsyncResult.get """ @@ -113,7 +102,7 @@ def __init__(self, read_method: Callable[..., Any], args: Tuple[Any, ...]) -> No self.args = args self._logger = logging.getLogger(NotAsyncResult.__name__) - def get(self, timeout: Optional[Any] = None) -> Any: + def get(self, timeout: Any | None = None) -> Any: """ Perform the work synchronously. """ @@ -130,7 +119,7 @@ def get(self, timeout: Optional[Any] = None) -> Any: def _read_files_strategy( index: "Namespace", apply_method: ApplyMethodT, - dsdl_files: Union[Path, str, Iterable[Union[Path, str]]], + dsdl_files: Path | str | Iterable[Path | str], job_timeout_seconds: float, omit_dependencies: bool, args: Iterable[Any], @@ -274,7 +263,7 @@ def wrap( """ return Generatable(definition, input_types, path) - def with_segments(self, *pathsegments: Union[str, PathLike]) -> Path: + def with_segments(self, *pathsegments: str | PathLike) -> Path: """ Path override: Construct a new path object from any number of path-like objects. We discard the Generatable type here and continue on with a default Path object. @@ -380,11 +369,11 @@ def strop_namespace(cls, full_namespace: str, language_context: LanguageContext) def add_types( cls, index: "Namespace", - types: Union[ - Tuple[pydsdl.CompositeType, List[pydsdl.CompositeType]], - List[Tuple[pydsdl.CompositeType, List[pydsdl.CompositeType]]], - ], - extension: Optional[str] = None, + types: ( + Tuple[pydsdl.CompositeType, List[pydsdl.CompositeType]] + | List[Tuple[pydsdl.CompositeType, List[pydsdl.CompositeType]]] + ), + extension: str | None = None, ) -> None: """ Add a set of types to a namespace tree building new nodes as needed. @@ -441,9 +430,9 @@ def Identity(cls, output_path: Path, lctx: LanguageContext) -> "Namespace": def read_namespace( cls, index: "Namespace", - root_namespace_directory: Union[Path, str], - lookup_directories: Optional[Union[Path, str, Iterable[Union[Path, str]]]] = None, - print_output_handler: Optional[Callable[[Path, int, str], None]] = None, + root_namespace_directory: Path | str, + lookup_directories: Path | str | Iterable[Path | str] | None = None, + print_output_handler: Callable[[Path, int, str], None] | None = None, allow_unregulated_fixed_port_id: bool = False, allow_root_namespace_name_collision: bool = True, ) -> "Namespace": @@ -477,9 +466,9 @@ def _( cls, output_path: str, lctx: LanguageContext, - root_namespace_directory: Union[Path, str], - lookup_directories: Optional[Union[Path, str, Iterable[Union[Path, str]]]] = None, - print_output_handler: Optional[Callable[[Path, int, str], None]] = None, + root_namespace_directory: Path | str, + lookup_directories: Path | str | Iterable[Path | str] | None = None, + print_output_handler: Callable[[Path, int, str], None] | None = None, allow_unregulated_fixed_port_id: bool = False, allow_root_namespace_name_collision: bool = True, ) -> pydsdl.Any: @@ -510,9 +499,9 @@ def _( cls, output_path: Path, lctx: LanguageContext, - root_namespace_directory: Union[Path, str], - lookup_directories: Optional[Union[Path, str, Iterable[Union[Path, str]]]] = None, - print_output_handler: Optional[Callable[[Path, int, str], None]] = None, + root_namespace_directory: Path | str, + lookup_directories: Path | str | Iterable[Path | str] | None = None, + print_output_handler: Callable[[Path, int, str], None] | None = None, allow_unregulated_fixed_port_id: bool = False, allow_root_namespace_name_collision: bool = True, ) -> pydsdl.Any: @@ -542,12 +531,12 @@ def _( def read_files( cls, index: "Namespace", - dsdl_files: Union[Path, str, Iterable[Union[Path, str]]], - root_namespace_directories_or_names: Optional[Union[Path, str, Iterable[Union[Path, str]]]], + dsdl_files: Path | str | Iterable[Path | str], + root_namespace_directories_or_names: Path | str | Iterable[Path | str] | None, jobs: int = 0, job_timeout_seconds: float = 0, - lookup_directories: Optional[Union[Path, str, Iterable[Union[Path, str]]]] = None, - print_output_handler: Optional[Callable[[Path, int, str], None]] = None, + lookup_directories: Path | str | Iterable[Path | str] | None = None, + print_output_handler: Callable[[Path, int, str], None] | None = None, allow_unregulated_fixed_port_id: bool = False, omit_dependencies: bool = False, ) -> "Namespace": @@ -590,12 +579,12 @@ def _( cls, output_path: Path, lctx: LanguageContext, - dsdl_files: Optional[Union[Path, str, Iterable[Union[Path, str]]]], - root_namespace_directories_or_names: Optional[Union[Path, str, Iterable[Union[Path, str]]]], + dsdl_files: Path | str | Iterable[Path | str] | None, + root_namespace_directories_or_names: Path | str | Iterable[Path | str] | None, jobs: int = 0, job_timeout_seconds: float = 0, - lookup_directories: Optional[Union[Path, str, Iterable[Union[Path, str]]]] = None, - print_output_handler: Optional[Callable[[Path, int, str], None]] = None, + lookup_directories: Path | str | Iterable[Path | str] | None = None, + print_output_handler: Callable[[Path, int, str], None] | None = None, allow_unregulated_fixed_port_id: bool = False, omit_dependencies: bool = False, ) -> pydsdl.Any: @@ -633,12 +622,12 @@ def _( cls, output_path: str, lctx: LanguageContext, - dsdl_files: Optional[Union[Path, str, Iterable[Union[Path, str]]]], - root_namespace_directories_or_names: Optional[Union[Path, str, Iterable[Union[Path, str]]]], + dsdl_files: Path | str | Iterable[Path | str] | None, + root_namespace_directories_or_names: Path | str | Iterable[Path | str] | None, jobs: int = 0, job_timeout_seconds: float = 0, - lookup_directories: Optional[Union[Path, str, Iterable[Union[Path, str]]]] = None, - print_output_handler: Optional[Callable[[Path, int, str], None]] = None, + lookup_directories: Path | str | Iterable[Path | str] | None = None, + print_output_handler: Callable[[Path, int, str], None] | None = None, allow_unregulated_fixed_port_id: bool = False, omit_dependencies: bool = False, ) -> pydsdl.Any: @@ -677,7 +666,7 @@ def __init__( full_namespace: str, namespace_dir: Path, language_context: LanguageContext, - parent: Optional["Namespace"] = None, + parent: "Namespace | None" = None, ): if full_namespace.startswith("."): full_namespace = full_namespace[1:] @@ -741,7 +730,7 @@ def output_path(self) -> Path: return self._output_path @property - def parent(self) -> Optional["Namespace"]: + def parent(self) -> "Namespace | None": """ The parent namespace of this namespace or None if this is a root namespace. """ @@ -934,14 +923,14 @@ def get_all_namespaces(self) -> Generator[Tuple["Namespace", Path], None, None]: """ yield from self._recursive_namespace_generator(self) - def get_all_types(self) -> Generator[Tuple[pydsdl.Any, Union[Generatable, Path]], None, None]: + def get_all_types(self) -> Generator[Tuple[pydsdl.Any, Generatable | Path], None, None]: """ Generates tuples relating datatypes and nested namespaces at and below this namespace to the path for each type's generated output. """ yield from self._recursive_data_type_and_namespace_generator(self) - def find_output_path_for_type(self, compound_type: Union["Namespace", pydsdl.CompositeType]) -> Path: + def find_output_path_for_type(self, compound_type: "Namespace | pydsdl.CompositeType") -> Path: """ Searches the entire namespace tree to find a mapping of the type to an output file path. @@ -958,7 +947,7 @@ def find_output_path_for_type(self, compound_type: Union["Namespace", pydsdl.Com return root_namespace._bfs_search_for_output_path(compound_type) # pylint: disable=protected-access def add_data_type( - self, dsdl_type: pydsdl.CompositeType, input_types: List[pydsdl.CompositeType], extension: Optional[str] + self, dsdl_type: pydsdl.CompositeType, input_types: List[pydsdl.CompositeType], extension: str | None ) -> Generatable: """ Add a datatype to this namespace. @@ -1098,7 +1087,7 @@ def _recursive_namespace_generator(cls, namespace: "Namespace") -> Generator[Tup @classmethod def _recursive_data_type_and_namespace_generator( cls, namespace: "Namespace" - ) -> Generator[Tuple[pydsdl.Any, Union[Path, Generatable]], None, None]: + ) -> Generator[Tuple[pydsdl.Any, Path | Generatable], None, None]: yield (namespace, namespace.output_path) for data_type, output_path in namespace.get_nested_types(): @@ -1113,8 +1102,8 @@ def _recursive_data_type_and_namespace_generator( def build_namespace_tree( types: List[pydsdl.CompositeType], - root_namespace_dir: Union[str, Path], - output_dir: Union[str, Path], + root_namespace_dir: str | Path, + output_dir: str | Path, language_context: LanguageContext, ) -> Namespace: """ diff --git a/src/nunavut/_postprocessors.py b/src/nunavut/_postprocessors.py index 5dae4993..046ddb84 100644 --- a/src/nunavut/_postprocessors.py +++ b/src/nunavut/_postprocessors.py @@ -6,11 +6,12 @@ """ Module containing post processing logic to run on generated files. """ + import abc import pathlib -import typing import re import sys +import typing from subprocess import run as subprocess_run # nosec # +---------------------------------------------------------------------------+ diff --git a/src/nunavut/_templates.py b/src/nunavut/_templates.py index b1e468d5..7c757574 100644 --- a/src/nunavut/_templates.py +++ b/src/nunavut/_templates.py @@ -6,6 +6,7 @@ """ Abstractions around template engine internals. """ + import functools import inspect import types diff --git a/src/nunavut/_utilities.py b/src/nunavut/_utilities.py index 1219a0d4..45f139aa 100644 --- a/src/nunavut/_utilities.py +++ b/src/nunavut/_utilities.py @@ -12,19 +12,16 @@ full-featured language, there should be very few truly generic utilities in Nunavut. """ + import collections.abc import copy import enum import logging import pathlib import sys +from importlib import resources as importlib_resources from typing import Any, Callable, Generator, Generic, MutableMapping, Optional, TypeVar, cast -if sys.version_info < (3, 9): - import importlib_resources -else: - from importlib import resources as importlib_resources - _logger = logging.getLogger(__name__) @@ -543,13 +540,13 @@ def test(self) -> int: def __init__(self, func: Callable[..., PropertyT]): self._func = func - self._attr_name: Optional[str] = None + self._attr_name: str | None = None self.__doc__ = func.__doc__ def __set_name__(self, owner: Any, name: str) -> None: self._attr_name = name - def __get__(self, instance: Any, owner: Optional[Any] = None) -> PropertyT: + def __get__(self, instance: Any, owner: Any | None = None) -> PropertyT: if self._attr_name is None: # pragma: no cover raise TypeError("Cannot use cached_property instance without calling __set_name__ on it.") cache = instance.__dict__ diff --git a/src/nunavut/_version.py b/src/nunavut/_version.py index d8959a1c..b8585a48 100644 --- a/src/nunavut/_version.py +++ b/src/nunavut/_version.py @@ -7,6 +7,7 @@ .. autodata:: __version__ """ + import sys __version__ = "3.0.0.dev2" # please update NunavutConfigVersion.cmake if changing the major or minor version. diff --git a/src/nunavut/cli/__init__.py b/src/nunavut/cli/__init__.py index 9a370b47..41c97644 100644 --- a/src/nunavut/cli/__init__.py +++ b/src/nunavut/cli/__init__.py @@ -82,8 +82,7 @@ def _make_parser(parser_type: Type[ParserT]) -> ParserT: """ - epilog = textwrap.dedent( - """ + epilog = textwrap.dedent(""" Copyright (C) OpenCyphal Development Team Copyright Amazon.com Inc. or its affiliates. @@ -98,8 +97,7 @@ def _make_parser(parser_type: Type[ParserT]) -> ParserT: nnvg --outdir include --templates c_jinja -e .h dsdl/uavcan:node/7509.Heartbeat.1.0.dsdl ᓄᓇᕗᑦ - """ - ) + """) if not issubclass(parser_type, argparse.ArgumentParser): raise ValueError("parser_type must be a subclass of argparse.ArgumentParser") @@ -113,8 +111,7 @@ def _make_parser(parser_type: Type[ParserT]) -> ParserT: parser.add_argument( "target_files_or_root_namespace", nargs="*", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" One or more dsdl files to generate from. @@ -149,15 +146,13 @@ def _make_parser(parser_type: Type[ParserT]) -> ParserT: for all .dsdl files found. To disable this behavior use the --no-target-namespaces argument which will cause an error if a folder is provided as a target. - """ - ).lstrip(), + """).lstrip(), ) parser.add_argument( "--no-target-namespaces", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" If provided then all target paths must be to individual DSDL files and not folders. If set and a folder is provided as a target an error will be raised. @@ -167,16 +162,14 @@ def _make_parser(parser_type: Type[ParserT]) -> ParserT: path itself as the root namespace. see target_files_or_root_namespace for more information. - """ - ).lstrip(), + """).lstrip(), ) parser.add_argument( "--lookup-dir", "-I", action="append", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" List of other namespace directories containing data type definitions that are referred to from the target root namespace. For example, if you are reading a vendor-specific namespace, @@ -227,8 +220,7 @@ def _make_parser(parser_type: Type[ParserT]) -> ParserT: CYPHAL_PATH=/path/to/types nnvg animals:mammals/cats/Tabby.1.0.dsdl \\ plants:trees/conifers/Fir.1.0.dsdl - """ - ).lstrip(), + """).lstrip(), ) parser.add_argument("--outdir", "-O", default="nunavut_out", help="output directory") @@ -236,16 +228,14 @@ def _make_parser(parser_type: Type[ParserT]) -> ParserT: parser.add_argument( "--target-language", "-l", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Language support to install into the templates. If provided then the output extension (-e) can be inferred otherwise the output extension must be provided. - """ - ).lstrip(), + """).lstrip(), ) # +-----------------------------------------------------------------------+ @@ -254,68 +244,59 @@ def _make_parser(parser_type: Type[ParserT]) -> ParserT: extended_group = parser.add_argument_group( "extended options", - description=textwrap.dedent( - """ + description=textwrap.dedent(""" Additional options to control output generation. - """ - ).lstrip(), + """).lstrip(), ) extended_group.add_argument( "--templates-dir", "--templates", type=Path, - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Paths to a directory containing templates to use when generating code. Templates found under these paths will override the built-in templates for a given language. - """ - ).lstrip(), + """).lstrip(), ) extended_group.add_argument( "--support-templates-dir", "--support-templates", type=Path, - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Paths to a directory containing templates to use when generating support code. Templates found under these paths will override the built-in support templates for a given language. - """ - ).lstrip(), + """).lstrip(), ) extended_group.add_argument( "--fallback-to-builtin-templates", "-tfb", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Normally, if providing a custom templates directory, the built-in templates are not searched for. This option will cause the built-in templates to be searched for if a template is not found in the custom templates directory first. - """ - ).lstrip(), + """).lstrip(), ) extended_group.add_argument( "--index-file", type=Path, action="append", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Index-files are generated from special templates that are given access to all types in all namespaces. This is useful for generating files that are not specific to a single @@ -340,8 +321,7 @@ def _make_parser(parser_type: Type[ParserT]) -> ParserT: path/to/types/animal:cat.1.0.dsdl \\ path/to/types/animal:dog.1.0.dsdl - """ - ).lstrip(), + """).lstrip(), ) extended_group.add_argument( @@ -349,8 +329,7 @@ def _make_parser(parser_type: Type[ParserT]) -> ParserT: "--experimental-languages", "-Xlang", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Activate languages with unstable, experimental support. @@ -359,8 +338,7 @@ def _make_parser(parser_type: Type[ParserT]) -> ParserT: may change in a non-backwards-compatible way in future versions, or that it might not even work yet. - """ - ).lstrip(), + """).lstrip(), ) def extension_type(raw_arg: str) -> str: @@ -373,8 +351,7 @@ def extension_type(raw_arg: str) -> str: "--output-extension", "-e", type=extension_type, - help=textwrap.dedent( - """ + help=textwrap.dedent(""" The output extension for generated files. If target language is provided an extension is inferred based on language configuration. This option allows overriding this inference. @@ -385,16 +362,14 @@ def extension_type(raw_arg: str) -> str: If a dot (.) is omitted one will be added, therefore; `-e h` and `-e .h` will both result in an extension of `.h`. - """ - ).lstrip(), + """).lstrip(), ) extended_group.add_argument( "--generate-support", choices=["always", "never", "as-needed", "only"], default="as-needed", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Change the criteria used to enable or disable support code generation. as-needed (default) - generate support if it is needed. @@ -406,15 +381,13 @@ def extension_type(raw_arg: str) -> str: types of support code. Where `--omit-serialization-support` is set different types of support code may still be generated unless this option is set to `never`. - """ - ).lstrip(), + """).lstrip(), ) extended_group.add_argument( "--generate-namespace-types", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" If enabled this script will generate source for namespaces. All namespaces including and under the root namespace will be treated as a pseudo-type and the appropriate template will be used. The generator will @@ -423,23 +396,20 @@ def extension_type(raw_arg: str) -> str: the default value for the --namespace-output-stem argument and can be changed using that argument. - """ - ).lstrip(), + """).lstrip(), ) extended_group.add_argument( "--omit-serialization-support", "-pod", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" If provided then the types generated will be POD datatypes with no additional logic. By default types generated include serialization routines and additional support libraries, headers, or methods as needed. These additional support artifacts can be suppressed using the `--generate-support` option. - """ - ).lstrip(), + """).lstrip(), ) extended_group.add_argument( @@ -450,52 +420,45 @@ def extension_type(raw_arg: str) -> str: extended_group.add_argument( "--no-overwrite", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" By default, generated files will be silently overwritten by subsequent invocations of the generator. If this argument is specified an error will be raised instead preventing overwrites. - """ - ).lstrip(), + """).lstrip(), ) extended_group.add_argument( "--file-mode", default=0o444, type=lambda value: int(value, 0), - help=textwrap.dedent( - """ + help=textwrap.dedent(""" The file-mode each generated file is set to after it is created. Note that this value is interpreted using python auto base detection. Because of this, to provide an octal value, you'll need to prefix your literal with '0o' (e.g. --file-mode 0o664). - """ - ).lstrip(), + """).lstrip(), ) extended_group.add_argument( "--allow-unregulated-fixed-port-id", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Do not reject unregulated fixed port identifiers. This is a dangerous feature that must not be used unless you understand the risks. The background information is provided in the Cyphal specification. - """ - ).lstrip(), + """).lstrip(), ) extended_group.add_argument( "--embed-auditing-info", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" If set, generators are instructed to add additional information in the form of language-specific comments or meta-data to use when auditing source code generated by @@ -504,21 +467,18 @@ def extension_type(raw_arg: str) -> str: a type may be included with this option where these paths will be different depending on the server used to run nnvg. - """ - ).lstrip(), + """).lstrip(), ) extended_group.add_argument( "--omit-dependencies", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Disables the generation of dependent types. This is useful when setting up build rules for a project where the dependent types are generated separately. - """ - ).lstrip(), + """).lstrip(), ) # +-----------------------------------------------------------------------+ @@ -527,13 +487,11 @@ def extension_type(raw_arg: str) -> str: run_mode_group = parser.add_argument_group( "run mode options", - description=textwrap.dedent( - """ + description=textwrap.dedent(""" Options that control the operation mode of the script. - """ - ).lstrip(), + """).lstrip(), ) run_mode_group.add_argument("--verbose", "-v", action="count", help="verbosity level (-v, -vv)") @@ -547,8 +505,7 @@ def extension_type(raw_arg: str) -> str: "-j", type=int, default=0, - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Limits the number of subprocesses nnvg can use to parallelize type discovery and code generation. @@ -559,15 +516,13 @@ def extension_type(raw_arg: str) -> str: If set to 1 then no subprocesses will be used and all work will be done in th main process. - """ - ).lstrip(), + """).lstrip(), ) run_mode_group.add_argument( "--list-outputs", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Emit a semicolon-separated list of files. (implies --dry-run) Emits files that would be generated if invoked without --dry-run. @@ -580,15 +535,13 @@ def extension_type(raw_arg: str) -> str: using value-delimited formats. Use --list-format to control the output format including using json to avoid the need for an empty-value delimiter. - """ - ).lstrip(), + """).lstrip(), ) run_mode_group.add_argument( "--list-inputs", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Emit a semicolon-separated list of files. (implies --dry-run) @@ -601,16 +554,14 @@ def extension_type(raw_arg: str) -> str: --list-format to control the output format including using json to avoid the need for an empty-value delimiter. - """ - ).lstrip(), + """).lstrip(), ) run_mode_group.add_argument( "--list-configuration", "-lc", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Lists all configuration values resolved for the given arguments. Unlike --list-inputs and --list-outputs this command does *not* imply --dry-run but can be used in conjunction @@ -618,16 +569,14 @@ def extension_type(raw_arg: str) -> str: This option is only available if --list-format is set to json. - """ - ).lstrip(), + """).lstrip(), ) run_mode_group.add_argument( "--list-format", default="scsv", choices=["csv", "scsv", "json", "json-pretty"], - help=textwrap.dedent( - """ + help=textwrap.dedent(""" For commands that emit lists of files this option controls the format of the output. @@ -636,23 +585,20 @@ def extension_type(raw_arg: str) -> str: json - json formatted results json-pretty - json formatted results with indentation - """ - ).lstrip(), + """).lstrip(), ) run_mode_group.add_argument( "--list-to-file", type=Path, - help=textwrap.dedent( - """ + help=textwrap.dedent(""" If provided then the output of --list-outputs, --list-inputs, or --list-configuration will also be written to the file specified. If the file exists it will be overwritten. This utf-8-encoded file will be written in the format specified by --list-format even if --dry-run is set. - """ - ).lstrip(), + """).lstrip(), ) # +-----------------------------------------------------------------------+ @@ -661,46 +607,39 @@ def extension_type(raw_arg: str) -> str: ln_pp_group = parser.add_argument_group( "post-processing options", - description=textwrap.dedent( - """ + description=textwrap.dedent(""" This options are all deprecated and will be removed in a future release. - """ - ).lstrip(), + """).lstrip(), ) ln_pp_group.add_argument( "--trim-blocks", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" If this is set to True the first newline after a block in a template is removed (block, not variable tag!). - """ - ).lstrip(), + """).lstrip(), ) ln_pp_group.add_argument( "--lstrip-blocks", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" If this is set to True leading spaces and tabs are stripped from the start of a line to a block in templates. - """ - ).lstrip(), + """).lstrip(), ) ln_pp_group.add_argument( "--pp-max-emptylines", type=int, - help=textwrap.dedent( - """ + help=textwrap.dedent(""" If provided this will suppress generation of additional consecutive empty lines beyond the limit set by this argument. @@ -709,15 +648,13 @@ def extension_type(raw_arg: str) -> str: performance. Consider using a code formatter on the generated output to enforce whitespace rules instead. - """ - ).lstrip(), + """).lstrip(), ) ln_pp_group.add_argument( "--pp-trim-trailing-whitespace", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Enables a line post-processor that will elide all whitespace at the end of each line. @@ -726,15 +663,13 @@ def extension_type(raw_arg: str) -> str: performance. Consider using a code formatter on the generated output to enforce whitespace rules instead. - """ - ).lstrip(), + """).lstrip(), ) ln_pp_group.add_argument( "-pp-rp", "--pp-run-program", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Runs a program after each file is generated but before the file is set to read-only. @@ -746,22 +681,19 @@ def extension_type(raw_arg: str) -> str: nnvg --outdir include --templates c_jinja -e .h -pp-rp clang-format -pp-rpa=-i dsdl - """ - ).lstrip(), + """).lstrip(), ) ln_pp_group.add_argument( "-pp-rpa", "--pp-run-program-arg", action="append", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Additional arguments to provide to the program specified by --pp-run-program. The last argument will always be the path to the generated file. - """ - ).lstrip(), + """).lstrip(), ) # +-----------------------------------------------------------------------+ @@ -769,36 +701,31 @@ def extension_type(raw_arg: str) -> str: # +-----------------------------------------------------------------------+ ln_opt_group = parser.add_argument_group( "language options", - description=textwrap.dedent( - """ + description=textwrap.dedent(""" Options passed through to templates as `options` on the target language. Note that these arguments are passed though without validation, have no effect on the Nunavut library, and may or may not be appropriate based on the target language and generator templates in use. - """ - ).lstrip(), + """).lstrip(), ) ln_opt_group.add_argument( "--target-endianness", choices=["any", "big", "little"], - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Specify the endianness of the target hardware. This allows serialization logic to be optimized for different CPU architectures. - """ - ).lstrip(), + """).lstrip(), ) ln_opt_group.add_argument( "--omit-float-serialization-support", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Instruct support header generators to omit support for floating point operations in serialization routines. This will result in errors if floating point types are used, @@ -806,15 +733,13 @@ def extension_type(raw_arg: str) -> str: point types in your message definitions this option will avoid dead code or compiler errors in generated serialization logic. - """ - ).lstrip(), + """).lstrip(), ) ln_opt_group.add_argument( "--enable-serialization-asserts", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Instruct support header generators to generate language-specific assert statements as part of serialization routines. By default the serialization logic generated may make assumptions @@ -822,30 +747,26 @@ def extension_type(raw_arg: str) -> str: behavior. The alternative, for languages that do not support exception handling, is to use assertions designed to halt a program rather than execute undefined logic. - """ - ).lstrip(), + """).lstrip(), ) ln_opt_group.add_argument( "--enable-override-variable-array-capacity", action="store_true", - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Instruct support header generators to add the possibility to override max capacity of a variable length array in serialization routines. This option will disable serialization buffer checks and add conditional compilation statements which violates MISRA. - """ - ).lstrip(), + """).lstrip(), ) ln_opt_group.add_argument( "--language-standard", "-std", choices=["c11", "c17", "c23", "c++14", "cetl++14-17", "c++17", "c++17-pmr", "c++20", "c++20-pmr"], - help=textwrap.dedent( - """ + help=textwrap.dedent(""" For language generators that support different standards of their core language this option can be used to optimize the output. For example, C templates may generate slightly different @@ -853,8 +774,7 @@ def extension_type(raw_arg: str) -> str: documentation for built-in templates (https://nunavut.readthedocs.io/en/latest/docs/templates.html#built-in-template-guide). - """ - ).lstrip(), + """).lstrip(), ) ln_opt_group.add_argument( @@ -862,8 +782,7 @@ def extension_type(raw_arg: str) -> str: "-c", nargs="*", type=Path, - help=textwrap.dedent( - """ + help=textwrap.dedent(""" There is a set of built-in configuration for Nunavut that provides default values for known languages as documented `in the template guide @@ -886,8 +805,7 @@ def extension_type(raw_arg: str) -> str: Also see ``--list-to-file`` which writes this configuration to disk if combined with ``--list-configuration``. - """ - ).lstrip(), + """).lstrip(), ) ln_opt_group.add_argument( @@ -895,8 +813,7 @@ def extension_type(raw_arg: str) -> str: "-o", nargs="*", type=keyvalue, - help=textwrap.dedent( - """ + help=textwrap.dedent(""" Passes a key=value pair to the template as a language option overriding default options where they are specified. This is useful for passing options to the template that are not available as command-line arguments. For example, if you have a template that uses the @@ -909,8 +826,7 @@ def extension_type(raw_arg: str) -> str: This option is similar to the `--configuration` option but only applies to the options section of the target language and doesn't require a file to be created. - """ - ).lstrip(), + """).lstrip(), ) return parser diff --git a/src/nunavut/cli/parsers.py b/src/nunavut/cli/parsers.py index 9b88a7be..5b1ccaa3 100644 --- a/src/nunavut/cli/parsers.py +++ b/src/nunavut/cli/parsers.py @@ -252,16 +252,12 @@ def _post_process_args(self, args: argparse.Namespace) -> None: # Can't list configuration as csv. Has to be a structured return format. if args.list_configuration and args.list_format in ("scsv", "csv"): - self.error( - textwrap.dedent( - f""" + self.error(textwrap.dedent(f""" --list-format {args.list_format} is not supported for --list-configuration. Use a structured format like --list-format json to list configuration information. - """ - ) - ) + """)) def _parse_target_paths( self, target_files_or_root_namespace: Optional[List[str]], greedy: bool, error_if_folder: bool = False diff --git a/src/nunavut/jinja/extensions.py b/src/nunavut/jinja/extensions.py index a8a1ec85..217abe45 100644 --- a/src/nunavut/jinja/extensions.py +++ b/src/nunavut/jinja/extensions.py @@ -6,6 +6,7 @@ """ Jinja2 extensions for use with the Nunavut code generator. """ + import typing from nunavut.jinja.jinja2 import TemplateAssertionError, UndefinedError, nodes diff --git a/src/nunavut/lang/__init__.py b/src/nunavut/lang/__init__.py index 18a3e883..be205bc8 100644 --- a/src/nunavut/lang/__init__.py +++ b/src/nunavut/lang/__init__.py @@ -9,6 +9,7 @@ This package contains modules that provide specific support for generating source for various languages using templates. """ + import functools import logging import pathlib diff --git a/src/nunavut/lang/_common.py b/src/nunavut/lang/_common.py index 9b65b03d..f3610e99 100644 --- a/src/nunavut/lang/_common.py +++ b/src/nunavut/lang/_common.py @@ -8,17 +8,18 @@ This package contains modules that provide specific support for generating source for various languages using templates. """ + import functools import pathlib import re import typing import pydsdl + from nunavut._utilities import ResourceType from ._language import Language - # +-------------------------------------------------------------------------------------------------------------------+ # | GENERATORS # +-------------------------------------------------------------------------------------------------------------------+ @@ -458,11 +459,9 @@ def strop(self, token: str, token_type: str = "any") -> str: """ token_type_lower = token_type.lower() if token_type_lower == "all": - raise ValueError( - """Token type 'all' is reserved for patterns that apply to all other types. A single token + raise ValueError("""Token type 'all' is reserved for patterns that apply to all other types. A single token can't be all token types at once but it can be compatible with any type; perhaps you meant 'any'? - """ - ) + """) # we encode first. encoded = self._do_for_type_and_all(self._encode, token, token_type_lower, False) diff --git a/src/nunavut/lang/_config.py b/src/nunavut/lang/_config.py index 1d0b539e..fc8227d0 100644 --- a/src/nunavut/lang/_config.py +++ b/src/nunavut/lang/_config.py @@ -6,6 +6,7 @@ """ Logic for parsing language configuration. """ + import json import re import types diff --git a/src/nunavut/lang/_language.py b/src/nunavut/lang/_language.py index ca53740b..771537e8 100644 --- a/src/nunavut/lang/_language.py +++ b/src/nunavut/lang/_language.py @@ -8,6 +8,7 @@ This module contains the Language object and supporting types. """ + import abc import functools import importlib diff --git a/src/nunavut/lang/c/__init__.py b/src/nunavut/lang/c/__init__.py index 2b874bdc..1ea9d74e 100644 --- a/src/nunavut/lang/c/__init__.py +++ b/src/nunavut/lang/c/__init__.py @@ -22,7 +22,7 @@ template_language_test, template_volatile_filter, ) -from nunavut._utilities import YesNoDefault, cached_property, ResourceType +from nunavut._utilities import ResourceType, YesNoDefault, cached_property from nunavut.jinja.environment import Environment from nunavut.lang._common import IncludeGenerator, TokenEncoder, UniqueNameGenerator from nunavut.lang._language import Language as BaseLanguage @@ -994,7 +994,7 @@ def is_zero_cost_primitive(language: Language, t: pydsdl.PrimitiveType) -> bool: u32 = pydsdl.UnsignedIntegerType(32, pydsdl.PrimitiveType.CastMode.TRUNCATED) f16 = pydsdl.FloatType(16, pydsdl.PrimitiveType.CastMode.TRUNCATED) f32 = pydsdl.FloatType(32, pydsdl.PrimitiveType.CastMode.SATURATED) - bl = pydsdl.BooleanType(pydsdl.PrimitiveType.CastMode.SATURATED) + bl = pydsdl.BooleanType() # and template = ( diff --git a/src/nunavut/lang/c/support/__init__.py b/src/nunavut/lang/c/support/__init__.py index 8ca64cb2..a42ab521 100644 --- a/src/nunavut/lang/c/support/__init__.py +++ b/src/nunavut/lang/c/support/__init__.py @@ -6,6 +6,7 @@ """ Contains supporting C headers to distribute with generated types. """ + import pathlib import typing diff --git a/src/nunavut/lang/cpp/__init__.py b/src/nunavut/lang/cpp/__init__.py index 55821663..d09f5528 100644 --- a/src/nunavut/lang/cpp/__init__.py +++ b/src/nunavut/lang/cpp/__init__.py @@ -8,7 +8,6 @@ module will be available in the template's global namespace as ``cpp``. """ - import fractions import functools import io @@ -20,12 +19,8 @@ import pydsdl from nunavut._dependencies import Dependencies -from nunavut._templates import ( - template_language_filter, - template_language_list_filter, - template_language_test, -) -from nunavut._utilities import YesNoDefault, cached_property, ResourceType +from nunavut._templates import template_language_filter, template_language_list_filter, template_language_test +from nunavut._utilities import ResourceType, YesNoDefault, cached_property from nunavut.jinja.environment import Environment from nunavut.lang._common import IncludeGenerator, TokenEncoder, UniqueNameGenerator from nunavut.lang._language import Language as BaseLanguage diff --git a/src/nunavut/lang/html/support/__init__.py b/src/nunavut/lang/html/support/__init__.py index 247761d7..3f425b93 100644 --- a/src/nunavut/lang/html/support/__init__.py +++ b/src/nunavut/lang/html/support/__init__.py @@ -6,6 +6,7 @@ """ Empty python package to ensure the support generator doesn't explode. """ + import pathlib import typing diff --git a/src/nunavut/lang/js/support/__init__.py b/src/nunavut/lang/js/support/__init__.py index d6121ad3..37d96f9a 100644 --- a/src/nunavut/lang/js/support/__init__.py +++ b/src/nunavut/lang/js/support/__init__.py @@ -6,6 +6,7 @@ """ Empty python package to ensure the support generator doesn't explode. """ + import pathlib import typing diff --git a/src/nunavut/lang/py/__init__.py b/src/nunavut/lang/py/__init__.py index 0f44819c..cd80a28b 100644 --- a/src/nunavut/lang/py/__init__.py +++ b/src/nunavut/lang/py/__init__.py @@ -7,6 +7,7 @@ Filters for generating python. All filters in this module will be available in the template's global namespace as ``py``. """ + from __future__ import annotations import base64 diff --git a/src/nunavut/lang/py/support/__init__.py b/src/nunavut/lang/py/support/__init__.py index 56d8b276..7ac4bb51 100644 --- a/src/nunavut/lang/py/support/__init__.py +++ b/src/nunavut/lang/py/support/__init__.py @@ -7,13 +7,11 @@ The contained support modules are not part of Nunavut, and one should not attempt to import them, as they may depend on modules that are not available in the local environment. """ + import pathlib import typing -from nunavut._utilities import ( - ResourceType, - empty_list_support_files, - iter_package_resources, -) + +from nunavut._utilities import ResourceType, empty_list_support_files, iter_package_resources __version__ = "1.0.0" """Version of the Python support module.""" diff --git a/test/gentest_lang/test_lang.py b/test/gentest_lang/test_lang.py index 5c447406..7ac0e81a 100644 --- a/test/gentest_lang/test_lang.py +++ b/test/gentest_lang/test_lang.py @@ -104,7 +104,7 @@ def ptest_lang_c( assert lang_c_output["ctype truncated uint64"] == "unsigned long long" assert lang_c_output["ctype saturated int64"] == "long long" - assert lang_c_output["ctype saturated bool"] == "bool" + assert lang_c_output["ctype bool"] == "bool" unique_name_evaluator(r"_nAME\d+_", lang_c_output["unique_name_0"]) unique_name_evaluator(r"_nAME\d+_", lang_c_output["unique_name_1"]) diff --git a/tox.ini b/tox.ini index 8ab25212..94035f25 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,8 @@ # -# The standard version to develop against is 3.11. +# The standard version to develop against is 3.11. Minimum supported version is 3.10. # [tox] -envlist = {py38,py39,py310,py311,py312,py313}-{test,nnvg,doctest,rstdoctest},lint,report,docs +envlist = {py310,py311,py312,py313}-{test,nnvg,doctest,rstdoctest},lint,report,docs [base] deps = @@ -99,7 +99,7 @@ warn_unused_ignores = True show_error_context = True mypy_path = src exclude = (jinja2|markupsafe) -python_version = 3.9 +python_version = 3.10 [mypy-pydsdl] ignore_missing_imports = True @@ -204,7 +204,7 @@ commands = --clear-cache-post-run=y \ --confidence=HIGH \ {toxinidir}/src/nunavut - black --check --line-length 120 --force-exclude '(/jinja2/|/markupsafe\/)' src + black --check --line-length 120 --force-exclude '(/jinja2/|/markupsafe\/)' {toxinidir}/src doc8 --ignore-path {toxinidir}/docs/cmake/build \ --ignore-path {toxinidir}/docs/cmake/external \ {toxinidir}/docs @@ -214,6 +214,17 @@ commands = --config-file {toxinidir}/tox.ini +[testenv:format] +basepython = python3.13 +deps = + {[dev]deps} + black + isort +commands = + isort --skip-glob '*/jinja2/*' --skip-glob '*/markupsafe/*' {toxinidir}/src/nunavut + black --line-length 120 --force-exclude '(/jinja2/|/markupsafe\/)' {toxinidir}/src + + [testenv:package] deps = build diff --git a/verification/python/noxfile.py b/verification/python/noxfile.py index ef5e8cea..dd5db7c0 100644 --- a/verification/python/noxfile.py +++ b/verification/python/noxfile.py @@ -10,7 +10,7 @@ import nox -PYTHONS = ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] +PYTHONS = ["3.10", "3.11", "3.12", "3.13"] nox.options.error_on_external_run = True