mdbash_tutorial/
script.rs

1use std::fmt;
2use std::fs;
3use std::fs::File;
4use std::io::{BufRead,BufReader};
5
6pub(crate) struct Step {
7    comments: Vec<String>,
8    code: Vec<String>,
9    format: String,
10}
11
12#[allow(dead_code)]
13pub(crate) struct Script {
14    shebang: Option<String>,
15    steps: Vec<Step>,
16}
17
18impl fmt::Display for Script {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        let s = self.steps
21            .iter()
22            .map(|s| s.to_string())
23            .collect::<Vec<String>>()
24            .join("");
25
26        // match &self.shebang {
27        //     Some(sb) => write!(f, "{}\n{}", sb, s),
28        //     None => write!(f, "{}", s),
29        // }
30        write!(f, "{}", s)
31    }
32}
33
34impl fmt::Display for Step {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        let comments = self.comments
37            .iter()
38            .map(|x| x
39                 .trim_start_matches('#')
40                 .trim_start()
41                 .to_string())
42            .collect::<Vec<String>>()
43            .join("\t");
44        let code = self.code.join("\t");
45
46        write!(f, "1. {}\n\t```{}\n\t{}\t```\n", comments, self.format, code)
47    }
48}
49
50fn read_file(file_path: &str) -> BufReader<File>
51{
52    let file = match fs::File::open(file_path) {
53        Ok(file) => file,
54        Err(_) => panic!("Unable to read file from {}", file_path)
55    };
56
57    let buffer = BufReader::new(file);
58    return buffer
59}
60
61pub(crate) fn parse(file_path: &str) -> Script {
62    let mut file_reader = read_file(file_path);
63    let shebang  = skip_shebang(&mut file_reader).expect("error looking for Shebang ");
64
65    let mut steps = vec![];
66    loop {
67        match read_step(&mut file_reader) {
68            Ok(x) => {
69                match x {
70                    Some(step) => steps.push(step),
71                    None => break,
72                }
73            },
74            Err(_) => panic!("error parsing step"),
75        }
76    }
77
78    return Script {
79        shebang,
80        steps,
81    }
82}
83
84fn skip_shebang(script: &mut BufReader<File>) -> Result<Option<String>, std::io::Error>
85{
86    let mut first_line = String::new();
87    script.read_line(&mut first_line).expect("Unable to read first line");
88
89    if first_line.starts_with("#!") {
90        return Ok(Some(first_line.to_string()));
91    }
92
93    let l = first_line.len() as i64;
94    script.seek_relative(-1 * l)?;
95    return Ok(None)
96}
97
98fn read_step(script: &mut BufReader<File>) -> Result<Option<Step>, std::io::Error>
99{
100    let mut comments = vec![];
101    let mut code = vec![];
102
103    loop {
104        let mut l = String::new();
105        let r = script.read_line(&mut l)?;
106        if r == 0 {
107            if comments.is_empty() {
108                return Ok(None);
109            }
110
111            let s = Step{
112                comments,
113                code,
114                format: "bash".to_string(),
115            };
116            return Ok(Some(s));
117        }
118
119        let t = l
120            .trim_end_matches('\r')
121            .trim_end_matches('\n');
122        if t.is_empty() || t.ends_with("# mdbash: skip-line") {
123            continue
124        }
125
126        let is_comment = l.starts_with("#");
127        if is_comment && !code.is_empty() {
128            match script.seek_relative(-1 * l.len() as i64) {
129                Ok(x) => x,
130                Err(_) => panic!("error resetting file reader"),
131            }
132            break
133        }
134
135        if is_comment {
136            comments.push(l);
137        } else {
138            code.push(l);
139        }
140    }
141
142    if comments.is_empty() {
143        return Ok(None)
144    }
145
146    let s = Step{
147        comments,
148        code,
149        format: "bash".to_string(),
150    };
151    return Ok(Some(s));
152}