From 06b158831f30b3ee986705d143e5264fee9c765e Mon Sep 17 00:00:00 2001 From: Nicky Sielicki Date: Thu, 17 Jun 2021 16:27:00 -0500 Subject: add an ability to check that a Path has an executable bit set --- src/click/types.py | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/click/types.py b/src/click/types.py index 550c6ff..e8a5c88 100644 --- a/src/click/types.py +++ b/src/click/types.py @@ -753,8 +753,9 @@ class Path(ParamType): exist, then all further checks are silently skipped. :param file_okay: Allow a file as a value. :param dir_okay: Allow a directory as a value. - :param writable: The file or directory must be writable. - :param readable: The file or directory must be readable. + :param readable: if true, a readable check is performed. + :param writable: if true, a writable check is performed. + :param executable: if true, an executable check is performed. :param resolve_path: Make the value absolute and resolve any symlinks. A ``~`` is not expanded, as this is supposed to be done by the shell only. @@ -765,6 +766,9 @@ class Path(ParamType): ``None``, keep Python's default, which is ``str``. Useful to convert to :class:`pathlib.Path`. + .. versionchanged:: 8.1 + Added the ``executable`` parameter. + .. versionchanged:: 8.0 Allow passing ``type=pathlib.Path``. @@ -779,8 +783,9 @@ class Path(ParamType): exists: bool = False, file_okay: bool = True, dir_okay: bool = True, - writable: bool = False, readable: bool = True, + writable: bool = False, + executable: bool = False, resolve_path: bool = False, allow_dash: bool = False, path_type: t.Optional[t.Type] = None, @@ -788,8 +793,9 @@ class Path(ParamType): self.exists = exists self.file_okay = file_okay self.dir_okay = dir_okay - self.writable = writable self.readable = readable + self.writable = writable + self.executable = executable self.resolve_path = resolve_path self.allow_dash = allow_dash self.type = path_type @@ -862,23 +868,34 @@ class Path(ParamType): ) if not self.dir_okay and stat.S_ISDIR(st.st_mode): self.fail( - _("{name} {filename!r} is a directory.").format( + _("{name} '{filename}' is a directory.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + + if self.readable and not os.access(rv, os.R_OK): + self.fail( + _("{name} '{filename}' is not executable.").format( name=self.name.title(), filename=os.fsdecode(value) ), param, ctx, ) + if self.writable and not os.access(rv, os.W_OK): self.fail( - _("{name} {filename!r} is not writable.").format( + _("{name} '{filename}' is not writable.").format( name=self.name.title(), filename=os.fsdecode(value) ), param, ctx, ) - if self.readable and not os.access(rv, os.R_OK): + + if self.executable and not os.access(value, os.X_OK): self.fail( - _("{name} {filename!r} is not readable.").format( + _("{name} {filename!r} is not executable.").format( name=self.name.title(), filename=os.fsdecode(value) ), param, -- cgit v1.2.1