ldcheck.py

changeset 146
3555679d276b
parent 94
109fb7cf658f
child 147
bec55b021ae7
equal deleted inserted replaced
95:a3536e51f6bc 146:3555679d276b
1 #!/usr/bin/env python3 1 #!/usr/bin/env python3
2 from sys import version_info 2 import sys
3 if version_info < (3, 4): 3 if sys.version_info < (3, 4):
4 raise RuntimeError('Python 3.4 or newer required') 4 raise RuntimeError('Python 3.4 or newer required')
5
6 from colours import load_colours 5 from colours import load_colours
7 from geometry import * 6 from geometry import *
8 from pathlib import Path 7 from pathlib import Path
9 import linetypes 8 import linetypes
10 import header 9 import header
11 import parse 10 import parse
12 11
13 from os.path import realpath 12 def check_library_paths(library_paths):
14 script_directory = Path(realpath(__file__)).parent 13 for library_path in library_paths:
15
16 def load_config(filename):
17 from configobj import ConfigObj
18 from copy import deepcopy
19 config = ConfigObj(filename, encoding = 'UTF8')
20 read_config = deepcopy(config)
21 if 'libraries' not in config:
22 config['libraries'] = ['/path/to/ldraw']
23 if config != read_config:
24 config.write()
25 check_library_paths(config)
26 return config
27
28 def library_paths(config):
29 for library_path_string in config['libraries']:
30 yield Path(library_path_string).expanduser()
31
32 def check_library_paths(config):
33 from sys import exit
34 problems = False
35 have_paths = False
36 for library_path in library_paths(config):
37 have_paths = True
38 if not library_path.exists(): 14 if not library_path.exists():
39 problems = True 15 raise RuntimeError(str.format(
40 print(str.format( 16 'error: library path {} does not exist',
41 'Library path {} does not exist',
42 library_path, 17 library_path,
43 )) 18 ))
44 elif not library_path.exists(): 19 elif not library_path.exists():
45 problems = True 20 raise RuntimeError(str.format(
46 print(str.format( 21 'error: library path {} is not a directory',
47 'Library path {} is not a directory',
48 library_path, 22 library_path,
49 )) 23 ))
50 if not have_paths:
51 print('No LDraw path specified')
52 problems = True
53 if problems:
54 print('Please fix ldcheck.cfg')
55 exit(1)
56 24
57 def find_ldconfig_ldr_paths(config): 25 def find_ldconfig_ldr_paths(libraries):
58 for library_path in library_paths(config): 26 for library_path in libraries:
59 yield from [ 27 yield from [
60 library_path / path 28 library_path / path
61 for path in ['LDConfig.ldr', 'ldconfig.ldr'] 29 for path in ['LDConfig.ldr', 'ldconfig.ldr']
62 if (library_path / path).is_file() 30 if (library_path / path).is_file()
63 ] 31 ]
64 32
65 import argparse 33 def load_ldconfig(libraries):
34 ldconfig_ldr_paths = list(find_ldconfig_ldr_paths(libraries))
35 if not ldconfig_ldr_paths:
36 raise RuntimeError('could not find any LDConfig.ldr')
37 for ldconfig_ldr_path in ldconfig_ldr_paths:
38 with ldconfig_ldr_path.open() as ldconfig_ldr:
39 load_colours(ldconfig_ldr)
66 40
67 class ListProblemsAction(argparse.Action): 41 def parse_commandline_arguments():
68 def __init__(self, option_strings, dest, nargs = None, **kwargs): 42 import os
69 super().__init__(option_strings, dest, nargs = 0, **kwargs) 43 rcpath = Path(os.path.expanduser('~/.config/ldcheckrc'))
70 def __call__(self, *args, **kwargs): 44 if rcpath.exists():
71 import testsuite 45 with rcpath.open() as file:
72 from sys import exit 46 rcargs = ['--' + line.strip() for line in file]
73 from re import sub 47 else:
74 test_suite = testsuite.load_tests() 48 rcargs = []
75 for warning_type in testsuite.all_problem_types(test_suite): 49 import argparse
76 print(str.format('{name}: {severity}: "{message}"', 50 class ListProblemTypesAction(argparse.Action):
77 name = warning_type.name, 51 def __init__(self, option_strings, dest, nargs = None, **kwargs):
78 severity = warning_type.severity, 52 super().__init__(option_strings, dest, nargs = 0, **kwargs)
79 message = warning_type.placeholder_message(), 53 def __call__(self, *args, **kwargs):
80 )) 54 import testsuite
81 exit(0) 55 test_suite = testsuite.load_tests()
82 56 for warning_type in testsuite.all_problem_types(test_suite):
83 if __name__ == '__main__': 57 print(str.format('{name}: {severity}: "{message}"',
84 from sys import argv 58 name = warning_type.name,
59 severity = warning_type.severity,
60 message = warning_type.placeholder_message(),
61 ))
62 sys.exit(0)
85 parser = argparse.ArgumentParser() 63 parser = argparse.ArgumentParser()
86 parser.add_argument('filename') 64 parser.add_argument('filename')
87 parser.add_argument('--list', 65 parser.add_argument('--list',
88 action = ListProblemsAction, 66 action = ListProblemTypesAction,
89 help = 'lists all possible problem types and exit', 67 help = 'lists all possible problem types and exit',
90 ) 68 )
91 parser.add_argument('--dump', 69 parser.add_argument('--dump',
92 action = 'store_true', 70 action = 'store_true',
93 help = 'dumps the internal parsed structure of the part file', 71 help = 'dumps the internal parsed structure of the part file',
99 ) 77 )
100 parser.add_argument('--subfile', 78 parser.add_argument('--subfile',
101 action = 'store_true', 79 action = 'store_true',
102 help = 'finds a subfile by name and prints out information about it' 80 help = 'finds a subfile by name and prints out information about it'
103 ) 81 )
104 args = parser.parse_args() 82 parser.add_argument('-l', '--library', action = 'append')
105 config = load_config('ldcheck.cfg') 83 arglist = rcargs + sys.argv[1:]
106 for ldconfig_ldr_path in find_ldconfig_ldr_paths(config): 84 return parser.parse_args(arglist)
107 with ldconfig_ldr_path.open() as ldconfig_ldr: 85
108 load_colours(ldconfig_ldr) 86 def main():
87 args = parse_commandline_arguments()
88 # Make sure that we have at least one library path specified.
89 if not args.library:
90 raise RuntimeError(
91 'Please specify libraries using the -l / --library switch.\n'
92 'For example: -l ~/ldraw or --library=~/ldraw\n'
93 'Multiple --library switches may be used.')
94 # Prepare the list of libraries. This also expands the ~ for the home
95 # directory
96 import os
97 libraries = [Path(os.path.expanduser(library)) for library in args.library]
98 check_library_paths(libraries)
99 load_ldconfig(libraries)
109 if args.subfile: 100 if args.subfile:
101 # Subfile debug mode: searches for the specified subfile from the LDraw
102 # libraries, opens it as if it was referenced by something and prints
103 # out all information that is calculated from this subfile.
110 import filecache 104 import filecache
111 cache = filecache.SubfileCache( 105 cache = filecache.SubfileCache(ldraw_directories = libraries)
112 ldraw_directories = config['libraries'],
113 )
114 subfile = cache.prepare_file(args.filename) 106 subfile = cache.prepare_file(args.filename)
115 if not subfile.valid: 107 if not subfile.valid:
116 print(subfile.problem) 108 print(subfile.problem)
117 else: 109 else:
118 print('Flat dimensions:', repr(subfile.flatness)) 110 print('Flat dimensions:', repr(subfile.flatness))
122 with open(args.filename, 'rb') as file: 114 with open(args.filename, 'rb') as file:
123 from os.path import basename 115 from os.path import basename
124 model = parse.read_ldraw( 116 model = parse.read_ldraw(
125 file, 117 file,
126 name = basename(args.filename), 118 name = basename(args.filename),
127 ldraw_directories = config['libraries']) 119 ldraw_directories = libraries)
128 if args.dump: 120 if args.dump:
121 # Dump mode: prints out the structure of the processed LDraw file
129 print('header: ' + type(model.header).__name__) 122 print('header: ' + type(model.header).__name__)
130 for key in sorted(dir(model.header)): 123 for key in sorted(dir(model.header)):
131 if not key.startswith('__'): 124 if not key.startswith('__'):
132 print('\t' + key + ': ' + repr(getattr(model.header, key))) 125 print('\t' + key + ': ' + repr(getattr(model.header, key)))
133 for i, entry in enumerate(model.body): 126 for i, entry in enumerate(model.body):
134 if model.header.valid and i == model.header_size: 127 if model.header.valid and i == model.header_size:
128 # Mark where the header is considered to end
135 print('--------- End of header') 129 print('--------- End of header')
136 print(entry) 130 print(entry)
137 elif args.rebuild: 131 elif args.rebuild:
132 # Debug rebuild mode: open the file, parse it and turn it back
133 # into LDraw code and write it into stdout. This is used to ensure
134 # that LDCheck does not miss any information while parsing files.
138 for entry in model.body: 135 for entry in model.body:
139 print(entry.textual_representation(), end = '\r\n') 136 print(entry.textual_representation(), end = '\r\n')
140 else: 137 else:
138 # Regular mode
141 from testsuite import load_tests, check_model, format_report 139 from testsuite import load_tests, check_model, format_report
140 # load the test suite
141 # TODO: maybe add some command line argument to filter tests
142 # in case the user wants to run some specific tests only or
143 # possibly leave some test out
142 test_suite = load_tests() 144 test_suite = load_tests()
145 # use the test suite to check the model
143 report = check_model(model, test_suite) 146 report = check_model(model, test_suite)
147 # print out the report
144 print(format_report(report, model, test_suite)) 148 print(format_report(report, model, test_suite))
149
150 if __name__ == '__main__':
151 try:
152 main()
153 except RuntimeError as e:
154 import sys
155 print('error:', str(e), file = sys.stderr)
156 sys.exit(1)

mercurial