···2233Documenting changes between versions beginning from v0.3.0
4455+## v0.6.0
66+77+Removed the Safir Memcache and moved it to its own project.
88+99+Addition of the Memcache functionality was making everything a bit messy to best to separate them both.
1010+They still operate the same, just different projects for each.
1111+1212+Any changes here wil lbe made in the Memcache version also.
1313+1414+* Removal of Memcache
1515+* Technically a reversion but meh
1616+517## v0.5.0
618719Added the ability to operate Safir as a Memcache server (storing contents in-memory instead of on disk).
···11-[package]
22-name = "safir"
33-version = "0.5.0"
44-edition = "2021"
55-authors = ["Graham Keenan graham.keenan@outlook.com"]
66-license = "MIT OR Apache-2.0"
77-description = "Key/Value store to share values between different shell sessions"
88-readme = "README.md"
99-homepage = "https://github.com/Tyrannican/safir"
1010-repository = "https://github.com/Tyrannican/safir"
1111-keywords = ["cli", "terminal", "utility", "key-value", "store"]
1212-categories = ["command-line-utilities"]
1313-1414-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1515-1616-[dependencies]
1717-clap = { version = "4.2.5" , features = ["derive"] }
1818-serde = { version = "1", features = ["derive"] }
1919-serde_json = "1"
2020-dirs = "5"
2121-colored = "2"
2222-rubin = "^0.3.1"
2323-tokio = { version = "1.28.2", features = ["full"] }
2424-psutil = { version = "3.2.2", optional = true }
2525-which = "4.4.0"
2626-sysinfo = "0.29.3"
2727-2828-[target.'cfg(unix)'.dependencies]
2929-psutil = "3.2.2"
11+[workspace]
22+members = ["safir", "safir-mem"]
33+resolver = "2"
LICENSE-APACHE
safir/LICENSE-APACHE
LICENSE-MIT
safir/LICENSE-MIT
+1-86
README.md
···11# Safir
2233-Simple CLI key/value store.
44-55-Store key/value pairs in the terminal and retrieve them later for use like in different shell sessions.
66-77-## Install
88-99-To install `safir`, run `cargo install safir`.
1010-1111-To build from source, clone the repository and run:
1212-1313-```bash
1414-cargo build --release
1515-```
1616-1717-Then move the binary to somewhere in your `$PATH`
1818-1919-## Usage
2020-2121-When `safir` is run, it creates a store file in your `$HOME` directory (`$HOME/.safirstore/safirstore.json`).
2222-2323-Run `safir --help` for usage:
2424-2525-```bash
2626-Key/Value store to share information between shell sessions
2727-2828-Usage: safir <COMMAND>
2929-3030-Commands:
3131- add Add a value to the store with the given key
3232- get Get a value from the store
3333- rm Remove values from the store
3434- alias Output the alias command for a key / value pair to be entered into a shell session
3535- export Output the export command for a key / value pair to be entered into a shell session
3636- clear Clear all keys/values from the store
3737- purge Purges the .safirstore directory, removing it and its contents
3838- mem Start or stop the Memcache (in-memory store) service
3939- help Print this message or the help of the given subcommand(s)
4040-4141-Options:
4242- -h, --help Print help
4343- -V, --version Print version
4444-```
4545-4646-## Memcache service
4747-4848-Safir offers the ability to run the store as a dedicate memcache service using in-memory storage.
4949-5050-The service runs on `localhost` or `127.0.0.1` on dedicated port `9876`.
5151-Once activated, Safir will continue to act as before expect that all new values added are given to the memcache instead of being saved on disk.
5252-5353-This can be enabled / diabled with the `start` and `stop` commands respectively.
5454-Note that when the memcache service is disabled, ALL data contained within it is lost so use wisely.
5555-5656-In cases where you want to save the contents of the memcache, the `dump` command will allow for the contents to be saved out to disk in JSON format.
5757-This behaves as a snapshot as the contents of the cache persist after usage.
5858-5959-### Requirements
6060-6161-Using this requires that the [Rubin CLI](https://crates.io/crates/rubin-cli) be installed.
6262-6363-```bash
6464-cargo install rubin-cli
6565-```
6666-6767-### Usage
6868-6969-Start or stop the Memcache (in-memory store) service
7070-7171-Usage: safir mem <COMMAND>
7272-7373-Commands:
7474- start Start the Safir Memcache server
7575- stop Stop the Safir Memcache server
7676- dump Dump the Safir Memcache server to disk
7777- help Print this message or the help of the given subcommand(s)
7878-7979-Options:
8080- -h, --help Print help
8181- -V, --version Print version
8282-8383-## v0.3.0 -> v0.4.0
8484-8585-v0.4.0 introduces a breaking change which makes it incompatible with older versions of Safir.
8686-8787-To prevent issues, please remove the old `.safirstore/` directory (store the old data somewhere) and re-run Safir.
8888-This should address any issues!
33+Repo containing the source for `safir` and `safir-mem`.
+29
safir-mem/Cargo.toml
···11+[package]
22+name = "safir-mem"
33+version = "0.1.0"
44+edition = "2021"
55+authors = ["Graham Keenan graham.keenan@outlook.com"]
66+license = "MIT OR Apache-2.0"
77+description = "In-memory key/value store to share values between different shell sessions"
88+readme = "README.md"
99+homepage = "https://github.com/Tyrannican/safir"
1010+repository = "https://github.com/Tyrannican/safir"
1111+keywords = ["cli", "terminal", "utility", "key-value", "store"]
1212+categories = ["command-line-utilities"]
1313+1414+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1515+1616+[dependencies]
1717+clap = { version = "4.2.5" , features = ["derive"] }
1818+serde = { version = "1", features = ["derive"] }
1919+serde_json = "1"
2020+dirs = "5"
2121+colored = "2"
2222+rubin = "^0.3.1"
2323+tokio = { version = "1.28.2", features = ["full"] }
2424+psutil = { version = "3.2.2", optional = true }
2525+which = "4.4.0"
2626+sysinfo = "0.29.3"
2727+2828+[target.'cfg(unix)'.dependencies]
2929+psutil = "3.2.2"
+201
safir-mem/LICENSE-APACHE
···11+ Apache License
22+ Version 2.0, January 2004
33+ http://www.apache.org/licenses/
44+55+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
66+77+ 1. Definitions.
88+99+ "License" shall mean the terms and conditions for use, reproduction,
1010+ and distribution as defined by Sections 1 through 9 of this document.
1111+1212+ "Licensor" shall mean the copyright owner or entity authorized by
1313+ the copyright owner that is granting the License.
1414+1515+ "Legal Entity" shall mean the union of the acting entity and all
1616+ other entities that control, are controlled by, or are under common
1717+ control with that entity. For the purposes of this definition,
1818+ "control" means (i) the power, direct or indirect, to cause the
1919+ direction or management of such entity, whether by contract or
2020+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
2121+ outstanding shares, or (iii) beneficial ownership of such entity.
2222+2323+ "You" (or "Your") shall mean an individual or Legal Entity
2424+ exercising permissions granted by this License.
2525+2626+ "Source" form shall mean the preferred form for making modifications,
2727+ including but not limited to software source code, documentation
2828+ source, and configuration files.
2929+3030+ "Object" form shall mean any form resulting from mechanical
3131+ transformation or translation of a Source form, including but
3232+ not limited to compiled object code, generated documentation,
3333+ and conversions to other media types.
3434+3535+ "Work" shall mean the work of authorship, whether in Source or
3636+ Object form, made available under the License, as indicated by a
3737+ copyright notice that is included in or attached to the work
3838+ (an example is provided in the Appendix below).
3939+4040+ "Derivative Works" shall mean any work, whether in Source or Object
4141+ form, that is based on (or derived from) the Work and for which the
4242+ editorial revisions, annotations, elaborations, or other modifications
4343+ represent, as a whole, an original work of authorship. For the purposes
4444+ of this License, Derivative Works shall not include works that remain
4545+ separable from, or merely link (or bind by name) to the interfaces of,
4646+ the Work and Derivative Works thereof.
4747+4848+ "Contribution" shall mean any work of authorship, including
4949+ the original version of the Work and any modifications or additions
5050+ to that Work or Derivative Works thereof, that is intentionally
5151+ submitted to Licensor for inclusion in the Work by the copyright owner
5252+ or by an individual or Legal Entity authorized to submit on behalf of
5353+ the copyright owner. For the purposes of this definition, "submitted"
5454+ means any form of electronic, verbal, or written communication sent
5555+ to the Licensor or its representatives, including but not limited to
5656+ communication on electronic mailing lists, source code control systems,
5757+ and issue tracking systems that are managed by, or on behalf of, the
5858+ Licensor for the purpose of discussing and improving the Work, but
5959+ excluding communication that is conspicuously marked or otherwise
6060+ designated in writing by the copyright owner as "Not a Contribution."
6161+6262+ "Contributor" shall mean Licensor and any individual or Legal Entity
6363+ on behalf of whom a Contribution has been received by Licensor and
6464+ subsequently incorporated within the Work.
6565+6666+ 2. Grant of Copyright License. Subject to the terms and conditions of
6767+ this License, each Contributor hereby grants to You a perpetual,
6868+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
6969+ copyright license to reproduce, prepare Derivative Works of,
7070+ publicly display, publicly perform, sublicense, and distribute the
7171+ Work and such Derivative Works in Source or Object form.
7272+7373+ 3. Grant of Patent License. Subject to the terms and conditions of
7474+ this License, each Contributor hereby grants to You a perpetual,
7575+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
7676+ (except as stated in this section) patent license to make, have made,
7777+ use, offer to sell, sell, import, and otherwise transfer the Work,
7878+ where such license applies only to those patent claims licensable
7979+ by such Contributor that are necessarily infringed by their
8080+ Contribution(s) alone or by combination of their Contribution(s)
8181+ with the Work to which such Contribution(s) was submitted. If You
8282+ institute patent litigation against any entity (including a
8383+ cross-claim or counterclaim in a lawsuit) alleging that the Work
8484+ or a Contribution incorporated within the Work constitutes direct
8585+ or contributory patent infringement, then any patent licenses
8686+ granted to You under this License for that Work shall terminate
8787+ as of the date such litigation is filed.
8888+8989+ 4. Redistribution. You may reproduce and distribute copies of the
9090+ Work or Derivative Works thereof in any medium, with or without
9191+ modifications, and in Source or Object form, provided that You
9292+ meet the following conditions:
9393+9494+ (a) You must give any other recipients of the Work or
9595+ Derivative Works a copy of this License; and
9696+9797+ (b) You must cause any modified files to carry prominent notices
9898+ stating that You changed the files; and
9999+100100+ (c) You must retain, in the Source form of any Derivative Works
101101+ that You distribute, all copyright, patent, trademark, and
102102+ attribution notices from the Source form of the Work,
103103+ excluding those notices that do not pertain to any part of
104104+ the Derivative Works; and
105105+106106+ (d) If the Work includes a "NOTICE" text file as part of its
107107+ distribution, then any Derivative Works that You distribute must
108108+ include a readable copy of the attribution notices contained
109109+ within such NOTICE file, excluding those notices that do not
110110+ pertain to any part of the Derivative Works, in at least one
111111+ of the following places: within a NOTICE text file distributed
112112+ as part of the Derivative Works; within the Source form or
113113+ documentation, if provided along with the Derivative Works; or,
114114+ within a display generated by the Derivative Works, if and
115115+ wherever such third-party notices normally appear. The contents
116116+ of the NOTICE file are for informational purposes only and
117117+ do not modify the License. You may add Your own attribution
118118+ notices within Derivative Works that You distribute, alongside
119119+ or as an addendum to the NOTICE text from the Work, provided
120120+ that such additional attribution notices cannot be construed
121121+ as modifying the License.
122122+123123+ You may add Your own copyright statement to Your modifications and
124124+ may provide additional or different license terms and conditions
125125+ for use, reproduction, or distribution of Your modifications, or
126126+ for any such Derivative Works as a whole, provided Your use,
127127+ reproduction, and distribution of the Work otherwise complies with
128128+ the conditions stated in this License.
129129+130130+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131131+ any Contribution intentionally submitted for inclusion in the Work
132132+ by You to the Licensor shall be under the terms and conditions of
133133+ this License, without any additional terms or conditions.
134134+ Notwithstanding the above, nothing herein shall supersede or modify
135135+ the terms of any separate license agreement you may have executed
136136+ with Licensor regarding such Contributions.
137137+138138+ 6. Trademarks. This License does not grant permission to use the trade
139139+ names, trademarks, service marks, or product names of the Licensor,
140140+ except as required for reasonable and customary use in describing the
141141+ origin of the Work and reproducing the content of the NOTICE file.
142142+143143+ 7. Disclaimer of Warranty. Unless required by applicable law or
144144+ agreed to in writing, Licensor provides the Work (and each
145145+ Contributor provides its Contributions) on an "AS IS" BASIS,
146146+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147147+ implied, including, without limitation, any warranties or conditions
148148+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149149+ PARTICULAR PURPOSE. You are solely responsible for determining the
150150+ appropriateness of using or redistributing the Work and assume any
151151+ risks associated with Your exercise of permissions under this License.
152152+153153+ 8. Limitation of Liability. In no event and under no legal theory,
154154+ whether in tort (including negligence), contract, or otherwise,
155155+ unless required by applicable law (such as deliberate and grossly
156156+ negligent acts) or agreed to in writing, shall any Contributor be
157157+ liable to You for damages, including any direct, indirect, special,
158158+ incidental, or consequential damages of any character arising as a
159159+ result of this License or out of the use or inability to use the
160160+ Work (including but not limited to damages for loss of goodwill,
161161+ work stoppage, computer failure or malfunction, or any and all
162162+ other commercial damages or losses), even if such Contributor
163163+ has been advised of the possibility of such damages.
164164+165165+ 9. Accepting Warranty or Additional Liability. While redistributing
166166+ the Work or Derivative Works thereof, You may choose to offer,
167167+ and charge a fee for, acceptance of support, warranty, indemnity,
168168+ or other liability obligations and/or rights consistent with this
169169+ License. However, in accepting such obligations, You may act only
170170+ on Your own behalf and on Your sole responsibility, not on behalf
171171+ of any other Contributor, and only if You agree to indemnify,
172172+ defend, and hold each Contributor harmless for any liability
173173+ incurred by, or claims asserted against, such Contributor by reason
174174+ of your accepting any such warranty or additional liability.
175175+176176+ END OF TERMS AND CONDITIONS
177177+178178+ APPENDIX: How to apply the Apache License to your work.
179179+180180+ To apply the Apache License to your work, attach the following
181181+ boilerplate notice, with the fields enclosed by brackets "[]"
182182+ replaced with your own identifying information. (Don't include
183183+ the brackets!) The text should be enclosed in the appropriate
184184+ comment syntax for the file format. We also recommend that a
185185+ file or class name and description of purpose be included on the
186186+ same "printed page" as the copyright notice for easier
187187+ identification within third-party archives.
188188+189189+ Copyright [yyyy] [name of copyright owner]
190190+191191+ Licensed under the Apache License, Version 2.0 (the "License");
192192+ you may not use this file except in compliance with the License.
193193+ You may obtain a copy of the License at
194194+195195+ http://www.apache.org/licenses/LICENSE-2.0
196196+197197+ Unless required by applicable law or agreed to in writing, software
198198+ distributed under the License is distributed on an "AS IS" BASIS,
199199+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200200+ See the License for the specific language governing permissions and
201201+ limitations under the License.
+21
safir-mem/LICENSE-MIT
···11+MIT License
22+33+Copyright (c) 2023 Graham Keenan
44+55+Permission is hereby granted, free of charge, to any person obtaining a copy
66+of this software and associated documentation files (the "Software"), to deal
77+in the Software without restriction, including without limitation the rights
88+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+copies of the Software, and to permit persons to whom the Software is
1010+furnished to do so, subject to the following conditions:
1111+1212+The above copyright notice and this permission notice shall be included in all
1313+copies or substantial portions of the Software.
1414+1515+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+SOFTWARE.
+49
safir-mem/README.md
···11+# Safir-mem
22+33+Simple in-memory CLI key/value store.
44+55+The in-memory version of [Safir](https://crates.io/crates/safir)!
66+77+`safir-mem` starts a service which runs on `localhost` or `127.0.0.1` on dedicated port `9876`.
88+99+This can be enabled / diabled with the `start` and `stop` commands respectively.
1010+Note that when the memcache service is disabled, ALL data contained within it is lost so use wisely.
1111+1212+In cases where you want to save the contents of the memcache, the `dump` command will allow for the contents to be saved out to disk in JSON format.
1313+This behaves as a snapshot as the contents of the cache persist after usage.
1414+1515+## Install
1616+1717+To install `safir-mem`, run `cargo install safir-mem`.
1818+1919+### Requirements
2020+2121+Using this requires that the [Rubin CLI](https://crates.io/crates/rubin-cli) be installed.
2222+2323+```bash
2424+cargo install rubin-cli
2525+```
2626+2727+## Usage
2828+2929+```bash
3030+In-memory key/value store to share information between shell sessions
3131+3232+Usage: safir-mem <COMMAND>
3333+3434+Commands:
3535+ add Add a value to the store with the given key
3636+ get Get a value from the store
3737+ rm Remove values from the store
3838+ alias Output the alias command for a key / value pair to be entered into a shell session
3939+ export Output the export command for a key / value pair to be entered into a shell session
4040+ clear Clear all keys/values from the store
4141+ start Start the memcache server
4242+ stop Stop the memcache server
4343+ dump Dump contents of memcache to disk
4444+ help Print this message or the help of the given subcommand(s)
4545+4646+Options:
4747+ -h, --help Print help
4848+ -V, --version Print version
4949+```
+85
safir-mem/src/cli.rs
···11+//! CLI for using the Safir binary
22+pub use clap::{Args, Parser, Subcommand};
33+44+/// CLI arguments for running the program
55+#[derive(Parser, Debug)]
66+#[command(author, version)]
77+#[command(propagate_version = true)]
88+#[command(about = "In-memory key/value store to share information between shell sessions", long_about = None)]
99+pub struct Cli {
1010+ /// Subcommands
1111+ #[command(subcommand)]
1212+ pub command: Commands,
1313+}
1414+1515+/// Subcommands for running the program
1616+#[derive(Subcommand, Debug, PartialEq)]
1717+pub enum Commands {
1818+ /// Add a value to the store with the given key
1919+ Add(AddArgs),
2020+2121+ /// Get a value from the store
2222+ Get(GetArgs),
2323+2424+ /// Remove values from the store
2525+ Rm(RemoveArgs),
2626+2727+ /// Output the alias command for a key / value pair to be entered into a shell session
2828+ Alias(SetArgs),
2929+3030+ /// Output the export command for a key / value pair to be entered into a shell session
3131+ Export(SetArgs),
3232+3333+ /// Clear all keys/values from the store
3434+ Clear,
3535+3636+ /// Start the memcache server
3737+ Start,
3838+3939+ /// Stop the memcache server
4040+ Stop,
4141+4242+ /// Dump contents of memcache to disk
4343+ Dump(DumpArgs),
4444+}
4545+4646+/// Arguments for adding a value to the store with a given key
4747+#[derive(Args, Debug, PartialEq)]
4848+pub struct AddArgs {
4949+ /// Name of the item to store
5050+ pub key: String,
5151+5252+ /// Value to store
5353+ pub value: String,
5454+}
5555+5656+/// Arguments for retrieving a value from the store with a given key
5757+#[derive(Args, Debug, PartialEq)]
5858+pub struct GetArgs {
5959+ /// Name of the value to retrieve from the store
6060+ ///
6161+ /// Returns nothing if the key does not exist
6262+ pub key: Option<String>,
6363+}
6464+6565+/// Arguments for removing values from the store with given keys
6666+#[derive(Args, Debug, PartialEq)]
6767+pub struct RemoveArgs {
6868+ /// Name of the keys to remove from the store
6969+ ///
7070+ /// Does nothing if the keys do not exist
7171+ pub key: Vec<String>,
7272+}
7373+7474+/// Arguments for outputting commands with a given prefix
7575+#[derive(Args, Debug, PartialEq)]
7676+pub struct SetArgs {
7777+ /// Name of the keys to display (e.g. alias / export)
7878+ pub keys: Vec<String>,
7979+}
8080+8181+#[derive(Args, Debug, PartialEq)]
8282+pub struct DumpArgs {
8383+ /// Path to save the store to
8484+ pub path: String,
8585+}
+108
safir-mem/src/main.rs
···11+mod cfg;
22+mod cli;
33+mod safir;
44+mod utils;
55+66+use cli::*;
77+88+use std::process::{Command, Stdio};
99+1010+#[tokio::main]
1111+async fn main() -> std::io::Result<()> {
1212+ let cli = Cli::parse();
1313+ let store_dir = utils::create_safir_directory().await?;
1414+ let safir_cfg = &store_dir.join("safir.cfg");
1515+ let mut cfg = utils::load_safir_config(&safir_cfg).await?;
1616+1717+ let safir_state = utils::is_safir_running(cfg.memcache_pid);
1818+ let safir_mem = safir::SafirMemcache::new(safir_state);
1919+2020+ match &cli.command {
2121+ Commands::Add(args) => safir_mem.add_entry(&args.key, &args.value).await?,
2222+ Commands::Get(args) => {
2323+ if let Some(key) = &args.key {
2424+ safir_mem.get_string(key).await?;
2525+ } else {
2626+ utils::print_header();
2727+ utils::print_output("A key is required for memcache GET command!");
2828+ }
2929+ }
3030+ Commands::Rm(args) => {
3131+ safir_mem.remove_entry(args.key.clone()).await?;
3232+ }
3333+ Commands::Alias(args) => {
3434+ safir_mem.set_commands("alias", &args.keys).await;
3535+ }
3636+ Commands::Export(args) => {
3737+ safir_mem.set_commands("export", &args.keys).await;
3838+ }
3939+ Commands::Clear => {
4040+ safir_mem.clear_entries().await?;
4141+ }
4242+ Commands::Start => {
4343+ if !utils::check_rubin_installed() {
4444+ eprintln!(
4545+ "The Rubin binary must be installed to use this feature, please install it via cargo using `cargo install rubin-cli`"
4646+ );
4747+ return Ok(());
4848+ }
4949+5050+ if let Some(pid) = cfg.memcache_pid {
5151+ println!(
5252+ "Safir memcache service is already running on 127.0.0.1:9876 - PID {}",
5353+ pid
5454+ );
5555+5656+ return Ok(());
5757+ }
5858+5959+ let child = Command::new("rubin")
6060+ .args(["server"])
6161+ .stdout(Stdio::null())
6262+ .stderr(Stdio::null())
6363+ .stdin(Stdio::null())
6464+ .spawn()
6565+ .expect("unable to spawn child process");
6666+6767+ let pid = child.id();
6868+ cfg.memcache_pid = Some(pid);
6969+ cfg.write(&safir_cfg).await?;
7070+ println!(
7171+ "Safir memcache service started at 127.0.0.1:9876 - PID {}",
7272+ pid
7373+ );
7474+ }
7575+ Commands::Stop => {
7676+ if !utils::check_rubin_installed() {
7777+ eprintln!("The Rubin binary must be installed to use this feature, please install it via cargo using `cargo install rubin-cli`");
7878+ return Ok(());
7979+ }
8080+8181+ let pid = match cfg.memcache_pid {
8282+ Some(pid) => pid,
8383+ None => {
8484+ println!("Safir memcache service does not seem to be running.");
8585+ return Ok(());
8686+ }
8787+ };
8888+8989+ if let Err(err) = utils::kill_process(pid).await {
9090+ eprintln!(
9191+ "Safir memcache service failed to stop, manual removal may be necessary - {}",
9292+ err
9393+ );
9494+ } else {
9595+ cfg.memcache_pid = None;
9696+ cfg.write(&safir_cfg).await?;
9797+ println!("Stopping Safir memcache service!");
9898+ }
9999+ }
100100+ Commands::Dump(args) => {
101101+ if let Err(e) = safir_mem.dump_store(&args.path).await {
102102+ eprintln!("unable to dump Safir memcache service: {}", e);
103103+ }
104104+ }
105105+ }
106106+107107+ Ok(())
108108+}
···11+[package]
22+name = "safir"
33+version = "0.6.0"
44+edition = "2021"
55+authors = ["Graham Keenan graham.keenan@outlook.com"]
66+license = "MIT OR Apache-2.0"
77+description = "Key/Value store to share values between different shell sessions"
88+readme = "README.md"
99+homepage = "https://github.com/Tyrannican/safir"
1010+repository = "https://github.com/Tyrannican/safir"
1111+keywords = ["cli", "terminal", "utility", "key-value", "store"]
1212+categories = ["command-line-utilities"]
1313+1414+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1515+1616+[dependencies]
1717+clap = { version = "4.2.5" , features = ["derive"] }
1818+serde = { version = "1", features = ["derive"] }
1919+serde_json = "1"
2020+dirs = "5"
2121+colored = "2"
2222+rubin = "^0.3.1"
2323+tokio = { version = "1.28.2", features = ["full"] }
+45
safir/README.md
···11+# Safir
22+33+Simple CLI key/value store.
44+55+Store key/value pairs in the terminal and retrieve them later for use like in different shell sessions.
66+77+## Install
88+99+To install `safir`, run `cargo install safir`.
1010+1111+To build from source, clone the repository and run:
1212+1313+```bash
1414+cargo build --release
1515+```
1616+1717+Then move the binary to somewhere in your `$PATH`
1818+1919+## Usage
2020+2121+When `safir` is run, it creates a store file in your `$HOME` directory (`$HOME/.safirstore/safirstore.json`).
2222+2323+Run `safir --help` for usage:
2424+2525+```bash
2626+Key/Value store to share information between shell sessions
2727+2828+Usage: safir <COMMAND>
2929+3030+Commands:
3131+ add Add a value to the store with the given key
3232+ get Get a value from the store
3333+ rm Remove values from the store
3434+ alias Output the alias command for a key / value pair to be entered into a shell session
3535+ export Output the export command for a key / value pair to be entered into a shell session
3636+ clear Clear all keys/values from the store
3737+ purge Purges the .safirstore directory, removing it and its contents
3838+ mem Start or stop the Memcache (in-memory store) service
3939+ help Print this message or the help of the given subcommand(s)
4040+4141+Options:
4242+ -h, --help Print help
4343+ -V, --version Print version
4444+```
4545+
···35353636 /// Purges the .safirstore directory, removing it and its contents
3737 Purge,
3838-3939- /// Start or stop the Memcache (in-memory store) service
4040- #[clap(subcommand)]
4141- Mem(MemArgs),
4238}
43394440/// Arguments for adding a value to the store with a given key
···7571 /// Name of the keys to display (e.g. alias / export)
7672 pub keys: Vec<String>,
7773}
7878-7979-/// Arguments for the Mem sub command
8080-#[derive(Subcommand, Debug)]
8181-pub enum MemArgs {
8282- /// Start the Safir Memcache server
8383- Start,
8484-8585- /// Stop the Safir Memcache server
8686- Stop,
8787-8888- /// Dump the Safir Memcache server to disk
8989- Dump(DumpArgs),
9090-}
9191-9292-#[derive(Args, Debug)]
9393-pub struct DumpArgs {
9494- /// Path to save the store to
9595- pub path: String,
9696-}
-153
src/main.rs
···11-mod cfg;
22-mod cli;
33-mod safir;
44-mod utils;
55-66-use cli::*;
77-88-use std::process::{Command, Stdio};
99-1010-#[tokio::main]
1111-async fn main() -> std::io::Result<()> {
1212- let cli = Cli::parse();
1313- let store_dir = utils::create_safir_directory().await?;
1414- let safir_cfg = &store_dir.join("safir.cfg");
1515-1616- let mut cfg = utils::load_safir_config(&safir_cfg).await?;
1717-1818- // Should probably only initialise when not using memcache but meh...
1919- // Easier this way
2020- let mut safir = safir::Safir::init(&store_dir).await?;
2121- let safir_mem = safir::SafirMemcache::new();
2222-2323- match &cli.command {
2424- Commands::Add(args) => {
2525- if cfg.memcache_flag {
2626- safir_mem.add_entry(&args.key, &args.value).await?
2727- } else {
2828- safir
2929- .add_entry(args.key.clone(), args.value.clone())
3030- .await?;
3131- }
3232- }
3333- Commands::Get(args) => {
3434- if cfg.memcache_flag {
3535- if let Some(key) = &args.key {
3636- safir_mem.get_string(key).await?;
3737- } else {
3838- utils::print_header();
3939- utils::print_output("A key is required for memcache GET command!");
4040- }
4141- } else if let Some(key) = &args.key {
4242- safir.get_entry(key.clone())?;
4343- } else {
4444- safir.display_all();
4545- }
4646- }
4747- Commands::Rm(args) => {
4848- if cfg.memcache_flag {
4949- safir_mem.remove_entry(args.key.clone()).await?;
5050- } else {
5151- safir.remove_entry(args.key.clone()).await?;
5252- }
5353- }
5454- Commands::Alias(args) => {
5555- if cfg.memcache_flag {
5656- safir_mem.set_commands("alias", &args.keys).await;
5757- } else {
5858- safir.set_commands("alias", &args.keys);
5959- }
6060- }
6161- Commands::Export(args) => {
6262- if cfg.memcache_flag {
6363- safir_mem.set_commands("export", &args.keys).await;
6464- } else {
6565- safir.set_commands("export", &args.keys);
6666- }
6767- }
6868- Commands::Clear => {
6969- if cfg.memcache_flag {
7070- safir_mem.clear_entries().await?;
7171- } else {
7272- safir.clear_entries().await?;
7373- }
7474- }
7575- Commands::Purge => {
7676- if !cfg.memcache_flag {
7777- safir.purge();
7878- }
7979- }
8080- Commands::Mem(args) => match args {
8181- MemArgs::Start => {
8282- if !utils::check_rubin_installed() {
8383- eprintln!(
8484- "The Rubin binary must be installed to use this feature, please install it via cargo using `cargo install rubin-cli`"
8585- );
8686- return Ok(());
8787- }
8888-8989- if let Some(pid) = cfg.memcache_pid {
9090- println!(
9191- "Safir memcache service is already running on 127.0.0.1:9876 - PID {}",
9292- pid
9393- );
9494-9595- return Ok(());
9696- }
9797-9898- let child = Command::new("rubin")
9999- .args(["server"])
100100- .stdout(Stdio::null())
101101- .stderr(Stdio::null())
102102- .stdin(Stdio::null())
103103- .spawn()
104104- .expect("unable to spawn child process");
105105-106106- let pid = child.id();
107107- cfg = cfg.pid(Some(pid)).set_memcache(true);
108108- cfg.write(&safir_cfg).await?;
109109- println!(
110110- "Safir memcache service started at 127.0.0.1:9876 - PID {}",
111111- pid
112112- );
113113- }
114114- MemArgs::Stop => {
115115- if !utils::check_rubin_installed() {
116116- eprintln!("The Rubin binary must be installed to use this feature, please install it via cargo using `cargo install rubin-cli`");
117117- return Ok(());
118118- }
119119-120120- let pid = match cfg.memcache_pid {
121121- Some(pid) => pid,
122122- None => {
123123- println!("Safir memcache service does not seem to be running.");
124124- return Ok(());
125125- }
126126- };
127127-128128- if let Err(err) = utils::kill_process(pid).await {
129129- eprintln!(
130130- "Safir memcache service failed to stop, manual removal may be necessary - {}",
131131- err
132132- );
133133- } else {
134134- cfg = cfg.pid(None).set_memcache(false);
135135- cfg.write(&safir_cfg).await?;
136136- println!("Stopping Safir memcache service!");
137137- }
138138- }
139139- MemArgs::Dump(args) => {
140140- if cfg.memcache_pid.is_none() {
141141- println!("Safir memcache service does not seem to be running");
142142- return Ok(());
143143- }
144144-145145- if let Err(e) = safir_mem.dump_store(&args.path).await {
146146- eprintln!("unable to dump Safir memcache service: {}", e);
147147- }
148148- }
149149- },
150150- }
151151-152152- Ok(())
153153-}