1#![allow(clippy::result_large_err)]
5
6use std::path::{Path, PathBuf};
7
8use gix::{Url, bstr::ByteSlice as _};
9use index::{LockType, PackageIndex};
10use lock::{LockFileDep, LockPrecisePkg};
11use nickel_lang_core::cache::normalize_abs_path;
12
13use config::Config;
14use error::Error;
15use serde::{Deserialize, Serialize};
16
17macro_rules! warn {
18 ($($tts:tt)*) => {
19 eprintln!($($tts)*);
20 }
21}
22
23macro_rules! info {
24 ($($tts:tt)*) => {
25 eprintln!($($tts)*);
26 }
27}
28
29pub mod config;
30pub mod error;
31pub mod index;
32pub mod lock;
33pub mod manifest;
34pub mod resolve;
35pub mod snapshot;
36pub mod version;
37
38pub use gix::ObjectId;
39pub use manifest::ManifestFile;
40use version::{SemVer, VersionReq};
41
42#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
44pub struct GitDependency {
45 #[serde(with = "serde_url")]
48 pub url: gix::Url,
49 #[serde(default, rename = "ref")]
50 pub target: nickel_lang_git::Target,
51 #[serde(default)]
53 pub path: PathBuf,
54}
55
56impl GitDependency {
57 pub fn relative_to(&self, relative_to: Option<&Path>) -> Result<Self, Error> {
59 if self.url.scheme.as_str() != "file" {
60 return Ok(self.clone());
61 }
62 let path = Path::new(self.url.path.to_str().unwrap());
64 if path.is_absolute() {
65 return Ok(self.clone());
66 }
67
68 match relative_to {
69 Some(relative_to) => {
70 let abs_path = relative_to.join(path);
71 Ok(GitDependency {
72 url: Url::try_from(abs_path.as_path())?,
73 ..self.clone()
74 })
75 }
76 None => Err(Error::RelativeGitImport {
77 path: path.to_owned(),
78 }),
79 }
80 }
81}
82
83#[derive(Clone, Debug, PartialEq, Eq, Hash)]
84pub struct IndexDependency {
86 pub id: index::Id,
87 pub version: VersionReq,
88}
89
90#[derive(Clone, Debug, PartialEq, Eq, Hash)]
94pub enum Dependency {
95 Git(GitDependency),
96 Path(PathBuf),
97 Index(IndexDependency),
98}
99
100impl Dependency {
101 pub fn matches(&self, entry: &LockFileDep, precise: &LockPrecisePkg) -> bool {
102 match self {
103 Dependency::Git(git) => match &entry.spec {
104 Some(locked_git) => git == locked_git,
105 None => false,
106 },
107 Dependency::Path(_) => true,
108 Dependency::Index(i) => {
109 if let LockPrecisePkg::Index { id, version } = precise {
110 i.id == *id && i.version.matches(version)
111 } else {
112 false
113 }
114 }
115 }
116 }
117
118 pub fn as_index_dep(self, parent_id: &index::Id) -> Result<IndexDependency, Error> {
119 match self {
120 Dependency::Index(i) => Ok(i),
121 Dependency::Git(g) => Err(Error::InvalidIndexDep {
122 id: parent_id.clone(),
123 dep: Box::new(crate::UnversionedDependency::Git(g)),
124 }),
125 Dependency::Path(path) => Err(Error::InvalidIndexDep {
126 id: parent_id.clone(),
127 dep: Box::new(crate::UnversionedDependency::Path(path)),
128 }),
129 }
130 }
131
132 pub fn as_unversioned(self) -> Option<UnversionedDependency> {
133 match self {
134 Dependency::Index(_i) => None,
135 Dependency::Git(g) => Some(UnversionedDependency::Git(g)),
136 Dependency::Path(p) => Some(UnversionedDependency::Path(p)),
137 }
138 }
139}
140
141#[derive(Clone, PartialEq, Eq, Hash, Debug)]
146pub enum UnversionedDependency {
147 Git(GitDependency),
148 Path(PathBuf),
149}
150
151impl From<UnversionedDependency> for Dependency {
152 fn from(p: UnversionedDependency) -> Self {
153 match p {
154 UnversionedDependency::Git(git) => Dependency::Git(git),
155 UnversionedDependency::Path(path) => Dependency::Path(path),
156 }
157 }
158}
159
160mod serde_url {
161 use serde::{Deserialize, Serialize as _, de::Error};
162
163 pub fn serialize<S: serde::Serializer>(url: &gix::Url, ser: S) -> Result<S::Ok, S::Error> {
164 std::str::from_utf8(url.to_bstring().as_slice())
167 .unwrap()
168 .serialize(ser)
169 }
170
171 pub fn deserialize<'de, D: serde::Deserializer<'de>>(de: D) -> Result<gix::Url, D::Error> {
172 let s = String::deserialize(de)?;
173 gix::Url::try_from(s).map_err(|e| D::Error::custom(e.to_string()))
174 }
175}
176
177#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
179pub struct PreciseIndexPkg {
180 pub id: index::Id,
181 pub version: SemVer,
182}
183
184impl PreciseIndexPkg {
185 pub fn local_path_without_subdir<T: LockType>(
193 &self,
194 config: &Config,
195 index: &PackageIndex<T>,
196 ) -> Result<PathBuf, Error> {
197 let pkg = index.package(&self.id, &self.version)?;
198
199 Ok(config
200 .index_package_dir
201 .join("contents")
202 .join(pkg.id.object_id().to_string()))
203 }
204
205 pub fn local_path<T: LockType>(
209 &self,
210 config: &Config,
211 index: &PackageIndex<T>,
212 ) -> Result<PathBuf, Error> {
213 let index::Id::Github { path, .. } = &self.id;
214 Ok(self.local_path_without_subdir(config, index)?.join(path))
215 }
216}
217
218#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
220pub struct PreciseGitPkg {
221 url: gix::Url,
222 id: ObjectId,
223 path: PathBuf,
224}
225
226impl PreciseGitPkg {
227 pub fn local_path(&self, config: &Config) -> PathBuf {
231 repo_root(config, &self.id).join(&self.path)
232 }
233}
234
235#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
237pub enum PrecisePkg {
238 Git(PreciseGitPkg),
240 Path(PathBuf),
247 Index(PreciseIndexPkg),
249}
250
251impl PrecisePkg {
252 pub fn local_path<T: LockType>(
257 &self,
258 config: &Config,
259 index: &PackageIndex<T>,
260 ) -> Result<PathBuf, Error> {
261 match self {
262 PrecisePkg::Git(pkg) => Ok(pkg.local_path(config)),
263 PrecisePkg::Path(path) => Ok(path.clone()),
264 PrecisePkg::Index(PreciseIndexPkg { id, version }) => {
265 let pkg = index.package(id, version)?;
266 let index::Id::Github { path, .. } = id;
267 Ok(config
268 .index_package_dir
269 .join("contents")
270 .join(pkg.id.object_id().to_string())
271 .join(path))
272 }
273 }
274 }
275
276 pub fn is_path(&self) -> bool {
278 matches!(self, PrecisePkg::Path { .. })
279 }
280
281 pub fn is_available_offline<T: LockType>(
283 &self,
284 config: &Config,
285 index: &PackageIndex<T>,
286 ) -> bool {
287 match self {
293 PrecisePkg::Path { .. } => true,
294 _ => self.local_path(config, index).is_ok_and(|p| p.is_dir()),
295 }
296 }
297
298 pub fn with_abs_path(self, root: &std::path::Path) -> Self {
300 match self {
301 PrecisePkg::Path(path) => PrecisePkg::Path(normalize_abs_path(&root.join(path))),
302 x => x,
303 }
304 }
305
306 pub fn unversioned_or_index(self) -> Result<UnversionedPrecisePkg, PreciseIndexPkg> {
307 match self {
308 PrecisePkg::Git(g) => Ok(UnversionedPrecisePkg::Git(g)),
309 PrecisePkg::Path(p) => Ok(UnversionedPrecisePkg::Path(p)),
310 PrecisePkg::Index(i) => Err(i),
311 }
312 }
313}
314
315#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
317pub enum UnversionedPrecisePkg {
318 Git(PreciseGitPkg),
319 Path(PathBuf),
320}
321
322impl UnversionedPrecisePkg {
323 pub fn local_path(&self, config: &Config) -> PathBuf {
324 match self {
325 Self::Git(PreciseGitPkg { id, path, .. }) => repo_root(config, id).join(path),
326 Self::Path(path) => path.clone(),
327 }
328 }
329}
330
331impl From<UnversionedPrecisePkg> for PrecisePkg {
332 fn from(uv: UnversionedPrecisePkg) -> PrecisePkg {
333 match uv {
334 UnversionedPrecisePkg::Git(g) => PrecisePkg::Git(g),
335 UnversionedPrecisePkg::Path(p) => PrecisePkg::Path(p),
336 }
337 }
338}
339
340fn repo_root(config: &Config, id: &ObjectId) -> PathBuf {
342 config.git_package_dir.join(id.to_string())
343}