Skip to content

Commit

Permalink
Implement variable substitution for Windows machines (#281)
Browse files Browse the repository at this point in the history
  • Loading branch information
Schottkyc137 authored Mar 29, 2024
1 parent 11b9f82 commit b43b96c
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 2 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ UNISIM.is_third_party = true
```

Paths in the `vhdl_ls.toml` can contain glob patterns (i.e., `.../*/`).
On Unix machines, they can also contain environment variables using the default `$NAME` or `${NAME}` syntax.
On Unix machines, they can contain environment variables using the `$NAME` or `${NAME}` syntax.
On Windows machines, use the `%NAME%` syntax to substitute environment variables.

## As an LSP-client developer how should I integrate VHDL-LS?

Expand Down
102 changes: 101 additions & 1 deletion vhdl_lang/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,12 +289,51 @@ where
M::Value: AsRef<str>,
{
if cfg!(windows) {
Ok(s.to_string())
substitute_variables_windows(s, map)
} else {
subst::substitute(s, map).map_err(|err| err.to_string())
}
}

fn substitute_variables_windows<'a, M>(s: &str, map: &'a M) -> Result<String, String>
where
M: VariableMap<'a> + ?Sized,
M::Value: AsRef<str>,
{
let mut output: Vec<char> = Vec::with_capacity(s.len());
let mut var_buf: Vec<char> = Vec::new();

let mut var_found = false;

for ch in s.chars() {
if ch == '%' {
if var_found {
let var_name = String::from_iter(var_buf);
var_buf = Vec::new();
match map.get(&var_name) {
None => {
return Err(format!("Variable '{var_name}' not found"));
}
Some(value) => {
output.extend(value.as_ref().chars());
}
}
}
var_found = !var_found;
} else if !var_found {
output.push(ch);
} else {
var_buf.push(ch)
}
}

if var_found {
Err("Unterminated variable".into())
} else {
Ok(String::from_iter(output))
}
}

/// Returns true if the pattern is a plain file name and not a glob pattern
fn is_literal(pattern: &str) -> bool {
for chr in pattern.chars() {
Expand Down Expand Up @@ -622,6 +661,67 @@ work.files = [
assert!(substitute_environment_variables("$not_unicode", &map).is_err());
}

#[test]
fn windows_variable_names() {
let mut map = HashMap::new();
map.insert("A".to_owned(), "a".to_owned());
map.insert("ABCD".to_owned(), "abcd".to_owned());
map.insert("A_0".to_owned(), "a0".to_owned());
map.insert("_".to_owned(), "u".to_owned());
map.insert("PATH".to_owned(), r#"some\path"#.to_owned());

assert_eq!(Ok("".to_owned()), substitute_variables_windows("", &map));
assert_eq!(
Ok("test".to_owned()),
substitute_variables_windows("test", &map)
);
assert_eq!(
Ok("a".to_owned()),
substitute_variables_windows("%A%", &map)
);
assert_eq!(
Ok("abcd".to_owned()),
substitute_variables_windows("%ABCD%", &map)
);
assert_eq!(
Ok("a0".to_owned()),
substitute_variables_windows("%A_0%", &map)
);
assert_eq!(
Ok("u".to_owned()),
substitute_variables_windows("%_%", &map)
);
assert_eq!(
Ok(r#"some\path"#.to_owned()),
substitute_variables_windows("%PATH%", &map)
);

// embedded in longer string
assert_eq!(
Ok(r#"test\a\test"#.to_owned()),
substitute_variables_windows(r#"test\%A%\test"#, &map)
);
assert_eq!(
Ok(r#"test\a"#.to_owned()),
substitute_variables_windows(r#"test\%A%"#, &map)
);
assert_eq!(
Ok(r#"a\test"#.to_owned()),
substitute_variables_windows(r#"%A%\test"#, &map)
);
assert_eq!(
Ok(r#"C:\test\some\path\test"#.to_owned()),
substitute_variables_windows(r#"C:\test\%PATH%\test"#, &map)
);

// error cases
assert_eq!(
substitute_variables_windows("%not_present%", &map),
Err("Variable 'not_present' not found".into())
);
assert!(substitute_variables_windows("%not_unicode%", &map).is_err());
}

// Issue #278
#[test]
#[cfg(windows)]
Expand Down

0 comments on commit b43b96c

Please sign in to comment.