1use crate::util::sha1dir;
2use colored::Colorize;
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use versions::Versioning;
5
6use log::error;
7use serde::de::Error as DeserializationError;
8use serde::ser::Error as SerializationError;
9use std::cmp::PartialEq;
10use std::collections::HashMap;
11use std::fmt::Display;
12use std::fs;
13use std::path::{Path, PathBuf};
14use std::str::FromStr;
15
16use crate::GitCloneAndCheckoutCap;
17
18use crate::package::management::copy_dir_all;
19use crate::package::{
20 deserialize_version, serialize_version,
21 target_properties::{LibraryTargetProperties, MergeTargetProperties},
22 tree::{DependencyTreeNode, PackageDetails, ProjectSource},
23 ConfigFile,
24};
25use crate::util::errors::LingoError;
26
27pub struct ParseLockSourceError {}
28
29#[derive(PartialEq, Debug)]
31pub enum PackageLockSourceType {
32 REGISTRY,
33 GIT,
34 TARBALL,
35 PATH,
36}
37
38#[derive(Debug)]
40pub struct PackageLockSource {
41 pub source_type: PackageLockSourceType,
42 pub uri: String,
43 pub rev: Option<String>,
44}
45
46impl FromStr for PackageLockSourceType {
48 type Err = ParseLockSourceError;
49
50 fn from_str(s: &str) -> Result<Self, Self::Err> {
51 match s {
52 "registry" => Ok(Self::REGISTRY),
53 "git" => Ok(Self::GIT),
54 "path" => Ok(Self::PATH),
55 "tar" => Ok(Self::TARBALL),
56 _ => Err(ParseLockSourceError {}),
57 }
58 }
59}
60
61impl Display for PackageLockSourceType {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 let str = match self {
65 Self::REGISTRY => "registry".to_string(),
66 Self::GIT => "git".to_string(),
67 Self::PATH => "path".to_string(),
68 Self::TARBALL => "tar".to_string(),
69 };
70 write!(f, "{}", str)
71 }
72}
73
74impl From<ProjectSource> for PackageLockSourceType {
75 fn from(value: ProjectSource) -> Self {
76 match value {
77 ProjectSource::Git(_) => Self::GIT,
78 ProjectSource::TarBall(_) => Self::TARBALL,
79 ProjectSource::Path(_) => Self::PATH,
80 }
81 }
82}
83
84impl FromStr for PackageLockSource {
87 type Err = ParseLockSourceError;
88
89 fn from_str(s: &str) -> Result<Self, Self::Err> {
90 match s.split_once("+") {
91 Some((source_type_string, mut uri)) => {
92 let source_type = PackageLockSourceType::from_str(source_type_string)?;
93
94 let rev: Option<String> = match source_type {
95 PackageLockSourceType::GIT => match uri.split_once("#") {
96 Some((url, rev)) => {
97 uri = url;
98 Some(rev.to_string())
99 }
100 None => {
101 return Err(ParseLockSourceError {});
102 }
103 },
104 _ => None,
105 };
106
107 Ok(PackageLockSource {
108 source_type,
109 uri: uri.to_string(),
110 rev,
111 })
112 }
113 None => Err(ParseLockSourceError {}),
114 }
115 }
116}
117
118#[derive(Deserialize, Serialize, Debug)]
119pub struct PackageLock {
120 pub name: String,
121 #[serde(
122 serialize_with = "serialize_version",
123 deserialize_with = "deserialize_version"
124 )]
125 pub version: Versioning,
126 pub source: PackageLockSource,
127 pub checksum: String,
128}
129
130impl From<DependencyTreeNode> for PackageLock {
131 fn from(value: DependencyTreeNode) -> Self {
132 let uri = match &value.package.mutual_exclusive {
133 ProjectSource::Git(git) => git.to_string(),
134 ProjectSource::TarBall(tar) => tar.to_string(),
135 ProjectSource::Path(path) => format!("{:?}", path),
136 };
137
138 PackageLock {
139 name: value.name,
140 version: value.version,
141 source: PackageLockSource {
142 source_type: PackageLockSourceType::from(value.package.mutual_exclusive),
143 uri,
144 rev: value.package.git_rev,
145 },
146 checksum: value.hash,
147 }
148 }
149}
150
151impl<'de> Deserialize<'de> for PackageLockSource {
152 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
153 where
154 D: Deserializer<'de>,
155 {
156 let s: String = Deserialize::deserialize(deserializer)?;
157 match PackageLockSource::from_str(&s) {
158 Ok(value) => Ok(value),
159 Err(_) => Err(D::Error::custom("cannot parse package source string!")),
160 }
161 }
162}
163
164impl Serialize for PackageLockSource {
165 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
166 where
167 S: Serializer,
168 {
169 let source_type = self.source_type.to_string();
170 let mut serialized_string = format!("{}+{}", source_type, self.uri);
171
172 if self.source_type == PackageLockSourceType::GIT {
173 if let Some(rev) = self.rev.clone() {
174 serialized_string = format!("{}#{}", serialized_string, rev)
175 } else {
176 error!("expected and revision but got none during serialization of lock file!");
177 return Err(S::Error::custom("expected revision but gone None"));
178 }
179 }
180
181 serializer.serialize_str(&serialized_string)
182 }
183}
184
185#[derive(Deserialize, Serialize, Default, Debug)]
186pub struct DependencyLock {
187 #[serde(flatten)]
189 pub dependencies: HashMap<String, PackageLock>,
190
191 #[serde(skip)]
193 loaded_dependencies: Vec<DependencyTreeNode>,
194}
195
196impl DependencyLock {
197 pub(crate) fn create(selected_dependencies: Vec<DependencyTreeNode>) -> DependencyLock {
198 let mut map = HashMap::new();
199 for dependency in &selected_dependencies {
200 map.insert(
201 dependency.name.clone(),
202 PackageLock::from(dependency.clone()),
203 );
204 }
205 Self {
206 dependencies: map,
207 loaded_dependencies: selected_dependencies,
208 }
209 }
210
211 pub fn init(
212 &mut self,
213 lfc_include_folder: &Path,
214 git_clone_and_checkout_cap: &GitCloneAndCheckoutCap,
215 ) -> anyhow::Result<()> {
216 for (_, lock) in self.dependencies.iter() {
217 let temp = lfc_include_folder.join(&lock.name);
218 if !temp.join("Lingo.toml").exists() {
220 let mut details = PackageDetails::try_from(&lock.source)?;
221
222 details
223 .fetch(&temp, git_clone_and_checkout_cap)
224 .expect("cannot pull package");
225 }
226
227 let hash = sha1dir::checksum_current_dir(&temp, false);
228
229 if hash.to_string() != lock.checksum {
230 error!("checksum does not match aborting!");
231 }
232
233 let lingo_toml_text = fs::read_to_string(temp.join("Lingo.toml"))?;
234 let read_toml = toml::from_str::<ConfigFile>(&lingo_toml_text)?.to_config(&temp);
235
236 println!(
237 "{} {} ... {}",
238 "Reading".green().bold(),
239 lock.name,
240 read_toml.package.version
241 );
242
243 let lib = match read_toml.library {
244 Some(value) => value,
245 None => {
246 return Err(LingoError::NoLibraryInLingoToml(
248 temp.join("Lingo.toml").display().to_string(),
249 )
250 .into());
251 }
252 };
253
254 self.loaded_dependencies.push(DependencyTreeNode {
255 name: read_toml.package.name.clone(),
256 version: read_toml.package.version.clone(),
257 package: PackageDetails {
258 version: Default::default(),
259 mutual_exclusive: ProjectSource::Path(PathBuf::new()),
260 git_tag: None,
261 git_rev: None,
262 },
263 location: temp.clone(),
264 include_path: lib.location.clone(),
265 hash: lock.checksum.clone(),
266 dependencies: vec![],
267 properties: lib.properties.clone(),
268 });
269 }
270
271 Ok(())
272 }
273
274 pub fn create_library_folder(
275 &self,
276 source_path: &Path,
277 target_path: &PathBuf,
278 ) -> anyhow::Result<()> {
279 fs::create_dir_all(target_path)?;
280 for (_, dep) in self.dependencies.iter() {
281 let local_source = source_path.join(&dep.checksum);
282 let find_source = target_path.clone().join(&dep.name);
283 fs::create_dir_all(&find_source)?;
284 copy_dir_all(&local_source, &find_source)?;
285 }
286
287 Ok(())
288 }
289
290 pub fn aggregate_target_properties(&self) -> anyhow::Result<LibraryTargetProperties> {
291 let mut i = LibraryTargetProperties::default();
292 for tp in &self.loaded_dependencies {
293 i.merge(&tp.properties)?;
294 }
295
296 Ok(i)
297 }
298}