Thu, 26 Aug 2021 19:16:25 +0300
Cleanup ldcheck.py
Remove dependency on configobj. Configuration file replaced with command line arguments and rcfile
colours.py | file | annotate | diff | comparison | revisions | |
ldcheck.py | file | annotate | diff | comparison | revisions |
--- a/colours.py Mon Jun 24 19:21:49 2019 +0300 +++ b/colours.py Thu Aug 26 19:16:25 2021 +0300 @@ -15,10 +15,7 @@ else: return str(self.index) def __repr__(self): - try: - return 'colours.' + colours_inverse_dict[self.index] - except KeyError: - return str.format('Colour({!r})', self.index) + return str.format('Colour({!r})', self.index) @property def is_direct_colour(self): return self.index >= 0x2000000 @@ -98,15 +95,7 @@ colour = parse_ldconfig_ldr_line(line) yield (colour['code'], colour) -class colours: - ''' - LDConfig colour namespace, exists for interactive mode and for - Colour.__repr__ to return something pretty. - ''' - pass - -# LDConfig lookup tables -colours_inverse_dict = {} +# LDConfig lookup table ldconfig_colour_data = {} def load_colours(ldconfig_ldr): @@ -115,13 +104,3 @@ ''' global ldconfig_colour_data ldconfig_colour_data = dict(parse_ldconfig_ldr(ldconfig_ldr)) - for index, colour in ldconfig_colour_data.items(): - identifier = colour['name'].replace(' ', '_').lower() - setattr(colours, identifier, Colour(index)) - colours_inverse_dict[index] = identifier - -# Interactive mode support (pass LDConfig.ldr path as a command-line argument) -if __name__ == '__main__': - from sys import argv - with open(argv[1]) as ldconfig_ldr: - load_colours(ldconfig_ldr)
--- a/ldcheck.py Mon Jun 24 19:21:49 2019 +0300 +++ b/ldcheck.py Thu Aug 26 19:16:25 2021 +0300 @@ -1,8 +1,7 @@ #!/usr/bin/env python3 -from sys import version_info -if version_info < (3, 4): +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 @@ -10,82 +9,61 @@ 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() - -def check_library_paths(config): - from sys import exit - problems = False - have_paths = False - for library_path in library_paths(config): - have_paths = True +def check_library_paths(library_paths): + for library_path in library_paths: if not library_path.exists(): - problems = True - print(str.format( - 'Library path {} does not exist', + raise RuntimeError(str.format( + 'error: library path {} does not exist', library_path, )) elif not library_path.exists(): - problems = True - print(str.format( - 'Library path {} is not a directory', + raise RuntimeError(str.format( + 'error: 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): +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() ] -import argparse +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) -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) - -if __name__ == '__main__': - from sys import argv +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 = ListProblemsAction, + action = ListProblemTypesAction, help = 'lists all possible problem types and exit', ) parser.add_argument('--dump', @@ -101,16 +79,30 @@ action = 'store_true', help = 'finds a subfile by name and prints out information about it' ) - args = parser.parse_args() - config = load_config('ldcheck.cfg') - for ldconfig_ldr_path in find_ldconfig_ldr_paths(config): - with ldconfig_ldr_path.open() as ldconfig_ldr: - load_colours(ldconfig_ldr) + 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 = config['libraries'], - ) + cache = filecache.SubfileCache(ldraw_directories = libraries) subfile = cache.prepare_file(args.filename) if not subfile.valid: print(subfile.problem) @@ -124,21 +116,41 @@ model = parse.read_ldraw( file, name = basename(args.filename), - ldraw_directories = config['libraries']) + 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)