huber_common/model/
package.rs

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    // {version}, {os}, {arch} can be used in each. Also, an external URL is acceptable
96    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    // only keep the {version} part
105    #[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}