Source code for pyrtma.compile

"""pyrtma.compile Message Type Compiler"""

import pathlib
import re
import sys
from typing import List, Union, Dict

from .parser import Parser, ParserError, FileFormatError
from rich.traceback import install
import warnings
from pyrtma.compilers.python import PyDefCompiler
from pyrtma.compilers.javascript import JSDefCompiler
from pyrtma.compilers.matlab import MatlabDefCompiler
from pyrtma.compilers.c99 import CDefCompiler
from pyrtma.compilers.yaml import YAMLCompiler
from pyrtma.compilers.info import InfoCompiler

install(word_wrap=True, show_locals=True)


[docs] def compile( defs_files: List[str], # str, # change back to str after removing v1 compiler out_dir: str, out_name: str, python: bool = False, javascript: bool = False, matlab: bool = False, c_lang: bool = False, info: bool = False, combined: bool = False, debug: bool = False, validate_alignment: bool = True, auto_pad: bool = True, import_coredefs: bool = True, ): """compile message defs Args: defs_files (List[str]): Root YAML message defintion file to parse. List of C header file(s) will use v1 python compiler (deprecated) out_name (str): Output directory for compiled files. For v1 compiler (deprecated), full output filename. python (bool, optional): Output python .py file. Defaults to False. javascript (bool, optional): Output javascript .js file. Defaults to False. matlab (bool, optional): Output matlab .m file. Defaults to False. c_lang (bool, optional): Output C .h file. Defaults to False. info (bool, optional): Output info .txt file. Defaults to False. combined (bool, optional): Output combined YAML file. Defaults to False. debug (bool, optional): Debug mode. Defaults to False. validate_alignment(bool, optional): Validate message 64-bit alignment. Defaults to True. auto_pad(bool, optional): Automatically pad messages failing 64-bit alignment validation. Defaults to True. Has no effect if validate_alignment is False. import_coredefs(bool, optional): Automatically import pyrtma.core_defs. Defaults to True. Raises: FileFormatError: Issue with input file format FileExistsError: Output file is not a directory RuntimeError: Invalid output filename """ # determine if using v1 or v2 compiler file1_ext = pathlib.Path(defs_files[0]).suffix if file1_ext.lower() == ".h": compiler_version = 1 warnings.warn( "V1 message def .h compiler is deprecated and has been replaced by the V2 yaml compiler.", FutureWarning, ) elif file1_ext.lower() in [".yaml", ".yml"]: compiler_version = 2 if len(defs_files) > 1: raise FileFormatError("defs_file must be a single .yaml file") else: raise FileFormatError("Unexpected file type. defs_file must be a .yaml file.") if compiler_version == 1: from pyrtma.compilers.python_v1 import python_v1_compile print("Building V1 python message definitions...") python_v1_compile(include_files=defs_files, out_filename=out_dir) print("DONE.") return # else continue with compiler V2 defs_file = defs_files[0] parser = Parser( debug=debug, validate_alignment=validate_alignment, auto_pad=auto_pad, import_coredefs=import_coredefs, ) parser.parse(pathlib.Path(defs_file)) if debug: with open("parser.json", "w") as f: f.write(parser.to_json()) # Use the same directory and filename as the message defs file by default if out_dir == "": outpath = pathlib.Path(defs_file).parent.resolve().absolute() else: outpath = pathlib.Path(out_dir) if not outpath.is_dir(): raise FileExistsError(f"The output location must be a directory.") # Make the final output directory, but no anything above it. outpath.mkdir(parents=False, exist_ok=True) if out_name == "": filename = pathlib.Path(defs_file).stem else: filename = out_name # Check for any invalid names if re.search(r"[^a-zA-Z0-9_-]", filename): raise RuntimeError(f"Invalid out filename: {filename}") compiler: Union[ PyDefCompiler, JSDefCompiler, MatlabDefCompiler, CDefCompiler, YAMLCompiler, InfoCompiler, ] if python: print("Building python message definitions...") compiler = PyDefCompiler(parser, debug=debug) ext = ".py" output = outpath / (filename + ext) compiler.generate(output) if javascript: print("Building javascript message definitions...") compiler = JSDefCompiler(parser, debug=debug) ext = ".js" output = outpath / (filename + ext) compiler.generate(output) if matlab: print("Building matlab message definitions...") compiler = MatlabDefCompiler(parser, debug=debug) ext = ".m" output = outpath / (filename + ext) compiler.generate(output) if c_lang: print("Building C/C++ message definitions...") compiler = CDefCompiler(parser, filename=filename, debug=debug) ext = ".h" output = outpath / (filename + ext) compiler.generate(output) if combined: print("Building combined yaml file...") compiler = YAMLCompiler(parser, filename=filename, debug=debug) ext = ".yaml" output = outpath / (filename + "_combined" + ext) compiler.generate(output) if info: print("Building info file...") compiler = InfoCompiler(parser, filename=filename, debug=debug) ext = ".txt" output = outpath / (filename + ext) compiler.generate(output) print("DONE.")
[docs] def main() -> None: import argparse # subparser for include file, to parse compiler options first argparser1 = argparse.ArgumentParser( description="pyrtma Message Definition Compiler.", add_help=False, ) argparser1.add_argument( "-i", "-I", "--defs", nargs="*", # for backwards compatibility with v1 compiler. Remove in future version along with v1 compiler. dest="defs_files", help="YAML message defintion file to parse. C header file(s) will use v1 python compiler (deprecated)", ) # full arg parser argparser = argparse.ArgumentParser( parents=[argparser1], conflict_handler="resolve", add_help=True, ) argparser.add_argument( "--c", "--c_lang", dest="c_lang", action="store_true", help="Output C .h file", ) argparser.add_argument( "--python", "--py", dest="python", action="store_true", help="Output python .py file", ) argparser.add_argument( "--javascript", "--js", dest="javascript", action="store_true", help="Output javascript .js file", ) argparser.add_argument( "--matlab", "--mat", "--ml", dest="matlab", action="store_true", help="Output matlab .m file", ) argparser.add_argument( "--info", dest="info", action="store_true", help="Output info .txt file", ) argparser.add_argument( "--combined", dest="combined", action="store_true", help="Output combined yaml file", ) argparser.add_argument( "-o", "--out", default="", dest="out_dir", help="Output directory for compiled files. For v1 compiler (deprecated), full output filename.", ) argparser.add_argument( "-n", "--name", default="", dest="out_name", help="Output file(s) base name.", ) argparser.add_argument( "--debug", dest="debug", action="store_true", help="Debug compiler", ) # parse input file first to parse compiler opts in yaml file args_temp, _ = argparser1.parse_known_args() # Default compiler options COMPILER_OPTS: Dict[str, Union[int, float, bool, str]] = { "IMPORT_COREDEFS": True, "VALIDATE_ALIGNMENT": True, "AUTO_PAD": True, } # parse yaml compiler_options and replace defaults prior to parsing command line args try: defs_files = args_temp.defs_files if ( defs_files and len(defs_files) == 1 and pathlib.Path(defs_files[0]).suffix.lower() in [ ".yaml", ".yml", ] ): defs_file = defs_files[0] parser = Parser() compiler_options = parser.parse_compiler_options(pathlib.Path(defs_file)) for key, value in compiler_options.items(): COMPILER_OPTS[key] = value.value except (ParserError, FileNotFoundError) as e: print() msg = " ".join(str(arg) for arg in e.args) print(f"{e.__class__.__name__}: {msg}") print() if e.__cause__: print("Details:") msg = " ".join(str(arg) for arg in e.__cause__.args) print(f"\t{e.__cause__.__class__.__name__}: {msg}") sys.exit(1) except Exception: raise # defualt compiler opts to values from yaml argparser.add_argument( "--no_val_align", dest="validate_alignment", action="store_false", default=COMPILER_OPTS["VALIDATE_ALIGNMENT"], help="Disable 64-bit alignment validation", ) argparser.add_argument( "--no_auto_pad", dest="auto_pad", action="store_false", default=COMPILER_OPTS["AUTO_PAD"], help="Disable 64-bit alignment auto-padding", ) argparser.add_argument( "--no_core_import", dest="import_coredefs", action="store_false", default=COMPILER_OPTS["IMPORT_COREDEFS"], help="Disable auto-import of pyrtma.core_defs", ) # parse all options args = argparser.parse_args() try: # run compiler compile(**vars(args)) except (ParserError, FileNotFoundError) as e: print() msg = " ".join(str(arg) for arg in e.args) print(f"{e.__class__.__name__}: {msg}") print() if e.__cause__: print("Details:") msg = " ".join(str(arg) for arg in e.__cause__.args) print(f"\t{e.__cause__.__class__.__name__}: {msg}") sys.exit(1) except Exception: raise sys.exit(0)
if __name__ == "__main__": main()