437 lines
12 KiB
Rust
437 lines
12 KiB
Rust
use std::path::{Path, PathBuf};
|
|
|
|
use anyhow::Context;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
|
pub struct Config {
|
|
#[serde(default)]
|
|
pub settings: Settings,
|
|
|
|
#[serde(default)]
|
|
pub providers: Providers,
|
|
}
|
|
|
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]
|
|
pub struct Settings {
|
|
#[serde(default)]
|
|
pub projects: Projects,
|
|
|
|
#[serde(default)]
|
|
pub cache: Cache,
|
|
|
|
pub post_clone_command: Option<PostCloneCommand>,
|
|
pub post_update_command: Option<PostUpdateCommand>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
|
#[serde(untagged)]
|
|
pub enum PostUpdateCommand {
|
|
Single(String),
|
|
Multiple(Vec<String>),
|
|
}
|
|
|
|
impl PostUpdateCommand {
|
|
pub fn get_commands(&self) -> Vec<String> {
|
|
match self.clone() {
|
|
PostUpdateCommand::Single(item) => vec![item],
|
|
PostUpdateCommand::Multiple(items) => items,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
|
#[serde(untagged)]
|
|
pub enum PostCloneCommand {
|
|
Single(String),
|
|
Multiple(Vec<String>),
|
|
}
|
|
|
|
impl PostCloneCommand {
|
|
pub fn get_commands(&self) -> Vec<String> {
|
|
match self.clone() {
|
|
PostCloneCommand::Single(item) => vec![item],
|
|
PostCloneCommand::Multiple(items) => items,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]
|
|
pub struct Projects {
|
|
pub directory: ProjectLocation,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct ProjectLocation(PathBuf);
|
|
|
|
impl From<PathBuf> for ProjectLocation {
|
|
fn from(value: PathBuf) -> Self {
|
|
Self(value)
|
|
}
|
|
}
|
|
|
|
impl From<ProjectLocation> for PathBuf {
|
|
fn from(value: ProjectLocation) -> Self {
|
|
value.0
|
|
}
|
|
}
|
|
|
|
impl Default for ProjectLocation {
|
|
fn default() -> Self {
|
|
let home = dirs::home_dir().unwrap_or_default();
|
|
|
|
Self(home.join("git"))
|
|
}
|
|
}
|
|
|
|
impl std::ops::Deref for ProjectLocation {
|
|
type Target = PathBuf;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]
|
|
pub struct Cache {
|
|
#[serde(default)]
|
|
pub location: CacheLocation,
|
|
|
|
#[serde(default)]
|
|
pub duration: CacheDuration,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct CacheLocation(PathBuf);
|
|
|
|
impl From<PathBuf> for CacheLocation {
|
|
fn from(value: PathBuf) -> Self {
|
|
Self(value)
|
|
}
|
|
}
|
|
|
|
impl From<CacheLocation> for PathBuf {
|
|
fn from(value: CacheLocation) -> Self {
|
|
value.0
|
|
}
|
|
}
|
|
|
|
impl Default for CacheLocation {
|
|
fn default() -> Self {
|
|
let home = dirs::home_dir().unwrap_or_default();
|
|
|
|
Self(home.join(".cache/gitnow"))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
|
#[serde(untagged)]
|
|
pub enum CacheDuration {
|
|
Enabled(bool),
|
|
Precise {
|
|
#[serde(default)]
|
|
days: u64,
|
|
#[serde(default)]
|
|
hours: u64,
|
|
#[serde(default)]
|
|
minutes: u64,
|
|
},
|
|
}
|
|
|
|
impl CacheDuration {
|
|
pub fn get_duration(&self) -> Option<std::time::Duration> {
|
|
match self {
|
|
CacheDuration::Enabled(true) => CacheDuration::default().get_duration(),
|
|
CacheDuration::Enabled(false) => None,
|
|
CacheDuration::Precise {
|
|
days,
|
|
hours,
|
|
minutes,
|
|
} => Some(
|
|
std::time::Duration::from_days(*days)
|
|
+ std::time::Duration::from_hours(*hours)
|
|
+ std::time::Duration::from_mins(*minutes),
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for CacheDuration {
|
|
fn default() -> Self {
|
|
Self::Precise {
|
|
days: 7,
|
|
hours: 0,
|
|
minutes: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]
|
|
pub struct Providers {
|
|
#[serde(default)]
|
|
pub github: Vec<GitHub>,
|
|
#[serde(default)]
|
|
pub gitea: Vec<Gitea>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
|
pub struct GitHub {
|
|
#[serde(default)]
|
|
pub url: Option<String>,
|
|
|
|
pub access_token: GitHubAccessToken,
|
|
|
|
#[serde(default)]
|
|
pub current_user: Option<String>,
|
|
|
|
#[serde(default)]
|
|
pub users: Vec<GitHubUser>,
|
|
#[serde(default)]
|
|
pub organisations: Vec<GitHubOrganisation>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
|
pub struct GitHubUser(String);
|
|
|
|
impl From<GitHubUser> for String {
|
|
fn from(value: GitHubUser) -> Self {
|
|
value.0
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a GitHubUser> for &'a str {
|
|
fn from(value: &'a GitHubUser) -> Self {
|
|
value.0.as_str()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
|
pub struct GitHubOrganisation(String);
|
|
|
|
impl From<GitHubOrganisation> for String {
|
|
fn from(value: GitHubOrganisation) -> Self {
|
|
value.0
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a GitHubOrganisation> for &'a str {
|
|
fn from(value: &'a GitHubOrganisation) -> Self {
|
|
value.0.as_str()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
|
pub struct Gitea {
|
|
pub url: String,
|
|
|
|
#[serde(default)]
|
|
pub access_token: Option<GiteaAccessToken>,
|
|
|
|
#[serde(default)]
|
|
pub current_user: Option<String>,
|
|
|
|
#[serde(default)]
|
|
pub users: Vec<GiteaUser>,
|
|
#[serde(default)]
|
|
pub organisations: Vec<GiteaOrganisation>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
|
#[serde(untagged)]
|
|
pub enum GiteaAccessToken {
|
|
Direct(String),
|
|
Env { env: String },
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
|
#[serde(untagged)]
|
|
pub enum GitHubAccessToken {
|
|
Direct(String),
|
|
Env { env: String },
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
|
pub struct GiteaUser(String);
|
|
|
|
impl From<GiteaUser> for String {
|
|
fn from(value: GiteaUser) -> Self {
|
|
value.0
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a GiteaUser> for &'a str {
|
|
fn from(value: &'a GiteaUser) -> Self {
|
|
value.0.as_str()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
|
pub struct GiteaOrganisation(String);
|
|
impl From<GiteaOrganisation> for String {
|
|
fn from(value: GiteaOrganisation) -> Self {
|
|
value.0
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a GiteaOrganisation> for &'a str {
|
|
fn from(value: &'a GiteaOrganisation) -> Self {
|
|
value.0.as_str()
|
|
}
|
|
}
|
|
|
|
impl Config {
|
|
pub async fn from_file(file_path: &Path) -> anyhow::Result<Config> {
|
|
if !file_path.exists() {
|
|
if let Some(parent) = file_path.parent() {
|
|
tokio::fs::create_dir_all(parent).await?;
|
|
}
|
|
tokio::fs::File::create(file_path).await?;
|
|
}
|
|
|
|
let file_content = tokio::fs::read_to_string(file_path).await?;
|
|
|
|
Self::from_string(&file_content)
|
|
}
|
|
|
|
pub fn from_string(content: &str) -> anyhow::Result<Config> {
|
|
toml::from_str(content).context("failed to deserialize config file")
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_can_parse_config() -> anyhow::Result<()> {
|
|
let content = r#"
|
|
[settings]
|
|
projects = { directory = "git" }
|
|
|
|
[settings.cache]
|
|
location = ".cache/gitnow"
|
|
duration = { days = 2 }
|
|
|
|
[[providers.github]]
|
|
current_user = "kjuulh"
|
|
access_token = "some-token"
|
|
users = ["kjuulh"]
|
|
organisations = ["lunarway"]
|
|
|
|
[[providers.github]]
|
|
access_token = { env = "something" }
|
|
users = ["other"]
|
|
organisations = ["org"]
|
|
|
|
[[providers.gitea]]
|
|
url = "https://git.front.kjuulh.io/api/v1"
|
|
current_user = "kjuulh"
|
|
users = ["kjuulh"]
|
|
organisations = ["lunarway"]
|
|
|
|
[[providers.gitea]]
|
|
url = "https://git.front.kjuulh.io/api/v1"
|
|
users = ["other"]
|
|
organisations = ["org"]
|
|
|
|
[[providers.gitea]]
|
|
url = "https://git.front.kjuulh.io/api/v1"
|
|
"#;
|
|
|
|
let config = Config::from_string(content)?;
|
|
|
|
pretty_assertions::assert_eq!(
|
|
Config {
|
|
providers: Providers {
|
|
github: vec![
|
|
GitHub {
|
|
users: vec![GitHubUser("kjuulh".into())],
|
|
organisations: vec![GitHubOrganisation("lunarway".into())],
|
|
url: None,
|
|
access_token: GitHubAccessToken::Direct("some-token".into()),
|
|
current_user: Some("kjuulh".into())
|
|
},
|
|
GitHub {
|
|
users: vec![GitHubUser("other".into())],
|
|
organisations: vec![GitHubOrganisation("org".into())],
|
|
url: None,
|
|
access_token: GitHubAccessToken::Env {
|
|
env: "something".into()
|
|
},
|
|
current_user: None
|
|
}
|
|
],
|
|
gitea: vec![
|
|
Gitea {
|
|
url: "https://git.front.kjuulh.io/api/v1".into(),
|
|
users: vec![GiteaUser("kjuulh".into())],
|
|
organisations: vec![GiteaOrganisation("lunarway".into())],
|
|
access_token: None,
|
|
current_user: Some("kjuulh".into())
|
|
},
|
|
Gitea {
|
|
url: "https://git.front.kjuulh.io/api/v1".into(),
|
|
users: vec![GiteaUser("other".into())],
|
|
organisations: vec![GiteaOrganisation("org".into())],
|
|
access_token: None,
|
|
current_user: None
|
|
},
|
|
Gitea {
|
|
url: "https://git.front.kjuulh.io/api/v1".into(),
|
|
users: vec![],
|
|
organisations: vec![],
|
|
access_token: None,
|
|
current_user: None
|
|
},
|
|
]
|
|
},
|
|
settings: Settings {
|
|
cache: Cache {
|
|
location: PathBuf::from(".cache/gitnow").into(),
|
|
duration: CacheDuration::Precise {
|
|
days: 2,
|
|
hours: 0,
|
|
minutes: 0
|
|
}
|
|
},
|
|
projects: Projects {
|
|
directory: PathBuf::from("git").into()
|
|
},
|
|
post_update_command: None,
|
|
post_clone_command: None
|
|
}
|
|
},
|
|
config
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_can_parse_empty_config() -> anyhow::Result<()> {
|
|
let content = r#"
|
|
# empty file
|
|
"#;
|
|
|
|
let config = Config::from_string(content)?;
|
|
|
|
assert_eq!(
|
|
Config {
|
|
providers: Providers {
|
|
github: vec![],
|
|
gitea: vec![]
|
|
},
|
|
settings: Settings {
|
|
cache: Cache::default(),
|
|
projects: Projects::default(),
|
|
post_update_command: None,
|
|
post_clone_command: None
|
|
}
|
|
},
|
|
config
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
}
|