From 9acb1f2fe26ba6c3633efcf70f85d8c3441965ac Mon Sep 17 00:00:00 2001 From: Roland Lutz Date: Sat, 5 Oct 2019 17:56:42 +0200 Subject: [PATCH] netlist: Add option for reporting errors/warnings in GUI dialog --- src/command/gnetlist.in | 11 +- src/command/netlist.py | 78 +++++++++++++- src/gaf/Makefile.am | 1 + src/gaf/netlist/reportgui.py | 244 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 331 insertions(+), 3 deletions(-) create mode 100644 src/gaf/netlist/reportgui.py diff --git a/src/command/gnetlist.in b/src/command/gnetlist.in index ded8d8e35..771405435 100644 --- a/src/command/gnetlist.in +++ b/src/command/gnetlist.in @@ -858,8 +858,8 @@ def main(): try: options, args = getopt.getopt( xorn.command.args, 'c:g:hil:L:m:o:O:p:qvV', [ - 'quiet', 'verbose', 'interactive', 'list-backends', - 'help', 'version']) + 'quiet', 'verbose', 'interactive', 'report-gui', + 'list-backends', 'help', 'version']) except getopt.GetoptError as e: xorn.command.invalid_arguments(e.msg) @@ -876,6 +876,7 @@ def main(): load_before_backend = [] load_after_backend = [] + report_gui = False list_backends_ = False for option, value in options: @@ -907,6 +908,8 @@ def main(): evaluate_at_startup.append(value) elif option == '-i' or option == '--interactive': interactive_mode = True + elif option == '--report-gui': + report_gui = True elif option == '--list-backends': list_backends_ = True @@ -927,6 +930,7 @@ Generate a netlist from one or more gEDA schematic files. -m FILE load Scheme file after loading backend -c EXPR evaluate Scheme expression at startup -i enter interactive Scheme REPL after loading + --report-gui report warnings and errors in GUI dialog --list-backends print a list of available netlist backends -h, --help help; this message -V, --version show version information @@ -1151,6 +1155,9 @@ GNU General Public License for more details. if guile_proc is not None and guile_proc.startswith('drc'): cmd.append('--ignore-errors') + if guile_proc == 'pcbfwd' or report_gui: + cmd.append('--report-gui') + if not quiet_mode: # gnetlist prints schematic names unless `-q' has been specified cmd.append('-v') diff --git a/src/command/netlist.py b/src/command/netlist.py index 364746dee..26b136ce2 100644 --- a/src/command/netlist.py +++ b/src/command/netlist.py @@ -17,7 +17,7 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -import getopt, gettext, os, sys +import cStringIO, getopt, gettext, os, sys from gettext import gettext as _ import xorn.command @@ -30,6 +30,10 @@ import gaf.netlist.slib APPEND, PREPEND = xrange(2) +report_gui_enabled = False +report_gui_buf = None +report_gui_stderr = None + def parse_bool(value): if value in ['disabled', 'no', 'n', 'off', 'false', '0']: return False @@ -202,6 +206,7 @@ Ignoring errors: sys.stdout.write("\n") sys.stdout.write(_("""\ Reporting errors: + --report-gui --show-error-coordinates --dont-show-error-coordinates [default] """)) @@ -243,7 +248,74 @@ def version(): sys.exit(0) +def enable_report_gui(): + global report_gui_enabled + global report_gui_buf + global report_gui_stderr + + if report_gui_enabled: + return + + sys.stderr.write(_("Redirecting warnings and errors to GUI dialog...\n")) + sys.stderr.flush() + + report_gui_enabled = True + report_gui_stderr = sys.stderr + report_gui_buf = cStringIO.StringIO() + sys.stderr = report_gui_buf + +def daemonize(fun): + try: + pid = os.fork() + if pid > 0: + # parent process: continue on + return + except OSError as e: + sys.stderr.write("fork: %s\n" % e.strerror) + # can't fork: just call fun() un-daemonized and continue on + fun() + return + + ### Don't return from here on! ### + + try: + os.chdir('/') + os.setsid() + try: + pid = os.fork() + if pid > 0: + # exit from intermediate process + os._exit(0) + except OSError, e: + sys.stderr.write("fork: %s\n" % e.strerror) + # can't fork second time: call fun() in intermediate process + + fun() + finally: + os._exit(0) + def main(): + try: + inner_main() + sys.exit(0) + except SystemExit as e: + if report_gui_enabled: + sys.stderr = report_gui_stderr + log = report_gui_buf.getvalue() + report_gui_buf.close() + import gaf.netlist.reportgui + daemonize( + lambda: gaf.netlist.reportgui.report_messages(e.code, log)) + raise + except KeyboardInterrupt: + raise + except: + if report_gui_enabled: + import gaf.netlist.reportgui + daemonize(lambda: gaf.netlist.reportgui.report_crash()) + raise + +def inner_main(): # TODO: this is totally hacky, re-do this correctly dirname = os.path.dirname(__file__) if dirname.endswith('/command'): @@ -349,6 +421,7 @@ def main(): 'hierarchy-netname-order=', 'ignore-errors', 'dont-ignore-errors', + 'report-gui', 'show-error-coordinates', 'dont-show-error-coordinates', 'list-backends', 'help', 'version']) @@ -433,6 +506,9 @@ def main(): elif option == '--dont-ignore-errors': ignore_errors = False + elif option == '--report-gui': + enable_report_gui() + elif option == '--show-error-coordinates': show_error_coordinates = True elif option == '--dont-show-error-coordinates': diff --git a/src/gaf/Makefile.am b/src/gaf/Makefile.am index 09a88eb05..fb47cb161 100644 --- a/src/gaf/Makefile.am +++ b/src/gaf/Makefile.am @@ -51,4 +51,5 @@ nobase_gafpyexec_PYTHON = \ netlist/pp_netattrib.py \ netlist/pp_power.py \ netlist/pp_slotting.py \ + netlist/reportgui.py \ netlist/slib.py diff --git a/src/gaf/netlist/reportgui.py b/src/gaf/netlist/reportgui.py new file mode 100644 index 000000000..0678956ef --- /dev/null +++ b/src/gaf/netlist/reportgui.py @@ -0,0 +1,244 @@ +# gaf.netlist - gEDA Netlist Extraction and Generation +# Copyright (C) 1998-2010 Ales Hvezda +# Copyright (C) 1998-2010 gEDA Contributors (see ChangeLog for details) +# Copyright (C) 2013-2019 Roland Lutz +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +## \namespace gaf.netlist.reportgui +## Report errors, warnings, and exceptions in a GUI dialog. + +import collections, cStringIO, gtk, pango, re, traceback +from gettext import gettext as _ +import xorn.config + +COLUMN_TYPE, \ +COLUMN_FILENAME, \ +COLUMN_WHAT, \ +COLUMN_MESSAGE = xrange(4) + +Issue = collections.namedtuple('Issue', [ + 'stock_id', 'filename', 'what', 'message']) + + +def build_window(message_type, stock_id, message, + widget, top_label = None, bottom_label = None): + image = gtk.Image() + image.set_from_stock(stock_id, gtk.ICON_SIZE_LARGE_TOOLBAR) + image.show() + + label = gtk.Label() + label.set_markup('%s' % message) + label.set_alignment(0., .5) + label.show() + + info_bar = gtk.InfoBar() + info_bar.set_message_type(message_type) + + content_area = info_bar.get_content_area() + content_area.pack_start(image, False, False, 0) + content_area.pack_start(label, True, True, 0) + + info_bar.show() + + scrolled_window = gtk.ScrolledWindow() + scrolled_window.add(widget) + scrolled_window.set_shadow_type(gtk.SHADOW_IN) + scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolled_window.show() + + close_button = gtk.Button(gtk.STOCK_CLOSE) + close_button.set_use_stock(True) + close_button.set_can_default(True) + close_button.connect('clicked', gtk.main_quit) + close_button.show() + + button_hbox = gtk.HBox(False, 4) + button_hbox.pack_end(close_button, False, False, 0) + button_hbox.show() + + dialog_vbox = gtk.VBox(False, 8) + dialog_vbox.set_border_width(8) + if top_label is not None: + dialog_vbox.pack_start(top_label, False, False, 0) + dialog_vbox.pack_start(scrolled_window, True, True, 0) + if bottom_label is not None: + dialog_vbox.pack_start(bottom_label, False, False, 0) + dialog_vbox.pack_start(button_hbox, False, False, 0) + dialog_vbox.show() + + top_vbox = gtk.VBox(False, 0) + top_vbox.pack_start(info_bar, False, False, 0) + top_vbox.pack_start(dialog_vbox, True, True, 0) + top_vbox.show() + + def key_press_event(window, event): + if event.keyval == gtk.keysyms.Escape: + gtk.main_quit() + + window = gtk.Window(gtk.WINDOW_TOPLEVEL) + window.set_title('gnetlist') + window.set_default_size(600, 400) + window.set_position(gtk.WIN_POS_CENTER) + window.connect('key-press-event', key_press_event) + window.connect('destroy', gtk.main_quit) + window.add(top_vbox) + window.show() + + +def report_messages(exit_code, log): + lines = log.split('\n') + if lines and lines[-1] == '': + del lines[-1] + + loading_re = re.compile('Loading schematic \[.*\]$') + message1_re = re.compile('(.*): (warning|error): (.*)') + message2_re = re.compile('(.*):(.*): (warning|error): (.*)') + + issues = [] + + for line in lines: + match = message2_re.match(line) + if match is not None: + if match.group(3) == 'warning': + stock_id = gtk.STOCK_DIALOG_WARNING + else: + stock_id = gtk.STOCK_DIALOG_ERROR + issues.append(Issue(stock_id = stock_id, + filename = match.group(1), + what = match.group(2), + message = match.group(4))) + continue + + match = message1_re.match(line) + if match is not None: + if match.group(2) == 'warning': + stock_id = gtk.STOCK_DIALOG_WARNING + else: + stock_id = gtk.STOCK_DIALOG_ERROR + issues.append(Issue(stock_id = stock_id, + filename = match.group(1), + what = '', + message = match.group(3))) + continue + + match = loading_re.match(line) + if match is not None: + continue + + issues.append(Issue(stock_id = '', + filename = '', + what = '', + message = line)) + + if exit_code == 0 and not issues: + return + + store = gtk.ListStore(str, str, str, str) + for issue in issues: + it = store.append() + store.set(it, COLUMN_TYPE, issue.stock_id, + COLUMN_FILENAME, issue.filename, + COLUMN_WHAT, issue.what, + COLUMN_MESSAGE, issue.message) + + tree_view = gtk.TreeView(store) + tree_view.show() + + # icon column + column = gtk.TreeViewColumn() + tree_view.append_column(column) + + renderer = gtk.CellRendererPixbuf() + column.pack_start(renderer, True) + column.add_attribute(renderer, "stock-id", COLUMN_TYPE) + + # filename column + column = gtk.TreeViewColumn() + column.set_resizable(True) + column.set_title(_("Filename")) + tree_view.append_column(column) + + renderer = gtk.CellRendererText() + column.pack_start(renderer, True) + column.add_attribute(renderer, "text", COLUMN_FILENAME) + + # what column + column = gtk.TreeViewColumn() + column.set_resizable(True) + column.set_title(_("Refdes")) + tree_view.append_column(column) + + renderer = gtk.CellRendererText() + column.pack_start(renderer, True) + column.add_attribute(renderer, "text", COLUMN_WHAT) + + # text column + column = gtk.TreeViewColumn() + column.set_resizable(True) + column.set_title(_("Text")) + tree_view.append_column(column) + + renderer = gtk.CellRendererText() + column.pack_start(renderer, True) + column.add_attribute(renderer, "text", COLUMN_MESSAGE) + + if exit_code == 0: + message_type = gtk.MESSAGE_INFO + stock_id = gtk.STOCK_DIALOG_INFO + message = _("There may be some issues with the netlist.") + else: + message_type = gtk.MESSAGE_WARNING + stock_id = gtk.STOCK_DIALOG_WARNING + message = _("There were errors while generating the netlist.") + + build_window(message_type, stock_id, message, tree_view) + + gtk.main() + return + + +def report_crash(): + s = cStringIO.StringIO() + traceback.print_exc(file = s) + + text_view = gtk.TextView() + text_view.set_editable(False) + text_view.set_cursor_visible(False) + text_view.get_buffer().set_text(s.getvalue()) + text_view.show() + + top_label = gtk.Label(_("Exception details:")) + top_label.set_alignment(0., .5) + top_label.show() + + bottom_label = gtk.Label() + bottom_label.set_markup(_("You can help improving gEDA by reporting this " + "to %s or\nsending an e-mail " + "to %s.") + % ('https://bugs.launchpad.net/geda', + 'https://bugs.launchpad.net/geda', + xorn.config.PACKAGE_BUGREPORT, + xorn.config.PACKAGE_BUGREPORT)) + bottom_label.set_alignment(0., .5) + bottom_label.show() + + build_window(gtk.MESSAGE_ERROR, + gtk.STOCK_DIALOG_ERROR, + _("The netlister encountered an internal error."), + text_view, top_label, bottom_label) + + gtk.main() + return -- 2.11.4.GIT