Skip to content

Commit

Permalink
Merge branch 'main' into pythongh-110109-purepathbase
Browse files Browse the repository at this point in the history
  • Loading branch information
barneygale committed Nov 13, 2023
2 parents 6c742ed + cf67ebf commit 3f56662
Show file tree
Hide file tree
Showing 38 changed files with 817 additions and 359 deletions.
6 changes: 6 additions & 0 deletions Doc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ help:
@echo " venv to create a venv with necessary tools"
@echo " html to make standalone HTML files"
@echo " htmlview to open the index page built by the html target in your browser"
@echo " htmllive to rebuild and reload HTML files in your browser"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " text to make plain text files"
Expand Down Expand Up @@ -139,6 +140,11 @@ pydoc-topics: build
htmlview: html
$(PYTHON) -c "import os, webbrowser; webbrowser.open('file://' + os.path.realpath('build/html/index.html'))"

.PHONY: htmllive
htmllive: SPHINXBUILD = $(VENVDIR)/bin/sphinx-autobuild
htmllive: SPHINXOPTS = --re-ignore="/venv/"
htmllive: html

.PHONY: clean
clean: clean-venv
-rm -rf build/*
Expand Down
24 changes: 24 additions & 0 deletions Doc/c-api/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,30 @@ List Objects
list is not supported.
.. c:function:: int PyList_Extend(PyObject *list, PyObject *iterable)
Extend *list* with the contents of *iterable*. This is the same as
``PyList_SetSlice(list, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, iterable)``
and analogous to ``list.extend(iterable)`` or ``list += iterable``.
Raise an exception and return ``-1`` if *list* is not a :class:`list`
object. Return 0 on success.
.. versionadded:: 3.13
.. c:function:: int PyList_Clear(PyObject *list)
Remove all items from *list*. This is the same as
``PyList_SetSlice(list, 0, PY_SSIZE_T_MAX, NULL)`` and analogous to
``list.clear()`` or ``del list[:]``.
Raise an exception and return ``-1`` if *list* is not a :class:`list`
object. Return 0 on success.
.. versionadded:: 3.13
.. c:function:: int PyList_Sort(PyObject *list)
Sort the items of *list* in place. Return ``0`` on success, ``-1`` on
Expand Down
39 changes: 39 additions & 0 deletions Doc/library/glob.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,45 @@ default. For example, consider a directory containing :file:`card.gif` and
>>> glob.glob('.c*')
['.card.gif']


.. function:: translate(pathname, *, recursive=False, include_hidden=False, seps=None)

Convert the given path specification to a regular expression for use with
:func:`re.match`. The path specification can contain shell-style wildcards.

For example:

>>> import glob, re
>>>
>>> regex = glob.translate('**/*.txt', recursive=True, include_hidden=True)
>>> regex
'(?s:(?:.+/)?[^/]*\\.txt)\\Z'
>>> reobj = re.compile(regex)
>>> reobj.match('foo/bar/baz.txt')
<re.Match object; span=(0, 15), match='foo/bar/baz.txt'>

Path separators and segments are meaningful to this function, unlike
:func:`fnmatch.translate`. By default wildcards do not match path
separators, and ``*`` pattern segments match precisely one path segment.

If *recursive* is true, the pattern segment "``**``" will match any number
of path segments. If "``**``" occurs in any position other than a full
pattern segment, :exc:`ValueError` is raised.

If *include_hidden* is true, wildcards can match path segments that start
with a dot (``.``).

A sequence of path separators may be supplied to the *seps* argument. If
not given, :data:`os.sep` and :data:`~os.altsep` (if available) are used.

.. seealso::

:meth:`pathlib.PurePath.match` and :meth:`pathlib.Path.glob` methods,
which call this function to implement pattern matching and globbing.

.. versionadded:: 3.13


.. seealso::

Module :mod:`fnmatch`
Expand Down
9 changes: 5 additions & 4 deletions Doc/reference/expressions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1781,10 +1781,11 @@ Or, when processing a file stream in chunks:
while chunk := file.read(9000):
process(chunk)
Assignment expressions must be surrounded by parentheses when used
as sub-expressions in slicing, conditional, lambda,
keyword-argument, and comprehension-if expressions
and in ``assert`` and ``with`` statements.
Assignment expressions must be surrounded by parentheses when
used as expression statements and when used as sub-expressions in
slicing, conditional, lambda,
keyword-argument, and comprehension-if expressions and
in ``assert``, ``with``, and ``assignment`` statements.
In all other places where they can be used, parentheses are not required,
including in ``if`` and ``while`` statements.

Expand Down
1 change: 1 addition & 0 deletions Doc/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ sphinx==6.2.1

blurb

