1use std::cmp::Ordering;
2use std::collections::HashMap;
3use std::fmt::{Display, Formatter};
4use std::path::{Path, PathBuf};
5use std::str::FromStr;
6use std::{env, fmt};
7
8use anyhow::anyhow;
9use regex::Regex;
10use semver::Version;
11use serde::{Deserialize, Serialize};
12
13use crate::model::release::{ReleaseKind, SortModelTrait};
14use crate::str::VersionCompareTrait;
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Package {
18 pub name: String,
19
20 #[serde(default)]
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub version: Option<String>,
23
24 #[serde(default)]
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub description: Option<String>,
27
28 pub source: PackageSource,
29
30 #[serde(default)]
31 pub targets: Vec<PackageTargetType>,
32
33 #[serde(skip)]
34 #[serde(with = "serde_yaml::with::singleton_map")]
35 pub detail: Option<PackageDetailType>,
36
37 #[serde(skip)]
38 #[serde(with = "serde_yaml::with::singleton_map")]
39 pub release_kind: Option<ReleaseKind>,
40}
41
42impl Default for Package {
43 fn default() -> Self {
44 Self {
45 name: "".to_string(),
46 version: None,
47 description: None,
48 source: PackageSource::Github {
49 owner: "".to_string(),
50 repo: "".to_string(),
51 },
52 targets: default_targets(),
53 detail: None,
54 release_kind: None,
55 }
56 }
57}
58
59unsafe impl Send for Package {}
60
61unsafe impl Sync for Package {}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct PackageSummary {
65 pub name: String,
66 pub description: Option<String>,
67 pub source: Option<String>,
68 pub version: Option<String>,
69 pub kind: Option<ReleaseKind>,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub enum PackageSource {
74 Github { owner: String, repo: String },
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub enum PackageDetailType {
79 Github { package: GithubPackage },
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub enum PackageTargetType {
84 LinuxAmd64(PackageManagement),
85 LinuxArm64(PackageManagement),
86 LinuxArm(PackageManagement),
87 MacOSAmd64(PackageManagement),
88 MacOSArm64(PackageManagement),
89 WindowsAmd64(PackageManagement),
90 Default(PackageManagement),
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize, Default)]
94pub struct PackageManagement {
95 pub artifact_templates: Vec<String>,
97
98 #[serde(skip_serializing_if = "Option::is_none")]
99 pub executable_mappings: Option<HashMap<String, String>>,
100
101 #[serde(skip_serializing_if = "Option::is_none")]
102 pub tag_version_regex_template: Option<String>,
103
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub scan_dirs: Option<Vec<String>>,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct GithubPackage {
111 pub url: String,
112 pub html_url: String,
113 pub assets_url: String,
114 pub upload_url: String,
115 pub tarball_url: String,
116 pub zipball_url: String,
117 pub id: u64,
118 pub tag_name: String,
119 pub target_commitish: String,
120 pub name: String,
121
122 #[serde(skip_deserializing)]
123 #[serde(skip_serializing)]
124 pub body: String,
125
126 pub draft: bool,
127 pub prerelease: bool,
128 pub created_at: String,
129 pub published_at: String,
130 pub assets: Vec<GithubAsset>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct GithubAsset {
135 pub url: String,
136 pub browser_download_url: String,
137 pub id: u64,
138 pub name: String,
139 pub label: Option<String>,
140 pub state: String,
141 pub content_type: String,
142 pub size: u64,
143 pub download_count: u64,
144 pub created_at: String,
145 pub updated_at: String,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct PackageIndex {
150 pub name: String,
151 pub owner: String,
152 pub source: String,
153}
154
155impl PackageSource {
156 pub fn url(&self) -> String {
157 match self {
158 PackageSource::Github { owner, repo } => {
159 format!("https://github.com/{}/{}", owner, repo)
160 }
161 }
162 }
163
164 pub fn owner(&self) -> String {
165 match self {
166 PackageSource::Github { owner, repo: _ } => owner.to_string(),
167 }
168 }
169}
170
171impl Package {
172 pub fn target(&self) -> anyhow::Result<PackageManagement> {
173 let os = env::consts::OS;
174 let arch = env::consts::ARCH;
175
176 let default_pkg_mgmt: Option<_> = self.targets.iter().find_map(|it| match it {
177 PackageTargetType::Default(m) => Some(m.clone()),
178 _ => None,
179 });
180
181 self.get_package_management(os, arch, default_pkg_mgmt)
182 .ok_or(anyhow!("Unsupported OS {} or ARCH {}", os, arch))
183 }
184
185 fn get_package_management(
186 &self,
187 os: &str,
188 arch: &str,
189 default_pkg_mgmt: Option<PackageManagement>,
190 ) -> Option<PackageManagement> {
191 match os {
192 "linux" => match arch {
193 "x86_64" => self.targets.iter().find_map(|it| match it {
194 PackageTargetType::LinuxAmd64(m) => Some(m.clone()),
195 _ => default_pkg_mgmt.clone(),
196 }),
197 "aarch64" => self.targets.iter().find_map(|it| match it {
198 PackageTargetType::LinuxArm64(m) => Some(m.clone()),
199 _ => default_pkg_mgmt.clone(),
200 }),
201 _ => None,
202 },
203 "macos" => match arch {
204 "x86_64" => self.targets.iter().find_map(|it| match it {
205 PackageTargetType::MacOSAmd64(m) => Some(m.clone()),
206 _ => default_pkg_mgmt.clone(),
207 }),
208 "aarch64" => self.targets.iter().find_map(|it| match it {
209 PackageTargetType::MacOSArm64(m) => Some(m.clone()),
210 _ => default_pkg_mgmt.clone(),
211 }),
212 _ => None,
213 },
214 "windows" => match arch {
215 "x86_64" => self.targets.iter().find_map(|it| match it {
216 PackageTargetType::WindowsAmd64(m) => Some(m.clone()),
217 _ => default_pkg_mgmt.clone(),
218 }),
219 _ => None,
220 },
221 _ => None,
222 }
223 }
224
225 pub fn parse_version_from_tag_name(&self, tag_name: &String) -> anyhow::Result<String> {
226 let mut version = tag_name.clone();
227
228 if let Some(ref template) = self.target()?.tag_version_regex_template {
229 let regex = Regex::new(&template.to_string())?;
230
231 if let Some(capture) = regex.captures(tag_name) {
232 if let Some(m) = capture.get(1) {
233 version = m.as_str().to_string();
234 } else {
235 return Err(anyhow!(
236 "Failed to capture the version from {} via tag_version_regex_template {}",
237 tag_name,
238 template
239 ));
240 }
241 }
242
243 if Version::parse(version.trim_start_matches("v")).is_err() {
244 return Err(anyhow!(
245 "Failed to parse the version {} from tag_name {}",
246 version,
247 tag_name
248 ));
249 }
250 }
251
252 Ok(version)
253 }
254
255 pub fn get_scan_dirs(&self, pkg_dir: &Path) -> anyhow::Result<Vec<PathBuf>> {
256 let mut scan_dirs = vec![];
257
258 if let Some(extra_scan_dirs) = self.target()?.scan_dirs {
259 let mut extra_scan_dirs: Vec<PathBuf> = extra_scan_dirs
260 .into_iter()
261 .map(|x| {
262 pkg_dir.join(x.replace(
263 "{version}",
264 self.version.as_ref().unwrap().trim_start_matches("v"),
265 ))
266 })
267 .collect();
268 scan_dirs.append(&mut extra_scan_dirs);
269 }
270
271 Ok(scan_dirs)
272 }
273}
274
275impl From<octocrab::models::repos::Release> for GithubPackage {
276 fn from(r: octocrab::models::repos::Release) -> Self {
277 Self {
278 url: r.url.into(),
279 html_url: r.html_url.into(),
280 assets_url: r.assets_url.into(),
281 upload_url: r.upload_url,
282 tarball_url: r.tarball_url.map_or("".into(), |x| x.into()),
283 zipball_url: r.zipball_url.map_or("".into(), |x| x.into()),
284 id: *r.id,
285 tag_name: r.tag_name,
286 target_commitish: r.target_commitish,
287 name: r.name.unwrap_or("".into()),
288 body: r.body.unwrap_or("".into()),
289 draft: r.draft,
290 prerelease: r.prerelease,
291 created_at: r.created_at.map_or("".into(), |x| x.to_string()),
292 published_at: r.published_at.map_or("".into(), |x| x.to_string()),
293 assets: r.assets.into_iter().map(GithubAsset::from).collect(),
294 }
295 }
296}
297
298impl From<octocrab::models::repos::Asset> for GithubAsset {
299 fn from(a: octocrab::models::repos::Asset) -> Self {
300 GithubAsset {
301 url: a.url.to_string(),
302 browser_download_url: a.browser_download_url.to_string(),
303 id: *a.id,
304 name: a.name,
305 label: a.label,
306 state: a.state,
307 content_type: a.content_type,
308 size: a.size as u64,
309 download_count: a.download_count as u64,
310 created_at: a.created_at.to_string(),
311 updated_at: a.updated_at.to_string(),
312 }
313 }
314}
315
316impl Display for Package {
317 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
318 write!(f, "{}", &self.name)
319 }
320}
321
322impl Display for PackageSource {
323 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
324 match self {
325 PackageSource::Github { .. } => write!(f, "github"),
326 }
327 }
328}
329
330impl PackageSummary {
331 pub fn compare(&self, pkg: &PackageSummary) -> anyhow::Result<Ordering> {
332 let v1 = Version::from_str(self.version.clone().unwrap().trim_start_matches("v"))?;
333 let v2 = Version::from_str(pkg.version.clone().unwrap().trim_start_matches("v"))?;
334
335 Ok(v1.cmp(&v2))
336 }
337}
338
339impl From<Package> for PackageSummary {
340 fn from(p: Package) -> Self {
341 PackageSummary {
342 name: p.name.clone(),
343 description: p.description.clone(),
344 source: Some(p.source.url()),
345 version: p.version.clone(),
346 kind: p.release_kind,
347 }
348 }
349}
350
351impl SortModelTrait for Vec<PackageSummary> {
352 fn sort_by_version(&mut self) {
353 self.sort_by(|x, y| {
354 y.version
355 .as_ref()
356 .unwrap()
357 .cmp_version(x.version.as_ref().unwrap())
358 .unwrap()
359 });
360 }
361
362 fn sort_by_name(&mut self) {
363 self.sort_by(|x, y| x.name.cmp(&y.name))
364 }
365}
366
367pub fn default_targets() -> Vec<PackageTargetType> {
368 vec![
369 PackageTargetType::LinuxAmd64(Default::default()),
370 PackageTargetType::LinuxArm64(Default::default()),
371 PackageTargetType::LinuxArm(Default::default()),
372 PackageTargetType::MacOSAmd64(Default::default()),
373 PackageTargetType::MacOSArm64(Default::default()),
374 PackageTargetType::WindowsAmd64(Default::default()),
375 ]
376}
377
378pub fn default_targets_no_arm() -> Vec<PackageTargetType> {
379 vec![
380 PackageTargetType::LinuxAmd64(Default::default()),
381 PackageTargetType::LinuxArm64(Default::default()),
382 PackageTargetType::MacOSAmd64(Default::default()),
383 PackageTargetType::MacOSArm64(Default::default()),
384 PackageTargetType::WindowsAmd64(Default::default()),
385 ]
386}
387
388pub fn default_targets_no_arm_windows() -> Vec<PackageTargetType> {
389 vec![
390 PackageTargetType::LinuxAmd64(Default::default()),
391 PackageTargetType::LinuxArm64(Default::default()),
392 PackageTargetType::MacOSAmd64(Default::default()),
393 PackageTargetType::MacOSArm64(Default::default()),
394 ]
395}
396
397pub fn default_targets_no_windows() -> Vec<PackageTargetType> {
398 vec![
399 PackageTargetType::LinuxAmd64(Default::default()),
400 PackageTargetType::LinuxArm64(Default::default()),
401 PackageTargetType::LinuxArm(Default::default()),
402 PackageTargetType::MacOSAmd64(Default::default()),
403 PackageTargetType::MacOSArm64(Default::default()),
404 ]
405}
406
407pub fn default_targets_no_arm64_arm() -> Vec<PackageTargetType> {
408 vec![
409 PackageTargetType::LinuxAmd64(Default::default()),
410 PackageTargetType::MacOSAmd64(Default::default()),
411 PackageTargetType::MacOSArm64(Default::default()),
412 PackageTargetType::WindowsAmd64(Default::default()),
413 ]
414}