diff --git a/coconut/command/command.py b/coconut/command/command.py index 9f0a51f0..4b13bf70 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -506,7 +506,7 @@ def process_source_dest(self, source, dest, args): ] return main_compilation_tasks, extra_compilation_tasks - def compile_path(self, source, dest=True, package=True, handling_exceptions_kwargs={}, **kwargs): + def compile_path(self, source, dest=True, package=True, **kwargs): """Compile a path and return paths to compiled files.""" if not isinstance(dest, bool): dest = fixpath(dest) @@ -514,11 +514,11 @@ def compile_path(self, source, dest=True, package=True, handling_exceptions_kwar destpath = self.compile_file(source, dest, package, **kwargs) return [destpath] if destpath is not None else [] elif os.path.isdir(source): - return self.compile_folder(source, dest, package, handling_exceptions_kwargs=handling_exceptions_kwargs, **kwargs) + return self.compile_folder(source, dest, package, **kwargs) else: raise CoconutException("could not find source path", source) - def compile_folder(self, directory, write=True, package=True, handling_exceptions_kwargs={}, **kwargs): + def compile_folder(self, directory, write=True, package=True, **kwargs): """Compile a directory and return paths to compiled files.""" if not isinstance(write, bool) and os.path.isfile(write): raise CoconutException("destination path cannot point to a file when compiling a directory") @@ -530,7 +530,7 @@ def compile_folder(self, directory, write=True, package=True, handling_exception writedir = os.path.join(write, os.path.relpath(dirpath, directory)) for filename in filenames: if os.path.splitext(filename)[1] in code_exts: - with self.handling_exceptions(**handling_exceptions_kwargs): + with self.handling_exceptions(**kwargs.get("handling_exceptions_kwargs", {})): destpath = self.compile_file(os.path.join(dirpath, filename), writedir, package, **kwargs) if destpath is not None: filepaths.append(destpath) @@ -576,7 +576,7 @@ def compile_file(self, filepath, write=True, package=False, force=False, **kwarg self.compile(filepath, destpath, package, force=force, **kwargs) return destpath - def compile(self, codepath, destpath=None, package=False, run=False, force=False, show_unchanged=True, callback=None): + def compile(self, codepath, destpath=None, package=False, run=False, force=False, show_unchanged=True, handling_exceptions_kwargs={}, callback=None): """Compile a source Coconut file to a destination Python file.""" with univ_open(codepath, "r") as opened: code = readfile(opened) @@ -599,43 +599,37 @@ def compile(self, codepath, destpath=None, package=False, run=False, force=False logger.print(foundhash) if run: self.execute_file(destpath, argv_source_path=codepath) + if callback is not None: + callback(destpath) else: logger.show_tabulated("Compiling", showpath(codepath), "...") def inner_callback(compiled): - try: + if destpath is None: + logger.show_tabulated("Compiled", showpath(codepath), "without writing to file.") + else: + with univ_open(destpath, "w") as opened: + writefile(opened, compiled) + logger.show_tabulated("Compiled to", showpath(destpath), ".") + if self.display: + logger.print(compiled) + if run: if destpath is None: - logger.show_tabulated("Compiled", showpath(codepath), "without writing to file.") + self.execute(compiled, path=codepath, allow_show=False) else: - with univ_open(destpath, "w") as opened: - writefile(opened, compiled) - logger.show_tabulated("Compiled to", showpath(destpath), ".") - if self.display: - logger.print(compiled) - if run: - if destpath is None: - self.execute(compiled, path=codepath, allow_show=False) - else: - self.execute_file(destpath, argv_source_path=codepath) - except BaseException as err: - if callback is not None: - callback(False, err) - raise - else: - if callback is not None: - callback(True, destpath) + self.execute_file(destpath, argv_source_path=codepath) + if callback is not None: + callback(destpath) parse_kwargs = dict( codepath=codepath, use_cache=self.use_cache, ) - if callback is not None: - parse_kwargs["error_callback"] = lambda err: callback(False, err) if package is True: - self.submit_comp_job(codepath, inner_callback, "parse_package", code, package_level=package_level, **parse_kwargs) + self.submit_comp_job(codepath, inner_callback, handling_exceptions_kwargs, "parse_package", code, package_level=package_level, **parse_kwargs) elif package is False: - self.submit_comp_job(codepath, inner_callback, "parse_file", code, **parse_kwargs) + self.submit_comp_job(codepath, inner_callback, handling_exceptions_kwargs, "parse_file", code, **parse_kwargs) else: raise CoconutInternalException("invalid value for package", package) @@ -677,11 +671,10 @@ def create_package(self, dirpath, retries_left=create_package_retries): time.sleep(random.random() / 10) self.create_package(dirpath, retries_left - 1) - def submit_comp_job(self, path, callback, method, *args, **kwargs): + def submit_comp_job(self, path, callback, handling_exceptions_kwargs, method, *args, **kwargs): """Submits a job on self.comp to be run in parallel.""" - error_callback = kwargs.pop("error_callback", None) if self.executor is None: - with self.handling_exceptions(): + with self.handling_exceptions(**handling_exceptions_kwargs): callback(getattr(self.comp, method)(*args, **kwargs)) else: path = showpath(path) @@ -691,15 +684,9 @@ def submit_comp_job(self, path, callback, method, *args, **kwargs): def callback_wrapper(completed_future): """Ensures that all errors are always caught, since errors raised in a callback won't be propagated.""" with logger.in_path(path): # handle errors in the path context - with self.handling_exceptions(): - try: - result = completed_future.result() - except BaseException as err: - if error_callback is not None: - error_callback(err) - raise - else: - callback(result) + with self.handling_exceptions(**handling_exceptions_kwargs): + result = completed_future.result() + callback(result) future.add_done_callback(callback_wrapper) def register_exit_code(self, code=1, errmsg=None, err=None): @@ -722,7 +709,7 @@ def register_exit_code(self, code=1, errmsg=None, err=None): self.exit_code = code or self.exit_code @contextmanager - def handling_exceptions(self, exit_on_error=None, on_keyboard_interrupt=None): + def handling_exceptions(self, exit_on_error=None, error_callback=None): """Perform proper exception handling.""" if exit_on_error is None: exit_on_error = self.fail_fast @@ -732,21 +719,22 @@ def handling_exceptions(self, exit_on_error=None, on_keyboard_interrupt=None): yield else: yield - except SystemExit as err: - self.register_exit_code(err.code) # make sure we don't catch GeneratorExit below except GeneratorExit: raise + except SystemExit as err: + self.register_exit_code(err.code) + if error_callback is not None: + error_callback(err) except BaseException as err: if isinstance(err, CoconutException): logger.print_exc() - elif isinstance(err, KeyboardInterrupt): - if on_keyboard_interrupt is not None: - on_keyboard_interrupt() - else: + elif not isinstance(err, KeyboardInterrupt): logger.print_exc() logger.printerr(report_this_text) self.register_exit_code(err=err) + if error_callback is not None: + error_callback(err) if exit_on_error: self.exit_on_error() @@ -1152,33 +1140,36 @@ def watch(self, all_compile_path_kwargs): interrupted = [False] # in list to allow modification - def interrupt(): - interrupted[0] = True - def recompile(path, callback, **kwargs): - def inner_callback(ok, path): - if ok: - self.run_mypy(path) + def error_callback(err): + if isinstance(err, KeyboardInterrupt): + interrupted[0] = True callback() path = fixpath(path) src = kwargs.pop("source") dest = kwargs.pop("dest") if os.path.isfile(path) and os.path.splitext(path)[1] in code_exts: - with self.handling_exceptions(on_keyboard_interrupt=interrupt): + with self.handling_exceptions(error_callback=error_callback): if dest is True or dest is None: writedir = dest else: # correct the compilation path based on the relative position of path to src dirpath = os.path.dirname(path) writedir = os.path.join(dest, os.path.relpath(dirpath, src)) + + def inner_callback(path): + self.run_mypy([path]) + callback() self.compile_path( path, writedir, show_unchanged=False, - handling_exceptions_kwargs=dict(on_keyboard_interrupt=interrupt), + handling_exceptions_kwargs=dict(error_callback=error_callback), callback=inner_callback, **kwargs # no comma for py2 ) + else: + callback() observer = Observer() watchers = [] @@ -1193,7 +1184,7 @@ def inner_callback(ok, path): while not interrupted[0]: time.sleep(watch_interval) except KeyboardInterrupt: - interrupt() + interrupted[0] = True finally: if interrupted[0]: logger.show_sig("Got KeyboardInterrupt; stopping watcher.") diff --git a/coconut/command/watch.py b/coconut/command/watch.py index 281900a6..a0852a0e 100644 --- a/coconut/command/watch.py +++ b/coconut/command/watch.py @@ -21,6 +21,7 @@ import sys +from coconut.terminal import logger from coconut.exceptions import CoconutException try: @@ -51,6 +52,9 @@ def __init__(self, recompile, *args, **kwargs): def on_modified(self, event): """Handle a file modified event.""" path = event.src_path - if path not in self.saw: + if path in self.saw: + logger.log("Skipping watch event for: " + repr(path) + "\n\t(currently compiling: " + repr(self.saw) + ")") + else: + logger.log("Handling watch event for: " + repr(path) + "\n\t(currently compiling: " + repr(self.saw) + ")") self.saw.add(path) - self.recompile(path, callback=lambda: self.saw.remove(path), *self.args, **self.kwargs) + self.recompile(path, callback=lambda: self.saw.discard(path), *self.args, **self.kwargs)