]>
Commit | Line | Data |
---|---|---|
ea8adc8c XL |
1 | // Copyright 2015 Google Inc. All rights reserved. |
2 | // | |
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy | |
4 | // of this software and associated documentation files (the "Software"), to deal | |
5 | // in the Software without restriction, including without limitation the rights | |
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
7 | // copies of the Software, and to permit persons to whom the Software is | |
8 | // furnished to do so, subject to the following conditions: | |
9 | // | |
10 | // The above copyright notice and this permission notice shall be included in | |
11 | // all copies or substantial portions of the Software. | |
12 | // | |
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
19 | // THE SOFTWARE. | |
20 | ||
21 | //! Command line tool to exercise pulldown-cmark. | |
22 | ||
23 | extern crate getopts; | |
24 | ||
25 | extern crate pulldown_cmark; | |
26 | ||
27 | use pulldown_cmark::Parser; | |
28 | use pulldown_cmark::{Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES}; | |
29 | use pulldown_cmark::html; | |
30 | ||
31 | use std::env; | |
32 | use std::io; | |
33 | use std::io::{Read, Write}; | |
34 | use std::path::Path; | |
35 | use std::fs::File; | |
36 | ||
37 | fn render_html(text: &str, opts: Options) -> String { | |
38 | let mut s = String::with_capacity(text.len() * 3 / 2); | |
39 | let p = Parser::new_ext(text, opts); | |
40 | html::push_html(&mut s, p); | |
41 | s | |
42 | } | |
43 | ||
44 | fn dry_run(text:&str, opts: Options) { | |
45 | let p = Parser::new_ext(text, opts); | |
46 | /* | |
47 | let events = p.collect::<Vec<_>>(); | |
48 | let count = events.len(); | |
49 | */ | |
50 | let count = p.count(); | |
51 | println!("{} events", count); | |
52 | } | |
53 | ||
54 | fn print_events(text: &str, opts: Options) { | |
55 | let mut p = Parser::new_ext(text, opts); | |
56 | loop { | |
57 | print!("{}: ", p.get_offset()); | |
58 | if let Some(event) = p.next() { | |
59 | println!("{:?}", event); | |
60 | } else { | |
61 | break; | |
62 | } | |
63 | } | |
64 | println!("EOF"); | |
65 | } | |
66 | ||
67 | fn read_file(filename: &str) -> String { | |
68 | let path = Path::new(filename); | |
69 | let mut file = match File::open(&path) { | |
70 | Err(why) => panic!("couldn't open {}: {}", path.display(), why), | |
71 | Ok(file) => file | |
72 | }; | |
73 | let mut s = String::new(); | |
74 | match file.read_to_string(&mut s) { | |
75 | Err(why) => panic!("couldn't open {}: {}", path.display(), why), | |
76 | Ok(_) => s | |
77 | } | |
78 | } | |
79 | ||
80 | // Tests in the spec (v0.26) are of the form: | |
81 | // | |
82 | // ```````````````````````````````` example | |
83 | // <markdown input> | |
84 | // . | |
85 | // <expected output> | |
86 | // ```````````````````````````````` | |
87 | struct Spec<'a> { | |
88 | spec: &'a str, | |
89 | test_n: usize, | |
90 | } | |
91 | ||
92 | impl<'a> Spec<'a> { | |
93 | pub fn new(spec: &'a str) -> Self { | |
94 | Spec{ spec: spec, test_n: 0 } | |
95 | } | |
96 | } | |
97 | ||
98 | struct TestCase<'a> { | |
99 | n: usize, | |
100 | input: &'a str, | |
101 | expected: &'a str, | |
102 | } | |
103 | ||
104 | impl<'a> TestCase<'a> { | |
105 | pub fn new(n: usize, input: &'a str, expected: &'a str) -> Self { | |
106 | TestCase { n: n, input: input, expected: expected } | |
107 | } | |
108 | } | |
109 | ||
110 | impl<'a> Iterator for Spec<'a> { | |
111 | type Item = TestCase<'a>; | |
112 | ||
113 | fn next(&mut self) -> Option<TestCase<'a>> { | |
114 | let spec = self.spec; | |
115 | ||
116 | let i_start = match self.spec.find("```````````````````````````````` example\n").map(|pos| pos + 41) { | |
117 | Some(pos) => pos, | |
118 | None => return None, | |
119 | }; | |
120 | ||
121 | let i_end = match self.spec[i_start..].find("\n.\n").map(|pos| pos + i_start){ | |
122 | Some(pos) => pos, | |
123 | None => return None, | |
124 | }; | |
125 | ||
126 | let e_end = match self.spec[i_end + 3..].find("````````````````````````````````\n").map(|pos| pos + i_end + 3){ | |
127 | Some(pos) => pos, | |
128 | None => return None, | |
129 | }; | |
130 | ||
131 | self.test_n += 1; | |
132 | self.spec = &self.spec[e_end + 33 ..]; | |
133 | ||
134 | Some(TestCase::new(self.test_n, &spec[i_start .. i_end], &spec[i_end + 3 .. e_end])) | |
135 | } | |
136 | } | |
137 | ||
138 | ||
139 | fn run_spec(spec_text: &str, args: &[String], opts: Options) { | |
140 | //println!("spec length={}, args={:?}", spec_text.len(), args); | |
141 | let (first, last) = if args.is_empty() { | |
142 | (None, None) | |
143 | } else { | |
144 | let mut iter = args[0].split(".."); | |
145 | let first = iter.next().and_then(|s| s.parse().ok()); | |
146 | let last = match iter.next() { | |
147 | Some(s) => s.parse().ok(), | |
148 | None => first | |
149 | }; | |
150 | (first, last) | |
151 | }; | |
152 | ||
153 | let spec = Spec::new(spec_text); | |
154 | let mut tests_failed = 0; | |
155 | let mut tests_run = 0; | |
156 | let mut line_count = 0; | |
157 | ||
158 | for test in spec { | |
159 | if first.map(|fst| test.n < fst).unwrap_or(false) { continue } | |
160 | if last.map(|lst| test.n > lst).unwrap_or(false) { break } | |
161 | ||
162 | if tests_run == 0 || line_count == 0 || (test.n % 10 == 0) { | |
163 | if line_count > 30 { | |
164 | println!(""); | |
165 | line_count = 0; | |
166 | } else if line_count > 0 { | |
167 | print!(" "); | |
168 | } | |
169 | print!("[{:3}]", test.n); | |
170 | } else if line_count > 0 && (test.n % 10) == 5 { | |
171 | print!(" "); | |
172 | } | |
173 | ||
174 | let our_html = render_html(&test.input.replace("→", "\t").replace("\n", "\r\n"), opts); | |
175 | ||
176 | if our_html == test.expected.replace("→", "\t") { | |
177 | print!("."); | |
178 | } else { | |
179 | if tests_failed == 0 { | |
180 | print!("FAIL {}:\n\n---input---\n{}\n\n---wanted---\n{}\n\n---got---\n{}\n", | |
181 | test.n, test.input, test.expected, our_html); | |
182 | } else { | |
183 | print!("X"); | |
184 | } | |
185 | tests_failed += 1; | |
186 | } | |
187 | ||
188 | let _ = io::stdout().flush(); | |
189 | tests_run += 1; | |
190 | line_count += 1; | |
191 | } | |
192 | ||
193 | println!("\n{}/{} tests passed", tests_run - tests_failed, tests_run) | |
194 | } | |
195 | ||
196 | fn brief<ProgramName>(program: ProgramName) -> String | |
197 | where ProgramName: std::fmt::Display { | |
198 | return format!("Usage: {} FILE [options]", program); | |
199 | } | |
200 | ||
201 | pub fn main() { | |
202 | let args: Vec<_> = env::args().collect(); | |
203 | let mut opts = getopts::Options::new(); | |
204 | opts.optflag("h", "help", "this help message"); | |
205 | opts.optflag("d", "dry-run", "dry run, produce no output"); | |
206 | opts.optflag("e", "events", "print event sequence instead of rendering"); | |
207 | opts.optflag("T", "enable-tables", "enable GitHub-style tables"); | |
208 | opts.optflag("F", "enable-footnotes", "enable Hoedown-style footnotes"); | |
209 | opts.optopt("s", "spec", "run tests from spec file", "FILE"); | |
210 | opts.optopt("b", "bench", "run benchmark", "FILE"); | |
211 | let matches = match opts.parse(&args[1..]) { | |
212 | Ok(m) => m, | |
213 | Err(f) => { | |
214 | let message = format!("{}\n{}\n", | |
215 | f.to_string(), | |
216 | opts.usage(&brief(&args[0]))); | |
217 | if let Err(err) = write!(std::io::stderr(), "{}", message) { | |
218 | panic!("Failed to write to standard error: {}\n\ | |
219 | Error encountered while trying to log the \ | |
220 | following message: \"{}\"", | |
221 | err, | |
222 | message); | |
223 | } | |
224 | std::process::exit(1); | |
225 | } | |
226 | }; | |
227 | if matches.opt_present("help") { | |
228 | println!("{}", opts.usage(&brief(&args[0]))); | |
229 | return; | |
230 | } | |
231 | let mut opts = Options::empty(); | |
232 | if matches.opt_present("enable-tables") { | |
233 | opts.insert(OPTION_ENABLE_TABLES); | |
234 | } | |
235 | if matches.opt_present("enable-footnotes") { | |
236 | opts.insert(OPTION_ENABLE_FOOTNOTES); | |
237 | } | |
238 | if let Some(filename) = matches.opt_str("spec") { | |
239 | run_spec(&read_file(&filename), &matches.free, opts); | |
240 | } else if let Some(filename) = matches.opt_str("bench") { | |
241 | let inp = read_file(&filename); | |
242 | for _ in 0..1000 { | |
243 | let _ = render_html(&inp, opts); | |
244 | } | |
245 | } else { | |
246 | let mut input = String::new(); | |
247 | if let Err(why) = io::stdin().read_to_string(&mut input) { | |
248 | panic!("couldn't read from stdin: {}", why) | |
249 | } | |
250 | if matches.opt_present("events") { | |
251 | print_events(&input, opts); | |
252 | } else if matches.opt_present("dry-run") { | |
253 | dry_run(&input, opts); | |
254 | } else { | |
255 | print!("{}", render_html(&input, opts)); | |
256 | } | |
257 | } | |
258 | } |