ldcheck.py

Thu, 26 Aug 2021 19:16:25 +0300

author
Teemu Piippo <teemu@hecknology.net>
date
Thu, 26 Aug 2021 19:16:25 +0300
changeset 146
3555679d276b
parent 94
109fb7cf658f
child 147
bec55b021ae7
permissions
-rwxr-xr-x

Cleanup ldcheck.py
Remove dependency on configobj. Configuration file replaced with command line arguments and rcfile

#!/usr/bin/env python3
import sys
if sys.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

def check_library_paths(library_paths):
    for library_path in library_paths:
        if not library_path.exists():
            raise RuntimeError(str.format(
                'error: library path {} does not exist',
                library_path,
            ))
        elif not library_path.exists():
            raise RuntimeError(str.format(
                'error: library path {} is not a directory',
                library_path,
            ))

def find_ldconfig_ldr_paths(libraries):
    for library_path in libraries:
        yield from [
            library_path / path
            for path in ['LDConfig.ldr', 'ldconfig.ldr']
            if (library_path / path).is_file()
        ]

def load_ldconfig(libraries):
    ldconfig_ldr_paths = list(find_ldconfig_ldr_paths(libraries))
    if not ldconfig_ldr_paths:
        raise RuntimeError('could not find any LDConfig.ldr')
    for ldconfig_ldr_path in ldconfig_ldr_paths:
        with ldconfig_ldr_path.open() as ldconfig_ldr:
            load_colours(ldconfig_ldr)

def parse_commandline_arguments():
    import os
    rcpath = Path(os.path.expanduser('~/.config/ldcheckrc'))
    if rcpath.exists():
        with rcpath.open() as file:
            rcargs = ['--' + line.strip() for line in file]
    else:
        rcargs = []
    import argparse
    class ListProblemTypesAction(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
            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(),
                ))
            sys.exit(0)
    parser = argparse.ArgumentParser()
    parser.add_argument('filename')
    parser.add_argument('--list',
        action = ListProblemTypesAction,
        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('-l', '--library', action = 'append')
    arglist = rcargs + sys.argv[1:]
    return parser.parse_args(arglist)

def main():
    args = parse_commandline_arguments()
    # Make sure that we have at least one library path specified.
    if not args.library:
        raise RuntimeError(
            'Please specify libraries using the -l / --library switch.\n'
            'For example: -l ~/ldraw or --library=~/ldraw\n'
            'Multiple --library switches may be used.')
    # Prepare the list of libraries. This also expands the ~ for the home
    # directory
    import os
    libraries = [Path(os.path.expanduser(library)) for library in args.library]
    check_library_paths(libraries)
    load_ldconfig(libraries)
    if args.subfile:
        # Subfile debug mode: searches for the specified subfile from the LDraw
        # libraries, opens it as if it was referenced by something and prints
        # out all information that is calculated from this subfile.
        import filecache
        cache = filecache.SubfileCache(ldraw_directories = 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:
        with open(args.filename, 'rb') as file:
            from os.path import basename
            model = parse.read_ldraw(
                file,
                name = basename(args.filename),
                ldraw_directories = libraries)
            if args.dump:
                # Dump mode: prints out the structure of the processed LDraw file
                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:
                        # Mark where the header is considered to end
                        print('--------- End of header')
                    print(entry)
            elif args.rebuild:
                # Debug rebuild mode: open the file, parse it and turn it back
                # into LDraw code and write it into stdout. This is used to ensure
                # that LDCheck does not miss any information while parsing files.
                for entry in model.body:
                    print(entry.textual_representation(), end = '\r\n')
            else:
                # Regular mode
                from testsuite import load_tests, check_model, format_report
                # load the test suite
                # TODO: maybe add some command line argument to filter tests
                # in case the user wants to run some specific tests only or
                # possibly leave some test out
                test_suite = load_tests()
                # use the test suite to check the model
                report = check_model(model, test_suite)
                # print out the report
                print(format_report(report, model, test_suite))

if __name__ == '__main__':
    try:
        main()
    except RuntimeError as e:
        import sys
        print('error:', str(e), file = sys.stderr)
        sys.exit(1)

mercurial