gitfetch_rs/cli/
interactive.rs

1use anyhow::Result;
2use dialoguer::{Input, Select, theme::Theme};
3use std::fmt;
4
5/// Custom theme that matches Python gitfetch's provider selection UI
6struct GitfetchTheme;
7
8impl Theme for GitfetchTheme {
9  fn format_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
10    write!(f, "{}", prompt)
11  }
12
13  fn format_select_prompt_item(
14    &self,
15    f: &mut dyn fmt::Write,
16    text: &str,
17    active: bool,
18  ) -> fmt::Result {
19    let indicator = if active { "●" } else { "○" };
20    write!(f, "{} {}", indicator, text)
21  }
22
23  fn format_input_prompt(
24    &self,
25    f: &mut dyn fmt::Write,
26    prompt: &str,
27    default: Option<&str>,
28  ) -> fmt::Result {
29    match default {
30      Some(default) => write!(f, "{} [{}]: ", prompt, default),
31      None => write!(f, "{}: ", prompt),
32    }
33  }
34
35  fn format_input_prompt_selection(
36    &self,
37    f: &mut dyn fmt::Write,
38    prompt: &str,
39    sel: &str,
40  ) -> fmt::Result {
41    write!(f, "{}: {}", prompt, sel)
42  }
43}
44
45pub fn prompt_provider() -> Result<String> {
46  let providers = vec!["GitHub", "GitLab", "Gitea/Forgejo/Codeberg", "Sourcehut"];
47
48  println!("Choose your git provider:");
49  println!();
50
51  let selection = Select::with_theme(&GitfetchTheme)
52    .items(&providers)
53    .default(0)
54    .interact()?;
55
56  println!();
57  println!("Use ↑/↓ arrows, ● = selected, Enter to confirm");
58
59  let provider = match selection {
60    0 => "github",
61    1 => "gitlab",
62    2 => "gitea",
63    3 => "sourcehut",
64    _ => unreachable!(),
65  };
66
67  Ok(provider.to_string())
68}
69
70pub fn prompt_url(provider: &str) -> Result<String> {
71  Input::new()
72    .with_prompt(format!("Enter {} instance URL", provider))
73    .interact_text()
74    .map_err(Into::into)
75}
76
77pub fn prompt_token(provider: &str) -> Result<String> {
78  Input::with_theme(&GitfetchTheme)
79    .with_prompt(format!(
80      "Enter your {} personal access token (optional, press Enter to skip)",
81      provider
82    ))
83    .allow_empty(true)
84    .interact_text()
85    .map_err(Into::into)
86}
87
88pub fn prompt_cache_expiry() -> Result<usize> {
89  let input: String = Input::with_theme(&GitfetchTheme)
90    .with_prompt("Cache expiry in minutes (default: 15, Enter for default)")
91    .allow_empty(true)
92    .interact_text()?;
93
94  if input.is_empty() {
95    Ok(15)
96  } else {
97    match input.parse::<usize>() {
98      Ok(minutes) if minutes >= 1 => Ok(minutes),
99      _ => {
100        println!("Cache expiry must be >= 1 min. Using default: 15");
101        Ok(15)
102      }
103    }
104  }
105}