|
| 1 | +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT |
| 2 | +// file at the top-level directory of this distribution and at |
| 3 | +// http://rust-lang.org/COPYRIGHT. |
| 4 | +// |
| 5 | +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 6 | +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 7 | +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| 8 | +// option. This file may not be copied, modified, or distributed |
| 9 | +// except according to those terms. |
| 10 | + |
| 11 | +use std::{str, io}; |
| 12 | +use std::cell::RefCell; |
| 13 | +use std::vec_ng::Vec; |
| 14 | + |
| 15 | +use collections::HashSet; |
| 16 | + |
| 17 | +use getopts; |
| 18 | +use testing; |
| 19 | + |
| 20 | +use html::escape::Escape; |
| 21 | +use html::markdown::{Markdown, find_testable_code, reset_headers}; |
| 22 | +use test::Collector; |
| 23 | + |
| 24 | +fn load_string(input: &Path) -> io::IoResult<Option<~str>> { |
| 25 | + let mut f = try!(io::File::open(input)); |
| 26 | + let d = try!(f.read_to_end()); |
| 27 | + Ok(str::from_utf8_owned(d)) |
| 28 | +} |
| 29 | +macro_rules! load_or_return { |
| 30 | + ($input: expr, $cant_read: expr, $not_utf8: expr) => { |
| 31 | + { |
| 32 | + let input = Path::new($input); |
| 33 | + match load_string(&input) { |
| 34 | + Err(e) => { |
| 35 | + let _ = writeln!(&mut io::stderr(), |
| 36 | + "error reading `{}`: {}", input.display(), e); |
| 37 | + return $cant_read; |
| 38 | + } |
| 39 | + Ok(None) => { |
| 40 | + let _ = writeln!(&mut io::stderr(), |
| 41 | + "error reading `{}`: not UTF-8", input.display()); |
| 42 | + return $not_utf8; |
| 43 | + } |
| 44 | + Ok(Some(s)) => s |
| 45 | + } |
| 46 | + } |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +/// Separate any lines at the start of the file that begin with `%`. |
| 51 | +fn extract_leading_metadata<'a>(s: &'a str) -> (Vec<&'a str>, &'a str) { |
| 52 | + let mut metadata = Vec::new(); |
| 53 | + for line in s.lines() { |
| 54 | + if line.starts_with("%") { |
| 55 | + // remove %<whitespace> |
| 56 | + metadata.push(line.slice_from(1).trim_left()) |
| 57 | + } else { |
| 58 | + let line_start_byte = s.subslice_offset(line); |
| 59 | + return (metadata, s.slice_from(line_start_byte)); |
| 60 | + } |
| 61 | + } |
| 62 | + // if we're here, then all lines were metadata % lines. |
| 63 | + (metadata, "") |
| 64 | +} |
| 65 | + |
| 66 | +fn load_external_files(names: &[~str]) -> Option<~str> { |
| 67 | + let mut out = ~""; |
| 68 | + for name in names.iter() { |
| 69 | + out.push_str(load_or_return!(name.as_slice(), None, None)); |
| 70 | + out.push_char('\n'); |
| 71 | + } |
| 72 | + Some(out) |
| 73 | +} |
| 74 | + |
| 75 | +/// Render `input` (e.g. "foo.md") into an HTML file in `output` |
| 76 | +/// (e.g. output = "bar" => "bar/foo.html"). |
| 77 | +pub fn render(input: &str, mut output: Path, matches: &getopts::Matches) -> int { |
| 78 | + let input_p = Path::new(input); |
| 79 | + output.push(input_p.filestem().unwrap()); |
| 80 | + output.set_extension("html"); |
| 81 | + |
| 82 | + let mut css = ~""; |
| 83 | + for name in matches.opt_strs("markdown-css").iter() { |
| 84 | + let s = format!("<link rel=\"stylesheet\" type=\"text/css\" href=\"{}\">\n", name); |
| 85 | + css.push_str(s) |
| 86 | + } |
| 87 | + |
| 88 | + let input_str = load_or_return!(input, 1, 2); |
| 89 | + |
| 90 | + let (in_header, before_content, after_content) = |
| 91 | + match (load_external_files(matches.opt_strs("markdown-in-header")), |
| 92 | + load_external_files(matches.opt_strs("markdown-before-content")), |
| 93 | + load_external_files(matches.opt_strs("markdown-after-content"))) { |
| 94 | + (Some(a), Some(b), Some(c)) => (a,b,c), |
| 95 | + _ => return 3 |
| 96 | + }; |
| 97 | + |
| 98 | + let mut out = match io::File::create(&output) { |
| 99 | + Err(e) => { |
| 100 | + let _ = writeln!(&mut io::stderr(), |
| 101 | + "error opening `{}` for writing: {}", |
| 102 | + output.display(), e); |
| 103 | + return 4; |
| 104 | + } |
| 105 | + Ok(f) => f |
| 106 | + }; |
| 107 | + |
| 108 | + let (metadata, text) = extract_leading_metadata(input_str); |
| 109 | + if metadata.len() == 0 { |
| 110 | + let _ = writeln!(&mut io::stderr(), |
| 111 | + "invalid markdown file: expecting initial line with `% ...TITLE...`"); |
| 112 | + return 5; |
| 113 | + } |
| 114 | + let title = metadata.get(0).as_slice(); |
| 115 | + |
| 116 | + reset_headers(); |
| 117 | + |
| 118 | + let err = write!( |
| 119 | + &mut out, |
| 120 | + r#"<!doctype html> |
| 121 | +<html lang="en"> |
| 122 | +<head> |
| 123 | + <meta charset="utf-8"> |
| 124 | + <meta name="generator" content="rustdoc"> |
| 125 | + <title>{title}</title> |
| 126 | +
|
| 127 | + {css} |
| 128 | + {in_header} |
| 129 | +</head> |
| 130 | +<body> |
| 131 | + <!--[if lte IE 8]> |
| 132 | + <div class="warning"> |
| 133 | + This old browser is unsupported and will most likely display funky |
| 134 | + things. |
| 135 | + </div> |
| 136 | + <![endif]--> |
| 137 | +
|
| 138 | + {before_content} |
| 139 | + <h1 class="title">{title}</h1> |
| 140 | + {text} |
| 141 | + {after_content} |
| 142 | +</body> |
| 143 | +</html>"#, |
| 144 | + title = Escape(title), |
| 145 | + css = css, |
| 146 | + in_header = in_header, |
| 147 | + before_content = before_content, |
| 148 | + text = Markdown(text), |
| 149 | + after_content = after_content); |
| 150 | + |
| 151 | + match err { |
| 152 | + Err(e) => { |
| 153 | + let _ = writeln!(&mut io::stderr(), |
| 154 | + "error writing to `{}`: {}", |
| 155 | + output.display(), e); |
| 156 | + 6 |
| 157 | + } |
| 158 | + Ok(_) => 0 |
| 159 | + } |
| 160 | +} |
| 161 | + |
| 162 | +/// Run any tests/code examples in the markdown file `input`. |
| 163 | +pub fn test(input: &str, libs: @RefCell<HashSet<Path>>, mut test_args: ~[~str]) -> int { |
| 164 | + let input_str = load_or_return!(input, 1, 2); |
| 165 | + |
| 166 | + let mut collector = Collector::new(input.to_owned(), libs, true); |
| 167 | + find_testable_code(input_str, &mut collector); |
| 168 | + test_args.unshift(~"rustdoctest"); |
| 169 | + testing::test_main(test_args, collector.tests); |
| 170 | + 0 |
| 171 | +} |
0 commit comments