♻️ Move Markdown previewing to its own file
parent
ab95485d92
commit
6e6e062c51
|
@ -1,14 +1,5 @@
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive (Debug, Error, PartialEq)]
|
|
||||||
pub enum MarkdownError {
|
|
||||||
#[error ("File is too big to process")]
|
|
||||||
TooBig,
|
|
||||||
|
|
||||||
#[error ("File is not UTF-8")]
|
|
||||||
NotUtf8,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive (Debug, Error)]
|
#[derive (Debug, Error)]
|
||||||
pub enum FileServerError {
|
pub enum FileServerError {
|
||||||
#[error ("Handlebars render error")]
|
#[error ("Handlebars render error")]
|
||||||
|
@ -29,8 +20,8 @@ pub enum FileServerError {
|
||||||
#[error ("File path is not UTF-8")]
|
#[error ("File path is not UTF-8")]
|
||||||
FilePathNotUtf8,
|
FilePathNotUtf8,
|
||||||
|
|
||||||
#[error ("Markdown error")]
|
//#[error ("Markdown error")]
|
||||||
Markdown (#[from] MarkdownError),
|
//Markdown (#[from] super::markdown::Error),
|
||||||
|
|
||||||
#[error ("Invalid URI")]
|
#[error ("Invalid URI")]
|
||||||
InvalidUri (#[from] http::uri::InvalidUri),
|
InvalidUri (#[from] http::uri::InvalidUri),
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
#[derive (Debug, thiserror::Error, PartialEq)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error ("File is too big to preview as Markdown")]
|
||||||
|
TooBig,
|
||||||
|
|
||||||
|
#[error ("File is not UTF-8")]
|
||||||
|
NotUtf8,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render (bytes: &[u8], out: &mut String) -> Result <(), Error> {
|
||||||
|
use pulldown_cmark::{Parser, Options, html};
|
||||||
|
|
||||||
|
let markdown_input = match std::str::from_utf8 (bytes) {
|
||||||
|
Err (_) => return Err (Error::NotUtf8),
|
||||||
|
Ok (x) => x,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut options = Options::empty ();
|
||||||
|
options.insert (Options::ENABLE_STRIKETHROUGH);
|
||||||
|
let parser = Parser::new_ext (markdown_input, options);
|
||||||
|
|
||||||
|
html::push_html (out, parser);
|
||||||
|
|
||||||
|
Ok (())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_styled (bytes: &[u8]) -> Result <String, Error> {
|
||||||
|
// Write to String buffer.
|
||||||
|
let mut out = String::new ();
|
||||||
|
|
||||||
|
out.push_str ("<body style=\"font-family: sans-serif;\">");
|
||||||
|
render (bytes, &mut out)?;
|
||||||
|
out.push_str ("</body>");
|
||||||
|
|
||||||
|
Ok (out)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg (test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn markdown () {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
for (input, expected) in vec! [
|
||||||
|
("", ""),
|
||||||
|
(
|
||||||
|
"Hello world, this is a ~~complicated~~ *very simple* example.",
|
||||||
|
"<p>Hello world, this is a <del>complicated</del> <em>very simple</em> example.</p>\n"
|
||||||
|
),
|
||||||
|
].into_iter () {
|
||||||
|
let mut out = String::default ();
|
||||||
|
render (input.as_bytes (), &mut out).expect ("Markdown sample failed");
|
||||||
|
assert_eq! (expected, &out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,12 +49,11 @@ use ptth_core::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
|
mod markdown;
|
||||||
mod range;
|
mod range;
|
||||||
|
|
||||||
use errors::{
|
use errors::FileServerError;
|
||||||
FileServerError,
|
use markdown::render_styled;
|
||||||
MarkdownError,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod emoji {
|
mod emoji {
|
||||||
pub const VIDEO: &str = "\u{1f39e}\u{fe0f}";
|
pub const VIDEO: &str = "\u{1f39e}\u{fe0f}";
|
||||||
|
@ -316,46 +315,6 @@ async fn serve_file (
|
||||||
Ok (response)
|
Ok (response)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serve_error (
|
|
||||||
status_code: StatusCode,
|
|
||||||
msg: &str
|
|
||||||
)
|
|
||||||
-> Response
|
|
||||||
{
|
|
||||||
let mut resp = Response::default ();
|
|
||||||
resp.status_code (status_code);
|
|
||||||
resp.body_bytes (msg.as_bytes ().to_vec ());
|
|
||||||
resp
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_markdown (bytes: &[u8], out: &mut String) -> Result <(), MarkdownError> {
|
|
||||||
use pulldown_cmark::{Parser, Options, html};
|
|
||||||
|
|
||||||
let markdown_input = match std::str::from_utf8 (bytes) {
|
|
||||||
Err (_) => return Err (MarkdownError::NotUtf8),
|
|
||||||
Ok (x) => x,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut options = Options::empty ();
|
|
||||||
options.insert (Options::ENABLE_STRIKETHROUGH);
|
|
||||||
let parser = Parser::new_ext (markdown_input, options);
|
|
||||||
|
|
||||||
html::push_html (out, parser);
|
|
||||||
|
|
||||||
Ok (())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_markdown_styled (bytes: &[u8]) -> Result <String, MarkdownError> {
|
|
||||||
// Write to String buffer.
|
|
||||||
let mut out = String::new ();
|
|
||||||
|
|
||||||
out.push_str ("<body style=\"font-family: sans-serif;\">");
|
|
||||||
render_markdown (bytes, &mut out)?;
|
|
||||||
out.push_str ("</body>");
|
|
||||||
|
|
||||||
Ok (out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort of an internal API endpoint to make testing work better.
|
// Sort of an internal API endpoint to make testing work better.
|
||||||
// Eventually we could expose this as JSON or Msgpack or whatever. For now
|
// Eventually we could expose this as JSON or Msgpack or whatever. For now
|
||||||
// it's just a Rust struct that we can test on without caring about
|
// it's just a Rust struct that we can test on without caring about
|
||||||
|
@ -387,7 +346,7 @@ enum InternalResponse {
|
||||||
ServeDir (ServeDirParams),
|
ServeDir (ServeDirParams),
|
||||||
ServeFile (ServeFileParams),
|
ServeFile (ServeFileParams),
|
||||||
|
|
||||||
MarkdownErr (MarkdownError),
|
MarkdownErr (markdown::Error),
|
||||||
MarkdownPreview (String),
|
MarkdownPreview (String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,14 +410,17 @@ async fn internal_serve_file (
|
||||||
}
|
}
|
||||||
|
|
||||||
if file_len > MAX_BUF_SIZE.into () {
|
if file_len > MAX_BUF_SIZE.into () {
|
||||||
InternalResponse::MarkdownErr (MarkdownError::TooBig)
|
InternalResponse::MarkdownErr (markdown::Error::TooBig)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let mut buffer = vec! [0_u8; MAX_BUF_SIZE.try_into ().expect ("Couldn't fit u32 into usize")];
|
let mut buffer = vec! [0_u8; MAX_BUF_SIZE.try_into ().expect ("Couldn't fit u32 into usize")];
|
||||||
let bytes_read = file.read (&mut buffer).await?;
|
let bytes_read = file.read (&mut buffer).await?;
|
||||||
buffer.truncate (bytes_read);
|
buffer.truncate (bytes_read);
|
||||||
|
|
||||||
InternalResponse::MarkdownPreview (render_markdown_styled (&buffer)?)
|
match render_styled (&buffer) {
|
||||||
|
Ok (x) => InternalResponse::MarkdownPreview (x),
|
||||||
|
Err (x) => InternalResponse::MarkdownErr (x),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -565,6 +527,18 @@ pub async fn serve_all (
|
||||||
{
|
{
|
||||||
use InternalResponse::*;
|
use InternalResponse::*;
|
||||||
|
|
||||||
|
fn serve_error <S: Into <Vec <u8>>> (
|
||||||
|
status_code: StatusCode,
|
||||||
|
msg: S
|
||||||
|
)
|
||||||
|
-> Response
|
||||||
|
{
|
||||||
|
let mut resp = Response::default ();
|
||||||
|
resp.status_code (status_code);
|
||||||
|
resp.body_bytes (msg.into ());
|
||||||
|
resp
|
||||||
|
}
|
||||||
|
|
||||||
Ok (match internal_serve_all (root, method, uri, headers, hidden_path).await? {
|
Ok (match internal_serve_all (root, method, uri, headers, hidden_path).await? {
|
||||||
Favicon => serve_error (StatusCode::NotFound, ""),
|
Favicon => serve_error (StatusCode::NotFound, ""),
|
||||||
Forbidden => serve_error (StatusCode::Forbidden, "403 Forbidden"),
|
Forbidden => serve_error (StatusCode::Forbidden, "403 Forbidden"),
|
||||||
|
@ -594,10 +568,15 @@ pub async fn serve_all (
|
||||||
send_body,
|
send_body,
|
||||||
range,
|
range,
|
||||||
}) => serve_file (file.into_inner (), send_body, range).await?,
|
}) => serve_file (file.into_inner (), send_body, range).await?,
|
||||||
MarkdownErr (e) => match e {
|
MarkdownErr (e) => {
|
||||||
MarkdownError::TooBig => serve_error (StatusCode::InternalServerError, "File is too big to preview as Markdown"),
|
use markdown::Error::*;
|
||||||
//MarkdownError::NotMarkdown => serve_error (StatusCode::BadRequest, "File is not Markdown"),
|
let code = match &e {
|
||||||
MarkdownError::NotUtf8 => serve_error (StatusCode::BadRequest, "File is not UTF-8"),
|
TooBig => StatusCode::InternalServerError,
|
||||||
|
//NotMarkdown => serve_error (StatusCode::BadRequest, "File is not Markdown"),
|
||||||
|
NotUtf8 => StatusCode::BadRequest,
|
||||||
|
};
|
||||||
|
|
||||||
|
serve_error (code, e.to_string ())
|
||||||
},
|
},
|
||||||
MarkdownPreview (s) => serve_html (s),
|
MarkdownPreview (s) => serve_html (s),
|
||||||
})
|
})
|
||||||
|
|
|
@ -181,20 +181,3 @@ fn file_server () {
|
||||||
fn parse_uri () {
|
fn parse_uri () {
|
||||||
assert! (http::Uri::from_maybe_shared ("/").is_ok ());
|
assert! (http::Uri::from_maybe_shared ("/").is_ok ());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn markdown () {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
for (input, expected) in vec! [
|
|
||||||
("", ""),
|
|
||||||
(
|
|
||||||
"Hello world, this is a ~~complicated~~ *very simple* example.",
|
|
||||||
"<p>Hello world, this is a <del>complicated</del> <em>very simple</em> example.</p>\n"
|
|
||||||
),
|
|
||||||
].into_iter () {
|
|
||||||
let mut out = String::default ();
|
|
||||||
render_markdown (input.as_bytes (), &mut out).expect ("Markdown sample failed");
|
|
||||||
assert_eq! (expected, &out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue