Compare commits
17 Commits
Author | SHA1 | Date |
---|---|---|
Alex Bethel | 2acfa8ac22 | |
Alex Bethel | f98636aae7 | |
Alex Bethel | f6112f70df | |
Alex Bethel | 71c9249916 | |
Alex Bethel | 2b6e6746f9 | |
Able | 8d8ecbcbd1 | |
Able | 3f39f4504a | |
Alex Bethel | c242eace21 | |
sam lovelace | 263582bd7e | |
sam lovelace | 8c8ee90845 | |
sam lovelace | 6d82ada427 | |
Able | d808ae46ad | |
Able | 203ed2632c | |
Alex Bethel | 69c08da72c | |
sam lovelace | 749d0fd0fe | |
sam lovelace | 3a596ffce0 | |
sam lovelace | 0f4e7a7b33 |
386
src/lib.rs
386
src/lib.rs
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue