diff --git a/orgize-sync/Cargo.toml b/orgize-sync/Cargo.toml index 36ff904..85f27cf 100644 --- a/orgize-sync/Cargo.toml +++ b/orgize-sync/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" all-features = true [features] -default = ["google_calendar", "toggl"] +default = ["dotenv", "google_calendar", "toggl"] google_calendar = [] toggl = [] @@ -16,6 +16,7 @@ toggl = [] app_dirs = "1.2.1" chrono = { version = "0.4.9", features = ["serde"] } colored = "1.8.0" +dotenv = { version = "0.14.1", optional = true } futures-preview = "=0.3.0-alpha.18" isahc = { version = "0.7.3", default-features = false, features = ["json", "http2", "static-curl"] } orgize = { path = "../orgize", default-features = false, features = ["chrono"] } diff --git a/orgize-sync/src/conf.rs b/orgize-sync/src/conf.rs index f95e87a..15ba584 100644 --- a/orgize-sync/src/conf.rs +++ b/orgize-sync/src/conf.rs @@ -1,50 +1,136 @@ -use app_dirs::{app_root, AppDataType, AppInfo}; -use serde::{Deserialize, Serialize}; +use std::env; +use std::fs; use std::path::PathBuf; +use app_dirs::{app_root, AppDataType, AppInfo}; +use serde::{Deserialize, Serialize}; + +use crate::conf::google_calendar::*; use crate::error::Result; -#[derive(Serialize, Deserialize)] +const APP_INFO: AppInfo = AppInfo { + name: "orgize-sync", + author: "PoiScript", +}; + +#[derive(Serialize, Deserialize, Debug)] pub struct Conf { + #[serde(default = "default_env_path")] + pub env_path: PathBuf, + #[serde(default)] pub files: Vec, #[cfg(feature = "google_calendar")] + #[serde(skip_serializing_if = "Option::is_none")] pub google_calendar: Option, } -#[derive(Serialize, Deserialize)] -#[cfg(feature = "google_calendar")] -pub struct GoogleCalendarGlobalConf { - pub client_id: String, - pub client_secret: String, - pub token_dir: String, - pub token_filename: String, - pub property: String, +#[derive(Serialize, Deserialize, Debug)] +pub struct EnvConf { + #[serde(default = "default_env_path")] + pub env_path: PathBuf, } -#[derive(Serialize, Deserialize)] -#[cfg(feature = "google_calendar")] -pub struct GoogleCalendarConf { - pub calendar: String, - pub append_new: bool, - pub append_headline: String, +pub fn user_config_path() -> PathBuf { + app_root(AppDataType::UserConfig, &APP_INFO).unwrap() } -#[derive(Serialize, Deserialize)] +pub fn user_cache_path() -> PathBuf { + app_root(AppDataType::UserCache, &APP_INFO).unwrap() +} + +pub fn default_config_path() -> PathBuf { + let mut path = user_config_path(); + path.push("conf.toml"); + path +} + +pub fn default_env_path() -> PathBuf { + let mut path = user_cache_path(); + path.push(".env"); + path +} + +impl Conf { + pub fn new(path: Option) -> Result { + let path = path.unwrap_or_else(default_config_path); + + let content = fs::read(&path).expect(&format!( + "Failed to read file: {}", + path.as_path().display() + )); + + if cfg!(feature = "dotenv") { + let env_conf: EnvConf = toml::from_slice(&content)?; + dotenv::from_path(env_conf.env_path.as_path())?; + } + + Ok(toml::from_slice(&content)?) + } +} + +#[derive(Serialize, Deserialize, Debug)] pub struct File { pub path: String, - pub name: String, + pub name: Option, #[cfg(feature = "google_calendar")] + #[serde(skip_serializing_if = "Option::is_none")] pub google_calendar: Option, } -pub fn default_conf_path() -> Result { - let mut path = app_root( - AppDataType::UserConfig, - &AppInfo { - name: "orgize-sync", - author: "PoiScript", - }, - )?; - path.push("conf.toml"); - Ok(path) +#[cfg(feature = "google_calendar")] +pub mod google_calendar { + use super::*; + + #[derive(Serialize, Deserialize, Debug)] + pub struct GoogleCalendarGlobalConf { + #[serde(default = "default_client_id")] + pub client_id: String, + #[serde(default = "default_client_secret")] + pub client_secret: String, + #[serde(default = "default_token_dir")] + pub token_dir: PathBuf, + #[serde(default = "default_token_filename")] + pub token_filename: String, + #[serde(default = "default_property")] + pub property: String, + #[serde(default = "default_redirect_uri")] + pub redirect_uri: String, + } + + #[derive(Serialize, Deserialize, Debug)] + pub struct GoogleCalendarConf { + pub calendar: String, + #[serde(default)] + pub append_new: bool, + #[serde(default = "default_append_headline")] + pub append_headline: String, + } + + fn default_client_id() -> String { + env::var("GOOGLE_CLIENT_ID").unwrap() + } + + fn default_client_secret() -> String { + env::var("GOOGLE_CLIENT_SECRET").unwrap() + } + + fn default_token_dir() -> PathBuf { + app_root(AppDataType::UserCache, &APP_INFO).unwrap() + } + + fn default_token_filename() -> String { + "google-token.json".into() + } + + fn default_property() -> String { + "EVENT_ID".into() + } + + fn default_redirect_uri() -> String { + "http://localhost".into() + } + + fn default_append_headline() -> String { + "Sync".into() + } } diff --git a/orgize-sync/src/error.rs b/orgize-sync/src/error.rs index b53f32c..211d41b 100644 --- a/orgize-sync/src/error.rs +++ b/orgize-sync/src/error.rs @@ -1,15 +1,21 @@ use app_dirs::AppDirsError; +use dotenv::Error as EnvError; use isahc::http::Error as HttpError; use isahc::Error as IsahcError; use std::convert::From; use std::io::Error as IOError; +use toml::de::Error as TomlDeError; +use toml::ser::Error as TomlSerError; use url::ParseError; #[derive(Debug)] pub enum Error { AppDirs(AppDirsError), - IO(IOError), + Env(EnvError), Http(IsahcError), + IO(IOError), + TomlDe(TomlDeError), + TomlSer(TomlSerError), Url(ParseError), } @@ -19,6 +25,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: EnvError) -> Self { + Error::Env(err) + } +} + impl From for Error { fn from(err: IOError) -> Self { Error::IO(err) @@ -37,6 +49,18 @@ impl From for Error { } } +impl From for Error { + fn from(err: TomlDeError) -> Self { + Error::TomlDe(err) + } +} + +impl From for Error { + fn from(err: TomlSerError) -> Self { + Error::TomlSer(err) + } +} + impl From for Error { fn from(err: ParseError) -> Self { Error::Url(err) diff --git a/orgize-sync/src/main.rs b/orgize-sync/src/main.rs index 480bf3b..56b8721 100644 --- a/orgize-sync/src/main.rs +++ b/orgize-sync/src/main.rs @@ -1,10 +1,14 @@ mod conf; mod error; +use std::fs; use std::path::PathBuf; use structopt::StructOpt; +use toml::to_string_pretty; -use crate::conf::default_conf_path; +use crate::conf::{ + default_config_path, default_env_path, user_cache_path, user_config_path, Conf, EnvConf, +}; use crate::error::Result; #[derive(StructOpt, Debug)] @@ -28,30 +32,64 @@ enum Cmd { conf_path: Option, }, #[structopt(name = "conf")] - Conf, + Conf { + #[structopt(short = "c", long = "conf", parse(from_os_str))] + conf_path: Option, + }, } fn main() -> Result<()> { let opt = Opt::from_args(); match opt.subcommand { + Cmd::Init => { + fs::create_dir_all(user_config_path())?; + fs::create_dir_all(user_cache_path())?; + + let default_env_path = default_env_path(); + let default_config_path = default_config_path(); + + if default_env_path.as_path().exists() { + println!( + "{} already existed, skipping ...", + default_env_path.as_path().display() + ); + } else { + println!("Creating {} ...", default_env_path.as_path().display()); + fs::write(default_env_path.clone(), "")?; + } + + if default_config_path.as_path().exists() { + println!( + "{} already existed, skipping ...", + default_config_path.as_path().display() + ); + } else { + println!("Creating {} ...", default_config_path.as_path().display()); + fs::write( + default_config_path, + to_string_pretty(&EnvConf { + env_path: default_env_path, + })?, + )?; + } + } Cmd::Sync { conf_path, skip_google_calendar, skip_toggl, } => { - let conf_path = conf_path - .map(Result::Ok) - .unwrap_or_else(default_conf_path)?; - - println!("{:#?}", conf_path); + let conf = Conf::new(conf_path)?; if cfg!(feature = "google_calendar") && !skip_google_calendar {} if cfg!(feature = "toggl") && !skip_toggl {} } - Cmd::Init => (), - Cmd::Conf => (), + Cmd::Conf { conf_path } => { + let conf = Conf::new(conf_path)?; + + println!("{}", to_string_pretty(&conf)?); + } } Ok(())