66
77//! Configuration of the design hierarchy and other settings
88
9- use crate :: data:: * ;
10- use fnv:: FnvHashMap ;
119use std:: env;
10+ use std:: env:: VarError ;
1211use std:: fs:: File ;
1312use std:: io;
1413use std:: io:: prelude:: * ;
1514use std:: path:: Path ;
15+
16+ use fnv:: FnvHashMap ;
1617use toml:: Value ;
1718
19+ use crate :: data:: * ;
20+
1821#[ derive( Clone , PartialEq , Eq , Default , Debug ) ]
1922pub struct Config {
2023 // A map from library name to file name
@@ -131,6 +134,17 @@ impl Config {
131134 . as_str ( )
132135 . ok_or_else ( || format ! ( "not a string {file}" ) ) ?;
133136
137+ let file = substitute_environment_variables ( file, |v| std:: env:: var ( v) ) . map_err (
138+ |( var, e) | match e {
139+ VarError :: NotPresent => {
140+ format ! ( "environment variable '{var}' is not defined" )
141+ }
142+ VarError :: NotUnicode ( _) => format ! (
143+ "environment variable '{var}' does not contain valid unicode data"
144+ ) ,
145+ } ,
146+ ) ?;
147+
134148 let path = parent. join ( file) ;
135149 let path = path
136150 . to_str ( )
@@ -278,6 +292,37 @@ impl Config {
278292 }
279293}
280294
295+ fn substitute_environment_variables (
296+ s : & str ,
297+ lookup : impl Fn ( & str ) -> Result < String , VarError > ,
298+ ) -> Result < String , ( String , VarError ) > {
299+ let mut result = String :: new ( ) ;
300+
301+ let mut left = s;
302+ while let Some ( start) = left. find ( '$' ) {
303+ // keep non-env var piece as-is
304+ result. push_str ( & left[ ..start] ) ;
305+ left = & left[ start + 1 ..] ;
306+
307+ // replace env var
308+ let env_name_len = left
309+ . find ( |c : char | !( c == '_' || c. is_ascii_alphanumeric ( ) ) )
310+ . unwrap_or ( left. len ( ) ) ;
311+ let env_name = & left[ ..env_name_len] ;
312+
313+ let replacement = lookup ( env_name) . map_err ( |e| ( env_name. to_owned ( ) , e) ) ?;
314+ result. push_str ( & replacement) ;
315+
316+ // skip past env var
317+ left = & left[ env_name_len..] ;
318+ }
319+
320+ // keep remaining string
321+ result. push_str ( left) ;
322+
323+ Ok ( result)
324+ }
325+
281326/// Returns true if the pattern is a plain file name and not a glob pattern
282327fn is_literal ( pattern : & str ) -> bool {
283328 for chr in pattern. chars ( ) {
@@ -293,9 +338,13 @@ fn is_literal(pattern: &str) -> bool {
293338
294339#[ cfg( test) ]
295340mod tests {
296- use super :: * ;
341+ use std:: collections:: HashMap ;
342+ use std:: ffi:: OsString ;
343+
297344 use pretty_assertions:: assert_eq;
298345
346+ use super :: * ;
347+
299348 /// Utility function to create an empty file in parent folder
300349 fn touch ( parent : & Path , file_name : & str ) -> PathBuf {
301350 let path = parent. join ( file_name) ;
@@ -501,6 +550,7 @@ lib.files = [
501550 assert_files_eq ( & file_names, & [ file1, file2] ) ;
502551 assert_eq ! ( messages, vec![ ] ) ;
503552 }
553+
504554 #[ test]
505555 fn test_warning_on_emtpy_glob_pattern ( ) {
506556 let parent = Path :: new ( "parent_folder" ) ;
@@ -541,4 +591,69 @@ work.files = [
541591 ) ;
542592 assert_eq ! ( config. expect_err( "Expected erroneous config" ) , "The 'work' library is not a valid library.\n Hint: To use a library that contains all files, use a common name for all libraries, i.e., 'defaultlib'" )
543593 }
594+
595+ #[ test]
596+ fn substitute ( ) {
597+ let mut map = HashMap :: new ( ) ;
598+ map. insert ( "A" . to_owned ( ) , Ok ( "a" . to_owned ( ) ) ) ;
599+ map. insert ( "ABCD" . to_owned ( ) , Ok ( "abcd" . to_owned ( ) ) ) ;
600+ map. insert ( "A_0" . to_owned ( ) , Ok ( "a0" . to_owned ( ) ) ) ;
601+ map. insert ( "_" . to_owned ( ) , Ok ( "u" . to_owned ( ) ) ) ;
602+ map. insert ( "PATH" . to_owned ( ) , Ok ( "some/path" . to_owned ( ) ) ) ;
603+ map. insert (
604+ "not_unicode" . to_owned ( ) ,
605+ Err ( VarError :: NotUnicode ( OsString :: new ( ) ) ) ,
606+ ) ;
607+ // note: "not_present" is not in the map
608+
609+ let lookup = |v : & str | map. get ( v) . unwrap_or ( & Err ( VarError :: NotPresent ) ) . clone ( ) ;
610+
611+ // simple pattern tests
612+ assert_eq ! (
613+ Ok ( "test" . to_owned( ) ) ,
614+ substitute_environment_variables( "test" , lookup)
615+ ) ;
616+ assert_eq ! (
617+ Ok ( "a" . to_owned( ) ) ,
618+ substitute_environment_variables( "$A" , lookup)
619+ ) ;
620+ assert_eq ! (
621+ Ok ( "abcd" . to_owned( ) ) ,
622+ substitute_environment_variables( "$ABCD" , lookup)
623+ ) ;
624+ assert_eq ! (
625+ Ok ( "a0" . to_owned( ) ) ,
626+ substitute_environment_variables( "$A_0" , lookup)
627+ ) ;
628+ assert_eq ! (
629+ Ok ( "u" . to_owned( ) ) ,
630+ substitute_environment_variables( "$_" , lookup)
631+ ) ;
632+ assert_eq ! (
633+ Ok ( "some/path" . to_owned( ) ) ,
634+ substitute_environment_variables( "$PATH" , lookup)
635+ ) ;
636+
637+ // embedded in longer string
638+ assert_eq ! (
639+ Ok ( "test/a/test" . to_owned( ) ) ,
640+ substitute_environment_variables( "test/$A/test" , lookup)
641+ ) ;
642+ assert_eq ! (
643+ Ok ( "test/a" . to_owned( ) ) ,
644+ substitute_environment_variables( "test/$A" , lookup)
645+ ) ;
646+ assert_eq ! (
647+ Ok ( "a/test" . to_owned( ) ) ,
648+ substitute_environment_variables( "$A/test" , lookup)
649+ ) ;
650+ assert_eq ! (
651+ Ok ( "test/some/path/test" . to_owned( ) ) ,
652+ substitute_environment_variables( "test/$PATH/test" , lookup)
653+ ) ;
654+
655+ // error cases
656+ assert ! ( substitute_environment_variables( "$not_present" , lookup) . is_err( ) ) ;
657+ assert ! ( substitute_environment_variables( "$not_unicode" , lookup) . is_err( ) ) ;
658+ }
544659}
0 commit comments