6
6
import shutil
7
7
8
8
from collections import deque , Iterable
9
- from os import DirEntry , scandir
9
+ from os import scandir
10
10
from pathlib import Path
11
- from typing import Union
12
11
13
12
import jinja2
14
13
15
14
from bs4 import BeautifulSoup
16
- from watchgod import DefaultWatcher
15
+ from watchfiles import Change , DefaultFilter
17
16
18
17
19
18
log = logging .getLogger (__name__ )
20
19
20
+ _FILE_PATTERN = re .compile (r"[^.].+\.(html|htm|css|js)" , re .IGNORECASE )
21
21
22
- class Context :
23
- """Collects shared configuration and simple methods for determining which files/directories to watch. There should only be one instance of this during the program's lifeycle."""
22
+ # _NOT_DIR_PATTERN = re.compile(r"(\.|venv_|__pycache)")
24
23
25
- _FILE_PATTERN = re .compile (r"[^.].+\.(html|htm|css|js)" , re .IGNORECASE )
26
24
27
- _NOT_DIR_PATTERN = re .compile (r"(\.|venv_|__pycache)" )
25
+ class Context :
26
+ """Collects shared configuration and simple methods for determining which files/directories to watch. There should only be one instance of this during the program's lifeycle."""
28
27
29
28
def __init__ (self , input_dir : Path = Path ("." ), output_dir : Path = Path ("out" ), template_dir : str = "templates" , ignore_list : set [Path ] = set (), dev_mode : bool = False ) -> None :
30
29
"""Initializer, creates a new `Context`. For best results, all `Path` type arguments should be absolute (this is automatically done in the initializer, but if you want to change the properties after initializing, make sure you do this).
@@ -88,27 +87,16 @@ def is_config_json(self, f: Path) -> bool:
88
87
"""
89
88
return f == self .config_json
90
89
91
- def should_watch_file (self , entry : DirEntry ) -> bool :
92
- """Determines whether a file should be watched. For use with the output of `os.scandir`
90
+ def should_watch_file (self , p : str ) -> bool :
91
+ """Determines whether a file should be watched.
93
92
94
93
Args:
95
- entry (DirEntry ): The file to check.
94
+ entry (str ): The path to the file to check. Must be a full path .
96
95
97
96
Returns:
98
97
bool: `True` if the file should be watched.
99
98
"""
100
- return Context ._FILE_PATTERN .match (entry .name ) or self .is_config_json (Path (entry .path ))
101
-
102
- def should_watch_dir (self , entry : DirEntry ) -> bool :
103
- """Determines whether a directory should be watched. For use with the output of `os.scandir`
104
-
105
- Args:
106
- entry (DirEntry): The directory to check.
107
-
108
- Returns:
109
- bool: `True` if the directory should be watched.
110
- """
111
- return Path (entry .path ) not in self .ignore_list and not Context ._NOT_DIR_PATTERN .match (entry .name )
99
+ return _FILE_PATTERN .match (p ) or self .is_config_json (Path (p ))
112
100
113
101
114
102
class WebsiteManager :
@@ -121,6 +109,7 @@ def __init__(self, context: Context) -> None:
121
109
context (Context): The `Context` to use.
122
110
"""
123
111
self .context = context
112
+ self .jinja_filter = JinjaFilter (context )
124
113
125
114
def find_acceptable_files (self ) -> set [Path ]:
126
115
"""Recursively searches the input directory, according to the input context, for files that should be processed. Useful for cases when the whole website needs to be rebuilt.
@@ -134,11 +123,13 @@ def find_acceptable_files(self) -> set[Path]:
134
123
while l :
135
124
with scandir (l .popleft ()) as it :
136
125
for entry in it :
126
+ entry_as_path = Path (entry )
127
+
137
128
if entry .is_file ():
138
- if self .context .should_watch_file (entry ):
139
- files .add (Path ( entry . path ) )
129
+ if self .context .should_watch_file (entry . path ):
130
+ files .add (entry_as_path )
140
131
else : # is_dir()
141
- if self .context .should_watch_dir ( entry ) and Path ( entry . path ) != self .context .template_dir :
132
+ if entry_as_path not in self .context .ignore_list and entry_as_path != self .context .template_dir and self . jinja_filter ( Change . added , entry . path ) :
142
133
l .append (entry .path )
143
134
144
135
return files
@@ -178,7 +169,7 @@ def build_files(self, files: Iterable[Path] = (), auto_find: bool = False) -> No
178
169
body_tag .append (script_tag )
179
170
180
171
# add livereload script
181
- body_tag .append (soup .new_tag ("script" , src = "https://cdnjs.cloudflare.com/ajax/libs/livereload-js/3.3.2 /livereload.min.js" , integrity = "sha512-XO7rFek26Xn8H4HecfAv2CwBbYsJE+RovkwE0nc0kYD+1yJr2OQOOEKSjOsmzE8rTrjP6AoXKFMqReMHj0Pjkw ==" , crossorigin = "anonymous" ))
172
+ body_tag .append (soup .new_tag ("script" , src = "https://cdnjs.cloudflare.com/ajax/libs/livereload-js/3.4.1 /livereload.min.js" , integrity = "sha512-rclIrxzYHDmi28xeUES7WqX493chZ4LFEdbjMAUYiosJlKqham0ZujKU539fTFnZywE0c76XIRl9pLJ05OJPKA ==" , crossorigin = "anonymous" ))
182
173
output = str (soup )
183
174
184
175
output_path .write_text (output )
@@ -189,41 +180,29 @@ def build_files(self, files: Iterable[Path] = (), auto_find: bool = False) -> No
189
180
log .error ("Unable to build HTML!" , exc_info = True )
190
181
191
182
192
- class JinjaWatcher ( DefaultWatcher ):
193
- """An `AllWatcher ` subclass for use with `watchgod` """
183
+ class JinjaFilter ( DefaultFilter ):
184
+ """A `DefaultFilter ` subclass which only finds jinja2html-related files, for use `watchfiles`. """
194
185
195
- def __init__ (self , root_path : Union [ Path , str ] = None , context : Context = Context () ) -> None :
196
- """Initializer, creates a new `JinjaWatcher `.
186
+ def __init__ (self , context : Context ) -> None :
187
+ """Initializer, creates a new `JinjaFilter `.
197
188
198
189
Args:
199
- root_path (Union[Path, str], optional): The root path (input) directory to watch. Defaults to None.
200
- context (Context, optional): The `Context` to use. Defaults to Context().
190
+ context (Context): The `Context` to use.
201
191
"""
202
192
self .context = context
193
+ super ().__init__ (ignore_paths = tuple (context .ignore_list ))
203
194
204
- super ().__init__ (root_path or context .input_dir )
205
-
206
- def should_watch_file (self , entry : DirEntry ) -> bool :
207
- """Determines whether a file should be watched.
208
-
209
- Args:
210
- entry (DirEntry): The file to check.
211
-
212
- Returns:
213
- bool: `True` if the file should be watched.
214
- """
215
- return self .context .should_watch_file (entry )
216
-
217
- def should_watch_dir (self , entry : DirEntry ) -> bool :
218
- """Determines whether a directory should be watched.
195
+ def __call__ (self , change : Change , path : str ) -> bool :
196
+ """Gets called by `watchfiles` when it checks if changes to a path should be reported.
219
197
220
198
Args:
221
- entry (DirEntry): The directory to check.
199
+ change (Change): The kind of `Change` detected
200
+ path (str): The path that was changed.
222
201
223
202
Returns:
224
- bool: `True` if the directory should be watched .
203
+ bool: `True` if the change should be reported .
225
204
"""
226
- return self .context .should_watch_dir ( entry )
205
+ return self .context .should_watch_file ( path ) and super (). __call__ ( change , path )
227
206
228
207
229
208
def _is_ext (f : Path , ext : tuple [str ]) -> bool :
0 commit comments