2022-01-03 14:14:19 +00:00
use process_control ::{ ChildExt , Control } ;
2021-07-16 19:20:59 +00:00
use std ::ffi ::OsStr ;
2021-05-18 21:30:00 +00:00
use std ::fmt ::Debug ;
use std ::fs ::read_to_string ;
2021-07-16 19:20:59 +00:00
use std ::io ::{ Error , ErrorKind , Result } ;
2021-07-04 19:32:58 +00:00
use std ::path ::{ Path , PathBuf } ;
2021-01-21 21:59:14 +00:00
use std ::process ::{ Command , Stdio } ;
use std ::time ::{ Duration , Instant } ;
2019-06-10 14:56:17 +00:00
2022-01-21 15:44:46 +00:00
use crate ::context ::Context ;
2020-02-06 16:10:59 +00:00
use crate ::context ::Shell ;
2022-01-21 15:44:46 +00:00
/// Create a `PathBuf` from an absolute path, where the root directory will be mocked in test
#[ cfg(not(test)) ]
#[ inline ]
#[ allow(dead_code) ]
pub fn context_path < S : AsRef < OsStr > + ? Sized > ( _context : & Context , s : & S ) -> PathBuf {
PathBuf ::from ( s )
}
/// Create a `PathBuf` from an absolute path, where the root directory will be mocked in test
#[ cfg(test) ]
#[ allow(dead_code) ]
pub fn context_path < S : AsRef < OsStr > + ? Sized > ( context : & Context , s : & S ) -> PathBuf {
let requested_path = PathBuf ::from ( s ) ;
if requested_path . is_absolute ( ) {
let mut path = PathBuf ::from ( context . root_dir . path ( ) ) ;
path . extend ( requested_path . components ( ) . skip ( 1 ) ) ;
path
} else {
requested_path
}
}
2019-06-10 14:56:17 +00:00
/// Return the string contents of a file
2021-05-18 21:30:00 +00:00
pub fn read_file < P : AsRef < Path > + Debug > ( file_name : P ) -> Result < String > {
log ::trace! ( " Trying to read from {:?} " , file_name ) ;
2019-06-10 14:56:17 +00:00
2021-05-18 21:30:00 +00:00
let result = read_to_string ( file_name ) ;
if result . is_err ( ) {
log ::debug! ( " Error reading file: {:?} " , result ) ;
} else {
2021-08-02 17:13:29 +00:00
log ::trace! ( " File read successfully " ) ;
2021-05-18 21:30:00 +00:00
} ;
result
2019-06-10 14:56:17 +00:00
}
2019-11-27 22:03:08 +00:00
2022-11-15 10:14:52 +00:00
/// Write a string to a file
#[ cfg(test) ]
pub fn write_file < P : AsRef < Path > , S : AsRef < str > > ( file_name : P , text : S ) -> Result < ( ) > {
use std ::io ::Write ;
let file_name = file_name . as_ref ( ) ;
let text = text . as_ref ( ) ;
log ::trace! ( " Trying to write {text:?} to {file_name:?} " ) ;
let mut file = match std ::fs ::OpenOptions ::new ( )
. write ( true )
. create ( true )
. truncate ( true )
. open ( file_name )
{
Ok ( file ) = > file ,
Err ( err ) = > {
log ::warn! ( " Error creating file: {:?} " , err ) ;
return Err ( err ) ;
}
} ;
match file . write_all ( text . as_bytes ( ) ) {
Ok ( _ ) = > {
log ::trace! ( " File {file_name:?} written successfully " ) ;
}
Err ( err ) = > {
log ::warn! ( " Error writing to file: {err:?} " ) ;
return Err ( err ) ;
}
}
file . sync_all ( )
}
2021-08-02 17:13:29 +00:00
/// Reads command output from stderr or stdout depending on to which stream program streamed it's output
pub fn get_command_string_output ( command : CommandOutput ) -> String {
if command . stdout . is_empty ( ) {
command . stderr
} else {
command . stdout
}
}
2021-07-16 19:20:59 +00:00
/// Attempt to resolve `binary_name` from and creates a new `Command` pointing at it
/// This allows executing cmd files on Windows and prevents running executable from cwd on Windows
2022-06-16 00:55:10 +00:00
/// This function also initializes std{err,out,in} to protect against processes changing the console mode
2021-07-16 19:20:59 +00:00
pub fn create_command < T : AsRef < OsStr > > ( binary_name : T ) -> Result < Command > {
let binary_name = binary_name . as_ref ( ) ;
2021-12-28 05:56:06 +00:00
log ::trace! ( " Creating Command for binary {:?} " , binary_name ) ;
2021-07-16 19:20:59 +00:00
let full_path = match which ::which ( binary_name ) {
Ok ( full_path ) = > {
log ::trace! ( " Using {:?} as {:?} " , full_path , binary_name ) ;
full_path
}
Err ( error ) = > {
log ::trace! ( " Unable to find {:?} in PATH, {:?} " , binary_name , error ) ;
return Err ( Error ::new ( ErrorKind ::NotFound , error ) ) ;
}
} ;
2022-02-25 04:31:01 +00:00
#[ allow(clippy::disallowed_methods) ]
2021-07-16 19:20:59 +00:00
let mut cmd = Command ::new ( full_path ) ;
cmd . stderr ( Stdio ::piped ( ) )
. stdout ( Stdio ::piped ( ) )
. stdin ( Stdio ::null ( ) ) ;
Ok ( cmd )
}
2021-02-13 18:32:35 +00:00
#[ derive(Debug, Clone) ]
2019-11-27 22:03:08 +00:00
pub struct CommandOutput {
pub stdout : String ,
pub stderr : String ,
}
impl PartialEq for CommandOutput {
fn eq ( & self , other : & Self ) -> bool {
self . stdout = = other . stdout & & self . stderr = = other . stderr
}
}
2021-08-23 16:49:30 +00:00
#[ cfg(test) ]
pub fn display_command < T : AsRef < OsStr > + Debug , U : AsRef < OsStr > + Debug > (
cmd : T ,
args : & [ U ] ,
) -> String {
std ::iter ::once ( cmd . as_ref ( ) )
2022-05-23 10:58:27 +00:00
. chain ( args . iter ( ) . map ( std ::convert ::AsRef ::as_ref ) )
2021-08-23 16:49:30 +00:00
. map ( | i | i . to_string_lossy ( ) . into_owned ( ) )
. collect ::< Vec < String > > ( )
. join ( " " )
}
2020-08-07 19:13:12 +00:00
/// Execute a command and return the output on stdout and stderr if successful
2021-08-23 16:49:30 +00:00
pub fn exec_cmd < T : AsRef < OsStr > + Debug , U : AsRef < OsStr > + Debug > (
cmd : T ,
args : & [ U ] ,
time_limit : Duration ,
) -> Option < CommandOutput > {
2021-12-28 05:56:06 +00:00
log ::trace! ( " Executing command {:?} with args {:?} " , cmd , args ) ;
#[ cfg(test) ]
if let Some ( o ) = mock_cmd ( & cmd , args ) {
return o ;
}
2021-07-29 18:27:46 +00:00
internal_exec_cmd ( cmd , args , time_limit )
2020-01-11 15:08:32 +00:00
}
#[ cfg(test) ]
2021-12-28 05:56:06 +00:00
pub fn mock_cmd < T : AsRef < OsStr > + Debug , U : AsRef < OsStr > + Debug > (
2021-08-23 16:49:30 +00:00
cmd : T ,
args : & [ U ] ,
2021-12-28 05:56:06 +00:00
) -> Option < Option < CommandOutput > > {
2021-08-23 16:49:30 +00:00
let command = display_command ( & cmd , args ) ;
2021-12-28 05:56:06 +00:00
let out = match command . as_str ( ) {
2022-08-01 10:59:36 +00:00
" bun --version " = > Some ( CommandOutput {
stdout : String ::from ( " 0.1.4 \n " ) ,
stderr : String ::default ( ) ,
} ) ,
2022-03-12 11:10:23 +00:00
" buf --version " = > Some ( CommandOutput {
stdout : String ::from ( " 1.0.0 " ) ,
stderr : String ::default ( ) ,
} ) ,
2022-03-25 04:10:19 +00:00
" cc --version " = > Some ( CommandOutput {
stdout : String ::from ( " \
FreeBSD clang version 11. 0.1 ( git @ github . com :llvm / llvm - project . git llvmorg - 11. 0.1 - 0 - g43ff75f2c3fe )
Target : x86_64 - unknown - freebsd13 . 0
Thread model : posix
InstalledDir : / usr / bin " ),
stderr : String ::default ( ) ,
} ) ,
" gcc --version " = > Some ( CommandOutput {
stdout : String ::from ( " \
cc ( Debian 10. 2.1 - 6 ) 10. 2.1 20210110
Copyright ( C ) 2020 Free Software Foundation , Inc .
This is free software ; see the source for copying conditions . There is NO
warranty ; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . " ),
stderr : String ::default ( ) ,
} ) ,
" clang --version " = > Some ( CommandOutput {
stdout : String ::from ( " \
OpenBSD clang version 11. 1.0
Target : amd64 - unknown - openbsd7 . 0
Thread model : posix
InstalledDir : / usr / bin " ),
stderr : String ::default ( ) ,
} ) ,
2021-09-07 14:59:14 +00:00
" cobc -version " = > Some ( CommandOutput {
stdout : String ::from ( " \
cobc ( GnuCOBOL ) 3. 1. 2.0
Copyright ( C ) 2020 Free Software Foundation , Inc .
License GPLv3 + : GNU GPL version 3 or later < https ://gnu.org/licenses/gpl.html>
This is free software ; see the source for copying conditions . There is NO
warranty ; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE .
Written by Keisuke Nishida , Roger While , Ron Norman , Simon Sobisch , Edward Hart
Built Dec 24 2020 19 :08 :58
Packaged Dec 23 2020 12 :04 :58 UTC
C version \ " 10.2.0 \" " ) ,
stderr : String ::default ( ) ,
} ) ,
2020-02-12 18:22:21 +00:00
" crystal --version " = > Some ( CommandOutput {
2020-07-19 21:01:53 +00:00
stdout : String ::from (
" \
Crystal 0.3 5.1 ( 2020 - 06 - 19 )
LLVM : 10. 0.0
Default target : x86_64 - apple - macosx \ n " ,
) ,
2020-02-12 18:22:21 +00:00
stderr : String ::default ( ) ,
} ) ,
2020-07-29 15:38:23 +00:00
" dart --version " = > Some ( CommandOutput {
stdout : String ::default ( ) ,
stderr : String ::from (
" Dart VM version: 2.8.4 (stable) (Wed Jun 3 12:26:04 2020 +0200) on \" macos_x64 \" " ,
) ,
} ) ,
2021-04-15 13:22:12 +00:00
" deno -V " = > Some ( CommandOutput {
stdout : String ::from ( " deno 1.8.3 \n " ) ,
stderr : String ::default ( )
} ) ,
2020-02-12 18:22:21 +00:00
" dummy_command " = > Some ( CommandOutput {
2020-07-19 21:01:53 +00:00
stdout : String ::from ( " stdout ok! \n " ) ,
stderr : String ::from ( " stderr ok! \n " ) ,
2020-02-12 18:22:21 +00:00
} ) ,
2020-06-09 17:14:47 +00:00
" elixir --version " = > Some ( CommandOutput {
stdout : String ::from (
" \
Erlang / OTP 22 [ erts - 10. 6.4 ] [ source ] [ 64 - bit ] [ smp :8 :8 ] [ ds :8 :8 :10 ] [ async - threads :1 ] [ hipe ]
2020-07-19 21:01:53 +00:00
Elixir 1.10 ( compiled with Erlang / OTP 22 ) \ n " ,
2020-06-09 17:14:47 +00:00
) ,
stderr : String ::default ( ) ,
} ) ,
2020-02-06 03:57:04 +00:00
" elm --version " = > Some ( CommandOutput {
2020-07-19 21:01:53 +00:00
stdout : String ::from ( " 0.19.1 \n " ) ,
2020-02-06 03:57:04 +00:00
stderr : String ::default ( ) ,
2020-02-06 17:11:20 +00:00
} ) ,
2022-12-21 16:53:53 +00:00
" fennel --version " = > Some ( CommandOutput {
stdout : String ::from ( " Fennel 1.2.1 on PUC Lua 5.4 \n " ) ,
stderr : String ::default ( ) ,
} ) ,
2023-01-31 08:06:46 +00:00
" fossil branch current " = > Some ( CommandOutput {
stdout : String ::from ( " topic-branch " ) ,
stderr : String ::default ( ) ,
} ) ,
" fossil branch new topic-branch trunk " = > Some ( CommandOutput {
stdout : String ::default ( ) ,
stderr : String ::default ( ) ,
} ) ,
" fossil update topic-branch " = > Some ( CommandOutput {
stdout : String ::default ( ) ,
stderr : String ::default ( ) ,
} ) ,
2020-02-12 18:22:21 +00:00
" go version " = > Some ( CommandOutput {
2020-07-19 21:01:53 +00:00
stdout : String ::from ( " go version go1.12.1 linux/amd64 \n " ) ,
2020-02-06 17:11:20 +00:00
stderr : String ::default ( ) ,
2020-02-06 03:57:04 +00:00
} ) ,
2022-03-18 06:45:51 +00:00
" ghc --numeric-version " = > Some ( CommandOutput {
stdout : String ::from ( " 9.2.1 \n " ) ,
stderr : String ::default ( ) ,
} ) ,
2020-07-17 07:51:25 +00:00
" helm version --short --client " = > Some ( CommandOutput {
2020-07-19 21:01:53 +00:00
stdout : String ::from ( " v3.1.1+gafe7058 \n " ) ,
2020-07-17 07:51:25 +00:00
stderr : String ::default ( ) ,
} ) ,
2020-07-29 16:26:46 +00:00
s if s . ends_with ( " java -Xinternalversion " ) = > Some ( CommandOutput {
stdout : String ::from ( " OpenJDK 64-Bit Server VM (13.0.2+8) for bsd-amd64 JRE (13.0.2+8), built on Feb 6 2020 02:07:52 by \" brew \" with clang 4.2.1 Compatible Apple LLVM 11.0.0 (clang-1100.0.33.17) " ) ,
stderr : String ::default ( ) ,
} ) ,
2021-03-14 19:37:00 +00:00
" scalac -version " = > Some ( CommandOutput {
stdout : String ::from ( " Scala compiler version 2.13.5 -- Copyright 2002-2020, LAMP/EPFL and Lightbend, Inc. " ) ,
stderr : String ::default ( ) ,
} ) ,
2020-04-03 18:16:34 +00:00
" julia --version " = > Some ( CommandOutput {
2020-07-19 21:01:53 +00:00
stdout : String ::from ( " julia version 1.4.0 \n " ) ,
2020-04-03 18:16:34 +00:00
stderr : String ::default ( ) ,
} ) ,
2020-12-26 14:26:50 +00:00
" kotlin -version " = > Some ( CommandOutput {
stdout : String ::from ( " Kotlin version 1.4.21-release-411 (JRE 14.0.1+7) \n " ) ,
stderr : String ::default ( ) ,
} ) ,
" kotlinc -version " = > Some ( CommandOutput {
stdout : String ::from ( " info: kotlinc-jvm 1.4.21 (JRE 14.0.1+7) \n " ) ,
stderr : String ::default ( ) ,
} ) ,
2020-10-27 18:05:20 +00:00
" lua -v " = > Some ( CommandOutput {
stdout : String ::from ( " Lua 5.4.0 Copyright (C) 1994-2020 Lua.org, PUC-Rio \n " ) ,
stderr : String ::default ( ) ,
} ) ,
" luajit -v " = > Some ( CommandOutput {
stdout : String ::from ( " LuaJIT 2.0.5 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/ \n " ) ,
stderr : String ::default ( ) ,
} ) ,
2020-06-09 17:14:47 +00:00
" nim --version " = > Some ( CommandOutput {
stdout : String ::from (
" \
Nim Compiler Version 1. 2.0 [ Linux : amd64 ]
Compiled at 2020 - 04 - 03
Copyright ( c ) 2006 - 2020 by Andreas Rumpf
git hash : 7e83 adff84be5d0c401a213eccb61e321a3fb1ff
active boot switches : - d :release \ n " ,
) ,
stderr : String ::default ( ) ,
} ) ,
2020-01-26 22:37:18 +00:00
" node --version " = > Some ( CommandOutput {
2020-07-19 21:01:53 +00:00
stdout : String ::from ( " v12.0.0 \n " ) ,
2020-01-26 22:37:18 +00:00
stderr : String ::default ( ) ,
} ) ,
2020-05-21 16:43:13 +00:00
" ocaml -vnum " = > Some ( CommandOutput {
2020-07-15 21:22:40 +00:00
stdout : String ::from ( " 4.10.0 \n " ) ,
2020-05-21 16:43:13 +00:00
stderr : String ::default ( ) ,
2020-07-05 17:20:11 +00:00
} ) ,
2022-10-15 16:15:33 +00:00
" opa version " = > Some ( CommandOutput {
stdout : String ::from ( " Version: 0.44.0
Build Commit : e8d488f
Build Timestamp : 2022 - 09 - 07 T23 :50 :25 Z
Build Hostname : 119428673 f4c
Go Version : go1 . 19.1
Platform : linux / amd64
WebAssembly : unavailable
" ),
stderr : String ::default ( ) ,
} ) ,
2021-04-02 17:21:48 +00:00
" opam switch show --safe " = > Some ( CommandOutput {
stdout : String ::from ( " default \n " ) ,
stderr : String ::default ( ) ,
} ) ,
2020-07-05 17:20:11 +00:00
" esy ocaml -vnum " = > Some ( CommandOutput {
2020-07-15 21:22:40 +00:00
stdout : String ::from ( " 4.08.1 \n " ) ,
2020-07-05 17:20:11 +00:00
stderr : String ::default ( ) ,
2020-05-21 16:43:13 +00:00
} ) ,
2020-08-04 16:22:44 +00:00
" perl -e printf q#%vd#,$^V; " = > Some ( CommandOutput {
stdout : String ::from ( " 5.26.1 " ) ,
stderr : String ::default ( ) ,
} ) ,
2020-08-24 17:05:43 +00:00
" php -nr echo PHP_MAJOR_VERSION. \" . \" .PHP_MINOR_VERSION. \" . \" .PHP_RELEASE_VERSION; " = > {
2020-02-21 16:52:39 +00:00
Some ( CommandOutput {
stdout : String ::from ( " 7.3.8 " ) ,
stderr : String ::default ( ) ,
} )
2021-10-05 23:27:25 +00:00
} ,
2022-12-31 14:55:23 +00:00
" pijul channel " = > Some ( CommandOutput {
stdout : String ::from ( " main \n * tributary-48198 " ) ,
stderr : String ::default ( ) ,
} ) ,
" pijul channel new tributary-48198 " = > Some ( CommandOutput {
stdout : String ::default ( ) ,
stderr : String ::default ( ) ,
} ) ,
" pijul channel switch tributary-48198 " = > Some ( CommandOutput {
stdout : String ::from ( " Outputting repository ↖ " ) ,
stderr : String ::default ( ) ,
} ) ,
2021-10-05 23:27:25 +00:00
" pulumi version " = > Some ( CommandOutput {
stdout : String ::from ( " 1.2.3-ver.1631311768+e696fb6c " ) ,
stderr : String ::default ( ) ,
} ) ,
2020-05-22 16:26:58 +00:00
" purs --version " = > Some ( CommandOutput {
2020-07-19 21:01:53 +00:00
stdout : String ::from ( " 0.13.5 \n " ) ,
2020-05-22 16:26:58 +00:00
stderr : String ::default ( ) ,
} ) ,
2020-09-21 15:48:26 +00:00
" pyenv version-name " = > Some ( CommandOutput {
stdout : String ::from ( " system \n " ) ,
stderr : String ::default ( ) ,
} ) ,
2020-11-30 19:14:18 +00:00
" python --version " = > None ,
" python2 --version " = > Some ( CommandOutput {
2020-07-19 21:01:53 +00:00
stdout : String ::default ( ) ,
stderr : String ::from ( " Python 2.7.17 \n " ) ,
2020-06-14 09:27:10 +00:00
} ) ,
" python3 --version " = > Some ( CommandOutput {
2020-07-19 21:01:53 +00:00
stdout : String ::from ( " Python 3.8.0 \n " ) ,
2020-06-14 09:27:10 +00:00
stderr : String ::default ( ) ,
} ) ,
2021-05-25 18:13:30 +00:00
" R --version " = > Some ( CommandOutput {
stdout : String ::default ( ) ,
stderr : String ::from (
r #" R version 4.1.0 (2021-05-18) -- " Camp Pontanezen "
Copyright ( C ) 2021 The R Foundation for Statistical Computing
Platform : x86_64 - w64 - mingw32 / x64 ( 64 - bit ) \ n
R is free software and comes with ABSOLUTELY NO WARRANTY .
You are welcome to redistribute it under the terms of the
GNU General Public License versions 2 or 3.
For more information about these matters see
https ://www.gnu.org/licenses/."#
) ,
} ) ,
2022-06-26 10:00:55 +00:00
" raku --version " = > Some ( CommandOutput {
stdout : String ::from (
" \
Welcome to Rakudo ™ v2021 . 12.
Implementing the Raku ® Programming Language v6 . d .
Built on MoarVM version 2021.1 2. \ n " ,
) ,
stderr : String ::default ( ) ,
} ) ,
2021-04-20 16:31:47 +00:00
" red --version " = > Some ( CommandOutput {
stdout : String ::from ( " 0.6.4 \n " ) ,
stderr : String ::default ( )
} ) ,
2020-02-21 16:52:39 +00:00
" ruby -v " = > Some ( CommandOutput {
2020-07-19 21:01:53 +00:00
stdout : String ::from ( " ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux-gnu] \n " ) ,
2020-02-21 16:52:39 +00:00
stderr : String ::default ( ) ,
} ) ,
2023-04-13 19:05:12 +00:00
" solc --version " = > Some ( CommandOutput {
stdout : String ::from ( " solc, the solidity compiler commandline interface
Version : 0. 8.16 + commit . 07 a7930e . Linux . g + + " ),
stderr : String ::default ( ) ,
} ) ,
" solcjs --version " = > Some ( CommandOutput {
stdout : String ::from ( " 0.8.15+commit.e14f2714.Emscripten.clang " ) ,
stderr : String ::default ( ) } ) ,
2020-07-29 15:36:49 +00:00
" swift --version " = > Some ( CommandOutput {
stdout : String ::from (
" \
Apple Swift version 5. 2.2 ( swiftlang - 1103. 0.3 2.6 clang - 1103. 0.3 2.51 )
Target : x86_64 - apple - darwin19 . 4.0 \ n " ,
) ,
stderr : String ::default ( ) ,
} ) ,
2021-01-30 11:05:16 +00:00
" vagrant --version " = > Some ( CommandOutput {
stdout : String ::from ( " Vagrant 2.2.10 \n " ) ,
stderr : String ::default ( ) ,
} ) ,
2021-05-03 19:50:29 +00:00
" v version " = > Some ( CommandOutput {
stdout : String ::from ( " V 0.2 30c0659 " ) ,
stderr : String ::default ( )
} ) ,
2020-05-21 16:49:49 +00:00
" zig version " = > Some ( CommandOutput {
2020-07-19 21:01:53 +00:00
stdout : String ::from ( " 0.6.0 \n " ) ,
2020-05-21 16:49:49 +00:00
stderr : String ::default ( ) ,
} ) ,
2020-07-09 19:40:33 +00:00
" cmake --version " = > Some ( CommandOutput {
stdout : String ::from (
" \
cmake version 3.1 7.3
CMake suite maintained and supported by Kitware ( kitware . com / cmake ) . \ n " ,
) ,
stderr : String ::default ( ) ,
} ) ,
2020-08-07 19:13:12 +00:00
" dotnet --version " = > Some ( CommandOutput {
stdout : String ::from ( " 3.1.103 " ) ,
stderr : String ::default ( ) ,
} ) ,
" dotnet --list-sdks " = > Some ( CommandOutput {
stdout : String ::from ( " 3.1.103 [/usr/share/dotnet/sdk] " ) ,
stderr : String ::default ( ) ,
} ) ,
2020-07-09 00:24:38 +00:00
" terraform version " = > Some ( CommandOutput {
2020-07-19 21:01:53 +00:00
stdout : String ::from ( " Terraform v0.12.14 \n " ) ,
2020-07-09 00:24:38 +00:00
stderr : String ::default ( ) ,
} ) ,
2020-07-19 21:01:53 +00:00
s if s . starts_with ( " erl -noshell -eval " ) = > Some ( CommandOutput {
stdout : String ::from ( " 22.1.3 \n " ) ,
2020-04-27 10:09:42 +00:00
stderr : String ::default ( ) ,
} ) ,
2021-12-28 05:56:06 +00:00
_ = > return None ,
} ;
Some ( out )
2020-01-11 15:08:32 +00:00
}
2021-11-01 21:18:45 +00:00
/// Wraps ANSI color escape sequences in the shell-appropriate wrappers.
pub fn wrap_colorseq_for_shell ( ansi : String , shell : Shell ) -> String {
2020-02-06 16:10:59 +00:00
const ESCAPE_BEGIN : char = '\u{1b}' ;
const ESCAPE_END : char = 'm' ;
wrap_seq_for_shell ( ansi , shell , ESCAPE_BEGIN , ESCAPE_END )
}
/// Many shells cannot deal with raw unprintable characters and miscompute the cursor position,
/// leading to strange visual bugs like duplicated/missing chars. This function wraps a specified
/// sequence in shell-specific escapes to avoid these problems.
pub fn wrap_seq_for_shell (
ansi : String ,
shell : Shell ,
escape_begin : char ,
escape_end : char ,
) -> String {
const BASH_BEG : & str = " \u{5c} \u{5b} " ; // \[
const BASH_END : & str = " \u{5c} \u{5d} " ; // \]
const ZSH_BEG : & str = " \u{25} \u{7b} " ; // %{
const ZSH_END : & str = " \u{25} \u{7d} " ; // %}
2021-02-27 18:55:27 +00:00
const TCSH_BEG : & str = " \u{25} \u{7b} " ; // %{
const TCSH_END : & str = " \u{25} \u{7d} " ; // %}
2020-02-06 16:10:59 +00:00
// ANSI escape codes cannot be nested, so we can keep track of whether we're
// in an escape or not with a single boolean variable
let mut escaped = false ;
let final_string : String = ansi
. chars ( )
. map ( | x | {
if x = = escape_begin & & ! escaped {
escaped = true ;
match shell {
2022-11-05 11:40:46 +00:00
Shell ::Bash = > format! ( " {BASH_BEG} {escape_begin} " ) ,
Shell ::Zsh = > format! ( " {ZSH_BEG} {escape_begin} " ) ,
Shell ::Tcsh = > format! ( " {TCSH_BEG} {escape_begin} " ) ,
2020-02-06 16:10:59 +00:00
_ = > x . to_string ( ) ,
}
} else if x = = escape_end & & escaped {
escaped = false ;
match shell {
2022-11-05 11:40:46 +00:00
Shell ::Bash = > format! ( " {escape_end} {BASH_END} " ) ,
Shell ::Zsh = > format! ( " {escape_end} {ZSH_END} " ) ,
Shell ::Tcsh = > format! ( " {escape_end} {TCSH_END} " ) ,
2020-02-06 16:10:59 +00:00
_ = > x . to_string ( ) ,
}
} else {
x . to_string ( )
}
} )
. collect ( ) ;
final_string
}
2021-08-23 16:49:30 +00:00
fn internal_exec_cmd < T : AsRef < OsStr > + Debug , U : AsRef < OsStr > + Debug > (
cmd : T ,
args : & [ U ] ,
time_limit : Duration ,
) -> Option < CommandOutput > {
2021-12-28 05:56:06 +00:00
let mut cmd = create_command ( cmd ) . ok ( ) ? ;
cmd . args ( args ) ;
exec_timeout ( & mut cmd , time_limit )
}
2020-12-22 16:44:38 +00:00
2021-12-28 05:56:06 +00:00
pub fn exec_timeout ( cmd : & mut Command , time_limit : Duration ) -> Option < CommandOutput > {
2020-12-22 16:44:38 +00:00
let start = Instant ::now ( ) ;
2021-12-28 05:56:06 +00:00
let process = match cmd . spawn ( ) {
2021-01-21 21:59:14 +00:00
Ok ( process ) = > process ,
Err ( error ) = > {
2021-12-28 05:56:06 +00:00
log ::info! ( " Unable to run {:?}, {:?} " , cmd . get_program ( ) , error ) ;
2021-01-21 21:59:14 +00:00
return None ;
}
} ;
2022-01-03 14:14:19 +00:00
match process
. controlled_with_output ( )
. time_limit ( time_limit )
. terminate_for_timeout ( )
. wait ( )
{
2021-01-21 21:59:14 +00:00
Ok ( Some ( output ) ) = > {
2021-02-13 13:35:15 +00:00
let stdout_string = match String ::from_utf8 ( output . stdout ) {
Ok ( stdout ) = > stdout ,
Err ( error ) = > {
log ::warn! ( " Unable to decode stdout: {:?} " , error ) ;
return None ;
}
} ;
let stderr_string = match String ::from_utf8 ( output . stderr ) {
Ok ( stderr ) = > stderr ,
Err ( error ) = > {
log ::warn! ( " Unable to decode stderr: {:?} " , error ) ;
return None ;
}
} ;
2019-11-27 22:03:08 +00:00
2020-12-22 16:44:38 +00:00
log ::trace! (
" stdout: {:?}, stderr: {:?}, exit code: \" {:?} \" , took {:?} " ,
stdout_string ,
stderr_string ,
output . status . code ( ) ,
start . elapsed ( )
) ;
2020-04-13 13:22:28 +00:00
2019-11-27 22:03:08 +00:00
if ! output . status . success ( ) {
return None ;
}
Some ( CommandOutput {
stdout : stdout_string ,
stderr : stderr_string ,
} )
}
2021-01-21 21:59:14 +00:00
Ok ( None ) = > {
2021-12-28 05:56:06 +00:00
log ::warn! ( " Executing command {:?} timed out. " , cmd . get_program ( ) ) ;
2021-02-11 20:34:47 +00:00
log ::warn! ( " You can set command_timeout in your config to a higher value to allow longer-running commands to keep executing. " ) ;
2021-01-21 21:59:14 +00:00
None
}
2020-03-06 21:17:34 +00:00
Err ( error ) = > {
2021-12-28 05:56:06 +00:00
log ::info! (
" Executing command {:?} failed by: {:?} " ,
cmd . get_program ( ) ,
error
) ;
2020-03-06 21:17:34 +00:00
None
}
2019-11-27 22:03:08 +00:00
}
}
2021-05-13 00:43:46 +00:00
// Render the time into a nice human-readable string
pub fn render_time ( raw_millis : u128 , show_millis : bool ) -> String {
2021-12-20 21:58:25 +00:00
// Make sure it renders something if the time equals zero instead of an empty string
if raw_millis = = 0 {
return " 0ms " . into ( ) ;
}
2021-05-13 00:43:46 +00:00
// Calculate a simple breakdown into days/hours/minutes/seconds/milliseconds
let ( millis , raw_seconds ) = ( raw_millis % 1000 , raw_millis / 1000 ) ;
let ( seconds , raw_minutes ) = ( raw_seconds % 60 , raw_seconds / 60 ) ;
let ( minutes , raw_hours ) = ( raw_minutes % 60 , raw_minutes / 60 ) ;
let ( hours , days ) = ( raw_hours % 24 , raw_hours / 24 ) ;
let components = [ days , hours , minutes , seconds ] ;
let suffixes = [ " d " , " h " , " m " , " s " ] ;
let mut rendered_components : Vec < String > = components
. iter ( )
. zip ( & suffixes )
. map ( render_time_component )
. collect ( ) ;
if show_millis | | raw_millis < 1000 {
rendered_components . push ( render_time_component ( ( & millis , & " ms " ) ) ) ;
}
rendered_components . join ( " " )
}
/// Render a single component of the time string, giving an empty string if component is zero
fn render_time_component ( ( component , suffix ) : ( & u128 , & & str ) ) -> String {
match component {
0 = > String ::new ( ) ,
2022-11-05 11:40:46 +00:00
n = > format! ( " {n} {suffix} " ) ,
2021-05-13 00:43:46 +00:00
}
}
2021-07-04 19:32:58 +00:00
pub fn home_dir ( ) -> Option < PathBuf > {
2022-03-24 19:06:24 +00:00
dirs_next ::home_dir ( )
2021-07-04 19:32:58 +00:00
}
2021-10-05 23:27:25 +00:00
const HEXTABLE : & [ char ] = & [
'0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' ,
] ;
/// Encode a u8 slice into a hexadecimal string.
pub fn encode_to_hex ( slice : & [ u8 ] ) -> String {
// let mut j = 0;
let mut dst = Vec ::with_capacity ( slice . len ( ) * 2 ) ;
for & v in slice {
dst . push ( HEXTABLE [ ( v > > 4 ) as usize ] as u8 ) ;
dst . push ( HEXTABLE [ ( v & 0x0f ) as usize ] as u8 ) ;
}
String ::from_utf8 ( dst ) . unwrap ( )
}
2023-04-02 14:37:27 +00:00
pub trait PathExt {
/// Get device / volume info
fn device_id ( & self ) -> Option < u64 > ;
}
#[ cfg(windows) ]
impl PathExt for Path {
fn device_id ( & self ) -> Option < u64 > {
// Maybe it should use unimplemented!
Some ( 42 u64 )
}
}
#[ cfg(not(windows)) ]
impl PathExt for Path {
#[ cfg(target_os = " linux " ) ]
fn device_id ( & self ) -> Option < u64 > {
use std ::os ::linux ::fs ::MetadataExt ;
match self . metadata ( ) {
Ok ( m ) = > Some ( m . st_dev ( ) ) ,
Err ( _ ) = > None ,
}
}
#[ cfg(all(unix, not(target_os = " linux " ))) ]
fn device_id ( & self ) -> Option < u64 > {
use std ::os ::unix ::fs ::MetadataExt ;
match self . metadata ( ) {
Ok ( m ) = > Some ( m . dev ( ) ) ,
Err ( _ ) = > None ,
}
}
}
2019-11-27 22:03:08 +00:00
#[ cfg(test) ]
mod tests {
use super ::* ;
2021-12-20 21:58:25 +00:00
#[ test ]
fn test_0ms ( ) {
assert_eq! ( render_time ( 0_ u128 , true ) , " 0ms " )
}
2021-05-13 00:43:46 +00:00
#[ test ]
fn test_500ms ( ) {
assert_eq! ( render_time ( 500_ u128 , true ) , " 500ms " )
}
#[ test ]
fn test_10s ( ) {
assert_eq! ( render_time ( 10_000_ u128 , true ) , " 10s " )
}
#[ test ]
fn test_90s ( ) {
assert_eq! ( render_time ( 90_000_ u128 , true ) , " 1m30s " )
}
#[ test ]
fn test_10110s ( ) {
assert_eq! ( render_time ( 10_110_000_ u128 , true ) , " 2h48m30s " )
}
#[ test ]
fn test_1d ( ) {
assert_eq! ( render_time ( 86_400_000_ u128 , true ) , " 1d " )
}
2020-01-11 15:08:32 +00:00
#[ test ]
fn exec_mocked_command ( ) {
2021-08-23 16:49:30 +00:00
let result = exec_cmd (
" dummy_command " ,
& [ ] as & [ & OsStr ] ,
Duration ::from_millis ( 500 ) ,
) ;
2020-01-11 15:08:32 +00:00
let expected = Some ( CommandOutput {
2020-07-19 21:01:53 +00:00
stdout : String ::from ( " stdout ok! \n " ) ,
stderr : String ::from ( " stderr ok! \n " ) ,
2020-01-11 15:08:32 +00:00
} ) ;
assert_eq! ( result , expected )
}
2021-01-21 21:59:14 +00:00
// While the exec_cmd should work on Windows some of these tests assume a Unix-like
// environment.
2019-11-27 22:03:08 +00:00
#[ test ]
2021-01-21 21:59:14 +00:00
#[ cfg(not(windows)) ]
2019-11-27 22:03:08 +00:00
fn exec_no_output ( ) {
2021-08-23 16:49:30 +00:00
let result = internal_exec_cmd ( " true " , & [ ] as & [ & OsStr ] , Duration ::from_millis ( 500 ) ) ;
2019-11-27 22:03:08 +00:00
let expected = Some ( CommandOutput {
2022-12-17 17:01:27 +00:00
stdout : String ::new ( ) ,
stderr : String ::new ( ) ,
2019-11-27 22:03:08 +00:00
} ) ;
assert_eq! ( result , expected )
}
#[ test ]
2021-01-21 21:59:14 +00:00
#[ cfg(not(windows)) ]
2019-11-27 22:03:08 +00:00
fn exec_with_output_stdout ( ) {
2021-02-11 20:34:47 +00:00
let result =
internal_exec_cmd ( " /bin/sh " , & [ " -c " , " echo hello " ] , Duration ::from_millis ( 500 ) ) ;
2019-11-27 22:03:08 +00:00
let expected = Some ( CommandOutput {
2020-02-04 17:48:01 +00:00
stdout : String ::from ( " hello \n " ) ,
2022-12-17 17:01:27 +00:00
stderr : String ::new ( ) ,
2019-11-27 22:03:08 +00:00
} ) ;
assert_eq! ( result , expected )
}
#[ test ]
2021-01-21 21:59:14 +00:00
#[ cfg(not(windows)) ]
2019-11-27 22:03:08 +00:00
fn exec_with_output_stderr ( ) {
2021-02-11 20:34:47 +00:00
let result = internal_exec_cmd (
" /bin/sh " ,
& [ " -c " , " echo hello >&2 " ] ,
Duration ::from_millis ( 500 ) ,
) ;
2019-11-27 22:03:08 +00:00
let expected = Some ( CommandOutput {
2022-12-17 17:01:27 +00:00
stdout : String ::new ( ) ,
2019-11-27 22:03:08 +00:00
stderr : String ::from ( " hello \n " ) ,
} ) ;
assert_eq! ( result , expected )
}
#[ test ]
2021-01-21 21:59:14 +00:00
#[ cfg(not(windows)) ]
2019-11-27 22:03:08 +00:00
fn exec_with_output_both ( ) {
2021-02-11 20:34:47 +00:00
let result = internal_exec_cmd (
" /bin/sh " ,
& [ " -c " , " echo hello; echo world >&2 " ] ,
Duration ::from_millis ( 500 ) ,
) ;
2019-11-27 22:03:08 +00:00
let expected = Some ( CommandOutput {
stdout : String ::from ( " hello \n " ) ,
stderr : String ::from ( " world \n " ) ,
} ) ;
assert_eq! ( result , expected )
}
#[ test ]
2021-01-21 21:59:14 +00:00
#[ cfg(not(windows)) ]
2019-11-27 22:03:08 +00:00
fn exec_with_non_zero_exit_code ( ) {
2021-08-23 16:49:30 +00:00
let result = internal_exec_cmd ( " false " , & [ ] as & [ & OsStr ] , Duration ::from_millis ( 500 ) ) ;
2019-11-27 22:03:08 +00:00
let expected = None ;
2021-01-21 21:59:14 +00:00
assert_eq! ( result , expected )
}
#[ test ]
#[ cfg(not(windows)) ]
fn exec_slow_command ( ) {
2021-02-11 20:34:47 +00:00
let result = internal_exec_cmd ( " sleep " , & [ " 500 " ] , Duration ::from_millis ( 500 ) ) ;
2021-01-21 21:59:14 +00:00
let expected = None ;
2019-11-27 22:03:08 +00:00
assert_eq! ( result , expected )
}
2020-02-06 16:10:59 +00:00
#[ test ]
fn test_color_sequence_wrappers ( ) {
let test0 = " \x1b 2mhellomynamekeyes \x1b 2m " ; // BEGIN: \x1b END: m
let test1 = " \x1b ]330;mlol \x1b ]0m " ; // BEGIN: \x1b END: m
let test2 = " \u{1b} J " ; // BEGIN: \x1b END: J
let test3 = " OH NO " ; // BEGIN: O END: O
let test4 = " herpaderp " ;
let test5 = " " ;
let zresult0 = wrap_seq_for_shell ( test0 . to_string ( ) , Shell ::Zsh , '\x1b' , 'm' ) ;
let zresult1 = wrap_seq_for_shell ( test1 . to_string ( ) , Shell ::Zsh , '\x1b' , 'm' ) ;
let zresult2 = wrap_seq_for_shell ( test2 . to_string ( ) , Shell ::Zsh , '\x1b' , 'J' ) ;
let zresult3 = wrap_seq_for_shell ( test3 . to_string ( ) , Shell ::Zsh , 'O' , 'O' ) ;
let zresult4 = wrap_seq_for_shell ( test4 . to_string ( ) , Shell ::Zsh , '\x1b' , 'm' ) ;
let zresult5 = wrap_seq_for_shell ( test5 . to_string ( ) , Shell ::Zsh , '\x1b' , 'm' ) ;
assert_eq! ( & zresult0 , " %{ \x1b 2m%}hellomynamekeyes%{ \x1b 2m%} " ) ;
assert_eq! ( & zresult1 , " %{ \x1b ]330;m%}lol%{ \x1b ]0m%} " ) ;
assert_eq! ( & zresult2 , " %{ \x1b J%} " ) ;
assert_eq! ( & zresult3 , " %{OH NO%} " ) ;
assert_eq! ( & zresult4 , " herpaderp " ) ;
assert_eq! ( & zresult5 , " " ) ;
let bresult0 = wrap_seq_for_shell ( test0 . to_string ( ) , Shell ::Bash , '\x1b' , 'm' ) ;
let bresult1 = wrap_seq_for_shell ( test1 . to_string ( ) , Shell ::Bash , '\x1b' , 'm' ) ;
let bresult2 = wrap_seq_for_shell ( test2 . to_string ( ) , Shell ::Bash , '\x1b' , 'J' ) ;
let bresult3 = wrap_seq_for_shell ( test3 . to_string ( ) , Shell ::Bash , 'O' , 'O' ) ;
let bresult4 = wrap_seq_for_shell ( test4 . to_string ( ) , Shell ::Bash , '\x1b' , 'm' ) ;
let bresult5 = wrap_seq_for_shell ( test5 . to_string ( ) , Shell ::Bash , '\x1b' , 'm' ) ;
assert_eq! ( & bresult0 , " \\ [ \x1b 2m \\ ]hellomynamekeyes \\ [ \x1b 2m \\ ] " ) ;
assert_eq! ( & bresult1 , " \\ [ \x1b ]330;m \\ ]lol \\ [ \x1b ]0m \\ ] " ) ;
assert_eq! ( & bresult2 , " \\ [ \x1b J \\ ] " ) ;
assert_eq! ( & bresult3 , " \\ [OH NO \\ ] " ) ;
assert_eq! ( & bresult4 , " herpaderp " ) ;
assert_eq! ( & bresult5 , " " ) ;
}
2021-03-05 07:47:07 +00:00
2021-09-12 23:55:46 +00:00
#[ test ]
2021-08-02 17:13:29 +00:00
fn test_get_command_string_output ( ) {
let case1 = CommandOutput {
stdout : String ::from ( " stdout " ) ,
stderr : String ::from ( " stderr " ) ,
} ;
assert_eq! ( get_command_string_output ( case1 ) , " stdout " ) ;
let case2 = CommandOutput {
2022-11-05 11:40:46 +00:00
stdout : String ::new ( ) ,
2021-08-02 17:13:29 +00:00
stderr : String ::from ( " stderr " ) ,
} ;
assert_eq! ( get_command_string_output ( case2 ) , " stderr " ) ;
}
2021-10-05 23:27:25 +00:00
#[ test ]
fn sha1_hex ( ) {
assert_eq! (
encode_to_hex ( & [ 8 , 13 , 9 , 189 , 129 , 94 ] ) ,
" 080d09bd815e " . to_string ( )
) ;
}
2019-11-27 22:03:08 +00:00
}