mod cache;
mod commands;
mod config;
mod db;
mod lookup;
mod metadata;
mod model;
mod storage;
mod ui;
use anyhow::Result;
use clap::{Parser, Subcommand};
use std::path::PathBuf;
use config::Config;
use model::Kind;
const BANNER: &str = r#"
=========================================================================
██████╗ ██████╗ ██████╗██╗ ██╗██████╗ ( (
██╔══██╗██╔═══██╗██╔════╝██║ ██║██╔══██╗ ) )
██║ ██║██║ ██║██║ ██║ ██║██████╔╝ ********
██║ ██║██║ ██║██║ ██║ ██║██╔══██╗ ████████▀▌
██████╔╝╚██████╔╝╚██████╗███████╗██║██████╔╝ ▀██████▀▀
╚═════╝ ╚═════╝ ╚═════╝╚══════╝╚═╝╚═════╝ ▀▀▀▀▀▀
A blazingly-fast ⚡ memory-safe 💾
Tag-indexed shelf for books 📚 and articles 📄, built in Rust 🦀
- CfM47 2026 -
=========================================================================
"#;
#[derive(Parser)]
#[command(
name = "doclib",
version,
about = "Tag-indexed library for books and articles",
before_help = BANNER
)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Import {
path: PathBuf,
#[arg(long)]
auto: bool,
#[arg(long, value_parser = ["book", "article"])]
kind: Option<String>,
},
Search { query: Option<String> },
Open {
#[arg(default_value = "")]
query: String,
},
Show {
#[arg(default_value = "")]
query: String,
},
Edit {
#[arg(default_value = "")]
query: String,
#[arg(long)]
lookup: bool,
},
Delete {
#[arg(default_value = "")]
query: String,
#[arg(long)]
purge: bool,
#[arg(short = 'y', long = "yes")]
assume_yes: bool,
},
List {
#[arg(long)]
tag: Option<String>,
#[arg(long, value_parser = ["book", "article"])]
kind: Option<String>,
},
Tags,
Tag {
#[arg(default_value = "")]
query: String,
},
Export {
file: Option<PathBuf>,
},
Restore {
file: PathBuf,
},
Stats,
Sync,
Purge {
#[arg(short = 'y', long = "yes")]
assume_yes: bool,
},
Cache {
#[command(subcommand)]
action: CacheAction,
},
Config {
#[arg(long, value_name = "REMOTE")]
remote: Option<String>,
#[arg(long)]
show: bool,
#[arg(long)]
path: bool,
},
}
#[derive(Subcommand)]
enum CacheAction {
Status,
Prune {
#[arg(long)]
max: Option<u64>,
},
}
pub fn now() -> String {
chrono::Utc::now().to_rfc3339()
}
fn parse_kind(value: &Option<String>) -> Result<Option<Kind>> {
match value {
Some(v) => Ok(Some(Kind::parse(v)?)),
None => Ok(None),
}
}
fn main() -> Result<()> {
let cli = Cli::parse();
let cfg = Config::load()?;
let conn = db::open(&cfg.db_path())?;
match &cli.command {
Command::Import { path, auto, kind } => {
commands::import::run(&conn, &cfg, path, *auto, parse_kind(kind)?)
}
Command::Search { query } => commands::search::run(&conn, query.as_deref()),
Command::Open { query } => commands::open::run(&conn, &cfg, query),
Command::Show { query } => commands::show::run(&conn, &cfg, query),
Command::Edit { query, lookup } => commands::edit::run(&conn, query, *lookup),
Command::Delete {
query,
purge,
assume_yes,
} => commands::delete::run(&conn, &cfg, query, *purge, *assume_yes),
Command::List { tag, kind } => {
commands::list::run(&conn, tag.as_deref(), parse_kind(kind)?)
}
Command::Tags => commands::list::tags(&conn),
Command::Tag { query } => commands::tag::run(&conn, query),
Command::Export { file } => commands::export::export(&conn, file.as_deref()),
Command::Restore { file } => commands::export::restore(&conn, &cfg, file),
Command::Stats => commands::stats::run(&conn, &cfg),
Command::Sync => commands::sync::run(&conn, &cfg),
Command::Purge { assume_yes } => commands::purge::run(&conn, &cfg, *assume_yes),
Command::Cache { action } => match action {
CacheAction::Status => commands::cache_cmd::status(&cfg),
CacheAction::Prune { max } => commands::cache_cmd::prune(&conn, &cfg, *max),
},
Command::Config { remote, show, path } => {
commands::config_cmd::run(&cfg, remote.as_deref(), *show, *path)
}
}
}