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) |