pub fn read_toml_string(text: &str, header: &str, key: &str) -> Option<String> {
let mut in_section = false;
for line in text.lines() {
let t = line.trim();
if t.starts_with('[') { in_section = t == header; continue; }
if !in_section { continue; }
if let Some(after) = t.strip_prefix(key) {
let after = after.trim_start().strip_prefix('=')?.trim_start();
let val = after.trim_matches('"').trim_matches('\'');
let val = match val.find('#') { Some(i) => &val[..i], None => val };
return Some(val.trim().trim_matches('"').to_string());
}
}
None
}
pub fn is_workspace_inherit(v: &str) -> bool {
let t = v.trim();
t.contains("workspace") && t.contains("true") && t.contains('{')
}
pub fn has_workspace_shorthand(text: &str, header: &str, key: &str) -> bool {
let mut in_section = false;
let needle = {
let mut s = String::from(key);
s.push_str(".workspace");
s
};
for line in text.lines() {
let t = line.trim();
if t.starts_with('[') { in_section = t == header; continue; }
if !in_section { continue; }
if t.starts_with(&needle) { return true; }
}
false
}
pub fn read_cargo_field(text: &str, key: &str) -> Option<String> {
if let Some(v) = read_toml_string(text, "[package]", key) {
if is_workspace_inherit(&v) {
return read_toml_string(text, "[workspace.package]", key);
}
return Some(v);
}
if has_workspace_shorthand(text, "[package]", key) {
return read_toml_string(text, "[workspace.package]", key);
}
read_toml_string(text, "[workspace.package]", key)
}
pub fn read_toml_string_array(text: &str, header: &str, key: &str) -> Vec<String> {
let mut in_section = false;
for line in text.lines() {
let t = line.trim();
if t.starts_with('[') { in_section = t == header; continue; }
if !in_section { continue; }
if let Some(after) = t.strip_prefix(key) {
let after = after.trim_start();
if !after.starts_with('=') { continue; }
let after = after[1..].trim_start();
if !after.starts_with('[') { continue; }
let close = after.find(']').unwrap_or(after.len());
let inner = &after[1..close];
return inner.split(',')
.map(|s| s.trim().trim_matches('"').trim_matches('\'').to_string())
.filter(|s| !s.is_empty())
.collect();
}
}
Vec::new()
}
pub fn read_json_string(text: &str, key: &str) -> Option<String> {
let needle = {
let mut s = String::from('"');
s.push_str(key);
s.push_str("\":");
s
};
let pos = text.find(&needle)?;
let after = text[pos + needle.len()..].trim_start();
let after = after.strip_prefix('"')?;
let end = after.find('"')?;
Some(after[..end].to_string())
}
pub fn read_yaml_string(text: &str, key: &str) -> Option<String> {
let needle = {
let mut s = String::from(key);
s.push(':');
s
};
for line in text.lines() {
if let Some(rest) = line.trim_start().strip_prefix(&needle) {
let val = rest.trim();
if let Some(inner) = val.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
let mut out = String::with_capacity(inner.len());
let mut it = inner.chars().peekable();
while let Some(c) = it.next() {
if c == '\\' {
if let Some(&n) = it.peek() {
if n == '"' || n == '\\' { out.push(it.next().unwrap()); continue; }
if n == 'n' { it.next(); out.push('\n'); continue; }
if n == 't' { it.next(); out.push('\t'); continue; }
}
}
out.push(c);
}
return Some(out);
}
if let Some(inner) = val.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
return Some(inner.replace("''", "'"));
}
return Some(val.to_string());
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn toml_string_handles_package_header() {
let t = "[package]\nname = \"foo\"\nversion = \"1.0\"\n";
assert_eq!(read_toml_string(t, "[package]", "name").as_deref(), Some("foo"));
assert_eq!(read_toml_string(t, "[package]", "version").as_deref(), Some("1.0"));
}
#[test]
fn toml_string_array_parses_keywords() {
let t = "[package]\nkeywords = [\"cli\", \"tool\", \"build\"]\n";
let v = read_toml_string_array(t, "[package]", "keywords");
assert_eq!(v, vec!["cli", "tool", "build"]);
}
#[test]
fn toml_string_array_empty_on_missing() {
let t = "[package]\nname = \"foo\"\n";
assert!(read_toml_string_array(t, "[package]", "keywords").is_empty());
}
#[test]
fn json_string_top_field() {
let t = "{\n \"name\": \"foo\",\n \"version\": \"1.0\"\n}\n";
assert_eq!(read_json_string(t, "name").as_deref(), Some("foo"));
}
#[test]
fn yaml_string_handles_both_quote_forms() {
let t = "name: 'foo'\ndescription: \"bar baz\"\n";
assert_eq!(read_yaml_string(t, "name").as_deref(), Some("foo"));
assert_eq!(read_yaml_string(t, "description").as_deref(), Some("bar baz"));
}
#[test]
fn yaml_string_unescapes_double_quote_form() {
let t = "description: \"buildkit's \\\"cache\\\" tool\"\n";
assert_eq!(read_yaml_string(t, "description").as_deref(),
Some("buildkit's \"cache\" tool"));
}
#[test]
fn yaml_string_unescapes_single_quote_form() {
let t = "description: 'buildkit''s cache'\n";
assert_eq!(read_yaml_string(t, "description").as_deref(),
Some("buildkit's cache"));
}
}