Description
Summary
findExe works well, but it would be nice to also have an equivalent of which -a
- ie like findExe
but returning all executables (as a seq
or iterator
).
Description
I propose a simple extension of the findExe
, named eg findExeAll
, which either yield
s all found executables, or returns a seq
. To do this we only need a simple modification to the findExe
code, to either assemble a seq
or yield
all found items instead of exiting once the first one is found.
This could be implemented as a flag to findExe
, such as returnFirst
or similar, though then the return type becomes ambiguous, so two functions would seem to make more sense... perhaps one that yields all results, and a second that simply takes the next
value of the yielding function.
Here's a possible implementation:
const ExeExtsSeq: seq[string] = toSeq(ExeExts)
proc iterExes(exe: string, followSymlinks: bool = true;
extensions: seq[string]=ExeExtsSeq): iterator () : string {.
tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect], noNimJs.} =
## Searches for `exe` in the current working directory and then
## in directories listed in the ``PATH`` environment variable.
##
## `exe` is added the `ExeExts <#ExeExts>`_ file extensions if it has none.
##
## If the system supports symlinks it also resolves them until it
## meets the actual file. This behavior can be disabled if desired
## by setting `followSymlinks = false`.
if exe.len == 0: return
result = iterator() : string =
template checkCurrentDir() =
for ext in extensions:
let exe_with_ext = addFileExt(exe, ext)
if fileExists(exe_with_ext):
yield exe_with_ext
when defined(posix):
if '/' in exe: checkCurrentDir()
else:
checkCurrentDir()
let path = getEnv("PATH")
for candidate in split(path, PathSep):
if candidate.len == 0: continue
when defined(windows):
var x = (if candidate[0] == '"' and candidate[^1] == '"':
substr(candidate, 1, candidate.len-2) else: candidate) /
exe
else:
var x = expandTilde(candidate) / exe
for ext in extensions:
var x = addFileExt(x, ext)
if not fileExists(x):
continue
when not defined(windows):
while followSymlinks: # doubles as if here
if x.symlinkExists:
var r = newString(maxSymlinkLen)
var len = readlink(x, r, maxSymlinkLen)
if len < 0:
raiseOSError(osLastError(), exe)
if len > maxSymlinkLen:
r = newString(len+1)
len = readlink(x, r, len)
setLen(r, len)
if isAbsolute(r):
x = r
else:
x = parentDir(x) / r
else:
break
yield x
proc findExe*(exe: string, followSymlinks: bool = true;
extensions: seq[string]=ExeExtsSeq): string {.
tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect], noNimJs.} =
## Searches for `exe` in the current working directory and then
## in directories listed in the ``PATH`` environment variable.
##
## Returns `""` if the `exe` cannot be found. `exe`
## is added the `ExeExts <#ExeExts>`_ file extensions if it has none.
##
## If the system supports symlinks it also resolves them until it
## meets the actual file. This behavior can be disabled if desired
## by setting `followSymlinks = false`.
return iterExes(exe, followSymlinks, extensions)()
proc findExeAll*(exe: string, followSymlinks: bool = true;
extensions: seq[string]=ExeExtsSeq): seq[string] {.
tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect], noNimJs.} =
## Searches for `exe` in the current working directory and then
## in directories listed in the ``PATH`` environment variable.
##
## Returns `""` if the `exe` cannot be found. `exe`
## is added the `ExeExts <#ExeExts>`_ file extensions if it has none.
##
## If the system supports symlinks it also resolves them until it
## meets the actual file. This behavior can be disabled if desired
## by setting `followSymlinks = false`.
var results : seq[string]
let exes = iterExes(exe, followSymlinks, extensions)
for exe in exes():
results.add(exe)
return results
Alternatives
No response
Standard Output Examples
No response
Backwards Compatibility
No response
Links
No response