SQLite-backed Key / Value Store
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #12 from Tyrannican/headless-mode

Headless mode

authored by

Graham Keenan and committed by
GitHub
dd4cf35c 752f91b8

+202 -95
+3 -3
Cargo.lock
··· 693 693 694 694 [[package]] 695 695 name = "safir" 696 - version = "0.7.1" 696 + version = "0.8.0" 697 697 dependencies = [ 698 698 "anyhow", 699 699 "clap", ··· 703 703 704 704 [[package]] 705 705 name = "safir-core" 706 - version = "0.2.0" 706 + version = "0.2.1" 707 707 dependencies = [ 708 708 "anyhow", 709 709 "async-trait", ··· 720 720 721 721 [[package]] 722 722 name = "safir-mem" 723 - version = "0.2.1" 723 + version = "0.3.0" 724 724 dependencies = [ 725 725 "anyhow", 726 726 "clap",
+1 -1
safir-core/Cargo.toml
··· 1 1 [package] 2 2 name = "safir-core" 3 - version = "0.2.0" 3 + version = "0.2.1" 4 4 edition = "2021" 5 5 description = "Internal lib used to build Safir and Safir-mem" 6 6 authors = ["Graham Keenan graham.keenan@outlook.com"]
+22 -3
safir-core/src/config.rs
··· 12 12 #[serde(skip)] 13 13 pub config_path: PathBuf, 14 14 pub memcache_pid: Option<u32>, 15 + pub headless_mode: Option<bool>, 15 16 } 16 17 17 18 impl SafirConfig { 18 19 pub fn new() -> Self { 19 - Self::default() 20 + let mut cfg = Self::default(); 21 + cfg.headless_mode = Some(false); 22 + cfg 20 23 } 21 24 22 25 pub async fn load(cfg_path: impl AsRef<Path>) -> Result<Self> { 23 - let cfg = fs::read_to_string(cfg_path).await?; 24 - Ok(serde_json::from_str(&cfg).expect("unable to deserialize safir config")) 26 + let cfg_str = fs::read_to_string(cfg_path).await?; 27 + let mut cfg: SafirConfig = 28 + serde_json::from_str(&cfg_str).expect("unable to deserialize safir config"); 29 + 30 + // Backwards compat with older Safir configs 31 + if cfg.headless_mode.is_none() { 32 + cfg.headless_mode = Some(false); 33 + } 34 + 35 + Ok(cfg) 25 36 } 26 37 27 38 pub async fn write(&self) -> Result<()> { ··· 30 41 file.write_all(data.as_bytes()).await?; 31 42 32 43 Ok(()) 44 + } 45 + 46 + pub fn get_headless_mode(&self) -> bool { 47 + if self.headless_mode.is_some() { 48 + self.headless_mode.unwrap() 49 + } else { 50 + false 51 + } 33 52 } 34 53 }
+72 -38
safir-core/src/disk.rs
··· 14 14 use colored::*; 15 15 16 16 use crate::config::SafirConfig; 17 - use crate::utils::{confirm_entry, print_header, print_output}; 17 + use crate::utils::{confirm_entry, print_header, print_headless, print_output}; 18 18 use crate::SafirEngine; 19 19 use rubin::store::persistence::PersistentStore; 20 20 21 21 /// Safir Store (fancy wrapper around reading and writing to a JSON file) 22 22 pub struct SafirStore { 23 23 pub store: PersistentStore, 24 + pub headless: bool, 25 + } 26 + 27 + impl SafirStore { 28 + /// Initialises the Safirstore if not already initialised 29 + pub async fn new(config: &SafirConfig) -> Result<Self> { 30 + let store_path = config.root_path.join("safirstore.json"); 31 + let mut ps = if store_path.exists() { 32 + PersistentStore::from_existing(store_path).await? 33 + } else { 34 + PersistentStore::new(store_path).await? 35 + }; 36 + 37 + ps.set_write_on_update(true); 38 + let headless = config.get_headless_mode(); 39 + 40 + Ok(Self { 41 + store: ps, 42 + headless, 43 + }) 44 + } 45 + 46 + /// Display all key/values in the store 47 + pub fn display_all(&self) { 48 + let strings = self.store.get_string_store_ref(); 49 + if self.headless { 50 + for (key, value) in strings.iter() { 51 + print_headless("", key, value); 52 + } 53 + 54 + return; 55 + } 56 + 57 + print_header(); 58 + let mut output: String; 59 + for (key, value) in strings.iter() { 60 + output = format!("{}: \"{}\"", key.bold().yellow(), value); 61 + print_output(&output); 62 + } 63 + } 64 + 65 + /// Remove the store directory and all contents 66 + pub fn purge(&self) { 67 + if confirm_entry("Are you sure you want to purge Safirstore?") { 68 + std::fs::remove_dir_all(&self.store.path) 69 + .expect("unable to remove safirstore directory"); 70 + } 71 + } 24 72 } 25 73 26 74 #[async_trait] ··· 33 81 34 82 /// Get an entry form the store by loading it from disk and displaying it 35 83 async fn get_entry(&self, key: String) -> Result<()> { 84 + let value = if let Ok(val) = self.store.get_string(&key) { 85 + val 86 + } else { 87 + String::from("") 88 + }; 89 + 90 + if self.headless { 91 + print_headless("", &key, &value); 92 + return Ok(()); 93 + } 94 + 36 95 print_header(); 37 - let output = if let Ok(val) = self.store.get_string(&key) { 38 - format!("{}: \"{}\"", key.bold().yellow(), val) 96 + let output = if !value.is_empty() { 97 + format!("{}: \"{}\"", key.bold().yellow(), value) 39 98 } else { 40 99 format!("{}: ", key.bold().yellow()) 41 100 }; ··· 59 118 /// E.g. With a prefix of `alias` this will display the command as 60 119 /// `alias {key}="{value}"` with {key} / {value} replaced with their values from the store 61 120 async fn set_commands(&mut self, prefix: &str, keys: &Vec<String>) { 121 + if self.headless { 122 + for key in keys { 123 + if let Ok(value) = self.store.get_string(key) { 124 + print_headless(prefix, key, &value); 125 + } 126 + } 127 + 128 + return; 129 + } 130 + 62 131 print_header(); 63 132 let prefix = match prefix { 64 133 "alias" => "alias".bold().green(), 65 134 "export" => "export".bold().magenta(), 66 135 _ => prefix.bold(), 67 136 }; 68 - 69 137 for key in keys { 70 138 if let Ok(value) = self.store.get_string(key) { 71 139 println!("{} {}=\"{}\"\n", prefix, key.bold().yellow(), value); ··· 86 154 self 87 155 } 88 156 } 89 - 90 - impl SafirStore { 91 - /// Initialises the Safirstore if not already initialised 92 - pub async fn new(config: &SafirConfig) -> Result<Self> { 93 - let store_path = config.root_path.join("safirstore.json"); 94 - let mut ps = if store_path.exists() { 95 - PersistentStore::from_existing(store_path).await? 96 - } else { 97 - PersistentStore::new(store_path).await? 98 - }; 99 - 100 - ps.set_write_on_update(true); 101 - Ok(Self { store: ps }) 102 - } 103 - 104 - /// Display all key/values in the store 105 - pub fn display_all(&self) { 106 - print_header(); 107 - let mut output: String; 108 - let strings = self.store.get_string_store_ref(); 109 - for (key, value) in strings.iter() { 110 - output = format!("{}: \"{}\"", key.bold().yellow(), value); 111 - print_output(&output); 112 - } 113 - } 114 - 115 - /// Remove the store directory and all contents 116 - pub fn purge(&self) { 117 - if confirm_entry("Are you sure you want to purge Safirstore?") { 118 - std::fs::remove_dir_all(&self.store.path) 119 - .expect("unable to remove safirstore directory"); 120 - } 121 - } 122 - }
+27 -36
safir-core/src/mem.rs
··· 1 1 use anyhow::Result; 2 2 3 3 use async_trait::async_trait; 4 - use colored::*; 5 4 6 5 use crate::config::SafirConfig; 7 - use crate::utils::{self, confirm_entry, print_header, print_output}; 6 + use crate::utils::{self, confirm_entry, print_headless}; 8 7 use crate::SafirEngine; 9 8 use rubin::net::client::RubinClient; 10 9 ··· 19 18 client: RubinClient, 20 19 } 21 20 21 + impl SafirMemcache { 22 + pub async fn new(cfg: &SafirConfig) -> Result<Self> { 23 + Ok(Self { 24 + is_online: utils::is_safir_running(cfg.memcache_pid), 25 + client: RubinClient::new("127.0.0.1", 9876), 26 + }) 27 + } 28 + 29 + pub async fn dump_store(&self, path: &str) -> Result<()> { 30 + if !self.is_online { 31 + safir_offline(); 32 + return Ok(()); 33 + } 34 + 35 + self.client.dump_store(path).await?; 36 + println!("Safir memcache dumped to {}", path); 37 + 38 + Ok(()) 39 + } 40 + } 41 + 22 42 #[async_trait] 23 43 impl SafirEngine for SafirMemcache { 24 44 async fn add_entry(&mut self, key: String, value: String) -> Result<()> { ··· 37 57 return Ok(()); 38 58 } 39 59 40 - print_header(); 41 - let output = if let Ok(val) = self.client.get_string(&key).await { 42 - format!("{}: \"{}\"", key.bold().yellow(), val) 60 + let value = if let Ok(val) = self.client.get_string(&key).await { 61 + val 43 62 } else { 44 - format!("{}: ", key.bold().yellow()) 63 + String::from("") 45 64 }; 46 65 47 - print_output(&output); 66 + print_headless("", &key, &value); 48 67 49 68 Ok(()) 50 69 } ··· 67 86 safir_offline(); 68 87 return; 69 88 } 70 - 71 - print_header(); 72 - let prefix = match prefix { 73 - "alias" => "alias".bold().green(), 74 - "export" => "export".bold().magenta(), 75 - _ => prefix.bold(), 76 - }; 77 89 78 90 for key in keys { 79 91 if let Ok(value) = self.client.get_string(key).await { 80 - println!("{} {}=\"{}\"\n", prefix, key.bold().yellow(), value); 92 + print_headless(prefix, key, &value); 81 93 } 82 94 } 83 95 } ··· 99 111 self 100 112 } 101 113 } 102 - 103 - impl SafirMemcache { 104 - pub async fn new(cfg: &SafirConfig) -> Result<Self> { 105 - Ok(Self { 106 - is_online: utils::is_safir_running(cfg.memcache_pid), 107 - client: RubinClient::new("127.0.0.1", 9876), 108 - }) 109 - } 110 - 111 - pub async fn dump_store(&self, path: &str) -> Result<()> { 112 - if !self.is_online { 113 - safir_offline(); 114 - return Ok(()); 115 - } 116 - 117 - self.client.dump_store(path).await?; 118 - println!("Safir memcache dumped to {}", path); 119 - 120 - Ok(()) 121 - } 122 - }
+20
safir-core/src/utils.rs
··· 89 89 println!("{}", "--=Safirstore=--\n".bold()); 90 90 } 91 91 92 + pub fn print_headless(prefix: &str, key: &str, value: &str) { 93 + if value == "" { 94 + return; 95 + } 96 + 97 + let has_whitespace = value.contains(char::is_whitespace); 98 + 99 + let output = if has_whitespace { 100 + format!("{}=\"{}\"", key, value) 101 + } else { 102 + format!("{}={}", key, value) 103 + }; 104 + 105 + if !prefix.is_empty() { 106 + println!("{} {}", prefix, output); 107 + } else { 108 + println!("{}", output); 109 + } 110 + } 111 + 92 112 /// Confirmation dialog for important calls 93 113 pub fn confirm_entry(msg: &str) -> bool { 94 114 let mut answer = String::new();
+8
safir-mem/CHANGELOG.md
··· 2 2 3 3 Documenting changes between versions 4 4 5 + ## v0.3.0 6 + 7 + Removed formatted output 8 + 9 + This makes more sense in the for safir-mem as it means you can easily evaluate output in the terminal 10 + 11 + Better suits the use case. 12 + 5 13 ## v0.2.1 6 14 7 15 Changes to the internal code structure, no changes to operation
+2 -2
safir-mem/Cargo.toml
··· 1 1 [package] 2 2 name = "safir-mem" 3 - version = "0.2.1" 3 + version = "0.3.0" 4 4 edition = "2021" 5 5 authors = ["Graham Keenan graham.keenan@outlook.com"] 6 6 license = "MIT OR Apache-2.0" ··· 16 16 [dependencies] 17 17 clap = { version = "4.2.5" , features = ["derive"] } 18 18 tokio = { version = "1.28.2", features = ["full"] } 19 - safir-core = { version = "0.2.0", path = "../safir-core" } 19 + safir-core = { version = "0.2.1", path = "../safir-core" } 20 20 anyhow = "1.0.75"
+1 -2
safir-mem/src/main.rs
··· 21 21 if let Some(key) = &args.key { 22 22 safir_mem.get_entry(key.to_string()).await?; 23 23 } else { 24 - utils::print_header(); 25 - utils::print_output("A key is required for memcache GET command!"); 24 + println!("A key is required for memcache GET command!"); 26 25 } 27 26 } 28 27 Commands::Rm(args) => {
+10
safir/CHANGELOG.md
··· 2 2 3 3 Documenting changes between versions beginning from v0.3.0 4 4 5 + ## v0.8.0 6 + 7 + Headless mode! 8 + 9 + You can now run safir in "headless" mode which removes all the fancy formatting for output 10 + 11 + This will allow you to evaluate safir output directly in the terminal. 12 + 13 + This also persist as a config setting saved to `~/.safirstore/safir.cfg` 14 + 5 15 ## v0.7.1 6 16 7 17 Changes to the internal code structure, no changes to operation
+2 -2
safir/Cargo.toml
··· 1 1 [package] 2 2 name = "safir" 3 - version = "0.7.1" 3 + version = "0.8.0" 4 4 edition = "2021" 5 5 authors = ["Graham Keenan graham.keenan@outlook.com"] 6 6 license = "MIT OR Apache-2.0" ··· 16 16 [dependencies] 17 17 clap = { version = "4.2.5" , features = ["derive"] } 18 18 tokio = { version = "1.28.2", features = ["full"] } 19 - safir-core = { version = "0.2.0", path = "../safir-core" } 19 + safir-core = { version = "0.2.1", path = "../safir-core" } 20 20 anyhow = "1.0.75"
+9 -8
safir/README.md
··· 28 28 Usage: safir <COMMAND> 29 29 30 30 Commands: 31 - add Add a value to the store with the given key 32 - get Get a value from the store 33 - rm Remove values from the store 34 - alias Output the alias command for a key / value pair to be entered into a shell session 35 - export Output the export command for a key / value pair to be entered into a shell session 36 - clear Clear all keys/values from the store 37 - purge Purges the .safirstore directory, removing it and its contents 38 - help Print this message or the help of the given subcommand(s) 31 + add Add a value to the store with the given key 32 + get Get a value from the store 33 + rm Remove values from the store 34 + alias Output the alias command for a key / value pair to be entered into a shell session 35 + export Output the export command for a key / value pair to be entered into a shell session 36 + clear Clear all keys/values from the store 37 + purge Purges the .safirstore directory, removing it and its contents 38 + headless Set the headless mode 39 + help Print this message or the help of the given subcommand(s) 39 40 40 41 Options: 41 42 -h, --help Print help
+13
safir/src/cli.rs
··· 35 35 36 36 /// Purges the .safirstore directory, removing it and its contents 37 37 Purge, 38 + 39 + /// Set the headless mode 40 + #[clap(subcommand)] 41 + Headless(HeadlessFlags), 38 42 } 39 43 40 44 /// Arguments for adding a value to the store with a given key ··· 71 75 /// Name of the keys to display (e.g. alias / export) 72 76 pub keys: Vec<String>, 73 77 } 78 + 79 + #[derive(Subcommand, Debug)] 80 + pub enum HeadlessFlags { 81 + /// Set headless mode ON 82 + On, 83 + 84 + /// Set headless mode OFF 85 + Off, 86 + }
+12
safir/src/main.rs
··· 40 40 let inner = safir.as_safir_store(); 41 41 inner.purge(); 42 42 } 43 + Commands::Headless(mode) => match mode { 44 + HeadlessFlags::On => { 45 + safir.config.headless_mode = Some(true); 46 + safir.config.write().await?; 47 + println!("Headless mode is ON"); 48 + } 49 + HeadlessFlags::Off => { 50 + safir.config.headless_mode = Some(false); 51 + safir.config.write().await?; 52 + println!("Headless mode is OFF"); 53 + } 54 + }, 43 55 } 44 56 45 57 Ok(())