Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/file.toit
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io
import system
import .directory
import .stream
import .os as os

export Stream

Expand Down Expand Up @@ -567,5 +568,51 @@ update-time path/string --access/Time?=null --modification/Time?=null:

update-times_ path atime-s atime-ns mtime-s mtime-ns

/**
Searches for an executable file with the given $name in the system PATH.

Returns null if no such executable is found.
Returns a path relative to the part of the PATH where the executable is found.
For example, if the PATH contains `..` and the executable is found there,
then `../name` (or `..\name`) is returned.
*/
find-executable name/string -> string?:
bin-paths := ?
extensions := ?
if system.platform == system.PLATFORM-WINDOWS:
path-env := os.env.get "PATH"
if not path-env: return null
bin-paths = path-env.split ";"
pathext-env := os.env.get "PATHEXT"
if not pathext-env:
extensions = [".exe", ".bat", ".cmd"]
else:
extensions = pathext-env.split ";"
else:
path-env := os.env.get "PATH"
if not path-env: return null
bin-paths = path-env.split ":"
extensions = [""]

bin-paths.do: | bin-path/string |
is-windows := system.platform == system.PLATFORM-WINDOWS
separator := is-windows ? "\\" : "/"
extensions.do: | ext/string |
candidate := if bin-path.ends-with "/" or bin-path.ends-with "\\":
"$bin-path$name$ext"
else:
"$bin-path/$separator$name$ext"
if is-file candidate:
if is-windows:
// On Windows, just being a file is enough.
return candidate
// On other platforms, it must also be executable.
stat := stat candidate
// We are a bit loose here and accept any executable bit, even if
// it is not for the current user.
if stat and stat[ST-MODE] & 0b001001001 != 0:
return candidate
return null

update-times_ path/string atime-s/int atime-ns/int mtime-s/int mtime-ns/int -> none:
#primitive.file.update-times
30 changes: 30 additions & 0 deletions tests/find-executable_test.toit
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (C) 2018 Toitware ApS.
// Use of this source code is governed by a Zero-Clause BSD license that can
// be found in the tests/TESTS_LICENSE file.

import expect show *

import host.file
import host.pipe
import system

main:
shell/string := ?
shell-args := ?
if system.platform == system.PLATFORM-WINDOWS:
// 'cmd.exe' should always be present on Windows systems.
shell = "cmd"
shell-args = ["/c", "echo hello"]
else:
// 'sh' should always be present on Unix-like systems.
shell = "sh"
shell-args = ["-c", "echo hello"]

path := file.find-executable shell
expect-not-null path

// Run the found executable to verify it works.
output := pipe.backticks [path] + shell-args
expect-equals "hello" output.trim

expect-null (file.find-executable "this_executable_does_not_exist")