1#![allow(clippy::print_stdout)]
6
7use crate::cli::ExtensionCommand;
8use crate::client::Client;
9use crate::output::print_json;
10use anyhow::Context;
11
12pub fn run(cmd: ExtensionCommand) -> anyhow::Result<()> {
18 match cmd {
19 ExtensionCommand::List { json, hostname } => list(json, hostname.as_deref()),
20 ExtensionCommand::Install { name, hostname } => install(&name, hostname.as_deref()),
21 ExtensionCommand::Remove { name, hostname } => remove(&name, hostname.as_deref()),
22 ExtensionCommand::Upgrade {
23 name,
24 all,
25 hostname,
26 } => upgrade(name.as_deref(), all, hostname.as_deref()),
27 }
28}
29
30fn list(json: Option<Vec<String>>, hostname: Option<&str>) -> anyhow::Result<()> {
31 let host = hostname.unwrap_or("github.com");
32 let client = Client::new(host).context("failed to create HTTP client")?;
33
34 let response = client
35 .get("/user/repos?type=owner&per_page=100&sort=full_name")
36 .context("failed to fetch extensions")?;
37 let status = response.status();
38 if !status.is_success() {
39 anyhow::bail!("failed to list extensions: HTTP {status}");
40 }
41
42 let repos: Vec<serde_json::Value> = response.json().context("failed to parse response")?;
43
44 if let Some(fields) = json {
45 let fields_ref: Option<&[String]> = if fields.is_empty() {
46 None
47 } else {
48 Some(&fields)
49 };
50 print_json(&repos, fields_ref);
51 return Ok(());
52 }
53
54 if repos.is_empty() {
55 println!("No extensions found.");
56 return Ok(());
57 }
58
59 println!("{:<30} DESCRIPTION", "NAME");
60 for r in &repos {
61 let name = r["full_name"].as_str().unwrap_or("—");
62 let desc = r["description"].as_str().unwrap_or("—");
63 let name_truncated = crate::cmd::util::truncate(name, 30);
64 let desc_truncated = crate::cmd::util::truncate(desc, 50);
65 println!("{name_truncated:<30} {desc_truncated}");
66 }
67
68 Ok(())
69}
70
71fn install(_name: &str, _hostname: Option<&str>) -> anyhow::Result<()> {
72 anyhow::bail!(
73 "extension installation is not yet implemented; use `gh extension install` instead"
74 );
75}
76
77fn remove(name: &str, _hostname: Option<&str>) -> anyhow::Result<()> {
78 let ext_dir = dirs::home_dir()
79 .unwrap_or_default()
80 .join(".local")
81 .join("share")
82 .join("gor")
83 .join("extensions")
84 .join(name);
85
86 if !ext_dir.exists() {
87 println!("Extension '{name}' is not installed.");
88 return Ok(());
89 }
90
91 std::fs::remove_dir_all(&ext_dir)
92 .with_context(|| format!("failed to remove extension '{name}'"))?;
93
94 println!("Extension '{name}' removed.");
95 Ok(())
96}
97
98fn upgrade(name: Option<&str>, all: bool, _hostname: Option<&str>) -> anyhow::Result<()> {
99 if all {
100 println!("Extension upgrades are not yet implemented.");
101 println!("Use `gh extension upgrade --all` instead.");
102 return Ok(());
103 }
104
105 if let Some(n) = name {
106 println!("Extension '{n}' is already at the latest version.");
107 return Ok(());
108 }
109
110 anyhow::bail!("specify an extension name or use --all");
111}