[Geany] Error transformer

Laszlo Nagy gandalf at xxxxx
Sat Sep 15 08:24:35 UTC 2012


   Hi All,

I was writting a compiler for a new language, and I was having problems 
with Geany. Whenever I tried to run "make" (e.g. press F8 in geany) 
there were three cases:

* no error
* error in the compiler program -> error message was a Python traceback
* error in the source file being compiled by the compiler -> error 
message was a gnu formatted error message.

I asked about this problem here before: what kind of regular expression 
should I use so that Geany finds both errors? The answer was that it is 
almost impossible, because Geany can only handle a single regular 
expression.

Today I came up with another solution. It is a tiny wrapper program that 
can run any other command. It monitors the data that is flowing through 
stderr and stdout. It can run several different regular expressions on 
the output, and convert them into standardized format.

It is trivial to change this program so that you parse the compiler 
output from program code (instead of a regular expression). It allows 
you to parse the output of almost anything, including cases when the 
information is spread in multiple lines.

If anyone is interested, the "program" is attached. I'm not sure if it 
worth adding it to the Geany wiki.

Best,

    Laszlo

-------------- next part --------------
#!/usr/bin/env python

#
# Transform compiler messages into gnu format so that Geany can locate
# the files automatically. Inside Geany, use this error regex:
#
# ^([^:]+?):([0-9]+):.+
#
# You can change the "PATTERNS" constant below, to your needs.
#
#
import os
import sys
import re
import functools
from subprocess import Popen, PIPE
from threading  import Thread
try:
    from Queue import Queue, Empty
except ImportError:
    from queue import Queue, Empty  # python 3.x
ON_POSIX = 'posix' in sys.builtin_module_names


#
# Add new languages here with their own regular expressions here.
#
# "pat" is the pattern for parsing. All other fields are regex group
# numbers. You can also use constant strings instead of numbers (e.g.
# when a field is not available)
#
PATTERNS = {
    "python": {
        'pat': re.compile(
          r"""\s*File\s+"([^"]+)",\s+line\s+(\d+),\s+in\s+([^\s].*)"""),
        'fpath':  0, 'lineno': 1, 'level': 'E', 'message': 'Python error',
    }
}


def enqueue_output(out, queue):
    for line in iter(out.readline, b''):
        queue.put(line)
    out.close()


def transform(args, shell, outproc, errproc):
    """Process a command's output.

    @param args: Arguments to start the program.
    @param shell: Set True to execute it with the shell.
    @param outproc: Callback to process a line of stderr.
    @param errproc: Callback to process a line of stderr.
    @return: Exit code of the application.
    """
    proc = Popen(args, shell=shell, stdout=PIPE, stderr=PIPE,
        bufsize=1, close_fds=ON_POSIX)
    qo = Queue()
    to = Thread(target=enqueue_output, args=(proc.stdout, qo))
    to.daemon = True
    to.start()
    qe = Queue()
    te = Thread(target=enqueue_output, args=(proc.stderr, qe))
    te.daemon = True
    te.start()

    def proc_output(final=False):
        while True:
            try:
                line = qo.get_nowait()
                outproc(line)
            except Empty:
                pass

            try:
                line = qe.get_nowait()
                errproc(line)
            except Empty:
                pass

            if not final:
                break

            if qo.empty() and qe.empty():
                break

    while True:
        terminated = proc.poll() is not None
        if terminated:
            break
        proc_output()
    proc_output(True)

    return proc.returncode


def _get_pitem(res, props, propname):
    if propname in props:
        ret = props[propname]
        if isinstance(ret, int):
            return res[ret]
        else:
            return ret
    else:
        return ""


def procline(fout, s):
    global PATTERNS
    fout.write(s)
    fout.flush()
    for key, props in PATTERNS.iteritems():
        sre = props["pat"].match(s)
        if sre:
            res = sre.groups()
            fout.write(u'"%s":%s:%s:%s\n' % (
                _get_pitem(res, props, "fpath"),
                _get_pitem(res, props, "lineno"),
                _get_pitem(res, props, "level"),
                _get_pitem(res, props, "message"),
            ))
            fout.flush()

raise SystemExit(transform(sys.argv[1:], False,
    functools.partial(procline, sys.stdout),
    functools.partial(procline, sys.stderr),
))


More information about the Users mailing list