1use std::env;
2use std::fs;
3use std::path::PathBuf;
4
5use anyhow::{Context, Result};
6use clap::CommandFactory;
7
8use crate::cli::Cli;
9use crate::prompt::confirm;
10
11const COMPLETION_MARKER: &str = "# added by git-stk setup";
13
14pub fn setup(yes: bool, refresh: bool) -> Result<()> {
15 if refresh {
16 return install_man_page();
21 }
22
23 install_man_page()?;
24 wire_completions(yes)?;
25 Ok(())
26}
27
28fn install_man_page() -> Result<()> {
31 if cfg!(windows) {
32 return Ok(());
33 }
34
35 let dir = man_dir()?;
36 fs::create_dir_all(&dir).with_context(|| format!("failed to create {}", dir.display()))?;
37
38 let mut buffer = Vec::new();
39 clap_mangen::Man::new(Cli::command())
40 .render(&mut buffer)
41 .context("failed to render man page")?;
42
43 let path = dir.join("git-stk.1");
44 fs::write(&path, buffer).with_context(|| format!("failed to write {}", path.display()))?;
45 println!("installed man page to {}", path.display());
46 Ok(())
47}
48
49fn man_dir() -> Result<PathBuf> {
50 let data_home = env::var_os("XDG_DATA_HOME")
51 .map(PathBuf::from)
52 .or_else(|| env::var_os("HOME").map(|home| PathBuf::from(home).join(".local/share")))
53 .context("cannot locate a data directory; set HOME or XDG_DATA_HOME")?;
54 Ok(data_home.join("man/man1"))
55}
56
57fn wire_completions(yes: bool) -> Result<()> {
59 let Some((shell, rc_path, line)) = completion_target()? else {
60 println!("could not detect a supported shell from $SHELL");
61 println!("see the README for manual completion setup");
62 return Ok(());
63 };
64
65 let existing = match fs::read_to_string(&rc_path) {
66 Ok(contents) => contents,
67 Err(error) if error.kind() == std::io::ErrorKind::NotFound => String::new(),
68 Err(error) => {
69 return Err(error).with_context(|| format!("failed to read {}", rc_path.display()));
70 }
71 };
72
73 if existing.contains(COMPLETION_MARKER) || existing.contains("git stk completions") {
74 println!(
75 "{shell} completions already configured in {}",
76 rc_path.display()
77 );
78 return Ok(());
79 }
80
81 if !yes
82 && !confirm(&format!(
83 "append completion setup to {}? [y/N] ",
84 rc_path.display()
85 ))?
86 {
87 println!("skipped completion setup");
88 println!("to configure manually, add this to {}:", rc_path.display());
89 println!(" {line}");
90 return Ok(());
91 }
92
93 let mut updated = existing;
94 if !updated.is_empty() && !updated.ends_with('\n') {
95 updated.push('\n');
96 }
97 updated.push_str(&format!("\n{COMPLETION_MARKER}\n{line}\n"));
98 fs::write(&rc_path, updated)
99 .with_context(|| format!("failed to write {}", rc_path.display()))?;
100 println!("added {shell} completion setup to {}", rc_path.display());
101 Ok(())
102}
103
104fn completion_target() -> Result<Option<(&'static str, PathBuf, &'static str)>> {
106 let shell = env::var("SHELL").unwrap_or_default();
107 let shell = shell.rsplit('/').next().unwrap_or_default();
108
109 let home = env::var_os("HOME")
110 .map(PathBuf::from)
111 .context("cannot locate home directory; set HOME")?;
112
113 let target = match shell {
114 "bash" => Some((
115 "bash",
116 home.join(".bashrc"),
117 "source <(git stk completions bash)",
118 )),
119 "zsh" => Some((
120 "zsh",
121 home.join(".zshrc"),
122 "source <(git stk completions zsh)",
123 )),
124 "fish" => Some((
125 "fish",
126 home.join(".config/fish/config.fish"),
127 "git stk completions fish | source",
128 )),
129 _ => None,
130 };
131 Ok(target)
132}