diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 02b659d..27289e3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,15 +1,15 @@ repos: + - repo: https://github.com/psf/black + rev: 23.1.0 + hooks: + - id: black + - repo: https://github.com/asottile/pyupgrade rev: v3.15.0 hooks: - id: pyupgrade args: [--py38-plus] - - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.10.1 - hooks: - - id: black - - repo: https://github.com/PyCQA/isort rev: 5.12.0 hooks: diff --git a/noxfile.py b/noxfile.py index b362b70..7c9027a 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,4 +1,10 @@ -"""Development automation.""" +"""Development automation. Use this to build the documentation and run tests. + +To install IPython for interactive debugging: + + nox -s -- -i + +""" import nox @@ -18,6 +24,10 @@ def _install_this_editable(session, *, extras=None): silent=True, ) + if "-i" in session.posargs: + session.posargs.pop(session.posargs.index("-i")) + session.install("ipython") + @nox.session(reuse_venv=True) def lint(session): @@ -44,6 +54,11 @@ def docs(session): @nox.session(name="docs-live", reuse_venv=True) def docs_live(session): _install_this_editable(session, extras=["docs"]) - session.run( - "sphinx-autobuild", "-b", "html", "docs/", "build/docs", *session.posargs - ) + cmd = [ + "sphinx-autobuild", + "-b=html", + "--port=0", + "docs/", + "build/docs", + ] + session.run(*cmd, *session.posargs) diff --git a/src/sphinx_autobuild/build.py b/src/sphinx_autobuild/build.py index 25a952b..5f2c4e6 100644 --- a/src/sphinx_autobuild/build.py +++ b/src/sphinx_autobuild/build.py @@ -6,6 +6,8 @@ from colorama import Fore, Style +# Hard-coded options that we know how to pass-through to Sphinx +# ref: https://www.sphinx-doc.org/en/master/man/sphinx-build.html#cmdoption-sphinx-build-M # noqa SPHINX_BUILD_OPTIONS = ( ("b", "builder"), ("a", None), @@ -23,7 +25,7 @@ ("q", None), ("Q", None), ("w", "file"), - ("-keep-going", None), + ("keep_going", None), ("W", None), ("T", None), ("P", None), diff --git a/src/sphinx_autobuild/ignore.py b/src/sphinx_autobuild/ignore.py index 7c65939..e82eddb 100644 --- a/src/sphinx_autobuild/ignore.py +++ b/src/sphinx_autobuild/ignore.py @@ -1,7 +1,8 @@ """Logic for ignoring paths.""" import fnmatch -import os import re +from glob import glob +from os.path import abspath, sep def get_ignore(regular, regex_based): @@ -11,11 +12,28 @@ def get_ignore(regular, regex_based): def ignore(path): """Determine if path should be ignored.""" - # Any regular pattern matches. + # Return the full path so we make sure we handle relative paths OK + path_expanded = abspath(path) + + # Any regular pattern and glob matches for pattern in regular_patterns: + # Expand the pattern into a list of files that match a glob + matched_files = [abspath(ii) for ii in glob(pattern, recursive=True)] + + # If this file matches any of the glob matches, we ignore it + if path_expanded in matched_files: + return True + + # If the parent of this path matches any of the glob matches, ignore it + if any(path_expanded.startswith(imatch) for imatch in matched_files): + return True + + # These two checks are for preserving old behavior. + # They might not be necessary but leaving here just in case. + # Neither depends on the files actually being on disk. if fnmatch.fnmatch(path, pattern): return True - if path.startswith(pattern + os.sep): + if path.strip(sep).startswith(pattern.strip(sep)): return True # Any regular expression matches. diff --git a/tests/test_ignore.py b/tests/test_ignore.py index fffcf90..5ae0043 100644 --- a/tests/test_ignore.py +++ b/tests/test_ignore.py @@ -1,3 +1,5 @@ +from glob import glob + from sphinx_autobuild.ignore import get_ignore @@ -72,3 +74,25 @@ def test_multiple_both(): assert ignored("foo/random.txt") assert ignored("foo/module.pyc") assert ignored("bar/__pycache__/file.pyc") + + +def test_glob_expression(): + ignored = get_ignore( + [ + # Glob for folder + "**/do_ignore", + # Glob for files + "**/*doignore*.*", + ], + [], + ) + # Root folder of our glob test files. Assume tests are run from project root. + for ifile in glob("tests/test_ignore_glob/**/*"): + # Convert to be relative to the tests directory since that mimics + # the user's behavior. + if "do_ignore" in ifile or "doignore" in ifile: + print(f"Should ignore: {ifile})") + assert ignored(ifile) + else: + print(f"Should NOT ignore: {ifile})") + assert not ignored(ifile) diff --git a/tests/test_ignore_glob/do_ignore/bar.txt b/tests/test_ignore_glob/do_ignore/bar.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_ignore_glob/do_ignore/foo.txt b/tests/test_ignore_glob/do_ignore/foo.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_ignore_glob/do_ignore/nested/foo.txt b/tests/test_ignore_glob/do_ignore/nested/foo.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_ignore_glob/do_not_ignore/1doignore.txt b/tests/test_ignore_glob/do_not_ignore/1doignore.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_ignore_glob/do_not_ignore/1doignore1.txt b/tests/test_ignore_glob/do_not_ignore/1doignore1.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_ignore_glob/do_not_ignore/doignore1.txt b/tests/test_ignore_glob/do_not_ignore/doignore1.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_ignore_glob/do_not_ignore/foo.txt b/tests/test_ignore_glob/do_not_ignore/foo.txt new file mode 100644 index 0000000..e69de29