refactored markdown parsing, added some documentation
All checks were successful
Test the running changes / Test (push) Successful in 40s
All checks were successful
Test the running changes / Test (push) Successful in 40s
This commit is contained in:
@@ -54,7 +54,9 @@ need_stdout = false
|
|||||||
[jobs.test]
|
[jobs.test]
|
||||||
command = [
|
command = [
|
||||||
"cargo", "nextest", "run",
|
"cargo", "nextest", "run",
|
||||||
"--hide-progress-bar", "--failure-output", "final"
|
"--hide-progress-bar",
|
||||||
|
"--failure-output", "final",
|
||||||
|
"--no-fail-fast"
|
||||||
]
|
]
|
||||||
need_stdout = true
|
need_stdout = true
|
||||||
analyzer = "nextest"
|
analyzer = "nextest"
|
||||||
@@ -62,7 +64,8 @@ analyzer = "nextest"
|
|||||||
[jobs.nextest]
|
[jobs.nextest]
|
||||||
command = [
|
command = [
|
||||||
"cargo", "nextest", "run",
|
"cargo", "nextest", "run",
|
||||||
"--hide-progress-bar", "--failure-output", "final"
|
"--hide-progress-bar",
|
||||||
|
"--failure-output", "final",
|
||||||
]
|
]
|
||||||
need_stdout = true
|
need_stdout = true
|
||||||
analyzer = "nextest"
|
analyzer = "nextest"
|
||||||
|
|||||||
@@ -1,19 +1,10 @@
|
|||||||
use crate::to_html::ToHtml;
|
//! Abstract syntax tree of "Markdown".
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Document {
|
pub struct Document {
|
||||||
pub blocks: Vec<Block>,
|
pub blocks: Vec<Block>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToHtml for Document {
|
|
||||||
fn to_html(self) -> String {
|
|
||||||
format!(
|
|
||||||
"<!doctype html><html lang=en><head></head><body>{}</body></html>",
|
|
||||||
self.blocks.to_html()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Block {
|
pub enum Block {
|
||||||
Paragraph(Vec<Inline>),
|
Paragraph(Vec<Inline>),
|
||||||
@@ -29,24 +20,6 @@ pub enum Block {
|
|||||||
Quote(Vec<Block>),
|
Quote(Vec<Block>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToHtml for Block {
|
|
||||||
fn to_html(self) -> String {
|
|
||||||
match self {
|
|
||||||
Self::Paragraph(content) => format!("<p>{}</p>", content.to_html()),
|
|
||||||
Self::Heading { level, content } => {
|
|
||||||
format!("<h{}>{}</h{}>", level, content.to_html(), level)
|
|
||||||
}
|
|
||||||
Self::Code {
|
|
||||||
language: _,
|
|
||||||
content,
|
|
||||||
} => {
|
|
||||||
format!("<pre><code>{}</code></pre>", content)
|
|
||||||
}
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ListItem {
|
pub struct ListItem {
|
||||||
pub blocks: Vec<Block>,
|
pub blocks: Vec<Block>,
|
||||||
@@ -60,167 +33,3 @@ pub enum Inline {
|
|||||||
Code(String),
|
Code(String),
|
||||||
Link { text: Vec<Inline>, href: String },
|
Link { text: Vec<Inline>, href: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToHtml for Inline {
|
|
||||||
fn to_html(self) -> String {
|
|
||||||
match self {
|
|
||||||
Self::Text(s) => s,
|
|
||||||
Self::Bold(content) => format!("<b>{}</b>", content.to_html()),
|
|
||||||
Self::Italic(content) => format!("<i>{}</i>", content.to_html()),
|
|
||||||
Self::Code(s) => format!("<code>{}</code>", s),
|
|
||||||
Self::Link { text, href } => format!("<a href=\"{}\">{}</a>", href, text.to_html()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ToHtml for Vec<T>
|
|
||||||
where
|
|
||||||
T: ToHtml,
|
|
||||||
{
|
|
||||||
fn to_html(self) -> String {
|
|
||||||
let mut rendered = String::new();
|
|
||||||
for i in self {
|
|
||||||
rendered.push_str(&i.to_html());
|
|
||||||
}
|
|
||||||
rendered
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------
|
|
||||||
// TESTS
|
|
||||||
// --------------------
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_header() {
|
|
||||||
let ast = Document {
|
|
||||||
blocks: vec![Block::Heading {
|
|
||||||
level: 1,
|
|
||||||
content: vec![Inline::Text("Heading 1".to_string())],
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
let html = ast.to_html();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
html,
|
|
||||||
"<!doctype html><html lang=en><head></head><body><h1>Heading 1</h1></body></html>"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn inline_bold_header() {
|
|
||||||
let ast = Document {
|
|
||||||
blocks: vec![Block::Heading {
|
|
||||||
level: 1,
|
|
||||||
content: vec![
|
|
||||||
Inline::Bold(vec![Inline::Text("Bold".to_string())]),
|
|
||||||
Inline::Text(" heading 1".to_string()),
|
|
||||||
],
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
let html = ast.to_html();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
html,
|
|
||||||
"<!doctype html><html lang=en><head></head><body><h1><b>Bold</b> heading 1</h1></body></html>"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn headings_and_paragraph_nested_code() {
|
|
||||||
let ast = Document {
|
|
||||||
blocks: vec![
|
|
||||||
Block::Heading {
|
|
||||||
level: 1,
|
|
||||||
content: vec![
|
|
||||||
Inline::Bold(vec![Inline::Text("Bold".to_string())]),
|
|
||||||
Inline::Text(" heading 1".to_string()),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
Block::Heading {
|
|
||||||
level: 2,
|
|
||||||
content: vec![Inline::Text("Heading 2".to_string())],
|
|
||||||
},
|
|
||||||
Block::Paragraph(vec![
|
|
||||||
Inline::Text("run ".to_string()),
|
|
||||||
Inline::Code("sudo rm -rf /".to_string()),
|
|
||||||
Inline::Text(" on your computer".to_string()),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
let html = ast.to_html();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
html,
|
|
||||||
"<!doctype html><html lang=en><head></head><body><h1><b>Bold</b> heading 1</h1><h2>Heading 2</h2><p>run <code>sudo rm -rf /</code> on your computer</p></body></html>"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod convert_md_to_html_test {
|
|
||||||
use crate::parser::parse;
|
|
||||||
use crate::to_html::ToHtml;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_header() {
|
|
||||||
let md = "# Header 1";
|
|
||||||
let ast = match parse(md) {
|
|
||||||
Ok(a) => a,
|
|
||||||
Err(e) => panic!("{}", e),
|
|
||||||
};
|
|
||||||
let html = ast.to_html();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
html,
|
|
||||||
"<!doctype html><html lang=en><head></head><body><h1>Header 1</h1></body></html>"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_header_wrong_format() {
|
|
||||||
let md = "#Whoops";
|
|
||||||
let ast = parse(md);
|
|
||||||
|
|
||||||
assert!(ast.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn nested_bold_headers_and_nested_code_paragraph() {
|
|
||||||
let md = "# *Bold* header 1\n## Header 2\nrun `sudo rm -rf /` on your computer";
|
|
||||||
let ast = match parse(md) {
|
|
||||||
Ok(a) => a,
|
|
||||||
Err(e) => panic!("{}", e),
|
|
||||||
};
|
|
||||||
let html = ast.to_html();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
html,
|
|
||||||
"<!doctype html><html lang=en><head></head><body><h1><b>Bold</b> header 1</h1><h2>Header 2</h2><p>run <code>sudo rm -rf /</code> on your computer</p></body></html>"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod parse_real_md {
|
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
use crate::parser::parse;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn go() {
|
|
||||||
let file = "./test.md";
|
|
||||||
let md = fs::read_to_string(file).expect("reading ./test.md failed");
|
|
||||||
|
|
||||||
let _ast = match parse(&md).map_err(|e| e.set_file(file.into())) {
|
|
||||||
Ok(a) => a,
|
|
||||||
Err(e) => panic!("{}", e),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
|
//! A "Markdown" parser and HTML generator. Part of a static site generator `marksmith-rs`.
|
||||||
|
//! Not following any standards, only vibes.
|
||||||
|
|
||||||
#![deny(unused_imports)]
|
#![deny(unused_imports)]
|
||||||
|
#![allow(clippy::needless_pass_by_value)]
|
||||||
|
|
||||||
use fstools::crawl_fs;
|
use fstools::crawl_fs;
|
||||||
use parser::parse;
|
use parser::parse;
|
||||||
@@ -57,6 +61,7 @@ impl MdParseError {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn set_line(self, line: usize) -> Self {
|
pub fn set_line(self, line: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
file: self.file,
|
file: self.file,
|
||||||
@@ -67,6 +72,7 @@ impl MdParseError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn set_file(self, file: PathBuf) -> Self {
|
pub fn set_file(self, file: PathBuf) -> Self {
|
||||||
Self {
|
Self {
|
||||||
file: Some(file),
|
file: Some(file),
|
||||||
@@ -98,7 +104,9 @@ impl std::error::Error for MdParseError {}
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
InDirIsNotDir,
|
||||||
OutDirIsNotEmpty,
|
OutDirIsNotEmpty,
|
||||||
|
OutDirIsNotDir,
|
||||||
OutDirFileDeleteNotAllowed,
|
OutDirFileDeleteNotAllowed,
|
||||||
OutDirDirectoryInPlaceOfFile,
|
OutDirDirectoryInPlaceOfFile,
|
||||||
FileRead,
|
FileRead,
|
||||||
@@ -125,7 +133,18 @@ impl std::error::Error for Error {}
|
|||||||
|
|
||||||
type Result<T> = std::result::Result<T, crate::Error>;
|
type Result<T> = std::result::Result<T, crate::Error>;
|
||||||
|
|
||||||
|
/// Takes two directories and a force flag as parameters, generates html files to the outdir in the
|
||||||
|
/// same directory structure as the md files in indir.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Anything wrong with reading files from the directories or parsing the files.
|
||||||
pub fn generate(indir: &PathBuf, outdir: &PathBuf, force: bool) -> Result<()> {
|
pub fn generate(indir: &PathBuf, outdir: &PathBuf, force: bool) -> Result<()> {
|
||||||
|
if !indir.is_dir() {
|
||||||
|
Err(Error::InDirIsNotDir)?;
|
||||||
|
}
|
||||||
|
if !outdir.is_dir() {
|
||||||
|
Err(Error::OutDirIsNotDir)?;
|
||||||
|
}
|
||||||
let files = crawl_fs(indir);
|
let files = crawl_fs(indir);
|
||||||
|
|
||||||
for path in files {
|
for path in files {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ pub trait ParsePattern: Iterator + Clone {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pub trait Parse: Iterator {
|
pub trait Parse: Iterator + Clone {
|
||||||
fn follows(&mut self, token: char) -> bool;
|
fn follows(&mut self, token: char) -> bool;
|
||||||
|
|
||||||
fn parse_token(&mut self, token: char) -> bool {
|
fn parse_token(&mut self, token: char) -> bool {
|
||||||
@@ -77,23 +77,32 @@ pub trait Parse: Iterator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_str(&mut self, _tokens: &str) -> bool {
|
fn parse_str(&mut self, tokens: &str) -> bool {
|
||||||
todo!()
|
let mut cloned = self.clone();
|
||||||
|
|
||||||
|
for pat_token in tokens.chars() {
|
||||||
|
if cloned.follows(pat_token) {
|
||||||
|
cloned.next();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*self = cloned;
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for std::iter::Peekable<std::str::Chars<'_>> {
|
impl Parse for std::iter::Peekable<std::str::Chars<'_>> {
|
||||||
fn follows(&mut self, token: char) -> bool {
|
fn follows(&mut self, token: char) -> bool {
|
||||||
self.peek().map(|c| c == &token).unwrap_or(false)
|
self.peek().is_some_and(|c| c == &token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for std::iter::Peekable<std::iter::Enumerate<std::str::Chars<'_>>> {
|
impl Parse for std::iter::Peekable<std::iter::Enumerate<std::str::Chars<'_>>> {
|
||||||
fn follows(&mut self, token: char) -> bool {
|
fn follows(&mut self, token: char) -> bool {
|
||||||
self.peekable()
|
self.peek().is_some_and(|&(_i, c)| c == token)
|
||||||
.peek()
|
|
||||||
.map(|&(_i, c)| c == token)
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,4 +117,60 @@ mod test {
|
|||||||
assert!(c.follows('a'));
|
assert!(c.follows('a'));
|
||||||
assert!(c.follows('a'));
|
assert!(c.follows('a'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chars_parse_tokens() {
|
||||||
|
let mut c = "abcdef".chars().peekable();
|
||||||
|
|
||||||
|
assert!(c.parse_token('a'));
|
||||||
|
assert!(c.parse_token('b'));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chars_parse_str() {
|
||||||
|
let mut c = "abcdef".chars().peekable();
|
||||||
|
|
||||||
|
assert!(c.parse_str("abc"));
|
||||||
|
assert!(c.parse_str("def"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enumerate_parse_follows_double() {
|
||||||
|
let mut c = "abc".chars().enumerate().peekable();
|
||||||
|
|
||||||
|
assert!(c.follows('a'));
|
||||||
|
assert!(c.follows('a'));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enumerate_parse_tokens() {
|
||||||
|
let mut c = "abcdef".chars().enumerate().peekable();
|
||||||
|
|
||||||
|
assert!(c.parse_token('a'));
|
||||||
|
assert!(c.parse_token('b'));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enumerate_parse_str() {
|
||||||
|
let mut c = "abcdef".chars().enumerate().peekable();
|
||||||
|
|
||||||
|
assert!(c.parse_str("abc"));
|
||||||
|
assert!(c.parse_str("def"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enumerate_parse_token_failed_not_consume() {
|
||||||
|
let mut c = "abc".chars().enumerate().peekable();
|
||||||
|
|
||||||
|
assert!(!c.parse_token('b'));
|
||||||
|
assert!(c.parse_token('a'));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enumerate_parse_str_failed_not_consume() {
|
||||||
|
let mut c = "abcdef".chars().enumerate().peekable();
|
||||||
|
|
||||||
|
assert!(!c.parse_str("def"));
|
||||||
|
assert!(c.parse_str("abc"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//! Parse "Markdown" to AST.
|
||||||
|
|
||||||
mod block;
|
mod block;
|
||||||
mod inline;
|
mod inline;
|
||||||
|
|
||||||
@@ -5,13 +7,15 @@ use block::parse_blocks;
|
|||||||
|
|
||||||
use crate::{MdParseError, ast::Document};
|
use crate::{MdParseError, ast::Document};
|
||||||
|
|
||||||
|
/// Parses the incoming data to a Markdown abstract syntax tree.
|
||||||
|
/// # Errors
|
||||||
|
/// This function will return an `MdParseError` when any part of the input is invalid Markdown.
|
||||||
pub fn parse(s: &str) -> Result<Document, MdParseError> {
|
pub fn parse(s: &str) -> Result<Document, MdParseError> {
|
||||||
Ok(Document {
|
Ok(Document {
|
||||||
blocks: parse_blocks(s)?,
|
blocks: parse_blocks(s)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::ast::*;
|
use crate::ast::*;
|
||||||
@@ -21,7 +25,7 @@ mod test {
|
|||||||
fn only_paragraph() {
|
fn only_paragraph() {
|
||||||
let md = "testing paragraph";
|
let md = "testing paragraph";
|
||||||
|
|
||||||
let doc = parse(md);
|
let doc = parse(md).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
doc,
|
doc,
|
||||||
Document {
|
Document {
|
||||||
@@ -36,7 +40,7 @@ mod test {
|
|||||||
fn different_headers() {
|
fn different_headers() {
|
||||||
let md = "# Header 1\n## Header 2";
|
let md = "# Header 1\n## Header 2";
|
||||||
|
|
||||||
let doc = parse(md);
|
let doc = parse(md).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
doc,
|
doc,
|
||||||
@@ -59,7 +63,7 @@ mod test {
|
|||||||
fn inline_bold_and_italics() {
|
fn inline_bold_and_italics() {
|
||||||
let md = "some *bold* and _italic_ text";
|
let md = "some *bold* and _italic_ text";
|
||||||
|
|
||||||
let doc = parse(md);
|
let doc = parse(md).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
doc,
|
doc,
|
||||||
@@ -79,7 +83,7 @@ mod test {
|
|||||||
fn inline_code() {
|
fn inline_code() {
|
||||||
let md = "run command `sudo rm -rf /`";
|
let md = "run command `sudo rm -rf /`";
|
||||||
|
|
||||||
let doc = parse(md);
|
let doc = parse(md).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
doc,
|
doc,
|
||||||
@@ -96,7 +100,7 @@ mod test {
|
|||||||
fn bold_header() {
|
fn bold_header() {
|
||||||
let md = "# Header is *bold*";
|
let md = "# Header is *bold*";
|
||||||
|
|
||||||
let doc = parse(md);
|
let doc = parse(md).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
doc,
|
doc,
|
||||||
@@ -116,7 +120,7 @@ mod test {
|
|||||||
fn anonymous_code_block() {
|
fn anonymous_code_block() {
|
||||||
let md = "```\necho hello\n```";
|
let md = "```\necho hello\n```";
|
||||||
|
|
||||||
let doc = parse(md);
|
let doc = parse(md).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
doc,
|
doc,
|
||||||
@@ -133,7 +137,7 @@ mod test {
|
|||||||
fn rust_code_block() {
|
fn rust_code_block() {
|
||||||
let md = "```rust\nfn main() {\n\tprintln!(\"Hello world!\");\n}\n```";
|
let md = "```rust\nfn main() {\n\tprintln!(\"Hello world!\");\n}\n```";
|
||||||
|
|
||||||
let doc = parse(md);
|
let doc = parse(md).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
doc,
|
doc,
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ pub fn parse_blocks(input: &str) -> Result<Vec<Block>, MdParseError> {
|
|||||||
while let Some((i, line)) = lines.next() {
|
while let Some((i, line)) = lines.next() {
|
||||||
let mut line_chars = line.chars().peekable();
|
let mut line_chars = line.chars().peekable();
|
||||||
|
|
||||||
|
// empty line
|
||||||
|
if line_chars.peek().is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// header
|
// header
|
||||||
let mut heading_level = 0;
|
let mut heading_level = 0;
|
||||||
while line_chars.parse_token('#') {
|
while line_chars.parse_token('#') {
|
||||||
@@ -53,6 +58,7 @@ pub fn parse_blocks(input: &str) -> Result<Vec<Block>, MdParseError> {
|
|||||||
};
|
};
|
||||||
let mut code = String::new();
|
let mut code = String::new();
|
||||||
|
|
||||||
|
let mut successful = false;
|
||||||
for (j, line) in lines.by_ref() {
|
for (j, line) in lines.by_ref() {
|
||||||
let mut code_line_chars = line.chars().peekable();
|
let mut code_line_chars = line.chars().peekable();
|
||||||
// code block end
|
// code block end
|
||||||
@@ -63,23 +69,31 @@ pub fn parse_blocks(input: &str) -> Result<Vec<Block>, MdParseError> {
|
|||||||
language: lang,
|
language: lang,
|
||||||
content: code,
|
content: code,
|
||||||
});
|
});
|
||||||
|
successful = true;
|
||||||
break;
|
break;
|
||||||
} else {
|
|
||||||
Err(MdParseError::from_line(
|
|
||||||
j + 1,
|
|
||||||
"```",
|
|
||||||
format!("```{}", remaining),
|
|
||||||
))?;
|
|
||||||
}
|
}
|
||||||
|
Err(MdParseError::from_line(
|
||||||
|
j + 1,
|
||||||
|
"```",
|
||||||
|
format!("```{remaining}"),
|
||||||
|
))?;
|
||||||
} else {
|
} else {
|
||||||
code.push_str(line);
|
code.push_str(line);
|
||||||
code.push('\n');
|
code.push('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if successful {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
Err(MdParseError::from_line(i + 1, "a terminating '```'", ""))?;
|
Err(MdParseError::from_line(i + 1, "a terminating '```'", ""))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// lists TODO
|
// lists TODO
|
||||||
|
|
||||||
|
// paragraph
|
||||||
|
blocks.push(Block::Paragraph(
|
||||||
|
parse_inlines(line).map_err(|e| e.set_line(i + 1))?,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(blocks)
|
Ok(blocks)
|
||||||
|
|||||||
@@ -36,11 +36,12 @@ pub fn parse_inlines(input: &str) -> Result<Vec<Inline>, MdParseError> {
|
|||||||
_ => {
|
_ => {
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
text.push(c);
|
text.push(c);
|
||||||
while let Some(nc) = chars.next() {
|
while let Some(&nc) = chars.peek() {
|
||||||
if matches!(nc, '*' | '_' | '`' | '[') {
|
if matches!(nc, '*' | '_' | '`' | '[') {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
text.push(nc);
|
let c = chars.next().ok_or(MdParseError::new("a character", ""))?;
|
||||||
|
text.push(c);
|
||||||
}
|
}
|
||||||
inlines.push(Inline::Text(text));
|
inlines.push(Inline::Text(text));
|
||||||
}
|
}
|
||||||
@@ -55,7 +56,7 @@ fn collect_until<I: Iterator<Item = char>>(
|
|||||||
end: char,
|
end: char,
|
||||||
) -> Result<String, MdParseError> {
|
) -> Result<String, MdParseError> {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
while let Some(c) = chars.next() {
|
for c in chars.by_ref() {
|
||||||
if c == end {
|
if c == end {
|
||||||
return Ok(s);
|
return Ok(s);
|
||||||
}
|
}
|
||||||
@@ -63,3 +64,68 @@ fn collect_until<I: Iterator<Item = char>>(
|
|||||||
}
|
}
|
||||||
Err(MdParseError::new(end, ""))
|
Err(MdParseError::new(end, ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::ast::Inline;
|
||||||
|
|
||||||
|
use super::parse_inlines;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bold_text() {
|
||||||
|
let md = "*abc*";
|
||||||
|
let inl = parse_inlines(md).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
inl,
|
||||||
|
vec![Inline::Bold(vec![Inline::Text("abc".to_string())])]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn italic_text() {
|
||||||
|
let md = "_abc_";
|
||||||
|
let inl = parse_inlines(md).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
inl,
|
||||||
|
vec![Inline::Italic(vec![Inline::Text("abc".to_string())])]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bold_italic_text() {
|
||||||
|
let md = "*_abc_*";
|
||||||
|
let inl = parse_inlines(md).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
inl,
|
||||||
|
vec![Inline::Bold(vec![Inline::Italic(vec![Inline::Text(
|
||||||
|
"abc".to_string()
|
||||||
|
)])])]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn code() {
|
||||||
|
let md = "`sudo rm -rf /`";
|
||||||
|
let inl = parse_inlines(md).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(inl, vec![Inline::Code("sudo rm -rf /".to_string())]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_and_code() {
|
||||||
|
let md = "run `sudo rm -rf /` on your computer";
|
||||||
|
let inl = parse_inlines(md).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
inl,
|
||||||
|
vec![
|
||||||
|
Inline::Text("run ".to_string()),
|
||||||
|
Inline::Code("sudo rm -rf /".to_string()),
|
||||||
|
Inline::Text(" on your computer".to_string())
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,198 @@
|
|||||||
|
//! A trait + implementations for generating HTML.
|
||||||
|
|
||||||
|
use crate::ast::{Block, Document, Inline};
|
||||||
|
|
||||||
pub trait ToHtml {
|
pub trait ToHtml {
|
||||||
fn to_html(self) -> String;
|
fn to_html(self) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToHtml for Document {
|
||||||
|
fn to_html(self) -> String {
|
||||||
|
format!(
|
||||||
|
"<!doctype html><html lang=en><head></head><body>{}</body></html>",
|
||||||
|
self.blocks.to_html()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToHtml for Block {
|
||||||
|
fn to_html(self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Paragraph(content) => format!("<p>{}</p>", content.to_html()),
|
||||||
|
Self::Heading { level, content } => {
|
||||||
|
format!("<h{}>{}</h{}>", level, content.to_html(), level)
|
||||||
|
}
|
||||||
|
Self::Code {
|
||||||
|
language: _,
|
||||||
|
content,
|
||||||
|
} => {
|
||||||
|
format!("<pre><code>{content}</code></pre>")
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToHtml for Inline {
|
||||||
|
fn to_html(self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Text(s) => s,
|
||||||
|
Self::Bold(content) => format!("<b>{}</b>", content.to_html()),
|
||||||
|
Self::Italic(content) => format!("<i>{}</i>", content.to_html()),
|
||||||
|
Self::Code(s) => format!("<code>{s}</code>"),
|
||||||
|
Self::Link { text, href } => format!("<a href=\"{}\">{}</a>", href, text.to_html()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ToHtml for Vec<T>
|
||||||
|
where
|
||||||
|
T: ToHtml,
|
||||||
|
{
|
||||||
|
fn to_html(self) -> String {
|
||||||
|
let mut rendered = String::new();
|
||||||
|
for i in self {
|
||||||
|
rendered.push_str(&i.to_html());
|
||||||
|
}
|
||||||
|
rendered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
// TESTS
|
||||||
|
// --------------------
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod unit_test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_header() {
|
||||||
|
let ast = Document {
|
||||||
|
blocks: vec![Block::Heading {
|
||||||
|
level: 1,
|
||||||
|
content: vec![Inline::Text("Heading 1".to_string())],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = ast.to_html();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
html,
|
||||||
|
"<!doctype html><html lang=en><head></head><body><h1>Heading 1</h1></body></html>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inline_bold_header() {
|
||||||
|
let ast = Document {
|
||||||
|
blocks: vec![Block::Heading {
|
||||||
|
level: 1,
|
||||||
|
content: vec![
|
||||||
|
Inline::Bold(vec![Inline::Text("Bold".to_string())]),
|
||||||
|
Inline::Text(" heading 1".to_string()),
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = ast.to_html();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
html,
|
||||||
|
"<!doctype html><html lang=en><head></head><body><h1><b>Bold</b> heading 1</h1></body></html>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn headings_and_paragraph_nested_code() {
|
||||||
|
let ast = Document {
|
||||||
|
blocks: vec![
|
||||||
|
Block::Heading {
|
||||||
|
level: 1,
|
||||||
|
content: vec![
|
||||||
|
Inline::Bold(vec![Inline::Text("Bold".to_string())]),
|
||||||
|
Inline::Text(" heading 1".to_string()),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Block::Heading {
|
||||||
|
level: 2,
|
||||||
|
content: vec![Inline::Text("Heading 2".to_string())],
|
||||||
|
},
|
||||||
|
Block::Paragraph(vec![
|
||||||
|
Inline::Text("run ".to_string()),
|
||||||
|
Inline::Code("sudo rm -rf /".to_string()),
|
||||||
|
Inline::Text(" on your computer".to_string()),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = ast.to_html();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
html,
|
||||||
|
"<!doctype html><html lang=en><head></head><body><h1><b>Bold</b> heading 1</h1><h2>Heading 2</h2><p>run <code>sudo rm -rf /</code> on your computer</p></body></html>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod convert_md_to_html_test {
|
||||||
|
use crate::parser::parse;
|
||||||
|
use crate::to_html::ToHtml;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_header() {
|
||||||
|
let md = "# Header 1";
|
||||||
|
let ast = match parse(md) {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(e) => panic!("{}", e),
|
||||||
|
};
|
||||||
|
let html = ast.to_html();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
html,
|
||||||
|
"<!doctype html><html lang=en><head></head><body><h1>Header 1</h1></body></html>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_header_wrong_format() {
|
||||||
|
let md = "#Whoops";
|
||||||
|
let ast = parse(md);
|
||||||
|
|
||||||
|
assert!(ast.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_bold_headers_and_nested_code_paragraph() {
|
||||||
|
let md = "# *Bold* header 1\n## Header 2\nrun `sudo rm -rf /` on your computer";
|
||||||
|
let ast = match parse(md) {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(e) => panic!("{}", e),
|
||||||
|
};
|
||||||
|
let html = ast.to_html();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
html,
|
||||||
|
"<!doctype html><html lang=en><head></head><body><h1><b>Bold</b> header 1</h1><h2>Header 2</h2><p>run <code>sudo rm -rf /</code> on your computer</p></body></html>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod parse_real_md {
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
use crate::parser::parse;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn go() {
|
||||||
|
let file = "./test.md";
|
||||||
|
let md = fs::read_to_string(file).expect("reading ./test.md failed");
|
||||||
|
|
||||||
|
let _ast = match parse(&md).map_err(|e| e.set_file(file.into())) {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(e) => panic!("{}", e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Header *1kkkkkkkkkkkkkkkkkkkkkk*
|
# Header *1kkkkkkkkkkkkkkkkkkkkkk*
|
||||||
|
|
||||||
this is some code: `abc
|
this is some code: `abc`
|
||||||
|
|
||||||
```code
|
```code
|
||||||
|
|
||||||
oiajwefoijao089uaoisdjfoijasdfoijasdofij
|
oiajwefoijao089uaoisdjfoijasdfoijasdofij
|
||||||
|
```
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ use std::collections::HashSet;
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// Recursively crawl through the directory given and aggregate all file handles to a HashMap with
|
/// Recursively crawl through the directory given and aggregate all file handles to a `HashMap` with
|
||||||
/// their respective (relative) paths as keys.
|
/// their respective (relative) paths as keys.
|
||||||
|
#[must_use]
|
||||||
pub fn crawl_fs(root: &PathBuf) -> HashSet<PathBuf> {
|
pub fn crawl_fs(root: &PathBuf) -> HashSet<PathBuf> {
|
||||||
crawl_fs_rec(root, root)
|
crawl_fs_rec(root, root)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function: Recursively crawl through the directory given and aggregate all file handles to a HashMap with
|
/// Helper function: Recursively crawl through the directory given and aggregate all file handles to a `HashMap` with
|
||||||
/// their respective (relative) paths as keys.
|
/// their respective (relative) paths as keys.
|
||||||
fn crawl_fs_rec(root: &PathBuf, path: &PathBuf) -> HashSet<PathBuf> {
|
fn crawl_fs_rec(root: &PathBuf, path: &PathBuf) -> HashSet<PathBuf> {
|
||||||
let mut subdirs = Vec::with_capacity(100);
|
let mut subdirs = Vec::with_capacity(100);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//! Simple and program specific command line argument parsing solution.
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
//! FileServer
|
//! A simple server implementation that just responds with the contents of the file requested in
|
||||||
|
//! the provided directory.
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::*,
|
error::{Error, ErrorKind, Result},
|
||||||
request::{HttpMethod, HttpRequest},
|
request::{HttpMethod, HttpRequest},
|
||||||
responder::Responder,
|
responder::Responder,
|
||||||
response::{HttpResponse, HttpStatus},
|
response::{HttpResponse, HttpStatus},
|
||||||
@@ -51,7 +52,6 @@ impl Responder for FileServer {
|
|||||||
let content_type = match req.path.extension() {
|
let content_type = match req.path.extension() {
|
||||||
Some(s) => match s.as_encoded_bytes() {
|
Some(s) => match s.as_encoded_bytes() {
|
||||||
b"html" | b"htm" => "text/html",
|
b"html" | b"htm" => "text/html",
|
||||||
b"txt" => "text/plain",
|
|
||||||
b"css" => "text/css",
|
b"css" => "text/css",
|
||||||
b"js" => "text/javascript",
|
b"js" => "text/javascript",
|
||||||
b"pdf" => "application/pdf",
|
b"pdf" => "application/pdf",
|
||||||
|
|||||||
@@ -1,36 +1,38 @@
|
|||||||
|
//! Wrapper type of a `HashMap` to contain HTTP headers and format them correctly.
|
||||||
|
|
||||||
use std::{collections::HashMap, fmt::Display};
|
use std::{collections::HashMap, fmt::Display};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct HttpHeaders {
|
pub struct HttpHeaders {
|
||||||
_inner: HashMap<String, String>,
|
inner: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpHeaders {
|
impl HttpHeaders {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
HttpHeaders {
|
HttpHeaders {
|
||||||
_inner: HashMap::new(),
|
inner: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, k: &str, v: &str) {
|
pub fn add(&mut self, k: &str, v: &str) {
|
||||||
self._inner.insert(k.to_string(), v.to_string());
|
self.inner.insert(k.to_string(), v.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn get(&self, k: &str) -> Option<&String> {
|
pub fn get(&self, k: &str) -> Option<&String> {
|
||||||
self._inner.get(k)
|
self.inner.get(k)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self._inner.len()
|
self.inner.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for HttpHeaders {
|
impl Display for HttpHeaders {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
for (k, v) in self._inner.iter() {
|
for (k, v) in &self.inner {
|
||||||
write!(f, "{}: {}\r\n", k, v)?;
|
write!(f, "{k}: {v}\r\n")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ impl Display for Level {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A logging macro. Takes a [`Level`] and a formatted string.
|
||||||
|
///
|
||||||
|
/// [`Level`]: ./logger/enum.Level.html
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! log {
|
macro_rules! log {
|
||||||
($level:expr, $($arg:tt)*) => {{
|
($level:expr, $($arg:tt)*) => {{
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
//! This is my very monolithic file server with zero dependencies (other than rust
|
//! A simple web server with 0 dependencies (other than Rust's stdlib).
|
||||||
//! stdlib).
|
|
||||||
//!
|
|
||||||
//! Documentation is a work in progress, go see my webpage at [jlux.dev](https://jlux.dev).
|
//! Documentation is a work in progress, go see my webpage at [jlux.dev](https://jlux.dev).
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
@@ -32,7 +30,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
if args.generate {
|
if args.generate {
|
||||||
match generate(&args.indir, &args.outdir, args.force) {
|
match generate(&args.indir, &args.outdir, args.force) {
|
||||||
Ok(_) => log!(
|
Ok(()) => log!(
|
||||||
Level::Info,
|
Level::Info,
|
||||||
"HTML generation from `{}` to `{}` successful",
|
"HTML generation from `{}` to `{}` successful",
|
||||||
args.indir.display(),
|
args.indir.display(),
|
||||||
@@ -50,7 +48,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
log!(Level::Error, "HTML generation failed with error: {}", e,);
|
log!(Level::Error, "HTML generation failed with error: {}", e,);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let listener = TcpListener::bind(&args.addr)?;
|
let listener = TcpListener::bind(&args.addr)?;
|
||||||
@@ -59,7 +57,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
for stream in listener.incoming() {
|
for stream in listener.incoming() {
|
||||||
match stream {
|
match stream {
|
||||||
Ok(stream) => {
|
Ok(stream) => {
|
||||||
let outdir = args.outdir.to_owned();
|
let outdir = args.outdir.clone();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
log!(Level::Debug, "TcpStream handler spawned");
|
log!(Level::Debug, "TcpStream handler spawned");
|
||||||
let mut reader = BufReader::new(&stream);
|
let mut reader = BufReader::new(&stream);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ impl TryFrom<&str> for HttpMethod {
|
|||||||
|
|
||||||
impl Display for HttpMethod {
|
impl Display for HttpMethod {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{:?}", self)
|
write!(f, "{self:?}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ impl TryFrom<&str> for HttpRequest {
|
|||||||
|
|
||||||
for line in lines {
|
for line in lines {
|
||||||
if let Some(v) = line.split_once(": ") {
|
if let Some(v) = line.split_once(": ") {
|
||||||
headers.add(v.0, v.1)
|
headers.add(v.0, v.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,55 +1,12 @@
|
|||||||
//! Traits helping HTTP connections
|
//! Helper trait(s).
|
||||||
|
|
||||||
use crate::request::HttpRequest;
|
use crate::request::HttpRequest;
|
||||||
use crate::response::HttpResponse;
|
use crate::response::HttpResponse;
|
||||||
|
|
||||||
/// Responder trait. Just a respond method that turns a HttpRequest to a HttpResponse.
|
/// Responder trait. Just a respond method that turns a [`HttpRequest`] to a [`HttpResponse`].
|
||||||
|
///
|
||||||
|
/// [`HttpRequest`]: ../request/struct.HttpRequest.html
|
||||||
|
/// [`HttpResponse`]: ../response/struct.HttpResponse.html
|
||||||
pub trait Responder {
|
pub trait Responder {
|
||||||
fn respond(&self, req: HttpRequest) -> HttpResponse;
|
fn respond(&self, req: HttpRequest) -> HttpResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// Size trait. Number of bytes when encoded.
|
|
||||||
pub trait Size {
|
|
||||||
fn size(&self) -> usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Standard implementations for Size trait
|
|
||||||
|
|
||||||
impl Size for u8 {
|
|
||||||
fn size(&self) -> usize {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Size for Vec<T>
|
|
||||||
where
|
|
||||||
T: Size,
|
|
||||||
{
|
|
||||||
fn size(&self) -> usize {
|
|
||||||
if let Some(elem) = self.first() {
|
|
||||||
elem.size() * self.len()
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Size for Option<T>
|
|
||||||
where
|
|
||||||
T: Size,
|
|
||||||
{
|
|
||||||
fn size(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
Some(t) => t.size(),
|
|
||||||
None => 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Size for String {
|
|
||||||
fn size(&self) -> usize {
|
|
||||||
self.len()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ impl Display for HttpResponse {
|
|||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{}\r\n",
|
"{}\r\n",
|
||||||
String::from_utf8(s.to_vec()).unwrap_or("<binary data>".to_string())
|
String::from_utf8(s.clone()).unwrap_or("<binary data>".to_string())
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user