Skip to content

Commit

Permalink
Add paths parameter to the API (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
ofek authored Sep 24, 2024
1 parent b64e2bc commit f4fde6c
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 16 deletions.
4 changes: 4 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## Unreleased

***Added:***

- Add `paths` parameter to the API

## 0.1.0 - 2024-09-22

This is the initial public release.
60 changes: 44 additions & 16 deletions src/find_exe/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,59 +12,87 @@
from collections.abc import Callable


def with_prefix(prefix: str, *, mode: int = os.F_OK | os.X_OK, path: str | None = None) -> list[str]:
def with_prefix(
prefix: str,
*,
paths: list[str] | None = None,
path: str | None = None,
mode: int = os.F_OK | os.X_OK,
) -> list[str]:
"""
Parameters:
prefix: The prefix used for searching.
paths: The list of paths to check. If `None`, the mutually exclusive `path` parameter is used.
path: The PATH to check, with each path separated by [`os.pathsep`][]. If `None`, the PATH environment
variable is used. Mutually exclusive with `paths`.
mode: The file mode used for checking access.
path: The PATH to check. If `None`, the PATH environment variable is used.
Returns:
A list of absolute paths to executables that match the given prefix.
A list of absolute paths to executables that start with the given prefix.
"""
return with_condition(lambda entry: entry.name.startswith(prefix), mode=mode, path=path)
return with_condition(lambda entry: entry.name.startswith(prefix), paths=paths, path=path, mode=mode)


def with_pattern(
pattern: str | re.Pattern[str], *, mode: int = os.F_OK | os.X_OK, path: str | None = None
pattern: str | re.Pattern[str],
*,
paths: list[str] | None = None,
path: str | None = None,
mode: int = os.F_OK | os.X_OK,
) -> list[str]:
"""
Parameters:
pattern: The pattern used for searching.
paths: The list of paths to check. If `None`, the mutually exclusive `path` parameter is used.
path: The PATH to check, with each path separated by [`os.pathsep`][]. If `None`, the PATH environment
variable is used. Mutually exclusive with `paths`.
mode: The file mode used for checking access.
path: The PATH to check. If `None`, the PATH environment variable is used.
Returns:
A list of absolute paths to executables that match the given pattern.
"""
return with_condition(lambda entry: re.search(pattern, entry.name) is not None, mode=mode, path=path)
return with_condition(lambda entry: re.search(pattern, entry.name) is not None, paths=paths, path=path, mode=mode)


def with_condition(
condition: Callable[[os.DirEntry], bool], *, mode: int = os.F_OK | os.X_OK, path: str | None = None
condition: Callable[[os.DirEntry], bool],
*,
paths: list[str] | None = None,
path: str | None = None,
mode: int = os.F_OK | os.X_OK,
) -> list[str]:
"""
Parameters:
condition: The condition used for searching.
paths: The list of paths to check. If `None`, the mutually exclusive `path` parameter is used.
path: The PATH to check, with each path separated by [`os.pathsep`][]. If `None`, the PATH environment
variable is used. Mutually exclusive with `paths`.
mode: The file mode used for checking access.
path: The PATH to check. If `None`, the PATH environment variable is used.
Returns:
A list of absolute paths to executables that match the given pattern.
A list of absolute paths to executables that satisfy the given condition.
"""
if path is None:

search_paths: list[str] = []
if paths is not None:
if path is not None:
message = 'the `paths` and `path` parameters are mutually exclusive'
raise ValueError(message)

search_paths[:] = paths
elif path is not None:
search_paths[:] = path.split(os.pathsep)
else:
path = os.environ.get('PATH', None)
if path is None:
path = path_fallback()

executables: list[str] = []
if not path:
return executables
search_paths[:] = path.split(os.pathsep)

search_paths = path.split(os.pathsep)
executables: list[str] = []
seen = set()
for search_path in search_paths:
if not os.path.isdir(search_path):
if not (search_path and os.path.isdir(search_path)):
continue

norm_path = os.path.normcase(search_path)
Expand Down
18 changes: 18 additions & 0 deletions tests/test_condition.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,21 @@ def test_duplicate_path(monkeypatch) -> None:
exe_name = os.path.basename(sys.executable)
monkeypatch.setenv('PATH', f'{exe_dir}{os.pathsep}{exe_dir}')
assert find_exe.with_condition(lambda entry: entry.name == exe_name) == [sys.executable]


class TestExplicitSearchPaths:
def test_mutual_exclusion(self) -> None:
with pytest.raises(ValueError, match='the `paths` and `path` parameters are mutually exclusive'):
find_exe.with_condition(bool, paths=[], path='')

def test_list(self) -> None:
exe_dir = os.path.dirname(sys.executable)
exe_name = os.path.basename(sys.executable)
paths = [exe_dir, exe_dir]
assert find_exe.with_condition(lambda entry: entry.name == exe_name, paths=paths) == [sys.executable]

def test_string(self) -> None:
exe_dir = os.path.dirname(sys.executable)
exe_name = os.path.basename(sys.executable)
path = f'{exe_dir}{os.pathsep}{exe_dir}'
assert find_exe.with_condition(lambda entry: entry.name == exe_name, path=path) == [sys.executable]

0 comments on commit f4fde6c

Please sign in to comment.