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.
Overhaul of PyCheckMate.py for Python 3.7 circa late 2018
#!/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.
# Extensively overhauled for verions 2.0 by Alexander Böhn
#
# 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
__version__ = "2.0"
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
###
### 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 = 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment