From 8af7ba286d67c838101e6df603398399df119da2 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Nov 2020 16:57:32 -0500 Subject: [PATCH 1/3] Factor replica and sync configuration into simple owned structs --- cli/src/cmd/add.rs | 2 +- cli/src/cmd/gc.rs | 2 +- cli/src/cmd/info.rs | 2 +- cli/src/cmd/list.rs | 2 +- cli/src/cmd/modify.rs | 2 +- cli/src/cmd/pending.rs | 2 +- cli/src/cmd/shared.rs | 27 ++++++++++++++++++--------- cli/src/cmd/sync.rs | 2 +- docs/src/usage.md | 1 + taskchampion/src/config.rs | 26 ++++++++++++++++++++++++++ taskchampion/src/lib.rs | 2 ++ taskchampion/src/replica.rs | 10 ++++++++-- taskchampion/src/server/local.rs | 2 +- taskchampion/src/server/mod.rs | 12 ++++++++++++ taskchampion/src/taskdb.rs | 8 ++++---- taskchampion/src/taskstorage/mod.rs | 2 ++ 16 files changed, 81 insertions(+), 23 deletions(-) create mode 100644 taskchampion/src/config.rs diff --git a/cli/src/cmd/add.rs b/cli/src/cmd/add.rs index cfc26b5a8..88e7a76f5 100644 --- a/cli/src/cmd/add.rs +++ b/cli/src/cmd/add.rs @@ -38,7 +38,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { let t = command - .get_replica() + .get_replica()? .new_task(Status::Pending, self.description.clone()) .unwrap(); println!("added task {}", t.get_uuid()); diff --git a/cli/src/cmd/gc.rs b/cli/src/cmd/gc.rs index d8948a715..15bdd288b 100644 --- a/cli/src/cmd/gc.rs +++ b/cli/src/cmd/gc.rs @@ -20,7 +20,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - command.get_replica().gc()?; + command.get_replica()?.gc()?; println!("garbage collected."); Ok(()) } diff --git a/cli/src/cmd/info.rs b/cli/src/cmd/info.rs index cdc39cc11..f55667dfb 100644 --- a/cli/src/cmd/info.rs +++ b/cli/src/cmd/info.rs @@ -30,7 +30,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica(); + let mut replica = command.get_replica()?; let task = shared::get_task(&mut replica, &self.task)?; let uuid = task.get_uuid(); diff --git a/cli/src/cmd/list.rs b/cli/src/cmd/list.rs index d29227c92..72ee206e0 100644 --- a/cli/src/cmd/list.rs +++ b/cli/src/cmd/list.rs @@ -23,7 +23,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica(); + let mut replica = command.get_replica()?; let mut t = Table::new(); t.set_format(table::format()); t.set_titles(row![b->"id", b->"description"]); diff --git a/cli/src/cmd/modify.rs b/cli/src/cmd/modify.rs index 487084a53..662941e34 100644 --- a/cli/src/cmd/modify.rs +++ b/cli/src/cmd/modify.rs @@ -36,7 +36,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica(); + let mut replica = command.get_replica()?; let task = shared::get_task(&mut replica, &self.task)?; let mut task = task.into_mut(&mut replica); diff --git a/cli/src/cmd/pending.rs b/cli/src/cmd/pending.rs index b55c044d0..f35756b8b 100644 --- a/cli/src/cmd/pending.rs +++ b/cli/src/cmd/pending.rs @@ -25,7 +25,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let working_set = command.get_replica().working_set().unwrap(); + let working_set = command.get_replica()?.working_set().unwrap(); let mut t = Table::new(); t.set_format(table::format()); t.set_titles(row![b->"id", b->"description"]); diff --git a/cli/src/cmd/shared.rs b/cli/src/cmd/shared.rs index 474bca280..bcd6fbf0b 100644 --- a/cli/src/cmd/shared.rs +++ b/cli/src/cmd/shared.rs @@ -2,7 +2,7 @@ use clap::Arg; use failure::{format_err, Fallible}; use std::env; use std::ffi::OsString; -use taskchampion::{server, taskstorage, Replica, Task, Uuid}; +use taskchampion::{server, Replica, ReplicaConfig, ServerConfig, Task, Uuid}; pub(super) fn task_arg<'a>() -> Arg<'a, 'a> { Arg::with_name("task") @@ -46,20 +46,29 @@ impl CommandInvocation { // -- utilities for command invocations - pub(super) fn get_replica(&self) -> Replica { + pub(super) fn get_replica(&self) -> Fallible { // temporarily use $TASK_DB to locate the taskdb let taskdb_dir = env::var_os("TASK_DB").unwrap_or_else(|| OsString::from("/tmp/tasks")); - Replica::new(Box::new(taskstorage::KVStorage::new(taskdb_dir).unwrap())) + let replica_config = ReplicaConfig { + taskdb_dir: taskdb_dir.into(), + }; + Ok(Replica::from_config(replica_config)?) } - pub(super) fn get_server(&self) -> Fallible { + pub(super) fn get_server(&self) -> Fallible> { // temporarily use $SYNC_SERVER_ORIGIN for the sync server - let sync_server_origin = env::var_os("SYNC_SERVER_ORIGIN") + let origin = env::var_os("SYNC_SERVER_ORIGIN") .map(|osstr| osstr.into_string().unwrap()) .unwrap_or_else(|| String::from("http://localhost:8080")); - Ok(server::RemoteServer::new( - sync_server_origin, - Uuid::parse_str("d5b55cbd-9a82-4860-9a39-41b67893b22f").unwrap(), - )) + 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 client_id = Uuid::parse_str(&client_id)?; + Ok(server::from_config(ServerConfig::Remote { + origin, + client_id, + })?) } } diff --git a/cli/src/cmd/sync.rs b/cli/src/cmd/sync.rs index a17920436..37c3a76d6 100644 --- a/cli/src/cmd/sync.rs +++ b/cli/src/cmd/sync.rs @@ -21,7 +21,7 @@ define_subcommand! { subcommand_invocation! { fn run(&self, command: &CommandInvocation) -> Fallible<()> { - let mut replica = command.get_replica(); + let mut replica = command.get_replica()?; let mut server = command.get_server()?; replica.sync(&mut server)?; Ok(()) diff --git a/docs/src/usage.md b/docs/src/usage.md index b16c724e1..47ab9388b 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -12,6 +12,7 @@ Note that the `task` interface does not match that of TaskWarrior. Temporarily, configuration is by environment variables. The directory containing the replica's task data is given by `TASK_DB`, defaulting to `/tmp/tasks`. the origin of the sync server is given by `SYNC_SERVER_ORIGIN`, defaulting to `http://localhost:8080`. +The client ID to use with the sync server is givne by `SYNC_SERVER_CLIENT_ID` (with no default). ## `taskchampion-sync-server` diff --git a/taskchampion/src/config.rs b/taskchampion/src/config.rs new file mode 100644 index 000000000..8a8ee5bcd --- /dev/null +++ b/taskchampion/src/config.rs @@ -0,0 +1,26 @@ +use std::path::PathBuf; +use uuid::Uuid; + +/// The configuration required for a replica. Use with [`crate::Replica::from_config`]. +pub struct ReplicaConfig { + /// Path containing the task DB. + pub taskdb_dir: PathBuf, +} + +/// The configuration for a replica's access to a sync server. Use with +/// [`crate::server::from_config`]. +pub enum ServerConfig { + /// A local task database, for situations with a single replica. + Local { + /// Path containing the server's DB + server_dir: PathBuf, + }, + /// A remote taskchampion-sync-server instance + Remote { + /// Sync server "origin"; a URL with schema and hostname but no path or trailing `/` + origin: String, + + /// Client ID to identify this replica to the server + client_id: Uuid, + }, +} diff --git a/taskchampion/src/lib.rs b/taskchampion/src/lib.rs index 932dbdeef..aa2c8bf99 100644 --- a/taskchampion/src/lib.rs +++ b/taskchampion/src/lib.rs @@ -23,6 +23,7 @@ for more information about the design and usage of the tool. */ +mod config; mod errors; mod replica; pub mod server; @@ -31,6 +32,7 @@ mod taskdb; pub mod taskstorage; mod utils; +pub use config::{ReplicaConfig, ServerConfig}; pub use replica::Replica; pub use task::Priority; pub use task::Status; diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index c33d31028..525be9888 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -1,8 +1,9 @@ +use crate::config::ReplicaConfig; use crate::errors::Error; use crate::server::Server; use crate::task::{Status, Task}; use crate::taskdb::TaskDB; -use crate::taskstorage::{Operation, TaskMap, TaskStorage}; +use crate::taskstorage::{KVStorage, Operation, TaskMap, TaskStorage}; use chrono::Utc; use failure::Fallible; use std::collections::HashMap; @@ -21,6 +22,11 @@ impl Replica { } } + pub fn from_config(config: ReplicaConfig) -> Fallible { + let storage = Box::new(KVStorage::new(config.taskdb_dir)?); + Ok(Replica::new(storage)) + } + #[cfg(test)] pub fn new_inmemory() -> Replica { Replica::new(Box::new(crate::taskstorage::InMemoryStorage::new())) @@ -140,7 +146,7 @@ impl Replica { } /// Synchronize this replica against the given server. - pub fn sync(&mut self, server: &mut dyn Server) -> Fallible<()> { + pub fn sync(&mut self, server: &mut Box) -> Fallible<()> { self.taskdb.sync(server) } diff --git a/taskchampion/src/server/local.rs b/taskchampion/src/server/local.rs index 89775acf1..9cc4b2b28 100644 --- a/taskchampion/src/server/local.rs +++ b/taskchampion/src/server/local.rs @@ -25,7 +25,7 @@ pub struct LocalServer<'t> { impl<'t> LocalServer<'t> { /// A test server has no notion of clients, signatures, encryption, etc. - pub fn new(directory: &Path) -> Fallible { + pub fn new>(directory: P) -> Fallible> { let mut config = Config::default(directory); config.bucket("versions", None); config.bucket("numbers", None); diff --git a/taskchampion/src/server/mod.rs b/taskchampion/src/server/mod.rs index 78c49c6f6..341428130 100644 --- a/taskchampion/src/server/mod.rs +++ b/taskchampion/src/server/mod.rs @@ -1,3 +1,6 @@ +use crate::ServerConfig; +use failure::Fallible; + #[cfg(test)] pub(crate) mod test; @@ -8,3 +11,12 @@ mod types; pub use local::LocalServer; pub use remote::RemoteServer; pub use types::*; + +pub fn from_config(config: ServerConfig) -> Fallible> { + Ok(match config { + ServerConfig::Local { server_dir } => Box::new(LocalServer::new(server_dir)?), + ServerConfig::Remote { origin, client_id } => { + Box::new(RemoteServer::new(origin, client_id)) + } + }) +} diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index eb69e35ae..4cdb927ca 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -164,7 +164,7 @@ impl TaskDB { } /// Sync to the given server, pulling remote changes and pushing local changes. - pub fn sync(&mut self, server: &mut dyn Server) -> Fallible<()> { + pub fn sync(&mut self, server: &mut Box) -> Fallible<()> { let mut txn = self.storage.txn()?; // retry synchronizing until the server accepts our version (this allows for races between @@ -542,7 +542,7 @@ mod tests { #[test] fn test_sync() { - let mut server = TestServer::new(); + let mut server: Box = Box::new(TestServer::new()); let mut db1 = newdb(); db1.sync(&mut server).unwrap(); @@ -602,7 +602,7 @@ mod tests { #[test] fn test_sync_create_delete() { - let mut server = TestServer::new(); + let mut server: Box = Box::new(TestServer::new()); let mut db1 = newdb(); db1.sync(&mut server).unwrap(); @@ -692,7 +692,7 @@ mod tests { // and delete operations that results in a task existing in one TaskDB but not existing in // another. So, the generated sequences focus on a single task UUID. fn transform_sequences_of_operations(action_sequence in action_sequence_strategy()) { - let mut server = TestServer::new(); + let mut server: Box = Box::new(TestServer::new()); let mut dbs = [newdb(), newdb(), newdb()]; for (action, db) in action_sequence { diff --git a/taskchampion/src/taskstorage/mod.rs b/taskchampion/src/taskstorage/mod.rs index 4ddd7df75..b5f94c8ff 100644 --- a/taskchampion/src/taskstorage/mod.rs +++ b/taskchampion/src/taskstorage/mod.rs @@ -2,11 +2,13 @@ use failure::Fallible; use std::collections::HashMap; use uuid::Uuid; +#[cfg(test)] mod inmemory; mod kv; mod operation; pub use self::kv::KVStorage; +#[cfg(test)] pub use inmemory::InMemoryStorage; pub use operation::Operation; From 0e926df578812e57049daa5850b75a30ec942960 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Nov 2020 18:18:43 -0500 Subject: [PATCH 2/3] Add configuration-file support to the 'task' command --- Cargo.lock | 178 ++++++++++++++++++++++++++++++++++++------ cli/Cargo.toml | 2 + cli/src/cmd/shared.rs | 42 ++++++---- cli/src/lib.rs | 1 + cli/src/settings.rs | 35 +++++++++ docs/src/usage.md | 23 ++++-- 6 files changed, 236 insertions(+), 45 deletions(-) create mode 100644 cli/src/settings.rs diff --git a/Cargo.lock b/Cargo.lock index 4147eb7c9..9a683e918 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,7 +74,7 @@ dependencies = [ "pin-project 1.0.2", "rand 0.7.3", "regex", - "serde", + "serde 1.0.117", "serde_json", "serde_urlencoded", "sha-1", @@ -102,7 +102,7 @@ dependencies = [ "http", "log", "regex", - "serde", + "serde 1.0.117", ] [[package]] @@ -241,7 +241,7 @@ dependencies = [ "mime", "pin-project 1.0.2", "regex", - "serde", + "serde 1.0.117", "serde_json", "serde_urlencoded", "socket2", @@ -372,7 +372,7 @@ dependencies = [ "mime", "percent-encoding", "rand 0.7.3", - "serde", + "serde 1.0.117", "serde_json", "serde_urlencoded", ] @@ -479,7 +479,7 @@ dependencies = [ "lazy_static", "memchr", "regex-automata", - "serde", + "serde 1.0.117", ] [[package]] @@ -535,8 +535,8 @@ checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ "libc", "num-integer", - "num-traits", - "serde", + "num-traits 0.2.14", + "serde 1.0.117", "time 0.1.44", "winapi 0.3.9", ] @@ -580,6 +580,22 @@ dependencies = [ "bitflags", ] +[[package]] +name = "config" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" +dependencies = [ + "lazy_static", + "nom", + "rust-ini", + "serde 1.0.117", + "serde-hjson", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "const_fn" version = "0.4.3" @@ -613,7 +629,7 @@ dependencies = [ "idna", "log", "publicsuffix", - "serde", + "serde 1.0.117", "serde_json", "time 0.2.23", "url", @@ -661,7 +677,7 @@ dependencies = [ "csv-core", "itoa", "ryu", - "serde", + "serde 1.0.117", ] [[package]] @@ -710,6 +726,26 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "dirs" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.9", +] + [[package]] name = "discard" version = "1.0.4" @@ -804,7 +840,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" dependencies = [ - "num-traits", + "num-traits 0.2.14", ] [[package]] @@ -1132,7 +1168,7 @@ checksum = "cb79e59d356a5ae85b13990bbb3649a293d64df1ca6e7890822076186527a9f7" dependencies = [ "lmdb-rkv", "rmp-serde", - "serde", + "serde 1.0.117", "thiserror", "toml", ] @@ -1149,12 +1185,35 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lexical-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if 0.1.10", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" +[[package]] +name = "linked-hash-map" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" +dependencies = [ + "serde 0.8.23", + "serde_test", +] + [[package]] name = "linked-hash-map" version = "0.5.3" @@ -1208,7 +1267,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" dependencies = [ - "linked-hash-map", + "linked-hash-map 0.5.3", ] [[package]] @@ -1298,6 +1357,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -1311,7 +1381,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg 1.0.1", - "num-traits", + "num-traits 0.2.14", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.14", ] [[package]] @@ -1527,7 +1606,7 @@ dependencies = [ "bitflags", "byteorder", "lazy_static", - "num-traits", + "num-traits 0.2.14", "quick-error", "rand 0.6.5", "rand_chacha 0.1.1", @@ -1828,7 +1907,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f10b46df14cf1ee1ac7baa4d2fbc2c52c0622a4b82fa8740e37bc452ac0184f" dependencies = [ "byteorder", - "num-traits", + "num-traits 0.2.14", ] [[package]] @@ -1839,7 +1918,7 @@ checksum = "4ce7d70c926fe472aed493b902010bccc17fa9f7284145cb8772fd22fdb052d8" dependencies = [ "byteorder", "rmp", - "serde", + "serde 1.0.117", ] [[package]] @@ -1854,6 +1933,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rust-ini" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" + [[package]] name = "rustc-demangle" version = "0.1.18" @@ -1931,6 +2016,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "serde" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" + [[package]] name = "serde" version = "1.0.117" @@ -1940,6 +2031,19 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-hjson" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" +dependencies = [ + "lazy_static", + "linked-hash-map 0.3.0", + "num-traits 0.1.43", + "regex", + "serde 0.8.23", +] + [[package]] name = "serde_derive" version = "1.0.117" @@ -1959,7 +2063,16 @@ checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" dependencies = [ "itoa", "ryu", - "serde", + "serde 1.0.117", +] + +[[package]] +name = "serde_test" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5" +dependencies = [ + "serde 0.8.23", ] [[package]] @@ -1971,7 +2084,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde", + "serde 1.0.117", ] [[package]] @@ -2041,6 +2154,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "stdweb" version = "0.4.20" @@ -2063,7 +2182,7 @@ checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ "proc-macro2", "quote", - "serde", + "serde 1.0.117", "serde_derive", "syn", ] @@ -2077,7 +2196,7 @@ dependencies = [ "base-x", "proc-macro2", "quote", - "serde", + "serde 1.0.117", "serde_derive", "serde_json", "sha1", @@ -2128,7 +2247,7 @@ dependencies = [ "kv", "lmdb-rkv", "proptest", - "serde", + "serde 1.0.117", "serde_json", "tempdir", "ureq", @@ -2141,6 +2260,8 @@ version = "0.1.0" dependencies = [ "assert_cmd", "clap", + "config", + "dirs 3.0.1", "failure", "predicates", "prettytable-rs", @@ -2189,7 +2310,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" dependencies = [ "byteorder", - "dirs", + "dirs 1.0.5", "winapi 0.3.9", ] @@ -2344,7 +2465,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" dependencies = [ - "serde", + "serde 1.0.117", ] [[package]] @@ -2510,7 +2631,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" dependencies = [ "rand 0.7.3", - "serde", + "serde 1.0.117", ] [[package]] @@ -2687,3 +2808,12 @@ dependencies = [ "winapi 0.2.8", "winapi-build", ] + +[[package]] +name = "yaml-rust" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" +dependencies = [ + "linked-hash-map 0.5.3", +] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 89b1b9ed9..2f8ff3c83 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -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" diff --git a/cli/src/cmd/shared.rs b/cli/src/cmd/shared.rs index bcd6fbf0b..9744ea6f3 100644 --- a/cli/src/cmd/shared.rs +++ b/cli/src/cmd/shared.rs @@ -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>(replica: &mut Replica, task_arg: S) -> Fal #[derive(Debug)] pub struct CommandInvocation { pub(crate) subcommand: Box, + settings: RefCell, } impl CommandInvocation { pub(crate) fn new(subcommand: Box) -> 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> { + { + // 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 { - // 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> { - // 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, })?) } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 48421f25a..4474af8f3 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -3,6 +3,7 @@ use failure::Fallible; use std::ffi::OsString; mod cmd; +pub(crate) mod settings; mod table; use cmd::ArgMatchResult; diff --git a/cli/src/settings.rs b/cli/src/settings.rs new file mode 100644 index 000000000..f8a92582e --- /dev/null +++ b/cli/src/settings.rs @@ -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 { + 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 = 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 = dir.into(); + settings.merge(config_file.required(false))?; + } + + // merge environment variables + settings.merge(Environment::with_prefix("TASKCHAMPION"))?; + + Ok(settings) +} diff --git a/docs/src/usage.md b/docs/src/usage.md index 47ab9388b..329cdf1eb 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -9,13 +9,26 @@ Note that the `task` interface does not match that of TaskWarrior. ### Configuration -Temporarily, configuration is by environment variables. -The directory containing the replica's task data is given by `TASK_DB`, defaulting to `/tmp/tasks`. -the origin of the sync server is given by `SYNC_SERVER_ORIGIN`, defaulting to `http://localhost:8080`. -The client ID to use with the sync server is givne by `SYNC_SERVER_CLIENT_ID` (with no default). +The `task` command will work out-of-the-box with no configuration file, using default values. + +Configuration is read from `taskchampion.yaml` (or `taskchampion.toml` or `taskchmapion.json` if you prefer) in your config directory. +On Linux systems, that directory is `~/.config`. +On OS X, it's `~/Library/Preferences`. +On Windows, it's `AppData/Roaming` in your home directory. +The path can be overridden by setting `$TASKCHAMPION_CONFIG`. + +Individual configuration parameters can be overridden by environemnt variables, converted to upper-case and prefixed with `TASKCHAMPION_`, e.g., `TASKCHAMPION_DATA_DIR`. +Nested configuration parameters cannot be overridden by environment variables. + +The following configuration parameters are available: + +* `data_dir` - path to a directory containing the replica's task data (which will be created if necessary). + Default: `taskchampion` in the local data directory +* `server_origin` - Origin of the taskchampion sync server, e.g., `https://taskchampion.example.com` +* `server_client_id` - Client ID to identify this replica to the sync server (a UUID) ## `taskchampion-sync-server` Run `taskchampion-sync-server` to start the sync server. It serves on port 8080 on all interfaces, using an in-memory database (meaning that all data is lost when the process exits). -Requests for previously-unknown clients are automatically added. +Requests for previously-unknown clients automatically create the client. From 6d08eacd98b873417435a72eff5c4fa26d338561 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Nov 2020 19:43:30 -0500 Subject: [PATCH 3/3] limit config file usage to just yaml --- Cargo.lock | 107 +++++++++++----------------------------------- cli/Cargo.toml | 2 +- docs/src/usage.md | 2 +- 3 files changed, 27 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a683e918..6b649fe16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,7 +74,7 @@ dependencies = [ "pin-project 1.0.2", "rand 0.7.3", "regex", - "serde 1.0.117", + "serde", "serde_json", "serde_urlencoded", "sha-1", @@ -102,7 +102,7 @@ dependencies = [ "http", "log", "regex", - "serde 1.0.117", + "serde", ] [[package]] @@ -241,7 +241,7 @@ dependencies = [ "mime", "pin-project 1.0.2", "regex", - "serde 1.0.117", + "serde", "serde_json", "serde_urlencoded", "socket2", @@ -372,7 +372,7 @@ dependencies = [ "mime", "percent-encoding", "rand 0.7.3", - "serde 1.0.117", + "serde", "serde_json", "serde_urlencoded", ] @@ -479,7 +479,7 @@ dependencies = [ "lazy_static", "memchr", "regex-automata", - "serde 1.0.117", + "serde", ] [[package]] @@ -535,8 +535,8 @@ checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ "libc", "num-integer", - "num-traits 0.2.14", - "serde 1.0.117", + "num-traits", + "serde", "time 0.1.44", "winapi 0.3.9", ] @@ -588,11 +588,7 @@ checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" dependencies = [ "lazy_static", "nom", - "rust-ini", - "serde 1.0.117", - "serde-hjson", - "serde_json", - "toml", + "serde", "yaml-rust", ] @@ -629,7 +625,7 @@ dependencies = [ "idna", "log", "publicsuffix", - "serde 1.0.117", + "serde", "serde_json", "time 0.2.23", "url", @@ -677,7 +673,7 @@ dependencies = [ "csv-core", "itoa", "ryu", - "serde 1.0.117", + "serde", ] [[package]] @@ -840,7 +836,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" dependencies = [ - "num-traits 0.2.14", + "num-traits", ] [[package]] @@ -1168,7 +1164,7 @@ checksum = "cb79e59d356a5ae85b13990bbb3649a293d64df1ca6e7890822076186527a9f7" dependencies = [ "lmdb-rkv", "rmp-serde", - "serde 1.0.117", + "serde", "thiserror", "toml", ] @@ -1204,16 +1200,6 @@ version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" -[[package]] -name = "linked-hash-map" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" -dependencies = [ - "serde 0.8.23", - "serde_test", -] - [[package]] name = "linked-hash-map" version = "0.5.3" @@ -1267,7 +1253,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" dependencies = [ - "linked-hash-map 0.5.3", + "linked-hash-map", ] [[package]] @@ -1381,16 +1367,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg 1.0.1", - "num-traits 0.2.14", -] - -[[package]] -name = "num-traits" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" -dependencies = [ - "num-traits 0.2.14", + "num-traits", ] [[package]] @@ -1606,7 +1583,7 @@ dependencies = [ "bitflags", "byteorder", "lazy_static", - "num-traits 0.2.14", + "num-traits", "quick-error", "rand 0.6.5", "rand_chacha 0.1.1", @@ -1907,7 +1884,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f10b46df14cf1ee1ac7baa4d2fbc2c52c0622a4b82fa8740e37bc452ac0184f" dependencies = [ "byteorder", - "num-traits 0.2.14", + "num-traits", ] [[package]] @@ -1918,7 +1895,7 @@ checksum = "4ce7d70c926fe472aed493b902010bccc17fa9f7284145cb8772fd22fdb052d8" dependencies = [ "byteorder", "rmp", - "serde 1.0.117", + "serde", ] [[package]] @@ -1933,12 +1910,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "rust-ini" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" - [[package]] name = "rustc-demangle" version = "0.1.18" @@ -2016,12 +1987,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -[[package]] -name = "serde" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" - [[package]] name = "serde" version = "1.0.117" @@ -2031,19 +1996,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-hjson" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" -dependencies = [ - "lazy_static", - "linked-hash-map 0.3.0", - "num-traits 0.1.43", - "regex", - "serde 0.8.23", -] - [[package]] name = "serde_derive" version = "1.0.117" @@ -2063,16 +2015,7 @@ checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" dependencies = [ "itoa", "ryu", - "serde 1.0.117", -] - -[[package]] -name = "serde_test" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5" -dependencies = [ - "serde 0.8.23", + "serde", ] [[package]] @@ -2084,7 +2027,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.117", + "serde", ] [[package]] @@ -2182,7 +2125,7 @@ checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ "proc-macro2", "quote", - "serde 1.0.117", + "serde", "serde_derive", "syn", ] @@ -2196,7 +2139,7 @@ dependencies = [ "base-x", "proc-macro2", "quote", - "serde 1.0.117", + "serde", "serde_derive", "serde_json", "sha1", @@ -2247,7 +2190,7 @@ dependencies = [ "kv", "lmdb-rkv", "proptest", - "serde 1.0.117", + "serde", "serde_json", "tempdir", "ureq", @@ -2465,7 +2408,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" dependencies = [ - "serde 1.0.117", + "serde", ] [[package]] @@ -2631,7 +2574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" dependencies = [ "rand 0.7.3", - "serde 1.0.117", + "serde", ] [[package]] @@ -2815,5 +2758,5 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" dependencies = [ - "linked-hash-map 0.5.3", + "linked-hash-map", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 2f8ff3c83..36e313f8e 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -9,7 +9,7 @@ clap = "^2.33.0" taskchampion = { path = "../taskchampion" } failure = "^0.1.8" prettytable-rs = "^0.8.0" -config = "^0.10.1" +config = { version="^0.10.1", default-features=false, features=["yaml"] } dirs = "^3.0.1" [dev-dependencies] diff --git a/docs/src/usage.md b/docs/src/usage.md index 329cdf1eb..37123bfd2 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -11,7 +11,7 @@ Note that the `task` interface does not match that of TaskWarrior. The `task` command will work out-of-the-box with no configuration file, using default values. -Configuration is read from `taskchampion.yaml` (or `taskchampion.toml` or `taskchmapion.json` if you prefer) in your config directory. +Configuration is read from `taskchampion.yaml` in your config directory. On Linux systems, that directory is `~/.config`. On OS X, it's `~/Library/Preferences`. On Windows, it's `AppData/Roaming` in your home directory.