From 5e70835656c88d2a843edd10bfa62b442683f5a9 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Tue, 26 Oct 2021 18:35:11 +0300 Subject: feat: add a "clab reformat" command Sponsored-by: author --- Cargo.lock | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + clab.md | 30 ++++++++++++++++++++++++++ src/main.rs | 47 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 149 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index ca7d2a8..deedaad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "ansi_term" version = "0.11.0" @@ -47,6 +49,7 @@ dependencies = [ "serde", "serde_yaml", "structopt", + "tempfile", ] [[package]] @@ -138,6 +141,12 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +[[package]] +name = "ppv-lite86" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -180,6 +189,46 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.2.6" @@ -199,6 +248,15 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "serde" version = "1.0.125" @@ -272,6 +330,20 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "textwrap" version = "0.11.0" diff --git a/Cargo.toml b/Cargo.toml index c61226c..a5efd08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,4 @@ directories-next = "2" serde = { version = "1", features = ["derive"] } serde_yaml = "0.8" structopt = "0.3" +tempfile = "3.2.0" diff --git a/clab.md b/clab.md index de09758..5893fa7 100644 --- a/clab.md +++ b/clab.md @@ -1,3 +1,4 @@ + --- title: "clab; – command line address book" author: Lars Wirzenius @@ -75,6 +76,20 @@ then stdout is exactly "clab found matches:\nalice@example.com\tAlice Atherthon\ last_checked: 2021-09 ~~~ +~~~{#address-book-reformatted .file .yaml} +--- +- name: Alice Atherthon + email: + work: alice@example.com + tags: + - co-worker + last_checked: 2021-09 +- name: Bob Bobbington + email: + personal: bob@example.com + last_checked: 2021-09 +~~~ + # Search by tag @@ -108,3 +123,18 @@ then command is successful then stdout contains "Alice Atherthon" then stdout contains "Bob" ~~~ + +# Reformat address book + +This scenario verifies that `clab` can reformat the address book in +place. + +~~~scenario +given an installed clab +given file .local/share/clab/address-book.yaml from address-book.yaml +given file reformatted.yaml from address-book-reformatted + +when I run clab reformat +then command is successful +then files .local/share/clab/address-book.yaml and reformatted.yaml match +~~~ diff --git a/src/main.rs b/src/main.rs index 2d847dd..8ea2a45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::{Path, PathBuf}; use structopt::StructOpt; +use tempfile::NamedTempFile; const APP: &str = "clab"; @@ -27,6 +28,7 @@ fn main() -> anyhow::Result<()> { Cmd::Search(x) => x.run(&opt, &book)?, Cmd::Tagged(x) => x.run(&opt, &book)?, Cmd::MuttQuery(x) => x.run(&opt, &book), + Cmd::Reformat(x) => x.run(&opt, &book)?, } Ok(()) } @@ -35,14 +37,23 @@ fn main() -> anyhow::Result<()> { #[serde(deny_unknown_fields)] struct Entry { name: String, + #[serde(skip_serializing_if = "Option::is_none")] org: Option, + #[serde(skip_serializing_if = "Option::is_none")] url: Option>, + #[serde(skip_serializing_if = "Option::is_none")] notes: Option, + #[serde(skip_serializing_if = "Option::is_none")] aliases: Option>, + #[serde(skip_serializing_if = "Option::is_none")] email: Option>, + #[serde(skip_serializing_if = "Option::is_none")] phone: Option>, + #[serde(skip_serializing_if = "Option::is_none")] irc: Option>, + #[serde(skip_serializing_if = "Option::is_none")] address: Option>, + #[serde(skip_serializing_if = "Option::is_none")] tags: Option>, last_checked: String, } @@ -77,16 +88,24 @@ fn contains(haystack: &str, needle: &str) -> bool { #[derive(std::default::Default)] struct AddressBook { + filename: PathBuf, entries: Vec, } impl AddressBook { fn load(db: &Path) -> anyhow::Result { - let mut book = Self::default(); + let mut book = Self { + filename: db.to_path_buf(), + entries: vec![], + }; book.add_from(db)?; Ok(book) } + fn filename(&self) -> &Path { + &self.filename + } + fn add_from(&mut self, filename: &Path) -> anyhow::Result<()> { let text = std::fs::read(&filename)?; let mut entries: Vec = serde_yaml::from_slice(&text)?; @@ -120,6 +139,7 @@ enum Cmd { Search(SearchCommand), Tagged(TaggedCommand), MuttQuery(MuttCommand), + Reformat(Reformat), } #[derive(Debug, StructOpt)] @@ -226,3 +246,28 @@ impl MuttCommand { entry.is_match(&self.word) } } + +#[derive(Debug, StructOpt)] +struct Reformat { + #[structopt(short, long)] + stdout: bool, +} + +impl Reformat { + fn run(&self, _opt: &Opt, book: &AddressBook) -> anyhow::Result<()> { + if self.stdout { + serde_yaml::to_writer(std::io::stdout(), book.entries())?; + } else { + let filename = book.filename(); + let dirname = match filename.parent() { + None => Path::new("/"), + Some(x) if x.display().to_string().is_empty() => Path::new("."), + Some(x) => x, + }; + let temp = NamedTempFile::new_in(dirname)?; + serde_yaml::to_writer(&temp, book.entries())?; + std::fs::rename(temp.path(), filename)?; + } + Ok(()) + } +} -- cgit v1.2.1