Skip to content

Instantly share code, notes, and snippets.

@dburriss
Last active November 2, 2022 12:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dburriss/7b689cb0f505c3b784d14e96d5fa05b7 to your computer and use it in GitHub Desktop.
Save dburriss/7b689cb0f505c3b784d14e96d5fa05b7 to your computer and use it in GitHub Desktop.
Finding and running a process in F# in a reliable way.
// ========================================================
// String: String module
// Helpers for working with strings
// ========================================================
module String =
let isNotEmpty s = not (String.IsNullOrEmpty s)
let join (ss: string seq) = String.Join("", ss)
let lower (s: string) = s.ToLowerInvariant()
let trim (s: string) = s.Trim()
let replace (oldValue: string) newValue (s: string) = s.Replace(oldValue,newValue)
let split (by: string) (s: string) = s.Split(by)
let endsWith (tail: string) (s: string) = s.EndsWith(tail)
let startsWith (head: string) (s: string) = s.StartsWith(head)
let contains (search: string) (s: string) = s.Contains(search)
// ========================================================
// Env: Env module
// Helper functions for interacting with the local environment
// ========================================================
type OS = | Windows | Linux | OSX | FreeBSD
module Env =
open System.Runtime.InteropServices
let os() =
if RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then Windows
elif RuntimeInformation.IsOSPlatform(OSPlatform.Linux) then Linux
elif RuntimeInformation.IsOSPlatform(OSPlatform.OSX) then OSX
else FreeBSD
let varNames() : string seq = seq { for k in Environment.GetEnvironmentVariables().Keys -> k :?> string }
let path() =
Environment.GetEnvironmentVariable("PATH")
let tryGet name =
let value = Environment.GetEnvironmentVariable(name)
if isNull value then None
else Some value
let executables os =
match os with
| Windows -> [".exe";".bat";".cmd";".ps1"]
| _ -> ["";".sh"]
let dirSep() = string IO.Path.DirectorySeparatorChar
// ========================================================
// Proc: Proc module
// For calling CLI process on the machine
// ========================================================
module Proc =
let private fileName (dir: string) command ext =
let pathSplit = Env.dirSep()
let path = if IO.Path.EndsInDirectorySeparator(dir) then dir else dir + pathSplit
$"{path}{command}{ext}"
let private searchDirectoryForCommand extensions command dir =
let fullFilePath = fileName dir command
let toCheck =
extensions
|> List.map fullFilePath
toCheck |> List.filter (fun f -> IO.File.Exists(f))
let private fetchCommand os envPATH command =
let extensions = Env.executables os
let dirs = envPATH |> String.split (string IO.Path.PathSeparator) |> List.ofArray
let executables =
dirs
|> List.map (searchDirectoryForCommand extensions command)
|> List.concat
executables |> List.tryHead
let run command args workingDir =
let commandOpt = fetchCommand (Env.os()) (Env.path()) command
match commandOpt with
| Some cmd ->
Debug.WriteLine(sprintf "RUNNING COMMAND: %s" cmd)
let info = ProcessStartInfo(cmd, args)
info.WorkingDirectory <- workingDir //must be full path not using ~/
info.UseShellExecute <- false
info.RedirectStandardOutput <- true
info.RedirectStandardError <- true
let p = Process.Start(info)
let output =
[|
while not p.StandardOutput.EndOfStream do
let o = p.StandardOutput.ReadLine()
if String.isNotEmpty o then o
|]
let err = p.StandardError.ReadToEnd()
p.WaitForExit()
if p.ExitCode = 0 || String.IsNullOrEmpty(err) then
Ok output
else Error (p.ExitCode, err)
| None -> Error(-1, $"Unable to find executable ({command}). Make sure it appears in your PATH.")
Proc.run "npm" "audit --json" "./project-dir"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment