Skip to content

Instantly share code, notes, and snippets.

@fish2000
Last active December 5, 2018 17:53
Show Gist options
  • Save fish2000/103061d3242ce9075f63a7ef2ffbff07 to your computer and use it in GitHub Desktop.
Save fish2000/103061d3242ce9075f63a7ef2ffbff07 to your computer and use it in GitHub Desktop.

Revisions

  1. fish2000 revised this gist Dec 5, 2018. 1 changed file with 15 additions and 25 deletions.
    40 changes: 15 additions & 25 deletions pycheckmate.py
    Original file line number Diff line number Diff line change
    @@ -18,8 +18,7 @@
    # locate these programs in the default python bin directory or to a full
    # path if the checker program is installed elsewhere.
    # - If for some reason you want to use the built-in sytax check when either
    # pychecker or pyflakes are installed, you may set TM_PYCHECKER to
    # "builtin".
    # pychecker or pyflakes are installed, set TM_PYCHECKER to "builtin".

    from __future__ import absolute_import, print_function

    @@ -29,7 +28,7 @@
    import traceback
    from html import escape

    __version__ = "2.0"
    __version__ = "2.0.2"

    PY3 = False
    if sys.version_info < (3, 0):
    @@ -61,16 +60,15 @@ def warning_link_urls():
    """ Compose all formatted warning URLs in a user-facing message: """
    one, two, three, four, five = format_warning_urls()
    return f"""
    <p class="warning">Please install {one}, {two}, {three}, {four} or {five} for more extensive code checking.</p>
    <p class="warning">Please install {one}, {two}, {three}, {four} or {five} to enable extensive code checking.</p>
    """.strip()

    # patterns to match output of checker programs
    PYCHECKER_RE = re.compile(r"^(?:\s*)(.*?\.pyc?):(\d+):(?:\s*)(.*)(?:\s*)$")

    def textmate_url(file, line=None, column=None):
    """ Compose a Textmate callback URL, for sending the cursor to a location
    within an active Textmate buffer:
    """
    within an active Textmate buffer: """
    url = f"txmt://open?url=file://{quote(file)}"
    if type(line) is int:
    url += f"&line={line}"
    @@ -185,26 +183,22 @@ def textmate_url(file, line=None, column=None):
    padding: 2px 0;
    }
    </style>
    </head>
    <body>
    """
    <body>"""

    HTML_HEADER_BODY = """
    <div id="body">
    <p><strong class="title">%s</strong></p>
    <p><strong>%s</strong></p>
    <br>
    <div id="output">
    """
    <p><strong class="title">%s</strong></p>
    <p><strong>%s</strong></p>
    <br>
    <div id="output">"""

    HTML_FOOTER = """
    </div>
    </div>
    </div>
    </body>
    </html>
    """
    </html>"""

    CHECKERS = ["pychecker", "pyflakes", "pylint", "pep8", "flake8"]

    @@ -372,14 +366,12 @@ def run_checker_program(checker_bin,
    whitespace += "<br>&nbsp;&nbsp;"
    number = int(idx) + 1

    print(f'''
    <div class="message">
    print(f'''<div class="message">
    <span class="number">{number:02}</span>
    <a href="{url}">{filename}:{lineno}</a>
    {whitespace}
    <span class="message-text">{message}</a>
    </div>
    ''')
    </div>''')

    idx += 1

    @@ -405,11 +397,9 @@ def run_checker_program(checker_bin,
    returncode = 'NULL'
    p.terminate()

    print(f'''
    <div id="exit-status">
    print(f'''<div id="exit-status">
    <br>Exit status: {returncode}
    </div>
    ''')
    </div>''')

    def main(script_path):
    checker_bin, checker_opts, checker_ver = find_checker_program()
  2. fish2000 revised this gist Dec 5, 2018. 1 changed file with 76 additions and 47 deletions.
    123 changes: 76 additions & 47 deletions pycheckmate.py
    Original file line number Diff line number Diff line change
    @@ -40,11 +40,29 @@
    basestring = str
    unicode = str

    PYCHECKER_URL = "http://pychecker.sourceforge.net/"
    PYFLAKES_URL = "http://divmod.org/projects/pyflakes"
    PYLINT_URL = "http://www.logilab.org/857"
    PEP8_URL = "http://pypi.python.org/pypi/pep8"
    FLAKE8_URL = "http://pypi.python.org/pypi/flake8/"
    warning_urls = {
    "PyChecker" : "http://pychecker.sourceforge.net/",
    "PyFlakes" : "http://divmod.org/projects/pyflakes",
    "PyLint" : "http://www.logilab.org/857",
    "PEP-8" : "http://pypi.python.org/pypi/pep8",
    "Flake8" : "http://pypi.python.org/pypi/flake8/"
    }

    def format_warning_urls():
    """ Format the warning URLs as necessary for TextMate to open them: """
    out = []
    for checker_name, checker_url in warning_urls.items():
    out.append(f"""
    <a href="javascript:TextMate.system('open {checker_url}', null)">{checker_name}</a>
    """.strip())
    return tuple(out)

    def warning_link_urls():
    """ Compose all formatted warning URLs in a user-facing message: """
    one, two, three, four, five = format_warning_urls()
    return f"""
    <p class="warning">Please install {one}, {two}, {three}, {four} or {five} for more extensive code checking.</p>
    """.strip()

    # patterns to match output of checker programs
    PYCHECKER_RE = re.compile(r"^(?:\s*)(.*?\.pyc?):(\d+):(?:\s*)(.*)(?:\s*)$")
    @@ -147,11 +165,33 @@ def textmate_url(file, line=None, column=None):
    }
    span.stderr { color: red; }
    p { margin: 0; padding: 2px 0; }
    p { margin: 0; }
    p.warning {
    padding: 0px;
    font-family: Consolas, Monaco;
    font-size: 13pt;
    border-top: 1px solid #333;
    border-bottom: 1px solid #333;
    border-left: 0px none;
    border-right: 0px none;
    margin: 1em;
    margin-left: 0;
    margin-right: 0;
    background-color: #EFEFEF;
    }
    div#output p {
    padding: 2px 0;
    }
    </style>
    </head>
    <body>
    """

    HTML_HEADER_BODY = """
    <div id="body">
    <p><strong class="title">%s</strong></p>
    <p><strong>%s</strong></p>
    @@ -210,9 +250,8 @@ def check_syntax(script_path):
    except SyntaxError as e:
    url = textmate_url(script_path, int(e.lineno),
    int(e.offset))
    print('<a href="%s">%s:%s</a> %s' % (url,
    escape(os.path.basename(script_path)),
    e.lineno, e.msg))
    script = escape(os.path.basename(script_path))
    print(f'<a href="{url}">{script}:{e.lineno}</a> {e.msg}')
    except:
    for line in traceback.format_exception(sys.exc_info()):
    stripped = line.lstrip()
    @@ -286,7 +325,7 @@ def find_checker_program():

    def run_checker_program(checker_bin,
    checker_opts,
    script_path):
    script_path, version_string):
    import subprocess
    basepath = os.getenv("TM_PROJECT_DIRECTORY")

    @@ -305,39 +344,43 @@ def run_checker_program(checker_bin,
    except subprocess.TimeoutExpired:
    p.kill()
    stdout, stderr = p.communicate(timeout=None)

    if stdout is None:
    stdout = b''
    if stderr is None:
    stderr = b''

    outlines = stdout.decode(UTF8_ENCODING).splitlines()
    issue_count = len(outlines)

    print(HTML_HEADER_BODY % (version_string,
    f'{issue_count} issues found'))

    idx = 0
    for line in stdout.decode(UTF8_ENCODING).splitlines():
    for line in outlines:
    match = PYCHECKER_RE.search(line)

    if match:
    filename, lineno, msg = match.groups()
    filename, lineno, message = match.groups()
    url = textmate_url(filename, int(lineno))
    if basepath is not None and filename.startswith(basepath):
    filename = filename[len(basepath)+1:]

    # naive linewrapping, but it seems to work well-enough
    add_br = ""
    if len(filename) + len(msg) > 80:
    add_br += "<br>&nbsp;&nbsp;"
    tpl = '''
    whitespace = ""
    if len(filename) + len(message) > 80:
    whitespace += "<br>&nbsp;&nbsp;"
    number = int(idx) + 1

    print(f'''
    <div class="message">
    <span class="number">%(number)02d</span>
    <a href="%(url)s">%(filename)s:%(lineno)d</a>
    %(whitespace)s
    <span class="message-text">%(message)s</a>
    <span class="number">{number:02}</span>
    <a href="{url}">{filename}:{lineno}</a>
    {whitespace}
    <span class="message-text">{message}</a>
    </div>
    '''
    print(tpl % dict(url=url,
    filename=filename,
    lineno=int(lineno),
    whitespace=add_br,
    message=msg,
    number=int(idx) + 1))
    ''')

    idx += 1

    else:
    @@ -370,42 +413,28 @@ def run_checker_program(checker_bin,

    def main(script_path):
    checker_bin, checker_opts, checker_ver = find_checker_program()
    basepath = os.getenv("TM_PROJECT_DIRECTORY")
    version_string = f"PyCheckMate {__version__} &ndash; {checker_ver}"
    warning_string = ""

    if not checker_bin:
    href_format = \
    "<a href=\"javascript:TextMate.system('open %s', null)\">%s</a>"
    pychecker_url = href_format % (PYCHECKER_URL, "PyChecker")
    pyflakes_url = href_format % (PYFLAKES_URL, "PyFlakes")
    pylint_url = href_format % (PYLINT_URL, "Pylint")
    pep8_url = href_format % (PEP8_URL, "PEP 8")
    flake8_url = href_format % (FLAKE8_URL, "flake8")
    warning_string = \
    "<p>Please install %s, %s, %s, %s or %s for more extensive code checking." \
    "</p><br>" % (pychecker_url,
    pyflakes_url,
    pylint_url,
    pep8_url,
    flake8_url)
    warning_string += warning_link_urls()

    basepath = os.getenv("TM_PROJECT_DIRECTORY")
    if basepath:
    project_dir = os.path.basename(basepath)
    script_name = os.path.basename(script_path)
    title = f"{escape(script_name)} &mdash; {escape(project_dir)}"
    else:
    title = escape(script_path)

    print(HTML_HEADER_FORMAT % (title,
    version_string,
    'xx issues found'))
    print(HTML_HEADER_FORMAT % title)

    if warning_string:
    print(warning_string)

    run_checker_program(checker_bin,
    checker_opts,
    script_path)
    script_path, version_string)

    print(HTML_FOOTER)

  3. fish2000 revised this gist Dec 5, 2018. 1 changed file with 24 additions and 17 deletions.
    41 changes: 24 additions & 17 deletions pycheckmate.py
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,7 @@
    # PyCheckMate, a PyChecker output beautifier for TextMate.
    # Copyright (c) Jay Soffian, 2005. <jay at soffian dot org>
    # Inspired by Domenico Carbotta's PyMate.
    # Extensively overhauled for verions 2.0 by Alexander Böhn
    # Extensively overhauled for version 2.0 by Alexander Böhn.
    #
    # License: Artistic.
    #
    @@ -49,9 +49,17 @@
    # patterns to match output of checker programs
    PYCHECKER_RE = re.compile(r"^(?:\s*)(.*?\.pyc?):(\d+):(?:\s*)(.*)(?:\s*)$")

    # careful editing these, they are format strings
    TXMT_URL1_FORMAT = "txmt://open?url=file://%s&line=%s"
    TXMT_URL2_FORMAT = "txmt://open?url=file://%s&line=%s&column=%s"
    def textmate_url(file, line=None, column=None):
    """ Compose a Textmate callback URL, for sending the cursor to a location
    within an active Textmate buffer:
    """
    url = f"txmt://open?url=file://{quote(file)}"
    if type(line) is int:
    url += f"&line={line}"
    if type(column) is int:
    url += f"&column={column}"
    return url

    HTML_HEADER_FORMAT = """
    <html>
    <head>
    @@ -200,10 +208,9 @@ def check_syntax(script_path):
    compile(source, script_path, "exec")
    print("None<br>")
    except SyntaxError as e:
    href = TXMT_URL2_FORMAT % (quote(script_path),
    e.lineno,
    e.offset)
    print('<a href="%s">%s:%s</a> %s' % (href,
    url = textmate_url(script_path, int(e.lineno),
    int(e.offset))
    print('<a href="%s">%s:%s</a> %s' % (url,
    escape(os.path.basename(script_path)),
    e.lineno, e.msg))
    except:
    @@ -309,7 +316,7 @@ def run_checker_program(checker_bin,
    match = PYCHECKER_RE.search(line)
    if match:
    filename, lineno, msg = match.groups()
    href = TXMT_URL1_FORMAT % (quote(filename), lineno)
    url = textmate_url(filename, int(lineno))
    if basepath is not None and filename.startswith(basepath):
    filename = filename[len(basepath)+1:]

    @@ -320,17 +327,17 @@ def run_checker_program(checker_bin,
    tpl = '''
    <div class="message">
    <span class="number">%(number)02d</span>
    <a href="%(href)s">%(filename)s:%(lineno)d</a>
    <a href="%(url)s">%(filename)s:%(lineno)d</a>
    %(whitespace)s
    <span class="message-text">%(message)s</a>
    </div>
    '''
    print(tpl % dict(href=href,
    filename=filename,
    lineno=int(lineno),
    whitespace=add_br,
    message=msg,
    number=int(idx) + 1))
    print(tpl % dict(url=url,
    filename=filename,
    lineno=int(lineno),
    whitespace=add_br,
    message=msg,
    number=int(idx) + 1))
    idx += 1

    else:
    @@ -409,5 +416,5 @@ def main(script_path):
    if len(sys.argv) == 2:
    sys.exit(main(sys.argv[1]))
    else:
    print(f"Usage: {sys.argv[0]} <file.py>", file=sys.stderr)
    print(f"Usage: {os.path.basename(sys.argv[0])} <file.py>", file=sys.stderr)
    sys.exit(1)
  4. fish2000 revised this gist Nov 29, 2018. 1 changed file with 4 additions and 10 deletions.
    14 changes: 4 additions & 10 deletions pycheckmate.py
    Original file line number Diff line number Diff line change
    @@ -174,7 +174,10 @@ def which(binary_name, pathvar=None):
    """
    from distutils.spawn import find_executable
    if not hasattr(which, 'pathvar'):
    prefix_bin = os.path.join(sys.prefix, 'bin')
    executable_bin = os.path.split(sys.executable)[0]
    which.pathvar = os.getenv("PATH", DEFAULT_PATH)
    which.pathvar += f":{prefix_bin}:{executable_bin}"
    return find_executable(binary_name, pathvar or which.pathvar) or ""

    UTF8_ENCODING = 'UTF-8'
    @@ -225,16 +228,7 @@ def find_checker_program():
    for checker in CHECKERS:
    basename = os.path.split(checker)[1]
    if checker == basename:
    # look for checker in same bin directory as python --
    # it might be symlinked:
    bindir = os.path.split(sys.executable)[0]
    checker = os.path.join(bindir, basename)
    if not os.path.isfile(checker):
    # continue searching python's installation directory:
    checker = os.path.join(sys.prefix, "bin", basename)
    if not os.path.isfile(checker):
    # search the PATH
    checker = which(basename)
    checker = which(basename)

    if not os.path.isfile(checker):
    continue
  5. fish2000 revised this gist Nov 29, 2018. 1 changed file with 11 additions and 12 deletions.
    23 changes: 11 additions & 12 deletions pycheckmate.py
    Original file line number Diff line number Diff line change
    @@ -345,16 +345,16 @@ def run_checker_program(checker_bin,
    # THEY TOLD ME TO FLUSH THE PIPES SO I FLUSHED THE PIPES
    sys.stdout.flush()

    for line in stderr.decode(UTF8_ENCODING).splitlines():
    # strip whitespace off front and replace with &nbsp; so that
    # we can allow the browser to wrap long lines but we don't lose
    # leading indentation otherwise.
    stripped = line.lstrip()
    pad = "&nbsp;" * (len(line) - len(stripped))
    line = escape(stripped.rstrip())
    print(f'<span class="stderr">{pad}{line}</span><br>')

    sys.stdout.flush()
    if stderr:
    for line in stderr.decode(UTF8_ENCODING).splitlines():
    # strip whitespace off front and replace with &nbsp; so that
    # we can allow the browser to wrap long lines but we don't lose
    # leading indentation otherwise.
    stripped = line.lstrip()
    pad = "&nbsp;" * (len(line) - len(stripped))
    line = escape(stripped.rstrip())
    print(f'<span class="stderr">{pad}{line}</span><br>')
    sys.stdout.flush()

    returncode = p.returncode
    if returncode is None:
    @@ -398,7 +398,6 @@ def main(script_path):
    print(HTML_HEADER_FORMAT % (title,
    version_string,
    'xx issues found'))
    sys.stdout.flush()

    if warning_string:
    print(warning_string)
    @@ -408,8 +407,8 @@ def main(script_path):
    script_path)

    print(HTML_FOOTER)
    sys.stdout.flush()

    sys.stdout.flush()
    return 0

    if __name__ == "__main__":
  6. fish2000 revised this gist Nov 29, 2018. 1 changed file with 58 additions and 53 deletions.
    111 changes: 58 additions & 53 deletions pycheckmate.py
    Original file line number Diff line number Diff line change
    @@ -160,6 +160,7 @@

    CHECKERS = ["pychecker", "pyflakes", "pylint", "pep8", "flake8"]

    DEFAULT_TIMEOUT = 60 # seconds
    DEFAULT_PATH = ":".join(filter(os.path.exists, ("/usr/local/bin",
    "/bin", "/usr/bin",
    "/sbin", "/usr/sbin")))
    @@ -298,66 +299,71 @@ def run_checker_program(checker_bin,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE)

    while 1:
    stdout, stderr = p.communicate()
    if stdout is None:
    break
    try:
    stdout, stderr = p.communicate(timeout=DEFAULT_TIMEOUT)
    except subprocess.TimeoutExpired:
    p.kill()
    stdout, stderr = p.communicate(timeout=None)

    idx = 0
    for line in stdout.decode(UTF8_ENCODING).splitlines():
    match = PYCHECKER_RE.search(line)
    if match:
    filename, lineno, msg = match.groups()
    href = TXMT_URL1_FORMAT % (quote(filename), lineno)
    if basepath is not None and filename.startswith(basepath):
    filename = filename[len(basepath)+1:]

    # naive linewrapping, but it seems to work well-enough
    add_br = ""
    if len(filename) + len(msg) > 80:
    add_br += "<br>&nbsp;&nbsp;"
    tpl = '''
    <div class="message">
    <span class="number">%(number)02d</span>
    <a href="%(href)s">%(filename)s:%(lineno)d</a>
    %(whitespace)s
    <span class="message-text">%(message)s</a>
    </div>
    '''
    print(tpl % dict(href=href,
    filename=filename,
    lineno=int(lineno),
    whitespace=add_br,
    message=msg,
    number=int(idx) + 1),
    file=sys.stdout)
    idx += 1

    else:
    print(f'{line}<br>')
    if stdout is None:
    stdout = b''
    if stderr is None:
    stderr = b''

    idx = 0
    for line in stdout.decode(UTF8_ENCODING).splitlines():
    match = PYCHECKER_RE.search(line)
    if match:
    filename, lineno, msg = match.groups()
    href = TXMT_URL1_FORMAT % (quote(filename), lineno)
    if basepath is not None and filename.startswith(basepath):
    filename = filename[len(basepath)+1:]

    # THEY TOLD ME TO FLUSH THE PIPES SO I FLUSHED THE PIPES
    sys.stdout.flush()
    # naive linewrapping, but it seems to work well-enough
    add_br = ""
    if len(filename) + len(msg) > 80:
    add_br += "<br>&nbsp;&nbsp;"
    tpl = '''
    <div class="message">
    <span class="number">%(number)02d</span>
    <a href="%(href)s">%(filename)s:%(lineno)d</a>
    %(whitespace)s
    <span class="message-text">%(message)s</a>
    </div>
    '''
    print(tpl % dict(href=href,
    filename=filename,
    lineno=int(lineno),
    whitespace=add_br,
    message=msg,
    number=int(idx) + 1))
    idx += 1

    for line in stderr.decode(UTF8_ENCODING).splitlines():
    # strip whitespace off front and replace with &nbsp; so that
    # we can allow the browser to wrap long lines but we don't lose
    # leading indentation otherwise.
    stripped = line.lstrip()
    pad = "&nbsp;" * (len(line) - len(stripped))
    line = escape(stripped.rstrip())
    print(f'<span class="stderr">{pad}{line}</span><br>', file=sys.stderr)
    sys.stderr.flush()
    else:
    print(f'{line}<br>')

    break
    # THEY TOLD ME TO FLUSH THE PIPES SO I FLUSHED THE PIPES
    sys.stdout.flush()

    for line in stderr.decode(UTF8_ENCODING).splitlines():
    # strip whitespace off front and replace with &nbsp; so that
    # we can allow the browser to wrap long lines but we don't lose
    # leading indentation otherwise.
    stripped = line.lstrip()
    pad = "&nbsp;" * (len(line) - len(stripped))
    line = escape(stripped.rstrip())
    print(f'<span class="stderr">{pad}{line}</span><br>')

    sys.stdout.flush()

    returncode = p.returncode
    if returncode is None:
    returncode = 'NULL'
    p.terminate()

    print(f'''
    <div id="exit-status">
    <br>Exit status: {returncode or 'NULL'}
    <br>Exit status: {returncode}
    </div>
    ''')

    @@ -391,8 +397,7 @@ def main(script_path):

    print(HTML_HEADER_FORMAT % (title,
    version_string,
    'xx issues found'),
    file=sys.stdout)
    'xx issues found'))
    sys.stdout.flush()

    if warning_string:
    @@ -401,10 +406,10 @@ def main(script_path):
    run_checker_program(checker_bin,
    checker_opts,
    script_path)
    sys.stdout.flush()

    print(HTML_FOOTER, file=sys.stdout)
    print(HTML_FOOTER)
    sys.stdout.flush()

    return 0

    if __name__ == "__main__":
  7. fish2000 revised this gist Nov 29, 2018. 1 changed file with 7 additions and 7 deletions.
    14 changes: 7 additions & 7 deletions pycheckmate.py
    Original file line number Diff line number Diff line change
    @@ -199,9 +199,9 @@ def check_syntax(script_path):
    href = TXMT_URL2_FORMAT % (quote(script_path),
    e.lineno,
    e.offset)
    print(str('<a href="%s">%s:%s</a> %s' % (href,
    escape(os.path.basename(script_path)),
    e.lineno, e.msg)))
    print('<a href="%s">%s:%s</a> %s' % (href,
    escape(os.path.basename(script_path)),
    e.lineno, e.msg))
    except:
    for line in traceback.format_exception(sys.exc_info()):
    stripped = line.lstrip()
    @@ -324,11 +324,11 @@ def run_checker_program(checker_bin,
    <span class="message-text">%(message)s</a>
    </div>
    '''
    print(tpl % dict(href=str(href),
    filename=str(filename),
    print(tpl % dict(href=href,
    filename=filename,
    lineno=int(lineno),
    whitespace=str(add_br),
    message=str(msg),
    whitespace=add_br,
    message=msg,
    number=int(idx) + 1),
    file=sys.stdout)
    idx += 1
  8. fish2000 revised this gist Nov 29, 2018. 1 changed file with 28 additions and 26 deletions.
    54 changes: 28 additions & 26 deletions pycheckmate.py
    Original file line number Diff line number Diff line change
    @@ -52,7 +52,8 @@
    # careful editing these, they are format strings
    TXMT_URL1_FORMAT = "txmt://open?url=file://%s&line=%s"
    TXMT_URL2_FORMAT = "txmt://open?url=file://%s&line=%s&column=%s"
    HTML_HEADER_FORMAT = """<html>
    HTML_HEADER_FORMAT = """
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>PyCheckMate %s</title>
    @@ -150,7 +151,8 @@
    <div id="output">
    """

    HTML_FOOTER = """</div>
    HTML_FOOTER = """
    </div>
    </div>
    </body>
    </html>
    @@ -204,8 +206,8 @@ def check_syntax(script_path):
    for line in traceback.format_exception(sys.exc_info()):
    stripped = line.lstrip()
    pad = "&nbsp;" * (len(line) - len(stripped))
    line = str(escape(stripped.rstrip()))
    print('<span class="stderr">%s%s</span><br>' % (pad, line))
    line = escape(stripped.rstrip())
    print(f'<span class="stderr">{pad}{line}</span><br>')

    def find_checker_program():
    tm_pychecker = os.getenv("TM_PYCHECKER")
    @@ -237,44 +239,44 @@ def find_checker_program():
    continue

    if basename == "pychecker":
    with os.popen('"%s" -V 2>/dev/null' % (checker)) as p:
    with os.popen(f'"{checker}" -V 2>/dev/null') as p:
    version = p.readline().strip()
    if version:
    version = "PyChecker %s" % version
    version = f"PyChecker {version}"
    return (checker, opts, version)

    elif basename == "pylint":
    with os.popen('"%s" --version 2>/dev/null' % (checker)) as p:
    with os.popen(f'"{checker}" --version 2>/dev/null') as p:
    version = p.readline().strip()
    if version:
    version = re.sub('^pylint\s*', '', version)
    version = re.sub(',$', '', version)
    version = "Pylint %s" % version
    version = f"Pylint {version}"
    opts += ('--output-format=parseable',)
    return (checker, opts, version)

    elif basename == "pyflakes":
    # pyflakes doesn't have a version string embedded anywhere,
    # so run it against itself to make sure it's functional
    with os.popen('"%s" "%s" 2>&1 >/dev/null' % (checker, checker)) as p:
    with os.popen(f'"{checker}" "{checker}" 2>&1 >/dev/null') as p:
    output = p.readlines()
    if not output:
    return (checker, opts, "PyFlakes")

    elif basename == "pep8":
    with os.popen('"%s" --version 2>/dev/null' % (checker)) as p:
    with os.popen(f'"{checker}" --version 2>/dev/null') as p:
    version = p.readline().strip()
    if version:
    version = "PEP 8 %s" % version
    version = f"PEP 8 {version}"
    global PYCHECKER_RE
    PYCHECKER_RE = re.compile(r"^(.*?\.pyc?):(\d+):(?:\d+:)?\s+(.*)$")
    return (checker, opts, version)

    elif basename == "flake8":
    with os.popen('"%s" --version 2>/dev/null' % (checker)) as p:
    with os.popen(f'"{checker}" --version 2>/dev/null') as p:
    version = p.readline().strip()
    if version:
    version = "flake8 %s" % version
    version = f"flake8 {version}"
    PYCHECKER_RE = re.compile(r"^(.*?\.pyc?):(\d+):(?:\d+:)?\s+(.*)$")
    return (checker, opts, version)

    @@ -307,8 +309,8 @@ def run_checker_program(checker_bin,
    if match:
    filename, lineno, msg = match.groups()
    href = TXMT_URL1_FORMAT % (quote(filename), lineno)
    if basepath is not None and str(filename).startswith(basepath):
    filename = str(filename[len(basepath)+1:])
    if basepath is not None and filename.startswith(basepath):
    filename = filename[len(basepath)+1:]

    # naive linewrapping, but it seems to work well-enough
    add_br = ""
    @@ -344,7 +346,7 @@ def run_checker_program(checker_bin,
    stripped = line.lstrip()
    pad = "&nbsp;" * (len(line) - len(stripped))
    line = escape(stripped.rstrip())
    print('<span class="stderr">%s%s</span><br>' % (pad, line), file=sys.stderr)
    print(f'<span class="stderr">{pad}{line}</span><br>', file=sys.stderr)
    sys.stderr.flush()

    break
    @@ -353,15 +355,15 @@ def run_checker_program(checker_bin,
    if returncode is None:
    p.terminate()

    print('''
    print(f'''
    <div id="exit-status">
    <br>Exit status: %s
    <br>Exit status: {returncode or 'NULL'}
    </div>
    ''' % returncode or 'NULL')
    ''')

    def main(script_path):
    checker_bin, checker_opts, checker_ver = find_checker_program()
    version_string = "PyCheckMate %s &ndash; %s" % (__version__, checker_ver)
    version_string = f"PyCheckMate {__version__} &ndash; {checker_ver}"
    warning_string = ""
    if not checker_bin:
    href_format = \
    @@ -383,13 +385,13 @@ def main(script_path):
    if basepath:
    project_dir = os.path.basename(basepath)
    script_name = os.path.basename(script_path)
    title = "%s &mdash; %s" % (escape(script_name), escape(project_dir))
    title = f"{escape(script_name)} &mdash; {escape(project_dir)}"
    else:
    title = escape(script_path)

    print(str(HTML_HEADER_FORMAT % (title,
    version_string,
    'xx issues found')),
    print(HTML_HEADER_FORMAT % (title,
    version_string,
    'xx issues found'),
    file=sys.stdout)
    sys.stdout.flush()

    @@ -401,13 +403,13 @@ def main(script_path):
    script_path)
    sys.stdout.flush()

    print(str(HTML_FOOTER), file=sys.stdout)
    print(HTML_FOOTER, file=sys.stdout)
    sys.stdout.flush()
    return 0

    if __name__ == "__main__":
    if len(sys.argv) == 2:
    sys.exit(main(sys.argv[1]))
    else:
    print("Usage: %s <file.py>" % sys.argv[0], file=sys.stderr)
    print(f"Usage: {sys.argv[0]} <file.py>", file=sys.stderr)
    sys.exit(1)
  9. fish2000 revised this gist Nov 29, 2018. 1 changed file with 9 additions and 4 deletions.
    13 changes: 9 additions & 4 deletions pycheckmate.py
    Original file line number Diff line number Diff line change
    @@ -285,19 +285,22 @@ def run_checker_program(checker_bin,
    script_path):
    import subprocess
    basepath = os.getenv("TM_PROJECT_DIRECTORY")
    cmd = []
    cmd.append(checker_bin)

    cmd = [checker_bin]
    if checker_opts:
    cmd.extend(checker_opts)
    cmd.append(script_path)

    p = subprocess.Popen(cmd, shell=False,
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE)

    while 1:
    stdout, stderr = p.communicate()
    if stdout is None:
    break

    idx = 0
    for line in stdout.decode(UTF8_ENCODING).splitlines():
    match = PYCHECKER_RE.search(line)
    @@ -306,6 +309,7 @@ def run_checker_program(checker_bin,
    href = TXMT_URL1_FORMAT % (quote(filename), lineno)
    if basepath is not None and str(filename).startswith(basepath):
    filename = str(filename[len(basepath)+1:])

    # naive linewrapping, but it seems to work well-enough
    add_br = ""
    if len(filename) + len(msg) > 80:
    @@ -348,11 +352,12 @@ def run_checker_program(checker_bin,
    returncode = p.returncode
    if returncode is None:
    p.terminate()
    print(str('''

    print('''
    <div id="exit-status">
    <br>Exit status: %s
    </div>
    ''' % returncode or 'NULL'))
    ''' % returncode or 'NULL')

    def main(script_path):
    checker_bin, checker_opts, checker_ver = find_checker_program()
  10. fish2000 revised this gist Nov 29, 2018. 1 changed file with 7 additions and 3 deletions.
    10 changes: 7 additions & 3 deletions pycheckmate.py
    Original file line number Diff line number Diff line change
    @@ -346,9 +346,13 @@ def run_checker_program(checker_bin,
    break

    returncode = p.returncode
    print(str('<div id="exit-status"><br>Exit status: %s</div>' % returncode))
    if returncode is None:
    p.terminate()
    print(str('''
    <div id="exit-status">
    <br>Exit status: %s
    </div>
    ''' % returncode or 'NULL'))

    def main(script_path):
    checker_bin, checker_opts, checker_ver = find_checker_program()
    @@ -369,14 +373,14 @@ def main(script_path):
    pylint_url,
    pep8_url,
    flake8_url)

    basepath = os.getenv("TM_PROJECT_DIRECTORY")
    if basepath:
    project_dir = os.path.basename(basepath)
    script_name = os.path.basename(script_path)
    title = "%s &mdash; %s" % (escape(script_name), escape(project_dir))
    else:
    title = script_path
    title = escape(script_path)

    print(str(HTML_HEADER_FORMAT % (title,
    version_string,
  11. fish2000 revised this gist Nov 29, 2018. 1 changed file with 16 additions and 27 deletions.
    43 changes: 16 additions & 27 deletions pycheckmate.py
    Original file line number Diff line number Diff line change
    @@ -40,10 +40,6 @@
    basestring = str
    unicode = str

    ###
    ### Constants
    ###

    PYCHECKER_URL = "http://pychecker.sourceforge.net/"
    PYFLAKES_URL = "http://divmod.org/projects/pyflakes"
    PYLINT_URL = "http://www.logilab.org/857"
    @@ -190,9 +186,6 @@ def utf8_encode(source):
    return source.encode(UTF8_ENCODING)
    return source

    class Error(Exception):
    pass

    def check_syntax(script_path):
    with open(script_path, 'r') as handle:
    source = ''.join(handle.readlines() + ["\n"])
    @@ -217,6 +210,7 @@ def check_syntax(script_path):
    def find_checker_program():
    tm_pychecker = os.getenv("TM_PYCHECKER")
    opts = filter(None, os.getenv('TM_PYCHECKER_OPTIONS', '').split())
    version = ''

    if tm_pychecker == "builtin":
    return ('', None, "Syntax check only")
    @@ -243,18 +237,16 @@ def find_checker_program():
    continue

    if basename == "pychecker":
    p = os.popen('"%s" -V 2>/dev/null' % (checker))
    version = p.readline().strip()
    status = p.close()
    if status is None and version:
    with os.popen('"%s" -V 2>/dev/null' % (checker)) as p:
    version = p.readline().strip()
    if version:
    version = "PyChecker %s" % version
    return (checker, opts, version)

    elif basename == "pylint":
    p = os.popen('"%s" --version 2>/dev/null' % (checker))
    version = p.readline().strip()
    status = p.close()
    if status is None and version:
    with os.popen('"%s" --version 2>/dev/null' % (checker)) as p:
    version = p.readline().strip()
    if version:
    version = re.sub('^pylint\s*', '', version)
    version = re.sub(',$', '', version)
    version = "Pylint %s" % version
    @@ -264,27 +256,24 @@ def find_checker_program():
    elif basename == "pyflakes":
    # pyflakes doesn't have a version string embedded anywhere,
    # so run it against itself to make sure it's functional
    p = os.popen('"%s" "%s" 2>&1 >/dev/null' % (checker, checker))
    output = p.readlines()
    status = p.close()
    if status is None and not output:
    with os.popen('"%s" "%s" 2>&1 >/dev/null' % (checker, checker)) as p:
    output = p.readlines()
    if not output:
    return (checker, opts, "PyFlakes")

    elif basename == "pep8":
    p = os.popen('"%s" --version 2>/dev/null' % (checker))
    version = p.readline().strip()
    status = p.close()
    if status is None and version:
    with os.popen('"%s" --version 2>/dev/null' % (checker)) as p:
    version = p.readline().strip()
    if version:
    version = "PEP 8 %s" % version
    global PYCHECKER_RE
    PYCHECKER_RE = re.compile(r"^(.*?\.pyc?):(\d+):(?:\d+:)?\s+(.*)$")
    return (checker, opts, version)

    elif basename == "flake8":
    p = os.popen('"%s" --version 2>/dev/null' % (checker))
    version = p.readline().strip()
    status = p.close()
    if status is None and version:
    with os.popen('"%s" --version 2>/dev/null' % (checker)) as p:
    version = p.readline().strip()
    if version:
    version = "flake8 %s" % version
    PYCHECKER_RE = re.compile(r"^(.*?\.pyc?):(\d+):(?:\d+:)?\s+(.*)$")
    return (checker, opts, version)
  12. fish2000 revised this gist Nov 29, 2018. 1 changed file with 60 additions and 50 deletions.
    110 changes: 60 additions & 50 deletions pycheckmate.py
    Original file line number Diff line number Diff line change
    @@ -160,9 +160,23 @@
    </html>
    """

    ###
    ### Helper classes
    ###
    CHECKERS = ["pychecker", "pyflakes", "pylint", "pep8", "flake8"]

    DEFAULT_PATH = ":".join(filter(os.path.exists, ("/usr/local/bin",
    "/bin", "/usr/bin",
    "/sbin", "/usr/sbin")))

    def which(binary_name, pathvar=None):
    """ Deduces the path corresponding to an executable name,
    as per the UNIX command `which`. Optionally takes an
    override for the $PATH environment variable.
    Always returns a string - an empty one for those
    executables that cannot be found.
    """
    from distutils.spawn import find_executable
    if not hasattr(which, 'pathvar'):
    which.pathvar = os.getenv("PATH", DEFAULT_PATH)
    return find_executable(binary_name, pathvar or which.pathvar) or ""

    UTF8_ENCODING = 'UTF-8'

    @@ -176,24 +190,20 @@ def utf8_encode(source):
    return source.encode(UTF8_ENCODING)
    return source


    class Error(Exception):
    pass

    ###
    ### Program code
    ###

    def check_syntax(script_path):
    f = open(script_path, 'r')
    source = ''.join(f.readlines()+["\n"])
    f.close()
    with open(script_path, 'r') as handle:
    source = ''.join(handle.readlines() + ["\n"])
    try:
    print("Syntax Errors...<br><br>")
    compile(source, script_path, "exec")
    print("None<br>")
    except SyntaxError as e:
    href = TXMT_URL2_FORMAT % (quote(script_path), e.lineno, e.offset)
    href = TXMT_URL2_FORMAT % (quote(script_path),
    e.lineno,
    e.offset)
    print(str('<a href="%s">%s:%s</a> %s' % (href,
    escape(os.path.basename(script_path)),
    e.lineno, e.msg)))
    @@ -205,44 +215,41 @@ def check_syntax(script_path):
    print('<span class="stderr">%s%s</span><br>' % (pad, line))

    def find_checker_program():
    checkers = ["pychecker", "pyflakes", "pylint", "pep8", "flake8"]
    tm_pychecker = os.getenv("TM_PYCHECKER")

    opts = filter(None, os.getenv('TM_PYCHECKER_OPTIONS', '').split())

    if tm_pychecker == "builtin":
    return ('', None, "Syntax check only")

    if tm_pychecker is not None:
    checkers.insert(0, tm_pychecker)

    for checker in checkers:
    if not tm_pychecker in CHECKERS:
    CHECKERS.insert(0, tm_pychecker)

    for checker in CHECKERS:
    basename = os.path.split(checker)[1]
    if checker == basename:
    # look for checker in same bin directory as python (might be
    # symlinked)
    # look for checker in same bin directory as python --
    # it might be symlinked:
    bindir = os.path.split(sys.executable)[0]
    checker = os.path.join(bindir, basename)
    if not os.path.isfile(checker):
    # look where python is installed
    # continue searching python's installation directory:
    checker = os.path.join(sys.prefix, "bin", basename)
    if not os.path.isfile(checker):
    # search the PATH
    p = os.popen("/usr/bin/which '%s'" % basename)
    checker = p.readline().strip()
    p.close()

    checker = which(basename)

    if not os.path.isfile(checker):
    continue

    if basename == "pychecker":
    p = os.popen('"%s" -V 2>/dev/null' % (checker))
    version = p.readline().strip()
    status = p.close()
    if status is None and version:
    version = "PyChecker %s" % version
    return (checker, opts, version)

    elif basename == "pylint":
    p = os.popen('"%s" --version 2>/dev/null' % (checker))
    version = p.readline().strip()
    @@ -253,7 +260,7 @@ def find_checker_program():
    version = "Pylint %s" % version
    opts += ('--output-format=parseable',)
    return (checker, opts, version)

    elif basename == "pyflakes":
    # pyflakes doesn't have a version string embedded anywhere,
    # so run it against itself to make sure it's functional
    @@ -262,7 +269,7 @@ def find_checker_program():
    status = p.close()
    if status is None and not output:
    return (checker, opts, "PyFlakes")

    elif basename == "pep8":
    p = os.popen('"%s" --version 2>/dev/null' % (checker))
    version = p.readline().strip()
    @@ -272,7 +279,7 @@ def find_checker_program():
    global PYCHECKER_RE
    PYCHECKER_RE = re.compile(r"^(.*?\.pyc?):(\d+):(?:\d+:)?\s+(.*)$")
    return (checker, opts, version)

    elif basename == "flake8":
    p = os.popen('"%s" --version 2>/dev/null' % (checker))
    version = p.readline().strip()
    @@ -281,36 +288,35 @@ def find_checker_program():
    version = "flake8 %s" % version
    PYCHECKER_RE = re.compile(r"^(.*?\.pyc?):(\d+):(?:\d+:)?\s+(.*)$")
    return (checker, opts, version)

    return ('', None, "Syntax check only")

    def run_checker_program(checker_bin, checker_opts, script_path):
    def run_checker_program(checker_bin,
    checker_opts,
    script_path):
    import subprocess
    basepath = os.getenv("TM_PROJECT_DIRECTORY")
    cmd = []
    cmd.append(checker_bin)
    if checker_opts:
    cmd.extend(checker_opts)
    cmd.append(script_path)
    p = subprocess.Popen(cmd,
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    shell=False)
    p = subprocess.Popen(cmd, shell=False,
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE)
    while 1:
    stdout, stderr = p.communicate()
    if stdout is None:
    break
    idx = 0
    for line in stdout.decode(UTF8_ENCODING).splitlines():
    global PYCHECKER_RE
    match = PYCHECKER_RE.search(line)
    if match:
    filename, lineno, msg = match.groups()
    global TXMT_URL1_FORMAT
    href = TXMT_URL1_FORMAT % (quote(filename), lineno)
    # if basepath is not None and str(filename).startswith(basepath):
    # filename = str(filename[len(basepath)+1:])
    if basepath is not None and str(filename).startswith(basepath):
    filename = str(filename[len(basepath)+1:])
    # naive linewrapping, but it seems to work well-enough
    add_br = ""
    if len(filename) + len(msg) > 80:
    @@ -369,7 +375,11 @@ def main(script_path):
    flake8_url = href_format % (FLAKE8_URL, "flake8")
    warning_string = \
    "<p>Please install %s, %s, %s, %s or %s for more extensive code checking." \
    "</p><br>" % (pychecker_url, pyflakes_url, pylint_url, pep8_url, flake8_url)
    "</p><br>" % (pychecker_url,
    pyflakes_url,
    pylint_url,
    pep8_url,
    flake8_url)

    basepath = os.getenv("TM_PROJECT_DIRECTORY")
    if basepath:
    @@ -379,20 +389,20 @@ def main(script_path):
    else:
    title = script_path

    print(str(HTML_HEADER_FORMAT % (title, version_string, 'xx issues found')), file=sys.stdout)
    print(str(HTML_HEADER_FORMAT % (title,
    version_string,
    'xx issues found')),
    file=sys.stdout)
    sys.stdout.flush()

    if warning_string:
    print(warning_string)

    run_checker_program(checker_bin, checker_opts, script_path)
    run_checker_program(checker_bin,
    checker_opts,
    script_path)
    sys.stdout.flush()

    # if checker_bin:
    # run_checker_program(checker_bin, checker_opts, script_path)
    # else:
    # check_syntax(script_path)

    print(str(HTML_FOOTER), file=sys.stdout)
    sys.stdout.flush()
    return 0
  13. fish2000 revised this gist Nov 21, 2018. 1 changed file with 4 additions and 13 deletions.
    17 changes: 4 additions & 13 deletions pycheckmate.py
    Original file line number Diff line number Diff line change
    @@ -303,26 +303,18 @@ def run_checker_program(checker_bin, checker_opts, script_path):
    break
    idx = 0
    for line in stdout.decode(UTF8_ENCODING).splitlines():
    # line = str(line.strip())
    global PYCHECKER_RE
    match = PYCHECKER_RE.search(line)
    # print(str(line), file=sys.stdout)
    if match:
    filename, lineno, msg = match.groups()
    # href = TXMT_URL1_FORMAT % (quote(os.path.abspath(filename.strip())),
    # lineno)
    global TXMT_URL1_FORMAT
    href = TXMT_URL1_FORMAT % (quote(filename), lineno)
    # if basepath is not None and str(filename).startswith(basepath):
    # filename = str(filename[len(basepath)+1:])
    # naive linewrapping, but it seems to work well-enough
    add_br = ""
    if len(filename) + len(msg) > 80:
    add_br = "<br>&nbsp;&nbsp;"
    else:
    add_br = " "
    # line = '<a href="%s">%s:%d</a>%s%s' % (
    # href, filename, int(lineno), add_br,
    # escape(msg))
    add_br += "<br>&nbsp;&nbsp;"
    tpl = '''
    <div class="message">
    <span class="number">%(number)02d</span>
    @@ -339,12 +331,11 @@ def run_checker_program(checker_bin, checker_opts, script_path):
    number=int(idx) + 1),
    file=sys.stdout)
    idx += 1

    else:
    print(f'{line}<br>')

    # else:
    # line = escape(line)
    # print(str("%s<br>" % line))
    # THEY TOLD ME TO FLUSH THE PIPES SO I FLUSHED THE PIPES
    sys.stdout.flush()

    for line in stderr.decode(UTF8_ENCODING).splitlines():
  14. fish2000 revised this gist Nov 21, 2018. 1 changed file with 2 additions and 120 deletions.
    122 changes: 2 additions & 120 deletions pycheckmate.py
    Original file line number Diff line number Diff line change
    @@ -4,6 +4,7 @@
    # PyCheckMate, a PyChecker output beautifier for TextMate.
    # Copyright (c) Jay Soffian, 2005. <jay at soffian dot org>
    # Inspired by Domenico Carbotta's PyMate.
    # Extensively overhauled for verions 2.0 by Alexander Böhn
    #
    # License: Artistic.
    #
    @@ -27,9 +28,8 @@
    import sys
    import traceback
    from html import escape
    from select import select

    __version__ = "1.2"
    __version__ = "2.0"

    PY3 = False
    if sys.version_info < (3, 0):
    @@ -180,123 +180,6 @@ def utf8_encode(source):
    class Error(Exception):
    pass

    class MyPopen(object):
    """Modifed version of standard popen2.Popen class that does what I need.
    Runs command with stdin redirected from /dev/null and monitors its stdout
    and stderr. Each time poll() is called a tuple of (stdout, stderr) is
    returned where stdout and stderr are lists of zero or more lines of output
    from the command. status() should be called before calling poll() and if
    it returns other than -1 then the child has terminated and poll() will
    return no additional output. At that point drain() should be called to
    return the last bit of output.
    As a simplication, readlines() can be called until it returns (None, None)
    """

    try:
    MAXFD = os.sysconf('SC_OPEN_MAX')
    except (AttributeError, ValueError):
    MAXFD = 256

    def __init__(self, cmd):
    stdout_r, stdout_w = os.pipe()
    stderr_r, stderr_w = os.pipe()
    self._status = -1
    self._drained = 0
    self._pid = os.fork()
    if self._pid == 0:
    # child
    devnull = open("/dev/null")
    os.dup2(devnull.fileno(), 0)
    os.dup2(stdout_w, 1)
    os.dup2(stderr_w, 2)
    devnull.close()
    self._run_child(cmd)
    else:
    # parent
    os.close(stdout_w)
    os.close(stderr_w)
    self._stdout = stdout_r
    self._stderr = stderr_r
    self._stdout_buf = ""
    self._stderr_buf = ""

    def _run_child(self, cmd):
    if isinstance(cmd, basestring):
    cmd = ['/bin/sh', '-c', cmd]
    for i in range(3, self.MAXFD):
    try:
    os.close(i)
    except OSError:
    pass
    try:
    os.execvp(cmd[0], cmd)
    finally:
    os._exit(1)

    def status(self):
    """Returns exit status of child or -1 if still running."""
    if self._status < 0:
    try:
    pid, this_status = os.waitpid(self._pid, os.WNOHANG)
    if pid == self._pid:
    self._status = this_status
    except os.error:
    pass
    return self._status

    def poll(self, timeout=None):
    """Returns (stdout, stderr) from child."""
    bufs = {
    self._stdout : self._stdout_buf,
    self._stderr : self._stderr_buf
    }
    fds, dummy, dummy = select(bufs.keys(), [], [], timeout)
    for fd in fds:
    data = os.read(fd, 4096)
    bufs[fd] += str(data)
    self._stdout_buf = ""
    self._stderr_buf = ""
    stdout_lines = bufs[self._stdout].splitlines()
    stderr_lines = bufs[self._stderr].splitlines()
    if stdout_lines and not bufs[self._stdout].endswith("\n"):
    self._stdout_buf = stdout_lines.pop()
    if stderr_lines and not bufs[self._stderr].endswith("\n"):
    self._stderr_buf = stderr_lines.pop()
    return (stdout_lines, stderr_lines)

    def drain(self):
    stdout, stderr = [self._stdout_buf], [self._stderr_buf]
    while 1:
    data = os.read(self._stdout, 4096)
    if not data:
    break
    stdout.append(data)
    while 1:
    data = os.read(self._stderr, 4096)
    if not data:
    break
    stderr.append(data)
    self._stdout_buf = ""
    self._stderr_buf = ""
    self._drained = 1
    stdout_lines = ''.join(stdout).splitlines()
    stderr_lines = ''.join(stderr).splitlines()
    return (stdout_lines, stderr_lines)

    def readlines(self):
    if self._drained:
    return None, None
    elif self.status() == -1:
    return self.poll()
    else:
    return self.drain()

    def close(self):
    os.close(self._stdout)
    os.close(self._stderr)

    ###
    ### Program code
    ###
    @@ -409,7 +292,6 @@ def run_checker_program(checker_bin, checker_opts, script_path):
    if checker_opts:
    cmd.extend(checker_opts)
    cmd.append(script_path)
    # p = MyPopen(cmd)
    p = subprocess.Popen(cmd,
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
  15. fish2000 created this gist Nov 21, 2018.
    532 changes: 532 additions & 0 deletions pycheckmate.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,532 @@
    #!/usr/bin/env python
    # encoding: utf-8
    #
    # PyCheckMate, a PyChecker output beautifier for TextMate.
    # Copyright (c) Jay Soffian, 2005. <jay at soffian dot org>
    # Inspired by Domenico Carbotta's PyMate.
    #
    # License: Artistic.
    #
    # Usage:
    # - Out of the box, pycheckmate.py will perform only a basic syntax check
    # by attempting to compile the python code.
    # - Install PyChecker or PyFlakes for more extensive checking. If both are
    # installed, PyChecker will be used.
    # - TM_PYCHECKER may be set to control which checker is used. Set it to just
    # "pychecker", "pyflakes", "pep8", "flake8", or "pylint", or "frosted" to
    # locate these programs in the default python bin directory or to a full
    # path if the checker program is installed elsewhere.
    # - If for some reason you want to use the built-in sytax check when either
    # pychecker or pyflakes are installed, you may set TM_PYCHECKER to
    # "builtin".

    from __future__ import absolute_import, print_function

    import os
    import re
    import sys
    import traceback
    from html import escape
    from select import select

    __version__ = "1.2"

    PY3 = False
    if sys.version_info < (3, 0):
    from urllib import quote
    else:
    from urllib.parse import quote
    PY3 = True
    basestring = str
    unicode = str

    ###
    ### Constants
    ###

    PYCHECKER_URL = "http://pychecker.sourceforge.net/"
    PYFLAKES_URL = "http://divmod.org/projects/pyflakes"
    PYLINT_URL = "http://www.logilab.org/857"
    PEP8_URL = "http://pypi.python.org/pypi/pep8"
    FLAKE8_URL = "http://pypi.python.org/pypi/flake8/"

    # patterns to match output of checker programs
    PYCHECKER_RE = re.compile(r"^(?:\s*)(.*?\.pyc?):(\d+):(?:\s*)(.*)(?:\s*)$")

    # careful editing these, they are format strings
    TXMT_URL1_FORMAT = "txmt://open?url=file://%s&line=%s"
    TXMT_URL2_FORMAT = "txmt://open?url=file://%s&line=%s&column=%s"
    HTML_HEADER_FORMAT = """<html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>PyCheckMate %s</title>
    <style type="text/css">
    body {
    background-color: #D8E2F1;
    margin: 0;
    }
    div#body {
    border-style: dotted;
    border-width: 1px 0;
    border-color: #666;
    margin: 10px 0;
    padding: 10px;
    background-color: #C9D9F0;
    }
    div#output {
    padding: 0;
    margin: 0;
    color: #121212;
    font-family: Consolas, Monaco;
    font-size: 11pt;
    }
    div#output div.message {
    vertical-align: middle;
    display: inline-block;
    margin: 0.5em;
    padding: 0.5em;
    margin-left: 0px;
    margin-right: 1em;
    padding-left: 2px;
    padding-right: 1em;
    margin-top: 10px;
    padding-top: 0px;
    border-radius: 10px;
    background-color: #D9E9FF;
    color: #121212;
    font-family: Consolas, Monaco;
    font-size: 11pt;
    }
    div#output div.message span.number {
    padding: 0;
    margin: 0;
    margin-left: 10px;
    color: #121212;
    font-family: Georgia, Times New Roman;
    font-size: 3em;
    }
    div#output div.message span.message-text {
    padding: 0;
    margin: 0;
    margin-left: 2.5em;
    }
    div#output div.message a {
    color: darkorange;
    }
    div#exit-status {
    padding: 0;
    margin: 0;
    padding-top: 1em;
    font-family: Consolas, Monaco;
    font-size: 11pt;
    }
    strong {
    margin-left: 3.0em;
    font-family: Aksidenz-Grotesk, Helvetica Neue, Helvetica, Arial;
    text-transform: uppercase;
    }
    strong.title {
    margin-top: 1em;
    font-size: 18pt;
    text-transform: uppercase;
    }
    span.stderr { color: red; }
    p { margin: 0; padding: 2px 0; }
    </style>
    </head>
    <body>
    <div id="body">
    <p><strong class="title">%s</strong></p>
    <p><strong>%s</strong></p>
    <br>
    <div id="output">
    """

    HTML_FOOTER = """</div>
    </div>
    </body>
    </html>
    """

    ###
    ### Helper classes
    ###

    UTF8_ENCODING = 'UTF-8'

    def utf8_encode(source):
    """ Encode a source as bytes using the UTF-8 codec """
    if PY3:
    if type(source) is bytes:
    return source
    return bytes(source, encoding=UTF8_ENCODING)
    if type(source) is unicode:
    return source.encode(UTF8_ENCODING)
    return source


    class Error(Exception):
    pass

    class MyPopen(object):
    """Modifed version of standard popen2.Popen class that does what I need.
    Runs command with stdin redirected from /dev/null and monitors its stdout
    and stderr. Each time poll() is called a tuple of (stdout, stderr) is
    returned where stdout and stderr are lists of zero or more lines of output
    from the command. status() should be called before calling poll() and if
    it returns other than -1 then the child has terminated and poll() will
    return no additional output. At that point drain() should be called to
    return the last bit of output.
    As a simplication, readlines() can be called until it returns (None, None)
    """

    try:
    MAXFD = os.sysconf('SC_OPEN_MAX')
    except (AttributeError, ValueError):
    MAXFD = 256

    def __init__(self, cmd):
    stdout_r, stdout_w = os.pipe()
    stderr_r, stderr_w = os.pipe()
    self._status = -1
    self._drained = 0
    self._pid = os.fork()
    if self._pid == 0:
    # child
    devnull = open("/dev/null")
    os.dup2(devnull.fileno(), 0)
    os.dup2(stdout_w, 1)
    os.dup2(stderr_w, 2)
    devnull.close()
    self._run_child(cmd)
    else:
    # parent
    os.close(stdout_w)
    os.close(stderr_w)
    self._stdout = stdout_r
    self._stderr = stderr_r
    self._stdout_buf = ""
    self._stderr_buf = ""

    def _run_child(self, cmd):
    if isinstance(cmd, basestring):
    cmd = ['/bin/sh', '-c', cmd]
    for i in range(3, self.MAXFD):
    try:
    os.close(i)
    except OSError:
    pass
    try:
    os.execvp(cmd[0], cmd)
    finally:
    os._exit(1)

    def status(self):
    """Returns exit status of child or -1 if still running."""
    if self._status < 0:
    try:
    pid, this_status = os.waitpid(self._pid, os.WNOHANG)
    if pid == self._pid:
    self._status = this_status
    except os.error:
    pass
    return self._status

    def poll(self, timeout=None):
    """Returns (stdout, stderr) from child."""
    bufs = {
    self._stdout : self._stdout_buf,
    self._stderr : self._stderr_buf
    }
    fds, dummy, dummy = select(bufs.keys(), [], [], timeout)
    for fd in fds:
    data = os.read(fd, 4096)
    bufs[fd] += str(data)
    self._stdout_buf = ""
    self._stderr_buf = ""
    stdout_lines = bufs[self._stdout].splitlines()
    stderr_lines = bufs[self._stderr].splitlines()
    if stdout_lines and not bufs[self._stdout].endswith("\n"):
    self._stdout_buf = stdout_lines.pop()
    if stderr_lines and not bufs[self._stderr].endswith("\n"):
    self._stderr_buf = stderr_lines.pop()
    return (stdout_lines, stderr_lines)

    def drain(self):
    stdout, stderr = [self._stdout_buf], [self._stderr_buf]
    while 1:
    data = os.read(self._stdout, 4096)
    if not data:
    break
    stdout.append(data)
    while 1:
    data = os.read(self._stderr, 4096)
    if not data:
    break
    stderr.append(data)
    self._stdout_buf = ""
    self._stderr_buf = ""
    self._drained = 1
    stdout_lines = ''.join(stdout).splitlines()
    stderr_lines = ''.join(stderr).splitlines()
    return (stdout_lines, stderr_lines)

    def readlines(self):
    if self._drained:
    return None, None
    elif self.status() == -1:
    return self.poll()
    else:
    return self.drain()

    def close(self):
    os.close(self._stdout)
    os.close(self._stderr)

    ###
    ### Program code
    ###

    def check_syntax(script_path):
    f = open(script_path, 'r')
    source = ''.join(f.readlines()+["\n"])
    f.close()
    try:
    print("Syntax Errors...<br><br>")
    compile(source, script_path, "exec")
    print("None<br>")
    except SyntaxError as e:
    href = TXMT_URL2_FORMAT % (quote(script_path), e.lineno, e.offset)
    print(str('<a href="%s">%s:%s</a> %s' % (href,
    escape(os.path.basename(script_path)),
    e.lineno, e.msg)))
    except:
    for line in traceback.format_exception(sys.exc_info()):
    stripped = line.lstrip()
    pad = "&nbsp;" * (len(line) - len(stripped))
    line = str(escape(stripped.rstrip()))
    print('<span class="stderr">%s%s</span><br>' % (pad, line))

    def find_checker_program():
    checkers = ["pychecker", "pyflakes", "pylint", "pep8", "flake8"]
    tm_pychecker = os.getenv("TM_PYCHECKER")

    opts = filter(None, os.getenv('TM_PYCHECKER_OPTIONS', '').split())

    if tm_pychecker == "builtin":
    return ('', None, "Syntax check only")

    if tm_pychecker is not None:
    checkers.insert(0, tm_pychecker)

    for checker in checkers:
    basename = os.path.split(checker)[1]
    if checker == basename:
    # look for checker in same bin directory as python (might be
    # symlinked)
    bindir = os.path.split(sys.executable)[0]
    checker = os.path.join(bindir, basename)
    if not os.path.isfile(checker):
    # look where python is installed
    checker = os.path.join(sys.prefix, "bin", basename)
    if not os.path.isfile(checker):
    # search the PATH
    p = os.popen("/usr/bin/which '%s'" % basename)
    checker = p.readline().strip()
    p.close()

    if not os.path.isfile(checker):
    continue

    if basename == "pychecker":
    p = os.popen('"%s" -V 2>/dev/null' % (checker))
    version = p.readline().strip()
    status = p.close()
    if status is None and version:
    version = "PyChecker %s" % version
    return (checker, opts, version)

    elif basename == "pylint":
    p = os.popen('"%s" --version 2>/dev/null' % (checker))
    version = p.readline().strip()
    status = p.close()
    if status is None and version:
    version = re.sub('^pylint\s*', '', version)
    version = re.sub(',$', '', version)
    version = "Pylint %s" % version
    opts += ('--output-format=parseable',)
    return (checker, opts, version)

    elif basename == "pyflakes":
    # pyflakes doesn't have a version string embedded anywhere,
    # so run it against itself to make sure it's functional
    p = os.popen('"%s" "%s" 2>&1 >/dev/null' % (checker, checker))
    output = p.readlines()
    status = p.close()
    if status is None and not output:
    return (checker, opts, "PyFlakes")

    elif basename == "pep8":
    p = os.popen('"%s" --version 2>/dev/null' % (checker))
    version = p.readline().strip()
    status = p.close()
    if status is None and version:
    version = "PEP 8 %s" % version
    global PYCHECKER_RE
    PYCHECKER_RE = re.compile(r"^(.*?\.pyc?):(\d+):(?:\d+:)?\s+(.*)$")
    return (checker, opts, version)

    elif basename == "flake8":
    p = os.popen('"%s" --version 2>/dev/null' % (checker))
    version = p.readline().strip()
    status = p.close()
    if status is None and version:
    version = "flake8 %s" % version
    PYCHECKER_RE = re.compile(r"^(.*?\.pyc?):(\d+):(?:\d+:)?\s+(.*)$")
    return (checker, opts, version)

    return ('', None, "Syntax check only")

    def run_checker_program(checker_bin, checker_opts, script_path):
    import subprocess
    basepath = os.getenv("TM_PROJECT_DIRECTORY")
    cmd = []
    cmd.append(checker_bin)
    if checker_opts:
    cmd.extend(checker_opts)
    cmd.append(script_path)
    # p = MyPopen(cmd)
    p = subprocess.Popen(cmd,
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    shell=False)
    while 1:
    stdout, stderr = p.communicate()
    if stdout is None:
    break
    idx = 0
    for line in stdout.decode(UTF8_ENCODING).splitlines():
    # line = str(line.strip())
    global PYCHECKER_RE
    match = PYCHECKER_RE.search(line)
    # print(str(line), file=sys.stdout)
    if match:
    filename, lineno, msg = match.groups()
    # href = TXMT_URL1_FORMAT % (quote(os.path.abspath(filename.strip())),
    # lineno)
    global TXMT_URL1_FORMAT
    href = TXMT_URL1_FORMAT % (quote(filename), lineno)
    # if basepath is not None and str(filename).startswith(basepath):
    # filename = str(filename[len(basepath)+1:])
    # naive linewrapping, but it seems to work well-enough
    if len(filename) + len(msg) > 80:
    add_br = "<br>&nbsp;&nbsp;"
    else:
    add_br = " "
    # line = '<a href="%s">%s:%d</a>%s%s' % (
    # href, filename, int(lineno), add_br,
    # escape(msg))
    tpl = '''
    <div class="message">
    <span class="number">%(number)02d</span>
    <a href="%(href)s">%(filename)s:%(lineno)d</a>
    %(whitespace)s
    <span class="message-text">%(message)s</a>
    </div>
    '''
    print(tpl % dict(href=str(href),
    filename=str(filename),
    lineno=int(lineno),
    whitespace=str(add_br),
    message=str(msg),
    number=int(idx) + 1),
    file=sys.stdout)
    idx += 1
    else:
    print(f'{line}<br>')

    # else:
    # line = escape(line)
    # print(str("%s<br>" % line))
    sys.stdout.flush()

    for line in stderr.decode(UTF8_ENCODING).splitlines():
    # strip whitespace off front and replace with &nbsp; so that
    # we can allow the browser to wrap long lines but we don't lose
    # leading indentation otherwise.
    stripped = line.lstrip()
    pad = "&nbsp;" * (len(line) - len(stripped))
    line = escape(stripped.rstrip())
    print('<span class="stderr">%s%s</span><br>' % (pad, line), file=sys.stderr)
    sys.stderr.flush()

    break

    returncode = p.returncode
    print(str('<div id="exit-status"><br>Exit status: %s</div>' % returncode))
    if returncode is None:
    p.terminate()

    def main(script_path):
    checker_bin, checker_opts, checker_ver = find_checker_program()
    version_string = "PyCheckMate %s &ndash; %s" % (__version__, checker_ver)
    warning_string = ""
    if not checker_bin:
    href_format = \
    "<a href=\"javascript:TextMate.system('open %s', null)\">%s</a>"
    pychecker_url = href_format % (PYCHECKER_URL, "PyChecker")
    pyflakes_url = href_format % (PYFLAKES_URL, "PyFlakes")
    pylint_url = href_format % (PYLINT_URL, "Pylint")
    pep8_url = href_format % (PEP8_URL, "PEP 8")
    flake8_url = href_format % (FLAKE8_URL, "flake8")
    warning_string = \
    "<p>Please install %s, %s, %s, %s or %s for more extensive code checking." \
    "</p><br>" % (pychecker_url, pyflakes_url, pylint_url, pep8_url, flake8_url)

    basepath = os.getenv("TM_PROJECT_DIRECTORY")
    if basepath:
    project_dir = os.path.basename(basepath)
    script_name = os.path.basename(script_path)
    title = "%s &mdash; %s" % (escape(script_name), escape(project_dir))
    else:
    title = script_path

    print(str(HTML_HEADER_FORMAT % (title, version_string, 'xx issues found')), file=sys.stdout)
    sys.stdout.flush()

    if warning_string:
    print(warning_string)

    run_checker_program(checker_bin, checker_opts, script_path)
    sys.stdout.flush()

    # if checker_bin:
    # run_checker_program(checker_bin, checker_opts, script_path)
    # else:
    # check_syntax(script_path)

    print(str(HTML_FOOTER), file=sys.stdout)
    sys.stdout.flush()
    return 0

    if __name__ == "__main__":
    if len(sys.argv) == 2:
    sys.exit(main(sys.argv[1]))
    else:
    print("Usage: %s <file.py>" % sys.argv[0], file=sys.stderr)
    sys.exit(1)