ldcheck.py

Fri, 18 Sep 2020 21:54:18 +0300

author
Teemu Piippo <teemu@hecknology.net>
date
Fri, 18 Sep 2020 21:54:18 +0300
changeset 143
d1bed5f4b21a
parent 122
116a81996832
child 145
fde18c4d6784
permissions
-rwxr-xr-x

added whitespace unit tests

#!/usr/bin/env python3
from sys import version_info
if version_info < (3, 4):
    raise RuntimeError('Python 3.4 or newer required')

appname = 'ldcheck'
version = (1, 0, 9999)
version_string = str.join('.', map(str, version))

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 = None):
    if filename is None:
        filename = script_directory / 'ldcheck.cfg'
    from configobj import ConfigObj
    from copy import deepcopy
    config = ConfigObj(str(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)
    load_ldconfig_ldr(config)
    return config

def library_paths(config):
    for library_path_string in config['libraries']:
        yield Path(library_path_string).expanduser()

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()
        ]

import argparse

class ListProblemsAction(argparse.Action):
    def __init__(self, option_strings, dest, nargs = None, **kwargs):
        super().__init__(option_strings, dest, nargs = 0, **kwargs)
    def __call__(self, *args, **kwargs):
        import testsuite
        from sys import exit
        from re import sub
        test_suite = testsuite.load_tests()
        for warning_type in testsuite.all_problem_types(test_suite):
            print(str.format('{name}: {severity}: "{message}"',
                name = warning_type.name,
                severity = warning_type.severity,
                message = warning_type.placeholder_message(),
            ))
        exit(0)

def load_ldconfig_ldr(config):
    for ldconfig_ldr_path in find_ldconfig_ldr_paths(config):
        with ldconfig_ldr_path.open() as ldconfig_ldr:
            load_colours(ldconfig_ldr)

def format_report(report, model, test_suite, *, use_colors = True):
    from testsuite import problem_text
    messages = []
    for problem in report['problems']:
        text_colour = ''
        if use_colors:
            if problem.severity == 'hold':
                text_colour = colorama.Fore.LIGHTRED_EX
            elif problem.severity == 'warning':
                text_colour = colorama.Fore.LIGHTBLUE_EX
        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 = use_colors and colorama.Fore.RESET or '',
            ldraw_code = ldraw_code,
        )
        messages.append(message)
    return '\n'.join(messages)

if __name__ == '__main__':
    from sys import argv, stderr, exit
    parser = argparse.ArgumentParser()
    parser.add_argument('filename')
    parser.add_argument('--list',
        action = ListProblemsAction,
        help = 'lists all possible problem types and exit',
    )
    parser.add_argument('--dump',
        action = 'store_true',
        help = 'dumps the internal parsed structure of the part file',
    )
    parser.add_argument('--rebuild',
        action = 'store_true',
        help = 'parses the part file and prints it back out, used for '
            'testing whether the program interprets part files correctly',
    )
    parser.add_argument('--subfile',
        action = 'store_true',
        help = 'finds a subfile by name and prints out information about it'
    )
    parser.add_argument('--color',
        action = 'store_true',
        help = 'use colors'
    )
    parser.add_argument('-v', '--version',
        action = 'version',
        version = str.format('{appname} {version}',
            appname = appname,
            version = version_string,
        ),
    )
    args = parser.parse_args()
    config = load_config()
    if args.color:
        try:
            import colorama
            colorama.init()
        except ImportError:
            print('Use of --color requires the colorama module, disabling colours', file = stderr)
            args.color = False
    if args.subfile:
        import filecache
        cache = filecache.SubfileCache(
            ldraw_directories = config['libraries'],
        )
        subfile = cache.prepare_file(args.filename)
        if not subfile.valid:
            print(subfile.problem)
        else:
            print('Flat dimensions:', repr(subfile.flatness))
            print('Description:', repr(subfile.description))
            print('Contains studs:', repr(subfile.has_studs))
    else:
        try:
            with open(args.filename, 'rb') as file:
                from os.path import basename
                model = parse.read_ldraw(
                    file,
                    name = basename(args.filename),
                    ldraw_directories = config['libraries'])
                if args.dump:
                    print('header: ' + type(model.header).__name__)
                    for key in sorted(dir(model.header)):
                        if not key.startswith('__'):
                            print('\t' + key + ': ' + repr(getattr(model.header, key)))
                    for i, entry in enumerate(model.body):
                        if model.header.valid and i == model.header_size:
                            print('--------- End of header')
                        print(entry)
                elif args.rebuild:
                    for entry in model.body:
                        print(entry.textual_representation(), end = '\r\n')
                else:
                    from testsuite import load_tests, check_model
                    test_suite = load_tests()
                    report = check_model(model, test_suite)
                    print(format_report(
                        report,
                        model,
                        test_suite,
                        use_colors = args.color
                    ))
        except FileNotFoundError:
            print(str.format(
                'no such file: {filename!r}',
                filename = args.filename
            ), file = stderr)
            exit(1)

mercurial