mirror of
https://github.com/getpelican/pelican.git
synced 2025-10-15 20:28:56 +02:00
fix: Fix auto-reload with globs in IGNORE_FILES (#3441)
The default `IGNORE_FILES` value of `'.*'` was being compiled to a regular expression and then passed into `watchfiles.DefaultFilter` to filter out files when auto-reload is enabled. This commit compares the patterns from `IGNORE_FILES` directly with the filename to match the usage of `fnmatch` elsewhere in the codebase. The new filtering class will continue working as expected for custom `IGNORE_FILES` settings.
This commit is contained in:
parent
5be013d9d2
commit
543424aa41
4 changed files with 96 additions and 9 deletions
|
|
@ -150,13 +150,17 @@ Basic settings
|
||||||
|
|
||||||
READERS = {'foo': FooReader}
|
READERS = {'foo': FooReader}
|
||||||
|
|
||||||
.. data:: IGNORE_FILES = ['.*']
|
.. data:: IGNORE_FILES = ['**/.*']
|
||||||
|
|
||||||
A list of glob patterns. Files and directories matching any of these patterns
|
A list of Unix glob patterns. Files and directories matching any of these patterns
|
||||||
will be ignored by the processor. For example, the default ``['.*']`` will
|
or any of the commonly hidden files and directories set by ``watchfiles.DefaultFilter``
|
||||||
|
will be ignored by the processor. For example, the default ``['**/.*']`` will
|
||||||
ignore "hidden" files and directories, and ``['__pycache__']`` would ignore
|
ignore "hidden" files and directories, and ``['__pycache__']`` would ignore
|
||||||
Python 3's bytecode caches.
|
Python 3's bytecode caches.
|
||||||
|
|
||||||
|
For a full list of the commonly hidden files set by ``watchfiles.DefaultFilter``,
|
||||||
|
please refer to the `watchfiles documentation`_.
|
||||||
|
|
||||||
.. data:: MARKDOWN = {...}
|
.. data:: MARKDOWN = {...}
|
||||||
|
|
||||||
Extra configuration settings for the Markdown processor. Refer to the Python
|
Extra configuration settings for the Markdown processor. Refer to the Python
|
||||||
|
|
@ -1423,3 +1427,4 @@ Example settings
|
||||||
|
|
||||||
.. _Jinja Environment documentation: https://jinja.palletsprojects.com/en/latest/api/#jinja2.Environment
|
.. _Jinja Environment documentation: https://jinja.palletsprojects.com/en/latest/api/#jinja2.Environment
|
||||||
.. _Docutils Configuration: http://docutils.sourceforge.net/docs/user/config.html
|
.. _Docutils Configuration: http://docutils.sourceforge.net/docs/user/config.html
|
||||||
|
.. _`watchfiles documentation`: https://watchfiles.helpmanual.io/api/filters/#watchfiles.DefaultFilter.ignore_dirs
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ DEFAULT_CONFIG = {
|
||||||
"PYGMENTS_RST_OPTIONS": {},
|
"PYGMENTS_RST_OPTIONS": {},
|
||||||
"TEMPLATE_PAGES": {},
|
"TEMPLATE_PAGES": {},
|
||||||
"TEMPLATE_EXTENSIONS": [".html"],
|
"TEMPLATE_EXTENSIONS": [".html"],
|
||||||
"IGNORE_FILES": [".*"],
|
"IGNORE_FILES": ["**/.*"],
|
||||||
"SLUG_REGEX_SUBSTITUTIONS": [
|
"SLUG_REGEX_SUBSTITUTIONS": [
|
||||||
(r"[^\w\s-]", ""), # remove non-alphabetical/whitespace/'-' chars
|
(r"[^\w\s-]", ""), # remove non-alphabetical/whitespace/'-' chars
|
||||||
(r"(?u)\A\s*", ""), # strip leading whitespace
|
(r"(?u)\A\s*", ""), # strip leading whitespace
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ from datetime import timezone
|
||||||
from sys import platform
|
from sys import platform
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
|
|
||||||
|
import watchfiles
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
|
|
@ -13,7 +15,7 @@ except ModuleNotFoundError:
|
||||||
|
|
||||||
from pelican import utils
|
from pelican import utils
|
||||||
from pelican.generators import TemplatePagesGenerator
|
from pelican.generators import TemplatePagesGenerator
|
||||||
from pelican.settings import read_settings
|
from pelican.settings import DEFAULT_CONFIG, read_settings
|
||||||
from pelican.tests.support import (
|
from pelican.tests.support import (
|
||||||
LoggedTestCase,
|
LoggedTestCase,
|
||||||
get_article,
|
get_article,
|
||||||
|
|
@ -990,3 +992,68 @@ class TestStringUtils(unittest.TestCase):
|
||||||
self.assertEqual("", utils.file_suffix(""))
|
self.assertEqual("", utils.file_suffix(""))
|
||||||
self.assertEqual("", utils.file_suffix("foo"))
|
self.assertEqual("", utils.file_suffix("foo"))
|
||||||
self.assertEqual("md", utils.file_suffix("foo.md"))
|
self.assertEqual("md", utils.file_suffix("foo.md"))
|
||||||
|
|
||||||
|
|
||||||
|
class TestFileChangeFilter(unittest.TestCase):
|
||||||
|
ignore_file_patterns = DEFAULT_CONFIG["IGNORE_FILES"]
|
||||||
|
|
||||||
|
def test_regular_files_not_filtered(self):
|
||||||
|
filter = utils.FileChangeFilter(ignore_file_patterns=self.ignore_file_patterns)
|
||||||
|
basename = "article.rst"
|
||||||
|
full_path = os.path.join(os.path.dirname(__file__), "content", basename)
|
||||||
|
|
||||||
|
for change in watchfiles.Change:
|
||||||
|
self.assertTrue(filter(change=change, path=basename))
|
||||||
|
self.assertTrue(filter(change=change, path=full_path))
|
||||||
|
|
||||||
|
def test_dotfiles_filtered(self):
|
||||||
|
filter = utils.FileChangeFilter(ignore_file_patterns=self.ignore_file_patterns)
|
||||||
|
basename = ".config"
|
||||||
|
full_path = os.path.join(os.path.dirname(__file__), "content", basename)
|
||||||
|
|
||||||
|
# Testing with just the hidden file name and the full file path to the hidden file
|
||||||
|
for change in watchfiles.Change:
|
||||||
|
self.assertFalse(filter(change=change, path=basename))
|
||||||
|
self.assertFalse(filter(change=change, path=full_path))
|
||||||
|
|
||||||
|
def test_default_filters(self):
|
||||||
|
# Testing a subset of the default filters
|
||||||
|
# For reference: https://watchfiles.helpmanual.io/api/filters/#watchfiles.DefaultFilter.ignore_dirs
|
||||||
|
filter = utils.FileChangeFilter(ignore_file_patterns=[])
|
||||||
|
test_basenames = [
|
||||||
|
"__pycache__",
|
||||||
|
".git",
|
||||||
|
".hg",
|
||||||
|
".svn",
|
||||||
|
".tox",
|
||||||
|
".venv",
|
||||||
|
".idea",
|
||||||
|
"node_modules",
|
||||||
|
".mypy_cache",
|
||||||
|
".pytest_cache",
|
||||||
|
".hypothesis",
|
||||||
|
".DS_Store",
|
||||||
|
"flycheck_file",
|
||||||
|
"test_file~",
|
||||||
|
]
|
||||||
|
|
||||||
|
for basename in test_basenames:
|
||||||
|
full_path = os.path.join(os.path.dirname(__file__), basename)
|
||||||
|
for change in watchfiles.Change:
|
||||||
|
self.assertFalse(filter(change=change, path=basename))
|
||||||
|
self.assertFalse(filter(change=change, path=full_path))
|
||||||
|
|
||||||
|
def test_custom_ignore_pattern(self):
|
||||||
|
filter = utils.FileChangeFilter(ignore_file_patterns=["*.rst"])
|
||||||
|
basename = "article.rst"
|
||||||
|
full_path = os.path.join(os.path.dirname(__file__), basename)
|
||||||
|
for change in watchfiles.Change:
|
||||||
|
self.assertFalse(filter(change=change, path=basename))
|
||||||
|
self.assertFalse(filter(change=change, path=full_path))
|
||||||
|
|
||||||
|
# If the user changes `IGNORE_FILES` to only contain ['*.rst'], then dotfiles would not be filtered anymore
|
||||||
|
basename = ".config"
|
||||||
|
full_path = os.path.join(os.path.dirname(__file__), basename)
|
||||||
|
for change in watchfiles.Change:
|
||||||
|
self.assertTrue(filter(change=change, path=basename))
|
||||||
|
self.assertTrue(filter(change=change, path=full_path))
|
||||||
|
|
|
||||||
|
|
@ -811,15 +811,30 @@ def order_content(
|
||||||
return content_list
|
return content_list
|
||||||
|
|
||||||
|
|
||||||
|
class FileChangeFilter(watchfiles.DefaultFilter):
|
||||||
|
def __init__(self, ignore_file_patterns: Sequence[str], *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.ignore_file_patterns = ignore_file_patterns
|
||||||
|
|
||||||
|
def __call__(self, change: watchfiles.Change, path: str) -> bool:
|
||||||
|
"""Returns `True` if a file should be watched for changes. The `IGNORE_FILES`
|
||||||
|
setting is a list of Unix glob patterns. This call will filter out files and
|
||||||
|
directories specified by `IGNORE_FILES` Pelican setting and by the default
|
||||||
|
filters of `watchfiles.DefaultFilter`, seen here:
|
||||||
|
https://watchfiles.helpmanual.io/api/filters/#watchfiles.DefaultFilter.ignore_dirs
|
||||||
|
"""
|
||||||
|
return super().__call__(change, path) and not any(
|
||||||
|
fnmatch.fnmatch(os.path.abspath(path), p) for p in self.ignore_file_patterns
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def wait_for_changes(
|
def wait_for_changes(
|
||||||
settings_file: str,
|
settings_file: str,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
) -> set[tuple[Change, str]]:
|
) -> set[tuple[Change, str]]:
|
||||||
content_path = settings.get("PATH", "")
|
content_path = settings.get("PATH", "")
|
||||||
theme_path = settings.get("THEME", "")
|
theme_path = settings.get("THEME", "")
|
||||||
ignore_files = {
|
ignore_file_patterns = set(settings.get("IGNORE_FILES", []))
|
||||||
fnmatch.translate(pattern) for pattern in settings.get("IGNORE_FILES", [])
|
|
||||||
}
|
|
||||||
|
|
||||||
candidate_paths = [
|
candidate_paths = [
|
||||||
settings_file,
|
settings_file,
|
||||||
|
|
@ -844,7 +859,7 @@ def wait_for_changes(
|
||||||
return next(
|
return next(
|
||||||
watchfiles.watch(
|
watchfiles.watch(
|
||||||
*watching_paths,
|
*watching_paths,
|
||||||
watch_filter=watchfiles.DefaultFilter(ignore_entity_patterns=ignore_files), # type: ignore
|
watch_filter=FileChangeFilter(ignore_file_patterns=ignore_file_patterns),
|
||||||
rust_timeout=0,
|
rust_timeout=0,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue