From 97cd07226db52e36384a2a87ef1e809b901d45e7 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 15 Jul 2022 15:14:04 +1000 Subject: [PATCH] Support JSON output for IDS results --- src/ifctester/ifctester/__main__.py | 19 ++++- src/ifctester/ifctester/reporter.py | 125 +++++++++++++--------------- 2 files changed, 75 insertions(+), 69 deletions(-) diff --git a/src/ifctester/ifctester/__main__.py b/src/ifctester/ifctester/__main__.py index bde6e103fa..5f741bef53 100644 --- a/src/ifctester/ifctester/__main__.py +++ b/src/ifctester/ifctester/__main__.py @@ -30,6 +30,12 @@ parser.add_argument("ifc", type=str, help="Path to an IFC") parser.add_argument( "-r", "--reporter", type=str, help="The reporting method to view audit results", default="Console" ) +parser.add_argument( + "--no-color", help="Disable colour output supported by Console reporting", action="store_true" +) +parser.add_argument( + "-o", "--output", help="Output file supported by Json reporting" +) args = parser.parse_args() start = time.time() @@ -40,4 +46,15 @@ start = time.time() specs.validate(ifc) print("Finished validating:", time.time() - start) start = time.time() -reporter.Console(specs).report() + +if args.reporter == "Console": + engine = reporter.Console(specs, use_colour=not args.no_color) +elif args.reporter == "Json": + engine = reporter.Json(specs) + +engine.report() + +if args.output: + engine.to_file(args.output) +else: + print(engine.to_string()) diff --git a/src/ifctester/ifctester/reporter.py b/src/ifctester/ifctester/reporter.py index 6678a7a8ae..6148526033 100644 --- a/src/ifctester/ifctester/reporter.py +++ b/src/ifctester/ifctester/reporter.py @@ -19,15 +19,25 @@ import os import sys import logging -import numpy as np -import ifcopenshell.util.placement -from bcf.v2.bcfxml import BcfXml -from bcf.v2 import data as bcf -class Console: - def __init__(self, ids, use_colour=True): +class Reporter: + def __init__(self, ids): self.ids = ids + + def report(self, ids): + pass + + def to_string(): + return "" + + def write(filepath): + pass + + +class Console(Reporter): + def __init__(self, ids, use_colour=True): + super().__init__(ids) self.use_colour = use_colour self.colours = { "red": "\033[1;31m", @@ -77,7 +87,6 @@ class Console: for applicability in specification.applicability: print(" " * 8 + applicability.to_string("applicability")) - if not total and specification.status is False: return @@ -99,7 +108,7 @@ class Console: def report_reason(self, reason, element): is_bold = False - for substring in reason.split("\""): + for substring in reason.split('"'): if is_bold: self.set_style("purple") else: @@ -115,76 +124,52 @@ class Console: sys.stdout.write("".join([self.colours[c] for c in colours])) -class JsonReporter: - def __init__(self, specifications): - self.specifications = specifications - self.results = [] +class Json(Reporter): + def __init__(self, ids): + super().__init__(ids) + self.results = {} def report(self): - for specification in self.specifications: - self.results.append(self.report_specification(specification)) + self.results["title"] = self.ids.info.get("title", "Untitled IDS") + self.results["specifications"] = [] + for specification in self.ids.specifications: + self.results["specifications"].append(self.report_specification(specification)) return self.results def report_specification(self, specification): - return {"status": specification.status} + requirements = [] + for requirement in specification.requirements: + requirements.append( + { + "description": requirement.to_string("requirement"), + "success": not requirement.failed_entities, + "failed_entities": [ + {"reason": requirement.failed_reasons[i], "element": str(e)} + for i, e in enumerate(requirement.failed_entities[0:10]) + ], + } + ) + total = len(specification.applicable_entities) + return { + "name": specification.name, + "status": specification.status, + "total_successes": total - len(specification.failed_entities), + "total": total, + "required": specification.minOccurs != 0, + "requirements": requirements, + } + def to_string(self): + import json -class SimpleHandler(logging.StreamHandler): - """Logging handler listing all cases in python list.""" + return json.dumps(self.results) - def __init__(self, report_valid=False): - """Logging handler listing all cases in python list. + def to_file(self, filepath): + import json - :param report_valid: True if you want to list all the compliant cases as well, defaults to False - :type report_valid: bool, optional - """ - logging.StreamHandler.__init__(self) - self.statements = [] - if report_valid: - self.setLevel(logging.DEBUG) - else: - self.setLevel(logging.ERROR) + with open(filepath, "w") as outfile: + return json.dump(self.results, outfile) - def emit(self, mymsg): - """Triggered on each use of logging with the Simple handler enabled. - - :param log_content: default logger message - :type log_content: string|dict - """ - self.statements.append(mymsg.msg) - - -class CsvHandler(logging.StreamHandler): - """Logging handler listing all cases in csv file.""" - - def __init__(self, filepath="./Report.csv", report_valid=False): - """Logging handler listing all cases in csv file. - - :param report_valid: True if you want to list all the compliant cases as well, defaults to False - :type report_valid: bool, optional - """ - import csv - - logging.StreamHandler.__init__(self) - if report_valid: - self.setLevel(logging.INFO) - else: - self.setLevel(logging.ERROR) - self.file = open(filepath, "w", encoding="UTF8", newline="") - self.csvwriter = csv.writer(self.file) - self.csvwriter.writerow(["guid", "result", "sentence"]) # header - - def emit(self, mymsg): - """Triggered on each use of logging with the Simple handler enabled. - - :param log_content: default logger message - :type log_content: string|dict - """ - # BUG bytes-like object is required, not 'str' - self.csvwriter.writerow(mymsg.msg) - - def flush(self): - self.file.close() class BcfHandler(logging.StreamHandler): @@ -212,6 +197,10 @@ class BcfHandler(logging.StreamHandler): """ def __init__(self, project_name="IDS Project", author="your@email.com", filepath=None, report_valid=False): + import numpy as np + import ifcopenshell.util.placement + from bcf.v2.bcfxml import BcfXml + from bcf.v2 import data as bcf logging.StreamHandler.__init__(self) if report_valid: