gitee_cli_rs/cmd/
release.rs1use std::io::Write;
2use std::path::Path;
3
4use super::{confirm, Ctx};
5use crate::api::releases::{CreateRelease, EditRelease};
6use crate::cli::ReleaseCmd;
7use crate::error::Result;
8use crate::out;
9
10pub fn execute(ctx: &Ctx, cmd: ReleaseCmd) -> Result<()> {
11 match cmd {
12 ReleaseCmd::List { limit } => {
13 let repo = ctx.repo()?;
14 let items = ctx.client.releases(repo).list(limit.limit)?;
15 let mut out = std::io::stdout().lock();
16 ctx.out
17 .render(&mut out, &items, |w| out::release_table(w, &items))?;
18 }
19 ReleaseCmd::View { tag, web } => {
20 let repo = ctx.repo()?;
21 if web {
22 let url = crate::web::release_url(&ctx.host, repo, &tag);
23 return crate::web::open_or_print(&url);
24 }
25 let release = ctx.client.releases(repo).get_by_tag(&tag)?;
26 let mut out = std::io::stdout().lock();
27 ctx.out
28 .render(&mut out, &release, |w| out::one_release(w, &release))?;
29 }
30 ReleaseCmd::Create {
31 tag,
32 name,
33 notes,
34 target,
35 prerelease,
36 } => {
37 let repo = ctx.repo()?;
38 let req = CreateRelease {
39 tag: &tag,
40 name: name.as_deref(),
41 notes: notes.as_deref(),
42 target: target.as_deref(),
43 prerelease,
44 };
45 let release = ctx.client.releases(repo).create(&req)?;
46 let mut out = std::io::stdout().lock();
47 ctx.out
48 .render(&mut out, &release, |w| out::one_release(w, &release))?;
49 }
50 ReleaseCmd::Upload { tag, files } => {
51 let repo = ctx.repo()?;
52 let releases = ctx.client.releases(repo);
53 let mut out = std::io::stdout().lock();
54 for file_path in files {
55 let asset = releases.upload(&tag, &file_path)?;
56 writeln!(out, "{}", asset.name)?;
57 }
58 }
59 ReleaseCmd::Download { tag, dir, pattern } => {
60 let repo = ctx.repo()?;
61 let release = ctx.client.releases(repo).get_by_tag(&tag)?;
62 let assets = release.assets.unwrap_or_default();
63 let matching: Vec<_> = assets
64 .iter()
65 .filter(|a| {
66 pattern
67 .as_deref()
68 .is_none_or(|p| glob_match(p, &a.name))
69 })
70 .collect();
71 let mut out = std::io::stdout().lock();
72 if matching.is_empty() {
73 if pattern.is_some() {
74 writeln!(out, "No assets match pattern")?;
75 } else {
76 writeln!(out, "No release assets")?;
77 }
78 return Ok(());
79 }
80 std::fs::create_dir_all(&dir)?;
81 for asset in matching {
82 let bytes = ctx.client.get_bytes(&asset.browser_download_url)?;
83 let path = Path::new(&dir).join(&asset.name);
84 std::fs::write(&path, &bytes)?;
85 writeln!(out, "Saved {} ({} bytes)", path.display(), bytes.len())?;
86 }
87 }
88 ReleaseCmd::Edit {
89 tag,
90 name,
91 notes,
92 prerelease,
93 } => {
94 let repo = ctx.repo()?;
95 let req = EditRelease {
96 name: name.as_deref(),
97 notes: notes.as_deref(),
98 prerelease: prerelease.then_some(true),
99 };
100 let release = ctx.client.releases(repo).edit(&tag, &req)?;
101 let mut out = std::io::stdout().lock();
102 ctx.out
103 .render(&mut out, &release, |w| out::one_release(w, &release))?;
104 }
105 ReleaseCmd::Delete { tag, yes } => {
106 confirm(&format!("Delete release {tag}"), yes)?;
107 let repo = ctx.repo()?;
108 ctx.client.releases(repo).delete(&tag)?;
109 let mut out = std::io::stdout().lock();
110 writeln!(out, "Deleted release {tag}")?;
111 }
112 }
113 Ok(())
114}
115
116pub fn glob_match(pattern: &str, name: &str) -> bool {
119 let p: Vec<char> = pattern.chars().collect();
120 let n: Vec<char> = name.chars().collect();
121 glob_at(&p, 0, &n, 0)
122}
123
124fn glob_at(p: &[char], pi: usize, n: &[char], ni: usize) -> bool {
125 if pi == p.len() {
126 return ni == n.len();
127 }
128 if p[pi] == '*' {
129 if pi + 1 == p.len() {
130 return true;
131 }
132 for i in ni..=n.len() {
133 if glob_at(p, pi + 1, n, i) {
134 return true;
135 }
136 }
137 return false;
138 }
139 if ni == n.len() {
140 return false;
141 }
142 if p[pi] == '?' {
143 return glob_at(p, pi + 1, n, ni + 1);
144 }
145 if p[pi] != n[ni] {
146 return false;
147 }
148 glob_at(p, pi + 1, n, ni + 1)
149}
150
151#[cfg(test)]
152mod glob_tests {
153 use super::glob_match;
154
155 #[test]
156 fn star_matches_empty() {
157 assert!(glob_match("*", ""));
158 assert!(glob_match("*.txt", "a.txt"));
159 assert!(glob_match("pre*post", "prepost"));
160 }
161
162 #[test]
163 fn star_matches_run() {
164 assert!(glob_match("*.tar.xz", "gitee-linux-amd64.tar.xz"));
165 assert!(!glob_match("*.tar.xz", "gitee-darwin-arm64.zip"));
166 }
167
168 #[test]
169 fn literal_must_match() {
170 assert!(glob_match("exact", "exact"));
171 assert!(!glob_match("exact", "exactx"));
172 }
173
174 #[test]
175 fn glob_match_question_mark_matches_one_char() {
176 assert!(glob_match("v?.txt", "v1.txt"));
177 assert!(!glob_match("v?.txt", "v12.txt"));
178 assert!(!glob_match("v?.txt", "v.txt"));
179 assert!(glob_match("?.txt", "a.txt"));
180 }
181}