clitest_lib/parser/
mod.rs1use crate::script::{Script, ScriptError, ScriptErrorType, ScriptFile, ScriptLocation};
2use std::{
3 collections::{BTreeMap, HashMap},
4 sync::Arc,
5};
6
7pub mod v0;
8
9#[derive(thiserror::Error, Debug)]
10pub enum ScriptReadError {
11 #[error("Error reading script file {file}: {error}")]
12 ParseError {
13 file: ScriptFile,
14 error: ScriptError,
15 },
16 #[error("Error reading script file {file}: {error}")]
17 IoError {
18 file: ScriptFile,
19 error: std::io::Error,
20 },
21}
22
23#[derive(Default)]
24pub struct Scripts {
25 pub scripts: BTreeMap<ScriptFile, Script>,
26}
27
28pub fn parse_script(file: ScriptFile, script: &str) -> Result<Script, ScriptError> {
29 let version = script.lines().next().unwrap_or_default();
30 match version {
31 "#!/usr/bin/env clitest --v0" => v0::parse_script(file, script),
32 _ => Err(ScriptError::new(
33 ScriptErrorType::InvalidVersion,
34 ScriptLocation::new(file, 1),
35 )),
36 }
37}
38
39pub fn parse_script_file(
40 parent: Option<ScriptFile>,
41 file: ScriptFile,
42) -> Result<Script, Vec<ScriptReadError>> {
43 let mut errors = Vec::new();
44 let path = parent
45 .map(|p| Arc::new(p.file.parent().unwrap().join(&*file.file)))
46 .unwrap_or(file.file.clone());
47 let script_contents = match std::fs::read_to_string(path.as_ref()) {
48 Ok(contents) => contents,
49 Err(e) => {
50 errors.push(ScriptReadError::IoError {
51 file: file.clone(),
52 error: e,
53 });
54 return Err(errors);
55 }
56 };
57 let script = parse_script(file.clone(), &script_contents);
58 let mut script = match script {
59 Ok(script) => script,
60 Err(e) => {
61 errors.push(ScriptReadError::ParseError {
62 file: file.clone(),
63 error: e,
64 });
65 return Err(errors);
66 }
67 };
68
69 let mut includes = HashMap::new();
70 for (location, include_path) in script.includes() {
71 match parse_script_file(Some(location.file.clone()), ScriptFile::new(&include_path)) {
74 Ok(script) => {
75 includes.insert(include_path.clone(), script);
76 }
77 Err(e) => {
78 errors.extend(e);
79 }
80 }
81 }
82
83 script.includes = Arc::new(includes);
84
85 if errors.is_empty() {
86 Ok(script)
87 } else {
88 Err(errors)
89 }
90}
91
92pub fn parse_script_files<T>(files: &[T]) -> Result<Scripts, Vec<ScriptReadError>>
93where
94 for<'a> &'a T: Into<ScriptFile>,
95{
96 let mut errors = Vec::new();
97 let mut scripts = Scripts::default();
98
99 for file in files {
101 let file = file.into();
102 let script = match parse_script_file(None, file.clone()) {
103 Ok(script) => script,
104 Err(e) => {
105 errors.extend(e);
106 continue;
107 }
108 };
109 scripts.scripts.insert(file, script);
110 }
111
112 if errors.is_empty() {
113 Ok(scripts)
114 } else {
115 Err(errors)
116 }
117}