74d3296882
Persist the entry catalog to a Notion page instead of an external script. - `hel store notion:<ref>` reads the dump from stdin and writes it into a single code block on the page; `hel load notion:<ref>` prints it back. `<ref>` is a page id (UUID) or a page title (case-insensitive search). Token from HEL_NOTION_TOKEN. Networking lives in helcli (ureq + serde_json); the core lib and the wasm build are untouched. argv is dispatched before the REPL starts, so the subcommands never read ~/.helrc or prompt for a master. - `set <key> <value>`: runtime config (e.g. hel_dump, hel_notion_token) from ~/.helrc instead of env vars. The map is exported (uppercased) into spawned pipe/source children, so `set hel_notion_token ...` reaches `hel store`. Values are redacted in history. - `ls` now matches case-insensitively. - `source` echoes `source <target>`. - `save` prints a `< removed` / `> added` diff against the last load/save snapshot so removals are noticed before they persist.
84 lines
2.8 KiB
Rust
84 lines
2.8 KiB
Rust
use crate::password::{fix_password_recursion, Name, PasswordRef};
|
|
use parking_lot::ReentrantMutex;
|
|
use regex::{Captures, Regex};
|
|
use std::cell::RefCell;
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
|
|
pub type LKRef = Arc<ReentrantMutex<RefCell<LK>>>;
|
|
|
|
#[derive(Debug)]
|
|
pub struct LK {
|
|
pub db: HashMap<Name, PasswordRef>,
|
|
pub ls: HashMap<String, PasswordRef>,
|
|
pub secrets: HashMap<Name, String>,
|
|
/// Serialized dump as of the last load (`source`) or save (`dump`). Used to
|
|
/// show a `< removed` / `> added` diff on save so removals are noticed.
|
|
pub last_dump: Option<String>,
|
|
}
|
|
|
|
impl LK {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
db: HashMap::new(),
|
|
ls: HashMap::new(),
|
|
secrets: HashMap::new(),
|
|
last_dump: None,
|
|
}
|
|
}
|
|
|
|
pub fn fix_hierarchy(&self) {
|
|
lazy_static! {
|
|
static ref RE: Regex = Regex::new(r"\s*\^([!-~]+)").unwrap();
|
|
}
|
|
for db in vec![&self.db, &self.ls] {
|
|
for (_, name) in db {
|
|
let comment = name.lock().borrow().comment.clone();
|
|
match comment {
|
|
Some(comment) => {
|
|
let mut changed = false;
|
|
let new = RE
|
|
.replace(comment.as_str(), |c: &Captures| {
|
|
let folder = c[1].to_string();
|
|
match self.db.get(&folder) {
|
|
Some(entry) => {
|
|
name.lock().borrow_mut().parent = Some(entry.clone());
|
|
changed = true;
|
|
}
|
|
None => (),
|
|
}
|
|
""
|
|
})
|
|
.trim()
|
|
.to_string();
|
|
if changed && new != comment {
|
|
name.lock().borrow_mut().comment = if new.len() > 0 { Some(new) } else { None }
|
|
}
|
|
}
|
|
None => (),
|
|
}
|
|
fix_password_recursion(name.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PartialEq for LK {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
if self.db.len() != other.db.len() || self.ls.len() != other.ls.len() || self.secrets != other.secrets {
|
|
return false;
|
|
}
|
|
for (k, v) in &self.db {
|
|
if !other.db.contains_key(k) || *other.db[k].lock() != *v.lock() {
|
|
return false;
|
|
}
|
|
}
|
|
for (k, v) in &self.ls {
|
|
if !other.ls.contains_key(k) || *other.ls[k].lock() != *v.lock() {
|
|
return false;
|
|
}
|
|
}
|
|
true
|
|
}
|
|
}
|