ldcheck.py

Thu, 26 Aug 2021 19:37:00 +0300

author
Teemu Piippo <teemu@hecknology.net>
date
Thu, 26 Aug 2021 19:37:00 +0300
changeset 148
8f621aa4cfd7
parent 147
bec55b021ae7
child 149
7c01f9876b69
permissions
-rwxr-xr-x

Update license year

#!/usr/bin/env python3
<<<<<<< /home/teemu/dev/ldcheck/ldcheck.py
import sys
if sys.version_info < (3, 4):
=======
import argparse
from sys import version_info
if version_info < (3, 4):
>>>>>>> /tmp/ldcheck~other.ou_xbg_k.py
    raise RuntimeError('Python 3.4 or newer required')
<<<<<<< /home/teemu/dev/ldcheck/ldcheck.py
from colours import load_colours
=======

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

>>>>>>> /tmp/ldcheck~other.ou_xbg_k.py
from geometry import *
from pathlib import Path
import linetypes
import header
import parse

<<<<<<< /home/teemu/dev/ldcheck/ldcheck.py
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)
=======
from os.path import realpath
script_directory = Path(realpath(__file__)).parent

def config_dirs():
    import appdirs
    appauthor = 'Teemu Piippo'
    dirs = appdirs.AppDirs(appname, appauthor)
    return {
        'user': Path(dirs.user_config_dir),
        'system': Path(dirs.site_config_dir),
    }

def ldraw_dirs_from_config():
    libraries = []
    dirs = config_dirs()
    for dirpath in [dirs['system'], dirs['user']]:
        filename = dirpath / 'ldcheck.cfg'
        from configobj import ConfigObj
        config = ConfigObj(str(filename), encoding = 'UTF8')
        if 'libraries' in config:
            libraries = expand_paths(config['libraries'])
    return libraries

def expand_paths(paths):
    return [
        Path(library).expanduser()
        for library in paths
    ]

class LDrawContext:
    '''
        Contains context-dependant LDraw information, like library directory
        paths and the colour table.
    '''
    def __init__(self, libraries = None):
        self._libraries = libraries
        if not self._libraries:
            self._libraries = ldraw_dirs_from_config()
        self.ldconfig_colour_data = self.load_ldconfig_ldr()
        self.check_library_paths()
    @property
    def libraries(self):
        return self._libraries
    @property
    def colours(self):
        return self.ldconfig_colour_data
    def ldconfig_ldr_discovery(self):
        for library_path in self.libraries:
            yield from [
                library_path / path
                for path in ['LDConfig.ldr', 'ldconfig.ldr']
                if (library_path / path).is_file()
            ]
    def load_ldconfig_ldr(self):
        from colours import load_colours
        for ldconfig_ldr_path in self.ldconfig_ldr_discovery():
            with open(ldconfig_ldr_path) as ldconfig_ldr:
                return load_colours(ldconfig_ldr)
    def check_library_paths(self):
        from sys import stderr
        problems = False
        have_paths = False
        for library_path in self.libraries:
            have_paths = True
            if not library_path.exists():
                problems = True
                print(str.format(
                    'Library path {} does not exist',
                    library_path,
                ), file = stderr)
            elif not library_path.exists():
                problems = True
                print(str.format(
                    'Library path {} is not a directory',
                    library_path,
                ), file = stderr)
        if not have_paths:
            raise RuntimeError('no LDraw library paths specified')
    def is_ldconfig_colour(self, colour):
        return colour.index in self.colours
    def colour_name(self, colour):
        if self.is_ldconfig_colour(colour):
            return self.colours[self.index]['name']
        else:
            return str(colour)
    def colour_face(self, colour):
        if self.is_ldconfig_colour(colour):
            return self.colours[self.index]['value']
        elif colour.is_direct_colour:
            return '#%06X' % (self.index & 0xffffff)
        else:
            return '#000000'
    def is_valid_colour(self, colour):
        return self.is_ldconfig_colour(colour) or colour.is_direct_colour
>>>>>>> /tmp/ldcheck~other.ou_xbg_k.py

<<<<<<< /home/teemu/dev/ldcheck/ldcheck.py
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)
=======
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 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
>>>>>>> /tmp/ldcheck~other.ou_xbg_k.py
    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'
    )
<<<<<<< /home/teemu/dev/ldcheck/ldcheck.py
    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)
=======
    parser.add_argument('--color',
        action = 'store_true',
        help = 'use colors'
    )
    parser.add_argument('-d', '--ldraw-dir',
        nargs = '+',
        help = 'specify LDraw directory path(s)',
    )
    parser.add_argument('-v', '--version',
        action = 'version',
        version = str.format('{appname} {version}',
            appname = appname,
            version = version_string,
        ),
    )
    args = parser.parse_args()
    libraries = ldraw_dirs_from_config()
    if args.ldraw_dir:
        libraries = expand_paths(args.ldraw_dir)
    try:
        context = LDrawContext(libraries)
    except RuntimeError as error:
        print('error:', str(error), file = stderr)
        exit(1)
    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
>>>>>>> /tmp/ldcheck~other.ou_xbg_k.py
    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
<<<<<<< /home/teemu/dev/ldcheck/ldcheck.py
        cache = filecache.SubfileCache(ldraw_directories = libraries)
=======
        cache = filecache.SubfileCache(context = context)
>>>>>>> /tmp/ldcheck~other.ou_xbg_k.py
        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:
<<<<<<< /home/teemu/dev/ldcheck/ldcheck.py
        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)
=======
        try:
            with open(args.filename, 'rb') as file:
                from os.path import basename
                model = parse.read_ldraw(
                    file,
                    name = basename(args.filename),
                    context = context)
                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)
>>>>>>> /tmp/ldcheck~other.ou_xbg_k.py

mercurial