Sat, 08 Jun 2019 11:17:17 +0300
cleanup
ldcheck.py | file | annotate | diff | comparison | revisions | |
linetypes.py | file | annotate | diff | comparison | revisions | |
parse.py | file | annotate | diff | comparison | revisions | |
testsuite.py | file | annotate | diff | comparison | revisions | |
webfront.py | file | annotate | diff | comparison | revisions |
--- a/ldcheck.py Sat Jun 08 01:51:50 2019 +0300 +++ b/ldcheck.py Sat Jun 08 11:17:17 2019 +0300 @@ -1,66 +1,47 @@ #!/usr/bin/env python3 +''' + Main LDCheck commandline program. +''' from sys import version_info if version_info < (3, 4): raise RuntimeError('Python 3.4 or newer required') -from colours import load_colours from geometry import * -from pathlib import Path import linetypes import header import parse - -from os.path import realpath -script_directory = Path(realpath(__file__)).parent - -def load_config(filename): - from configobj import ConfigObj - from copy import deepcopy - config = ConfigObj(filename, encoding = 'UTF8') - read_config = deepcopy(config) - if 'libraries' not in config: - config['libraries'] = ['/path/to/ldraw'] - if config != read_config: - config.write() - check_library_paths(config) - return config - -def library_paths(config): - for library_path_string in config['libraries']: - yield Path(library_path_string).expanduser() +import config +import colours -def check_library_paths(config): - from sys import exit - problems = False - have_paths = False - for library_path in library_paths(config): - have_paths = True - if not library_path.exists(): - problems = True - print(str.format( - 'Library path {} does not exist', - library_path, - )) - elif not library_path.exists(): - problems = True - print(str.format( - 'Library path {} is not a directory', - library_path, - )) - if not have_paths: - print('No LDraw path specified') - problems = True - if problems: - print('Please fix ldcheck.cfg') - exit(1) - -def find_ldconfig_ldr_paths(config): - for library_path in library_paths(config): - yield from [ - library_path / path - for path in ['LDConfig.ldr', 'ldconfig.ldr'] - if (library_path / path).is_file() - ] +def format_report(report, model): + ''' + Formats the report from the test suite so as to be + printed in the console. + ''' + import colorama + colorama.init() + messages = [] + for problem in report['problems']: + if problem.severity == 'hold': + text_colour = colorama.Fore.LIGHTRED_EX + elif problem.severity == 'warning': + text_colour = colorama.Fore.LIGHTBLUE_EX + else: + text_colour = '' + ldraw_code = model.body[problem.body_index].textual_representation() + message = str.format( + '{text_colour}{model_name}:{line_number}: {problem_type}: {message}' + '{colour_reset}\n\t{ldraw_code}', + text_colour = text_colour, + model_name = model.name, + line_number = problem.line_number, + problem_type = problem.severity, + message = str(problem), + colour_reset = colorama.Fore.RESET, + ldraw_code = ldraw_code, + ) + messages.append(message) + return '\n'.join(messages) import argparse @@ -92,14 +73,14 @@ parser.add_argument('--rebuild', action = 'store_true') parser.add_argument('--flatness', action = 'store_true') args = parser.parse_args() - config = load_config('ldcheck.cfg') - for ldconfig_ldr_path in find_ldconfig_ldr_paths(config): + config_object = config.load_config('ldcheck.cfg') + for ldconfig_ldr_path in config.find_ldconfig_ldr_paths(config_object): with ldconfig_ldr_path.open() as ldconfig_ldr: - load_colours(ldconfig_ldr) + colours.load_colours(ldconfig_ldr) if args.flatness: import filecache cache = filecache.SubfileCache( - ldraw_directories = config['libraries'], + ldraw_directories = config_object['libraries'], ) subfile = cache.prepare_file(args.filename) if not subfile.valid: @@ -118,7 +99,7 @@ model = parse.read_ldraw( file, name = basename(args.filename), - ldraw_directories = config['libraries']) + ldraw_directories = config_object['libraries']) if args.dump_structure: print('header: ' + type(model.header).__name__) for key in sorted(dir(model.header)): @@ -130,7 +111,7 @@ for entry in model.body: print(entry.textual_representation(), end = '\r\n') else: - from testsuite import load_tests, check_model, format_report - test_suite = load_tests() - report = check_model(model, test_suite) - print(format_report(report, model, test_suite)) + import testsuite + test_suite = testsuite.load_tests() + report = testsuite.check_model(model, test_suite) + print(format_report(report, model))
--- a/linetypes.py Sat Jun 08 01:51:50 2019 +0300 +++ b/linetypes.py Sat Jun 08 11:17:17 2019 +0300 @@ -1,3 +1,8 @@ +#!/usr/bin/env python3 +''' + Classes for formally storing LDraw objects +''' + def ldraw_str(value): ' Like str() except removes unneeded ".0"-suffixes ' rep = str(value)
--- a/parse.py Sat Jun 08 01:51:50 2019 +0300 +++ b/parse.py Sat Jun 08 11:17:17 2019 +0300 @@ -1,3 +1,7 @@ +#!/usr/bin/env python3 +''' + Routines for parsing LDraw code. +''' import linetypes import re from geometry import *
--- a/testsuite.py Sat Jun 08 01:51:50 2019 +0300 +++ b/testsuite.py Sat Jun 08 11:17:17 2019 +0300 @@ -1,6 +1,13 @@ +#!/usr/bin/env python3 +''' + Routines for assembling the test suite, and running it on a model. +''' from warnings import warn class ProblemType: + ''' + Represents a type of issue in the part + ''' severities = ['hold', 'warning'] # in descending order def __init__(self, name, severity, message): if severity not in ProblemType.severities: @@ -18,6 +25,10 @@ **args, ) def placeholder_message(self): + ''' + Returns the error message of this problem type, with placeholders + filled in. + ''' if callable(self.message): import inspect spec = inspect.getfullargspec(self.message) @@ -30,6 +41,9 @@ return self.message class Problem: + ''' + Represents a single issue in the part + ''' def __init__(self, problem_class, bad_object, **args): self.problem_class = problem_class self.severity = problem_class.severity @@ -42,6 +56,10 @@ return self.problem_class.message def problem_type(problem_name, **args): + ''' + A decorator that creates a new ProblemType and attaches it to the + decoratable function. + ''' def wrapper(function): if not hasattr(function, 'ldcheck_problem_types'): function.ldcheck_problem_types = {} @@ -51,6 +69,10 @@ return wrapper def report_problem(problem_name, *, bad_object, **args): + ''' + Called from test functions to report issues. This gets later mapped + into Problem objects. + ''' return {'type': problem_name, 'bad-object': bad_object, 'args': args} def name_of_package(package): @@ -87,6 +109,9 @@ return test_suite def problem_key(problem): + ''' + Sorts problems by rank and line number. + ''' rank = ProblemType.severities.index(problem.severity) # sort by severity return (rank, problem.line_number) @@ -100,6 +125,9 @@ return problem_object def check_model(model, test_suite = None): + ''' + Runs the test suite on the model and prepares a report. + ''' if not test_suite: test_suite = load_tests() problems = [] @@ -123,65 +151,18 @@ 'problems': sorted(problems, key = problem_key), } -def problem_text(problem, test_suite): - message = problem.problem_class.message - if callable(message): - message = message(**problem.args) - return message - -def format_report_html(report, model, test_suite): - messages = [] - for problem in report['problems']: - ldraw_code = model.body[problem.body_index].textual_representation() - message = str.format( - '<li class="{problem_type}">{model_name}:{line_number}:' - '{problem_type}: {message}<br />{ldraw_code}</li>', - model_name = model.name, - line_number = problem.line_number, - problem_type = problem.severity, - message = problem_text(problem, test_suite), - ldraw_code = ldraw_code, - ) - messages.append(message) - return '\n'.join(messages) - -def format_report(report, model, test_suite): - import colorama - colorama.init() - messages = [] - for problem in report['problems']: - if problem.severity == 'hold': - text_colour = colorama.Fore.LIGHTRED_EX - elif problem.severity == 'warning': - text_colour = colorama.Fore.LIGHTBLUE_EX - else: - text_colour = '' - ldraw_code = model.body[problem.body_index].textual_representation() - message = str.format( - '{text_colour}{model_name}:{line_number}: {problem_type}: {message}' - '{colour_reset}\n\t{ldraw_code}', - text_colour = text_colour, - model_name = model.name, - line_number = problem.line_number, - problem_type = problem.severity, - message = problem_text(problem, test_suite), - colour_reset = colorama.Fore.RESET, - ldraw_code = ldraw_code, - ) - messages.append(message) - return '\n'.join(messages) - def iterate_problems(test_suite): + ''' + Yields all problem types. + ''' for test_function in test_suite['tests']: yield from test_function.ldcheck_problem_types.values() def all_problem_types(test_suite): + ''' + Returns a sorted list of problem types. + ''' return sorted( iterate_problems(test_suite), key = lambda problem_type: problem_type.name ) - - -if __name__ == '__main__': - from pprint import pprint - pprint(load_tests())
--- a/webfront.py Sat Jun 08 01:51:50 2019 +0300 +++ b/webfront.py Sat Jun 08 11:17:17 2019 +0300 @@ -1,47 +1,48 @@ #!/usr/bin/env python3 -from flask import Flask, render_template, redirect, request -from ldcheck import load_config, load_colours, find_ldconfig_ldr_paths -from parse import read_ldraw -from testsuite import load_tests, check_model, problem_text, all_problem_types +import flask +import testsuite +import config +import parse +import colours -app = Flask('LDCheck') +app = flask.Flask('LDCheck') @app.route('/', methods = ['GET', 'POST']) def web_front(): - test_suite = load_tests() + test_suite = testsuite.load_tests() + request = flask.request if request.method == 'POST': # check if the post request has the file part if 'file' not in request.files or not request.files['file'].filename: - return redirect(request.url) + return flask.redirect(request.url) file = request.files['file'] filename = file.filename - config = load_config('ldcheck.cfg') - for ldconfig_ldr_path in find_ldconfig_ldr_paths(config): + config_object = config.load_config('ldcheck.cfg') + for ldconfig_ldr_path in config.find_ldconfig_ldr_paths(config_object): with ldconfig_ldr_path.open() as ldconfig_ldr: - load_colours(ldconfig_ldr) - model = read_ldraw( + colours.load_colours(ldconfig_ldr) + model = parse.read_ldraw( file.stream, name = filename, - ldraw_directories = config['libraries'], + ldraw_directories = config_object['libraries'], ) - report = check_model(model, test_suite) + report = testsuite.check_model(model, test_suite) # Amend human-readable messages into the report for problem in report['problems']: object = model.body[problem.body_index] - problem.message_str = problem_text(problem, test_suite) + problem.message_str = str(problem) problem.ldraw_code = object.textual_representation() - return render_template('webfront.html', + return flask.render_template('webfront.html', report = report, name = filename, - problem_types = all_problem_types(test_suite) + problem_types = testsuite.all_problem_types(test_suite) ) else: - test_suite = load_tests() - return render_template('webfront.html', + return flask.render_template('webfront.html', report = None, name = None, - problem_types = all_problem_types(test_suite) + problem_types = testsuite.all_problem_types(test_suite) ) @app.route('/static/<path:path>')