Add init command

This commit is contained in:
fluo10 2025-05-06 22:15:29 +09:00
parent cb83a386dc
commit ba84742a13
10 changed files with 151 additions and 93 deletions

6
Cargo.lock generated
View file

@ -838,15 +838,19 @@ name = "dpts-cli"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"chrono-tz",
"clap", "clap",
"dpts-core", "dpts-client",
"thiserror 2.0.12", "thiserror 2.0.12",
"tokio",
"toml",
] ]
[[package]] [[package]]
name = "dpts-client" name = "dpts-client"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chrono-tz",
"clap", "clap",
"dpts-core", "dpts-core",
"serde", "serde",

View file

@ -6,7 +6,10 @@ edition.workspace = true
repository.workspace = true repository.workspace = true
[dependencies] [dependencies]
dpts-core = {workspace = true} dpts-client = {workspace = true}
chrono = {workspace = true} chrono = {workspace = true}
chrono-tz.workspace = true
clap = {workspace = true, features = ["derive"]} clap = {workspace = true, features = ["derive"]}
thiserror.workspace = true thiserror.workspace = true
tokio.workspace = true
toml.workspace = true

View file

@ -3,6 +3,18 @@ pub enum Error {
#[error("Parse int error")] #[error("Parse int error")]
ParseInt(#[from] std::num::ParseIntError), ParseInt(#[from] std::num::ParseIntError),
#[error("Missing config value: ({0})")] #[error("Missing config value: ({0})")]
MissingConfig(String) MissingConfig(String),
#[error("Parse toml error")]
TomlDe(#[from] toml::de::Error),
} }
impl From<dpts_client::error::Error> for Error {
fn from(e: dpts_client::error::Error) -> Self {
match e {
dpts_client::error::Error::TomlDe(x)=> Self::TomlDe(x),
dpts_client::error::Error::ParseInt(x) => Self::ParseInt(x),
dpts_client::error::Error::MissingConfig(x) => Self::MissingConfig(x),
}
}
}

72
dpts-cli/src/init.rs Normal file
View file

@ -0,0 +1,72 @@
use clap::{Args, Subcommand};
use chrono_tz::Tz;
use crate::error::Error;
use dpts_client::auth::try_login;
use dpts_client::config::{
Config,
ClientConfig,
ClientRemoteStorageConfig,
ClientStorageConfig,
PartialGlobalConfig
};
#[derive(Args, Clone, Debug)]
pub struct InitArgs {
#[command(subcommand)]
pub command: InitCommand,
}
impl InitArgs {
pub fn run(self) -> Result<(), Error> {
match self.command {
InitCommand::Local(x) => x.run(),
InitCommand::Remote(x) => x.run(),
}
}
}
#[derive(Clone, Debug, Subcommand)]
enum InitCommand {
Local(InitLocalArgs),
Remote(InitRemoteArgs),
}
#[derive(Args, Clone, Debug)]
pub struct InitLocalArgs {
pub time_zone: Option<Tz>,
}
impl InitLocalArgs {
pub fn run(self) -> Result<(), Error> {
unimplemented!()
}
}
#[derive(Args, Clone, Debug)]
pub struct InitRemoteArgs {
pub endpoint: String,
#[arg(short, long)]
pub user_name: String,
#[arg(short, long)]
pub password: String,
#[arg(short, long)]
pub time_zone: Option<Tz>,
}
impl InitRemoteArgs {
pub fn run(self) -> Result<(), Error> {
let token: String = try_login(&self.user_name, &self.password, &self.endpoint)?;
let config: Config = Config{
global: PartialGlobalConfig {
time_zone: self.time_zone.clone()
},
client: ClientConfig {
storage: ClientStorageConfig::Remote(ClientRemoteStorageConfig {
endpoint: self.endpoint,
access_key: token,
}),
}
};
todo!() // Write config
}
}

View file

@ -1,13 +1,15 @@
//mod label; //mod label;
pub mod error; pub mod error;
mod init;
mod record; mod record;
//use label::LabelArgs; //use label::LabelArgs;
use record::{RecordArgs,RecordAddArgs}; use record::{RecordArgs,RecordAddArgs};
use error::Error; use error::Error;
use init::InitArgs;
use clap::{Args, CommandFactory, Parser, Subcommand}; use clap::{Args, CommandFactory, Parser, Subcommand};
use std::ffi::OsString; use std::ffi::OsString;
@ -22,18 +24,18 @@ struct Cli {
#[derive(Clone, Debug, Subcommand)] #[derive(Clone, Debug, Subcommand)]
enum Command { enum Command {
Init(InitArgs),
Record(RecordArgs), Record(RecordArgs),
} }
#[tokio::main]
fn main() -> Result<(), Error> { async fn main() -> Result<(), Error> {
let cli = Cli::parse(); let cli = Cli::parse();
match cli.command { match cli.command {
//Some(Commands::Add(x)) => x.run(), //Some(Commands::Add(x)) => x.run(),
Command::Init(x) => x.run(),
//Some(Commands::Label(x)) => x.run(), //Some(Commands::Label(x)) => x.run(),
Command::Record(x) => x.run(), Command::Record(x) => x.run(),
} }
} }

View file

@ -8,6 +8,7 @@ default = ["clap"]
clap = ["dep:clap"] clap = ["dep:clap"]
[dependencies] [dependencies]
chrono-tz.workspace = true
clap = { workspace = true, optional = true } clap = { workspace = true, optional = true }
dpts-core.workspace = true dpts-core.workspace = true
serde.workspace = true serde.workspace = true

5
dpts-client/src/auth.rs Normal file
View file

@ -0,0 +1,5 @@
use crate::error::Error;
pub fn try_login(user_name: &str, password: &str, endpoint: &str) -> Result<String, Error> {
todo!()
}

View file

@ -1,7 +1,10 @@
mod storage; mod storage;
pub use dpts_core::config::*;
pub use storage::*; pub use storage::*;
use crate::error::Error; use crate::error::Error;
use serde::{ use serde::{
Deserialize, Deserialize,
@ -12,84 +15,45 @@ use tokio::sync::OnceCell;
pub static CLIENT_CONFIG: OnceCell<ClientConfig> = OnceCell::const_new(); pub static CLIENT_CONFIG: OnceCell<ClientConfig> = OnceCell::const_new();
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct Config {
pub struct ClientConfig { pub client: ClientConfig,
pub storage: ClientStorageConfig, pub global: PartialGlobalConfig,
}
impl TryFrom<&PartialClientConfig> for ClientConfig {
type Error = Error;
fn try_from(p: &PartialClientConfig) -> Result<ClientConfig, Self::Error> {
Ok(ClientConfig{
storage: p.clone().storage.ok_or(Error::MissingConfig("storage".to_string()))?,
})
}
} }
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct PartialClientConfig { pub struct ClientConfig {
pub storage: Option<ClientStorageConfig>, pub storage: ClientStorageConfig,
} }
impl PartialClientConfig {
}
impl Default for PartialClientConfig {
fn default() -> Self {
PartialClientConfig {
storage: None
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use chrono_tz::UTC;
use tokio::sync::OnceCell; use tokio::sync::OnceCell;
const EMPTY_CONFIG_TOML: &str = r#""#;
static EMPTY_CONFIG_STRUCT: OnceCell<PartialClientConfig> = OnceCell::const_new();
async fn get_empty_config_struct() -> &'static PartialClientConfig { const LOCAL_STORAGE_CONFIG_TOML: &str = r#"[client]
EMPTY_CONFIG_STRUCT.get_or_init(|| async {
PartialClientConfig{
storage: None,
}
}).await
}
#[tokio::test]
async fn deserialize_empty_client_config() {
let config: PartialClientConfig = toml::from_str(EMPTY_CONFIG_TOML).unwrap();
assert_eq!(&config, get_empty_config_struct().await);
}
#[tokio::test]
async fn serialize_empty_client_config() {
assert_eq!(EMPTY_CONFIG_TOML, toml::to_string(get_empty_config_struct().await).unwrap());
}
const LOCAL_STORAGE_CONFIG_TOML: &str = r#"time_zone = "UTC"
storage = "local" storage = "local"
"#;
static LOCAL_STORAGE_CONFIG_STRUCT: OnceCell<PartialClientConfig> = OnceCell::const_new();
async fn get_local_storage_client_config_struct() -> &'static PartialClientConfig { [global]
"#;
static LOCAL_STORAGE_CONFIG_STRUCT: OnceCell<Config> = OnceCell::const_new();
async fn get_local_storage_client_config_struct() -> &'static Config {
LOCAL_STORAGE_CONFIG_STRUCT.get_or_init(|| async { LOCAL_STORAGE_CONFIG_STRUCT.get_or_init(|| async {
PartialClientConfig{ Config{
storage: Some(ClientStorageConfig::Local), client: ClientConfig{
storage: ClientStorageConfig::Local,
},
global: PartialGlobalConfig { time_zone: None },
} }
}).await }).await
} }
#[tokio::test] #[tokio::test]
async fn deserialize_local_storage_client_config() { async fn deserialize_local_storage_client_config() {
let config: PartialClientConfig = toml::from_str(LOCAL_STORAGE_CONFIG_TOML).unwrap(); let config: Config = toml::from_str(LOCAL_STORAGE_CONFIG_TOML).unwrap();
assert_eq!(&config, get_local_storage_client_config_struct().await); assert_eq!(&config, get_local_storage_client_config_struct().await);
} }
@ -98,27 +62,31 @@ storage = "local"
assert_eq!(LOCAL_STORAGE_CONFIG_TOML, toml::to_string(get_local_storage_client_config_struct().await).unwrap()); assert_eq!(LOCAL_STORAGE_CONFIG_TOML, toml::to_string(get_local_storage_client_config_struct().await).unwrap());
} }
const REMOTE_STORAGE_CONFIG_TOML: &str = r#"time_zone = "UTC" const REMOTE_STORAGE_CONFIG_TOML: &str = r#"[client.storage.remote]
[storage.remote]
endpoint = "https://example.com" endpoint = "https://example.com"
access_key = "test" access_key = "test"
"#;
static REMOTE_STORAGE_CONFIG_STRUCT: OnceCell<PartialClientConfig> = OnceCell::const_new();
async fn get_remote_storage_client_config_struct() -> &'static PartialClientConfig { [global]
time_zone = "UTC"
"#;
static REMOTE_STORAGE_CONFIG_STRUCT: OnceCell<Config> = OnceCell::const_new();
async fn get_remote_storage_client_config_struct() -> &'static Config {
REMOTE_STORAGE_CONFIG_STRUCT.get_or_init(|| async { REMOTE_STORAGE_CONFIG_STRUCT.get_or_init(|| async {
PartialClientConfig{ Config{
storage: Some(ClientStorageConfig::Remote(ClientRemoteStorageConfig { client: ClientConfig {
endpoint: "https://example.com".to_string(), storage: ClientStorageConfig::Remote(ClientRemoteStorageConfig {
access_key: "test".to_string(), endpoint: "https://example.com".to_string(),
})), access_key: "test".to_string(),
})
},
global: PartialGlobalConfig { time_zone: Some(UTC) }
} }
}).await }).await
} }
#[tokio::test] #[tokio::test]
async fn deserialize_remote_storage_client_config() { async fn deserialize_remote_storage_client_config() {
let config: PartialClientConfig = toml::from_str(REMOTE_STORAGE_CONFIG_TOML).unwrap(); let config: Config = toml::from_str(REMOTE_STORAGE_CONFIG_TOML).unwrap();
assert_eq!(&config, get_remote_storage_client_config_struct().await); assert_eq!(&config, get_remote_storage_client_config_struct().await);
} }

View file

@ -1,17 +1,8 @@
pub mod auth;
pub mod config; pub mod config;
pub mod error; pub mod error;
pub fn add(left: u64, right: u64) -> u64 { use error::Error;
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View file

@ -1,13 +1,13 @@
use chrono_tz::Tz; use chrono_tz::Tz;
#[cfg(feature="clap")] #[cfg(feature="clap")]
use clap::Args; use clap::Args;
use serde::Deserialize; use serde::{Deserialize, Serialize};
use tokio::sync::OnceCell; use tokio::sync::OnceCell;
use crate::Error; use crate::Error;
#[derive(Clone, Debug, Deserialize, PartialEq)] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct GlobalConfig { pub struct GlobalConfig {
pub time_zone: Tz, pub time_zone: Tz,
} }
@ -21,7 +21,7 @@ impl TryFrom<PartialGlobalConfig> for GlobalConfig{
} }
} }
#[derive(Clone, Debug, Default, Deserialize, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature="clap", derive(Args))] #[cfg_attr(feature="clap", derive(Args))]
pub struct PartialGlobalConfig { pub struct PartialGlobalConfig {
#[cfg_attr(feature="clap", arg(short, long))] #[cfg_attr(feature="clap", arg(short, long))]