diff options
author | Lars Wirzenius <liw@liw.fi> | 2021-10-26 15:36:09 +0000 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2021-10-26 15:36:09 +0000 |
commit | 032d2d975db7fefa45341c1c8d90975d054d379f (patch) | |
tree | b0366a47221c30d239543a37b7200e8312e98c98 | |
parent | acae9764fd617a57858cde5c764df2b98d3a0179 (diff) | |
parent | 5e70835656c88d2a843edd10bfa62b442683f5a9 (diff) | |
download | clab-032d2d975db7fefa45341c1c8d90975d054d379f.tar.gz |
Merge branch 'reformat' into 'main'
feat: add a "clab reformat" command
Closes #8
See merge request larswirzenius/clab!7
-rw-r--r-- | Cargo.lock | 72 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | clab.md | 30 | ||||
-rw-r--r-- | src/main.rs | 47 |
4 files changed, 149 insertions, 1 deletions
@@ -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]] @@ -139,6 +142,12 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -181,6 +190,46 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -200,6 +249,15 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -273,6 +331,20 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -12,3 +12,4 @@ directories-next = "2" serde = { version = "1", features = ["derive"] } serde_yaml = "0.8" structopt = "0.3" +tempfile = "3.2.0" @@ -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<String>, + #[serde(skip_serializing_if = "Option::is_none")] url: Option<Vec<String>>, + #[serde(skip_serializing_if = "Option::is_none")] notes: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] aliases: Option<Vec<String>>, + #[serde(skip_serializing_if = "Option::is_none")] email: Option<HashMap<String, String>>, + #[serde(skip_serializing_if = "Option::is_none")] phone: Option<HashMap<String, String>>, + #[serde(skip_serializing_if = "Option::is_none")] irc: Option<HashMap<String, String>>, + #[serde(skip_serializing_if = "Option::is_none")] address: Option<HashMap<String, String>>, + #[serde(skip_serializing_if = "Option::is_none")] tags: Option<Vec<String>>, last_checked: String, } @@ -77,16 +88,24 @@ fn contains(haystack: &str, needle: &str) -> bool { #[derive(std::default::Default)] struct AddressBook { + filename: PathBuf, entries: Vec<Entry>, } impl AddressBook { fn load(db: &Path) -> anyhow::Result<Self> { - 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<Entry> = 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(()) + } +} |