Fix reading of asciicasts v1 and v2 having null env values in header

This commit is contained in:
Marcin Kulik
2025-10-22 22:23:24 +02:00
parent f7008ac2c5
commit 37153a6740
5 changed files with 85 additions and 8 deletions

View File

@@ -274,6 +274,11 @@ mod tests {
assert_eq!(version, 1);
assert_eq!((header.term_cols, header.term_rows), (100, 50));
let mut expected_env = HashMap::new();
expected_env.insert("SHELL".to_owned(), "/bin/bash".to_owned());
expected_env.insert("TERM".to_owned(), "xterm-256color".to_owned());
assert_eq!(header.env.unwrap(), expected_env);
assert_eq!(events[0].time, Duration::from_micros(1));
assert!(matches!(events[0].data, EventData::Output(ref s) if s == "ż"));
@@ -284,6 +289,18 @@ mod tests {
assert!(matches!(events[2].data, EventData::Output(ref s) if s == "\r\n"));
}
#[test]
fn open_v1_with_nulls_in_header() {
let Asciicast {
version, header, ..
} = super::open_from_path("tests/casts/nulls-v1.json").unwrap();
assert_eq!(version, 1);
let mut expected_env = HashMap::new();
expected_env.insert("SHELL".to_owned(), "/bin/bash".to_owned());
assert_eq!(header.env.unwrap(), expected_env);
}
#[test]
fn open_v2_minimal() {
let Asciicast {
@@ -319,6 +336,11 @@ mod tests {
assert_eq!(theme.bg, RGB8::new(0xff, 0xff, 0xff));
assert_eq!(theme.palette[0], RGB8::new(0x24, 0x1f, 0x31));
let mut expected_env = HashMap::new();
expected_env.insert("SHELL".to_owned(), "/bin/bash".to_owned());
expected_env.insert("TERM".to_owned(), "xterm-256color".to_owned());
assert_eq!(header.env.unwrap(), expected_env);
assert_eq!(events[0].time, Duration::from_micros(1));
assert!(matches!(events[0].data, EventData::Output(ref s) if s == "ż"));
@@ -338,6 +360,18 @@ mod tests {
assert!(matches!(events[4].data, EventData::Output(ref s) if s == "\r\n"));
}
#[test]
fn open_v2_with_nulls_in_header() {
let Asciicast {
version, header, ..
} = super::open_from_path("tests/casts/nulls-v2.cast").unwrap();
assert_eq!(version, 2);
let mut expected_env = HashMap::new();
expected_env.insert("SHELL".to_owned(), "/bin/bash".to_owned());
assert_eq!(header.env.unwrap(), expected_env);
}
#[test]
fn open_v3_minimal() {
let Asciicast {

View File

@@ -14,7 +14,7 @@ struct V1 {
height: u16,
command: Option<String>,
title: Option<String>,
env: Option<HashMap<String, String>>,
env: Option<HashMap<String, Option<String>>>,
stdout: Vec<V1OutputEvent>,
}
@@ -35,8 +35,16 @@ pub fn load(json: String) -> Result<Asciicast<'static>> {
let term_type = asciicast
.env
.as_ref()
.and_then(|env| env.get("TERM"))
.cloned();
.map(|env| env.get("TERM"))
.unwrap_or_default()
.cloned()
.unwrap_or_default();
let env = asciicast.env.map(|env| {
env.into_iter()
.filter_map(|(k, v)| v.map(|v| (k, v)))
.collect()
});
let header = Header {
term_cols: asciicast.width,
@@ -48,7 +56,7 @@ pub fn load(json: String) -> Result<Asciicast<'static>> {
idle_time_limit: None,
command: asciicast.command.clone(),
title: asciicast.title.clone(),
env: asciicast.env.clone(),
env,
};
let events = Box::new(asciicast.stdout.into_iter().scan(

View File

@@ -18,7 +18,7 @@ struct V2Header {
idle_time_limit: Option<f64>,
command: Option<String>,
title: Option<String>,
env: Option<HashMap<String, String>>,
env: Option<HashMap<String, Option<String>>>,
theme: Option<V2Theme>,
}
@@ -70,9 +70,22 @@ pub fn open(header_line: &str) -> Result<Parser> {
impl Parser {
pub fn parse<'a, I: Iterator<Item = io::Result<String>> + 'a>(self, lines: I) -> Asciicast<'a> {
let term_type = self.0.env.as_ref().and_then(|env| env.get("TERM").cloned());
let term_type = self
.0
.env
.as_ref()
.map(|env| env.get("TERM").cloned())
.unwrap_or_default()
.unwrap_or_default();
let term_theme = self.0.theme.as_ref().map(|t| t.into());
let env = self.0.env.map(|env| {
env.into_iter()
.filter_map(|(k, v)| v.map(|v| (k, v)))
.collect()
});
let header = Header {
term_cols: self.0.width,
term_rows: self.0.height,
@@ -83,7 +96,7 @@ impl Parser {
idle_time_limit: self.0.idle_time_limit,
command: self.0.command.clone(),
title: self.0.title.clone(),
env: self.0.env.clone(),
env,
};
let events = Box::new(lines.filter_map(parse_line));
@@ -367,6 +380,11 @@ impl serde::Serialize for V2Palette {
impl From<&Header> for V2Header {
fn from(header: &Header) -> Self {
let env = header
.env
.clone()
.map(|env| env.into_iter().map(|(k, v)| (k, Some(v))).collect());
V2Header {
version: 2,
width: header.term_cols,
@@ -375,7 +393,7 @@ impl From<&Header> for V2Header {
idle_time_limit: header.idle_time_limit,
command: header.command.clone(),
title: header.title.clone(),
env: header.env.clone(),
env,
theme: header.term_theme.as_ref().map(|t| t.into()),
}
}

15
tests/casts/nulls-v1.json Normal file
View File

@@ -0,0 +1,15 @@
{
"version": 1,
"width": 100,
"height": 50,
"env": {
"SHELL": "/bin/bash",
"TERM": null
},
"stdout": [
[
1.230000,
"hello"
]
]
}

View File

@@ -0,0 +1,2 @@
{"version":2,"width":100,"height":50,"env":{"TERM":null,"SHELL":"/bin/bash"}}
[1.23, "o", "hello"]