| 
									
										
										
										
											2020-04-22 23:29:27 +01:00
										 |  |  | #!/usr/bin/env python3.8 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import argparse | 
					
						
							|  |  |  | import ast | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | import sys | 
					
						
							|  |  |  | import time | 
					
						
							|  |  |  | import traceback | 
					
						
							| 
									
										
										
										
											2020-05-25 22:51:58 +03:00
										 |  |  | import tokenize | 
					
						
							| 
									
										
										
										
											2020-06-20 11:10:31 +03:00
										 |  |  | from glob import glob, escape | 
					
						
							| 
									
										
										
										
											2020-04-22 23:29:27 +01:00
										 |  |  | from pathlib import PurePath | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 07:21:40 +03:00
										 |  |  | from typing import List, Optional, Any, Tuple | 
					
						
							| 
									
										
										
										
											2020-04-22 23:29:27 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | sys.path.insert(0, os.getcwd()) | 
					
						
							| 
									
										
										
										
											2020-05-02 05:23:06 +01:00
										 |  |  | from pegen.ast_dump import ast_dump | 
					
						
							| 
									
										
										
										
											2020-04-22 23:29:27 +01:00
										 |  |  | from pegen.testutil import print_memstats | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | SUCCESS = "\033[92m" | 
					
						
							|  |  |  | FAIL = "\033[91m" | 
					
						
							|  |  |  | ENDC = "\033[0m" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 07:21:40 +03:00
										 |  |  | COMPILE = 2 | 
					
						
							|  |  |  | PARSE = 1 | 
					
						
							|  |  |  | NOTREE = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-22 23:29:27 +01:00
										 |  |  | argparser = argparse.ArgumentParser( | 
					
						
							|  |  |  |     prog="test_parse_directory", | 
					
						
							|  |  |  |     description="Helper program to test directories or files for pegen", | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | argparser.add_argument("-d", "--directory", help="Directory path containing files to test") | 
					
						
							|  |  |  | argparser.add_argument( | 
					
						
							|  |  |  |     "-e", "--exclude", action="append", default=[], help="Glob(s) for matching files to exclude" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | argparser.add_argument( | 
					
						
							|  |  |  |     "-s", "--short", action="store_true", help="Only show errors, in a more Emacs-friendly format" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | argparser.add_argument( | 
					
						
							|  |  |  |     "-v", "--verbose", action="store_true", help="Display detailed errors for failures" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def report_status( | 
					
						
							|  |  |  |     succeeded: bool, | 
					
						
							|  |  |  |     file: str, | 
					
						
							|  |  |  |     verbose: bool, | 
					
						
							|  |  |  |     error: Optional[Exception] = None, | 
					
						
							|  |  |  |     short: bool = False, | 
					
						
							|  |  |  | ) -> None: | 
					
						
							|  |  |  |     if short and succeeded: | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if succeeded is True: | 
					
						
							|  |  |  |         status = "OK" | 
					
						
							|  |  |  |         COLOR = SUCCESS | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         status = "Fail" | 
					
						
							|  |  |  |         COLOR = FAIL | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if short: | 
					
						
							|  |  |  |         lineno = 0 | 
					
						
							|  |  |  |         offset = 0 | 
					
						
							|  |  |  |         if isinstance(error, SyntaxError): | 
					
						
							|  |  |  |             lineno = error.lineno or 1 | 
					
						
							|  |  |  |             offset = error.offset or 1 | 
					
						
							|  |  |  |             message = error.args[0] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             message = f"{error.__class__.__name__}: {error}" | 
					
						
							|  |  |  |         print(f"{file}:{lineno}:{offset}: {message}") | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         print(f"{COLOR}{file:60} {status}{ENDC}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if error and verbose: | 
					
						
							|  |  |  |             print(f"  {str(error.__class__.__name__)}: {error}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-12 01:55:35 +01:00
										 |  |  | def parse_file(source: str, file: str) -> Tuple[Any, float]: | 
					
						
							| 
									
										
										
										
											2020-06-06 07:21:40 +03:00
										 |  |  |     t0 = time.time() | 
					
						
							| 
									
										
										
										
											2020-06-12 01:55:35 +01:00
										 |  |  |     result = ast.parse(source, filename=file) | 
					
						
							| 
									
										
										
										
											2020-06-06 07:21:40 +03:00
										 |  |  |     t1 = time.time() | 
					
						
							|  |  |  |     return result, t1 - t0 | 
					
						
							| 
									
										
										
										
											2020-04-22 23:29:27 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 07:21:40 +03:00
										 |  |  | def generate_time_stats(files, total_seconds) -> None: | 
					
						
							| 
									
										
										
										
											2020-04-22 23:29:27 +01:00
										 |  |  |     total_files = len(files) | 
					
						
							|  |  |  |     total_bytes = 0 | 
					
						
							|  |  |  |     total_lines = 0 | 
					
						
							|  |  |  |     for file in files: | 
					
						
							|  |  |  |         # Count lines and bytes separately | 
					
						
							|  |  |  |         with open(file, "rb") as f: | 
					
						
							|  |  |  |             total_lines += sum(1 for _ in f) | 
					
						
							|  |  |  |             total_bytes += f.tell() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     print( | 
					
						
							|  |  |  |         f"Checked {total_files:,} files, {total_lines:,} lines,", | 
					
						
							|  |  |  |         f"{total_bytes:,} bytes in {total_seconds:,.3f} seconds.", | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     if total_seconds > 0: | 
					
						
							|  |  |  |         print( | 
					
						
							|  |  |  |             f"That's {total_lines / total_seconds :,.0f} lines/sec,", | 
					
						
							|  |  |  |             f"or {total_bytes / total_seconds :,.0f} bytes/sec.", | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-06 07:21:40 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-12 01:55:35 +01:00
										 |  |  | def parse_directory(directory: str, verbose: bool, excluded_files: List[str], short: bool) -> int: | 
					
						
							| 
									
										
										
										
											2020-06-06 07:21:40 +03:00
										 |  |  |     # For a given directory, traverse files and attempt to parse each one | 
					
						
							|  |  |  |     # - Output success/failure for each file | 
					
						
							|  |  |  |     errors = 0 | 
					
						
							|  |  |  |     files = [] | 
					
						
							|  |  |  |     total_seconds = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-20 11:10:31 +03:00
										 |  |  |     for file in sorted(glob(os.path.join(escape(directory), f"**/*.py"), recursive=True)): | 
					
						
							| 
									
										
										
										
											2020-06-06 07:21:40 +03:00
										 |  |  |         # Only attempt to parse Python files and files that are not excluded | 
					
						
							|  |  |  |         if any(PurePath(file).match(pattern) for pattern in excluded_files): | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         with tokenize.open(file) as f: | 
					
						
							|  |  |  |             source = f.read() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         try: | 
					
						
							| 
									
										
										
										
											2020-06-12 01:55:35 +01:00
										 |  |  |             result, dt = parse_file(source, file) | 
					
						
							| 
									
										
										
										
											2020-06-06 07:21:40 +03:00
										 |  |  |             total_seconds += dt | 
					
						
							|  |  |  |             report_status(succeeded=True, file=file, verbose=verbose, short=short) | 
					
						
							|  |  |  |         except SyntaxError as error: | 
					
						
							| 
									
										
										
										
											2020-06-12 01:55:35 +01:00
										 |  |  |             report_status(succeeded=False, file=file, verbose=verbose, error=error, short=short) | 
					
						
							|  |  |  |             errors += 1 | 
					
						
							| 
									
										
										
										
											2020-06-06 07:21:40 +03:00
										 |  |  |         files.append(file) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     generate_time_stats(files, total_seconds) | 
					
						
							| 
									
										
										
										
											2020-04-22 23:29:27 +01:00
										 |  |  |     if short: | 
					
						
							|  |  |  |         print_memstats() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if errors: | 
					
						
							|  |  |  |         print(f"Encountered {errors} failures.", file=sys.stderr) | 
					
						
							|  |  |  |         return 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def main() -> None: | 
					
						
							|  |  |  |     args = argparser.parse_args() | 
					
						
							|  |  |  |     directory = args.directory | 
					
						
							|  |  |  |     verbose = args.verbose | 
					
						
							|  |  |  |     excluded_files = args.exclude | 
					
						
							|  |  |  |     short = args.short | 
					
						
							| 
									
										
										
										
											2020-06-12 01:55:35 +01:00
										 |  |  |     sys.exit(parse_directory(directory, verbose, excluded_files, short)) | 
					
						
							| 
									
										
										
										
											2020-04-22 23:29:27 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							|  |  |  |     main() |