blob: e0878572d0536fef10b44309557eb19b5e80457e [file] [log] [blame]
#!/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')