cargo_aur/
lib.rs

1//! Independently testable types and functions.
2
3use serde::Deserialize;
4use std::ops::Not;
5use std::path::{Path, PathBuf};
6
7/// The git forge in which a project's source code is stored.
8pub enum GitHost {
9    Github,
10    Gitlab,
11}
12
13impl GitHost {
14    pub fn source(&self, package: &Package) -> String {
15        match self {
16            GitHost::Github => format!(
17                "{}/releases/download/v$pkgver/{}-$pkgver-x86_64.tar.gz",
18                package.repository, package.name
19            ),
20            GitHost::Gitlab => format!(
21                "{}/-/archive/v$pkgver/{}-$pkgver-x86_64.tar.gz",
22                package.repository, package.name
23            ),
24        }
25    }
26}
27
28/// The critical fields read from a `Cargo.toml` and rewritten into a PKGBUILD.
29#[derive(Deserialize, Debug)]
30pub struct Package {
31    pub name: String,
32    pub version: String,
33    pub authors: Vec<String>,
34    pub description: String,
35    pub repository: String,
36    pub license: String,
37    pub metadata: Option<Metadata>,
38    pub homepage: Option<String>,
39    pub documentation: Option<String>,
40}
41
42impl Package {
43    /// The name of the tarball that should be produced from this `Package`.
44    pub fn tarball(&self, output: &Path) -> PathBuf {
45        output.join(format!("{}-{}-x86_64.tar.gz", self.name, self.version))
46    }
47
48    pub fn git_host(&self) -> Option<GitHost> {
49        if self.repository.starts_with("https://github") {
50            Some(GitHost::Github)
51        } else if self.repository.starts_with("https://gitlab") {
52            Some(GitHost::Gitlab)
53        } else {
54            None
55        }
56    }
57
58    /// Fetch the package URL from its `homepage`, `documentation` or
59    /// `repository` field.
60    pub fn url(&self) -> &str {
61        self.homepage
62            .as_deref()
63            .or(self.documentation.as_deref())
64            .unwrap_or(&self.repository)
65    }
66}
67
68// {
69//     Package {
70//         name: "aura".to_string(),
71//         version: "1.2.3".to_string(),
72//         authors: vec![],
73//         description: "".to_string(),
74//         homepage: "".to_string(),
75//         repository: "".to_string(),
76//         license: "".to_string(),
77//         metadata: None,
78//     }.tarball(Path::new("foobar"))
79// }
80
81/// The `[package.metadata]` TOML block.
82#[derive(Deserialize, Debug)]
83pub struct Metadata {
84    /// Deprecated.
85    #[serde(default)]
86    pub depends: Vec<String>,
87    /// Deprecated.
88    #[serde(default)]
89    pub optdepends: Vec<String>,
90    /// > [package.metadata.aur]
91    pub aur: Option<AUR>,
92}
93
94impl Metadata {
95    /// The metadata block actually has some contents.
96    pub fn non_empty(&self) -> bool {
97        self.depends.is_empty().not()
98            || self.optdepends.is_empty().not()
99            || self
100                .aur
101                .as_ref()
102                .is_some_and(|aur| aur.depends.is_empty().not() || aur.optdepends.is_empty().not())
103    }
104}
105
106impl std::fmt::Display for Metadata {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        // Reconcile which section to read extra dependency information from.
109        // The format we hope the user is using is:
110        //
111        // > [package.metadata.aur]
112        //
113        // But version 1.5 originally supported:
114        //
115        // > [package.metadata]
116        //
117        // To avoid a sudden breakage for users, we support both definition
118        // locations but favour the newer one.
119        //
120        // We print a warning to the user elsewhere if they're still using the
121        // old way.
122        let (deps, opts) = if let Some(aur) = self.aur.as_ref() {
123            (aur.depends.as_slice(), aur.optdepends.as_slice())
124        } else {
125            (self.depends.as_slice(), self.optdepends.as_slice())
126        };
127
128        match deps {
129            [middle @ .., last] => {
130                write!(f, "depends=(")?;
131                for item in middle {
132                    write!(f, "\"{}\" ", item)?;
133                }
134                if opts.is_empty().not() {
135                    writeln!(f, "\"{}\")", last)?;
136                } else {
137                    write!(f, "\"{}\")", last)?;
138                }
139            }
140            [] => {}
141        }
142
143        match opts {
144            [middle @ .., last] => {
145                write!(f, "optdepends=(")?;
146                for item in middle {
147                    write!(f, "\"{}\" ", item)?;
148                }
149                write!(f, "\"{}\")", last)?;
150            }
151            [] => {}
152        }
153
154        Ok(())
155    }
156}
157
158/// The inner values of a `[package.metadata.aur]` TOML block.
159#[derive(Deserialize, Debug)]
160pub struct AUR {
161    #[serde(default)]
162    depends: Vec<String>,
163    #[serde(default)]
164    optdepends: Vec<String>,
165    #[serde(default)]
166    pub files: Vec<(PathBuf, PathBuf)>,
167}