Built from repo's markdown files as a proof-of-concept for prest and maintained to prettify documentation. Served at prest.blog. Here goes all it's source code:
Cargo.toml
[package]
name = "blog"
edition = "2021"
[dependencies]
prest = { version = "0.2", path = "../../" }
wasm-bindgen = "0.2"
markdown = "1.0.0-alpha.16"
toml = "0.8.8"
[build-dependencies]
prest-build = { path = "../../build/", version = "0.2" }
[package.metadata]
domain = "prest.blog"
build.rs
use prest_build::*;
fn main() {
default_cfg_aliases();
build_pwa(PWAOptions::default()).unwrap();
}
src/main.rs
use prest::*;
embed_build_output_as!(BuiltAssets);
fn main() {
init!();
blog::routes().embed(BuiltAssets).run()
}
src/lib.rs
use prest::*;
mod content;
use content::{ExampleCategory::*, EXAMPLES, INTERNALS, PREST_VERSION, README};
pub fn routes() -> Router {
let mut router = route("/", get(README.clone())).route("/internals", get(INTERNALS.clone()));
for readme in EXAMPLES.iter() {
router = router.route(&readme.url, get(readme.content.as_str()));
}
router.wrap_non_htmx(page)
}
async fn page(content: Markup) -> Markup {
let dbs = EXAMPLES.iter().filter(|r| r.category == Database);
let todos = EXAMPLES.iter().filter(|r| r.category == Todo);
let others = EXAMPLES.iter().filter(|r| r.category == Other);
html!((DOCTYPE) html data-theme="dark" {
(Head::with_title("Prest Blog").style(CODE_STYLES).css("https://unpkg.com/prismjs@1.29.0/themes/prism-tomorrow.min.css"))
body."max-w-screen-md lg:max-w-screen-lg container md:mx-auto"
hx-boost="true" hx-swap="innerHTML transition:true show:window:top" hx-target="main" {
nav."navbar bg-base-200 shadow-lg rounded-box my-4"{
."navbar-start md:gap-2" {
a."btn btn-ghost btn-circle" href="https://docs.rs/prest" target="_blank" {(PreEscaped(include_str!("../icons/docs.svg")))}
a."btn btn-ghost btn-circle" href="https://github.com/edezhic/prest" target="_blank" {(PreEscaped(include_str!("../icons/github.svg")))}
}
."navbar-center" { a."btn btn-ghost" href="/" {"PREST"} }
."navbar-end"{."dropdown dropdown-bottom dropdown-end"{
@if is_pwa() {
."indicator mr-4" hx-get="/sw/health" hx-target="this" hx-trigger="load delay:3s" hx-swap="none"
hx-on--after-request="
const b = document.querySelector('#sw-badge');
const s = event.detail.successful ? 'badge-success' : 'badge-error';
b.classList.replace('badge-warning', s)"
{
#"sw-badge" ."indicator-item badge badge-warning"{}
."font-bold" {"PWA"}
}
}
."btn btn-ghost btn-circle" tabindex="0" role="button" {
svg."h-5 w-5" style="transform: scale(-1,1)" fill="none" viewBox="0 0 24 24" stroke="currentColor" {
path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" {}
}
}
ul."menu menu-md dropdown-content mt-3 z-10 p-2 bg-base-300 shadow-xl rounded-box w-52" tabindex="0" {
li { a href="/internals" {"internals"}}
li{ h2."menu-title"{"tutorials"}
ul{@for r in todos {
li { a href={(r.url)} {(r.label)}}
}}}
li{ h2."menu-title"{"databases"}
ul{@for r in dbs {
li { a href={(r.url)} {(r.label)}}
}}}
li{ h2."menu-title"{"others"}
ul{@for r in others {
li { a href={(r.url)} {(r.label)}}
}}}
li { a href="/about" {"about"}}
}
}}
}
main."view-transition mx-auto p-4 prose lg:prose-xl lg:w-[1024px] lg:max-w-[1024px] [overflow-wrap:anywhere]"
hx-history-elt hx-on--before-swap="document.activeElement.blur()"
hx-on--after-swap=(format!("Prism.highlightAll(); {LINKS_JS}"))
{(content)}
script {(PreEscaped(LINKS_JS))}
."menu menu-horizontal w-full items-center justify-center bg-base-200 rounded-box mb-4 mx-auto"{
."font-mono" {"v"(*PREST_VERSION)}
."ml-4 mr-2" {"made by Egor Dezhic"}
a."btn btn-ghost btn-circle" href="https://twitter.com/eDezhic" target="_blank" {(PreEscaped(include_str!("../icons/twitter.svg")))}
a."btn btn-ghost btn-circle" href="https://edezhic.medium.com" target="_blank" {(PreEscaped(include_str!("../icons/medium.svg")))}
a."btn btn-ghost btn-circle" href="mailto:edezhic@gmail.com" target="_blank" {(PreEscaped(include_str!("../icons/email.svg")))}
}
(Scripts::default()
.include("https://unpkg.com/prismjs@1.29.0/components/prism-core.min.js")
.include("https://unpkg.com/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js")
)
}
})
}
const LINKS_JS: &str = "document.querySelectorAll('main a').forEach(el => !el.href.includes('prest') && !el.href.includes('localhost') && el.setAttribute('target', '_blank'))";
const CODE_STYLES: PreEscaped<&str> = PreEscaped(
r#"
code {
font-size: 12px !important;
}
@media screen and (min-width: 1024px) {
code {
font-size: 15px !important;
}
}
code .table {
display: inherit;
font-size: inherit;
}
p:has(> code):has(+ pre) {
margin: 16px 0 4px 24px !important;
}
"#,
);
#[cfg(sw)]
#[wasm_bindgen(start)]
pub fn main() {
routes().handle_fetch_events()
}
src/content.rs
use prest::*;
state!(README: String = { md_to_html(include_str!("../../../README.md")) });
state!(INTERNALS: String = {
let md = include_str!("../../../UNDER_THE_HOOD.md").to_owned();
let processed = preprocess_md(md, "../../..", Some(include_str!("../../../Cargo.toml")));
md_to_html(&processed)
});
state!(PREST_VERSION: String = {
let manifest = include_str!("../../../Cargo.toml").parse::<toml::Table>().unwrap();
manifest["package"]["version"].as_str().unwrap().to_owned()
});
embed_as!(ExamplesDocs from "../" only "*.md");
embed_as!(ExamplesCode from "../" except "*.md");
pub struct Example {
pub path: String,
pub url: String,
pub label: String,
pub content: String,
pub category: ExampleCategory,
}
#[derive(PartialEq)]
pub enum ExampleCategory {
Blog,
Database,
Todo,
Other,
}
pub use ExampleCategory::*;
state!(EXAMPLES: Vec<Example> = {
let mut examples = vec![];
for path in ExamplesDocs::iter() {
let path = path.to_string();
let url = if path.starts_with("blog") {
"/about".to_owned()
} else {
format!("/{}", path.trim_start_matches("databases/").trim_end_matches("/README.md"))
};
let label = url.replace('/', "").replace('-', " ");
let category = match path.split('/').next().unwrap() {
"databases" => Database,
s if s.contains("todo") => Todo,
s if s == "blog" => Blog,
_ => Other
};
let doc = ExamplesDocs::get_content(&path).unwrap();
let dir = path.trim_end_matches("/README.md");
let processed = preprocess_md(doc, &dir, None);
let content = md_to_html(&processed);
examples.push(Example { path, url, label, content, category });
}
examples
});
pub fn preprocess_md(doc: String, doc_dir: &str, code: Option<&str>) -> String {
let mut processed = String::new();
for line in doc.lines() {
// replace references like {src/main.rs 50:71} with lines from the referenced files
if line.starts_with("{") && line.ends_with("}") {
let reference = line.replace(['{', '}'], "");
let Some(relative_path) = reference.split(':').next() else {
panic!("Reference {reference} from {doc_dir} has invalid syntax")
};
let lang = source_language(&relative_path);
let full_path = format!("{doc_dir}/{relative_path}");
let code = match code {
Some(code) => code.to_owned(),
None => match ExamplesCode::get_content(&full_path) {
Some(code) => code,
None => panic!("Not found {full_path} mentioned in {doc_dir}"),
},
};
let code = match reference.split(':').skip(1).next() {
// select lines like :25 or :50-71
Some(lines_refs) => {
let mut refs = lines_refs.split('-');
let start = refs.next().unwrap().parse::<usize>().unwrap();
let end = if let Some(end) = refs.next() {
end.parse::<usize>().unwrap()
} else {
start
};
let mut snippet = if start > 1 {
"...\n".to_owned()
} else {
String::new()
};
snippet += &code.lines().skip(start - 1).take(end - start + 1).fold(
String::new(),
|mut code, line| {
code += line;
code + "\n"
},
);
if code.lines().count() > end {
snippet += "...\n";
}
snippet
}
None => code,
};
processed += &format!("`{reference}`\n");
processed += &format!("\n```{lang}\n{}\n```\n", code.trim_end());
} else {
processed += &format!("{line}\n");
}
}
processed
}
use markdown::{to_html_with_options, Options};
pub fn md_to_html(md: &str) -> String {
#[cfg(debug_assertions)]
let md = md.replace("https://prest.blog", "http://localhost");
to_html_with_options(&md, &Options::gfm()).unwrap()
}
fn source_language(filename: &str) -> &str {
match filename {
f if f.ends_with(".rs") => "rust",
f if f.ends_with(".toml") => "toml",
f if f.ends_with(".css") => "css",
f if f.ends_with(".scss") => "scss",
f if f.ends_with(".html") => "html",
f if f.ends_with(".sql") => "sql",
f if f.ends_with(".ts") => "typescript",
_ => "",
}
}