warg_client/
version_util.rs

1use anyhow::{bail, Result};
2use ptree::TreeBuilder;
3use semver::{Comparator, Op, Prerelease, Version, VersionReq};
4use warg_crypto::hash::AnyHash;
5use warg_protocol::package::Release;
6use wasmparser::names::KebabStr;
7
8/// Kind of import encountered while parsing
9#[derive(Debug, Eq, PartialEq, Hash)]
10pub enum ImportKind {
11    /// Locked Version
12    Locked(Option<String>),
13    /// Unlocked Version Range
14    Unlocked,
15    /// Interface
16    Interface(Option<String>),
17}
18
19/// Dependency in dep solve
20#[derive(Debug, Eq, PartialEq, Hash)]
21pub struct Import {
22    /// Import name
23    pub name: String,
24    /// Version Requirements
25    pub req: VersionReq,
26    /// Import kind
27    pub kind: ImportKind,
28}
29
30/// Parser for dep solve deps
31pub struct DependencyImportParser<'a> {
32    /// string to be parsed
33    pub next: &'a str,
34    /// index of parser
35    pub offset: usize,
36}
37
38impl<'a> DependencyImportParser<'a> {
39    /// Parses import
40    pub fn parse(&mut self) -> Result<Import> {
41        if self.eat_str("unlocked-dep=") {
42            self.expect_str("<")?;
43            let imp = self.pkgidset_up_to('>')?;
44            self.expect_str(">")?;
45            return Ok(imp);
46        }
47
48        if self.eat_str("locked-dep=") {
49            self.expect_str("<")?;
50            let imp = self.pkgver()?;
51            return Ok(imp);
52        }
53
54        let name = self.eat_until('@');
55        let v = self.semver(self.next)?;
56        let comp = Comparator {
57            op: semver::Op::Exact,
58            major: v.major,
59            minor: Some(v.minor),
60            patch: Some(v.patch),
61            pre: v.pre,
62        };
63        let req = VersionReq {
64            comparators: vec![comp],
65        };
66        Ok(Import {
67            name: name.unwrap().to_string(),
68            req,
69            kind: ImportKind::Interface(Some(self.next.to_string())),
70        })
71    }
72
73    fn eat_str(&mut self, prefix: &str) -> bool {
74        match self.next.strip_prefix(prefix) {
75            Some(rest) => {
76                self.next = rest;
77                true
78            }
79            None => false,
80        }
81    }
82
83    fn expect_str(&mut self, prefix: &str) -> Result<()> {
84        if self.eat_str(prefix) {
85            Ok(())
86        } else {
87            bail!(format!(
88                "expected `{prefix}` at `{}` at {}",
89                self.next, self.offset
90            ));
91        }
92    }
93
94    fn eat_up_to(&mut self, c: char) -> Option<&'a str> {
95        let i = self.next.find(c)?;
96        let (a, b) = self.next.split_at(i);
97        self.next = b;
98        Some(a)
99    }
100
101    fn eat_until(&mut self, c: char) -> Option<&'a str> {
102        let ret = self.eat_up_to(c);
103        if ret.is_some() {
104            self.next = &self.next[c.len_utf8()..];
105        }
106        ret
107    }
108
109    fn kebab(&self, s: &'a str) -> Result<&'a KebabStr> {
110        match KebabStr::new(s) {
111            Some(name) => Ok(name),
112            None => bail!(format!("`{s}` is not in kebab case at {}", self.offset)),
113        }
114    }
115
116    fn take_until(&mut self, c: char) -> Result<&'a str> {
117        match self.eat_until(c) {
118            Some(s) => Ok(s),
119            None => bail!(format!("failed to find `{c}` character at {}", self.offset)),
120        }
121    }
122
123    fn take_up_to(&mut self, c: char) -> Result<&'a str> {
124        match self.eat_up_to(c) {
125            Some(s) => Ok(s),
126            None => bail!(format!("failed to find `{c}` character at {}", self.offset)),
127        }
128    }
129
130    fn semver(&self, s: &str) -> Result<Version> {
131        match Version::parse(s) {
132            Ok(v) => Ok(v),
133            Err(e) => bail!(format!(
134                "`{s}` is not a valid semver: {e} at {}",
135                self.offset
136            )),
137        }
138    }
139
140    fn pkgver(&mut self) -> Result<Import> {
141        let namespace = self.take_until(':')?;
142        self.kebab(namespace)?;
143        let name = match self.eat_until('@') {
144            Some(name) => name,
145            // a:b
146            None => {
147                let name = self.take_up_to(',')?;
148                self.kebab(name)?;
149                return Ok(Import {
150                    name: format!("{namespace}:{name}"),
151                    req: VersionReq::STAR,
152                    kind: ImportKind::Locked(None),
153                });
154            }
155        };
156        let version = self.eat_until('>');
157        let req = if let Some(v) = version {
158            let v = self.semver(v)?;
159            let comp = Comparator {
160                op: semver::Op::Exact,
161                major: v.major,
162                minor: Some(v.minor),
163                patch: Some(v.patch),
164                pre: Prerelease::default(),
165            };
166            VersionReq {
167                comparators: vec![comp],
168            }
169        } else {
170            VersionReq::STAR
171        };
172        let digest = if self.eat_str(",") {
173            self.eat_until('<');
174            self.eat_until('>').map(|d| d.to_string())
175        } else {
176            None
177        };
178        Ok(Import {
179            name: format!("{namespace}:{name}"),
180            req,
181            kind: ImportKind::Locked(digest),
182        })
183    }
184    fn pkgidset_up_to(&mut self, end: char) -> Result<Import> {
185        let namespace = self.take_until(':')?;
186        self.kebab(namespace)?;
187        let name = match self.eat_until('@') {
188            Some(name) => name,
189            // a:b
190            None => {
191                let name = self.take_up_to(end)?;
192                self.kebab(name)?;
193                return Ok(Import {
194                    name: format!("{namespace}:{name}"),
195                    req: VersionReq::STAR,
196                    kind: ImportKind::Unlocked,
197                });
198            }
199        };
200        self.kebab(name)?;
201        // a:b@*
202        if self.eat_str("*") {
203            return Ok(Import {
204                name: format!("{namespace}:{name}"),
205                req: VersionReq::STAR,
206                kind: ImportKind::Unlocked,
207            });
208        }
209        self.expect_str("{")?;
210        if self.eat_str(">=") {
211            match self.eat_until(' ') {
212                Some(lower) => {
213                    let lower = self.semver(lower)?;
214                    self.expect_str("<")?;
215                    let upper = self.take_until('}')?;
216                    let upper = self.semver(upper)?;
217                    let lc = Comparator {
218                        op: semver::Op::GreaterEq,
219                        major: lower.major,
220                        minor: Some(lower.minor),
221                        patch: Some(lower.patch),
222                        pre: Prerelease::default(),
223                    };
224                    let uc = Comparator {
225                        op: semver::Op::Less,
226                        major: upper.major,
227                        minor: Some(upper.minor),
228                        patch: Some(upper.patch),
229                        pre: Prerelease::default(),
230                    };
231                    let comparators = vec![lc, uc];
232                    return Ok(Import {
233                        name: format!("{namespace}:{name}"),
234                        req: VersionReq { comparators },
235                        kind: ImportKind::Unlocked,
236                    });
237                }
238                // a:b@{>=1.2.3}
239                None => {
240                    let lower = self.take_until('}')?;
241                    let lower = self.semver(lower)?;
242                    let comparator = Comparator {
243                        op: semver::Op::GreaterEq,
244                        major: lower.major,
245                        minor: Some(lower.minor),
246                        patch: Some(lower.patch),
247                        pre: Prerelease::default(),
248                    };
249                    let comparators = vec![comparator];
250                    return Ok(Import {
251                        name: format!("{namespace}:{name}"),
252                        req: VersionReq { comparators },
253                        kind: ImportKind::Unlocked,
254                    });
255                }
256            }
257        }
258
259        // a:b@{<1.2.3}
260        // .. or
261        // a:b@{<1.2.3 >=1.2.3}
262        self.expect_str("<")?;
263        let upper = self.take_until('}')?;
264        let upper = self.semver(upper)?;
265        let uc = Comparator {
266            op: semver::Op::Less,
267            major: upper.major,
268            minor: Some(upper.minor),
269            patch: Some(upper.patch),
270            pre: Prerelease::default(),
271        };
272        let mut comparators: Vec<Comparator> = Vec::new();
273        comparators.push(uc);
274        Ok(Import {
275            name: format!("{namespace}:{name}"),
276            req: VersionReq { comparators },
277            kind: ImportKind::Unlocked,
278        })
279    }
280}
281
282/// Returns locked package string
283pub fn locked_package(pkg_name: &str, release: &Release, content: &AnyHash) -> String {
284    format!(
285        "locked-dep=<{}@{}>,integrity=<{}>",
286        &pkg_name,
287        &release.version.to_string(),
288        &content.to_string().replace(':', "-")
289    )
290}
291
292/// Package name with version range
293pub fn versioned_package(pkg_name: &str, version: VersionReq) -> String {
294    let ver = version.clone().to_string();
295    let range = if ver == "*" {
296        "".to_string()
297    } else {
298        // @{<verlower> <verupper>}
299        format!("@{{{}}}", ver.replace(',', ""))
300    };
301    format!("{}{range}", pkg_name)
302}
303
304/// Remove import kind from import beginning
305pub fn kindless_name(import_name: &str) -> &str {
306    // unlocked-dep=<foo:bar@version> --> <foo:bar@version>
307    let kindless_name = import_name.splitn(2, '=').last().unwrap();
308    // remove angle brackets
309    &kindless_name[1..kindless_name.len() - 1]
310}
311
312/// Stringify version
313pub fn version_string(version: &VersionReq) -> String {
314    if version.to_string() == "*" {
315        "*".to_string()
316    } else if version.comparators.len() == 1 && version.comparators[0].op == Op::Exact {
317        version.to_string()
318    } else {
319        format!("{{{}}}", version.to_string().replace(',', ""))
320    }
321}
322
323/// Create TreeBuilder child node
324pub fn create_child_node<'a>(
325    node: &'a mut TreeBuilder,
326    name: &str,
327    version: &str,
328) -> &'a mut TreeBuilder {
329    node.begin_child(format!("{}@{}", name, version))
330}
331
332/// Create new TreeBuilder
333pub fn new_tree(namespace: &str, name: &str, version: &Version) -> TreeBuilder {
334    TreeBuilder::new(format!("{}:{}@{}", namespace, name, version))
335}