2021-08-07 10:41:19 +00:00
use crate ::env_var ;
2021-04-05 12:58:55 +00:00
pub use crate ::fs ::{
create_dir , exe_string , pathbuf_to_string , read_lines , remove_dir , InvalidPath , UnreadableDir ,
} ;
use crate ::parser ;
2020-08-27 18:15:09 +00:00
use crate ::structures ::cheat ::VariableMap ;
2021-04-05 12:58:55 +00:00
use crate ::structures ::fetcher ;
2021-04-16 11:29:04 +00:00
use anyhow ::Result ;
2021-04-05 12:58:55 +00:00
use directories_next ::BaseDirs ;
2021-06-15 12:43:40 +00:00
use regex ::Regex ;
2021-04-05 12:58:55 +00:00
use std ::collections ::HashSet ;
2021-04-06 12:45:46 +00:00
use std ::path ::{ Path , PathBuf } ;
2021-04-05 12:58:55 +00:00
use walkdir ::WalkDir ;
2021-04-06 12:45:46 +00:00
pub fn all_cheat_files ( path : & Path ) -> Vec < String > {
WalkDir ::new ( & path )
2021-04-05 12:58:55 +00:00
. follow_links ( true )
. into_iter ( )
. filter_map ( | e | e . ok ( ) )
. map ( | e | e . path ( ) . to_str ( ) . unwrap_or ( " " ) . to_string ( ) )
. filter ( | e | e . ends_with ( " .cheat " ) )
. collect ::< Vec < String > > ( )
}
fn paths_from_path_param ( env_var : & str ) -> impl Iterator < Item = & str > {
env_var . split ( ':' ) . filter ( | folder | folder ! = & " " )
}
2021-04-16 11:29:04 +00:00
pub fn default_cheat_pathbuf ( ) -> Result < PathBuf > {
2021-04-05 12:58:55 +00:00
let base_dirs = BaseDirs ::new ( ) . ok_or_else ( | | anyhow! ( " Unable to get base dirs " ) ) ? ;
let mut pathbuf = PathBuf ::from ( base_dirs . data_dir ( ) ) ;
pathbuf . push ( " navi " ) ;
pathbuf . push ( " cheats " ) ;
Ok ( pathbuf )
}
2021-04-17 13:17:22 +00:00
pub fn default_config_pathbuf ( ) -> Result < PathBuf > {
let base_dirs = BaseDirs ::new ( ) . ok_or_else ( | | anyhow! ( " Unable to get base dirs " ) ) ? ;
let mut pathbuf = PathBuf ::from ( base_dirs . config_dir ( ) ) ;
pathbuf . push ( " navi " ) ;
pathbuf . push ( " config.yaml " ) ;
Ok ( pathbuf )
}
2021-04-16 11:29:04 +00:00
pub fn cheat_paths ( path : Option < String > ) -> Result < String > {
2021-04-05 12:58:55 +00:00
if let Some ( p ) = path {
Ok ( p )
} else {
2021-04-06 12:45:46 +00:00
pathbuf_to_string ( & default_cheat_pathbuf ( ) ? )
2021-04-05 12:58:55 +00:00
}
}
2021-04-16 11:29:04 +00:00
pub fn tmp_pathbuf ( ) -> Result < PathBuf > {
2021-04-06 12:45:46 +00:00
let mut root = default_cheat_pathbuf ( ) ? ;
root . push ( " tmp " ) ;
Ok ( root )
}
2021-04-15 13:49:23 +00:00
fn without_first ( string : & str ) -> String {
string
. char_indices ( )
. next ( )
. and_then ( | ( i , _ ) | string . get ( i + 1 .. ) )
. expect ( " Should have at least one char " )
. to_string ( )
}
2021-08-09 13:23:07 +00:00
fn interpolate_paths ( paths : String ) -> String {
2021-08-07 10:41:19 +00:00
let re = Regex ::new ( r # "\$\{?[a-zA-Z_][a-zA-Z_0-9]*"# ) . unwrap ( ) ;
let mut newtext = paths . to_string ( ) ;
for capture in re . captures_iter ( & paths ) {
if let Some ( c ) = capture . get ( 0 ) {
2021-12-19 20:00:11 +00:00
let varname = c . as_str ( ) . replace ( '$' , " " ) . replace ( '{' , " " ) . replace ( '}' , " " ) ;
2021-08-09 13:23:07 +00:00
if let Ok ( replacement ) = & env_var ::get ( & varname ) {
newtext = newtext
. replace ( & format! ( " $ {} " , varname ) , replacement )
. replace ( & format! ( " $ {{ {} }} " , varname ) , replacement ) ;
}
2021-08-07 10:41:19 +00:00
}
}
2021-08-09 13:23:07 +00:00
newtext
2021-08-07 10:41:19 +00:00
}
2021-04-15 13:49:23 +00:00
fn gen_lists ( tag_rules : Option < String > ) -> ( Option < Vec < String > > , Option < Vec < String > > ) {
let mut allowlist = None ;
let mut denylist : Option < Vec < String > > = None ;
if let Some ( rules ) = tag_rules {
let words : Vec < _ > = rules . split ( ',' ) . collect ( ) ;
allowlist = Some (
words
. iter ( )
. filter ( | w | ! w . starts_with ( '!' ) )
. map ( | w | w . to_string ( ) )
. collect ( ) ,
) ;
denylist = Some (
words
. iter ( )
. filter ( | w | w . starts_with ( '!' ) )
. map ( | w | without_first ( w ) )
. collect ( ) ,
) ;
}
( allowlist , denylist )
}
2021-04-06 12:45:46 +00:00
pub struct Fetcher {
path : Option < String > ,
2021-04-15 13:49:23 +00:00
allowlist : Option < Vec < String > > ,
denylist : Option < Vec < String > > ,
2021-04-06 12:45:46 +00:00
}
impl Fetcher {
2021-04-15 13:49:23 +00:00
pub fn new ( path : Option < String > , tag_rules : Option < String > ) -> Self {
let ( allowlist , denylist ) = gen_lists ( tag_rules ) ;
Self {
path ,
allowlist ,
denylist ,
}
2021-04-06 12:45:46 +00:00
}
}
impl fetcher ::Fetcher for Fetcher {
fn fetch (
& self ,
stdin : & mut std ::process ::ChildStdin ,
files : & mut Vec < String > ,
2021-04-16 11:29:04 +00:00
) -> Result < Option < VariableMap > > {
2021-04-15 13:49:23 +00:00
let mut variables = VariableMap ::new ( ) ;
let mut found_something = false ;
let mut visited_lines = HashSet ::new ( ) ;
let path = self . path . clone ( ) ;
let paths = cheat_paths ( path ) ;
if paths . is_err ( ) {
return Ok ( None ) ;
} ;
let paths = paths . expect ( " Unable to get paths " ) ;
2021-08-09 13:23:07 +00:00
let interpolated_paths = interpolate_paths ( paths ) ;
2021-08-07 10:41:19 +00:00
let folders = paths_from_path_param ( & interpolated_paths ) ;
2021-04-15 13:49:23 +00:00
2021-06-15 12:43:40 +00:00
let home_regex = Regex ::new ( r "^~" ) . unwrap ( ) ;
let home = BaseDirs ::new ( ) . and_then ( | b | pathbuf_to_string ( b . home_dir ( ) ) . ok ( ) ) ;
2021-04-15 13:49:23 +00:00
for folder in folders {
2021-06-15 12:43:40 +00:00
let interpolated_folder = match & home {
Some ( h ) = > home_regex . replace ( folder , h ) . to_string ( ) ,
None = > folder . to_string ( ) ,
} ;
let folder_pathbuf = PathBuf ::from ( interpolated_folder ) ;
2021-04-15 13:49:23 +00:00
for file in all_cheat_files ( & folder_pathbuf ) {
files . push ( file . clone ( ) ) ;
let index = files . len ( ) - 1 ;
let read_file_result = {
2021-04-17 13:17:22 +00:00
let path = PathBuf ::from ( & file ) ;
let lines = read_lines ( & path ) ? ;
2021-04-15 13:49:23 +00:00
parser ::read_lines (
lines ,
& file ,
index ,
& mut variables ,
& mut visited_lines ,
stdin ,
self . allowlist . as_ref ( ) ,
self . denylist . as_ref ( ) ,
)
} ;
if read_file_result . is_ok ( ) & & ! found_something {
found_something = true
}
}
}
if ! found_something {
return Ok ( None ) ;
}
Ok ( Some ( variables ) )
2021-04-06 12:45:46 +00:00
}
}
2021-04-05 12:58:55 +00:00
#[ cfg(test) ]
mod tests {
use super ::* ;
2021-04-15 13:49:23 +00:00
/* TODO
2021-04-05 12:58:55 +00:00
use crate ::finder ::structures ::{ Opts as FinderOpts , SuggestionType } ;
2021-04-05 13:00:48 +00:00
use crate ::writer ;
2021-04-05 12:58:55 +00:00
use std ::process ::{ Command , Stdio } ;
#[ test ]
fn test_read_file ( ) {
let path = " tests/cheats/ssh.cheat " ;
let mut variables = VariableMap ::new ( ) ;
let mut child = Command ::new ( " cat " )
. stdin ( Stdio ::piped ( ) )
. stdout ( Stdio ::null ( ) )
. spawn ( )
. unwrap ( ) ;
let child_stdin = child . stdin . as_mut ( ) . unwrap ( ) ;
let mut visited_lines : HashSet < u64 > = HashSet ::new ( ) ;
2021-04-05 13:00:48 +00:00
let mut writer : Box < dyn Writer > = Box ::new ( writer ::terminal ::Writer ::new ( ) ) ;
2021-04-05 12:58:55 +00:00
read_file (
path ,
0 ,
& mut variables ,
& mut visited_lines ,
& mut * writer ,
child_stdin ,
)
. unwrap ( ) ;
let expected_suggestion = (
r # " echo -e "$(whoami)\nroot" "# . to_string ( ) ,
Some ( FinderOpts {
header_lines : 0 ,
column : None ,
delimiter : None ,
suggestion_type : SuggestionType ::SingleSelection ,
.. Default ::default ( )
} ) ,
) ;
let actual_suggestion = variables . get_suggestion ( " ssh " , " user " ) ;
assert_eq! ( Some ( & expected_suggestion ) , actual_suggestion ) ;
}
2021-04-15 13:49:23 +00:00
* /
2021-04-05 12:58:55 +00:00
#[ test ]
fn splitting_of_dirs_param_may_not_contain_empty_items ( ) {
// Trailing colon indicates potential extra path. Split returns an empty item for it. This empty item should be filtered away, which is what this test checks.
let given_path_config = " SOME_PATH:ANOTHER_PATH: " ;
let found_paths = paths_from_path_param ( given_path_config ) ;
let mut expected_paths = vec! [ " SOME_PATH " , " ANOTHER_PATH " ] . into_iter ( ) ;
for found in found_paths {
let expected = expected_paths . next ( ) . unwrap ( ) ;
assert_eq! ( found , expected )
}
}
}