use std::{ path::{Path, PathBuf}, vec, }; use comrak::Options; use serde::{Deserialize, Serialize}; use tracing::warn; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] pub struct AppConfig { /// Set the title and heading of the repository index page /// /// Default value: "Git repository browser" pub root_title: String, /// Set a subtitle for the repository index page /// /// Default value: `unset` pub root_description: String, /// Include some more info about example.com on the index page /// /// Default value: `unset` pub root_readme: String, /// Allow download of source tarballs /// /// Default value: `["tar.bz", "tar.bz2", "zip"]` pub snapshots: Vec, /// Add a favicon /// /// Default value: `unset` pub favicon: String, /// Use a custom logo image /// /// Default value: `unset` pub logo: String, /// Alt text for the custom logo image /// /// Used as a fallback when `logo` is unset /// /// Default value: "🏡" pub logo_alt: String, /// Url loaded when clicking on the logo /// /// Default value: "/" pub logo_link: String, /// Allow http transport git clone /// /// Default value: `true` pub enable_http_clone: bool, /// Show extra links for each repository on the index page /// /// Default value: `false` pub enable_index_links: bool, /// Show owner on the index page /// /// Default value: `true` pub enable_index_owner: bool, /// Generate HTTPS clone urls /// /// Default value: `unset` pub clone_prefix: String, /// Generate SSH clone urls /// /// Default value: `unset` pub ssh_clone_prefix: String, /// Specifies the maximum number of commit message characters to display in "log" view. /// /// A value of "0" indicates no limit. /// /// Default value: "0" pub max_commit_message_length: usize, /// Specifies the maximum number of description characters to display on the repository index page. /// /// A value of "0" indicates no limit. /// /// Default value: "0" pub max_repo_desc_length: usize, /// Specifies an absolute path on the filesystem to the css document to include in all pages. /// /// If set, the default stylesheet will be replaced with this one. /// /// Default value: `None` pub css: Option, /// Specifies an absolute path on the filesystem to the file to serve at `/robots.txt`. /// /// If set, the default `robots.txt` will be replaced with this one. /// /// Default value: `None` pub robots_txt: Option, /// Specifies the style of header to use. /// /// `Default` is the default style of an emoji and title text /// /// `Text` is only the title text and a subtitle (if applicable) /// /// `Image` is an image, title text, and a subtitle (if applicable) /// /// Default value: `Default` pub header: Option, } impl ::std::default::Default for AppConfig { fn default() -> Self { Self { root_title: String::from("Git repository browser"), root_description: String::new(), root_readme: String::new(), snapshots: vec![ String::from("tar.gz"), String::from("tar.bz2"), String::from("zip"), ], favicon: String::new(), logo: String::from("🏡"), logo_alt: String::new(), logo_link: String::from("/"), enable_http_clone: true, enable_index_links: false, enable_index_owner: true, clone_prefix: String::new(), ssh_clone_prefix: String::new(), max_commit_message_length: 0, max_repo_desc_length: 0, css: None, robots_txt: None, header: Some(HeaderStyle::default()), } } } impl AppConfig { pub fn load(path: String) -> Self { confy::load_path(path).unwrap_or_default() } pub fn http_clone_enabled(&self) -> bool { (!self.clone_prefix.is_empty() || !self.ssh_clone_prefix.is_empty()) && self.enable_http_clone } pub fn root_readme_is_valid(&self) -> bool { Path::new(&self.root_readme).try_exists().unwrap_or(false) } /// Loads a (s)css file from disk and compiles it using `rsass` /// /// if `Ok()`, returns the file contents. /// If `Err()` or `None`, returns an empty slice. pub fn get_css_data(&self) -> Box<[u8]> { self.css .as_ref() .map(|file| { let format = rsass::output::Format { style: rsass::output::Style::Compressed, ..rsass::output::Format::default() }; rsass::compile_scss_path(&PathBuf::from(file), format).unwrap_or_else(|_| { warn!( "Unable to load or build css from path {:?}. Using defaults.", file ); Vec::new() }) }) .unwrap_or_else(|| Vec::new()) .into_boxed_slice() } } pub struct ReadmeConfig; impl ReadmeConfig { pub fn gfm() -> Options { // enable gfm extensions // https://github.github.com/gfm/ let mut options = Options::default(); options.extension.autolink = true; options.extension.footnotes = true; options.extension.strikethrough = true; options.extension.table = true; options.extension.tagfilter = true; options.extension.tasklist = true; options } } /// Represents available header styles for rendering the frontend. #[derive(Debug, Default, Clone, Serialize, PartialEq, Eq)] pub enum HeaderStyle { /// Emoji logo, text title #[default] Default, /// Text title, text subtitle Text, /// Image logo, text title, text subtitle Image, } impl<'de> Deserialize<'de> for HeaderStyle { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let variant: &str = Deserialize::deserialize(deserializer)?; // Support case insensitivity match variant.to_lowercase().as_str() { "default" => Ok(HeaderStyle::Default), "text" => Ok(HeaderStyle::Text), "image" => Ok(HeaderStyle::Image), _ => Ok(HeaderStyle::Default), } } }