gitscope
← rgit / 0.1.1 / src/main.rs
https://dev.kronoz.win/r/rgit.git
src/main.rs · Rust
Blame Raw
  1#![deny(clippy::pedantic)]
  2
  3use std::net::SocketAddr;
  4use std::path::PathBuf;
  5use std::{sync::Arc, time::Duration};
  6
  7use askama::Template;
  8use axum::{
  9    body::Body,
 10    http,
 11    http::{HeaderValue, StatusCode},
 12    response::{IntoResponse, Response},
 13    routing::get,
 14    Extension, Router,
 15};
 16use bat::assets::HighlightingAssets;
 17use clap::Parser;
 18use syntect::html::ClassStyle;
 19use tower_http::cors::CorsLayer;
 20use tower_layer::layer_fn;
 21use tracing::{info, instrument};
 22
 23use crate::{git::Git, layers::logger::LoggingMiddleware};
 24
 25mod database;
 26mod git;
 27mod layers;
 28mod methods;
 29mod syntax_highlight;
 30
 31const CRATE_VERSION: &str = clap::crate_version!();
 32
 33#[derive(Parser, Debug)]
 34#[clap(author, version, about)]
 35pub struct Args {
 36    /// Path to a directory in which the Sled database should be stored, will be created if it doesn't already exist
 37    ///
 38    /// The Sled database is very quick to generate, so this can be pointed to temporary storage
 39    #[clap(short, long, value_parser)]
 40    db_store: PathBuf,
 41    /// The socket address to bind to (eg. 0.0.0.0:3333)
 42    bind_address: SocketAddr,
 43    /// The path in which your bare Git repositories reside (will be scanned recursively)
 44    scan_path: PathBuf,
 45}
 46
 47#[tokio::main]
 48async fn main() {
 49    let args: Args = Args::parse();
 50
 51    let subscriber = tracing_subscriber::fmt();
 52    #[cfg(debug_assertions)]
 53    let subscriber = subscriber.pretty();
 54    subscriber.init();
 55
 56    let db = sled::Config::default()
 57        .use_compression(true)
 58        .path(&args.db_store)
 59        .open()
 60        .unwrap();
 61
 62    std::thread::spawn({
 63        let db = db.clone();
 64        let scan_path = args.scan_path.clone();
 65
 66        move || loop {
 67            info!("Running periodic index");
 68            crate::database::indexer::run(&scan_path, &db);
 69            info!("Finished periodic index");
 70
 71            std::thread::sleep(Duration::from_secs(300));
 72        }
 73    });
 74
 75    let bat_assets = HighlightingAssets::from_binary();
 76    let syntax_set = bat_assets.get_syntax_set().unwrap().clone();
 77
 78    let theme = bat_assets.get_theme("GitHub");
 79    let css = syntect::html::css_for_theme_with_class_style(theme, ClassStyle::Spaced).unwrap();
 80    let css = Box::leak(
 81        format!(r#"@media (prefers-color-scheme: light){{{css}}}"#)
 82            .into_boxed_str()
 83            .into_boxed_bytes(),
 84    );
 85
 86    let dark_theme = bat_assets.get_theme("TwoDark");
 87    let dark_css =
 88        syntect::html::css_for_theme_with_class_style(dark_theme, ClassStyle::Spaced).unwrap();
 89    let dark_css = Box::leak(
 90        format!(r#"@media (prefers-color-scheme: dark){{{dark_css}}}"#)
 91            .into_boxed_str()
 92            .into_boxed_bytes(),
 93    );
 94
 95    let static_favicon = |content: &'static [u8]| {
 96        move || async move {
 97            let mut resp = Response::new(Body::from(content));
 98            resp.headers_mut().insert(
 99                http::header::CONTENT_TYPE,
100                HeaderValue::from_static("image/x-icon"),
101            );
102            resp
103        }
104    };
105
106    let static_css = |content: &'static [u8]| {
107        move || async move {
108            let mut resp = Response::new(Body::from(content));
109            resp.headers_mut().insert(
110                http::header::CONTENT_TYPE,
111                HeaderValue::from_static("text/css"),
112            );
113            resp
114        }
115    };
116
117    let app = Router::new()
118        .route("/", get(methods::index::handle))
119        .route(
120            "/style.css",
121            get(static_css(include_bytes!(concat!(
122                env!("OUT_DIR"),
123                "/statics/css/style.css"
124            )))),
125        )
126        .route("/highlight.css", get(static_css(css)))
127        .route("/highlight-dark.css", get(static_css(dark_css)))
128        .route(
129            "/favicon.ico",
130            get(static_favicon(include_bytes!("../statics/favicon.ico"))),
131        )
132        .fallback(methods::repo::service)
133        .layer(layer_fn(LoggingMiddleware))
134        .layer(Extension(Arc::new(Git::new(syntax_set))))
135        .layer(Extension(db))
136        .layer(Extension(Arc::new(args.scan_path)))
137        .layer(CorsLayer::new());
138
139    axum::Server::bind(&args.bind_address)
140        .serve(app.into_make_service_with_connect_info::<std::net::SocketAddr>())
141        .await
142        .unwrap();
143}
144
145#[instrument(skip(t))]
146pub fn into_response<T: Template>(t: &T) -> Response {
147    match t.render() {
148        Ok(body) => {
149            let headers = [(
150                http::header::CONTENT_TYPE,
151                HeaderValue::from_static(T::MIME_TYPE),
152            )];
153
154            (headers, body).into_response()
155        }
156        Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
157    }
158}