Config stuff working

This commit is contained in:
Daniel Flanagan 2024-04-19 15:27:16 -05:00
parent 75816058cf
commit 9d61b04731
3 changed files with 93 additions and 58 deletions

View file

@ -47,6 +47,7 @@
# debugger # debugger
lldb lldb
gdbgui
# libs # libs
libopus libopus

View file

@ -1,12 +1,16 @@
use std::{fs::File, io::Write}; use cliclack::{input, password};
use futures::StreamExt;
use cliclack::input; use signal_hook::consts::*;
use signal_hook_tokio::Signals;
use crate::cli::prelude::*; use crate::cli::prelude::*;
#[derive(Subcommand)] #[derive(Subcommand)]
pub enum Command { pub enum Command {
/// Write the current config (without secrets) in TOML format
Show, Show,
/// Interactively setup a Taskr configuration
Setup, Setup,
} }
@ -14,66 +18,78 @@ impl Command {
pub async fn exec(&self, tasks: SharedTasks) -> Result<()> { pub async fn exec(&self, tasks: SharedTasks) -> Result<()> {
match self { match self {
Command::Show => self.show(tasks).await, 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<()> { pub async fn show(&self, tasks: SharedTasks) -> Result<()> {
println!("{:#?}", tasks.config()); println!("{}", toml::to_string_pretty(tasks.config()?)?);
Ok(()) Ok(())
} }
pub async fn setup(&self, tasks: SharedTasks) -> Result<()> { pub async fn setup(stasks: SharedTasks) -> Result<()> {
use cliclack::{intro, outro}; use cliclack::{intro, outro};
let tasks = stasks.clone();
let cliclack_handle: tokio::task::JoinHandle<Result<()>> = tokio::spawn(async move {
// TODO: maybe use the normal builder // 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?") let gitlab_url: String = input("What is your GitLab URL?")
// .placeholder("https://gitlab.com") .placeholder("https://gitlab.com")
// .validate_interactively(|input: &String| match url::Url::parse(input) { .validate_interactively(|input: &String| match url::Url::parse(input) {
// Ok(_) => Ok(()), Ok(_) => Ok(()),
// Err(_) => Err("Must be a URL"), Err(_) => Err("Must be a URL"),
// }) })
// .interact()?; .interact()?;
// let jira_url: String = input("What is your Jira URL?") let jira_url: String = input("What is your Jira URL?")
// .placeholder("https://company.atlassian.net") .placeholder("https://company.atlassian.net")
// .validate_interactively(|input: &String| match url::Url::parse(input) { .validate_interactively(|input: &String| match url::Url::parse(input) {
// Ok(_) => Ok(()), Ok(_) => Ok(()),
// Err(_) => Err("Must be a URL"), Err(_) => Err("Must be a URL"),
// }) })
// .interact()?; .interact()?;
let gitlab_url = "https://gitlab.com"; let mut config = Config::build(
let jira_url = "https://jira.com"; 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)? // TODO: link user to page to setup tokens
.set_override("gitlab.url", gitlab_url)? config.secrets.gitlab_token =
.set_override("jira.url", jira_url)? password("What is your GitLab Personal Access Token? (scopes: read_api)")
.set_override("secrets.gitlab_token", gitlab_url)? // .placeholder("glpat-XXXX...")
.set_override("secrets.jira_token", jira_url)?; .interact()?;
dbg!(&config_builder); config.secrets.jira_token = password("What is your Jira API Token?").interact()?;
let built = config_builder.build()?;
dbg!(&built);
let config_result = built.try_deserialize(); config.save(&tasks.config_file_path)?;
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())?;
// TODO: initialize known keyring values/entries // TODO: initialize known keyring values/entries
outro("All set! Try `taskr list --sync` now!")?; 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?
Ok(()) Ok(())
} }
} }

View file

@ -1,6 +1,6 @@
use crate::prelude::*; 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 color_eyre::{eyre::Context, Section};
use config::{builder::DefaultState, Config as CConfig, ConfigBuilder, Value}; use config::{builder::DefaultState, Config as CConfig, ConfigBuilder, Value};
@ -20,8 +20,8 @@ pub struct Jira {
#[derive(Deserialize, Debug, Clone, Default)] #[derive(Deserialize, Debug, Clone, Default)]
pub struct Secrets { pub struct Secrets {
jira_token: String, pub jira_token: String,
gitlab_token: String, pub gitlab_token: String,
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
@ -36,15 +36,10 @@ pub struct Config {
impl Config { impl Config {
pub fn load(config_file_path: &Path) -> Result<Self> { pub fn load(config_file_path: &Path) -> Result<Self> {
let builder = Self::builder(config_file_path)?.build()?; let mut conf = Self::build(Self::builder(config_file_path)?)?;
let mut conf: Self = builder
.try_deserialize()
.suggestion("run `taskr config setup` to ensure valid configuration")
.with_context(|| "failed to deserialize configuration")?;
if conf.secrets.jira_token == "" { if conf.secrets.jira_token == "" {
conf.secrets.jira_token = conf.secrets.jira_token = keyring::Entry::new("taskr", "jira_token")?.get_password()?;
keyring::Entry::new("taskr", "gitlab_token")?.get_password()?;
} }
if conf.secrets.gitlab_token == "" { if conf.secrets.gitlab_token == "" {
@ -55,6 +50,30 @@ impl Config {
Ok(conf) 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<DefaultState>) -> Result<Self> {
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<ConfigBuilder<DefaultState>> pub fn builder(config_file_path: &Path) -> Result<ConfigBuilder<DefaultState>>
where { where {
Ok(Self::default_builder()? Ok(Self::default_builder()?
@ -76,8 +95,7 @@ where {
#[allow(dead_code)] #[allow(dead_code)]
pub fn defaults() -> Result<Self> { pub fn defaults() -> Result<Self> {
let c = Self::default_builder()?.build()?; Self::build(Self::default_builder()?)
Ok(c.try_deserialize()?)
} }
} }