Files
historian/src/main.rs
Gregory Marco 3ec4c11219
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Add tag search.
2025-08-31 01:52:25 -05:00

324 lines
11 KiB
Rust

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<String>),
Text(RawText<String>)
}
#[get("/<path..>")]
async fn page<'r>(
path: PathBuf, origin: &Origin<'r>,
historian: &State<Historian>, renderer: &State<PageRenderer>
) -> Option<PageResponder> {
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::<String>::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("/<path..>", data = "<action>")]
fn action(
path: PathBuf, origin: &Origin,
historian: &State<Historian>, renderer: &State<PageRenderer>,
action: Form<PageAction<'_>>
) -> 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<String>,
/// Print a tree of the wiki contents.
#[arg(long)]
print_tree: bool,
/// Render the wiki to a static website
#[arg(long)]
render_to: Option<String>,
/// Resolve a link to a page on the wiki
#[arg(long)]
resolve_link: Option<String>,
/// Resolve all wiki links in the given page, output the modified text
#[arg(long)]
resolve_links: Option<String>,
/// Path to templates
#[arg(long)]
template_path: Option<String>,
/// Search the wiki
#[arg(long)]
search: Option<String>,
/// Search the wiki by tag
#[arg(long)]
tag: Option<String>,
/// Search root
#[arg(long)]
search_root: Option<String>
}
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;
}