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.4", path = "../../" }
wasm-bindgen = "0.2"
markdown = "1.0.0-alpha.16"
toml = "0.8.8"
[build-dependencies]
prest-build = { version = "0.3", path = "../../build", features = ["typescript", "sass"] }
[package.metadata]
domain = "prest.blog"
build.rs
use prest_build::*;
fn main() {
default_cfg_aliases();
bundle_ts("src/ui/prism.js").unwrap();
bundle_sass("src/ui/prism-tomorrow.css").unwrap();
build_pwa(PWAOptions::new()).unwrap();
}
src/main.rs
use prest::*;
embed_build_output_as!(BuiltAssets);
#[derive(Debug, Table, Default, Serialize, Deserialize)]
struct Todo {
pub id: Uuid,
pub custom: Inner,
pub done: bool,
}
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
struct Inner {
a: String,
b: NaiveDateTime,
}
fn main() {
init!(tables Todo);
// prepare table to showcase in the admin panel
Todo {
id: Uuid::now_v7(),
custom: Inner {
a: "' delete * from users;".to_owned(),
b: Default::default(),
},
done: false,
}
.save()
.expect("should save");
blog::routes().embed(BuiltAssets).run()
}
src/lib.rs
use prest::*;
mod content;
pub use content::{ExampleCategory::*, EXAMPLES, INTERNALS, PREST_VERSION, README, RUST};
pub fn routes() -> Router {
// pre-init content
let _ = *EXAMPLES;
let _ = *INTERNALS;
let _ = *README;
let _ = *RUST;
let _ = *PREST_VERSION;
let mut router = route("/", get(README.clone()))
.route("/internals", get(INTERNALS.clone()))
.route("/rust", get(RUST.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 $"bg-stone-800 font-sans text-[#bbc4d4]" _="on click remove .open from #menu" {
(Head::with_title("Prest Blog"))
body $"max-w-screen-md lg:max-w-screen-lg md:mx-auto" boost="true" swap="innerHTML show:window:top" into="main" {
nav $"bg-stone-900 top-4 mb-4 p-5 shadow-lg rounded-full grid grid-cols-3 items-center sticky z-10" {
$"flex gap-6" {
a $"hover:text-white" href="https://github.com/edezhic/prest" {(include_html!("icons/github.svg"))}
a $"hover:text-white" href="https://docs.rs/prest" {(include_html!("icons/docs.svg"))}
}
a $"font-mono font-bold text-center hover:text-white" href="/" {"PREST"}
$"flex justify-end" {
a $"hover:text-white mr-2 lg:mr-6" href="/admin" boost="false" {(include_html!("icons/admin.svg"))}
@if is_pwa() {
div $"mr-2 lg:mr-6 font-bold text-sm" get="/sw/health" swap="none transition:false" trigger="load delay:3s"
_="on htmx:afterRequest
if event.detail.successful set my.style.color to '#059669'
else set my.style.color to '#991b1b'
halt the event"
{"SW"}
}
$"hover:text-white" _="on click add .open to #menu halt" {
svg $"h-5 w-5 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" {}
}
}
div #menu $"absolute bg-stone-950 z-20 top-8 px-4 truncate shadow-xl rounded-xl w-52" {
style {"
#menu { max-height: 0px } #menu.open { max-height: 1000px }
#menu a { display: flex; align-items: center; padding: 0.25rem 0 0.25rem 0.5rem; border-radius: 1rem; }
#menu a:hover { background-color: #292524 }
"}
$"py-4 flex flex-col gap-2 text-xs" {
a href="/rust" {"about rust"}
a href="/internals" {"internals"}
a href="/about" {"about blog"}
$"font-bold text-sm pt-2" {"tutorials"}
@for r in todos { a href={(r.url)} {(r.label)}}
$"font-bold text-sm pt-2" {"databases"}
@for r in dbs { a href={(r.url)} {(r.label)}}
$"font-bold text-sm pt-2" {"others"}
@for r in others { a href={(r.url)} {(r.label)}}
}
}
}
}
style {r#"
main a { text-decoration: underline }
main h3 { font-size: 2em }
main ul, main ol { list-style: circle }
main h4 {
font-size: 2rem;
font-weight: 100;
padding-top: 1rem;
}
code { font-size: 13px !important }
"#}
main history-elt _="on load or htmx:afterSwap call format_content()"
$"opacity-80 mx-auto p-4 gap-3 flex flex-col text-sm lg:text-base leading-loose"
{(content)}
$"flex items-center justify-evenly p-4 w-full bg-stone-900 rounded-full mb-4 mx-auto text-xs lg:text-base" {
$"font-mono" {"v"(*PREST_VERSION)}
$"text-sm" {"made by Egor Dezhic"}
$"flex gap-3"{
a href="https://twitter.com/eDezhic" {(include_html!("icons/twitter.svg"))}
a href="mailto:edezhic@gmail.com" {(include_html!("icons/email.svg"))}
}
}
(Scripts::default()
.include("/prism.js")
.css("/prism-tomorrow.css")
.hyperscript("
def format_content()
call Prism.highlightAll()
for a in <a /> in <body />
if not (a.href contains 'prest.') and not (a.href contains 'localhost')
set @target of a to '_blank'
")
)
}
})
}
#[cfg(sw)]
#[wasm_bindgen(start)]
pub fn main() {
routes().handle_fetch_events()
}
src/content.rs
use prest::*;
state!(README: String = {
let src = include_str!("../../../README.md");
let homepage = src.trim_start_matches("# prest").trim_start();
md_to_html(homepage)
});
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!(RUST: String = { md_to_html(include_str!("../../../RUST.md")) });
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");
#[allow(dead_code)]
pub struct Example {
pub path: String,
pub url: String,
pub label: String,
pub content: String,
pub category: ExampleCategory,
}
#[derive(PartialEq, Serialize)]
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",
_ => "",
}
}