Skip to content

Commit

Permalink
Add support for Zsh style *** (#225)
Browse files Browse the repository at this point in the history
* Add support for Zsh style `***`

* Add tests for glob

* Use R'' for non regex strings

* Add globmatch tests

* Add pathlib tests and fix some issues

- Add missing `PurePath.full_match` method that redirects to
  `PurePath.globmatch`.
- `PurePath.match` should respect hidden file rules and symlink rules
  the same as `rglob`. `match` matches what `rglob` globs.

* Fix mypy Python 3.13
  • Loading branch information
facelessuser authored Sep 26, 2024
1 parent 35e9cac commit 0c069cb
Show file tree
Hide file tree
Showing 11 changed files with 591 additions and 245 deletions.
1 change: 1 addition & 0 deletions docs/src/dictionary/en-custom.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ preprocessing
preprocessors
prerelease
prereleases
recurse
recursing
regex
sharepoint
Expand Down
6 changes: 5 additions & 1 deletion docs/src/markdown/about/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

## 10.0

- **NEW**: Added `GLOBSTARLONG` which adds support for the Zsh style `***` which acts like `**` with `GLOBSTAR` but
but traverses symlinks.
- **NEW**: `pathlib.match` will respect symlinks (when the `REALPATH` flag is given) and hidden file rules (enable
`DOTALL` to match hidden files).
- **NEW**: Symlinks should not be traversed when `GLOBSTAR` is enabled unless `FOLLOW` is also enabled, but they
should still be matched. Prior to this change, symlinks were not traversed _and_ they were ignored from matching
which contradicts how Bash works, which is are general target.
which contradicts how Bash works and could be confusing to users.
- **FIX**: Fix some inconsistencies with `globmatch` and symlink handling when `REALPATH` is enabled.

## 9.0
Expand Down
23 changes: 22 additions & 1 deletion docs/src/markdown/glob.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ character `#!py3 r'\?'`. If you want to represent a literal backslash, you must
Pattern | Meaning
----------------- | -------
`*` | Matches everything except slashes. On Windows it will avoid matching backslashes as well as slashes.
`**` | Matches zero or more directories, but will never match the directories `.` and `..`. Requires the [`GLOBSTAR`](#globstar) flag.
`**` | Matches zero or more directories, but will never match the directories ` . ` and `..`. Requires the [`GLOBSTAR`](#globstar) flag.
`***` | Like `**` but will also recurse symlinks. Requires the [`GLOBSTARLONG`](#globstarlong) flag.
`?` | Matches any single character.
`[seq]` | Matches any character in seq.
`[!seq]` | Matches any character not in seq. Will also accept character exclusions in the form of `[^seq]`.
Expand Down Expand Up @@ -766,10 +767,27 @@ When `MINUSNEGATE` is used with [`NEGATE`](#negate), exclusion patterns are reco

`GLOBSTAR` enables the feature where `**` matches zero or more directories.

#### `glob.GLOBSTARLONG, glob.GL` {: #globstarlong}

/// new | New 10.0
///

When `GLOBSTARLONG` is enabled `***` will act like `**`, but will cause symlinks to be traversed as well.

Enabling `GLOBSTARLONG` automatically enables [`GLOBSTAR`](#globstar).

[`FOLLOW`](#follow) will be ignored and `***` will be required to traverse a symlink. But it should be noted that when
using [`MATCHBASE`](#matchbase) and [`FOLLOW`](#follow) with `GLOBSTARLONG`, that [`FOLLOW`](#follow) will cause the
implicit leading `**` that [`MATCHBASE`](#matchbase) applies to act as an implicit `***`.

#### `glob.FOLLOW, glob.L` {: #follow}

`FOLLOW` will cause [`GLOBSTAR`](#globstar) patterns (`**`) to traverse symlink directories.

`FOLLOW` will have no affect if using [`GLOBSTARLONG`](#globstarlong) and an explicit `***` will be required to traverse
a symlink. `FOLLOW` will have an affect if enabled with [`GLOBSTARLONG`](#globstarlong) and [`MATCHBASE`](#matchbase)
and will cause the implicit leading `**` that `MATCHBASE` applies to act as an implicit `***`.

#### `glob.REALPATH, glob.P` {: #realpath}

In the past, only [`glob`](#glob) and [`iglob`](#iglob) operated on the filesystem, but with `REALPATH`, other
Expand All @@ -786,6 +804,9 @@ logic so that the path must meet the following in order to match:
- Path must exist.
- Directories that are symlinks will not be traversed by [`GLOBSTAR`](#globstar) patterns (`**`) unless the
[`FOLLOW`](#follow) flag is enabled.
- If [`GLOBSTARLONG`](#globstarlong) is enabled, `***` will traverse symlinks, [`FOLLOW`](#follow) will be ignored
except if [`MATCHBASE`](#matchbase) is also enabled, in that case, the implicit leading `**` added by
[`MATCHBASE`](#matchbase) will act as `***`.
- When presented with a pattern where the match must be a directory, but the file path being compared doesn't indicate
the file is a directory with a trailing slash, the command will look at the filesystem to determine if it is a
directory.
Expand Down
54 changes: 39 additions & 15 deletions docs/src/markdown/pathlib.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,22 +252,12 @@ pattern(s).

`match` mimics Python's `pathlib` version of `match`. Python's `match` uses a right to left evaluation. Wildcard Match
emulates this behavior as well. What this means is that when provided with a path `some/path/name`, the patterns `name`,
`path/name` and `some/path/name` will all match.

Because the path is evaluated right to left, dot files may not prevent matches when `DOTGLOB` is disabled.

```pycon3
>>> from wcmatch import pathlib
>>> pathlib.PurePath('.dotfile/file').match('file')
True
>>> pathlib.PurePath('../.dotfile/file').match('file')
True
```
`path/name` and `some/path/name` will all match. Essentially, it matches what [`rglob`](#rglob) returns.

`match` does not access the filesystem, but you can force the path to access the filesystem if you give it the
[`REALPATH`](#realpath) flag. We do not restrict this, but we do not enable it by default.
[`REALPATH`](#realpath) simply forces the match to check the filesystem to see if the file exists and is a
directory or not.
[`REALPATH`](#realpath) simply forces the match to check the filesystem to see if the file exists, if it is a
directory or not, and whether it is a symlink.

Since [`Path`](#path) is derived from [`PurePath`](#purepath), this method is also available in
[`Path`](#path) objects.
Expand Down Expand Up @@ -302,8 +292,8 @@ a list of patterns. It will return a boolean indicating whether the objects file

`globmatch` does not access the filesystem, but you can force the path to access the filesystem if you give it the
[`REALPATH`](#realpath) flag. We do not restrict this, but we do not enable it by default.
[`REALPATH`](#realpath) simply forces the match to check the filesystem to see if the file exists and is a
directory or not.
[`REALPATH`](#realpath) simply forces the match to check the filesystem to see if the file exists, if it is a
directory or not, and whether it is a symlink.

Since [`Path`](#path) is derived from [`PurePath`](#purepath), this method is also available in
[`Path`](#path) objects.
Expand All @@ -323,6 +313,19 @@ True
`exclude` parameter was added.
///

#### `PurePath.full_match` {: #full_match}

/// new | new 10.0
///

```py3
def full_match(self, patterns, *, flags=0, limit=1000, exclude=None):
```

Python 3.13 added the new `full_match` method to `PurePath` objects. Essentially, this does for normal `pathlib`
what our existing `PurePath.globmatch` has been doing prior to Python 3.13. We've added an alias for
`PurePath.full_match` that redirects to [`PurePath.globmatch`](#globmatch) for completeness.

#### `Path.glob` {: #glob}

```py3
Expand Down Expand Up @@ -446,10 +449,27 @@ When `MINUSNEGATE` is used with [`NEGATE`](#negate), exclusion patterns are reco

`GLOBSTAR` enables the feature where `**` matches zero or more directories.

#### `glob.GLOBSTARLONG, glob.GL` {: #globstarlong}

/// new | New 10.0
///

When `GLOBSTARLONG` is enabled `***` will act like `**`, but will cause symlinks to be traversed as well.

Enabling `GLOBSTARLONG` automatically enables [`GLOBSTAR`](#globstar).

[`FOLLOW`](#follow) will be ignored and `***` will be required to traverse a symlink. But it should be noted that when
using [`MATCHBASE`](#matchbase) and [`FOLLOW`](#follow) with `GLOBSTARLONG`, that [`FOLLOW`](#follow) will cause the
implicit leading `**` that [`MATCHBASE`](#matchbase) applies to act as an implicit `***`.

#### `pathlib.FOLLOW, pathlib.L` {: #follow}

`FOLLOW` will cause `GLOBSTAR` patterns (`**`) to match and traverse symlink directories.

`FOLLOW` will have no affect if using [`GLOBSTARLONG`](#globstarlong) and an explicit `***` will be required to traverse
a symlink. `FOLLOW` will have an affect if enabled with [`GLOBSTARLONG`](#globstarlong) and [`MATCHBASE`](#matchbase)
and will cause the implicit leading `**` that `MATCHBASE` applies to act as an implicit `***`.

#### `pathlib.REALPATH, pathlib.P` {: #realpath}

In the past, only `glob` and `iglob` operated on the filesystem, but with `REALPATH`, other functions will now operate
Expand All @@ -466,6 +486,10 @@ that the path must meet the following in order to match:
- Path must exist.
- Directories that are symlinks will not be matched by [`GLOBSTAR`](#globstar) patterns (`**`) unless the
[`FOLLOW`](#follow) flag is enabled.
- If [`GLOBSTARLONG`](#globstarlong) is enabled, `***` will traverse symlinks, [`FOLLOW`](#follow) will be ignored
except if [`MATCHBASE`](#matchbase) is also enabled, in that case, the implicit leading `**` added by
[`MATCHBASE`](#matchbase) will act as `***`. This also affects the implicit leading `**` adding by
[`rglob`](#rglob).
- When presented with a pattern where the match must be a directory, but the file path being compared doesn't indicate
the file is a directory with a trailing slash, the command will look at the filesystem to determine if it is a
directory.
Expand Down
Loading

0 comments on commit 0c069cb

Please sign in to comment.