From 9d61b04731bb836e44e9e1307f63528434d98e3c Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Fri, 19 Apr 2024 15:27:16 -0500 Subject: [PATCH] Config stuff working --- flake.nix | 1 + src/cli/config.rs | 108 ++++++++++++++++++++++++++-------------------- src/config.rs | 42 ++++++++++++------ 3 files changed, 93 insertions(+), 58 deletions(-) diff --git a/flake.nix b/flake.nix index 7f46cc4..8781ffc 100644 --- a/flake.nix +++ b/flake.nix @@ -47,6 +47,7 @@ # debugger lldb + gdbgui # libs libopus diff --git a/src/cli/config.rs b/src/cli/config.rs index 8e7151a..76789e6 100644 --- a/src/cli/config.rs +++ b/src/cli/config.rs @@ -1,12 +1,16 @@ -use std::{fs::File, io::Write}; - -use cliclack::input; +use cliclack::{input, password}; +use futures::StreamExt; +use signal_hook::consts::*; +use signal_hook_tokio::Signals; use crate::cli::prelude::*; #[derive(Subcommand)] pub enum Command { + /// Write the current config (without secrets) in TOML format Show, + + /// Interactively setup a Taskr configuration Setup, } @@ -14,66 +18,78 @@ impl Command { pub async fn exec(&self, tasks: SharedTasks) -> Result<()> { match self { Command::Show => self.show(tasks).await, - Command::Setup => self.setup(tasks).await, + Command::Setup => Self::setup(tasks).await, } } pub async fn show(&self, tasks: SharedTasks) -> Result<()> { - println!("{:#?}", tasks.config()); + println!("{}", toml::to_string_pretty(tasks.config()?)?); Ok(()) } - pub async fn setup(&self, tasks: SharedTasks) -> Result<()> { + pub async fn setup(stasks: SharedTasks) -> Result<()> { use cliclack::{intro, outro}; - // TODO: maybe use the normal builder + let tasks = stasks.clone(); + let cliclack_handle: tokio::task::JoinHandle> = tokio::spawn(async move { + // TODO: maybe use the normal builder + // TODO: handle sigint and reset the terminal gracefully - intro("Configure taskr")?; + intro("Configure taskr")?; - // let gitlab_url: String = input("What is your GitLab URL?") - // .placeholder("https://gitlab.com") - // .validate_interactively(|input: &String| match url::Url::parse(input) { - // Ok(_) => Ok(()), - // Err(_) => Err("Must be a URL"), - // }) - // .interact()?; + let gitlab_url: String = input("What is your GitLab URL?") + .placeholder("https://gitlab.com") + .validate_interactively(|input: &String| match url::Url::parse(input) { + Ok(_) => Ok(()), + Err(_) => Err("Must be a URL"), + }) + .interact()?; - // let jira_url: String = input("What is your Jira URL?") - // .placeholder("https://company.atlassian.net") - // .validate_interactively(|input: &String| match url::Url::parse(input) { - // Ok(_) => Ok(()), - // Err(_) => Err("Must be a URL"), - // }) - // .interact()?; + let jira_url: String = input("What is your Jira URL?") + .placeholder("https://company.atlassian.net") + .validate_interactively(|input: &String| match url::Url::parse(input) { + Ok(_) => Ok(()), + Err(_) => Err("Must be a URL"), + }) + .interact()?; - let gitlab_url = "https://gitlab.com"; - let jira_url = "https://jira.com"; + let mut config = Config::build( + Config::builder(&tasks.config_file_path)? + .set_override("gitlab.url", gitlab_url.as_str())? + .set_override("jira.url", jira_url.as_str())?, + )?; - let config_builder = Config::builder(&tasks.config_file_path)? - .set_override("gitlab.url", gitlab_url)? - .set_override("jira.url", jira_url)? - .set_override("secrets.gitlab_token", gitlab_url)? - .set_override("secrets.jira_token", jira_url)?; + // TODO: link user to page to setup tokens + config.secrets.gitlab_token = + password("What is your GitLab Personal Access Token? (scopes: read_api)") + // .placeholder("glpat-XXXX...") + .interact()?; - dbg!(&config_builder); - let built = config_builder.build()?; - dbg!(&built); + config.secrets.jira_token = password("What is your Jira API Token?").interact()?; - let config_result = built.try_deserialize(); - dbg!(&config_result); - if let Err(err) = &config_result { - match err { - config::ConfigError::Type { origin, unexpected, expected, key } => println!("Type Error at key {key:#?} expected {expected:#?} from origin {origin:#?} (unexpected: {unexpected:#?})"), -config::ConfigError::Message(str) => println!("Message {err:#?} {str}"), - _ => println!("I dunno. {err:#?}"), - } - } - let config = config_result?; - let mut file = File::create(&tasks.config_file_path)?; - file.write_all(toml::to_string(&config)?.as_bytes())?; + config.save(&tasks.config_file_path)?; + + // TODO: initialize known keyring values/entries + outro("All set! Try `taskr list --sync` now!")?; + Ok(()) + }); + + let mut signals = Signals::new(&[SIGHUP, SIGTERM, SIGINT, SIGQUIT])?; + let signal_handle = signals.handle(); + + // TODO: either we will die, get a signal, or we setup + + tokio::select! { + _ = signals.next() => { + // outro_cancel("Canceled taskr setup")?; + } + _ = cliclack_handle => { + signal_handle.close(); + } + }; + + // reset our terminal? - // TODO: initialize known keyring values/entries - outro("All set! Try `taskr list --sync` now!")?; Ok(()) } } diff --git a/src/config.rs b/src/config.rs index 48eb9d6..2d9a761 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,6 @@ use crate::prelude::*; -use std::{collections::HashMap, fmt::Debug, path::Path}; +use std::{collections::HashMap, fmt::Debug, fs::File, io::Write, path::Path}; use color_eyre::{eyre::Context, Section}; use config::{builder::DefaultState, Config as CConfig, ConfigBuilder, Value}; @@ -20,8 +20,8 @@ pub struct Jira { #[derive(Deserialize, Debug, Clone, Default)] pub struct Secrets { - jira_token: String, - gitlab_token: String, + pub jira_token: String, + pub gitlab_token: String, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -36,15 +36,10 @@ pub struct Config { impl Config { pub fn load(config_file_path: &Path) -> Result { - let builder = Self::builder(config_file_path)?.build()?; - let mut conf: Self = builder - .try_deserialize() - .suggestion("run `taskr config setup` to ensure valid configuration") - .with_context(|| "failed to deserialize configuration")?; + let mut conf = Self::build(Self::builder(config_file_path)?)?; if conf.secrets.jira_token == "" { - conf.secrets.jira_token = - keyring::Entry::new("taskr", "gitlab_token")?.get_password()?; + conf.secrets.jira_token = keyring::Entry::new("taskr", "jira_token")?.get_password()?; } if conf.secrets.gitlab_token == "" { @@ -55,6 +50,30 @@ impl Config { Ok(conf) } + /// This should inject the files/values/whatever into their appropriate + /// places such that the next time Self::load() is called (with the same + /// `config_file_path`) you end up with the same Self. + pub fn save(&self, config_file_path: &Path) -> Result<()> { + if let Some(p) = config_file_path.parent() { + std::fs::create_dir_all(p)?; + } + let mut file = File::create(config_file_path)?; + file.write_all(toml::to_string(self)?.as_bytes())?; + + keyring::Entry::new("taskr", "gitlab_token")?.set_password(&self.secrets.gitlab_token)?; + keyring::Entry::new("taskr", "jira_token")?.set_password(&self.secrets.jira_token)?; + + Ok(()) + } + + pub fn build(builder: ConfigBuilder) -> Result { + builder + .build()? + .try_deserialize() + .suggestion("run `taskr config setup` to ensure valid configuration") + .with_context(|| "failed to deserialize configuration") + } + pub fn builder(config_file_path: &Path) -> Result> where { Ok(Self::default_builder()? @@ -76,8 +95,7 @@ where { #[allow(dead_code)] pub fn defaults() -> Result { - let c = Self::default_builder()?.build()?; - Ok(c.try_deserialize()?) + Self::build(Self::default_builder()?) } }