Skip to main content

bookmarks/
cli.rs

1use anyhow::{Context, 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_all = ["bookmarks_file", "local"])]
21    pub global: bool,
22
23    /// Use local config (./bookmarks.toml), create if missing
24    #[arg(short, long, conflicts_with_all = ["bookmarks_file", "global"])]
25    pub local: bool,
26
27    /// Open active bookmarks file in $EDITOR (use -gc for global)
28    #[arg(short, long)]
29    pub config: bool,
30
31    /// Open the desktop app
32    #[cfg(feature = "app")]
33    #[arg(short = 'a', long)]
34    pub app: bool,
35
36    /// Open the webapp
37    #[cfg(feature = "webapp")]
38    #[arg(short = 'w', long)]
39    pub webapp: bool,
40
41    /// Things to open (url names, aliases, or groups)
42    pub urls: Vec<String>,
43}
44
45/// Resolve which bookmarks file to use and ensure it exists:
46/// 1. --bookmarks-file flag (explicit, must exist)
47/// 2. --local flag (cwd, auto-created)
48/// 3. --global flag (skip cwd, use global)
49/// 4. bookmarks.toml in cwd (local, must exist)
50/// 5. ~/.config/bookmarks/bookmarks.toml (global, auto-created)
51fn resolve_storage(
52    bookmarks_file: Option<PathBuf>,
53    global: bool,
54    local: bool,
55) -> Result<TomlStorage> {
56    if let Some(path) = bookmarks_file {
57        anyhow::ensure!(
58            path.exists(),
59            "bookmarks file not found: {}",
60            path.display()
61        );
62        return Ok(TomlStorage::new(path));
63    }
64
65    if local {
66        let cwd_path = TomlStorage::cwd_path().context("failed to get current directory")?;
67        let storage = TomlStorage::new(cwd_path);
68        storage.init()?;
69        return Ok(storage);
70    }
71
72    if !global
73        && let Some(cwd_path) = TomlStorage::cwd_path()
74        && cwd_path.exists()
75    {
76        return Ok(TomlStorage::new(cwd_path));
77    }
78
79    let storage = TomlStorage::with_default_path()?;
80    storage.init()?;
81    Ok(storage)
82}
83
84pub fn run_cli<I, T>(args: I) -> Result<()>
85where
86    I: IntoIterator<Item = T>,
87    T: Into<std::ffi::OsString> + Clone,
88{
89    let args = Args::parse_from(args);
90
91    let storage = resolve_storage(args.bookmarks_file, args.global, args.local)?;
92
93    #[cfg(feature = "app")]
94    if args.app {
95        return bookmarks_app::run_app(Box::new(storage)).map_err(|e| anyhow::anyhow!("{e}"));
96    }
97
98    #[cfg(feature = "webapp")]
99    if args.webapp {
100        return bookmarks_webapp::run_webapp(Box::new(storage));
101    }
102
103    if args.config {
104        match storage.path() {
105            Some(path) => edit_config(path)?,
106            None => anyhow::bail!("no bookmarks file found to edit"),
107        }
108        return Ok(());
109    }
110
111    let config = storage.load()?;
112
113    if args.urls.is_empty() {
114        print_config(&config);
115    } else {
116        open_links(&args.urls, &config)?;
117    }
118
119    if let Some(path) = storage.path() {
120        println!(
121            "(using {}, use --bookmarks-file to override)",
122            path.display()
123        );
124    }
125
126    Ok(())
127}