diff --git a/src/objdictgen/__main__.py b/src/objdictgen/__main__.py index f11682f..259cad6 100644 --- a/src/objdictgen/__main__.py +++ b/src/objdictgen/__main__.py @@ -30,14 +30,12 @@ import objdictgen from objdictgen import jsonod +from objdictgen.node import Node from objdictgen.printing import format_node from objdictgen.typing import TDiffEntries, TDiffNodes, TPath T = TypeVar('T') -if TYPE_CHECKING: - from objdictgen.node import Node - # Initalize the python logger to simply output to stdout log = logging.getLogger() log.setLevel(logging.INFO) @@ -79,7 +77,7 @@ def open_od(fname: TPath|str, validate=True, fix=False) -> "Node": """ Open and validate the OD file""" try: - od = objdictgen.LoadFile(fname) + od = Node.LoadFile(fname, validate=validate) if validate: od.Validate(fix=fix) @@ -145,6 +143,7 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None): # -- COMMON -- opt_debug = dict(action='store_true', help="Debug: enable tracebacks on errors") opt_od = dict(metavar='od', default=None, help="Object dictionary") + opt_novalidate = dict(action='store_true', help="Don't validate input files") parser.add_argument('--version', action='version', version='%(prog)s ' + objdictgen.__version__) parser.add_argument('--no-color', action='store_true', help="Disable colored output") @@ -175,8 +174,7 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None): help="Store in internal format (json only)") subp.add_argument('--no-sort', action="store_true", help="Don't order of parameters in output OD") - subp.add_argument('--novalidate', action="store_true", - help="Don't validate files before conversion") + subp.add_argument('--novalidate', **opt_novalidate) # type: ignore[arg-type] subp.add_argument('-D', '--debug', **opt_debug) # type: ignore[arg-type] # -- DIFF -- @@ -186,8 +184,7 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None): subp.add_argument('od1', **opt_od) # type: ignore[arg-type] subp.add_argument('od2', **opt_od) # type: ignore[arg-type] subp.add_argument('--internal', action="store_true", help="Diff internal object") - subp.add_argument('--novalidate', action="store_true", - help="Don't validate input files before diff") + subp.add_argument('--novalidate', **opt_novalidate) # type: ignore[arg-type] subp.add_argument('--show', action="store_true", help="Show difference data") subp.add_argument('-D', '--debug', **opt_debug) # type: ignore[arg-type] subp.add_argument('--no-color', action='store_true', help="Disable colored output") @@ -214,6 +211,7 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None): subp.add_argument('--unused', action="store_true", help="Include unused profile parameters") subp.add_argument('--internal', action="store_true", help="Show internal data") subp.add_argument('-D', '--debug', **opt_debug) # type: ignore[arg-type] + subp.add_argument('--novalidate', **opt_novalidate) # type: ignore[arg-type] subp.add_argument('--no-color', action='store_true', help="Disable colored output") # -- NETWORK -- @@ -270,7 +268,7 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None): # -- CONVERT command -- elif opts.command in ("convert", "conv", "gen"): - od = open_od(opts.od, fix=opts.fix) + od = open_od(opts.od, fix=opts.fix, validate=not opts.novalidate) to_remove: set[int] = set() @@ -347,7 +345,7 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None): if len(opts.od) > 1: print(Fore.LIGHTBLUE_EX + name + '\n' + "=" * len(name) + Style.RESET_ALL) - od = open_od(name) + od = open_od(name, validate=not opts.novalidate) for line in format_node(od, name, index=opts.index, opts=opts): print(line) diff --git a/src/objdictgen/jsonod.py b/src/objdictgen/jsonod.py index 758dadf..aa874a3 100644 --- a/src/objdictgen/jsonod.py +++ b/src/objdictgen/jsonod.py @@ -366,7 +366,7 @@ def generate_jsonc(node: "Node", compact=False, sort=False, internal=False, return text -def generate_node(contents: str|TODJson) -> "Node": +def generate_node(contents: str|TODJson, validate: bool = True) -> "Node": """ Import from JSON string or objects """ if isinstance(contents, str): @@ -394,14 +394,15 @@ def generate_node(contents: str|TODJson) -> "Node": with open(objdictgen.JSON_SCHEMA, 'r', encoding="utf-8") as f: SCHEMA = json.loads(remove_jsonc(f.read())) - if SCHEMA and jd.get('$version') == JSON_VERSION: + if validate and SCHEMA and jd.get('$version') == JSON_VERSION: jsonschema.validate(jd, schema=SCHEMA) # Get the object type mappings forwards (int to str) and backwards (str to int) objtypes_i2s, objtypes_s2i = get_object_types(dictionary=jd.get("dictionary", [])) # Validate the input json against for the OD format specifics - validate_fromdict(jd, objtypes_i2s, objtypes_s2i) + if validate: + validate_fromdict(jd, objtypes_i2s, objtypes_s2i) return node_fromdict(jd, objtypes_s2i) diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index 107183f..8be1220 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -171,7 +171,7 @@ def isEds(filepath: TPath) -> bool: return header == "[FileInfo]" @staticmethod - def LoadFile(filepath: TPath) -> "Node": + def LoadFile(filepath: TPath, **kwargs) -> "Node": """ Open a file and create a new node """ if Node.isXml(filepath): log.debug("Loading XML OD '%s'", filepath) @@ -184,12 +184,12 @@ def LoadFile(filepath: TPath) -> "Node": log.debug("Loading JSON OD '%s'", filepath) with open(filepath, "r", encoding="utf-8") as f: - return Node.LoadJson(f.read()) + return Node.LoadJson(f.read(), **kwargs) @staticmethod - def LoadJson(contents: str) -> "Node": + def LoadJson(contents: str, validate=True) -> "Node": """ Import a new Node from a JSON string """ - return jsonod.generate_node(contents) + return jsonod.generate_node(contents, validate=validate) def DumpFile(self, filepath: TPath, filetype: str|None = "jsonc", **kwargs): """ Save node into file """ diff --git a/tests/conftest.py b/tests/conftest.py index 45ede4f..f80fd92 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -31,6 +31,7 @@ # Files to exclude from testing all ODs OD_EXCLUDE: list[Path] = [ ODDIR / 'fail-validation.od', + ODDIR / 'schema-error.json', ] # Files to exclude from py2 legacy testing diff --git a/tests/od/schema-error.json b/tests/od/schema-error.json new file mode 100644 index 0000000..b6141a3 --- /dev/null +++ b/tests/od/schema-error.json @@ -0,0 +1,89 @@ +{ + "$id": "od data", + "$version": "1", + "$description": "Canfestival object dictionary data", + "$tool": "odg 3.4", + "$dates": "2024-02-27T00:27:21.144432", + "name": "master", + "description": "Empty master OD", + "type": "master", + "id": 0, + "profile": "None", + "dictionary": [ + { + "index": "0x1000", // 4096 + "name": "Device Type", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Device Type", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + }, + { + "index": "0x1001", // 4097 + "name": "Error Register", + "struct": "var", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Error Register", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": true, + "value": 0 + } + ] + }, + { + "index": "0x1018", // 4120 + "name": "Identity", + "struct": "record", + "group": "built-in", + "mandatory": true, + "sub": [ + { + "name": "Number of Entries", + "type": "UNSIGNED8", // 5 + "access": "ro", + "pdo": false + }, + { + "name": "Vendor ID", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Product Code", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Revision Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + }, + { + "name": "Serial Number", + "type": "UNSIGNED32", // 7 + "access": "ro", + "pdo": false, + "value": 0 + } + ] + } + ] +} \ No newline at end of file