From cd5cd5b83387a5246aa322150aebcbcc0670dd78 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 30 Mar 2021 15:18:27 -0700 Subject: [PATCH] Standardized error reporting formatting. --- openscad_docsgen/__init__.py | 13 ++++- openscad_docsgen/blocks.py | 85 +++++++++++++++++++++++++------- openscad_docsgen/imagemanager.py | 64 ++++++++++++------------ setup.py | 4 +- 4 files changed, 112 insertions(+), 54 deletions(-) diff --git a/openscad_docsgen/__init__.py b/openscad_docsgen/__init__.py index d5a54b4..ea15ed2 100644 --- a/openscad_docsgen/__init__.py +++ b/openscad_docsgen/__init__.py @@ -7,7 +7,7 @@ import os.path import argparse -from .blocks import DocsGenParser, DocsGenException +from .blocks import DocsGenParser, DocsGenException, errorlog def processFiles( files, docs_dir, @@ -19,6 +19,7 @@ def processFiles( gen_index=False, gen_topics=False, gen_cheat=False, + report=False, dump_tree=False ): fail = False @@ -33,7 +34,7 @@ def processFiles( print("{} is not readable.".format(infile)) fail = True if fail: - os.exit(-1) + sys.exit(-1) docsgen = DocsGenParser() for infile in files: @@ -54,8 +55,13 @@ def processFiles( docsgen.write_topics_file() if gen_index: docsgen.write_cheatsheet_file() + if dump_tree: docsgen.dump_full_tree() + if report: + errorlog.write_report() + if errorlog.has_errors: + sys.exit(-1) def main(): @@ -78,6 +84,8 @@ def main(): help='If given, generate TOC.md table of contents file.') parser.add_argument('-c', '--gen-cheat', action="store_true", help='If given, generate CheatSheet.md file with all Usage lines.') + parser.add_argument('-r', '--report', action="store_true", + help='If given, write all warnings and errors to docsgen_report.json') parser.add_argument('-d', '--dump-tree', action="store_true", help='If given, dumps the documentation tree for debugging.') parser.add_argument('srcfile', nargs='+', help='List of input source files.') @@ -95,6 +103,7 @@ def main(): gen_index=args.gen_index, gen_topics=args.gen_topics, gen_cheat=args.gen_cheat, + report=args.report, dump_tree=args.dump_tree ) diff --git a/openscad_docsgen/blocks.py b/openscad_docsgen/blocks.py index 597e7e4..36eafef 100644 --- a/openscad_docsgen/blocks.py +++ b/openscad_docsgen/blocks.py @@ -5,6 +5,7 @@ import re import sys import glob +import json import string import hashlib from collections import namedtuple @@ -122,9 +123,12 @@ def parse_links(self, line, controller): name = m.group(2) line = m.group(3) if name not in controller.items_by_name: - raise DocsGenException("Invalid Link {{{{{0}}}}} in file {1}, line {2}".format(name, self.origin.file, self.origin.line)) - item = controller.items_by_name[name] - oline += item.get_link(label=name, currfile=self.origin.file) + msg = "Invalid Link {{{{{0}}}}}".format(name) + errorlog.add_entry(self.origin.file, self.origin.line, msg, ErrorLog.FAIL) + oline += mkdn_esc(name) + else: + item = controller.items_by_name[name] + oline += item.get_link(label=name, currfile=self.origin.file) else: oline += mkdn_esc(line) line = "" @@ -192,8 +196,12 @@ def get_markdown(self, controller): items = [] for name in names: if name not in controller.items_by_name: - raise DocsGenException("Invalid Link '{0}' in file {1}, line {2}".format(name, self.origin.file, self.origin.line)) - items.append( controller.items_by_name[name] ) + msg = "Invalid Link '{0}'".format(name) + errorlog.add_entry(self.origin.file, self.origin.line, msg, ErrorLog.FAIL) + else: + item = controller.items_by_name[name] + if item is not self.parent: + items.append( item ) links = ", ".join( item.get_link(currfile=self.origin.file) for item in items ) out = [] out.append("**{}:** {}".format(mkdn_esc(self.title), mkdn_esc(links))) @@ -506,22 +514,21 @@ def _img_proc_done(self, req): print(req.status) sys.stdout.flush() return - out = "\n\n" + pfx = " " + out = "Failed OpenSCAD script:\n" + out += pfx + "Image: {}\n".format( os.path.basename(req.image_file) ) for line in req.echos: - out += line + "\n" + out += pfx + line + "\n" for line in req.warnings: - out += line + "\n" + out += pfx + line + "\n" for line in req.errors: - out += line + "\n" - out += "LibFile: {} Line: {} Image: {}\n".format( - req.src_file, req.src_line, os.path.basename(req.image_file) - ) - out += "------------------------------------------------------------------------------\n" + out += pfx + line + "\n" + out += pfx + ("-=" * 32) + "-\n" for line in req.script_lines: - out += line + "\n" - out += "------------------------------------------------------------------------------\n" - print(out, file=sys.stderr) - sys.exit(-1) + out += pfx + line + "\n" + out += pfx + ("=-" * 32) + "=" + print("", file=sys.stderr) + errorlog.add_entry(req.src_file, req.src_line, out, ErrorLog.FAIL) def get_markdown(self, controller): fileblock = self.parent @@ -568,6 +575,45 @@ def __init__(self, title, subtitle, body, origin, parent, meta="", docs_dir=""): super().__init__(title, subtitle, body, origin, parent=parent, meta=meta, docs_dir=docs_dir) +class ErrorLog(object): + NOTE = "notice" + WARN = "warning" + FAIL = "error" + + REPORT_FILE = "docsgen_report.json" + + def __init__(self): + self.errlist = [] + self.has_errors = False + self.badfiles = {} + + def add_entry(self, file, line, msg, level): + self.errlist.append( (file, line, msg, level) ) + self.badfiles[file] = 1 + print("!! {} at {}:{}: {}".format(level.upper(), file, line, msg) , file=sys.stderr) + if level == self.FAIL: + self.has_errors = True + + def write_report(self): + report = [ + { + "file": file, + "line": line, + "title": "DocsGen {}".format(level), + "message": msg, + "annotation_level": level + } + for file, line, msg, level in self.errlist + ] + with open(self.REPORT_FILE, "w") as f: + f.write(json.dumps(report, sort_keys=True, indent=4)) + + def file_has_errors(self, file): + return file in self.badfiles + +errorlog = ErrorLog() + + class DocsGenParser(object): _header_pat = re.compile("^// ([A-Z][A-Za-z0-9_&-]*( ?[A-Z][A-Za-z0-9_&-]*)?)(\([^)]*\))?:( .*)?$") RCFILE = ".openscad_gendocs_rc" @@ -834,8 +880,7 @@ def _parse_block(self, lines, line_num=0, src_file=None): line_num = self._skip_lines(lines, line_num) except DocsGenException as e: - print("{} at {}:{}".format(str(e), origin.file, origin.line), file=sys.stderr) - sys.exit(-1) + errorlog.add_entry(origin.file, origin.line, str(e), ErrorLog.FAIL) return line_num @@ -863,6 +908,8 @@ def parse_file(self, filename, commentless=False, images=True, test_only=False, else: imgmgr.purge_requests() if not test_only: + if errorlog.file_has_errors(filename): + self.file_hashes.pop(filename) self.write_hashes() def dump_tree(self, nodes, pfx="", maxdepth=6): diff --git a/openscad_docsgen/imagemanager.py b/openscad_docsgen/imagemanager.py index 40d1f70..b67b319 100644 --- a/openscad_docsgen/imagemanager.py +++ b/openscad_docsgen/imagemanager.py @@ -170,37 +170,39 @@ def process_request(self, req): for line in req.script_lines: f.write(line + "\n") - no_vp = True - for line in req.script_lines: - if "$vp" in line: - no_vp = False - - render_mode = req.render_mode - animate = req.animation_frames - if self.test_only: - render_mode = RenderMode.test_only - animate = None - - osc = OpenScadRunner( - script_file, - new_img_file, - animate=animate, - animate_duration=req.frame_ms, - imgsize=req.imgsize, - antialias=2, - orthographic=True, - camera=req.camera, - auto_center=no_vp, - view_all=no_vp, - show_edges=req.show_edges, - show_axes=req.show_axes, - render_mode=render_mode, - hard_warnings=no_vp - ) - osc.run() - osc.warnings = [line for line in osc.warnings if "Viewall and autocenter disabled" not in line] - - os.unlink(script_file) + try: + no_vp = True + for line in req.script_lines: + if "$vp" in line: + no_vp = False + + render_mode = req.render_mode + animate = req.animation_frames + if self.test_only: + render_mode = RenderMode.test_only + animate = None + + osc = OpenScadRunner( + script_file, + new_img_file, + animate=animate, + animate_duration=req.frame_ms, + imgsize=req.imgsize, + antialias=2, + orthographic=True, + camera=req.camera, + auto_center=no_vp, + view_all=no_vp, + show_edges=req.show_edges, + show_axes=req.show_axes, + render_mode=render_mode, + hard_warnings=no_vp + ) + osc.run() + osc.warnings = [line for line in osc.warnings if "Viewall and autocenter disabled" not in line] + + finally: + os.unlink(script_file) if not osc.good() or osc.warnings or osc.errors: osc.success = False diff --git a/setup.py b/setup.py index 10de0c3..3009aaa 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup -VERSION = "1.1.11" +VERSION = "1.2.0" with open('README.rst') as f: @@ -23,7 +23,7 @@ packages=['openscad_docsgen'], license='MIT License', classifiers=[ - 'Development Status :: 4 - Beta', + 'Development Status :: 5 - Stable', 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: Manufacturing',