From 2d9d511bb86aa883f6deec4f8c78999f56f4148f Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Sat, 12 Nov 2022 15:51:34 +0530 Subject: [PATCH] feat: read configuration from repositories ref: https://git.batsense.net/LibrePages/librepages/issues/8 --- Cargo.lock | 21 ++ Cargo.toml | 2 + src/main.rs | 1 + src/page_config.rs | 198 ++++++++++++++++++ .../contains-everything/toml/librepages.toml | 18 ++ 5 files changed, 240 insertions(+) create mode 100644 src/page_config.rs create mode 100644 tests/cases/contains-everything/toml/librepages.toml diff --git a/Cargo.lock b/Cargo.lock index cb0c13d..b321ce0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1611,9 +1611,11 @@ dependencies = [ "rust-embed", "serde", "serde_json", + "serde_yaml", "sqlx", "tera", "tokio", + "toml", "tracing", "tracing-actix-web", "url", @@ -2158,6 +2160,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d232d893b10de3eb7258ff01974d6ee20663d8e833263c99409d4b13a0209da" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha-1" version = "0.10.0" @@ -2732,6 +2747,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68" + [[package]] name = "untrusted" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index a2b31f5..f8e1e97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,8 @@ rust-embed = "6.3.0" rand = "0.8.5" tracing = { version = "0.1.37", features = ["log"]} tracing-actix-web = "0.6.2" +toml = "0.5.9" +serde_yaml = "0.9.14" [dependencies.cache-buster] git = "https://github.com/realaravinth/cache-buster" diff --git a/src/main.rs b/src/main.rs index a7f2cd1..c90d9c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,7 @@ mod errors; mod git; mod meta; mod page; +mod page_config; mod pages; mod preview; mod serve; diff --git a/src/page_config.rs b/src/page_config.rs new file mode 100644 index 0000000..b32acf2 --- /dev/null +++ b/src/page_config.rs @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2022 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +use std::path::Path; + +use serde::{Deserialize, Serialize}; + +use crate::git::{ContentType, GitFileMode}; + +#[derive(Deserialize, Serialize, Debug, Eq, PartialEq, Clone)] +pub struct Config { + pub source: Source, + pub domains: Option>, + pub forms: Option, + pub image_compression: Option, + pub redirects: Option>, +} + +#[derive(Deserialize, Serialize, PartialEq, Eq, Debug, Clone)] +pub struct Source { + production_branch: String, + staging: Option, +} + +#[derive(Deserialize, Serialize, PartialEq, Eq, Debug, Clone)] +pub struct Forms { + pub enable: bool, +} + +#[derive(Deserialize, Serialize, PartialEq, Eq, Debug, Clone)] +pub struct ImageCompression { + pub enable: bool, +} + +#[derive(Deserialize, Serialize, PartialEq, Eq, Debug, Clone)] +pub struct Redirects { + pub from: String, + pub to: String, +} + +#[derive(Deserialize, Debug, Serialize, PartialEq, Eq)] +struct Policy<'a> { + rel_path: &'a str, + format: SupportedFormat, +} + +impl<'a> Policy<'a> { + const fn new(rel_path: &'a str, format: SupportedFormat) -> Self { + Self { rel_path, format } + } +} + +#[derive(Deserialize, Debug, Serialize, PartialEq, Eq)] +enum SupportedFormat { + Json, + Yaml, + Toml, +} + +impl Config { + pub fn load>(repo_path: &P, branch: &str) -> Option { + const POLICIES: [Policy; 2] = [ + Policy::new("librepages.toml", SupportedFormat::Toml), + Policy::new("librepages.json", SupportedFormat::Json), + ]; + + if let Some(policy) = Self::discover(repo_path, branch, &POLICIES) { + // let path = p.repo.as_ref().join(policy.rel_path); + //let contents = fs::read_to_string(path).await.unwrap(); + + let file = + crate::git::read_preview_file(&repo_path.as_ref().into(), branch, policy.rel_path) + .unwrap(); + if let ContentType::Text(contents) = file.content { + let res = match policy.format { + SupportedFormat::Json => Self::load_json(&contents), + SupportedFormat::Yaml => Self::load_yaml(&contents), + SupportedFormat::Toml => Self::load_toml(&contents), + }; + + return Some(res); + }; + } + + None + } + fn discover<'a, P: AsRef>( + repo_path: &P, + branch: &str, + policies: &'a [Policy<'a>], + ) -> Option<&'a Policy<'a>> { + let repo = git2::Repository::open(&repo_path).unwrap(); + + let branch = repo.find_branch(&branch, git2::BranchType::Local).unwrap(); + // let tree = head.peel_to_tree().unwrap(); + let branch = branch.into_reference(); + let tree = branch.peel_to_tree().unwrap(); + + for p in policies.iter() { + let file_exists = tree.iter().any(|x| { + if let Some(name) = x.name() { + if policies.iter().any(|p| p.rel_path == name) { + let mode: GitFileMode = x.into(); + match mode { + GitFileMode::Executable | GitFileMode::Regular => true, + _ => false, + } + } else { + false + } + } else { + false + } + }); + + if file_exists { + return Some(p); + } + } + None + } + + fn load_toml(c: &str) -> Config { + toml::from_str(c).unwrap() + } + + fn load_yaml(c: &str) -> Config { + serde_yaml::from_str(c).unwrap() + } + + fn load_json(c: &str) -> Config { + serde_json::from_str(&c).unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::git::tests::write_file_util; + use mktemp::Temp; + + #[actix_rt::test] + async fn page_config_test() { + let tmp_dir = Temp::new_dir().unwrap(); + let repo_path = tmp_dir.join("page_config_test"); + + let content = std::fs::read_to_string( + &Path::new("./tests/cases/contains-everything/toml/librepages.toml") + .canonicalize() + .unwrap(), + ) + .unwrap(); + + write_file_util( + repo_path.to_str().unwrap(), + "librepages.toml", + Some(&content), + ); + + let config = Config::load(&repo_path, "master").unwrap(); + assert!(config.forms.as_ref().unwrap().enable); + assert!(config.image_compression.as_ref().unwrap().enable); + assert_eq!(config.source.production_branch, "librepages"); + assert_eq!(config.source.staging.as_ref().unwrap(), "beta"); + + assert_eq!( + config.redirects.as_ref().unwrap(), + &vec![ + Redirects { + from: "/from1".into(), + to: "/to1".into() + }, + Redirects { + from: "/from2".into(), + to: "/to2".into() + }, + ] + ); + + assert_eq!( + config.domains.as_ref().unwrap(), + &vec!["example.org".to_string(), "example.com".to_string(),] + ); + } +} diff --git a/tests/cases/contains-everything/toml/librepages.toml b/tests/cases/contains-everything/toml/librepages.toml new file mode 100644 index 0000000..a5e2c94 --- /dev/null +++ b/tests/cases/contains-everything/toml/librepages.toml @@ -0,0 +1,18 @@ +domains = [ + "example.org", + "example.com", +] +redirects = [ +{from = "/from1", to = "/to1"}, +{from = "/from2", to = "/to2"}, +] + +[source] +production_branch = "librepages" +staging = "beta" + +[forms] +enable = true + +[image_compression] +enable = true