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#[derive(Debug, Eq, PartialEq, Hash)]
10pub enum ImportKind {
11 Locked(Option<String>),
13 Unlocked,
15 Interface(Option<String>),
17}
18
19#[derive(Debug, Eq, PartialEq, Hash)]
21pub struct Import {
22 pub name: String,
24 pub req: VersionReq,
26 pub kind: ImportKind,
28}
29
30pub struct DependencyImportParser<'a> {
32 pub next: &'a str,
34 pub offset: usize,
36}
37
38impl<'a> DependencyImportParser<'a> {
39 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 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 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 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 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 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
282pub 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
292pub 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 format!("@{{{}}}", ver.replace(',', ""))
300 };
301 format!("{}{range}", pkg_name)
302}
303
304pub fn kindless_name(import_name: &str) -> &str {
306 let kindless_name = import_name.splitn(2, '=').last().unwrap();
308 &kindless_name[1..kindless_name.len() - 1]
310}
311
312pub 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
323pub 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
332pub fn new_tree(namespace: &str, name: &str, version: &Version) -> TreeBuilder {
334 TreeBuilder::new(format!("{}:{}@{}", namespace, name, version))
335}