use std::path::PathBuf; extern crate historian; use historian::{Historian, Page, Edit, PageRenderer, export_wiki, Linker}; #[macro_use] extern crate rocket; use rocket::serde::json::Json; use rocket::Responder; use rocket::response::{Redirect, content::{RawHtml, RawText}}; use rocket::form::Form; use rocket::http::uri::Origin; use rocket::fs::NamedFile; use rocket::State; extern crate clap; use clap::Parser; extern crate grep; #[macro_use] extern crate toml; #[derive(Responder)] enum PageResponder { Redirect(Redirect), File(NamedFile), Page(RawHtml), Text(RawText) } #[get("/")] async fn page<'r>( path: PathBuf, origin: &Origin<'r>, historian: &State, renderer: &State ) -> Option { let path_str = path.to_str().unwrap(); if let Some(resource_path) = renderer.resolve_to_resource(path_str) { Some(PageResponder::File(NamedFile::open(resource_path).await.unwrap())) } else if let Some(attachment_path) = historian.resolve_to_attachment(path_str) { Some(PageResponder::File(NamedFile::open(attachment_path).await.unwrap())) } else { historian.resolve_to_page(path_str).map(|page| { if let Some(query) = origin.query() { for (key, value) in query.segments() { if key == "action" { if value == "edit" { return PageResponder::Page(RawHtml(renderer.render_page_template("edit.html", &historian, &page, &toml! { dynamic = true }))) } else if value == "history" { return PageResponder::Page(RawHtml(renderer.template("history.html") .with_historian(&historian) .with_page(&page) .insert("revisions", &historian.get_history_of_page(&page)) .insert("options", &toml! { dynamic = true }) .render())) } } if key == "revision" { if let Some(revision) = historian.get_revision_by_id(value) { return PageResponder::Page(RawHtml(renderer.template("revision.html") .with_historian(&historian) .with_page(&page) .insert("revision", &revision) .insert("content", &historian.get_page_text_of_revision(&page, &revision).unwrap()) .insert("options", &toml! { dynamic = true }) .render())) } } } } if page.is_directory && !origin.path().ends_with("/") { PageResponder::Redirect(Redirect::to(origin.path().as_str().to_owned() + "/")) } else { PageResponder::Page(RawHtml(renderer.render_page(&historian, &page, &toml! { dynamic = true }))) } }).or_else(|| { if let Some(query) = origin.query() { for (key, value) in query.segments() { if key == "action" && value == "edit" { return Some(PageResponder::Page(RawHtml(renderer.template("edit.html") .with_historian(&historian) .with_url(&path_str) .insert("page", &historian.start_page(path_str)) .insert("content", "") .insert("ancestors", &Vec::::with_capacity(0)) .insert("options", &toml! { dynamic = true }) .render()))) } } None } else { Some(PageResponder::Redirect(Redirect::to(origin.path().as_str().to_owned() + "?action=edit"))) } }) } } #[derive(FromForm)] struct PageAction<'r> { content: &'r str, author: &'r str, summary: &'r str, subaction: &'r str } #[post("/", data = "")] fn action( path: PathBuf, origin: &Origin, historian: &State, renderer: &State, action: Form> ) -> PageResponder { let path_str = path.to_str().unwrap(); let page = historian.resolve_to_page(path_str).unwrap_or_else(|| historian.start_page(path_str)); if let Some(query) = origin.query() { println!("{:?}", query); for (key, value) in query.segments() { if key == "action" { if value == "edit" { if action.subaction == "preview" { return PageResponder::Page(RawHtml(renderer.template("edit.html") .with_page(&page) .with_historian(&historian) .insert("content", action.content) .insert("subaction", action.subaction) .insert("summary", action.summary) .insert("author", action.author) .insert("options", &toml! { dynamic = true }) .render())) } else { historian.submit_edit(&page, &Edit { author: Some(action.author.to_owned()), summary: action.summary.to_owned(), content: action.content.to_owned() }); return PageResponder::Redirect(Redirect::to(origin.path().as_str().to_owned())) } } else if value == "resolvelinks" { let linker = Linker::new(&historian); return PageResponder::Text(RawText(linker.resolve_links_for_edit(&page, &Edit { author: None, summary: action.summary.to_owned(), content: action.content.to_owned() }))) } } } } PageResponder::Page(RawHtml(renderer.render_template("message.html", &historian, &toml! { header = "Invalid Action" message = "Action was invalid or misunderstood" }, &toml! { dynamic = true }))) } #[derive(Parser)] #[command(version, about, long_about = None)] struct Args { /// Path to wiki repository wiki_path: String, /// Render the wiki to a static website #[arg(long)] render_to: Option, /// Resolve a link to a page on the wiki #[arg(long)] resolve_link: Option, /// Resolve all wiki links in the given page, output the modified text #[arg(long)] resolve_links: Option, /// Path to templates #[arg(long)] template_path: Option } #[rocket::main] async fn main() { let args = Args::parse(); let historian = Historian::new(args.wiki_path); let renderer = if let Some(template_path) = args.template_path { PageRenderer::with_template_path(&template_path) } else { PageRenderer::new() }; let linker = Linker::new(&historian); if let Some(resolve_link) = args.resolve_link { println!("{}", linker.resolve_link(&resolve_link).unwrap()); return; } if let Some(resolve_links) = args.resolve_links { let page = historian.resolve_to_page(&resolve_links).expect("failed to find page"); println!("{}", linker.resolve_links(&page)); return; } if let Some(render_to) = args.render_to { export_wiki(&historian, &renderer, &render_to); return; } rocket::build() .manage(historian) .manage(renderer) .mount("/", routes![page, action]) .launch() .await; }