mdbash_tutorial/
script.rs1use 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 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}