From 51e7a5f46653a057923a9337f44a9b2e0d2382ab Mon Sep 17 00:00:00 2001 From: Ken Lauer Date: Fri, 18 Aug 2023 15:14:10 -0700 Subject: [PATCH] ENH: add --disable-macros + passthrough context --- whatrecord/bin/parse.py | 22 +++++++++++++++++++++- whatrecord/macro.py | 26 ++++++++++++++++++++++++-- whatrecord/parse.py | 11 +++++++++-- whatrecord/tests/test_macro.py | 15 ++++++++++++++- 4 files changed, 68 insertions(+), 6 deletions(-) diff --git a/whatrecord/bin/parse.py b/whatrecord/bin/parse.py index 18f7155b..9619f98c 100644 --- a/whatrecord/bin/parse.py +++ b/whatrecord/bin/parse.py @@ -2,6 +2,9 @@ "whatrecord parse" is used to parse and interpret any of whatrecord's supported file formats, dumping the results to the console (standard output) in JSON format, by default. + +Unless ``--disable-macros`` is specified, all text will go through the macro +context as if the files were being loaded in an IOC shell. """ import argparse @@ -67,6 +70,12 @@ def build_arg_parser(parser=None): help="Macro to add, in the usual form ``macro=value,...``", ) + parser.add_argument( + "--disable-macros", + action="store_true", + help="Disable macro handling, leaving unexpanded macros in the output." + ) + parser.add_argument( "-o", "--output", type=str, @@ -105,6 +114,7 @@ def parse_from_cli_args( dbd: Optional[str] = None, standin_directory: Optional[List[str]] = None, macros: Optional[str] = None, + disable_macros: bool = False, use_gdb: bool = False, format: Optional[str] = None, expand: bool = False, @@ -116,6 +126,9 @@ def parse_from_cli_args( This variant uses the raw CLI arguments, mapping them on to those that `parse` expects. For programmatic usage, please use ``parse()`` directly. + Unless ``disable_macros`` is set, all text will go through the macro + context as if the files were being loaded in an IOC shell. + Parameters ---------- filename : str or pathlib.Path @@ -128,7 +141,11 @@ def parse_from_cli_args( A list of substitute directories of the form ``DirectoryA=DirectoryB``. macros : str, optional - Macro string to use when parsing the file. + Macro string to use when parsing the file. Ignored if + ``disable_macros`` is set. + + disable_macros : bool, optional + Disable macro handling, leaving unexpanded macros in the output. expand : bool, optional Expand a substitutions file. @@ -163,6 +180,7 @@ def parse_from_cli_args( dbd=dbd, standin_directories=standin_directories, macros=macros, + disable_macros=disable_macros, use_gdb=use_gdb, format=format, expand=expand, @@ -178,6 +196,7 @@ def main( output_format: str = "json", output: Optional[str] = None, macros: Optional[str] = None, + disable_macros: bool = False, use_gdb: bool = False, expand: bool = False, v3: bool = False, @@ -187,6 +206,7 @@ def main( dbd=dbd, standin_directory=standin_directory, macros=macros, + disable_macros=disable_macros, use_gdb=use_gdb, format=input_format, expand=expand, diff --git a/whatrecord/macro.py b/whatrecord/macro.py index 79487745..471a67da 100644 --- a/whatrecord/macro.py +++ b/whatrecord/macro.py @@ -35,6 +35,23 @@ def macros_from_string( return macro_context.define_from_string(macro_string) +class PassthroughMacroContext(MacroContext): + """ + A stand-in MacroContext which performs no macro expansion. + + That is, a pass-through/no-operation macro context. + """ + + def expand(self, value: str, **_) -> str: + return value + + def expand_by_line(self, contents: str, *, delimiter: str = "\n") -> str: + return delimiter.join( + line + for line in contents.splitlines() + ) + + @dataclasses.dataclass class _SerializedMacroContext: #: Show warnings @@ -43,6 +60,8 @@ class _SerializedMacroContext: string_encoding: str #: The macros, including any environment variables (if use_environment set). macros: Dict[str, str] + #: If the context is a passthrough one. + passthrough: bool RE_MACRO_KEY_SKIP = [] @@ -124,6 +143,7 @@ def _serialize_macro_context(ctx: MacroContext) -> Dict[str, Any]: show_warnings=ctx.show_warnings, string_encoding=ctx.string_encoding, macros=macros, + passthrough=isinstance(ctx, PassthroughMacroContext), ) ) @@ -134,7 +154,9 @@ def _deserialize_macro_context(info: Dict[str, Any]) -> MacroContext: _SerializedMacroContext, info ) - return MacroContext( + + cls = PassthroughMacroContext if obj.passthrough else MacroContext + return cls( show_warnings=obj.show_warnings, string_encoding=obj.string_encoding, use_environment=False, @@ -142,4 +164,4 @@ def _deserialize_macro_context(info: Dict[str, Any]) -> MacroContext: ) -__all__ = ["MacroContext", "macros_from_string"] +__all__ = ["MacroContext", "PassthroughMacroContext", "macros_from_string"] diff --git a/whatrecord/parse.py b/whatrecord/parse.py index d5dd6e1f..32294188 100644 --- a/whatrecord/parse.py +++ b/whatrecord/parse.py @@ -13,7 +13,7 @@ from .db import Database from .dbtemplate import TemplateSubstitution from .gateway import PVList as GatewayPVList -from .macro import MacroContext +from .macro import MacroContext, PassthroughMacroContext from .makefile import Makefile from .shell import LoadedIoc from .snl import SequencerProgram @@ -39,6 +39,7 @@ def parse( dbd: Optional[str] = None, standin_directories: Optional[Dict[str, str]] = None, macros: Optional[str] = None, + disable_macros: bool = False, use_gdb: bool = False, format: Optional[FileFormat] = None, expand: bool = False, @@ -67,6 +68,9 @@ def parse( macros : str, optional Macro string to use when parsing the file. + disable_macros : bool, optional + Disable macro handling, leaving unexpanded macros in the output. + expand : bool, optional Expand a substitutions file. @@ -95,7 +99,10 @@ def parse( filename = pathlib.Path(filename) # The shared macro context - used in different ways below: - macro_context = MacroContext(macro_string=macros or "") + if disable_macros: + macro_context = PassthroughMacroContext() + else: + macro_context = MacroContext(macro_string=macros or "") if format is None: format = FileFormat.from_filename(filename) diff --git a/whatrecord/tests/test_macro.py b/whatrecord/tests/test_macro.py index 49f24b14..f99e9b48 100644 --- a/whatrecord/tests/test_macro.py +++ b/whatrecord/tests/test_macro.py @@ -4,7 +4,7 @@ import pytest from .. import settings -from ..macro import MacroContext +from ..macro import MacroContext, PassthroughMacroContext def test_skip_keys(monkeypatch): @@ -46,8 +46,21 @@ def test_env_include_exclude(monkeypatch, include: bool): monkeypatch.setattr(settings, "MACRO_INCLUDE_ENV", include) ctx = MacroContext(use_environment=True) keys = set(apischema.serialize(MacroContext, ctx)["macros"]) + assert not apischema.serialize(MacroContext, ctx)["passthrough"] if not include: assert set(keys) == set() else: assert 0 < len(set(keys)) <= len(os.environ) + + +def test_passthrough(): + ctx = PassthroughMacroContext() + assert ctx.expand("$(ABC)") == "$(ABC)" + + serialized = apischema.serialize(MacroContext, ctx) + assert serialized["passthrough"] + + deserialized = apischema.deserialize(MacroContext, serialized) + assert isinstance(deserialized, PassthroughMacroContext) + assert deserialized.expand("$(ABC)") == "$(ABC)"