Add configuration-file support to the 'task' command

This commit is contained in:
Dustin J. Mitchell 2020-11-28 18:18:43 -05:00
parent 8af7ba286d
commit 0e926df578
6 changed files with 236 additions and 45 deletions

View file

@ -9,6 +9,8 @@ clap = "^2.33.0"
taskchampion = { path = "../taskchampion" }
failure = "^0.1.8"
prettytable-rs = "^0.8.0"
config = "^0.10.1"
dirs = "^3.0.1"
[dev-dependencies]
assert_cmd = "^1.0.1"

View file

@ -1,7 +1,8 @@
use crate::settings;
use clap::Arg;
use config::{Config, ConfigError};
use failure::{format_err, Fallible};
use std::env;
use std::ffi::OsString;
use std::cell::{Ref, RefCell};
use taskchampion::{server, Replica, ReplicaConfig, ServerConfig, Task, Uuid};
pub(super) fn task_arg<'a>() -> Arg<'a, 'a> {
@ -33,11 +34,15 @@ pub(super) fn get_task<S: AsRef<str>>(replica: &mut Replica, task_arg: S) -> Fal
#[derive(Debug)]
pub struct CommandInvocation {
pub(crate) subcommand: Box<dyn super::SubCommandInvocation>,
settings: RefCell<Config>,
}
impl CommandInvocation {
pub(crate) fn new(subcommand: Box<dyn super::SubCommandInvocation>) -> Self {
Self { subcommand }
Self {
subcommand,
settings: RefCell::new(Config::default()),
}
}
pub fn run(self) -> Fallible<()> {
@ -46,28 +51,33 @@ impl CommandInvocation {
// -- utilities for command invocations
pub(super) fn get_settings(&self) -> Fallible<Ref<Config>> {
{
// use the special `_loaded" config value to detect whether we have
// loaded the configuration yet
let mut settings = self.settings.borrow_mut();
if let Err(ConfigError::NotFound(_)) = settings.get_bool("_loaded") {
settings.merge(settings::read_settings()?)?;
settings.set("_loaded", true)?;
}
}
Ok(self.settings.borrow())
}
pub(super) fn get_replica(&self) -> Fallible<Replica> {
// temporarily use $TASK_DB to locate the taskdb
let taskdb_dir = env::var_os("TASK_DB").unwrap_or_else(|| OsString::from("/tmp/tasks"));
let settings = self.get_settings()?;
let replica_config = ReplicaConfig {
taskdb_dir: taskdb_dir.into(),
taskdb_dir: settings.get_str("data_dir")?.into(),
};
Ok(Replica::from_config(replica_config)?)
}
pub(super) fn get_server(&self) -> Fallible<Box<dyn server::Server>> {
// temporarily use $SYNC_SERVER_ORIGIN for the sync server
let origin = env::var_os("SYNC_SERVER_ORIGIN")
.map(|osstr| osstr.into_string().unwrap())
.unwrap_or_else(|| String::from("http://localhost:8080"));
let client_id = env::var_os("SYNC_SERVER_CLIENT_ID")
.ok_or_else(|| format_err!("SYNC_SERVER_CLIENT_ID not set"))?;
let client_id = client_id
.into_string()
.map_err(|_| format_err!("SYNC_SERVER_CLIENT_ID is not a valid UUID"))?;
let settings = self.get_settings()?;
let client_id = settings.get_str("server_client_id")?;
let client_id = Uuid::parse_str(&client_id)?;
Ok(server::from_config(ServerConfig::Remote {
origin,
origin: settings.get_str("server_origin")?,
client_id,
})?)
}

View file

@ -3,6 +3,7 @@ use failure::Fallible;
use std::ffi::OsString;
mod cmd;
pub(crate) mod settings;
mod table;
use cmd::ArgMatchResult;

35
cli/src/settings.rs Normal file
View file

@ -0,0 +1,35 @@
use config::{Config, Environment, File, FileSourceFile};
use failure::Fallible;
use std::env;
use std::path::PathBuf;
pub(crate) fn read_settings() -> Fallible<Config> {
let mut settings = Config::default();
// set up defaults
if let Some(mut dir) = dirs::data_local_dir() {
dir.push("taskchampion");
settings.set_default(
"data_dir",
// the config crate does not support non-string paths
dir.to_str().expect("data_local_dir is not utf-8"),
)?;
}
// load either from the path in TASKCHAMPION_CONFIG, or from CONFIG_DIR/taskchampion
if let Some(config_file) = env::var_os("TASKCHAMPION_CONFIG") {
let config_file: PathBuf = config_file.into();
let config_file: File<FileSourceFile> = config_file.into();
settings.merge(config_file.required(true))?;
env::remove_var("TASKCHAMPION_CONFIG");
} else if let Some(mut dir) = dirs::config_dir() {
dir.push("taskchampion");
let config_file: File<FileSourceFile> = dir.into();
settings.merge(config_file.required(false))?;
}
// merge environment variables
settings.merge(Environment::with_prefix("TASKCHAMPION"))?;
Ok(settings)
}