1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// Copyright 2020 The Evcxr Authors.
//
// Licensed under the Apache License, Version 2.0 <LICENSE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE
// or https://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use crate::errors::Error;
use crate::errors::bail;
use once_cell::sync::Lazy;
use regex::Regex;
use std::path::Path;
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct ExternalCrate {
// The name, as it appears in the crate registry. This may be different than the key against
// which this ExternalCrate is stored. If this name is "foo-bar", the key would be normalized as
// "foo_bar".
pub(crate) name: String,
// Of the form "name = ..."
pub(crate) config: String,
}
fn make_paths_absolute(config: String) -> Result<String, Error> {
// Perhaps not the nicest way to do this. Using a toml parser would possibly
// be nicer. At the time this was written that wasn't an option due to a
// compiler bug that prevented us from using any crate that used custom
// derive. That bug is long fixed though, so switching this to use a toml
// parser would be an option.
static PATH_RE: Lazy<Regex> =
Lazy::new(|| Regex::new("^(.*)path *= *\"([^\"]+)\"(.*)$").unwrap());
if let Some(captures) = PATH_RE.captures(&config) {
let path = Path::new(&captures[2]);
if !path.is_absolute() {
match path.canonicalize() {
Ok(path) => {
return Ok(captures[1].to_owned()
+ "path = \""
+ &escape_toml_string(&path.to_string_lossy())
+ "\""
+ &captures[3]);
}
Err(err) => {
bail!("{}: {:?}", err, path);
}
}
}
}
Ok(config)
}
/// Escapes a TOML string, see https://toml.io/en/v1.0.0#string
fn escape_toml_string(string: &str) -> String {
let mut escaped = String::new();
for char in string.chars() {
match char {
'"' | '\\' => {
escaped.push('\\');
escaped.push(char);
}
// Control characters with special escape sequences
'\u{0008}' => escaped.push_str("\\b"),
'\t' => escaped.push_str("\\t"),
'\n' => escaped.push_str("\\n"),
'\u{000C}' => escaped.push_str("\\f"),
'\r' => escaped.push_str("\\r"),
// Control characters using \uXXXX escape sequence
'\0'..='\u{001F}' | '\u{007F}' => {
escaped.push_str(&format!("\\u{:04X}", char as u32));
}
_ => escaped.push(char),
}
}
escaped
}
impl ExternalCrate {
pub(crate) fn new(name: String, config: String) -> Result<ExternalCrate, Error> {
let config = make_paths_absolute(config)?;
Ok(ExternalCrate { name, config })
}
}
#[cfg(test)]
mod tests {
use super::ExternalCrate;
use super::escape_toml_string;
use std::path::Path;
#[test]
fn test_escape_toml_string() {
let string = "test \" \\ \\u1234 \u{10FFFF}";
assert_eq!(
escape_toml_string(string),
"test \\\" \\\\ \\\\u1234 \u{10FFFF}"
);
let string = "\u{0000} \u{0008} \t \n \u{000C} \r \u{001F} \u{007F}";
assert_eq!(
escape_toml_string(string),
r"\u0000 \b \t \n \f \r \u001F \u007F"
);
}
#[test]
fn make_paths_absolute() {
let krate =
ExternalCrate::new("foo".to_owned(), "{ path = \"src/testdata\" }".to_owned()).unwrap();
assert_eq!(krate.name, "foo");
let expected_path_string = &escape_toml_string(
&Path::new("src/testdata")
.canonicalize()
.unwrap()
.to_string_lossy(),
);
assert_eq!(
krate.config,
format!("{{ path = \"{expected_path_string}\" }}")
);
}
}