use std::path::PathBuf; extern crate historian; use historian::{Historian, Page, Edit, PageRenderer, export_wiki, Linker, Searcher, SearchResult}; #[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 key == "q" { return PageResponder::Page(RawHtml(renderer.template("search.html") .with_historian(&historian) .with_page(&page) .insert("results", &Searcher::new(&historian).search(&page, value)) .insert("options", &toml! { dynamic = true }) .render())) } if key == "tag" { return PageResponder::Page(RawHtml(renderer.template("search.html") .with_historian(&historian) .with_page(&page) .insert("results", &Searcher::new(&historian).tag_search(&page, value)) .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: Option, /// Print a tree of the wiki contents. #[arg(long)] print_tree: bool, /// 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, /// Search the wiki #[arg(long)] search: Option, /// Search the wiki by tag #[arg(long)] tag: Option, /// Search root #[arg(long)] search_root: Option } fn print_tree(historian: &Historian, page: &Page, prefix: &str) { for child in &page.children { if let Some(child_page) = historian.resolve_to_page(&child.full_name) { println!("{}{}", prefix, child_page.full_name); print_tree(historian, &child_page, &format!("{}{}", prefix, prefix)); } } } fn print_result(result: &SearchResult) { println!("{}", result.page.full_name); for line in &result.matches { println!("{}", line); } println!("---"); } #[rocket::main] async fn main() { let args = Args::parse(); if let Some(wiki_path) = args.wiki_path { let historian = Historian::new(wiki_path); if args.print_tree { print_tree(&historian, &historian.resolve_to_page("").unwrap(), "*"); return; } 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; } if let Some(search) = args.search { let searcher = Searcher::new(&historian); let search_root = args.search_root.as_deref().unwrap_or(""); let page = historian.resolve_to_page(&search_root).expect("failed to find page"); for result in searcher.search(&page, &search) { print_result(&result); } return; } if let Some(tag) = args.tag { let searcher = Searcher::new(&historian); let search_root = args.search_root.as_deref().unwrap_or(""); let page = historian.resolve_to_page(&search_root).expect("failed to find page"); for result in searcher.tag_search(&page, &tag) { print_result(&result); } return; } rocket::build() .manage(historian) .manage(renderer) .mount("/", routes![page, action]) .launch() .await; return; } else if let Some(resolve_links) = args.resolve_links { if let Some((historian, page)) = Historian::resolve_from_file(&resolve_links) { let linker = Linker::new(&historian); println!("{}", linker.resolve_links(&page)); return; } return; } return; }