From ee518773d1dbfe258b3220e991bb193de8d0b4e7 Mon Sep 17 00:00:00 2001 From: Daniel Fagnan Date: Thu, 7 Aug 2014 14:27:23 -0600 Subject: [PATCH] Rustdoc: Frontmatter Support This is the initial PR to add frontmatter support. Note that it currently only supports strings (and booleans) as values. Lists, integers and such can be added in future PRs. This doesn't integrate the frontmatter into the rest of rustdoc as I wanted to get this out there. --- src/librustdoc/frontmatter.rs | 285 ++++++++++++++++++++++++++++++++++ src/librustdoc/lib.rs | 1 + 2 files changed, 286 insertions(+) create mode 100644 src/librustdoc/frontmatter.rs diff --git a/src/librustdoc/frontmatter.rs b/src/librustdoc/frontmatter.rs new file mode 100644 index 0000000000000..6e2d37aed0bac --- /dev/null +++ b/src/librustdoc/frontmatter.rs @@ -0,0 +1,285 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::io::{Buffer, IoResult, IoError, OtherIoError}; +use std::char::is_alphanumeric; +use std::collections::hashmap::HashMap; + +#[deriving(PartialEq, Show, Clone)] +pub enum Token { + Noop, + Matter, + Colon, + Ident(String), + TString(String) +} + +#[deriving(Hash, PartialEq, Show)] +pub enum Value { + VString(String), + VBool(bool), + VInt(int), + VUint(uint) +} + +pub struct Frontmatter { + lexer: Lexer, + pub keys: HashMap +} + +impl Frontmatter { + pub fn new(buf: R) -> Frontmatter { + Frontmatter { + lexer: Lexer::new(buf), + keys: HashMap::new() + } + } + + pub fn expect(&mut self, tok: Token) -> bool { + match self.lexer.bump() { + Ok(ref t) if t == &tok => true, + _ => false + } + } + + pub fn parse_pair(&mut self, key: String) -> Result<(), &'static str> { + if !self.expect(Colon) { + return Err("Expected a colon"); + } + + match self.lexer.bump() { + Ok(ref v) => { + let val = match v { + &TString(ref s) => VString(s.clone()), + _ => return Err("Further types have not been implemented yet.") + }; + self.keys.insert(key, val); + }, + _ => return Err("Failed!") + } + + Ok(()) + } + + pub fn parse_keys(&mut self) -> Result<(), &'static str> { + + loop { + match self.lexer.bump() { + Ok(Ident(f)) => { + try!(self.parse_pair(f)); + }, + _ => break + } + } + + Ok(()) + } + + pub fn parse(&mut self) -> Result, &'static str> { + if !self.expect(Matter) { + return Err("Expected a matter token."); + } + + try!(self.parse_keys()); + + if !self.expect(Matter) { + return Err("Expected a matter token."); + } + + Ok(self.lexer.buf.read_to_end().unwrap()) + } +} + +pub struct Lexer { + buf: R, + next_token: Option +} + +impl Lexer { + pub fn new(buf: R) -> Lexer { + Lexer { + buf: buf, + next_token: None + } + } + + pub fn bump(&mut self) -> IoResult { + if self.next_token.is_some() { + return Ok(self.next_token.take().unwrap()); + } + + let ch = try!(self.buf.read_char()); + + match ch { + '-' => { + let line = try!(self.buf.read_line()); + if line.as_slice() == "--" || line.as_slice() == "--\n" { + return Ok(Matter); + } + }, + ':' => return Ok(Colon), + '\n' => { return self.bump() }, + ' ' => { return self.bump() }, + '"' => { + let mut val = String::new(); + + loop { + let next_ch = try!(self.buf.read_char()); + + if next_ch == '"' { + break; + } else { + val.push_char(next_ch); + } + } + + return Ok(TString(val)); + }, + 'A'..'Z' | 'a'..'z' => { + let mut ident = String::new(); + ident.push_char(ch); + + loop { + let next_ch = try!(self.buf.read_char()); + if is_alphanumeric(next_ch) { + ident.push_char(next_ch); + continue; + } else if next_ch == ':' { + self.next_token = Some(Colon); + } + + break; + } + + return Ok(Ident(ident)); + }, + _ => {} + } + + Err(IoError { kind: OtherIoError, desc: "Invalid token", detail: None }) + } +} + + +#[cfg(test)] +mod tests { + use super::*; + use std::io::MemReader; + + #[test] + fn empty_source() { + let buf = MemReader::new(vec![]); + let mut lex = Lexer::new(buf); + + assert!(lex.bump().is_err()); + } + + #[test] + fn matter_one() { + let src = String::from_str("---").into_bytes(); + let buf = MemReader::new(src); + let mut lex = Lexer::new(buf); + + assert_eq!(lex.bump().unwrap(), Matter); + } + + #[test] + fn matter_two() { + let src = String::from_str("---\n---").into_bytes(); + let buf = MemReader::new(src); + let mut lex = Lexer::new(buf); + + assert_eq!(lex.bump().unwrap(), Matter); + assert_eq!(lex.bump().unwrap(), Matter); + } + + #[test] + fn ident() { + let src = String::from_str("---\nfoon\n---").into_bytes(); + let buf = MemReader::new(src); + let mut lex = Lexer::new(buf); + + assert_eq!(lex.bump().unwrap(), Matter); + assert_eq!(lex.bump().unwrap(), Ident("foon".to_string())); + assert_eq!(lex.bump().unwrap(), Matter); + } + + #[test] + fn ident_colon() { + let src = String::from_str("---\nfoon:\n---").into_bytes(); + let buf = MemReader::new(src); + let mut lex = Lexer::new(buf); + + assert_eq!(lex.bump().unwrap(), Matter); + assert_eq!(lex.bump().unwrap(), Ident("foon".to_string())); + assert_eq!(lex.bump().unwrap(), Colon); + assert_eq!(lex.bump().unwrap(), Matter); + } + + #[test] + fn ident_colon_value_str() { + let src = String::from_str("---\nfoon: \"bar\"\n---").into_bytes(); + let buf = MemReader::new(src); + let mut lex = Lexer::new(buf); + + assert_eq!(lex.bump().unwrap(), Matter); + assert_eq!(lex.bump().unwrap(), Ident("foon".to_string())); + assert_eq!(lex.bump().unwrap(), Colon); + assert_eq!(lex.bump().unwrap(), TString("bar".to_string())); + assert_eq!(lex.bump().unwrap(), Matter); + } + + #[test] + fn ident_colon_value_multiline_str() { + let src = String::from_str("---\nfoon: \"b\nar\"\n---").into_bytes(); + let buf = MemReader::new(src); + let mut lex = Lexer::new(buf); + + assert_eq!(lex.bump().unwrap(), Matter); + assert_eq!(lex.bump().unwrap(), Ident("foon".to_string())); + assert_eq!(lex.bump().unwrap(), Colon); + assert_eq!(lex.bump().unwrap(), TString("b\nar".to_string())); + assert_eq!(lex.bump().unwrap(), Matter); + } + + #[test] + fn ident_colon_value_bool() { + let src = String::from_str("---\nfoon: true\n---").into_bytes(); + let buf = MemReader::new(src); + let mut lex = Lexer::new(buf); + + assert_eq!(lex.bump().unwrap(), Matter); + assert_eq!(lex.bump().unwrap(), Ident("foon".to_string())); + assert_eq!(lex.bump().unwrap(), Colon); + assert_eq!(lex.bump().unwrap(), Ident("true".to_string())); + assert_eq!(lex.bump().unwrap(), Matter); + } + + #[test] + fn parse_keyval() { + let src = String::from_str("---\nfoon: \"bar\"\n---").into_bytes(); + let buf = MemReader::new(src); + let mut matter = Frontmatter::new(buf); + matter.parse(); + + assert_eq!(matter.keys.get(&"foon".to_string()), &VString("bar".to_string())); + } + + #[test] + fn parse_multi_keyval() { + let src = String::from_str("---\nfoon: \"bar\"\ntitle: \"Something\"\n---").into_bytes(); + let buf = MemReader::new(src); + let mut matter = Frontmatter::new(buf); + matter.parse(); + + assert_eq!(matter.keys.get(&"foon".to_string()), &VString("bar".to_string())); + assert_eq!(matter.keys.get(&"title".to_string()), &VString("Something".to_string())); + } +} diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index fb974403858a0..68a17e1052d59 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -37,6 +37,7 @@ use externalfiles::ExternalHtml; // reexported from `clean` so it can be easily updated with the mod itself pub use clean::SCHEMA_VERSION; +pub mod frontmatter; pub mod clean; pub mod core; pub mod doctree;