Compare commits

...

17 Commits

1 changed files with 382 additions and 4 deletions

View File

@ -1,7 +1,16 @@
pub enum ProgrammingLanguage {
None,
#[derive(Debug, PartialEq)]
struct Url {
scheme: String,
user: String,
password: String,
host: String,
port: u16,
path: String,
query: String,
fragment: String,
}
#[derive(Debug, PartialEq)]
pub struct RGBA {
red: u8,
green: u8,
@ -9,13 +18,382 @@ pub struct RGBA {
alpha: u8,
}
#[derive(Debug, PartialEq)]
pub struct Header {
label: String,
level: u8,
}
#[derive(Debug, PartialEq)]
pub struct CodeBlock {
language: ProgrammingLanguage,
language: String,
text: String,
}
pub fn parse() {}
#[derive(Debug, PartialEq)]
pub struct StyledText {
italic: bool,
bold: bool,
underline: bool,
color: Option<RGBA>,
text: String,
}
#[derive(Debug, PartialEq)]
pub struct Blockquote {
level: u8,
text: String,
}
#[derive(Debug, PartialEq)]
pub struct Link {
text: String,
url: Url,
title: Option<String>,
}
#[derive(Debug, PartialEq)]
pub struct Image {
text: String,
image: Url,
title: Option<String>,
link: Option<Link>,
}
#[derive(Debug, PartialEq)]
pub enum Node {
Header(Header),
Paragraph(Vec<StyledText>),
HRule,
}
#[derive(Debug, PartialEq)]
pub struct Document(Vec<Node>);
// TODO: able take care of lists i am too drunk for this tonight
pub enum ListType {
Ordered,
Unordered,
}
pub struct List {}
pub fn parse(source: &str) -> Document {
let lines = source.lines().collect::<Vec<_>>();
let mut document = Vec::new();
let mut to_parse = &lines[..];
while let Some((node, remaining)) = parse_node(to_parse) {
document.push(node);
to_parse = &to_parse[remaining..];
}
Document(document)
}
/// Parse a single node from the list of lines, if one exists. Return
/// the node parsed, and the number of lines of input consumed.
fn parse_node(mut lines: &[&str]) -> Option<(Node, usize)> {
if lines.is_empty() {
return None;
}
while lines[0].is_empty() {
lines = &lines[1..];
if lines.is_empty() {
return None;
}
}
None.or_else(|| parse_normal_header(lines))
.or_else(|| parse_underline_header(lines))
.or_else(|| parse_hrule(lines))
.or_else(|| parse_paragraph(lines))
}
/// Parse a header as a number of "#"-signs followed by the header
/// text.
fn parse_normal_header(lines: &[&str]) -> Option<(Node, usize)> {
if lines[0].starts_with('#') {
let depth = lines[0].chars().take_while(|&c| c == '#').count();
let text = lines[0][depth..].trim().to_string();
Some((
Node::Header(Header {
label: text,
level: depth as u8,
}),
1,
))
} else {
None
}
}
/// Parse a header as a single line of text followed by an underline,
/// i.e. a line of "-" or "=" characters.
fn parse_underline_header(lines: &[&str]) -> Option<(Node, usize)> {
if lines.len() < 2 {
return None;
}
let text = lines[0];
let underline = lines[1];
if !text.is_empty() && !underline.is_empty() {
if underline.chars().all(|c| c == '-') {
Some((
Node::Header(Header {
label: text.to_string(),
level: 2,
}),
2,
))
} else if underline.chars().all(|c| c == '=') {
Some((
Node::Header(Header {
label: text.to_string(),
level: 1,
}),
2,
))
} else {
None
}
} else {
None
}
}
/// Parse a horizontal rule.
fn parse_hrule(lines: &[&str]) -> Option<(Node, usize)> {
let line = lines[0];
if !line.is_empty() && (line.chars().all(|c| c == '-') || line.chars().all(|c| c == '=')) {
Some((Node::HRule, 1))
} else {
None
}
}
/// Parse a paragraph of text.
fn parse_paragraph(lines: &[&str]) -> Option<(Node, usize)> {
let len = lines.iter().take_while(|line| !line.is_empty()).count();
let text = lines[0..len].join(" ");
// TODO: Parse style components from `text` here, and turn it into
// a Vec<StyledText>.
Some((
Node::Paragraph(vec![StyledText {
italic: false,
bold: false,
underline: false,
color: None,
text,
}]),
len,
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_text() {
let doc = parse("Hello World!");
let doc_ref = Document(vec![Node::Paragraph(vec![StyledText {
italic: false,
bold: false,
underline: false,
color: None,
text: "Hello World!".to_string(),
}])]);
assert_eq!(doc, doc_ref);
}
/// Single paragraphs are allowed to include newlines; double
/// newlines, however, separate paragraphs.
#[test]
fn paragraphs() {
let doc = parse(
"\
Hello
World!
New paragraph",
);
let doc_ref = Document(vec![
Node::Paragraph(vec![StyledText {
italic: false,
bold: false,
underline: false,
color: None,
text: "Hello World!".to_string(),
}]),
Node::Paragraph(vec![StyledText {
italic: false,
bold: false,
underline: false,
color: None,
text: "New paragraph".to_string(),
}]),
]);
assert_eq!(doc, doc_ref);
}
/// Italics and bold.
#[test]
fn emphasis() {
let doc = parse("Normal *italics* **bold** ***both***");
let doc_ref = Document(vec![Node::Paragraph(vec![
StyledText {
italic: false,
bold: false,
underline: false,
color: None,
text: "Normal ".to_string(),
},
StyledText {
italic: true,
bold: false,
underline: false,
color: None,
text: "italics".to_string(),
},
StyledText {
italic: false,
bold: false,
underline: false,
color: None,
text: " ".to_string(),
},
StyledText {
italic: false,
bold: true,
underline: false,
color: None,
text: "bold".to_string(),
},
StyledText {
italic: false,
bold: false,
underline: false,
color: None,
text: " ".to_string(),
},
StyledText {
italic: true,
bold: true,
underline: false,
color: None,
text: "both".to_string(),
},
])]);
assert_eq!(doc, doc_ref);
}
/// Test "#" headers and their interactions with paragraphs.
#[test]
fn headers() {
let doc = parse(
"\
Paragraph 1
# Header 1
Paragraph 2
## Subheader 1
Paragraph 3
## Subheader 2
Paragraph 4",
);
let doc_ref = Document(vec![
Node::Paragraph(vec![StyledText {
italic: false,
bold: false,
underline: false,
color: None,
text: "Paragraph 1".to_string(),
}]),
Node::Header(Header {
label: "Header 1".to_string(),
level: 1,
}),
Node::Paragraph(vec![StyledText {
italic: false,
bold: false,
underline: false,
color: None,
text: "Paragraph 2".to_string(),
}]),
Node::Header(Header {
label: "Subheader 1".to_string(),
level: 2,
}),
Node::Paragraph(vec![StyledText {
italic: false,
bold: false,
underline: false,
color: None,
text: "Paragraph 3".to_string(),
}]),
Node::Header(Header {
label: "Subheader 2".to_string(),
level: 2,
}),
Node::Paragraph(vec![StyledText {
italic: false,
bold: false,
underline: false,
color: None,
text: "Paragraph 4".to_string(),
}]),
]);
assert_eq!(doc, doc_ref);
}
/// Test headers with underlines.
#[test]
fn alt_headers() {
let doc = parse(
"\
Text
Header 1
========
More text
Header 2
--------
",
);
let doc_ref = Document(vec![
Node::Paragraph(vec![StyledText {
italic: false,
bold: false,
underline: false,
color: None,
text: "Text".to_string(),
}]),
Node::Header(Header {
label: "Header 1".to_string(),
level: 1,
}),
Node::Paragraph(vec![StyledText {
italic: false,
bold: false,
underline: false,
color: None,
text: "More text".to_string(),
}]),
Node::Header(Header {
label: "Header 2".to_string(),
level: 2,
}),
]);
assert_eq!(doc, doc_ref);
}
}