forked from project-chip/connectedhomeip
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdata_model_xml_parser.py
executable file
·181 lines (151 loc) · 5.6 KB
/
data_model_xml_parser.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#!/usr/bin/env python3
# Copyright (c) 2023 Project CHIP Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import os
import sys
from typing import Optional
import click
try:
from matter_idl.data_model_xml import ParseSource, ParseXmls
except ImportError:
sys.path.append(os.path.abspath(
os.path.join(os.path.dirname(__file__), '..')))
from matter_idl.data_model_xml import ParseSource, ParseXmls
from matter_idl.generators import GeneratorStorage
from matter_idl.generators.idl import IdlGenerator
from matter_idl.matter_idl_parser import CreateParser
from matter_idl.matter_idl_types import Idl
class InMemoryStorage(GeneratorStorage):
def __init__(self):
super().__init__()
self.content: Optional[str] = None
def get_existing_data(self, relative_path: str):
# Force re-generation each time
return None
def write_new_data(self, relative_path: str, content: str):
if self.content:
raise Exception(
"Unexpected extra data: single file generation expected")
self.content = content
def normalize_order(idl: Idl):
"""Re-sorts contents of things inside a cluster so that
output is easily diffed by humans
"""
# This method exists because `zapt` generation of IDL files
# are generally based on SQL select query ordering, likely
# with some sort fields to achieve determinism
#
# However overall, especially if manual editing, it seems
# easier to just fix a sort order instead of trying to
# match another tool ordering that resides in another
# code location.
idl.clusters.sort(key=lambda c: c.name)
for cluster in idl.clusters:
cluster.enums.sort(key=lambda e: e.name)
cluster.bitmaps.sort(key=lambda b: b.name)
cluster.events.sort(key=lambda e: e.code)
cluster.attributes.sort(key=lambda a: a.definition.code)
cluster.structs.sort(key=lambda s: s.name)
cluster.commands.sort(key=lambda c: c.code)
# Supported log levels, mapping string values required for argument
# parsing into logging constants
__LOG_LEVELS__ = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warn': logging.WARN,
'fatal': logging.FATAL,
}
@click.command()
@click.option(
'--log-level',
default='INFO',
show_default=True,
type=click.Choice(list(__LOG_LEVELS__.keys()), case_sensitive=False),
help='Determines the verbosity of script output.')
@click.option(
'--no-print',
show_default=True,
default=False,
is_flag=True,
help='Do not pring output data (parsed data)')
@click.option(
"-o",
"--output",
default=None,
type=click.Path(),
help="Where to output the parsed IDL."
)
@click.option(
"--compare",
default=None,
type=click.Path(exists=True),
help="An input .matter IDL to compare with."
)
@click.option(
"--compare-output",
default=None,
type=click.Path(),
help="Where to output the compare IDL"
)
@click.argument('filenames', nargs=-1)
def main(log_level, no_print, output, compare, compare_output, filenames):
"""
A program supporting parsing of CSA data model XML files and generating them
as human readable IDL output.
Also supports parsing and generating a diff against an existing .matter file,
such as using:
\b
./scripts/py_matter_idl/matter_idl/data_model_xml_parser.py \\
--compare src/controller/data_model/controller-clusters.matter \\
--compare-output out/orig.matter \\
--output out/from_xml.matter \\
data_model/clusters/Switch.xml
"""
logging.basicConfig(
level=__LOG_LEVELS__[log_level],
format='%(asctime)s %(levelname)-7s %(message)s',
)
if (compare is None) != (compare_output is None):
logging.error(
"Either both or none of --compare AND --compare-output must be set")
sys.exit(1)
logging.info("Starting to parse ...")
sources = [ParseSource(source=name) for name in filenames]
data = ParseXmls(sources)
logging.info("Parse completed")
if compare:
other_idl = CreateParser(skip_meta=True).parse(
open(compare).read(), file_name=compare)
# ensure that input file is filtered to only interesting
# clusters
loaded_clusters = set([c.code for c in data.clusters])
other_idl.clusters = [
c for c in other_idl.clusters if c.code in loaded_clusters]
# Ensure consistent ordering for compares
normalize_order(data)
normalize_order(other_idl)
storage = InMemoryStorage()
IdlGenerator(storage=storage, idl=other_idl).render(dry_run=False)
with open(compare_output, 'wt', encoding="utf8") as o:
o.write(storage.content)
storage = InMemoryStorage()
IdlGenerator(storage=storage, idl=data).render(dry_run=False)
if output:
with open(output, 'wt', encoding="utf8") as o:
o.write(storage.content)
elif not no_print:
print(storage.content)
if __name__ == '__main__':
main(auto_envvar_prefix='CHIP')