Skip to content

Commit

Permalink
Further improve watching
Browse files Browse the repository at this point in the history
  • Loading branch information
evhub committed May 24, 2024
1 parent 570c918 commit 2b4edd0
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 58 deletions.
103 changes: 47 additions & 56 deletions coconut/command/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,19 +506,19 @@ 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)
if os.path.isfile(source):
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")
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)

Expand Down Expand Up @@ -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)
Expand All @@ -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):
Expand All @@ -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
Expand All @@ -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()

Expand Down Expand Up @@ -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 = []
Expand All @@ -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.")
Expand Down
8 changes: 6 additions & 2 deletions coconut/command/watch.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import sys

from coconut.terminal import logger
from coconut.exceptions import CoconutException

try:
Expand Down Expand Up @@ -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)

0 comments on commit 2b4edd0

Please sign in to comment.