sphinx-autobuild
sphinxext-opengraph==0.7.5

# The theme used by the documentation is stored separately, so we need
Expand Down
11 changes: 11 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,13 @@ doctest
:attr:`doctest.TestResults.skipped` attributes.
(Contributed by Victor Stinner in :gh:`108794`.)

glob
----

* Add :func:`glob.translate` function that converts a path specification with
shell-style wildcards to a regular expression.
(Contributed by Barney Gale in :gh:`72904`.)

io
--

Expand Down Expand Up @@ -1164,6 +1171,10 @@ New Features
:c:func:`PyErr_WriteUnraisable`, but allow to customize the warning mesage.
(Contributed by Serhiy Storchaka in :gh:`108082`.)

* Add :c:func:`PyList_Extend` and :c:func:`PyList_Clear` functions: similar to
Python ``list.extend()`` and ``list.clear()`` methods.
(Contributed by Victor Stinner in :gh:`111138`.)


Porting to Python 3.13
----------------------
Expand Down
3 changes: 3 additions & 0 deletions Include/cpython/listobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,6 @@ PyList_SET_ITEM(PyObject *op, Py_ssize_t index, PyObject *value) {
}
#define PyList_SET_ITEM(op, index, value) \
PyList_SET_ITEM(_PyObject_CAST(op), (index), _PyObject_CAST(value))

PyAPI_FUNC(int) PyList_Extend(PyObject *self, PyObject *iterable);
PyAPI_FUNC(int) PyList_Clear(PyObject *self);
11 changes: 9 additions & 2 deletions Lib/fnmatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ def translate(pat):
"""

STAR = object()
parts = _translate(pat, STAR, '.')
return _join_translated_parts(parts, STAR)


def _translate(pat, STAR, QUESTION_MARK):
res = []
add = res.append
i, n = 0, len(pat)
Expand All @@ -89,7 +94,7 @@ def translate(pat):
if (not res) or res[-1] is not STAR:
add(STAR)
elif c == '?':
add('.')
add(QUESTION_MARK)
elif c == '[':
j = i
if j < n and pat[j] == '!':
Expand Down Expand Up @@ -146,9 +151,11 @@ def translate(pat):
else:
add(re.escape(c))
assert i == n
return res


def _join_translated_parts(inp, STAR):
# Deal with STARs.
inp = res
res = []
add = res.append
i, n = 0, len(inp)
Expand Down
60 changes: 60 additions & 0 deletions Lib/glob.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,63 @@ def escape(pathname):


_dir_open_flags = os.O_RDONLY | getattr(os, 'O_DIRECTORY', 0)


def translate(pat, *, recursive=False, include_hidden=False, seps=None):
"""Translate a pathname with shell wildcards to a regular expression.
If `recursive` is true, the pattern segment '**' will match any number of
path segments; if '**' appears outside its own segment, ValueError will be
raised.
If `include_hidden` is true, wildcards can match path segments beginning
with a dot ('.').
If a sequence of separator characters is given to `seps`, they will be
used to split the pattern into segments and match path separators. If not
given, os.path.sep and os.path.altsep (where available) are used.
"""
if not seps:
if os.path.altsep:
seps = (os.path.sep, os.path.altsep)
else:
seps = os.path.sep
escaped_seps = ''.join(map(re.escape, seps))
any_sep = f'[{escaped_seps}]' if len(seps) > 1 else escaped_seps
not_sep = f'[^{escaped_seps}]'
if include_hidden:
one_last_segment = f'{not_sep}+'
one_segment = f'{one_last_segment}{any_sep}'
any_segments = f'(?:.+{any_sep})?'
any_last_segments = '.*'
else:
one_last_segment = f'[^{escaped_seps}.]{not_sep}*'
one_segment = f'{one_last_segment}{any_sep}'
any_segments = f'(?:{one_segment})*'
any_last_segments = f'{any_segments}(?:{one_last_segment})?'

results = []
parts = re.split(any_sep, pat)
last_part_idx = len(parts) - 1
for idx, part in enumerate(parts):
if part == '*':
results.append(one_segment if idx < last_part_idx else one_last_segment)
continue
if recursive:
if part == '**':
if idx < last_part_idx:
if parts[idx + 1] != '**':
results.append(any_segments)
else:
results.append(any_last_segments)
continue
elif '**' in part:
raise ValueError("Invalid pattern: '**' can only be an entire path component")
if part:
if not include_hidden and part[0] in '*?':
results.append(r'(?!\.)')
results.extend(fnmatch._translate(part, f'{not_sep}*', not_sep))
if idx < last_part_idx:
results.append(any_sep)
res = ''.join(results)
return fr'(?s:{res})\Z'

0 comments on commit 3f56662

Please sign in to comment.