diff --git a/CHANGES.rst b/CHANGES.rst index b09e184e8..3cc811a57 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,15 @@ Babel Changelog =============== +Version 2.19.0 (Unreleased) +---------------------------- + +Features +~~~~~~~~ + +* Messages: Add ``ignore_hidden`` parameter to ``extract_from_dir()`` to skip + files and directories whose names start with a dot by @2025sl93041-gkb in :gh:`471` + Version 2.18.0 -------------- diff --git a/babel/messages/extract.py b/babel/messages/extract.py index 6fad84304..6170208e3 100644 --- a/babel/messages/extract.py +++ b/babel/messages/extract.py @@ -160,6 +160,7 @@ def extract_from_dir( callback: Callable[[str, str, dict[str, Any]], object] | None = None, strip_comment_tags: bool = False, directory_filter: Callable[[str], bool] | None = None, + ignore_hidden: bool = False, ) -> Generator[_FileExtractionResult, None, None]: """Extract messages from any source files found in the given directory. @@ -228,6 +229,8 @@ def extract_from_dir( :param directory_filter: a callback to determine whether a directory should be recursed into. Receives the full directory path; should return True if the directory is valid. + :param ignore_hidden: if True, skip files and directories whose names start + with a dot (e.g. ``.git``, ``.hidden.py``). :see: `pathmatch` """ if dirname is None: @@ -246,11 +249,15 @@ def extract_from_dir( for root, dirnames, filenames in os.walk(dirname): dirnames[:] = [ - subdir for subdir in dirnames if directory_filter(os.path.join(root, subdir)) + subdir for subdir in dirnames + if directory_filter(os.path.join(root, subdir)) + and (not ignore_hidden or not subdir.startswith('.')) ] dirnames.sort() filenames.sort() for filename in filenames: + if ignore_hidden and filename.startswith('.'): + continue filepath = os.path.join(root, filename).replace(os.sep, '/') yield from check_and_call_extract_file( diff --git a/tests/messages/test_extract.py b/tests/messages/test_extract.py index 1a834d1b7..38f2e14f8 100644 --- a/tests/messages/test_extract.py +++ b/tests/messages/test_extract.py @@ -174,3 +174,35 @@ def test_issue_1195_2(): 'NOTE: This should still be considered, even if', 'the text is far away', ] + + +def test_extract_from_dir_ignore_hidden(tmp_path): + (tmp_path / "visible.py").write_text("_('visible')\n") + (tmp_path / ".hidden.py").write_text("_('hidden_file')\n") + hidden_dir = tmp_path / ".hiddendir" + hidden_dir.mkdir() + (hidden_dir / "inside.py").write_text("_('inside_hidden_dir')\n") + + # ignore_hidden=False (default): hidden file extracted; hidden dir skipped + # by default directory_filter which already ignores dot dirs + results_default = list(extract.extract_from_dir(tmp_path)) + msgids_default = [r[2] for r in results_default] + assert 'visible' in msgids_default + assert 'hidden_file' in msgids_default + + # ignore_hidden=True: hidden files AND hidden dirs skipped + results_no_hidden = list(extract.extract_from_dir(tmp_path, ignore_hidden=True)) + msgids_no_hidden = [r[2] for r in results_no_hidden] + assert 'visible' in msgids_no_hidden + assert 'hidden_file' not in msgids_no_hidden + + # ignore_hidden=True also overrides a permissive directory_filter + results_override = list(extract.extract_from_dir( + tmp_path, + directory_filter=lambda p: True, + ignore_hidden=True, + )) + msgids_override = [r[2] for r in results_override] + assert 'visible' in msgids_override + assert 'hidden_file' not in msgids_override + assert 'inside_hidden_dir' not in msgids_override