Parent [>]
1mod cli;
2mod error;
3mod filters;
4mod routes;
5mod theme;
6
7use std::sync::OnceLock;
8
9use clap::Parser;
10use tokio::net::TcpListener;
11
12use crate::error::Result;
13use crate::theme::{BASE_CSS, MARKDOWN_CSS};
14
15/// Package name, sourced from `Cargo.toml` at compile time.
16pub const PKG_NAME: &str = env!("CARGO_PKG_NAME");
17
18/// Package version, sourced from `Cargo.toml` at compile time.
19pub const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
20
21/// Hostname used in breadcrumbs and clone URLs, set once from `--hostname`.
22static HOSTNAME: OnceLock<String> = OnceLock::new();
23
24/// SSH clone URL path prefix, set once from `--ssh-prefix`.
25static SSH_PREFIX: OnceLock<String> = OnceLock::new();
26
27/// Base URL path prefix for reverse proxy support, set once from `--base-url`.
28static BASE_URL: OnceLock<String> = OnceLock::new();
29
30/// Returns the configured hostname, or `"index"` if none was provided.
31pub fn hostname() -> &'static str {
32    HOSTNAME.get().map_or("index", |s| s.as_str())
33}
34
35/// Returns the configured SSH prefix, or an empty string if none was provided.
36pub fn ssh_prefix() -> &'static str {
37    SSH_PREFIX.get().map_or("", |s| s.as_str())
38}
39
40/// Returns the configured base URL path prefix, or an empty string if none was
41/// provided.
42pub fn base_url() -> &'static str {
43    BASE_URL.get().map_or("", |s| s.as_str())
44}
45
46#[tokio::main]
47async fn main() -> Result<()> {
48    let args = cli::Args::parse();
49
50    if let Some(hostname) = args.hostname {
51        let _ = HOSTNAME.set(hostname);
52    }
53    if let Some(ssh_prefix) = args.ssh_prefix {
54        let _ = SSH_PREFIX.set(ssh_prefix);
55    }
56    if let Some(base_url) = args.base_url {
57        let _ = BASE_URL.set(base_url);
58    }
59
60    // Concatenate base styles, markdown styles, and theme variables into a
61    // single stylesheet served at /style.css.
62    let theme_vars = match args.theme_file {
63        Some(path) => std::fs::read_to_string(path)?,
64        None => args.theme.css().to_owned(),
65    };
66    let css = format!("{theme_vars}\n{BASE_CSS}\n{MARKDOWN_CSS}");
67
68    let state = routes::AppState {
69        root: args.root,
70        css,
71    };
72
73    let router = routes::router(state);
74    let addr = format!("{}:{}", args.bind, args.port);
75    let socket = TcpListener::bind(&addr).await?;
76
77    axum::serve(socket, router).await?;
78
79    Ok(())
80}