182 lines
6.0 KiB
Rust
182 lines
6.0 KiB
Rust
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
|
// file at the top-level directory of this distribution and at
|
|
// http://rust-lang.org/COPYRIGHT.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
// option. This file may not be copied, modified, or distributed
|
|
// except according to those terms.
|
|
|
|
use collections::HashSet;
|
|
use std::{str, io};
|
|
use std::strbuf::StrBuf;
|
|
|
|
use getopts;
|
|
use testing;
|
|
|
|
use html::escape::Escape;
|
|
use html::markdown::{MarkdownWithToc, find_testable_code, reset_headers};
|
|
use test::Collector;
|
|
|
|
fn load_string(input: &Path) -> io::IoResult<Option<StrBuf>> {
|
|
let mut f = try!(io::File::open(input));
|
|
let d = try!(f.read_to_end());
|
|
Ok(str::from_utf8(d.as_slice()).map(|s| s.to_strbuf()))
|
|
}
|
|
macro_rules! load_or_return {
|
|
($input: expr, $cant_read: expr, $not_utf8: expr) => {
|
|
{
|
|
let input = Path::new($input);
|
|
match load_string(&input) {
|
|
Err(e) => {
|
|
let _ = writeln!(&mut io::stderr(),
|
|
"error reading `{}`: {}", input.display(), e);
|
|
return $cant_read;
|
|
}
|
|
Ok(None) => {
|
|
let _ = writeln!(&mut io::stderr(),
|
|
"error reading `{}`: not UTF-8", input.display());
|
|
return $not_utf8;
|
|
}
|
|
Ok(Some(s)) => s
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Separate any lines at the start of the file that begin with `%`.
|
|
fn extract_leading_metadata<'a>(s: &'a str) -> (Vec<&'a str>, &'a str) {
|
|
let mut metadata = Vec::new();
|
|
for line in s.lines() {
|
|
if line.starts_with("%") {
|
|
// remove %<whitespace>
|
|
metadata.push(line.slice_from(1).trim_left())
|
|
} else {
|
|
let line_start_byte = s.subslice_offset(line);
|
|
return (metadata, s.slice_from(line_start_byte));
|
|
}
|
|
}
|
|
// if we're here, then all lines were metadata % lines.
|
|
(metadata, "")
|
|
}
|
|
|
|
fn load_external_files(names: &[StrBuf]) -> Option<StrBuf> {
|
|
let mut out = StrBuf::new();
|
|
for name in names.iter() {
|
|
out.push_str(load_or_return!(name.as_slice(), None, None).as_slice());
|
|
out.push_char('\n');
|
|
}
|
|
Some(out)
|
|
}
|
|
|
|
/// Render `input` (e.g. "foo.md") into an HTML file in `output`
|
|
/// (e.g. output = "bar" => "bar/foo.html").
|
|
pub fn render(input: &str, mut output: Path, matches: &getopts::Matches) -> int {
|
|
let input_p = Path::new(input);
|
|
output.push(input_p.filestem().unwrap());
|
|
output.set_extension("html");
|
|
|
|
let mut css = StrBuf::new();
|
|
for name in matches.opt_strs("markdown-css").iter() {
|
|
let s = format!("<link rel=\"stylesheet\" type=\"text/css\" href=\"{}\">\n", name);
|
|
css.push_str(s)
|
|
}
|
|
|
|
let input_str = load_or_return!(input, 1, 2);
|
|
|
|
let (in_header, before_content, after_content) =
|
|
match (load_external_files(matches.opt_strs("markdown-in-header")
|
|
.move_iter()
|
|
.map(|x| x.to_strbuf())
|
|
.collect::<Vec<_>>()
|
|
.as_slice()),
|
|
load_external_files(matches.opt_strs("markdown-before-content")
|
|
.move_iter()
|
|
.map(|x| x.to_strbuf())
|
|
.collect::<Vec<_>>()
|
|
.as_slice()),
|
|
load_external_files(matches.opt_strs("markdown-after-content")
|
|
.move_iter()
|
|
.map(|x| x.to_strbuf())
|
|
.collect::<Vec<_>>()
|
|
.as_slice())) {
|
|
(Some(a), Some(b), Some(c)) => (a,b,c),
|
|
_ => return 3
|
|
};
|
|
|
|
let mut out = match io::File::create(&output) {
|
|
Err(e) => {
|
|
let _ = writeln!(&mut io::stderr(),
|
|
"error opening `{}` for writing: {}",
|
|
output.display(), e);
|
|
return 4;
|
|
}
|
|
Ok(f) => f
|
|
};
|
|
|
|
let (metadata, text) = extract_leading_metadata(input_str.as_slice());
|
|
if metadata.len() == 0 {
|
|
let _ = writeln!(&mut io::stderr(),
|
|
"invalid markdown file: expecting initial line with `% ...TITLE...`");
|
|
return 5;
|
|
}
|
|
let title = metadata.get(0).as_slice();
|
|
|
|
reset_headers();
|
|
|
|
let err = write!(
|
|
&mut out,
|
|
r#"<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="generator" content="rustdoc">
|
|
<title>{title}</title>
|
|
|
|
{css}
|
|
{in_header}
|
|
</head>
|
|
<body>
|
|
<!--[if lte IE 8]>
|
|
<div class="warning">
|
|
This old browser is unsupported and will most likely display funky
|
|
things.
|
|
</div>
|
|
<![endif]-->
|
|
|
|
{before_content}
|
|
<h1 class="title">{title}</h1>
|
|
{text}
|
|
{after_content}
|
|
</body>
|
|
</html>"#,
|
|
title = Escape(title),
|
|
css = css,
|
|
in_header = in_header,
|
|
before_content = before_content,
|
|
text = MarkdownWithToc(text),
|
|
after_content = after_content);
|
|
|
|
match err {
|
|
Err(e) => {
|
|
let _ = writeln!(&mut io::stderr(),
|
|
"error writing to `{}`: {}",
|
|
output.display(), e);
|
|
6
|
|
}
|
|
Ok(_) => 0
|
|
}
|
|
}
|
|
|
|
/// Run any tests/code examples in the markdown file `input`.
|
|
pub fn test(input: &str, libs: HashSet<Path>, mut test_args: Vec<StrBuf>) -> int {
|
|
let input_str = load_or_return!(input, 1, 2);
|
|
|
|
let mut collector = Collector::new(input.to_strbuf(), libs, true, true);
|
|
find_testable_code(input_str.as_slice(), &mut collector);
|
|
test_args.unshift("rustdoctest".to_strbuf());
|
|
testing::test_main(test_args.as_slice(), collector.tests);
|
|
0
|
|
}
|