Use the original os-release file parser
This commit is contained in:
parent
7229dd85f9
commit
05cb051f05
|
@ -1,7 +1,7 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::{collections::BTreeMap, str::FromStr};
|
use std::{collections::BTreeMap, str::FromStr};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::generation::Generation;
|
use crate::generation::Generation;
|
||||||
|
|
||||||
|
@ -58,31 +58,162 @@ impl FromStr for OsRelease {
|
||||||
fn from_str(value: &str) -> Result<Self> {
|
fn from_str(value: &str) -> Result<Self> {
|
||||||
let mut map = BTreeMap::new();
|
let mut map = BTreeMap::new();
|
||||||
|
|
||||||
// All valid lines
|
enum State {
|
||||||
let lines = value
|
PreKey,
|
||||||
.lines()
|
Key,
|
||||||
.map(str::trim)
|
PreValue,
|
||||||
.filter(|x| !x.starts_with('#') && !x.is_empty());
|
Value,
|
||||||
// Split into keys/values
|
ValueEscape,
|
||||||
let key_value_lines = lines.map(|x| x.split('=').collect::<Vec<&str>>());
|
SingleQuoteValue,
|
||||||
for kv in key_value_lines {
|
DoubleQuoteValue,
|
||||||
let k = kv
|
DoubleQuoteValueEscape,
|
||||||
.first()
|
Comment,
|
||||||
.with_context(|| format!("Failed to get first element from {kv:?}"))?;
|
CommentEscape,
|
||||||
let v = kv
|
}
|
||||||
.get(1)
|
use State::*;
|
||||||
.map(|s| s.strip_prefix(|c| c == '"' || c == '\'').unwrap_or(s))
|
|
||||||
.map(|s| s.strip_suffix(|c| c == '"' || c == '\'').unwrap_or(s))
|
|
||||||
.with_context(|| format!("Failed to get second element from {kv:?}"))?;
|
|
||||||
// Clean up the value. We already have the value without leading/tailing "
|
|
||||||
// so we just need to unescape the string.
|
|
||||||
let v = v
|
|
||||||
.replace("\\$", "$")
|
|
||||||
.replace("\\\"", "\"")
|
|
||||||
.replace("\\`", "`")
|
|
||||||
.replace("\\\\", "\\");
|
|
||||||
|
|
||||||
map.insert(String::from(*k), v);
|
let mut state = State::PreKey;
|
||||||
|
|
||||||
|
let mut current_key = String::new();
|
||||||
|
let mut current_value = String::new();
|
||||||
|
|
||||||
|
const COMMENTS: &str = "#;";
|
||||||
|
const WHITESPACE: &str = " \t\n\r";
|
||||||
|
const NEWLINE: &str = "\r\n";
|
||||||
|
const SHELL_NEED_ESCAPE: &str = "\"\\`$";
|
||||||
|
|
||||||
|
for c in value.chars() {
|
||||||
|
match state {
|
||||||
|
PreKey => {
|
||||||
|
if COMMENTS.contains(c) {
|
||||||
|
state = Comment;
|
||||||
|
} else if !WHITESPACE.contains(c) {
|
||||||
|
state = Key;
|
||||||
|
current_key.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Key => {
|
||||||
|
if NEWLINE.contains(c) {
|
||||||
|
// keys without any '=' are simply ignored
|
||||||
|
state = PreKey;
|
||||||
|
current_key.clear();
|
||||||
|
} else if c == '=' {
|
||||||
|
state = PreValue;
|
||||||
|
} else {
|
||||||
|
current_key.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PreValue => {
|
||||||
|
if NEWLINE.contains(c) {
|
||||||
|
state = PreKey;
|
||||||
|
// strip trailing whitespace from key
|
||||||
|
let key = current_key.trim_end().to_owned();
|
||||||
|
map.insert(key, current_value.clone());
|
||||||
|
|
||||||
|
current_key.clear();
|
||||||
|
current_value.clear();
|
||||||
|
} else if c == '\'' {
|
||||||
|
state = SingleQuoteValue;
|
||||||
|
} else if c == '"' {
|
||||||
|
state = DoubleQuoteValue;
|
||||||
|
} else if c == '\\' {
|
||||||
|
state = ValueEscape;
|
||||||
|
} else if !WHITESPACE.contains(c) {
|
||||||
|
state = Value;
|
||||||
|
current_value.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value => {
|
||||||
|
if NEWLINE.contains(c) {
|
||||||
|
state = PreKey;
|
||||||
|
// strip trailing whitespace from key
|
||||||
|
let key = current_key.trim_end().to_owned();
|
||||||
|
// strip trailing whitespace from value
|
||||||
|
let value = current_value.trim_end().to_owned();
|
||||||
|
map.insert(key, value);
|
||||||
|
|
||||||
|
current_key.clear();
|
||||||
|
current_value.clear();
|
||||||
|
} else if c == '\\' {
|
||||||
|
state = ValueEscape;
|
||||||
|
} else {
|
||||||
|
current_value.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ValueEscape => {
|
||||||
|
state = Value;
|
||||||
|
|
||||||
|
if !NEWLINE.contains(c) {
|
||||||
|
// Escaped newlines we eat up entirely
|
||||||
|
current_value.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SingleQuoteValue => {
|
||||||
|
if c == '\'' {
|
||||||
|
state = PreValue;
|
||||||
|
} else {
|
||||||
|
current_value.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DoubleQuoteValue => {
|
||||||
|
if c == '"' {
|
||||||
|
state = PreValue;
|
||||||
|
} else if c == '\\' {
|
||||||
|
state = DoubleQuoteValueEscape;
|
||||||
|
} else {
|
||||||
|
current_value.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DoubleQuoteValueEscape => {
|
||||||
|
state = DoubleQuoteValue;
|
||||||
|
|
||||||
|
if SHELL_NEED_ESCAPE.contains(c) {
|
||||||
|
// If this is a char that needs escaping, just unescape it.
|
||||||
|
current_value.push(c);
|
||||||
|
} else if c != '\n' {
|
||||||
|
// If other char than what needs escaping, keep the "\"
|
||||||
|
// in place, like the real shell does.
|
||||||
|
current_value.push('\\');
|
||||||
|
current_value.push(c);
|
||||||
|
}
|
||||||
|
// Escaped newlines (aka "continuation lines") are eaten up entirely
|
||||||
|
}
|
||||||
|
Comment => {
|
||||||
|
if c == '\\' {
|
||||||
|
state = CommentEscape;
|
||||||
|
} else if NEWLINE.contains(c) {
|
||||||
|
state = PreKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommentEscape => {
|
||||||
|
log::debug!("The line which doesn't begin with \";\" or \"#\", but follows a comment line trailing with escape is now treated as a non comment line since v254.");
|
||||||
|
if NEWLINE.contains(c) {
|
||||||
|
state = PreKey;
|
||||||
|
} else {
|
||||||
|
state = Comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(
|
||||||
|
state,
|
||||||
|
PreValue
|
||||||
|
| Value
|
||||||
|
| ValueEscape
|
||||||
|
| SingleQuoteValue
|
||||||
|
| DoubleQuoteValue
|
||||||
|
| DoubleQuoteValueEscape
|
||||||
|
) {
|
||||||
|
// strip trailing whitespace from key
|
||||||
|
let key = current_key.trim_end().to_owned();
|
||||||
|
let value = if matches!(state, Value) {
|
||||||
|
// strip trailing whitespace from value
|
||||||
|
current_value.trim_end().to_owned()
|
||||||
|
} else {
|
||||||
|
current_value
|
||||||
|
};
|
||||||
|
map.insert(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self(map))
|
Ok(Self(map))
|
||||||
|
@ -138,7 +269,7 @@ mod tests {
|
||||||
assert!(os_release.0["ESCAPED_DOLLAR"] == "$1.2");
|
assert!(os_release.0["ESCAPED_DOLLAR"] == "$1.2");
|
||||||
assert!(os_release.0["UNESCAPED_BACKTICK"] == "`1.2");
|
assert!(os_release.0["UNESCAPED_BACKTICK"] == "`1.2");
|
||||||
assert!(os_release.0["ESCAPED_BACKTICK"] == "`1.2");
|
assert!(os_release.0["ESCAPED_BACKTICK"] == "`1.2");
|
||||||
assert!(os_release.0["UNESCAPED_QUOTE"] == "\"1.2");
|
assert!(os_release.0["UNESCAPED_QUOTE"] == "1.2\"");
|
||||||
assert!(os_release.0["ESCAPED_QUOTE"] == "\"1.2");
|
assert!(os_release.0["ESCAPED_QUOTE"] == "\"1.2");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in New Issue