objdictgen 3.5__tar.gz → 3.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {objdictgen-3.5/src/objdictgen.egg-info → objdictgen-3.5.2}/PKG-INFO +19 -11
- {objdictgen-3.5 → objdictgen-3.5.2}/README.md +18 -10
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/__init__.py +8 -2
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/__main__.py +4 -3
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/jsonod.py +29 -6
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/node.py +8 -6
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/schema/od.schema.json +5 -1
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/ui/networkedit.py +7 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/ui/objdictedit.py +15 -6
- {objdictgen-3.5 → objdictgen-3.5.2/src/objdictgen.egg-info}/PKG-INFO +19 -11
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen.egg-info/SOURCES.txt +1 -0
- objdictgen-3.5.2/tests/test_installer.py +38 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/tests/test_jsonod.py +30 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/LICENSE +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/pyproject.toml +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/setup.cfg +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/config/DS-302.prf +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/config/DS-401.prf +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/config/DS-402.prf +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/config/DS-404.prf +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/config/DS-406.prf +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/config/DS-408.prf +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/config/DS-410.prf +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/config/DS-418.prf +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/config/DS-419.prf +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/eds_utils.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/gen_cfile.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/img/networkedit.ico +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/img/networkedit.png +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/maps.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/nodelist.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/nodemanager.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/nosis.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/py.typed +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/typing.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/ui/__init__.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/ui/commondialogs.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/ui/exception.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/ui/networkeditortemplate.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/ui/nodeeditortemplate.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen/ui/subindextable.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen.egg-info/dependency_links.txt +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen.egg-info/entry_points.txt +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen.egg-info/requires.txt +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/src/objdictgen.egg-info/top_level.txt +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/tests/test_imports.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/tests/test_knownfailures.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/tests/test_main.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/tests/test_maps.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/tests/test_node.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/tests/test_nodelist.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/tests/test_nodemanager.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/tests/test_nosis.py +0 -0
- {objdictgen-3.5 → objdictgen-3.5.2}/tests/test_odcompare.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: objdictgen
|
|
3
|
-
Version: 3.5
|
|
3
|
+
Version: 3.5.2
|
|
4
4
|
Summary: CanFestival Object Dictionary tool
|
|
5
5
|
Home-page: https://github.com/Laerdal/python-objdictgen
|
|
6
6
|
Author: Svein Seldal
|
|
@@ -61,8 +61,8 @@ Laerdal Medical fork for the canfestival library:
|
|
|
61
61
|
objdictgen is a tool to parse, view and manipulate files containing object
|
|
62
62
|
dictionary (OD). An object dictionary is entries with data and configuration
|
|
63
63
|
in CANopen devices. The `odg` executable is installed. It supports
|
|
64
|
-
reading and writing OD files in `.json` format, in legacy XML `.od`
|
|
65
|
-
files. It can generate c code for use with the canfestival library.
|
|
64
|
+
reading and writing OD files in `.json`/`.jsonc` format, in legacy XML `.od`
|
|
65
|
+
and `.eds` files. It can generate c code for use with the canfestival library.
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
## Install
|
|
@@ -119,14 +119,11 @@ descriptions, help with values and validate the file.
|
|
|
119
119
|
"json.schemas": [
|
|
120
120
|
{
|
|
121
121
|
"fileMatch": [
|
|
122
|
-
"**.
|
|
122
|
+
"**.jsonc"
|
|
123
123
|
],
|
|
124
124
|
"url": "./src/objdictgen/schema/od.schema.json"
|
|
125
125
|
}
|
|
126
126
|
],
|
|
127
|
-
"files.associations": {
|
|
128
|
-
"*.json": "jsonc"
|
|
129
|
-
}
|
|
130
127
|
```
|
|
131
128
|
|
|
132
129
|
## Conversion
|
|
@@ -134,7 +131,7 @@ descriptions, help with values and validate the file.
|
|
|
134
131
|
The recommended way to convert existing/legacy `.od` files to the new JSON
|
|
135
132
|
format is:
|
|
136
133
|
|
|
137
|
-
$ odg generate <file.od> <file.
|
|
134
|
+
$ odg generate <file.od> <file.jsonc> --fix --drop-unused [--nosort]
|
|
138
135
|
|
|
139
136
|
The `--fix` option might be necessary if the OD-file contains internal
|
|
140
137
|
inconsistencies. It is safe to run this option as it will not delete any active
|
|
@@ -145,10 +142,10 @@ parameter that might be used in the file.
|
|
|
145
142
|
## Motivation
|
|
146
143
|
|
|
147
144
|
The biggest improvement with the new tool over the original implementation is
|
|
148
|
-
the introduction of a new `.
|
|
149
|
-
The JSON format is well-known and easy to read. The tool
|
|
145
|
+
the introduction of a new `.jsonc` based format to store the object dictionary.
|
|
146
|
+
The JSON format is well-known and easy to read. The tool use jsonc,
|
|
150
147
|
allowing comments in the json file. `odg` will process the file in a repeatable
|
|
151
|
-
manner, making it possible support diffing of the `.
|
|
148
|
+
manner, making it possible support diffing of the `.jsonc` file output. `odg`
|
|
152
149
|
remains 100% compatible with the legacy `.od` format on both input and output.
|
|
153
150
|
|
|
154
151
|
The original objdictedit and objdictgen tool were written in legacy python 2 and
|
|
@@ -159,6 +156,17 @@ This tool is a fork from upstream canfestival-3-asc repo:
|
|
|
159
156
|
> https://github.com/Laerdal/canfestival-3-asc
|
|
160
157
|
|
|
161
158
|
|
|
159
|
+
## Making objdictedit excutable
|
|
160
|
+
|
|
161
|
+
To be able build an executable that can be run from anywhere:
|
|
162
|
+
|
|
163
|
+
$ pip install pyinstaller
|
|
164
|
+
$ pyinstaller packaging/objdictedit.spec
|
|
165
|
+
|
|
166
|
+
The file `dist/objdictedit.exe` can now be used anywhere. It does not need any
|
|
167
|
+
pre-installed software.
|
|
168
|
+
|
|
169
|
+
|
|
162
170
|
## License
|
|
163
171
|
|
|
164
172
|
Objdictgen has been based on the python tool included in CanFestival. This
|
|
@@ -18,8 +18,8 @@ Laerdal Medical fork for the canfestival library:
|
|
|
18
18
|
objdictgen is a tool to parse, view and manipulate files containing object
|
|
19
19
|
dictionary (OD). An object dictionary is entries with data and configuration
|
|
20
20
|
in CANopen devices. The `odg` executable is installed. It supports
|
|
21
|
-
reading and writing OD files in `.json` format, in legacy XML `.od`
|
|
22
|
-
files. It can generate c code for use with the canfestival library.
|
|
21
|
+
reading and writing OD files in `.json`/`.jsonc` format, in legacy XML `.od`
|
|
22
|
+
and `.eds` files. It can generate c code for use with the canfestival library.
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
## Install
|
|
@@ -76,14 +76,11 @@ descriptions, help with values and validate the file.
|
|
|
76
76
|
"json.schemas": [
|
|
77
77
|
{
|
|
78
78
|
"fileMatch": [
|
|
79
|
-
"**.
|
|
79
|
+
"**.jsonc"
|
|
80
80
|
],
|
|
81
81
|
"url": "./src/objdictgen/schema/od.schema.json"
|
|
82
82
|
}
|
|
83
83
|
],
|
|
84
|
-
"files.associations": {
|
|
85
|
-
"*.json": "jsonc"
|
|
86
|
-
}
|
|
87
84
|
```
|
|
88
85
|
|
|
89
86
|
## Conversion
|
|
@@ -91,7 +88,7 @@ descriptions, help with values and validate the file.
|
|
|
91
88
|
The recommended way to convert existing/legacy `.od` files to the new JSON
|
|
92
89
|
format is:
|
|
93
90
|
|
|
94
|
-
$ odg generate <file.od> <file.
|
|
91
|
+
$ odg generate <file.od> <file.jsonc> --fix --drop-unused [--nosort]
|
|
95
92
|
|
|
96
93
|
The `--fix` option might be necessary if the OD-file contains internal
|
|
97
94
|
inconsistencies. It is safe to run this option as it will not delete any active
|
|
@@ -102,10 +99,10 @@ parameter that might be used in the file.
|
|
|
102
99
|
## Motivation
|
|
103
100
|
|
|
104
101
|
The biggest improvement with the new tool over the original implementation is
|
|
105
|
-
the introduction of a new `.
|
|
106
|
-
The JSON format is well-known and easy to read. The tool
|
|
102
|
+
the introduction of a new `.jsonc` based format to store the object dictionary.
|
|
103
|
+
The JSON format is well-known and easy to read. The tool use jsonc,
|
|
107
104
|
allowing comments in the json file. `odg` will process the file in a repeatable
|
|
108
|
-
manner, making it possible support diffing of the `.
|
|
105
|
+
manner, making it possible support diffing of the `.jsonc` file output. `odg`
|
|
109
106
|
remains 100% compatible with the legacy `.od` format on both input and output.
|
|
110
107
|
|
|
111
108
|
The original objdictedit and objdictgen tool were written in legacy python 2 and
|
|
@@ -116,6 +113,17 @@ This tool is a fork from upstream canfestival-3-asc repo:
|
|
|
116
113
|
> https://github.com/Laerdal/canfestival-3-asc
|
|
117
114
|
|
|
118
115
|
|
|
116
|
+
## Making objdictedit excutable
|
|
117
|
+
|
|
118
|
+
To be able build an executable that can be run from anywhere:
|
|
119
|
+
|
|
120
|
+
$ pip install pyinstaller
|
|
121
|
+
$ pyinstaller packaging/objdictedit.spec
|
|
122
|
+
|
|
123
|
+
The file `dist/objdictedit.exe` can now be used anywhere. It does not need any
|
|
124
|
+
pre-installed software.
|
|
125
|
+
|
|
126
|
+
|
|
119
127
|
## License
|
|
120
128
|
|
|
121
129
|
Objdictgen has been based on the python tool included in CanFestival. This
|
|
@@ -18,12 +18,15 @@
|
|
|
18
18
|
# USA
|
|
19
19
|
|
|
20
20
|
import os
|
|
21
|
+
import sys
|
|
21
22
|
from pathlib import Path
|
|
22
23
|
|
|
23
24
|
from objdictgen.node import Node
|
|
24
25
|
from objdictgen.nodemanager import NodeManager
|
|
25
26
|
|
|
26
|
-
__version__ = "3.5"
|
|
27
|
+
__version__ = "3.5.2"
|
|
28
|
+
__version_tuple__ = (3, 5, 2, 0)
|
|
29
|
+
__copyright__ = "(c) 2024 Svein Seldal, Laerdal Medical AS, and several. Licensed under GPLv2.1."
|
|
27
30
|
|
|
28
31
|
# Shortcuts
|
|
29
32
|
LoadFile = Node.LoadFile
|
|
@@ -31,7 +34,10 @@ LoadJson = Node.LoadJson
|
|
|
31
34
|
|
|
32
35
|
ODG_PROGRAM = "odg"
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
if hasattr(sys, '_MEIPASS'):
|
|
38
|
+
SCRIPT_DIRECTORY = Path(sys._MEIPASS) / 'objdictgen'
|
|
39
|
+
else:
|
|
40
|
+
SCRIPT_DIRECTORY = Path(__file__).resolve().parent
|
|
35
41
|
|
|
36
42
|
PROFILE_DIRECTORIES: list[Path] = [
|
|
37
43
|
SCRIPT_DIRECTORY / 'config'
|
|
@@ -220,7 +220,7 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None):
|
|
|
220
220
|
subp.add_argument('-x', '--exclude', action="append", help="OD Index to exclude.")
|
|
221
221
|
subp.add_argument('-f', '--fix', action="store_true",
|
|
222
222
|
help="Fix any inconsistency errors in OD before generate output")
|
|
223
|
-
subp.add_argument('-t', '--type', choices=['od', 'eds', 'json', 'c'],
|
|
223
|
+
subp.add_argument('-t', '--type', choices=['od', 'eds', 'json', 'jsonc', 'c'],
|
|
224
224
|
help="Select output file type")
|
|
225
225
|
subp.add_argument('--drop-unused', action="store_true", help="Remove unused parameters")
|
|
226
226
|
subp.add_argument('--internal', action="store_true",
|
|
@@ -351,8 +351,9 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None):
|
|
|
351
351
|
|
|
352
352
|
# Write the data
|
|
353
353
|
od.DumpFile(opts.out,
|
|
354
|
-
filetype=opts.type,
|
|
355
|
-
|
|
354
|
+
filetype=opts.type,
|
|
355
|
+
# These additional options are only used for JSON output
|
|
356
|
+
sort=not opts.no_sort, internal=opts.internal, validate=not opts.novalidate
|
|
356
357
|
)
|
|
357
358
|
|
|
358
359
|
|
|
@@ -58,12 +58,13 @@ class ValidationError(Exception):
|
|
|
58
58
|
# 1 - Default JSON format
|
|
59
59
|
JSON_ID = "od data"
|
|
60
60
|
JSON_DESCRIPTION = "Canfestival object dictionary data"
|
|
61
|
+
JSON_SCHEMA = "https://raw.githubusercontent.com/Laerdal/python-objdictgen/main/src/objdictgen/schema/od.schema.json"
|
|
61
62
|
JSON_INTERNAL_VERSION = "0"
|
|
62
63
|
JSON_VERSION = "1"
|
|
63
64
|
|
|
64
65
|
# Output order in JSON file
|
|
65
66
|
JSON_TOP_ORDER = (
|
|
66
|
-
"$id", "$version", "$description", "$tool", "$date", "$schema",
|
|
67
|
+
"$id", "$version", "$description", "$schema", "$tool", "$date", "$schema",
|
|
67
68
|
"name", "description", "type", "id", "profile",
|
|
68
69
|
"default_string_size", "dictionary",
|
|
69
70
|
)
|
|
@@ -117,6 +118,7 @@ FIELDS_DATA_MUST = {
|
|
|
117
118
|
}
|
|
118
119
|
FIELDS_DATA_OPT = {
|
|
119
120
|
'$description', # info only
|
|
121
|
+
'$schema', # info only
|
|
120
122
|
'$tool', # info only
|
|
121
123
|
'$date', # info only
|
|
122
124
|
'id', # default 0
|
|
@@ -357,7 +359,8 @@ def compare_profile(profilename: TPath, params: ODMapping, menu: TProfileMenu|No
|
|
|
357
359
|
return False, False
|
|
358
360
|
|
|
359
361
|
|
|
360
|
-
def generate_jsonc(node: "Node", compact=False, sort=False, internal=False,
|
|
362
|
+
def generate_jsonc(node: "Node", compact=False, sort=False, internal=False,
|
|
363
|
+
validate=True, jsonc=True) -> str:
|
|
361
364
|
""" Export a JSONC string representation of the node """
|
|
362
365
|
|
|
363
366
|
# Get the dict representation
|
|
@@ -374,20 +377,37 @@ def generate_jsonc(node: "Node", compact=False, sort=False, internal=False, vali
|
|
|
374
377
|
text = json.dumps(jd, separators=(',', ': '), indent=2)
|
|
375
378
|
|
|
376
379
|
# Convert the special __ fields to jsonc comments
|
|
380
|
+
# Syntax: "__<field>: <value>"
|
|
377
381
|
text = re.sub(
|
|
378
382
|
r'^(\s*)"__(\w+)": "(.*)",?$',
|
|
379
|
-
|
|
383
|
+
# In regular json files, __* fields are omitted from output
|
|
384
|
+
# In jsonc files, the __* entry is converted to a comment:
|
|
385
|
+
# "// <field>: <value>"
|
|
386
|
+
r'\1// "\2": "\3"' if jsonc else '',
|
|
380
387
|
text,
|
|
381
388
|
flags=re.MULTILINE,
|
|
382
389
|
)
|
|
383
390
|
|
|
391
|
+
if jsonc:
|
|
392
|
+
# In jsonc the field is converted to "<field>, // <comment>"
|
|
393
|
+
repl = lambda m: m[1].replace('\\"', '"') + m[3] + m[2]
|
|
394
|
+
else:
|
|
395
|
+
# In json the field is converted to "<field>,"
|
|
396
|
+
repl = lambda m: m[1].replace('\\"', '"') + m[3]
|
|
397
|
+
|
|
384
398
|
# Convert the special @@ fields to jsonc comments
|
|
399
|
+
# Syntax: "@@<field>, // <comment>@@"
|
|
385
400
|
text = re.sub(
|
|
386
401
|
r'"@@(.*?)(\s*//.*?)?@@"(.*)$',
|
|
387
|
-
|
|
402
|
+
repl,
|
|
388
403
|
text,
|
|
389
404
|
flags=re.MULTILINE,
|
|
390
405
|
)
|
|
406
|
+
|
|
407
|
+
# In case the json contains empty lines, remove them
|
|
408
|
+
if not jsonc:
|
|
409
|
+
text = "\n".join(line for line in text.splitlines() if line.strip())
|
|
410
|
+
|
|
391
411
|
return text
|
|
392
412
|
|
|
393
413
|
|
|
@@ -501,6 +521,7 @@ def node_todict(node: "Node", sort=False, rich=True, internal=False, validate=Tr
|
|
|
501
521
|
'$id': JSON_ID,
|
|
502
522
|
'$version': JSON_INTERNAL_VERSION if internal else JSON_VERSION,
|
|
503
523
|
'$description': JSON_DESCRIPTION,
|
|
524
|
+
'$schema': JSON_SCHEMA,
|
|
504
525
|
'$tool': str(objdictgen.ODG_PROGRAM) + ' ' + str(objdictgen.__version__),
|
|
505
526
|
'$date': datetime.now().astimezone().isoformat(),
|
|
506
527
|
'name': node.Name,
|
|
@@ -722,7 +743,7 @@ def rearrage_for_json(obj: TODObjJson, node: "Node", objtypes_i2s: dict[int, str
|
|
|
722
743
|
|
|
723
744
|
# Add __type when rich format
|
|
724
745
|
if info and "type" not in sub:
|
|
725
|
-
sub["__type"] = objtypes_i2s.get(info["type"], info["type"])
|
|
746
|
+
sub["__type"] = f'@@"{objtypes_i2s.get(info["type"], info["type"])}" // {info["type"]}@@'
|
|
726
747
|
|
|
727
748
|
# Replace value
|
|
728
749
|
if rich and "value" in sub:
|
|
@@ -744,7 +765,8 @@ def rearrage_for_json(obj: TODObjJson, node: "Node", objtypes_i2s: dict[int, str
|
|
|
744
765
|
# Replace numeric type with string value
|
|
745
766
|
if rich and "type" in each:
|
|
746
767
|
# FIXME: The cast is to ensure mypy is able keep track
|
|
747
|
-
|
|
768
|
+
n = objtypes_i2s.get(cast(int, each["type"]), each["type"])
|
|
769
|
+
each["type"] = f'@@"{n}" // {each["type"]}@@'
|
|
748
770
|
|
|
749
771
|
# Rearrage order of 'sub' and 'each'
|
|
750
772
|
obj["sub"] = [
|
|
@@ -1148,6 +1170,7 @@ def validate_fromdict(jsonobj: TODJson, objtypes_i2s: dict[int, str], objtypes_s
|
|
|
1148
1170
|
# "type" (must)
|
|
1149
1171
|
# Y "dictionary" (must)
|
|
1150
1172
|
# "$description" (optional)
|
|
1173
|
+
# "$schema" (optional)
|
|
1151
1174
|
# "$tool" (optional)
|
|
1152
1175
|
# "$date" (optional)
|
|
1153
1176
|
# "id" (optional, default 0)
|
|
@@ -196,14 +196,14 @@ class Node(NodeProtocol):
|
|
|
196
196
|
""" Import a new Node from a JSON string """
|
|
197
197
|
return jsonod.generate_node(contents)
|
|
198
198
|
|
|
199
|
-
def DumpFile(self, filepath: TPath, filetype: str|None = "
|
|
199
|
+
def DumpFile(self, filepath: TPath, filetype: str|None = "jsonc", **kwargs):
|
|
200
200
|
""" Save node into file """
|
|
201
201
|
|
|
202
202
|
# Attempt to determine the filetype from the filepath
|
|
203
203
|
if not filetype:
|
|
204
204
|
filetype = Path(filepath).suffix[1:]
|
|
205
205
|
if not filetype:
|
|
206
|
-
filetype = "
|
|
206
|
+
filetype = "jsonc"
|
|
207
207
|
|
|
208
208
|
if filetype == 'od':
|
|
209
209
|
log.debug("Writing XML OD '%s'", filepath)
|
|
@@ -219,9 +219,11 @@ class Node(NodeProtocol):
|
|
|
219
219
|
f.write(content)
|
|
220
220
|
return
|
|
221
221
|
|
|
222
|
-
if filetype
|
|
222
|
+
if filetype in ('json', 'jsonc'):
|
|
223
223
|
log.debug("Writing JSON OD '%s'", filepath)
|
|
224
|
-
|
|
224
|
+
kw = kwargs.copy()
|
|
225
|
+
kw['jsonc'] = filetype == 'jsonc'
|
|
226
|
+
jdata = self.DumpJson(**kw)
|
|
225
227
|
with open(filepath, "w", encoding="utf-8") as f:
|
|
226
228
|
f.write(jdata)
|
|
227
229
|
return
|
|
@@ -234,10 +236,10 @@ class Node(NodeProtocol):
|
|
|
234
236
|
|
|
235
237
|
raise ValueError("Unknown file suffix, unable to write file")
|
|
236
238
|
|
|
237
|
-
def DumpJson(self, compact=False, sort=False, internal=False, validate=True) -> str:
|
|
239
|
+
def DumpJson(self, compact=False, sort=False, internal=False, validate=True, jsonc=True) -> str:
|
|
238
240
|
""" Dump the node into a JSON string """
|
|
239
241
|
return jsonod.generate_jsonc(
|
|
240
|
-
self, compact=compact, sort=sort, internal=internal, validate=validate
|
|
242
|
+
self, compact=compact, sort=sort, internal=internal, validate=validate, jsonc=jsonc
|
|
241
243
|
)
|
|
242
244
|
|
|
243
245
|
def asdict(self) -> dict[str, Any]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema",
|
|
3
|
-
"$id": "https://raw.githubusercontent.com/
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/Laerdal/python-objdictgen/main/src/objdictgen/schema/od.schema.json",
|
|
4
4
|
"title": "Object Dictionary",
|
|
5
5
|
"description": "A Object Dictionary description for canfestival",
|
|
6
6
|
"type": "object",
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"description": "Description about the file",
|
|
22
22
|
"type": "string"
|
|
23
23
|
},
|
|
24
|
+
"$schema": {
|
|
25
|
+
"description": "The schema that this file is based on",
|
|
26
|
+
"type": "string"
|
|
27
|
+
},
|
|
24
28
|
"$tool": {
|
|
25
29
|
"description": "Tool that generated this file (optional)",
|
|
26
30
|
"type": "string"
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
|
19
19
|
# USA
|
|
20
20
|
|
|
21
|
+
import ctypes
|
|
21
22
|
import getopt
|
|
22
23
|
import logging
|
|
23
24
|
import os
|
|
@@ -415,6 +416,12 @@ class NetworkEdit(NetworkEditorTemplate):
|
|
|
415
416
|
|
|
416
417
|
|
|
417
418
|
def uimain(project):
|
|
419
|
+
|
|
420
|
+
# Set the application ID for Windows taskbar
|
|
421
|
+
if sys.platform == "win32":
|
|
422
|
+
myappid = 'objdictgen.objdictedit.' + objdictgen.__version__
|
|
423
|
+
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
|
|
424
|
+
|
|
418
425
|
app = wx.PySimpleApp()
|
|
419
426
|
|
|
420
427
|
wx.InitAllImageHandlers()
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
|
19
19
|
# USA
|
|
20
20
|
|
|
21
|
+
import ctypes
|
|
21
22
|
import getopt
|
|
22
23
|
import logging
|
|
23
24
|
import os
|
|
@@ -196,9 +197,9 @@ class ObjdictEdit(NodeEditorTemplate):
|
|
|
196
197
|
|
|
197
198
|
def _init_ctrls(self, parent):
|
|
198
199
|
wx.Frame.__init__(
|
|
199
|
-
self, id=ID_OBJDICTEDIT, name=
|
|
200
|
+
self, id=ID_OBJDICTEDIT, name=self.title,
|
|
200
201
|
parent=parent, pos=wx.Point(149, 178), size=wx.Size(1000, 700),
|
|
201
|
-
style=wx.DEFAULT_FRAME_STYLE, title=
|
|
202
|
+
style=wx.DEFAULT_FRAME_STYLE, title=self.title,
|
|
202
203
|
)
|
|
203
204
|
self._init_utils()
|
|
204
205
|
self.SetClientSize(wx.Size(1000, 700))
|
|
@@ -228,6 +229,8 @@ class ObjdictEdit(NodeEditorTemplate):
|
|
|
228
229
|
self.SetStatusBar(self.HelpBar)
|
|
229
230
|
|
|
230
231
|
def __init__(self, parent, manager: NodeManager|None = None, filesopen: list[TPath]|None = None):
|
|
232
|
+
self.title = f"Object dictionary editor v{objdictgen.__version__}"
|
|
233
|
+
|
|
231
234
|
filesopen = filesopen or []
|
|
232
235
|
if manager is None:
|
|
233
236
|
NodeEditorTemplate.__init__(self, NodeManager(), True)
|
|
@@ -322,9 +325,9 @@ class ObjdictEdit(NodeEditorTemplate):
|
|
|
322
325
|
|
|
323
326
|
def RefreshTitle(self):
|
|
324
327
|
if self.FileOpened.GetPageCount() > 0:
|
|
325
|
-
self.SetTitle(f"
|
|
328
|
+
self.SetTitle(self.title + f" - {self.Manager.GetCurrentFilename()}")
|
|
326
329
|
else:
|
|
327
|
-
self.SetTitle(
|
|
330
|
+
self.SetTitle(self.title)
|
|
328
331
|
|
|
329
332
|
def RefreshCurrentIndexList(self):
|
|
330
333
|
selected = self.FileOpened.GetSelection()
|
|
@@ -424,7 +427,7 @@ class ObjdictEdit(NodeEditorTemplate):
|
|
|
424
427
|
|
|
425
428
|
with wx.FileDialog(
|
|
426
429
|
self, "Choose a file", directory, "",
|
|
427
|
-
"OD
|
|
430
|
+
wildcard="OD JSON file (*.jsonc;*.json)|*.jsonc;*.json|Legacy OD file (*.od)|*.od|EDS file (*.eds)|*.eds|All files|*.*",
|
|
428
431
|
style=wx.FD_OPEN | wx.FD_CHANGE_DIR,
|
|
429
432
|
) as dialog:
|
|
430
433
|
if dialog.ShowModal() != wx.ID_OK:
|
|
@@ -475,7 +478,7 @@ class ObjdictEdit(NodeEditorTemplate):
|
|
|
475
478
|
|
|
476
479
|
with wx.FileDialog(
|
|
477
480
|
self, "Choose a file", directory, filename,
|
|
478
|
-
wildcard="OD JSON file (*.json)|*.json
|
|
481
|
+
wildcard="OD JSON file (*.jsonc;*.json)|*.jsonc;*.json|Legacy OD file (*.od)|*.od|EDS file (*.eds)|*.eds",
|
|
479
482
|
style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT | wx.FD_CHANGE_DIR,
|
|
480
483
|
) as dialog:
|
|
481
484
|
if dialog.ShowModal() != wx.ID_OK:
|
|
@@ -608,6 +611,12 @@ class ObjdictEdit(NodeEditorTemplate):
|
|
|
608
611
|
|
|
609
612
|
|
|
610
613
|
def uimain(args):
|
|
614
|
+
|
|
615
|
+
# Set the application ID for Windows taskbar
|
|
616
|
+
if sys.platform == "win32":
|
|
617
|
+
myappid = 'objdictgen.objdictedit.' + objdictgen.__version__
|
|
618
|
+
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
|
|
619
|
+
|
|
611
620
|
app = wx.App()
|
|
612
621
|
|
|
613
622
|
wx.InitAllImageHandlers()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: objdictgen
|
|
3
|
-
Version: 3.5
|
|
3
|
+
Version: 3.5.2
|
|
4
4
|
Summary: CanFestival Object Dictionary tool
|
|
5
5
|
Home-page: https://github.com/Laerdal/python-objdictgen
|
|
6
6
|
Author: Svein Seldal
|
|
@@ -61,8 +61,8 @@ Laerdal Medical fork for the canfestival library:
|
|
|
61
61
|
objdictgen is a tool to parse, view and manipulate files containing object
|
|
62
62
|
dictionary (OD). An object dictionary is entries with data and configuration
|
|
63
63
|
in CANopen devices. The `odg` executable is installed. It supports
|
|
64
|
-
reading and writing OD files in `.json` format, in legacy XML `.od`
|
|
65
|
-
files. It can generate c code for use with the canfestival library.
|
|
64
|
+
reading and writing OD files in `.json`/`.jsonc` format, in legacy XML `.od`
|
|
65
|
+
and `.eds` files. It can generate c code for use with the canfestival library.
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
## Install
|
|
@@ -119,14 +119,11 @@ descriptions, help with values and validate the file.
|
|
|
119
119
|
"json.schemas": [
|
|
120
120
|
{
|
|
121
121
|
"fileMatch": [
|
|
122
|
-
"**.
|
|
122
|
+
"**.jsonc"
|
|
123
123
|
],
|
|
124
124
|
"url": "./src/objdictgen/schema/od.schema.json"
|
|
125
125
|
}
|
|
126
126
|
],
|
|
127
|
-
"files.associations": {
|
|
128
|
-
"*.json": "jsonc"
|
|
129
|
-
}
|
|
130
127
|
```
|
|
131
128
|
|
|
132
129
|
## Conversion
|
|
@@ -134,7 +131,7 @@ descriptions, help with values and validate the file.
|
|
|
134
131
|
The recommended way to convert existing/legacy `.od` files to the new JSON
|
|
135
132
|
format is:
|
|
136
133
|
|
|
137
|
-
$ odg generate <file.od> <file.
|
|
134
|
+
$ odg generate <file.od> <file.jsonc> --fix --drop-unused [--nosort]
|
|
138
135
|
|
|
139
136
|
The `--fix` option might be necessary if the OD-file contains internal
|
|
140
137
|
inconsistencies. It is safe to run this option as it will not delete any active
|
|
@@ -145,10 +142,10 @@ parameter that might be used in the file.
|
|
|
145
142
|
## Motivation
|
|
146
143
|
|
|
147
144
|
The biggest improvement with the new tool over the original implementation is
|
|
148
|
-
the introduction of a new `.
|
|
149
|
-
The JSON format is well-known and easy to read. The tool
|
|
145
|
+
the introduction of a new `.jsonc` based format to store the object dictionary.
|
|
146
|
+
The JSON format is well-known and easy to read. The tool use jsonc,
|
|
150
147
|
allowing comments in the json file. `odg` will process the file in a repeatable
|
|
151
|
-
manner, making it possible support diffing of the `.
|
|
148
|
+
manner, making it possible support diffing of the `.jsonc` file output. `odg`
|
|
152
149
|
remains 100% compatible with the legacy `.od` format on both input and output.
|
|
153
150
|
|
|
154
151
|
The original objdictedit and objdictgen tool were written in legacy python 2 and
|
|
@@ -159,6 +156,17 @@ This tool is a fork from upstream canfestival-3-asc repo:
|
|
|
159
156
|
> https://github.com/Laerdal/canfestival-3-asc
|
|
160
157
|
|
|
161
158
|
|
|
159
|
+
## Making objdictedit excutable
|
|
160
|
+
|
|
161
|
+
To be able build an executable that can be run from anywhere:
|
|
162
|
+
|
|
163
|
+
$ pip install pyinstaller
|
|
164
|
+
$ pyinstaller packaging/objdictedit.spec
|
|
165
|
+
|
|
166
|
+
The file `dist/objdictedit.exe` can now be used anywhere. It does not need any
|
|
167
|
+
pre-installed software.
|
|
168
|
+
|
|
169
|
+
|
|
162
170
|
## License
|
|
163
171
|
|
|
164
172
|
Objdictgen has been based on the python tool included in CanFestival. This
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
import pytest
|
|
3
|
+
from unittest import mock
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
@pytest.fixture
|
|
8
|
+
def setenvvar(monkeypatch):
|
|
9
|
+
with mock.patch.dict(os.environ, {"TEST": "foobar"}):
|
|
10
|
+
yield
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_filereplacer(basepath, wd, setenvvar):
|
|
14
|
+
|
|
15
|
+
sys.path.append(str(basepath / "packaging"))
|
|
16
|
+
os.chdir(basepath)
|
|
17
|
+
from filereplacer import convert
|
|
18
|
+
|
|
19
|
+
tests = [
|
|
20
|
+
(1, "Test data", "Test data"),
|
|
21
|
+
(2, "@@{name}", "objdictgen"),
|
|
22
|
+
(3, "@@{TEST}", "foobar"), # Read from the mocked environment variable
|
|
23
|
+
(4, "@@{nonexisting}", "non-existing"),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
for i, data, result in tests:
|
|
27
|
+
infile = wd / "test.txt"
|
|
28
|
+
outfile = wd / "out.txt"
|
|
29
|
+
with open(infile, "w", encoding="utf-8") as f:
|
|
30
|
+
f.write(data)
|
|
31
|
+
if i == 4:
|
|
32
|
+
with pytest.raises(KeyError):
|
|
33
|
+
convert(infile, outfile)
|
|
34
|
+
continue
|
|
35
|
+
else:
|
|
36
|
+
convert(infile, outfile)
|
|
37
|
+
with open(outfile, "r", encoding="utf-8") as f:
|
|
38
|
+
assert f.read() == result
|
|
@@ -130,3 +130,33 @@ def test_jsonod_timezone():
|
|
|
130
130
|
if m:
|
|
131
131
|
print(m[1])
|
|
132
132
|
assert m[1] == cmp + "+" + tz
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_jsonod_comments(odpath):
|
|
136
|
+
""" Test that the json file exports comments correctly. """
|
|
137
|
+
|
|
138
|
+
fname_jsonc = odpath / "jsonod-comments.jsonc"
|
|
139
|
+
fname_json = odpath / "jsonod-comments.json"
|
|
140
|
+
|
|
141
|
+
m1 = Node.LoadFile(fname_jsonc)
|
|
142
|
+
|
|
143
|
+
with open(fname_jsonc, "r") as f:
|
|
144
|
+
jsonc_data = f.read()
|
|
145
|
+
with open(fname_json, "r") as f:
|
|
146
|
+
json_data = f.read()
|
|
147
|
+
|
|
148
|
+
out = generate_jsonc(m1, compact=False, sort=False, internal=False, validate=True, jsonc=True)
|
|
149
|
+
|
|
150
|
+
# Compare the jsonc data with the generated data
|
|
151
|
+
for a, b in zip(jsonc_data.splitlines(), out.splitlines()):
|
|
152
|
+
if '"$date"' in a or '"$tool"' in a:
|
|
153
|
+
continue
|
|
154
|
+
assert a == b
|
|
155
|
+
|
|
156
|
+
out = generate_jsonc(m1, compact=False, sort=False, internal=False, validate=True, jsonc=False)
|
|
157
|
+
|
|
158
|
+
# Compare the json data with the generated data
|
|
159
|
+
for a, b in zip(json_data.splitlines(), out.splitlines()):
|
|
160
|
+
if '"$date"' in a or '"$tool"' in a:
|
|
161
|
+
continue
|
|
162
|
+
assert a == b
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|