Config stuff working
This commit is contained in:
parent
75816058cf
commit
9d61b04731
3 changed files with 93 additions and 58 deletions
|
@ -47,6 +47,7 @@
|
||||||
|
|
||||||
# debugger
|
# debugger
|
||||||
lldb
|
lldb
|
||||||
|
gdbgui
|
||||||
|
|
||||||
# libs
|
# libs
|
||||||
libopus
|
libopus
|
||||||
|
|
|
@ -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};
|
||||||
|
|
||||||
// TODO: maybe use the normal builder
|
let tasks = stasks.clone();
|
||||||
|
let cliclack_handle: tokio::task::JoinHandle<Result<()>> = 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?")
|
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 {
|
// TODO: initialize known keyring values/entries
|
||||||
match err {
|
outro("All set! Try `taskr list --sync` now!")?;
|
||||||
config::ConfigError::Type { origin, unexpected, expected, key } => println!("Type Error at key {key:#?} expected {expected:#?} from origin {origin:#?} (unexpected: {unexpected:#?})"),
|
Ok(())
|
||||||
config::ConfigError::Message(str) => println!("Message {err:#?} {str}"),
|
});
|
||||||
_ => println!("I dunno. {err:#?}"),
|
|
||||||
}
|
let mut signals = Signals::new(&[SIGHUP, SIGTERM, SIGINT, SIGQUIT])?;
|
||||||
}
|
let signal_handle = signals.handle();
|
||||||
let config = config_result?;
|
|
||||||
let mut file = File::create(&tasks.config_file_path)?;
|
// TODO: either we will die, get a signal, or we setup
|
||||||
file.write_all(toml::to_string(&config)?.as_bytes())?;
|
|
||||||
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()?)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue