use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::path::Path;
#[derive(Debug, Default, Eq, PartialEq)]
pub struct PropLine {
pub console_spec: Option<String>,
}
fn strip_newline(s: &mut String) {
if s.ends_with('\n') {
s.pop();
if s.ends_with('\r') {
s.pop();
}
}
}
fn parse_propline(line: &str) -> io::Result<PropLine> {
let without_comment = match line.strip_prefix("' ") {
Some(line) => Some(line),
None => {
if line.len() <= 4 {
None
} else {
let token = line[0..4].to_ascii_lowercase();
if token == "rem " { Some(&line[4..]) } else { None }
}
}
};
let Some(payload) = without_comment else {
return Ok(PropLine::default());
};
let Some(payload) = payload.strip_prefix("endbasic cli: ") else {
return Ok(PropLine::default());
};
let Some(console_spec) = payload.strip_prefix("console=") else {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Invalid EndBASIC propline: {}", line),
));
};
if console_spec.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid EndBASIC propline: missing console specification",
));
}
Ok(PropLine { console_spec: Some(console_spec.to_owned()) })
}
pub fn extract_propline<P: AsRef<Path>>(path: P) -> io::Result<PropLine> {
let input = File::open(path)?;
let mut input = BufReader::new(input);
let mut line1 = String::new();
if input.read_line(&mut line1)? == 0 {
return Ok(PropLine::default());
}
strip_newline(&mut line1);
if !line1.starts_with("#!") {
return Ok(PropLine::default());
}
let mut line2 = String::new();
if input.read_line(&mut line2)? == 0 {
return Ok(PropLine::default());
}
strip_newline(&mut line2);
parse_propline(&line2)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
fn do_extract(contents: &str) -> io::Result<PropLine> {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("script.bas");
fs::write(&path, contents).unwrap();
extract_propline(path)
}
#[test]
fn test_extract_propline_no_shebang() {
let propline = do_extract("PRINT \"hello\"\n").unwrap();
assert_eq!(PropLine::default(), propline);
}
#[test]
fn test_extract_propline_apostrophe() {
let propline =
do_extract("#!/usr/bin/env endbasic\n' endbasic cli: console=sdl\nPRINT 1\n").unwrap();
assert_eq!(Some("sdl".to_owned()), propline.console_spec);
}
#[test]
fn test_extract_propline_rem_mixed_case() {
let propline =
do_extract("#!/usr/bin/env endbasic\nReM endbasic cli: console=sdl:fs\n").unwrap();
assert_eq!(Some("sdl:fs".to_owned()), propline.console_spec);
}
#[test]
fn test_extract_propline_ignores_unrelated_comment() {
let propline = do_extract("#!/usr/bin/env endbasic\nREM regular comment\n").unwrap();
assert_eq!(PropLine::default(), propline);
}
#[test]
fn test_extract_propline_errors_on_bad_prefix() {
let error = do_extract("#!/usr/bin/env endbasic\n' endbasic cli: nope\n").unwrap_err();
assert_eq!(io::ErrorKind::InvalidInput, error.kind());
assert_eq!("Invalid EndBASIC propline: ' endbasic cli: nope", error.to_string());
}
#[test]
fn test_extract_propline_errors_on_empty_console_spec() {
let error =
do_extract("#!/usr/bin/env endbasic\nREM endbasic cli: console=\n").unwrap_err();
assert_eq!(io::ErrorKind::InvalidInput, error.kind());
assert_eq!("Invalid EndBASIC propline: missing console specification", error.to_string());
}
}