@@ -289,12 +289,51 @@ where
289289 M :: Value : AsRef < str > ,
290290{
291291 if cfg ! ( windows) {
292- Ok ( s . to_string ( ) )
292+ substitute_variables_windows ( s , map )
293293 } else {
294294 subst:: substitute ( s, map) . map_err ( |err| err. to_string ( ) )
295295 }
296296}
297297
298+ fn substitute_variables_windows < ' a , M > ( s : & str , map : & ' a M ) -> Result < String , String >
299+ where
300+ M : VariableMap < ' a > + ?Sized ,
301+ M :: Value : AsRef < str > ,
302+ {
303+ let mut output: Vec < char > = Vec :: with_capacity ( s. len ( ) ) ;
304+ let mut var_buf: Vec < char > = Vec :: new ( ) ;
305+
306+ let mut var_found = false ;
307+
308+ for ch in s. chars ( ) {
309+ if ch == '%' {
310+ if var_found {
311+ let var_name = String :: from_iter ( var_buf) ;
312+ var_buf = Vec :: new ( ) ;
313+ match map. get ( & var_name) {
314+ None => {
315+ return Err ( format ! ( "Variable '{var_name}' not found" ) ) ;
316+ }
317+ Some ( value) => {
318+ output. extend ( value. as_ref ( ) . chars ( ) ) ;
319+ }
320+ }
321+ }
322+ var_found = !var_found;
323+ } else if !var_found {
324+ output. push ( ch) ;
325+ } else {
326+ var_buf. push ( ch)
327+ }
328+ }
329+
330+ if var_found {
331+ Err ( "Unterminated variable" . into ( ) )
332+ } else {
333+ Ok ( String :: from_iter ( output) )
334+ }
335+ }
336+
298337/// Returns true if the pattern is a plain file name and not a glob pattern
299338fn is_literal ( pattern : & str ) -> bool {
300339 for chr in pattern. chars ( ) {
@@ -622,6 +661,67 @@ work.files = [
622661 assert ! ( substitute_environment_variables( "$not_unicode" , & map) . is_err( ) ) ;
623662 }
624663
664+ #[ test]
665+ fn windows_variable_names ( ) {
666+ let mut map = HashMap :: new ( ) ;
667+ map. insert ( "A" . to_owned ( ) , "a" . to_owned ( ) ) ;
668+ map. insert ( "ABCD" . to_owned ( ) , "abcd" . to_owned ( ) ) ;
669+ map. insert ( "A_0" . to_owned ( ) , "a0" . to_owned ( ) ) ;
670+ map. insert ( "_" . to_owned ( ) , "u" . to_owned ( ) ) ;
671+ map. insert ( "PATH" . to_owned ( ) , r#"some\path"# . to_owned ( ) ) ;
672+
673+ assert_eq ! ( Ok ( "" . to_owned( ) ) , substitute_variables_windows( "" , & map) ) ;
674+ assert_eq ! (
675+ Ok ( "test" . to_owned( ) ) ,
676+ substitute_variables_windows( "test" , & map)
677+ ) ;
678+ assert_eq ! (
679+ Ok ( "a" . to_owned( ) ) ,
680+ substitute_variables_windows( "%A%" , & map)
681+ ) ;
682+ assert_eq ! (
683+ Ok ( "abcd" . to_owned( ) ) ,
684+ substitute_variables_windows( "%ABCD%" , & map)
685+ ) ;
686+ assert_eq ! (
687+ Ok ( "a0" . to_owned( ) ) ,
688+ substitute_variables_windows( "%A_0%" , & map)
689+ ) ;
690+ assert_eq ! (
691+ Ok ( "u" . to_owned( ) ) ,
692+ substitute_variables_windows( "%_%" , & map)
693+ ) ;
694+ assert_eq ! (
695+ Ok ( r#"some\path"# . to_owned( ) ) ,
696+ substitute_variables_windows( "%PATH%" , & map)
697+ ) ;
698+
699+ // embedded in longer string
700+ assert_eq ! (
701+ Ok ( r#"test\a\test"# . to_owned( ) ) ,
702+ substitute_variables_windows( r#"test\%A%\test"# , & map)
703+ ) ;
704+ assert_eq ! (
705+ Ok ( r#"test\a"# . to_owned( ) ) ,
706+ substitute_variables_windows( r#"test\%A%"# , & map)
707+ ) ;
708+ assert_eq ! (
709+ Ok ( r#"a\test"# . to_owned( ) ) ,
710+ substitute_variables_windows( r#"%A%\test"# , & map)
711+ ) ;
712+ assert_eq ! (
713+ Ok ( r#"C:\test\some\path\test"# . to_owned( ) ) ,
714+ substitute_variables_windows( r#"C:\test\%PATH%\test"# , & map)
715+ ) ;
716+
717+ // error cases
718+ assert_eq ! (
719+ substitute_variables_windows( "%not_present%" , & map) ,
720+ Err ( "Variable 'not_present' not found" . into( ) )
721+ ) ;
722+ assert ! ( substitute_variables_windows( "%not_unicode%" , & map) . is_err( ) ) ;
723+ }
724+
625725 // Issue #278
626726 #[ test]
627727 #[ cfg( windows) ]
0 commit comments