src/main.rs

changeset 2
254e1e4bd795
parent 0
548bf3cc032e
child 3
cec573b16b46
equal deleted inserted replaced
1:a88aed2bdf13 2:254e1e4bd795
1 // The main documentation is in the README.
2 #![doc = include_str!("../README.md")]
3
1 use std::io; 4 use std::io;
2 use std::io::BufWriter; 5 use std::io::BufWriter;
3 use std::io::Write; 6 use std::io::Write;
7 use clap::Parser;
8
9 /// Command line parameters
10 #[derive(Parser, Debug)]
11 #[clap(
12 about = env!("CARGO_PKG_DESCRIPTION"),
13 author = env!("CARGO_PKG_AUTHORS"),
14 version = env!("CARGO_PKG_VERSION"),
15 )]
16 struct CommandLineArgs {
17 #[arg(long, short = 'c')]
18 /// Strip comments
19 strip_comments : bool,
20
21 #[arg(long, short = 'w')]
22 /// Strip unnecessary whitespace
23 strip_whitespace : bool,
24 }
4 25
5 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 26 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
6 enum Element { 27 enum Element {
7 Added, 28 Added,
8 Deleted, 29 Deleted,
16 Output(Element), 37 Output(Element),
17 Ignore(Element), 38 Ignore(Element),
18 Scan(Element, bool), 39 Scan(Element, bool),
19 } 40 }
20 41
42 use Status::*;
43 use Element::*;
44
45 struct Out<W : Write> {
46 only_whitespace : bool,
47 stored_whitespace : String,
48 output : W,
49 stack : Vec<Status>,
50 whitespace_satisfied : bool,
51 par_satisfied : bool,
52 }
53
54 impl<W : Write> Out<W> {
55 fn current(&self) -> Status {
56 self.stack.last().map_or(Output(Other), |s| *s)
57 }
58
59 fn raw_out(&mut self, c : char) {
60 write!(self.output, "{}", c).unwrap();
61 }
62
63 pub fn out(&mut self, c : char) {
64 self.only_whitespace = false;
65 write!(self.output, "{}{}", self.stored_whitespace, c).unwrap();
66 self.stored_whitespace.clear();
67 self.whitespace_satisfied = false;
68 self.par_satisfied = false;
69 }
70
71 pub fn whitespace(&mut self, c : char) {
72 self.stored_whitespace.push(c);
73 }
74
75 pub fn line_end(&mut self, strip_ws : bool, input_only_ws : bool) {
76 let cur = self.current();
77 let skip_linefeed = if input_only_ws {
78 // Need a paragraph break
79 strip_ws && self.par_satisfied
80 } else if strip_ws {
81 self.only_whitespace && self.whitespace_satisfied
82 } else if let Ignore(Comment) = cur {
83 // Skip comment-only lines if the comment is ignored
84 self.only_whitespace
85 } else if let Ignore(_) = cur {
86 // Skip line feeds in ignored bits
87 true
88 } else {
89 false
90 };
91
92 if !skip_linefeed {
93 if !strip_ws {
94 write!(self.output, "{}", self.stored_whitespace).unwrap();
95 }
96 self.raw_out('\n');
97 self.whitespace_satisfied = true;
98 self.par_satisfied = self.only_whitespace;
99 }
100
101 if let Ignore(Comment) | Output(Comment) = cur {
102 self.stack.pop();
103 }
104
105 self.stored_whitespace.clear();
106 self.only_whitespace = true;
107 }
108
109 pub fn flush(&mut self) {
110 self.output.flush().unwrap();
111 }
112 }
113
21 fn main() { 114 fn main() {
115 let cli = CommandLineArgs::parse();
22 let input = io::stdin(); 116 let input = io::stdin();
23 let mut output = BufWriter::new(io::stdout()); 117
24 let mut status_stack = Vec::new(); 118 let mut o = Out {
25 119 only_whitespace : true,
26 use Status::*; 120 stored_whitespace : String::new(),
27 use Element::*; 121 output : BufWriter::new(io::stdout()),
28 122 stack : Vec::new(),
29 let current = |s : &Vec<Status>| s.last().map_or(Output(Other), |s| *s); 123 whitespace_satisfied : true,
30 let mut out = |c : char| { write!(output, "{}", c).unwrap(); }; 124 par_satisfied : true,
125 };
126
31 let mut lineno = 0; 127 let mut lineno = 0;
32 128
33 for l in input.lines().map(|l| l.unwrap()) { 129 for l in input.lines().map(|l| l.unwrap()) {
34 lineno += 1; 130 lineno += 1;
35 let mut chars = l.chars(); 131 let mut chars = l.chars();
36 let started_ignore = if let Ignore(_) = current(&status_stack) { true } else { false };
37 let mut maybe_next_char = None; 132 let mut maybe_next_char = None;
133 let mut input_only_ws = true;
134
38 'process_line: loop { 135 'process_line: loop {
39 let next_char = match maybe_next_char { 136 let next_char = match maybe_next_char {
40 None => chars.next(), 137 None => chars.next(),
41 Some(c) => { 138 Some(c) => {
42 maybe_next_char = None; 139 maybe_next_char = None;
43 Some(c) 140 Some(c)
44 } 141 }
45 }; 142 };
46 match(current(&status_stack), next_char) { 143 input_only_ws = input_only_ws && next_char.map_or(true, |c| c.is_whitespace());
144 match(o.current(), next_char) {
47 (_, None) => { 145 (_, None) => {
48 break 'process_line; 146 break 'process_line;
49 }, 147 },
50 (st @ (Output(e) | Ignore(e)), Some('\\')) if e != Comment => { 148 (st @ (Output(e) | Ignore(e)), Some('\\')) if e != Comment => {
51 let mut command = String::new(); 149 let mut command = String::new();
66 first = false; 164 first = false;
67 }; 165 };
68 let output_guard = if let Ignore(_) = st { false } else { true }; 166 let output_guard = if let Ignore(_) = st { false } else { true };
69 match command.as_str() { 167 match command.as_str() {
70 "added" => { 168 "added" => {
71 status_stack.push(Scan(Added, true && output_guard)); 169 o.stack.push(Scan(Added, true && output_guard));
72 }, 170 },
73 "replaced" => { 171 "replaced" => {
74 status_stack.push(Scan(Replaced, true && output_guard)); 172 o.stack.push(Scan(Replaced, true && output_guard));
75 }, 173 },
76 "deleted" => { 174 "deleted" => {
77 status_stack.push(Scan(Deleted, false)); 175 o.stack.push(Scan(Deleted, false));
78 }, 176 },
79 _ => { 177 _ => {
80 if output_guard { 178 if output_guard {
81 out('\\'); 179 o.out('\\');
82 command.chars().for_each(|c| out(c.clone())); 180 command.chars().for_each(|c| o.out(c.clone()));
83 } 181 }
84 } 182 }
85 }; 183 };
86 }, 184 },
87 (Scan(next, o), Some(c)) => { 185 (Scan(next, out), Some(c)) => {
88 match c { 186 match c {
89 '{' => { 187 '{' => {
90 status_stack.pop(); 188 o.stack.pop();
91 status_stack.push(if o { Output(next) } else { Ignore(next) }); 189 o.stack.push(if out { Output(next) } else { Ignore(next) });
92 }, 190 },
93 ' ' => { 191 ' ' => {
94 }, 192 },
95 _ => panic!("Non-whitespace character ({c}) separating arguments on\ 193 _ => panic!("Non-whitespace character ({c}) separating arguments on\
96 line {lineno}"), 194 line {lineno}"),
97 } 195 }
98 }, 196 },
99 (Output(e), Some('{')) if e != Comment => { 197 (Output(e), Some('{')) if e != Comment => {
100 out('{'); 198 o.out('{');
101 status_stack.push(Output(Other)); 199 o.stack.push(Output(Other));
102 }, 200 },
103 (Ignore(e), Some('{')) if e != Comment => { 201 (Ignore(e), Some('{')) if e != Comment => {
104 status_stack.push(Ignore(Other)); 202 o.stack.push(Ignore(Other));
105 }, 203 },
106 (Output(Added) | Ignore(Added) | Output(Deleted) | Ignore(Deleted), Some('}')) => { 204 (Output(Added) | Ignore(Added) | Output(Deleted) | Ignore(Deleted), Some('}')) => {
107 status_stack.pop(); 205 o.stack.pop();
108 }, 206 },
109 (Output(Replaced) | Ignore(Replaced), Some('}')) => { 207 (Output(Replaced) | Ignore(Replaced), Some('}')) => {
110 status_stack.pop(); 208 o.stack.pop();
111 status_stack.push(Scan(Deleted, false)); 209 o.stack.push(Scan(Deleted, false));
112 }, 210 },
113 (Output(Other), Some('}')) => { 211 (Output(Other), Some('}')) => {
114 out('}'); 212 o.out('}');
115 status_stack.pop(); 213 o.stack.pop();
116 }, 214 },
117 (Ignore(_), Some('}')) => { 215 (Ignore(e), Some('}')) if e != Comment => {
118 status_stack.pop(); 216 o.stack.pop();
119 }, 217 },
120 (Output(e), Some('%')) if e != Comment=> { 218 (Output(e), Some('%')) if e != Comment=> {
121 out('%'); 219 if cli.strip_comments {
122 status_stack.push(Output(Comment)); 220 if o.stored_whitespace.is_empty() && !o.only_whitespace {
221 // Output comment marker if it is required to maintain
222 // lack of whitespace.
223 o.out('%');
224 }
225 o.stack.push(Ignore(Comment));
226 } else {
227 o.out('%');
228 o.stack.push(Output(Comment));
229 }
123 }, 230 },
124 (Ignore(e), Some('%')) if e != Comment => { 231 (Ignore(e), Some('%')) if e != Comment => {
125 status_stack.push(Ignore(Comment)); 232 o.stack.push(Ignore(Comment));
233 },
234 (Output(_), Some(c)) if c.is_whitespace() => {
235 o.whitespace(c);
126 }, 236 },
127 (Output(_), Some(c)) => { 237 (Output(_), Some(c)) => {
128 out(c); 238 o.out(c);
129 }, 239 },
130 (Ignore(_), Some(_)) => { 240 (Ignore(_), Some(_)) => {
131 }, 241 },
132 }; 242 };
133 } 243 }
134 match current(&status_stack) { 244
135 Ignore(e) => { 245 o.line_end(cli.strip_whitespace, input_only_ws);
136 if !started_ignore { 246 }
137 out('\n'); 247
138 } 248 o.flush();
139 if e == Comment { 249 }
140 status_stack.pop();
141 }
142 },
143 Output(e) => {
144 out('\n');
145 if e == Comment {
146 status_stack.pop();
147 }
148 },
149 Scan(_, _) => {
150 },
151 }
152 }
153
154 output.flush().unwrap();
155 }

mercurial