diff --git a/changelog.txt b/changelog.txt index fa6fc40..8aba157 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +# 0.11.1 +* livereload fix: live server waits for daemon to terminate before restarting +* livereload fix: Fix bug for ignore_glob not firing +* livereload fix: always track stat mtime as one type (int) + # 0.11.0 * Add html_compose.resource module which includes js_import, css_import, and font_import helpers diff --git a/pyproject.toml b/pyproject.toml index e15db70..d6baee2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "html-compose" -version = "0.11.0" +version = "0.11.1" description = "Composable HTML generation in python" authors = [ { name = "jealouscloud", email = "github@noaha.org" } diff --git a/src/html_compose/__init__.py b/src/html_compose/__init__.py index 0fc6d3e..aeab72f 100644 --- a/src/html_compose/__init__.py +++ b/src/html_compose/__init__.py @@ -192,6 +192,7 @@ ```sh html-compose convert {filename or empty for stdin} html-compose convert --noimport el # produces el.div() style references +html-convert # an alias for html-compose convert ``` `html-convert` provides access to this tool as shorthand. diff --git a/src/html_compose/base_element.py b/src/html_compose/base_element.py index c01c2ad..ba40e5a 100644 --- a/src/html_compose/base_element.py +++ b/src/html_compose/base_element.py @@ -13,7 +13,7 @@ class ElementMeta(ABCMeta): """ - The metaclass for all HTML elements to hack the base class interface + The metaclass for all HTML elements """ # We aggressively hack the type checker here diff --git a/src/html_compose/base_types.py b/src/html_compose/base_types.py index 2060867..b6a138a 100644 --- a/src/html_compose/base_types.py +++ b/src/html_compose/base_types.py @@ -66,6 +66,14 @@ def __html__(self) -> str: return self.render() +# A node resolver is a callable that returns a Node, +# possibly taking the calling element and parent element as arguments. +NodeResolver = ( + Callable[[], "Node"] + | Callable[[ElementBase], "Node"] + | Callable[[ElementBase, ElementBase], "Node"] +) + # The Node type is a union of all possible types that can be rendered Node = ( None # None will not be appended to the output children @@ -76,9 +84,7 @@ def __html__(self) -> str: | ElementBase # Base class for all HTML elements | _HasHtml # Returns HTML that does not need escaping | Iterable["Node"] - | Callable[[], "Node"] - | Callable[[ElementBase], "Node"] - | Callable[[ElementBase, ElementBase], "Node"] + | NodeResolver ) # These types are used for attribute values diff --git a/src/html_compose/document.py b/src/html_compose/document.py index 6b811a9..cc3c8b1 100644 --- a/src/html_compose/document.py +++ b/src/html_compose/document.py @@ -127,7 +127,7 @@ def document_generator( lang: str | None = None, head: el.head | list | None = None, body: Iterable[Node] | el.body | None = None, -): +) -> str: """ Return a full HTML5 document as a string. diff --git a/src/html_compose/elements/__init__.py b/src/html_compose/elements/__init__.py index 7445393..b659107 100644 --- a/src/html_compose/elements/__init__.py +++ b/src/html_compose/elements/__init__.py @@ -102,6 +102,8 @@ def get(value: str) -> BaseAttribute: """ +import os + from .a_element import a as a from .abbr_element import abbr as abbr from .address_element import address as address @@ -216,8 +218,6 @@ def get(value: str) -> BaseAttribute: from .video_element import video as video from .wbr_element import wbr as wbr -import os - # hack: force PDOC to treat elements as submodules if not os.environ.get("PDOC_GENERATING", False): __all__ = [ diff --git a/src/html_compose/live/live_server.py b/src/html_compose/live/live_server.py index b825ee9..b1105d1 100644 --- a/src/html_compose/live/live_server.py +++ b/src/html_compose/live/live_server.py @@ -123,6 +123,7 @@ def live_server( ) daemon_task = ProcessTask(daemon, delay=0, sync=False) + daemon_stop_task = Task(action=lambda: daemon_task.cancel(), sync=True) # Run livereload server server = run_server(host, port) tr = TaskRunner() @@ -186,6 +187,10 @@ def reload(): if reload_tripped: daemon_task.delay = delay + daemon_delay + + # this should make them fire on the same tick, in order + daemon_stop_task.delay = daemon_task.delay + # This constant should mean the server port is up browser_update_task.delay = ( daemon_task.delay + livereload_delay @@ -194,6 +199,7 @@ def reload(): f"Reloading daemon after {daemon_task.delay} seconds..." ) if any([c.server_reload for c in conds_hit]): + tr.add_task(daemon_stop_task) tr.add_task(daemon_task) tr.add_task(browser_update_task) sleep(loop_delay) diff --git a/src/html_compose/live/watcher.py b/src/html_compose/live/watcher.py index 861eb28..b12b129 100644 --- a/src/html_compose/live/watcher.py +++ b/src/html_compose/live/watcher.py @@ -263,7 +263,7 @@ def try_path_hit(self, path: str) -> bool: for pattern in self.ignore_glob: if glob_matcher(pattern, path): - return True + return False for pattern in self.path_glob: if glob_matcher(pattern, path): @@ -415,7 +415,7 @@ def stat_watcher(self): if not stat.S_ISREG(st.st_mode): # Not a regular file, skip it continue - mtime = st.st_mtime + mtime = int(st.st_mtime) except OSError: # Might be deleted, we'll catch it later continue diff --git a/src/html_compose/resource/js_import.py b/src/html_compose/resource/js_import.py index 178f012..192e703 100644 --- a/src/html_compose/resource/js_import.py +++ b/src/html_compose/resource/js_import.py @@ -203,7 +203,7 @@ def __init__( "If hash is set, crossorigin must be set to ''/'anonymous'" ) - def uri(self): + def uri(self) -> str: """ Returns the source URI - with cache busting if enabled which is implemented by getting the mtime of the local file diff --git a/src/html_compose/resource/util_funcs.py b/src/html_compose/resource/util_funcs.py index 6090d88..2086efa 100644 --- a/src/html_compose/resource/util_funcs.py +++ b/src/html_compose/resource/util_funcs.py @@ -20,11 +20,8 @@ def _cachebust_resource_uri(source: str): misc_stat_cache = _State.misc_stat_cache stat_cache = _State.stat_cache - cache_cap = settings.cache_cap - stat_poll_interval = settings.stat_poll_interval base_dir = settings.base_dir - query_string = settings.query_string - base_dir = base_dir + if misc_stat_cache.get(base_dir) is None: try: misc_stat_cache[base_dir] = int(stat(base_dir).st_mtime) @@ -35,10 +32,11 @@ def _cachebust_resource_uri(source: str): "does not exist" ) from exc + source = source.lstrip("/") resource_path = path.join(base_dir, source) now = time() ts = misc_stat_cache.get(path.join(base_dir, source), None) - update_ts = ts is None or (now - ts) > stat_poll_interval + update_ts = ts is None or (now - ts) > settings.stat_poll_interval if update_ts: try: ts = int(stat(resource_path).st_mtime) @@ -49,7 +47,7 @@ def _cachebust_resource_uri(source: str): "does not exist" ) from exc - if len(stat_cache) >= cache_cap: + if len(stat_cache) >= settings.cache_cap: # Clear if it's too big stat_cache.clear() stat_cache[resource_path] = ts @@ -64,7 +62,7 @@ def _cachebust_resource_uri(source: str): errors="surrogateescape", ) # add our cache buster - pairs.append((query_string, str(int(ts)))) + pairs.append((settings.query_string, str(int(ts)))) # re assemble the query string, try our best to preservees exactly new_qs = urllib.parse.urlencode( pairs, diff --git a/src/html_compose/util_funcs.py b/src/html_compose/util_funcs.py index 6d9d56e..e69481b 100644 --- a/src/html_compose/util_funcs.py +++ b/src/html_compose/util_funcs.py @@ -86,7 +86,7 @@ def get_livereload_env() -> dict | None: def generate_livereload_env( - host, port, proxy_host: str | None, proxy_uri: str | None = None + host: str, port: int, proxy_host: str | None, proxy_uri: str | None = None ) -> dict: flags = { "port": port, diff --git a/tests/test_util_funcs.py b/tests/test_util_funcs.py index d8ba89c..0691725 100644 --- a/tests/test_util_funcs.py +++ b/tests/test_util_funcs.py @@ -114,6 +114,7 @@ def test_glob_func(): "path/nextsection/dir_cursive/nextsection/end.txt", True, ), + ("front/css/", "front/css/utilities/debug-outline.css", True), ] for pattern, target, expected in test_cases: assert glob_matcher(pattern, target) == expected, (