(index<- )        ./librustdoc/markdown.rs

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