Last active
October 20, 2024 16:19
-
-
Save Gribouillis/3fc674d8629921650cc72c4c180a0572 to your computer and use it in GitHub Desktop.
CLI program to validate the syntax of Python source files by compiling them
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# check_syntax.py - script to check the syntax of Python files | |
# inspired by the standard library's 'py_compile' module. | |
import importlib.machinery | |
import sys | |
import traceback | |
__all__ = ["has_valid_syntax", "main", "CheckSyntaxError"] | |
__version__ = "2024.10.20" | |
class CheckSyntaxError(Exception): | |
"""Exception raised when an error occurs while attempting to | |
compile the file. | |
To raise this exception, use | |
raise PyCompileError(exc_type,exc_value,file[,msg]) | |
where | |
exc_type: exception type to be used in error message | |
type name can be accesses as class variable | |
'exc_type_name' | |
exc_value: exception value to be used in error message | |
can be accesses as class variable 'exc_value' | |
file: name of file being compiled to be used in error message | |
can be accesses as class variable 'file' | |
msg: string message to be written as error message | |
If no value is given, a default exception message will be | |
given, consistent with 'standard' py_compile output. | |
message (or default) can be accesses as class variable | |
'msg' | |
""" | |
def __init__(self, exc_type, exc_value, file, msg=""): | |
exc_type_name = exc_type.__name__ | |
if exc_type is SyntaxError: | |
tbtext = "".join(traceback.format_exception_only(exc_type, exc_value)) | |
errmsg = tbtext.replace('File "<string>"', 'File "%s"' % file) | |
else: | |
errmsg = "Sorry: %s: %s" % (exc_type_name, exc_value) | |
Exception.__init__(self, msg or errmsg, exc_type_name, exc_value, file) | |
self.exc_type_name = exc_type_name | |
self.exc_value = exc_value | |
self.file = file | |
self.msg = msg or errmsg | |
def __str__(self): | |
return self.msg | |
def has_valid_syntax(file, dfile=None, doraise=False, optimize=-1, quiet=0): | |
"""Check the syntax of a Python source file. | |
:param file: The source file name. | |
:param dfile: Purported file name, i.e. the file name that shows up in | |
error messages. Defaults to the source file name. | |
:param doraise: Flag indicating whether or not an exception should be | |
raised when a compile error is found. If an exception occurs and this | |
flag is set to False, a string indicating the nature of the exception | |
will be printed, and the function will return to the caller. If an | |
exception occurs and this flag is set to True, a CheckSyntaxError | |
exception will be raised. | |
:param optimize: The optimization level for the compiler. Valid values | |
are -1, 0, 1 and 2. A value of -1 means to use the optimization | |
level of the current interpreter, as given by -O command line options. | |
:param quiet: Return full output with False or 0, errors only with 1, | |
and no output with 2. | |
:return: None | |
""" | |
loader = importlib.machinery.SourceFileLoader("<check_syntax>", file) | |
source_bytes = loader.get_data(file) | |
try: | |
compile(source_bytes, dfile or file, "exec", optimize=optimize) | |
except Exception as err: | |
py_exc = CheckSyntaxError(err.__class__, err, dfile or file) | |
if quiet < 2: | |
if doraise: | |
raise py_exc | |
else: | |
sys.stderr.write(py_exc.msg + "\n") | |
return False | |
else: | |
return True | |
def main(): | |
import argparse | |
description = """Check Python source file syntax by attempting compilation. | |
Exit status is 0 if Python syntax is correct. | |
""" | |
parser = argparse.ArgumentParser(description=description) | |
parser.add_argument( | |
"-q", "--quiet", action="store_true", help="do not print error messages" | |
) | |
parser.add_argument( | |
"filenames", | |
nargs="+", | |
help="Files to compile", | |
metavar="FILENAME", | |
) | |
args = parser.parse_args() | |
if args.filenames == ["-"]: | |
filenames = [filename.rstrip("\n") for filename in sys.stdin.readlines()] | |
else: | |
filenames = args.filenames | |
result = True | |
kwargs = {"doraise": False, "quiet": 2 if args.quiet else 0} | |
for filename in filenames: | |
filename = filename.rstrip("\n") | |
result = has_valid_syntax(filename, **kwargs) and result | |
else: | |
parser.exit(0 if result else 1) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment