Skip to main content

bookmarks/
cli.rs

1use anyhow::Result;
2use clap::Parser;
3use std::path::PathBuf;
4
5use bookmarks_core::config::{edit_config, print_config};
6use bookmarks_core::open::open_links;
7use bookmarks_core::storage::Storage;
8use bookmarks_core::toml_storage::TomlStorage;
9
10#[derive(Parser, Debug)]
11#[command(name = "bookmarks")]
12#[command(about = "Bookmarks in your filesystem")]
13#[command(version)]
14pub struct Args {
15    /// Path to bookmarks file (overrides cwd and global)
16    #[arg(short = 'f', long = "bookmarks-file", conflicts_with = "global")]
17    pub bookmarks_file: Option<PathBuf>,
18
19    /// Use global config (~/.config/bookmarks/bookmarks.toml), ignore local
20    #[arg(short, long, conflicts_with = "bookmarks_file")]
21    pub global: bool,
22
23    /// Open active bookmarks file in $EDITOR (use -gc for global)
24    #[arg(short, long)]
25    pub config: bool,
26
27    /// Open the desktop app
28    #[cfg(feature = "app")]
29    #[arg(short = 'a', long)]
30    pub app: bool,
31
32    /// Open the webapp
33    #[cfg(feature = "webapp")]
34    #[arg(short = 'w', long)]
35    pub webapp: bool,
36
37    /// Things to open
38    pub links: Vec<String>,
39}
40
41/// Resolve which bookmarks file to use and ensure it exists:
42/// 1. --bookmarks-file flag (explicit, must exist)
43/// 2. --global flag (skip cwd, use global)
44/// 3. bookmarks.toml in cwd (local, must exist)
45/// 4. ~/.config/bookmarks/bookmarks.toml (global, auto-created)
46fn resolve_storage(bookmarks_file: Option<PathBuf>, global: bool) -> Result<TomlStorage> {
47    if let Some(path) = bookmarks_file {
48        anyhow::ensure!(
49            path.exists(),
50            "bookmarks file not found: {}",
51            path.display()
52        );
53        return Ok(TomlStorage::new(path));
54    }
55
56    if !global
57        && let Some(cwd_path) = TomlStorage::cwd_path()
58        && cwd_path.exists()
59    {
60        return Ok(TomlStorage::new(cwd_path));
61    }
62
63    let storage = TomlStorage::with_default_path()?;
64    storage.init()?;
65    Ok(storage)
66}
67
68pub fn run_cli<I, T>(args: I) -> Result<()>
69where
70    I: IntoIterator<Item = T>,
71    T: Into<std::ffi::OsString> + Clone,
72{
73    let args = Args::parse_from(args);
74
75    let storage = resolve_storage(args.bookmarks_file, args.global)?;
76
77    #[cfg(feature = "app")]
78    if args.app {
79        return bookmarks_app::run(Box::new(storage)).map_err(|e| anyhow::anyhow!("{e}"));
80    }
81
82    #[cfg(feature = "webapp")]
83    if args.webapp {
84        return bookmarks_webapp::run(Box::new(storage));
85    }
86
87    if args.config {
88        if let Some(path) = storage.path() {
89            edit_config(path)?;
90        }
91        return Ok(());
92    }
93
94    let config = storage.load()?;
95
96    if args.links.is_empty() {
97        print_config(&config);
98    } else {
99        open_links(&args.links, &config)?;
100    }
101
102    if let Some(path) = storage.path() {
103        println!(
104            "(using {}, use --bookmarks-file to override)",
105            path.display()
106        );
107    }
108
109    Ok(())
110}