irongit

The dotfiles manager.

refactor: use function to get the dothub path

huncholanehuncholaneauthored
parent 52d2381commit 9a38abf9c2d1d443cfe13adc9f5de674ff7bd28aBrowse files

1 file changed, +63 -16

+63-16src/main.rs
@@ -10,16 +10,21 @@ use std::time::Duration;
1010 use std::fs;
1111 use std::io;
1212 use std::io::Write;
13-use std::path::Path;
13+use std::path::{Path, PathBuf};
1414 use std::process::Command;
1515
16-const DOTHUB_DIR: &str = "/usr/local/share/dothub";
16+const DEFAULT_DOTHUB_PATH: &str = ".local/share/dothub";
1717 const DEFAULT_HUB_URL: &str =
18- "https://raw.githubusercontent.com/huncholane/dothub/refs/heads/main/hub.yml";
18+ "https://raw.githubusercontent.com/huncholane/dothub/main/hub.yml";
1919 const GH_TOKEN_HELP_URL: &str = "https://github.com/settings/personal-access-tokens";
2020
2121 #[derive(Parser)]
22-#[command(name = "dothub", about = "Manage dotfile repos and links", version)]
22+#[command(
23+ name = "dothub",
24+ about = "Manage dotfile repos and links",
25+ version,
26+ after_help = "Environment variables:\n DOTHUB_DIR Override the store directory (default: XDG data dir, e.g. ~/.local/share/dothub)\n GITHUB_TOKEN GitHub token to speed up star fetching via GraphQL (optional)"
27+)]
2328 struct Cli {
2429 /// Optional filter: types to include (e.g. nvim, tmux). Comma-separated or space-separated.
2530 #[arg(value_name = "TYPE", num_args = 0.., value_delimiter = ',')]
@@ -44,6 +49,8 @@ enum Commands {
4449 Active,
4550 /// List repositories installed in the dothub store
4651 List,
52+ /// Remove a repository from the dothub store
53+ Remove(RemoveArgs),
4754 /// Generate shell completions to stdout (bash|zsh|fish|powershell|elvish)
4855 Completions { shell: Shell },
4956 }
@@ -64,6 +71,12 @@ struct LinkArgs {
6471 target: String,
6572 }
6673
74+#[derive(Args)]
75+struct RemoveArgs {
76+ /// Repository name stored under dothub to remove
77+ name: String,
78+}
79+
6780 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
6881 enum Shell {
6982 Bash,
@@ -82,16 +95,31 @@ fn main() -> Result<()> {
8295 Some(Commands::Update) => cmd_update(),
8396 Some(Commands::Active) => cmd_active(),
8497 Some(Commands::List) => cmd_list(),
98+ Some(Commands::Remove(args)) => cmd_remove(&args.name),
8599 Some(Commands::Completions { shell }) => cmd_completions(shell),
86100 None => cmd_hub(cli.types, cli.url),
87101 }
88102 }
89103
104+fn dothub_dir() -> PathBuf {
105+ if let Ok(p) = env::var("DOTHUB_DIR") {
106+ return PathBuf::from(p);
107+ }
108+ if let Some(mut data) = dirs::data_dir() {
109+ data.push("dothub");
110+ return data;
111+ }
112+ if let Some(mut home) = dirs::home_dir() {
113+ home.push(DEFAULT_DOTHUB_PATH);
114+ return home;
115+ }
116+ PathBuf::from(".dothub")
117+}
118+
90119 fn ensure_store_dir() -> Result<()> {
91- let path = Path::new(DOTHUB_DIR);
120+ let path = dothub_dir();
92121 if !path.exists() {
93- fs::create_dir_all(path)
94- .with_context(|| format!("Failed creating {} (need sudo?)", DOTHUB_DIR))?;
122+ fs::create_dir_all(&path).with_context(|| format!("Failed creating {}", path.display()))?;
95123 }
96124 Ok(())
97125 }
@@ -110,7 +138,7 @@ fn cmd_install(repo: &str) -> Result<()> {
110138 bail!("Could not infer repository name from URL: {}", repo);
111139 }
112140
113- let dest = Path::new(DOTHUB_DIR).join(&name);
141+ let dest = dothub_dir().join(&name);
114142 if dest.exists() {
115143 println!("Repo already exists: {}", dest.display());
116144 return Ok(());
@@ -136,7 +164,7 @@ fn cmd_install(repo: &str) -> Result<()> {
136164 }
137165
138166 fn cmd_link(name: &str, target_name: &str) -> Result<()> {
139- let source = Path::new(DOTHUB_DIR).join(name);
167+ let source = dothub_dir().join(name);
140168 if !source.exists() {
141169 bail!("Source repo not found: {}", source.display());
142170 }
@@ -198,11 +226,11 @@ fn cmd_update() -> Result<()> {
198226 bail!("git is not installed or not found in PATH");
199227 }
200228
201- let root = Path::new(DOTHUB_DIR);
229+ let root = dothub_dir();
202230 let mut updated = 0usize;
203231 let mut skipped = 0usize;
204232
205- for entry in fs::read_dir(root).with_context(|| format!("Reading {}", DOTHUB_DIR))? {
233+ for entry in fs::read_dir(&root).with_context(|| format!("Reading {}", root.display()))? {
206234 let entry = entry?;
207235 let path = entry.path();
208236 if !path.is_dir() {
@@ -306,7 +334,8 @@ fn cmd_active() -> Result<()> {
306334
307335 let resolved = abs_target.canonicalize().unwrap_or(abs_target.clone());
308336
309- if resolved.starts_with(DOTHUB_DIR) {
337+ let store = dothub_dir();
338+ if resolved.starts_with(&store) {
310339 let name = path
311340 .file_name()
312341 .and_then(|s| s.to_str())
@@ -328,9 +357,9 @@ fn cmd_active() -> Result<()> {
328357
329358 fn cmd_list() -> Result<()> {
330359 ensure_store_dir()?;
331- let root = Path::new(DOTHUB_DIR);
360+ let root = dothub_dir();
332361 let mut repos: Vec<String> = Vec::new();
333- for entry in fs::read_dir(root).with_context(|| format!("Reading {}", DOTHUB_DIR))? {
362+ for entry in fs::read_dir(&root).with_context(|| format!("Reading {}", root.display()))? {
334363 let entry = entry?;
335364 let path = entry.path();
336365 if !path.is_dir() {
@@ -344,7 +373,7 @@ fn cmd_list() -> Result<()> {
344373 }
345374 repos.sort();
346375 if repos.is_empty() {
347- println!("No repositories installed in {}.", DOTHUB_DIR);
376+ println!("No repositories installed in {}.", root.display());
348377 } else {
349378 for r in repos {
350379 println!("{}", r);
@@ -353,6 +382,24 @@ fn cmd_list() -> Result<()> {
353382 Ok(())
354383 }
355384
385+fn cmd_remove(name: &str) -> Result<()> {
386+ ensure_store_dir()?;
387+ let path = dothub_dir().join(name);
388+ if !path.exists() {
389+ bail!("Repository not found: {}", path.display());
390+ }
391+ let md = fs::symlink_metadata(&path).with_context(|| format!("stat {}", path.display()))?;
392+ if md.is_dir() {
393+ fs::remove_dir_all(&path)
394+ .with_context(|| format!("Removing directory {}", path.display()))?;
395+ } else {
396+ fs::remove_file(&path)
397+ .with_context(|| format!("Removing file {}", path.display()))?;
398+ }
399+ println!("Removed {}", name);
400+ Ok(())
401+}
402+
356403 #[derive(Debug, serde::Deserialize)]
357404 #[serde(untagged)]
358405 enum FlexEntry {
@@ -436,7 +483,7 @@ fn cmd_hub(types: Vec<String>, url: Option<String>) -> Result<()> {
436483 for (idx, (_ty, link, stars)) in detailed.into_iter().enumerate() {
437484 let rank = (idx + 1).to_string();
438485 let name = derive_repo_name(&link);
439- let installed = Path::new(DOTHUB_DIR).join(&name).exists();
486+ let installed = dothub_dir().join(&name).exists();
440487 let installed_str = if installed { "y" } else { "n" };
441488 table.add_row(vec![rank, stars.to_string(), installed_str.to_string(), link]);
442489 }