2019-11-06 16:30:03 +01:00
|
|
|
import re
|
2019-12-07 15:16:04 +01:00
|
|
|
import sys
|
|
|
|
import argparse
|
|
|
|
from pathlib import Path
|
2019-11-19 19:57:14 +01:00
|
|
|
from textwrap import fill, indent
|
2019-11-06 16:30:03 +01:00
|
|
|
|
|
|
|
|
2019-12-06 16:22:21 +01:00
|
|
|
INDENT = ' ' * 4
|
|
|
|
|
2019-11-06 16:30:03 +01:00
|
|
|
CLASS = """
|
2019-12-06 16:22:21 +01:00
|
|
|
|
|
|
|
class {name}({parents}):{doc}
|
2019-11-19 19:57:14 +01:00
|
|
|
"""
|
2019-11-06 16:30:03 +01:00
|
|
|
|
2019-12-06 16:22:21 +01:00
|
|
|
INIT = """
|
2019-11-06 16:30:03 +01:00
|
|
|
def __init__({args}):
|
2019-12-06 16:22:21 +01:00
|
|
|
super().__init__({format}"{message}")
|
2019-11-06 16:30:03 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
2019-12-06 16:22:21 +01:00
|
|
|
class ErrorClass:
|
|
|
|
|
|
|
|
def __init__(self, hierarchy, name, message):
|
|
|
|
self.hierarchy = hierarchy.replace('**', '')
|
|
|
|
self.other_parents = []
|
|
|
|
if '(' in name:
|
|
|
|
assert ')' in name, f"Missing closing parenthesis in '{name}'."
|
|
|
|
self.other_parents = name[name.find('(')+1:name.find(')')].split(',')
|
|
|
|
name = name[:name.find('(')]
|
|
|
|
self.name = name
|
|
|
|
self.class_name = name+'Error'
|
|
|
|
self.message = message
|
|
|
|
self.comment = ""
|
|
|
|
if '--' in message:
|
|
|
|
self.message, self.comment = message.split('--')
|
|
|
|
self.message = self.message.strip()
|
|
|
|
self.comment = self.comment.strip()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def is_leaf(self):
|
|
|
|
return 'x' not in self.hierarchy
|
|
|
|
|
|
|
|
@property
|
|
|
|
def code(self):
|
|
|
|
return self.hierarchy.replace('x', '')
|
|
|
|
|
|
|
|
@property
|
|
|
|
def parent_codes(self):
|
|
|
|
return self.hierarchy[0:2], self.hierarchy[0]
|
|
|
|
|
|
|
|
def get_arguments(self):
|
|
|
|
args = ['self']
|
|
|
|
for arg in re.findall('{([a-z0-1]+)}', self.message):
|
|
|
|
args.append(arg)
|
|
|
|
return args
|
|
|
|
|
|
|
|
def get_doc_string(self, doc):
|
|
|
|
if doc:
|
|
|
|
return f'\n{INDENT}"""\n{indent(fill(doc, 100), INDENT)}\n{INDENT}"""'
|
|
|
|
return ""
|
2019-11-06 16:30:03 +01:00
|
|
|
|
2019-12-06 16:22:21 +01:00
|
|
|
def render(self, out, parent):
|
|
|
|
if not parent:
|
|
|
|
parents = ['BaseError']
|
|
|
|
else:
|
|
|
|
parents = [parent.class_name]
|
|
|
|
parents += self.other_parents
|
|
|
|
args = self.get_arguments()
|
|
|
|
if self.is_leaf:
|
|
|
|
out.write((CLASS + INIT).format(
|
|
|
|
name=self.class_name, parents=', '.join(parents), args=', '.join(args),
|
|
|
|
message=self.message, doc=self.get_doc_string(self.comment), format='f' if len(args) > 1 else ''
|
|
|
|
))
|
|
|
|
else:
|
|
|
|
out.write(CLASS.format(
|
|
|
|
name=self.class_name, parents=', '.join(parents),
|
|
|
|
doc=self.get_doc_string(self.comment or self.message)
|
|
|
|
))
|
|
|
|
|
|
|
|
|
2019-12-07 15:16:04 +01:00
|
|
|
def get_errors():
|
|
|
|
with open('README.md', 'r') as readme:
|
|
|
|
lines = iter(readme.readlines())
|
|
|
|
for line in lines:
|
|
|
|
if line.startswith('## Exceptions Table'):
|
|
|
|
break
|
|
|
|
for line in lines:
|
|
|
|
if line.startswith('---:|'):
|
|
|
|
break
|
|
|
|
for line in lines:
|
|
|
|
if not line:
|
|
|
|
break
|
|
|
|
yield ErrorClass(*[c.strip() for c in line.split('|')])
|
2019-12-06 16:22:21 +01:00
|
|
|
|
|
|
|
|
|
|
|
def find_parent(stack, child):
|
|
|
|
for parent_code in child.parent_codes:
|
|
|
|
parent = stack.get(parent_code)
|
|
|
|
if parent:
|
|
|
|
return parent
|
|
|
|
|
|
|
|
|
2019-12-07 15:16:04 +01:00
|
|
|
def generate(out):
|
|
|
|
out.write('from .base import BaseError\n')
|
|
|
|
stack = {}
|
|
|
|
for error in get_errors():
|
|
|
|
error.render(out, find_parent(stack, error))
|
|
|
|
if not error.is_leaf:
|
|
|
|
assert error.code not in stack, f"Duplicate code: {error.code}"
|
|
|
|
stack[error.code] = error
|
|
|
|
|
|
|
|
|
|
|
|
def analyze():
|
|
|
|
errors = {e.class_name: [] for e in get_errors() if e.is_leaf}
|
|
|
|
here = Path(__file__).absolute().parents[0]
|
|
|
|
module = here.parent
|
|
|
|
for file_path in module.glob('**/*.py'):
|
|
|
|
if here in file_path.parents:
|
|
|
|
continue
|
|
|
|
with open(file_path) as src_file:
|
|
|
|
src = src_file.read()
|
|
|
|
for error in errors.keys():
|
|
|
|
found = src.count(error)
|
|
|
|
if found > 0:
|
|
|
|
errors[error].append((file_path, found))
|
|
|
|
|
|
|
|
print('Unused Errors:\n')
|
|
|
|
for error, used in errors.items():
|
|
|
|
if used:
|
|
|
|
print(f' - {error}')
|
|
|
|
for use in used:
|
|
|
|
print(f' {use[0].relative_to(module.parent)} {use[1]}')
|
|
|
|
print('')
|
|
|
|
|
|
|
|
print('')
|
|
|
|
print('Unused Errors:')
|
|
|
|
for error, used in errors.items():
|
|
|
|
if not used:
|
|
|
|
print(f' - {error}')
|
2019-11-06 16:30:03 +01:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2019-12-07 15:16:04 +01:00
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
parser.add_argument("action", choices=['generate', 'analyze'])
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.action == "analyze":
|
|
|
|
analyze()
|
|
|
|
elif args.action == "generate":
|
|
|
|
generate(sys.stdout)
|