Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
euclio committed Jul 5, 2022
1 parent bcfafde commit 1c43f7d
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 63 deletions.
71 changes: 8 additions & 63 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,23 +60,26 @@ use tokio::sync::watch::{self, Sender};
use tower_http::trace::TraceLayer;
use tracing::log::*;

mod render;
mod service;

pub use render::*;

/// Markdown preview server.
///
/// Listens for HTTP connections and serves a page containing a live markdown preview. The page
/// contains JavaScript to open a websocket connection back to the server for rendering updates.
#[derive(Debug)]
pub struct Server {
pub struct Server<R> {
addr: SocketAddr,
config: Arc<RwLock<Config>>,
external_renderer: Option<RefCell<Command>>,
renderer: R,
output: RefCell<String>,
tx: Sender<String>,
_shutdown_tx: oneshot::Sender<()>,
}

impl Server {
impl<R> Server<R> where R: Renderer {
/// Binds the server to a specified address.
///
/// Binding to port 0 will request a port assignment from the OS. Use [`addr()`][Self::addr]
Expand Down Expand Up @@ -131,30 +134,14 @@ impl Server {
///
/// This method forwards errors from an external renderer, if set. Otherwise, the method is
/// infallible.
pub async fn send(&self, markdown: &str) -> io::Result<()> {
pub async fn send(&self, markdown: &str) -> Result<(), R::Error> {
let mut output = self.output.take();
output.clear();

// Heuristic taken from rustdoc
output.reserve(markdown.len() * 3 / 2);

if let Some(renderer) = &self.external_renderer {
let child = renderer.borrow_mut().spawn()?;

child.stdin.unwrap().write_all(markdown.as_bytes()).await?;

child.stdout.unwrap().read_to_string(&mut output).await?;
} else {
let parser = Parser::new_ext(
markdown,
Options::ENABLE_FOOTNOTES
| Options::ENABLE_TABLES
| Options::ENABLE_STRIKETHROUGH
| Options::ENABLE_TASKLISTS,
);

pulldown_cmark::html::push_html(&mut output, parser);
};
self.renderer.render(markdown, &mut output)?;

self.output.replace(self.tx.send_replace(output));

Expand Down Expand Up @@ -210,48 +197,6 @@ impl Server {
Ok(())
}

/// Set an external program to use for rendering the markdown.
///
/// By default, aurelius uses [`pulldown_cmark`] to render markdown in-process.
/// `pulldown-cmark` is an extremely fast, [CommonMark]-compliant parser that is sufficient
/// for most use-cases. However, other markdown renderers may provide additional features.
///
/// The `Command` supplied to this function should expect markdown on stdin and print HTML on
/// stdout.
///
/// # Example
///
/// To use [`pandoc`] to render markdown:
///
///
/// ```no_run
/// # async fn dox() -> Result<(), Box<dyn std::error::Error>> {
/// use std::net::SocketAddr;
/// use tokio::process::Command;
/// use aurelius::Server;
///
/// let addr = "127.0.0.1:1337".parse::<SocketAddr>()?;
/// let mut server = Server::bind(&addr).await?;
///
/// let mut pandoc = Command::new("pandoc");
/// pandoc.args(&["-f", "markdown", "-t", "html"]);
///
/// server.set_external_renderer(pandoc);
/// # Ok(())
/// # }
/// ```
///
/// [`pulldown_cmark`]: https://github.com/raphlinus/pulldown-cmark
/// [CommonMark]: https://commonmark.org/
/// [`pandoc`]: https://pandoc.org/
pub fn set_external_renderer(&mut self, mut command: Command) {
command
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null());
self.external_renderer = Some(RefCell::new(command));
}

/// Opens the user's default browser with the server's URL in the background.
///
/// This function uses platform-specific utilities to determine the browser. The following
Expand Down
27 changes: 27 additions & 0 deletions src/render.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
mod external;
mod markdown;

pub use external::ExternalCommand;
pub use markdown::Markdown;

/// Markdown renderer implementation.
///
/// Implementors of this trait convert markdown into HTML.
pub trait Renderer {
/// Potential errors returned by rendering. If rendering is infallible (markdown can always
/// produce HTML from its input), this type can be set to [`std::convert::Infallible`].
type Error;

/// Renders markdown as HTML.
///
/// The HTML should be written directly into the `html` buffer.
fn render(&self, input: &str, html: &mut String) -> Result<(), Self::Error>;

/// A hint for how many bytes the output will be.
///
/// This hint should be cheap to compute and is not required to be accurate. However, accurate
/// hints may improve performance by saving intermediate allocations.
fn size_hint(&self, input: &str) -> usize {
input.len()
}
}
59 changes: 59 additions & 0 deletions src/render/external.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use std::cell::RefCell;
use std::io::{self, prelude::*};
use std::process::{Command, Stdio};

use super::Renderer;

/// Markdown renderer that uses an external command as a backend.
///
/// The [`Markdown`] renderer uses an extremely fast, in-memory parser that is sufficient for most
/// use-cases. However, this renderer may be useful if your markdown requires features unsupported
/// by [`pulldown_cmark`].
///
/// # Example
///
/// Creating an external renderer that uses [pandoc](https://pandoc.org/):
///
/// ```no_run
/// use std::process::Command;
/// use aurelius::ExternalCommand;
///
/// let mut pandoc = Command::new("pandoc");
/// pandoc.args(&["-f", "markdown", "-t", "html"]);
///
/// ExternalCommand::new(pandoc);
/// ```
#[derive(Debug)]
pub struct ExternalCommand {
command: RefCell<Command>,
}

impl ExternalCommand {
/// Create a new external command renderer that will spawn processes using the given `command`.
///
/// The provided [`Command`] should expect markdown input on stdin and print HTML on stdout.
pub fn new(mut command: Command) -> ExternalCommand {
command
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null());

ExternalCommand {
command: RefCell::new(command),
}
}
}

impl Renderer for ExternalCommand {
type Error = io::Error;

fn render(&self, markdown: &str, html: &mut String) -> Result<(), Self::Error> {
let child = self.command.borrow_mut().spawn()?;

child.stdin.unwrap().write_all(markdown.as_bytes())?;

child.stdout.unwrap().read_to_string(html)?;

Ok(())
}
}
45 changes: 45 additions & 0 deletions src/render/markdown.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use std::convert::Infallible;

use pulldown_cmark::{html, Options, Parser};

use super::Renderer;

/// Markdown renderer that uses [`pulldown_cmark`] as the backend.
#[derive(Debug)]
pub struct Markdown {
options: Options,
}

impl Markdown {
/// Create a new instance of the renderer.
pub fn new() -> Markdown {
Markdown {
options: Options::ENABLE_FOOTNOTES
| Options::ENABLE_TABLES
| Options::ENABLE_STRIKETHROUGH
| Options::ENABLE_TASKLISTS,
}
}
}

impl Default for Markdown {
fn default() -> Self {
Self::new()
}
}

impl Renderer for Markdown {
type Error = Infallible;

fn render(&self, markdown: &str, html: &mut String) -> Result<(), Self::Error> {
let parser = Parser::new_ext(markdown, self.options);

html::push_html(html, parser);

Ok(())
}

fn size_hint(&self, input: &str) -> usize {
input.len() * 3 / 2
}
}

0 comments on commit 1c43f7d

Please sign in to comment.