ldcheck.py

changeset 146
3555679d276b
parent 94
109fb7cf658f
child 147
bec55b021ae7
--- 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)

mercurial