ldcheck.py

changeset 149
7c01f9876b69
parent 147
bec55b021ae7
child 150
fcc07f6907a8
equal deleted inserted replaced
148:8f621aa4cfd7 149:7c01f9876b69
1 #!/usr/bin/env python3 1 #!/usr/bin/env python3
2 <<<<<<< /home/teemu/dev/ldcheck/ldcheck.py
3 import sys 2 import sys
4 if sys.version_info < (3, 4): 3 if sys.version_info < (3, 4):
5 =======
6 import argparse
7 from sys import version_info
8 if version_info < (3, 4):
9 >>>>>>> /tmp/ldcheck~other.ou_xbg_k.py
10 raise RuntimeError('Python 3.4 or newer required') 4 raise RuntimeError('Python 3.4 or newer required')
11 <<<<<<< /home/teemu/dev/ldcheck/ldcheck.py
12 from colours import load_colours 5 from colours import load_colours
13 ======= 6
7 try:
8 import colorama
9 except ImportError:
10 colorama = None
14 11
15 appname = 'ldcheck' 12 appname = 'ldcheck'
16 version = (1, 0, 9999) 13 version = (1, 0, 9999)
17 version_string = str.join('.', map(str, version)) 14 version_string = str.join('.', map(str, version))
18
19 >>>>>>> /tmp/ldcheck~other.ou_xbg_k.py
20 from geometry import * 15 from geometry import *
21 from pathlib import Path 16 from pathlib import Path
22 import linetypes 17 import linetypes
23 import header 18 import header
24 import parse 19 import parse
25 20
26 <<<<<<< /home/teemu/dev/ldcheck/ldcheck.py
27 def check_library_paths(library_paths): 21 def check_library_paths(library_paths):
28 for library_path in library_paths: 22 for library_path in library_paths:
29 if not library_path.exists(): 23 if not library_path.exists():
30 raise RuntimeError(str.format( 24 raise RuntimeError(str.format(
31 'error: library path {} does not exist', 25 'error: library path {} does not exist',
50 if not ldconfig_ldr_paths: 44 if not ldconfig_ldr_paths:
51 raise RuntimeError('could not find any LDConfig.ldr') 45 raise RuntimeError('could not find any LDConfig.ldr')
52 for ldconfig_ldr_path in ldconfig_ldr_paths: 46 for ldconfig_ldr_path in ldconfig_ldr_paths:
53 with ldconfig_ldr_path.open() as ldconfig_ldr: 47 with ldconfig_ldr_path.open() as ldconfig_ldr:
54 load_colours(ldconfig_ldr) 48 load_colours(ldconfig_ldr)
55 =======
56 from os.path import realpath
57 script_directory = Path(realpath(__file__)).parent
58
59 def config_dirs():
60 import appdirs
61 appauthor = 'Teemu Piippo'
62 dirs = appdirs.AppDirs(appname, appauthor)
63 return {
64 'user': Path(dirs.user_config_dir),
65 'system': Path(dirs.site_config_dir),
66 }
67
68 def ldraw_dirs_from_config():
69 libraries = []
70 dirs = config_dirs()
71 for dirpath in [dirs['system'], dirs['user']]:
72 filename = dirpath / 'ldcheck.cfg'
73 from configobj import ConfigObj
74 config = ConfigObj(str(filename), encoding = 'UTF8')
75 if 'libraries' in config:
76 libraries = expand_paths(config['libraries'])
77 return libraries
78
79 def expand_paths(paths):
80 return [
81 Path(library).expanduser()
82 for library in paths
83 ]
84 49
85 class LDrawContext: 50 class LDrawContext:
86 ''' 51 '''
87 Contains context-dependant LDraw information, like library directory 52 Contains context-dependant LDraw information, like library directory
88 paths and the colour table. 53 paths and the colour table.
145 return '#%06X' % (self.index & 0xffffff) 110 return '#%06X' % (self.index & 0xffffff)
146 else: 111 else:
147 return '#000000' 112 return '#000000'
148 def is_valid_colour(self, colour): 113 def is_valid_colour(self, colour):
149 return self.is_ldconfig_colour(colour) or colour.is_direct_colour 114 return self.is_ldconfig_colour(colour) or colour.is_direct_colour
150 >>>>>>> /tmp/ldcheck~other.ou_xbg_k.py 115
151
152 <<<<<<< /home/teemu/dev/ldcheck/ldcheck.py
153 def parse_commandline_arguments(): 116 def parse_commandline_arguments():
154 import os 117 import os
155 rcpath = Path(os.path.expanduser('~/.config/ldcheckrc')) 118 rcpath = Path(os.path.expanduser('~/.config/ldcheckrc'))
156 if rcpath.exists(): 119 if rcpath.exists():
157 with rcpath.open() as file: 120 with rcpath.open() as file:
170 name = warning_type.name, 133 name = warning_type.name,
171 severity = warning_type.severity, 134 severity = warning_type.severity,
172 message = warning_type.placeholder_message(), 135 message = warning_type.placeholder_message(),
173 )) 136 ))
174 sys.exit(0) 137 sys.exit(0)
175 ======= 138 parser = argparse.ArgumentParser()
176 class ListProblemsAction(argparse.Action): 139 parser.add_argument('filename')
177 def __init__(self, option_strings, dest, nargs = None, **kwargs): 140 parser.add_argument('-l', '--library', action = 'append')
178 super().__init__(option_strings, dest, nargs = 0, **kwargs) 141 parser.add_argument('--list',
179 def __call__(self, *args, **kwargs): 142 action = ListProblemTypesAction,
180 import testsuite 143 help = 'lists all possible problem types and exit',
181 from sys import exit 144 )
182 from re import sub 145 parser.add_argument('--dump',
183 test_suite = testsuite.load_tests() 146 action = 'store_true',
184 for warning_type in testsuite.all_problem_types(test_suite): 147 help = 'dumps the internal parsed structure of the part file',
185 print(str.format('{name}: {severity}: "{message}"', 148 )
186 name = warning_type.name, 149 parser.add_argument('--rebuild',
187 severity = warning_type.severity, 150 action = 'store_true',
188 message = warning_type.placeholder_message(), 151 help = 'parses the part file and prints it back out, used for '
189 )) 152 'testing whether the program interprets part files correctly',
190 exit(0) 153 )
154 parser.add_argument('--subfile',
155 action = 'store_true',
156 help = 'finds a subfile by name and prints out information about it'
157 )
158 parser.add_argument('--color',
159 action = 'store_true',
160 help = 'use colors'
161 )
162 parser.add_argument('-d', '--ldraw-dir',
163 nargs = '+',
164 help = 'specify LDraw directory path(s)',
165 )
166 parser.add_argument('-v', '--version',
167 action = 'version',
168 version = str.format('{appname} {version}',
169 appname = appname,
170 version = version_string,
171 ),
172 )
173 arglist = rcargs + sys.argv[1:]
174 return parser.parse_args(arglist)
191 175
192 def format_report(report, model, test_suite, *, use_colors = True): 176 def format_report(report, model, test_suite, *, use_colors = True):
193 from testsuite import problem_text 177 from testsuite import problem_text
194 messages = [] 178 messages = []
195 for problem in report['problems']: 179 for problem in report['problems']:
212 ldraw_code = ldraw_code, 196 ldraw_code = ldraw_code,
213 ) 197 )
214 messages.append(message) 198 messages.append(message)
215 return '\n'.join(messages) 199 return '\n'.join(messages)
216 200
217 if __name__ == '__main__':
218 from sys import argv, stderr, exit
219 >>>>>>> /tmp/ldcheck~other.ou_xbg_k.py
220 parser = argparse.ArgumentParser()
221 parser.add_argument('filename')
222 parser.add_argument('--list',
223 action = ListProblemTypesAction,
224 help = 'lists all possible problem types and exit',
225 )
226 parser.add_argument('--dump',
227 action = 'store_true',
228 help = 'dumps the internal parsed structure of the part file',
229 )
230 parser.add_argument('--rebuild',
231 action = 'store_true',
232 help = 'parses the part file and prints it back out, used for '
233 'testing whether the program interprets part files correctly',
234 )
235 parser.add_argument('--subfile',
236 action = 'store_true',
237 help = 'finds a subfile by name and prints out information about it'
238 )
239 <<<<<<< /home/teemu/dev/ldcheck/ldcheck.py
240 parser.add_argument('-l', '--library', action = 'append')
241 arglist = rcargs + sys.argv[1:]
242 return parser.parse_args(arglist)
243
244 def main(): 201 def main():
245 args = parse_commandline_arguments() 202 args = parse_commandline_arguments()
246 # Make sure that we have at least one library path specified. 203 # Make sure that we have at least one library path specified.
247 if not args.library: 204 if not args.library:
248 raise RuntimeError( 205 raise RuntimeError(
253 # directory 210 # directory
254 import os 211 import os
255 libraries = [Path(os.path.expanduser(library)) for library in args.library] 212 libraries = [Path(os.path.expanduser(library)) for library in args.library]
256 check_library_paths(libraries) 213 check_library_paths(libraries)
257 load_ldconfig(libraries) 214 load_ldconfig(libraries)
258 =======
259 parser.add_argument('--color',
260 action = 'store_true',
261 help = 'use colors'
262 )
263 parser.add_argument('-d', '--ldraw-dir',
264 nargs = '+',
265 help = 'specify LDraw directory path(s)',
266 )
267 parser.add_argument('-v', '--version',
268 action = 'version',
269 version = str.format('{appname} {version}',
270 appname = appname,
271 version = version_string,
272 ),
273 )
274 args = parser.parse_args()
275 libraries = ldraw_dirs_from_config()
276 if args.ldraw_dir:
277 libraries = expand_paths(args.ldraw_dir)
278 try: 215 try:
279 context = LDrawContext(libraries) 216 context = LDrawContext(libraries)
280 except RuntimeError as error: 217 except RuntimeError as error:
281 print('error:', str(error), file = stderr) 218 print('error:', str(error), file = stderr)
282 exit(1) 219 exit(1)
285 import colorama 222 import colorama
286 colorama.init() 223 colorama.init()
287 except ImportError: 224 except ImportError:
288 print('Use of --color requires the colorama module, disabling colours', file = stderr) 225 print('Use of --color requires the colorama module, disabling colours', file = stderr)
289 args.color = False 226 args.color = False
290 >>>>>>> /tmp/ldcheck~other.ou_xbg_k.py
291 if args.subfile: 227 if args.subfile:
292 # Subfile debug mode: searches for the specified subfile from the LDraw 228 # Subfile debug mode: searches for the specified subfile from the LDraw
293 # libraries, opens it as if it was referenced by something and prints 229 # libraries, opens it as if it was referenced by something and prints
294 # out all information that is calculated from this subfile. 230 # out all information that is calculated from this subfile.
295 import filecache 231 import filecache
296 <<<<<<< /home/teemu/dev/ldcheck/ldcheck.py
297 cache = filecache.SubfileCache(ldraw_directories = libraries)
298 =======
299 cache = filecache.SubfileCache(context = context) 232 cache = filecache.SubfileCache(context = context)
300 >>>>>>> /tmp/ldcheck~other.ou_xbg_k.py
301 subfile = cache.prepare_file(args.filename) 233 subfile = cache.prepare_file(args.filename)
302 if not subfile.valid: 234 if not subfile.valid:
303 print(subfile.problem) 235 print(subfile.problem)
304 else: 236 else:
305 print('Flat dimensions:', repr(subfile.flatness)) 237 print('Flat dimensions:', repr(subfile.flatness))
306 print('Description:', repr(subfile.description)) 238 print('Description:', repr(subfile.description))
307 print('Contains studs:', repr(subfile.has_studs)) 239 print('Contains studs:', repr(subfile.has_studs))
308 else: 240 else:
309 <<<<<<< /home/teemu/dev/ldcheck/ldcheck.py
310 with open(args.filename, 'rb') as file: 241 with open(args.filename, 'rb') as file:
311 from os.path import basename 242 try:
312 model = parse.read_ldraw( 243 from os.path import basename
313 file, 244 model = parse.read_ldraw(
314 name = basename(args.filename), 245 file,
315 ldraw_directories = libraries) 246 name = basename(args.filename),
316 if args.dump: 247 context = context)
317 # Dump mode: prints out the structure of the processed LDraw file 248 if args.dump:
318 print('header: ' + type(model.header).__name__) 249 # Dump mode: prints out the structure of the processed LDraw file
319 for key in sorted(dir(model.header)): 250 print('header: ' + type(model.header).__name__)
320 if not key.startswith('__'): 251 for key in sorted(dir(model.header)):
321 print('\t' + key + ': ' + repr(getattr(model.header, key))) 252 if not key.startswith('__'):
322 for i, entry in enumerate(model.body): 253 print('\t' + key + ': ' + repr(getattr(model.header, key)))
323 if model.header.valid and i == model.header_size: 254 for i, entry in enumerate(model.body):
324 # Mark where the header is considered to end 255 if model.header.valid and i == model.header_size:
325 print('--------- End of header') 256 # Mark where the header is considered to end
326 print(entry) 257 print('--------- End of header')
327 elif args.rebuild: 258 print(entry)
328 # Debug rebuild mode: open the file, parse it and turn it back 259 elif args.rebuild:
329 # into LDraw code and write it into stdout. This is used to ensure 260 # Debug rebuild mode: open the file, parse it and turn it back
330 # that LDCheck does not miss any information while parsing files. 261 # into LDraw code and write it into stdout. This is used to ensure
331 for entry in model.body: 262 # that LDCheck does not miss any information while parsing files.
332 print(entry.textual_representation(), end = '\r\n') 263 for entry in model.body:
333 else: 264 print(entry.textual_representation(), end = '\r\n')
334 # Regular mode 265 else:
335 from testsuite import load_tests, check_model, format_report 266 # Regular mode
336 # load the test suite 267 from testsuite import load_tests, check_model
337 # TODO: maybe add some command line argument to filter tests 268 # load the test suite
338 # in case the user wants to run some specific tests only or 269 # TODO: maybe add some command line argument to filter tests
339 # possibly leave some test out 270 # in case the user wants to run some specific tests only or
340 test_suite = load_tests() 271 # possibly leave some test out
341 # use the test suite to check the model 272 test_suite = load_tests()
342 report = check_model(model, test_suite) 273 # use the test suite to check the model
343 # print out the report 274 report = check_model(model, test_suite)
344 print(format_report(report, model, test_suite)) 275 # print out the report
276 print(format_report(
277 report,
278 model,
279 test_suite,
280 use_colors = args.color
281 ))
282 except FileNotFoundError:
283 print(str.format(
284 'no such file: {filename!r}',
285 filename = args.filename
286 ), file = stderr)
287 exit(1)
345 288
346 if __name__ == '__main__': 289 if __name__ == '__main__':
347 try: 290 try:
348 main() 291 main()
349 except RuntimeError as e: 292 except RuntimeError as e:
350 import sys 293 import sys
351 print('error:', str(e), file = sys.stderr) 294 print('error:', str(e), file = sys.stderr)
352 sys.exit(1) 295 sys.exit(1)
353 =======
354 try:
355 with open(args.filename, 'rb') as file:
356 from os.path import basename
357 model = parse.read_ldraw(
358 file,
359 name = basename(args.filename),
360 context = context)
361 if args.dump:
362 print('header: ' + type(model.header).__name__)
363 for key in sorted(dir(model.header)):
364 if not key.startswith('__'):
365 print('\t' + key + ': ' + repr(getattr(model.header, key)))
366 for i, entry in enumerate(model.body):
367 if model.header.valid and i == model.header_size:
368 print('--------- End of header')
369 print(entry)
370 elif args.rebuild:
371 for entry in model.body:
372 print(entry.textual_representation(), end = '\r\n')
373 else:
374 from testsuite import load_tests, check_model
375 test_suite = load_tests()
376 report = check_model(model, test_suite)
377 print(format_report(
378 report,
379 model,
380 test_suite,
381 use_colors = args.color
382 ))
383 except FileNotFoundError:
384 print(str.format(
385 'no such file: {filename!r}',
386 filename = args.filename
387 ), file = stderr)
388 exit(1)
389 >>>>>>> /tmp/ldcheck~other.ou_xbg_k.py

mercurial