making it look better
This commit is contained in:
parent
ad84a79335
commit
30255be018
22 changed files with 2567 additions and 72 deletions
|
@ -4,9 +4,17 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
axum = "0.6.20"
|
||||
codespan-reporting = "0.11.1"
|
||||
copy_dir = "0.1.3"
|
||||
handlebars = "4.3.7"
|
||||
pulldown-cmark = { version = "0.9.3", default-features = false }
|
||||
serde = { version = "1.0.183", features = ["derive"] }
|
||||
thiserror = "1.0.47"
|
||||
tokio = "1.32.0"
|
||||
tower-http = { version = "0.4.3", features = ["fs"] }
|
||||
tower-livereload = "0.8.0"
|
||||
treehouse-format = { workspace = true }
|
||||
watchexec = "2.3.0"
|
||||
|
||||
|
|
439
crates/treehouse/src/html/markdown.rs
Normal file
439
crates/treehouse/src/html/markdown.rs
Normal file
|
@ -0,0 +1,439 @@
|
|||
// NOTE: This code is pasted pretty much verbatim from pulldown-cmark but tweaked to have my own
|
||||
// cool additions.
|
||||
|
||||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//! HTML renderer that takes an iterator of events as input.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
|
||||
use pulldown_cmark::escape::{escape_href, escape_html, StrWrite};
|
||||
use pulldown_cmark::{Alignment, CodeBlockKind, Event, LinkType, Tag};
|
||||
use pulldown_cmark::{CowStr, Event::*};
|
||||
|
||||
enum TableState {
|
||||
Head,
|
||||
Body,
|
||||
}
|
||||
|
||||
struct HtmlWriter<'a, I, W> {
|
||||
/// Iterator supplying events.
|
||||
iter: I,
|
||||
|
||||
/// Writer to write to.
|
||||
writer: W,
|
||||
|
||||
/// Whether or not the last write wrote a newline.
|
||||
end_newline: bool,
|
||||
|
||||
table_state: TableState,
|
||||
table_alignments: Vec<Alignment>,
|
||||
table_cell_index: usize,
|
||||
numbers: HashMap<CowStr<'a>, usize>,
|
||||
}
|
||||
|
||||
impl<'a, I, W> HtmlWriter<'a, I, W>
|
||||
where
|
||||
I: Iterator<Item = Event<'a>>,
|
||||
W: StrWrite,
|
||||
{
|
||||
fn new(iter: I, writer: W) -> Self {
|
||||
Self {
|
||||
iter,
|
||||
writer,
|
||||
end_newline: true,
|
||||
table_state: TableState::Head,
|
||||
table_alignments: vec![],
|
||||
table_cell_index: 0,
|
||||
numbers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a new line.
|
||||
fn write_newline(&mut self) -> io::Result<()> {
|
||||
self.end_newline = true;
|
||||
self.writer.write_str("\n")
|
||||
}
|
||||
|
||||
/// Writes a buffer, and tracks whether or not a newline was written.
|
||||
#[inline]
|
||||
fn write(&mut self, s: &str) -> io::Result<()> {
|
||||
self.writer.write_str(s)?;
|
||||
|
||||
if !s.is_empty() {
|
||||
self.end_newline = s.ends_with('\n');
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(mut self) -> io::Result<()> {
|
||||
while let Some(event) = self.iter.next() {
|
||||
match event {
|
||||
Start(tag) => {
|
||||
self.start_tag(tag)?;
|
||||
}
|
||||
End(tag) => {
|
||||
self.end_tag(tag)?;
|
||||
}
|
||||
Text(text) => {
|
||||
escape_html(&mut self.writer, &text)?;
|
||||
self.end_newline = text.ends_with('\n');
|
||||
}
|
||||
Code(text) => {
|
||||
self.write("<code>")?;
|
||||
escape_html(&mut self.writer, &text)?;
|
||||
self.write("</code>")?;
|
||||
}
|
||||
Html(html) => {
|
||||
self.write(&html)?;
|
||||
}
|
||||
SoftBreak => {
|
||||
self.write_newline()?;
|
||||
}
|
||||
HardBreak => {
|
||||
self.write("<br />\n")?;
|
||||
}
|
||||
Rule => {
|
||||
if self.end_newline {
|
||||
self.write("<hr />\n")?;
|
||||
} else {
|
||||
self.write("\n<hr />\n")?;
|
||||
}
|
||||
}
|
||||
FootnoteReference(name) => {
|
||||
let len = self.numbers.len() + 1;
|
||||
self.write("<sup class=\"footnote-reference\"><a href=\"#")?;
|
||||
escape_html(&mut self.writer, &name)?;
|
||||
self.write("\">")?;
|
||||
let number = *self.numbers.entry(name).or_insert(len);
|
||||
write!(&mut self.writer, "{}", number)?;
|
||||
self.write("</a></sup>")?;
|
||||
}
|
||||
TaskListMarker(true) => {
|
||||
self.write("<input disabled=\"\" type=\"checkbox\" checked=\"\"/>\n")?;
|
||||
}
|
||||
TaskListMarker(false) => {
|
||||
self.write("<input disabled=\"\" type=\"checkbox\"/>\n")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes the start of an HTML tag.
|
||||
fn start_tag(&mut self, tag: Tag<'a>) -> io::Result<()> {
|
||||
match tag {
|
||||
Tag::Paragraph => {
|
||||
if self.end_newline {
|
||||
self.write("<p>")
|
||||
} else {
|
||||
self.write("\n<p>")
|
||||
}
|
||||
}
|
||||
Tag::Heading(level, id, classes) => {
|
||||
if self.end_newline {
|
||||
self.end_newline = false;
|
||||
self.write("<")?;
|
||||
} else {
|
||||
self.write("\n<")?;
|
||||
}
|
||||
write!(&mut self.writer, "{}", level)?;
|
||||
if let Some(id) = id {
|
||||
self.write(" id=\"")?;
|
||||
escape_html(&mut self.writer, id)?;
|
||||
self.write("\"")?;
|
||||
}
|
||||
let mut classes = classes.iter();
|
||||
if let Some(class) = classes.next() {
|
||||
self.write(" class=\"")?;
|
||||
escape_html(&mut self.writer, class)?;
|
||||
for class in classes {
|
||||
self.write(" ")?;
|
||||
escape_html(&mut self.writer, class)?;
|
||||
}
|
||||
self.write("\"")?;
|
||||
}
|
||||
self.write(">")
|
||||
}
|
||||
Tag::Table(alignments) => {
|
||||
self.table_alignments = alignments;
|
||||
self.write("<table>")
|
||||
}
|
||||
Tag::TableHead => {
|
||||
self.table_state = TableState::Head;
|
||||
self.table_cell_index = 0;
|
||||
self.write("<thead><tr>")
|
||||
}
|
||||
Tag::TableRow => {
|
||||
self.table_cell_index = 0;
|
||||
self.write("<tr>")
|
||||
}
|
||||
Tag::TableCell => {
|
||||
match self.table_state {
|
||||
TableState::Head => {
|
||||
self.write("<th")?;
|
||||
}
|
||||
TableState::Body => {
|
||||
self.write("<td")?;
|
||||
}
|
||||
}
|
||||
match self.table_alignments.get(self.table_cell_index) {
|
||||
Some(&Alignment::Left) => self.write(" style=\"text-align: left\">"),
|
||||
Some(&Alignment::Center) => self.write(" style=\"text-align: center\">"),
|
||||
Some(&Alignment::Right) => self.write(" style=\"text-align: right\">"),
|
||||
_ => self.write(">"),
|
||||
}
|
||||
}
|
||||
Tag::BlockQuote => {
|
||||
if self.end_newline {
|
||||
self.write("<blockquote>\n")
|
||||
} else {
|
||||
self.write("\n<blockquote>\n")
|
||||
}
|
||||
}
|
||||
Tag::CodeBlock(info) => {
|
||||
if !self.end_newline {
|
||||
self.write_newline()?;
|
||||
}
|
||||
match info {
|
||||
CodeBlockKind::Fenced(info) => {
|
||||
let lang = info.split(' ').next().unwrap();
|
||||
if lang.is_empty() {
|
||||
self.write("<pre><code>")
|
||||
} else {
|
||||
self.write("<pre><code class=\"language-")?;
|
||||
escape_html(&mut self.writer, lang)?;
|
||||
self.write("\">")
|
||||
}
|
||||
}
|
||||
CodeBlockKind::Indented => self.write("<pre><code>"),
|
||||
}
|
||||
}
|
||||
Tag::List(Some(1)) => {
|
||||
if self.end_newline {
|
||||
self.write("<ol>\n")
|
||||
} else {
|
||||
self.write("\n<ol>\n")
|
||||
}
|
||||
}
|
||||
Tag::List(Some(start)) => {
|
||||
if self.end_newline {
|
||||
self.write("<ol start=\"")?;
|
||||
} else {
|
||||
self.write("\n<ol start=\"")?;
|
||||
}
|
||||
write!(&mut self.writer, "{}", start)?;
|
||||
self.write("\">\n")
|
||||
}
|
||||
Tag::List(None) => {
|
||||
if self.end_newline {
|
||||
self.write("<ul>\n")
|
||||
} else {
|
||||
self.write("\n<ul>\n")
|
||||
}
|
||||
}
|
||||
Tag::Item => {
|
||||
if self.end_newline {
|
||||
self.write("<li>")
|
||||
} else {
|
||||
self.write("\n<li>")
|
||||
}
|
||||
}
|
||||
Tag::Emphasis => self.write("<em>"),
|
||||
Tag::Strong => self.write("<strong>"),
|
||||
Tag::Strikethrough => self.write("<del>"),
|
||||
Tag::Link(LinkType::Email, dest, title) => {
|
||||
self.write("<a href=\"mailto:")?;
|
||||
escape_href(&mut self.writer, &dest)?;
|
||||
if !title.is_empty() {
|
||||
self.write("\" title=\"")?;
|
||||
escape_html(&mut self.writer, &title)?;
|
||||
}
|
||||
self.write("\">")
|
||||
}
|
||||
Tag::Link(_link_type, dest, title) => {
|
||||
self.write("<a href=\"")?;
|
||||
escape_href(&mut self.writer, &dest)?;
|
||||
if !title.is_empty() {
|
||||
self.write("\" title=\"")?;
|
||||
escape_html(&mut self.writer, &title)?;
|
||||
}
|
||||
self.write("\">")
|
||||
}
|
||||
Tag::Image(_link_type, dest, title) => {
|
||||
self.write("<img src=\"")?;
|
||||
escape_href(&mut self.writer, &dest)?;
|
||||
self.write("\" alt=\"")?;
|
||||
self.raw_text()?;
|
||||
if !title.is_empty() {
|
||||
self.write("\" title=\"")?;
|
||||
escape_html(&mut self.writer, &title)?;
|
||||
}
|
||||
self.write("\" />")
|
||||
}
|
||||
Tag::FootnoteDefinition(name) => {
|
||||
if self.end_newline {
|
||||
self.write("<div class=\"footnote-definition\" id=\"")?;
|
||||
} else {
|
||||
self.write("\n<div class=\"footnote-definition\" id=\"")?;
|
||||
}
|
||||
escape_html(&mut self.writer, &name)?;
|
||||
self.write("\"><sup class=\"footnote-definition-label\">")?;
|
||||
let len = self.numbers.len() + 1;
|
||||
let number = *self.numbers.entry(name).or_insert(len);
|
||||
write!(&mut self.writer, "{}", number)?;
|
||||
self.write("</sup>")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn end_tag(&mut self, tag: Tag) -> io::Result<()> {
|
||||
match tag {
|
||||
Tag::Paragraph => {
|
||||
self.write("</p>\n")?;
|
||||
}
|
||||
Tag::Heading(level, _id, _classes) => {
|
||||
self.write("</")?;
|
||||
write!(&mut self.writer, "{}", level)?;
|
||||
self.write(">\n")?;
|
||||
}
|
||||
Tag::Table(_) => {
|
||||
self.write("</tbody></table>\n")?;
|
||||
}
|
||||
Tag::TableHead => {
|
||||
self.write("</tr></thead><tbody>\n")?;
|
||||
self.table_state = TableState::Body;
|
||||
}
|
||||
Tag::TableRow => {
|
||||
self.write("</tr>\n")?;
|
||||
}
|
||||
Tag::TableCell => {
|
||||
match self.table_state {
|
||||
TableState::Head => {
|
||||
self.write("</th>")?;
|
||||
}
|
||||
TableState::Body => {
|
||||
self.write("</td>")?;
|
||||
}
|
||||
}
|
||||
self.table_cell_index += 1;
|
||||
}
|
||||
Tag::BlockQuote => {
|
||||
self.write("</blockquote>\n")?;
|
||||
}
|
||||
Tag::CodeBlock(_) => {
|
||||
self.write("</code></pre>\n")?;
|
||||
}
|
||||
Tag::List(Some(_)) => {
|
||||
self.write("</ol>\n")?;
|
||||
}
|
||||
Tag::List(None) => {
|
||||
self.write("</ul>\n")?;
|
||||
}
|
||||
Tag::Item => {
|
||||
self.write("</li>\n")?;
|
||||
}
|
||||
Tag::Emphasis => {
|
||||
self.write("</em>")?;
|
||||
}
|
||||
Tag::Strong => {
|
||||
self.write("</strong>")?;
|
||||
}
|
||||
Tag::Strikethrough => {
|
||||
self.write("</del>")?;
|
||||
}
|
||||
Tag::Link(_, _, _) => {
|
||||
self.write("</a>")?;
|
||||
}
|
||||
Tag::Image(_, _, _) => (), // shouldn't happen, handled in start
|
||||
Tag::FootnoteDefinition(_) => {
|
||||
self.write("</div>\n")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// run raw text, consuming end tag
|
||||
fn raw_text(&mut self) -> io::Result<()> {
|
||||
let mut nest = 0;
|
||||
while let Some(event) = self.iter.next() {
|
||||
match event {
|
||||
Start(_) => nest += 1,
|
||||
End(_) => {
|
||||
if nest == 0 {
|
||||
break;
|
||||
}
|
||||
nest -= 1;
|
||||
}
|
||||
Html(text) | Code(text) | Text(text) => {
|
||||
escape_html(&mut self.writer, &text)?;
|
||||
self.end_newline = text.ends_with('\n');
|
||||
}
|
||||
SoftBreak | HardBreak | Rule => {
|
||||
self.write(" ")?;
|
||||
}
|
||||
FootnoteReference(name) => {
|
||||
let len = self.numbers.len() + 1;
|
||||
let number = *self.numbers.entry(name).or_insert(len);
|
||||
write!(&mut self.writer, "[{}]", number)?;
|
||||
}
|
||||
TaskListMarker(true) => self.write("[x]")?,
|
||||
TaskListMarker(false) => self.write("[ ]")?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and
|
||||
/// push it to a `String`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use pulldown_cmark::{html, Parser};
|
||||
///
|
||||
/// let markdown_str = r#"
|
||||
/// hello
|
||||
/// =====
|
||||
///
|
||||
/// * alpha
|
||||
/// * beta
|
||||
/// "#;
|
||||
/// let parser = Parser::new(markdown_str);
|
||||
///
|
||||
/// let mut html_buf = String::new();
|
||||
/// html::push_html(&mut html_buf, parser);
|
||||
///
|
||||
/// assert_eq!(html_buf, r#"<h1>hello</h1>
|
||||
/// <ul>
|
||||
/// <li>alpha</li>
|
||||
/// <li>beta</li>
|
||||
/// </ul>
|
||||
/// "#);
|
||||
/// ```
|
||||
pub fn push_html<'a, I>(s: &mut String, iter: I)
|
||||
where
|
||||
I: Iterator<Item = Event<'a>>,
|
||||
{
|
||||
HtmlWriter::new(iter, s).run().unwrap();
|
||||
}
|
2
crates/treehouse/src/html/mod.rs
Normal file
2
crates/treehouse/src/html/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
mod markdown;
|
||||
pub mod tree;
|
|
@ -1,5 +1,7 @@
|
|||
use treehouse_format::{ast::Branch, pull::BranchKind};
|
||||
|
||||
use super::markdown;
|
||||
|
||||
pub fn branch_to_html(s: &mut String, branch: &Branch, source: &str) {
|
||||
s.push_str("<li>");
|
||||
{
|
||||
|
@ -10,7 +12,18 @@ pub fn branch_to_html(s: &mut String, branch: &Branch, source: &str) {
|
|||
});
|
||||
s.push_str("<summary>");
|
||||
}
|
||||
s.push_str(&source[branch.content.clone()]);
|
||||
|
||||
let raw_block_content = &source[branch.content.clone()];
|
||||
let mut unindented_block_content = String::with_capacity(raw_block_content.len());
|
||||
let indent = " ".repeat(branch.indent_level);
|
||||
for line in raw_block_content.lines() {
|
||||
unindented_block_content.push_str(line.strip_prefix(&indent).unwrap_or(line));
|
||||
unindented_block_content.push('\n');
|
||||
}
|
||||
|
||||
let markdown_parser = pulldown_cmark::Parser::new(&unindented_block_content);
|
||||
markdown::push_html(s, markdown_parser);
|
||||
|
||||
if !branch.children.is_empty() {
|
||||
s.push_str("</summary>");
|
||||
branches_to_html(s, &branch.children, source);
|
|
@ -1,46 +1,33 @@
|
|||
mod tree_html;
|
||||
mod html;
|
||||
|
||||
use axum::Router;
|
||||
use codespan_reporting::{
|
||||
diagnostic::{Diagnostic, Label, LabelStyle, Severity},
|
||||
files::SimpleFile,
|
||||
term::termcolor::{ColorChoice, StandardStream},
|
||||
};
|
||||
use tree_html::branches_to_html;
|
||||
use treehouse_format::{
|
||||
ast::{Branch, Roots},
|
||||
pull::Parser,
|
||||
};
|
||||
use copy_dir::copy_dir;
|
||||
use handlebars::Handlebars;
|
||||
use html::tree::branches_to_html;
|
||||
use serde::Serialize;
|
||||
use tower_http::services::ServeDir;
|
||||
use tower_livereload::LiveReloadLayer;
|
||||
use treehouse_format::{ast::Roots, pull::Parser};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum Error {
|
||||
#[error("I/O error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("treehouse parsing error: {0}")]
|
||||
Parse(#[from] treehouse_format::ParseError),
|
||||
#[derive(Serialize)]
|
||||
pub struct TemplateData {
|
||||
pub tree: String,
|
||||
}
|
||||
|
||||
fn print_branch(branch: &Branch, source: &str) {
|
||||
fn inner(branch: &Branch, source: &str, indent_level: usize) {
|
||||
for _ in 0..indent_level {
|
||||
print!(" ");
|
||||
}
|
||||
println!(
|
||||
"{} {:?}",
|
||||
branch.kind.char(),
|
||||
&source[branch.content.clone()]
|
||||
);
|
||||
for child in &branch.children {
|
||||
inner(child, source, indent_level + 1);
|
||||
}
|
||||
}
|
||||
inner(branch, source, 0);
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn regenerate() -> anyhow::Result<()> {
|
||||
let _ = std::fs::remove_dir_all("target/site");
|
||||
std::fs::create_dir_all("target/site")?;
|
||||
|
||||
copy_dir("static", "target/site/static")?;
|
||||
|
||||
let mut handlebars = Handlebars::new();
|
||||
handlebars.register_template_file("template/index.hbs", "template/index.hbs")?;
|
||||
|
||||
let root_file = std::fs::read_to_string("content/tree/root.tree")?;
|
||||
let parse_result = Roots::parse(&mut Parser {
|
||||
input: &root_file,
|
||||
|
@ -49,13 +36,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
match parse_result {
|
||||
Ok(roots) => {
|
||||
let mut html = String::from("<!DOCTYPE html><html><head></head><body>");
|
||||
for root in &roots.branches {
|
||||
print_branch(root, &root_file);
|
||||
}
|
||||
branches_to_html(&mut html, &roots.branches, &root_file);
|
||||
std::fs::write("target/site/index.html", &html)?;
|
||||
html.push_str("</body></html>")
|
||||
let mut tree = String::new();
|
||||
branches_to_html(&mut tree, &roots.branches, &root_file);
|
||||
|
||||
let index_html = handlebars.render("template/index.hbs", &TemplateData { tree })?;
|
||||
|
||||
std::fs::write("target/site/index.html", index_html)?;
|
||||
}
|
||||
Err(error) => {
|
||||
let writer = StandardStream::stderr(ColorChoice::Auto);
|
||||
|
@ -77,29 +63,43 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
}
|
||||
}
|
||||
|
||||
// let mut parser = treehouse_format::Parser {
|
||||
// input: &root_file,
|
||||
// position: 0,
|
||||
// };
|
||||
// let mut generator = HtmlGenerator::default();
|
||||
// while let Some(branch) = parser.next_branch()? {
|
||||
// for _ in 0..branch.indent_level {
|
||||
// print!(" ");
|
||||
// }
|
||||
// println!(
|
||||
// "{} {:?}",
|
||||
// branch.kind.char(),
|
||||
// &root_file[branch.content.clone()]
|
||||
// );
|
||||
// generator.add(&root_file, &branch);
|
||||
// }
|
||||
// std::fs::write(
|
||||
// "target/site/index.html",
|
||||
// format!(
|
||||
// "<!DOCTYPE html><html><head></head><body>{}</body></html>",
|
||||
// generator.finish()
|
||||
// ),
|
||||
// )?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn regenerate_or_report_error() {
|
||||
eprintln!("regenerating");
|
||||
|
||||
match regenerate() {
|
||||
Ok(_) => (),
|
||||
Err(error) => eprintln!("error: {error:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn web_server() -> anyhow::Result<()> {
|
||||
let app = Router::new().nest_service("/", ServeDir::new("target/site"));
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let app = app.layer(LiveReloadLayer::new());
|
||||
|
||||
Ok(axum::Server::bind(&([0, 0, 0, 0], 8080).into())
|
||||
.serve(app.into_make_service())
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn fallible_main() -> anyhow::Result<()> {
|
||||
regenerate_or_report_error();
|
||||
|
||||
web_server().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
match fallible_main().await {
|
||||
Ok(_) => (),
|
||||
Err(error) => eprintln!("fatal: {error:?}"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue