2019-06-10 14:56:17 +00:00
use std ::fs ::File ;
use std ::io ::{ Read , Result } ;
2019-09-05 16:45:04 +00:00
use std ::path ::Path ;
2019-11-27 22:03:08 +00:00
use std ::process ::Command ;
2019-06-10 14:56:17 +00:00
2020-02-06 16:10:59 +00:00
use crate ::context ::Shell ;
2019-06-10 14:56:17 +00:00
/// Return the string contents of a file
2019-09-05 16:45:04 +00:00
pub fn read_file < P : AsRef < Path > > ( file_name : P ) -> Result < String > {
2019-06-10 14:56:17 +00:00
let mut file = File ::open ( file_name ) ? ;
let mut data = String ::new ( ) ;
file . read_to_string ( & mut data ) ? ;
Ok ( data )
}
2019-11-27 22:03:08 +00:00
#[ derive(Debug) ]
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
}
}
2020-08-07 19:13:12 +00:00
/// Execute a command and return the output on stdout and stderr if successful
2020-01-11 15:08:32 +00:00
#[ cfg(not(test)) ]
2019-11-27 22:03:08 +00:00
pub fn exec_cmd ( cmd : & str , args : & [ & str ] ) -> Option < CommandOutput > {
2020-01-11 15:08:32 +00:00
internal_exec_cmd ( & cmd , & args )
}
#[ cfg(test) ]
pub fn exec_cmd ( cmd : & str , args : & [ & str ] ) -> Option < CommandOutput > {
let command = match args . len ( ) {
0 = > String ::from ( cmd ) ,
_ = > format! ( " {} {} " , cmd , args . join ( " " ) ) ,
} ;
match command . as_str ( ) {
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 \" " ,
) ,
} ) ,
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
} ) ,
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
} ) ,
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 ( ) ,
} ) ,
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-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
} ) ,
" 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 ( ) ,
} )
}
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-06-14 09:27:10 +00:00
" python --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 ( ) ,
} ) ,
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 ( ) ,
} ) ,
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 ( ) ,
} ) ,
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 ( ) ,
} ) ,
2020-01-11 15:08:32 +00:00
// If we don't have a mocked command fall back to executing the command
_ = > internal_exec_cmd ( & cmd , & args ) ,
}
}
2020-02-06 16:10:59 +00:00
/// Wraps ANSI color escape sequences in the shell-appropriate wrappers.
pub fn wrap_colorseq_for_shell ( ansi : String , shell : Shell ) -> String {
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} " ; // %}
// 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 {
Shell ::Bash = > format! ( " {} {} " , BASH_BEG , escape_begin ) ,
Shell ::Zsh = > format! ( " {} {} " , ZSH_BEG , escape_begin ) ,
_ = > x . to_string ( ) ,
}
} else if x = = escape_end & & escaped {
escaped = false ;
match shell {
Shell ::Bash = > format! ( " {} {} " , escape_end , BASH_END ) ,
Shell ::Zsh = > format! ( " {} {} " , escape_end , ZSH_END ) ,
_ = > x . to_string ( ) ,
}
} else {
x . to_string ( )
}
} )
. collect ( ) ;
final_string
}
2020-01-11 15:08:32 +00:00
fn internal_exec_cmd ( cmd : & str , args : & [ & str ] ) -> Option < CommandOutput > {
2020-04-13 13:22:28 +00:00
log ::trace! ( " Executing command {:?} with args {:?} " , cmd , args ) ;
2019-11-27 22:03:08 +00:00
match Command ::new ( cmd ) . args ( args ) . output ( ) {
Ok ( output ) = > {
let stdout_string = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let stderr_string = String ::from_utf8 ( output . stderr ) . unwrap ( ) ;
2020-04-13 13:22:28 +00:00
log ::trace! ( " stdout: {:?} " , stdout_string ) ;
log ::trace! ( " stderr: {:?} " , stderr_string ) ;
log ::trace! ( " exit code: \" {:?} \" " , output . status . code ( ) ) ;
2019-11-27 22:03:08 +00:00
if ! output . status . success ( ) {
return None ;
}
Some ( CommandOutput {
stdout : stdout_string ,
stderr : stderr_string ,
} )
}
2020-03-06 21:17:34 +00:00
Err ( error ) = > {
2020-03-22 21:56:18 +00:00
log ::trace! ( " Executing command {:?} failed by: {:?} " , cmd , error ) ;
2020-03-06 21:17:34 +00:00
None
}
2019-11-27 22:03:08 +00:00
}
}
#[ cfg(test) ]
#[ cfg(not(windows)) ] // While the exec_cmd should work on Windows these tests assume a Unix-like environment.
mod tests {
use super ::* ;
2020-01-11 15:08:32 +00:00
#[ test ]
fn exec_mocked_command ( ) {
let result = exec_cmd ( " dummy_command " , & [ ] ) ;
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 )
}
2019-11-27 22:03:08 +00:00
#[ test ]
fn exec_no_output ( ) {
2020-01-11 15:08:32 +00:00
let result = internal_exec_cmd ( " true " , & [ ] ) ;
2019-11-27 22:03:08 +00:00
let expected = Some ( CommandOutput {
stdout : String ::from ( " " ) ,
stderr : String ::from ( " " ) ,
} ) ;
assert_eq! ( result , expected )
}
#[ test ]
fn exec_with_output_stdout ( ) {
2020-02-04 17:48:01 +00:00
let result = internal_exec_cmd ( " /bin/sh " , & [ " -c " , " echo hello " ] ) ;
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 " ) ,
2019-11-27 22:03:08 +00:00
stderr : String ::from ( " " ) ,
} ) ;
assert_eq! ( result , expected )
}
#[ test ]
fn exec_with_output_stderr ( ) {
2020-01-11 15:08:32 +00:00
let result = internal_exec_cmd ( " /bin/sh " , & [ " -c " , " echo hello >&2 " ] ) ;
2019-11-27 22:03:08 +00:00
let expected = Some ( CommandOutput {
stdout : String ::from ( " " ) ,
stderr : String ::from ( " hello \n " ) ,
} ) ;
assert_eq! ( result , expected )
}
#[ test ]
fn exec_with_output_both ( ) {
2020-01-11 15:08:32 +00:00
let result = internal_exec_cmd ( " /bin/sh " , & [ " -c " , " echo hello; echo world >&2 " ] ) ;
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 ]
fn exec_with_non_zero_exit_code ( ) {
2020-01-11 15:08:32 +00:00
let result = internal_exec_cmd ( " false " , & [ ] ) ;
2019-11-27 22:03:08 +00:00
let expected = None ;
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 , " " ) ;
}
2019-11-27 22:03:08 +00:00
}