1use itertools::Itertools;
2use path_slash::PathBufExt;
3use project_toml::{
4 LocalProjectTomlValidationError, PartialProjectToml, RemoteProjectTomlValidationError,
5};
6use std::{
7 io,
8 ops::Deref,
9 path::{Path, PathBuf},
10 str::FromStr,
11};
12use thiserror::Error;
13use toml_edit::{DocumentMut, Item};
14
15use crate::{
16 config::Config,
17 git::{
18 self,
19 shorthand::RemoteGitUrlShorthand,
20 url::RemoteGitUrl,
21 utils::{GitError, SemVerTagOrSha},
22 },
23 lua_rockspec::{
24 LocalLuaRockspec, LuaRockspecError, LuaVersionError, PartialLuaRockspec,
25 PartialRockspecError, RemoteLuaRockspec,
26 },
27 lua_version::LuaVersion,
28 package::SpecRev,
29 remote_package_db::RemotePackageDB,
30 rockspec::{
31 lua_dependency::{DependencyType, LuaDependencySpec, LuaDependencyType},
32 LuaVersionCompatibility,
33 },
34};
35use crate::{
36 lockfile::PinnedState,
37 package::{PackageName, PackageReq},
38};
39
40pub(crate) mod gen;
41pub mod project_toml;
42
43pub use project_toml::PROJECT_TOML;
44
45pub const EXTRA_ROCKSPEC: &str = "extra.rockspec";
46
47#[derive(Error, Debug)]
48#[error(transparent)]
49pub enum ProjectError {
50 #[error("error reading project TOML at {0}:\n{1}")]
51 ReadProjectTOML(String, io::Error),
52 #[error("error creating project root at {0}:\n{1}")]
53 CreateProjectRoot(String, io::Error),
54 Project(#[from] LocalProjectTomlValidationError),
55 Toml(#[from] toml::de::Error),
56 #[error("error when parsing `extra.rockspec`: {0}")]
57 Rockspec(#[from] PartialRockspecError),
58}
59
60#[derive(Error, Debug)]
61#[error(transparent)]
62pub enum IntoLocalRockspecError {
63 LocalProjectTomlValidationError(#[from] LocalProjectTomlValidationError),
64 RockspecError(#[from] LuaRockspecError),
65}
66
67#[derive(Error, Debug)]
68#[error(transparent)]
69pub enum IntoRemoteRockspecError {
70 RocksTomlValidationError(#[from] RemoteProjectTomlValidationError),
71 RockspecError(#[from] LuaRockspecError),
72}
73
74#[derive(Error, Debug)]
75pub enum ProjectEditError {
76 #[error(transparent)]
77 Io(#[from] tokio::io::Error),
78 #[error(transparent)]
79 Toml(#[from] toml_edit::TomlError),
80 #[error("error parsing {PROJECT_TOML} after edit. This is probably a bug.")]
81 TomlDe(#[from] toml::de::Error),
82 #[error(transparent)]
83 Git(#[from] GitError),
84 #[error("unable to query latest version for {0}")]
85 LatestVersionNotFound(PackageName),
86 #[error("expected field to be a value, but got {0}")]
87 ExpectedValue(Box<toml_edit::Item>),
88 #[error("expected string, but got {0}")]
89 ExpectedString(Box<toml_edit::Value>),
90 #[error(transparent)]
91 GitUrlShorthandParse(#[from] git::shorthand::ParseError),
92}
93
94#[derive(Error, Debug)]
95pub enum PinError {
96 #[error("package {0} not found in dependencies")]
97 PackageNotFound(PackageName),
98 #[error("dependency {dep} is already {}pinned!", if *.pin_state == PinnedState::Unpinned { "un" } else { "" })]
99 PinStateUnchanged {
100 pin_state: PinnedState,
101 dep: PackageName,
102 },
103 #[error(transparent)]
104 Toml(#[from] toml_edit::TomlError),
105 #[error("error parsing lux.toml after edit. This is probably a bug.")]
106 TomlDe(#[from] toml::de::Error),
107 #[error(transparent)]
108 Io(#[from] tokio::io::Error),
109}
110
111#[derive(Clone, Debug)]
114#[cfg_attr(test, derive(Default))]
115pub struct ProjectRoot(PathBuf);
116
117impl ProjectRoot {
118 pub(crate) fn new() -> Self {
119 Self(PathBuf::new())
120 }
121}
122
123impl AsRef<Path> for ProjectRoot {
124 fn as_ref(&self) -> &Path {
125 self.0.as_ref()
126 }
127}
128
129impl Deref for ProjectRoot {
130 type Target = PathBuf;
131
132 fn deref(&self) -> &Self::Target {
133 &self.0
134 }
135}
136
137#[derive(Clone, Debug)]
138pub struct Project {
139 root: ProjectRoot,
141 toml: PartialProjectToml,
143}
144
145impl Project {
146 pub fn from_exact(start: impl AsRef<Path>) -> Result<Option<Self>, ProjectError> {
147 if !start.as_ref().exists() {
148 return Ok(None);
149 }
150
151 if start.as_ref().join(PROJECT_TOML).exists() {
152 let project_toml_path = start.as_ref().join(PROJECT_TOML);
153 let toml_content = std::fs::read_to_string(&project_toml_path).map_err(|err| {
154 ProjectError::ReadProjectTOML(project_toml_path.to_string_lossy().to_string(), err)
155 })?;
156 let root = start.as_ref();
157
158 let mut project = Project {
159 root: ProjectRoot(root.to_path_buf()),
160 toml: PartialProjectToml::new(&toml_content, ProjectRoot(root.to_path_buf()))?,
161 };
162
163 if let Some(extra_rockspec) = project.extra_rockspec()? {
164 project.toml = project.toml.merge(extra_rockspec);
165 }
166
167 Ok(Some(project))
168 } else {
169 Ok(None)
170 }
171 }
172
173 pub fn toml_path(&self) -> PathBuf {
175 self.root.join(PROJECT_TOML)
176 }
177
178 pub fn extra_rockspec_path(&self) -> PathBuf {
180 self.root.join(EXTRA_ROCKSPEC)
181 }
182
183 pub fn root(&self) -> &ProjectRoot {
184 &self.root
185 }
186
187 pub fn toml(&self) -> &PartialProjectToml {
188 &self.toml
189 }
190
191 pub fn local_rockspec(&self) -> Result<LocalLuaRockspec, IntoLocalRockspecError> {
192 Ok(self.toml().into_local()?.to_lua_rockspec()?)
193 }
194
195 pub fn remote_rockspec(
196 &self,
197 specrev: Option<SpecRev>,
198 ) -> Result<RemoteLuaRockspec, IntoRemoteRockspecError> {
199 Ok(self.toml().into_remote(specrev)?.to_lua_rockspec()?)
200 }
201
202 pub fn extra_rockspec(&self) -> Result<Option<PartialLuaRockspec>, PartialRockspecError> {
203 if self.extra_rockspec_path().exists() {
204 Ok(Some(PartialLuaRockspec::new(&std::fs::read_to_string(
205 self.extra_rockspec_path(),
206 )?)?))
207 } else {
208 Ok(None)
209 }
210 }
211
212 pub fn lua_version(&self, config: &Config) -> Result<LuaVersion, LuaVersionError> {
213 self.toml().lua_version_matches(config)
214 }
215
216 pub async fn add(
217 &mut self,
218 dependencies: DependencyType<&PackageReq>,
219 package_db: &RemotePackageDB,
220 ) -> Result<(), ProjectEditError> {
221 let mut project_toml =
222 toml_edit::DocumentMut::from_str(&tokio::fs::read_to_string(self.toml_path()).await?)?;
223
224 prepare_dependency_tables(&mut project_toml);
225 let table = match dependencies {
226 DependencyType::Regular(_) => &mut project_toml["dependencies"],
227 DependencyType::Build(_) => &mut project_toml["build_dependencies"],
228 DependencyType::Test(_) => &mut project_toml["test_dependencies"],
229 DependencyType::External(_) => &mut project_toml["external_dependencies"],
230 };
231
232 match dependencies {
233 DependencyType::Regular(ref deps)
234 | DependencyType::Build(ref deps)
235 | DependencyType::Test(ref deps) => {
236 for dep in deps {
237 let dep_version_str = if dep.version_req().is_any() {
238 package_db
239 .latest_version(dep.name())
240 .map(|latest_version| latest_version.to_string())
241 .unwrap_or_else(|| dep.version_req().to_string())
242 } else {
243 dep.version_req().to_string()
244 };
245 table[dep.name().to_string()] = toml_edit::value(dep_version_str);
246 }
247 }
248 DependencyType::External(ref deps) => {
249 for (name, dep) in deps {
250 if let Some(path) = &dep.header {
251 table[name]["header"] = toml_edit::value(path.to_slash_lossy().to_string());
252 }
253 if let Some(path) = &dep.library {
254 table[name]["library"] =
255 toml_edit::value(path.to_slash_lossy().to_string());
256 }
257 }
258 }
259 };
260
261 let toml_content = project_toml.to_string();
262 tokio::fs::write(self.toml_path(), &toml_content).await?;
263 self.toml = PartialProjectToml::new(&toml_content, self.root.clone())?;
264
265 Ok(())
266 }
267
268 pub async fn add_git(
269 &mut self,
270 dependencies: LuaDependencyType<&RemoteGitUrlShorthand>,
271 ) -> Result<(), ProjectEditError> {
272 let mut project_toml =
273 toml_edit::DocumentMut::from_str(&tokio::fs::read_to_string(self.toml_path()).await?)?;
274
275 prepare_dependency_tables(&mut project_toml);
276 let table = match dependencies {
277 LuaDependencyType::Regular(_) => &mut project_toml["dependencies"],
278 LuaDependencyType::Build(_) => &mut project_toml["build_dependencies"],
279 LuaDependencyType::Test(_) => &mut project_toml["test_dependencies"],
280 };
281
282 match dependencies {
283 LuaDependencyType::Regular(urls)
284 | LuaDependencyType::Build(urls)
285 | LuaDependencyType::Test(urls) => {
286 for url in urls {
287 let git_url: RemoteGitUrl = url.clone().into();
288 let mut dep_entry = toml_edit::table();
289 match git::utils::latest_semver_tag_or_commit_sha(&git_url)? {
290 SemVerTagOrSha::SemVerTag(tag) => {
291 dep_entry["git"] = Item::Value(url.to_string().into());
292 dep_entry["version"] = Item::Value(tag.clone().into());
293 if tag.contains("-") {
294 dep_entry["rev"] = Item::Value(tag.into());
296 }
297 }
298 SemVerTagOrSha::CommitSha(sha) => {
299 dep_entry["git"] = Item::Value(url.to_string().into());
300 dep_entry["version"] = Item::Value(sha.into());
301 }
302 }
303 table[git_url.repo.clone()] = dep_entry;
304 }
305 }
306 }
307
308 let toml_content = project_toml.to_string();
309 tokio::fs::write(self.toml_path(), &toml_content).await?;
310 self.toml = PartialProjectToml::new(&toml_content, self.root.clone())?;
311
312 Ok(())
313 }
314
315 pub async fn remove(
316 &mut self,
317 dependencies: DependencyType<&PackageName>,
318 ) -> Result<(), ProjectEditError> {
319 let mut project_toml =
320 toml_edit::DocumentMut::from_str(&tokio::fs::read_to_string(self.toml_path()).await?)?;
321
322 prepare_dependency_tables(&mut project_toml);
323 let table = match dependencies {
324 DependencyType::Regular(_) => &mut project_toml["dependencies"],
325 DependencyType::Build(_) => &mut project_toml["build_dependencies"],
326 DependencyType::Test(_) => &mut project_toml["test_dependencies"],
327 DependencyType::External(_) => &mut project_toml["external_dependencies"],
328 };
329
330 match dependencies {
331 DependencyType::Regular(ref deps)
332 | DependencyType::Build(ref deps)
333 | DependencyType::Test(ref deps) => {
334 for dep in deps {
335 table[dep.to_string()] = Item::None;
336 }
337 }
338 DependencyType::External(ref deps) => {
339 for (name, dep) in deps {
340 if dep.header.is_some() {
341 table[name]["header"] = Item::None;
342 }
343 if dep.library.is_some() {
344 table[name]["library"] = Item::None;
345 }
346 }
347 }
348 };
349
350 let toml_content = project_toml.to_string();
351 tokio::fs::write(self.toml_path(), &toml_content).await?;
352 self.toml = PartialProjectToml::new(&toml_content, self.root.clone())?;
353
354 Ok(())
355 }
356
357 pub async fn upgrade(
358 &mut self,
359 dependencies: LuaDependencyType<&PackageName>,
360 package_db: &RemotePackageDB,
361 ) -> Result<(), ProjectEditError> {
362 let mut project_toml =
363 toml_edit::DocumentMut::from_str(&tokio::fs::read_to_string(self.toml_path()).await?)?;
364
365 prepare_dependency_tables(&mut project_toml);
366 let table = match dependencies {
367 LuaDependencyType::Regular(_) => &mut project_toml["dependencies"],
368 LuaDependencyType::Build(_) => &mut project_toml["build_dependencies"],
369 LuaDependencyType::Test(_) => &mut project_toml["test_dependencies"],
370 };
371
372 match dependencies {
373 LuaDependencyType::Regular(deps)
374 | LuaDependencyType::Build(deps)
375 | LuaDependencyType::Test(deps) => {
376 let latest_rock_version_str =
377 |dep: &PackageName| -> Result<String, ProjectEditError> {
378 Ok(package_db
379 .latest_version(dep)
380 .ok_or(ProjectEditError::LatestVersionNotFound(dep.clone()))?
381 .to_string())
382 };
383 for dep in deps {
384 let mut dep_item = table[dep.to_string()].clone();
385 match &dep_item {
386 Item::Value(_) => {
387 let dep_version_str = latest_rock_version_str(dep)?;
388 table[dep.to_string()] = toml_edit::value(dep_version_str);
389 }
390 Item::Table(tbl) => {
391 match tbl.get("git") {
392 Some(git_item) => {
393 let git_value =
394 git_item.clone().into_value().map_err(|err| {
395 ProjectEditError::ExpectedValue(Box::new(err))
396 })?;
397 let git_url_str = git_value.as_str().ok_or(
398 ProjectEditError::ExpectedString(Box::new(
399 git_value.clone(),
400 )),
401 )?;
402 let shorthand: RemoteGitUrlShorthand = git_url_str.parse()?;
403 match git::utils::latest_semver_tag_or_commit_sha(
404 &shorthand.into(),
405 )? {
406 SemVerTagOrSha::SemVerTag(latest_tag) => {
407 table[dep.to_string()]["version"] =
408 Item::Value(latest_tag.clone().into());
409 if latest_tag.contains("-") {
410 table[dep.to_string()]["rev"] =
412 Item::Value(latest_tag.into());
413 }
414 }
415 SemVerTagOrSha::CommitSha(latest_sha) => {
416 table[dep.to_string()]["version"] =
417 Item::Value(latest_sha.into());
418 }
419 }
420 table[dep.to_string()] = dep_item;
421 }
422 None => {
423 let dep_version_str = latest_rock_version_str(dep)?;
424 dep_item["version".to_string()] =
425 toml_edit::value(dep_version_str);
426 table[dep.to_string()] = dep_item;
427 }
428 }
429 }
430 _ => {}
431 }
432 }
433 }
434 }
435
436 let toml_content = project_toml.to_string();
437 tokio::fs::write(self.toml_path(), &toml_content).await?;
438 self.toml = PartialProjectToml::new(&toml_content, self.root.clone())?;
439
440 Ok(())
441 }
442
443 pub async fn upgrade_all(
444 &mut self,
445 package_db: &RemotePackageDB,
446 ) -> Result<(), ProjectEditError> {
447 if let Some(dependencies) = &self.toml().dependencies {
448 let packages = dependencies
449 .iter()
450 .map(|dep| dep.name())
451 .cloned()
452 .collect_vec();
453 self.upgrade(
454 LuaDependencyType::Regular(packages.iter().collect()),
455 package_db,
456 )
457 .await?;
458 }
459 if let Some(dependencies) = &self.toml().build_dependencies {
460 let packages = dependencies
461 .iter()
462 .map(|dep| dep.name())
463 .cloned()
464 .collect_vec();
465 self.upgrade(
466 LuaDependencyType::Build(packages.iter().collect()),
467 package_db,
468 )
469 .await?;
470 }
471 if let Some(dependencies) = &self.toml().test_dependencies {
472 let packages = dependencies
473 .iter()
474 .map(|dep| dep.name())
475 .cloned()
476 .collect_vec();
477 self.upgrade(
478 LuaDependencyType::Test(packages.iter().collect()),
479 package_db,
480 )
481 .await?;
482 }
483 Ok(())
484 }
485
486 pub async fn set_pinned_state(
487 &mut self,
488 dependencies: LuaDependencyType<&PackageName>,
489 pin: PinnedState,
490 ) -> Result<(), PinError> {
491 let mut project_toml =
492 toml_edit::DocumentMut::from_str(&tokio::fs::read_to_string(self.toml_path()).await?)?;
493
494 prepare_dependency_tables(&mut project_toml);
495 let table = match dependencies {
496 LuaDependencyType::Regular(_) => &mut project_toml["dependencies"],
497 LuaDependencyType::Build(_) => &mut project_toml["build_dependencies"],
498 LuaDependencyType::Test(_) => &mut project_toml["test_dependencies"],
499 };
500
501 match dependencies {
502 LuaDependencyType::Regular(ref _deps) => {
503 self.toml.dependencies = Some(
504 self.toml
505 .dependencies
506 .take()
507 .unwrap_or_default()
508 .into_iter()
509 .map(|dep| LuaDependencySpec { pin, ..dep })
510 .collect(),
511 )
512 }
513 LuaDependencyType::Build(ref _deps) => {
514 self.toml.build_dependencies = Some(
515 self.toml
516 .build_dependencies
517 .take()
518 .unwrap_or_default()
519 .into_iter()
520 .map(|dep| LuaDependencySpec { pin, ..dep })
521 .collect(),
522 )
523 }
524 LuaDependencyType::Test(ref _deps) => {
525 self.toml.test_dependencies = Some(
526 self.toml
527 .test_dependencies
528 .take()
529 .unwrap_or_default()
530 .into_iter()
531 .map(|dep| LuaDependencySpec { pin, ..dep })
532 .collect(),
533 )
534 }
535 }
536
537 match dependencies {
538 LuaDependencyType::Regular(ref deps)
539 | LuaDependencyType::Build(ref deps)
540 | LuaDependencyType::Test(ref deps) => {
541 for dep in deps {
542 let mut dep_item = table[dep.to_string()].clone();
543 match dep_item {
544 version @ Item::Value(_) => match &pin {
545 PinnedState::Unpinned => {}
546 PinnedState::Pinned => {
547 if let Ok(mut dep_entry) = toml_edit::table().into_table() {
548 dep_entry.set_implicit(true);
549 dep_entry["version"] = version;
550 dep_entry["pin"] = toml_edit::value(true);
551 table[dep.to_string()] = toml_edit::Item::Table(dep_entry);
552 }
553 }
554 },
555 Item::Table(_) => {
556 dep_item["pin".to_string()] = toml_edit::value(pin.as_bool());
557 table[dep.to_string()] = dep_item;
558 }
559 _ => {}
560 }
561 }
562 }
563 }
564
565 let toml_content = project_toml.to_string();
566 tokio::fs::write(self.toml_path(), &toml_content).await?;
567 self.toml = PartialProjectToml::new(&toml_content, self.root.clone())?;
568
569 Ok(())
570 }
571
572 pub fn project_files(&self) -> Vec<PathBuf> {
573 project_files(&self.root().0)
574 }
575}
576
577pub(crate) fn project_files(src: &Path) -> Vec<PathBuf> {
580 ignore::WalkBuilder::new(src)
581 .add(src.join(".cargo"))
582 .follow_links(false)
583 .build()
584 .filter_map(Result::ok)
585 .filter(|entry| entry.file_type().is_some_and(|ft| ft.is_file()))
586 .map(|entry| entry.into_path())
587 .collect_vec()
588}
589
590fn prepare_dependency_tables(project_toml: &mut DocumentMut) {
591 if !project_toml.contains_table("dependencies") {
592 if let Ok(mut table) = toml_edit::table().into_table() {
593 table.set_implicit(true);
594 project_toml["dependencies"] = toml_edit::Item::Table(table);
595 }
596 }
597 if !project_toml.contains_table("build_dependencies") {
598 if let Ok(mut table) = toml_edit::table().into_table() {
599 table.set_implicit(true);
600 project_toml["build_dependencies"] = toml_edit::Item::Table(table);
601 }
602 }
603 if !project_toml.contains_table("test_dependencies") {
604 if let Ok(mut table) = toml_edit::table().into_table() {
605 table.set_implicit(true);
606 project_toml["test_dependencies"] = toml_edit::Item::Table(table);
607 }
608 }
609 if !project_toml.contains_table("external_dependencies") {
610 if let Ok(mut table) = toml_edit::table().into_table() {
611 table.set_implicit(true);
612 project_toml["external_dependencies"] = toml_edit::Item::Table(table);
613 }
614 }
615}
616
617#[cfg(test)]
619mod tests {
620 use std::collections::HashMap;
621
622 use assert_fs::prelude::{PathChild, PathCopy, PathCreateDir};
623 use url::Url;
624
625 use super::*;
626 use crate::{
627 lua_rockspec::ExternalDependencySpec,
628 manifest::{Manifest, ManifestMetadata},
629 package::PackageReq,
630 rockspec::Rockspec,
631 };
632
633 #[tokio::test]
634 async fn test_add_various_dependencies() {
635 let sample_project: PathBuf = "resources/test/sample-projects/no-build-spec/".into();
636 let project_root = assert_fs::TempDir::new().unwrap();
637 project_root.copy_from(&sample_project, &["**"]).unwrap();
638 let project_root: PathBuf = project_root.path().into();
639 let mut project = Project::from_exact(&project_root).unwrap().unwrap();
640 let add_dependencies = [PackageReq::new("busted".into(), Some(">= 1.0.0".into())).unwrap()];
641 let expected_dependencies = vec![PackageReq::new("busted".into(), Some(">= 1.0.0".into()))
642 .unwrap()
643 .into()];
644
645 let test_manifest_path =
646 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/manifest-5.1");
647 let content = String::from_utf8(std::fs::read(&test_manifest_path).unwrap()).unwrap();
648 let metadata = ManifestMetadata::new(&content).unwrap();
649 let package_db = Manifest::new(Url::parse("https://example.com").unwrap(), metadata).into();
650
651 project
652 .add(
653 DependencyType::Regular(add_dependencies.iter().collect_vec()),
654 &package_db,
655 )
656 .await
657 .unwrap();
658
659 project
660 .add(
661 DependencyType::Build(add_dependencies.iter().collect_vec()),
662 &package_db,
663 )
664 .await
665 .unwrap();
666 project
667 .add(
668 DependencyType::Test(add_dependencies.iter().collect_vec()),
669 &package_db,
670 )
671 .await
672 .unwrap();
673
674 project
675 .add(
676 DependencyType::External(HashMap::from([(
677 "lib".into(),
678 ExternalDependencySpec {
679 library: Some("path.so".into()),
680 header: None,
681 },
682 )])),
683 &package_db,
684 )
685 .await
686 .unwrap();
687
688 let project = Project::from_exact(&project_root).unwrap().unwrap();
691 let validated_toml = project.toml().into_remote(None).unwrap();
692
693 assert_eq!(
694 validated_toml.dependencies().current_platform(),
695 &expected_dependencies
696 );
697 assert_eq!(
698 validated_toml.build_dependencies().current_platform(),
699 &expected_dependencies
700 );
701 assert_eq!(
702 validated_toml.test_dependencies().current_platform(),
703 &expected_dependencies
704 );
705 assert_eq!(
706 validated_toml
707 .external_dependencies()
708 .current_platform()
709 .get("lib")
710 .unwrap(),
711 &ExternalDependencySpec {
712 library: Some("path.so".into()),
713 header: None
714 }
715 );
716 }
717
718 #[tokio::test]
719 async fn test_remove_dependencies() {
720 let sample_project: PathBuf = "resources/test/sample-projects/dependencies/".into();
721 let project_root = assert_fs::TempDir::new().unwrap();
722 project_root.copy_from(&sample_project, &["**"]).unwrap();
723 let project_root: PathBuf = project_root.path().into();
724 let mut project = Project::from_exact(&project_root).unwrap().unwrap();
725 let lua_cjson = "lua-cjson".into();
726 let plenary_nvim = "plenary.nvim".into();
727 let remove_dependencies = vec![&lua_cjson, &plenary_nvim];
728 project
729 .remove(DependencyType::Regular(remove_dependencies.clone()))
730 .await
731 .unwrap();
732 let check = |project: &Project| {
733 for name in &remove_dependencies {
734 assert!(!project
735 .toml()
736 .dependencies
737 .clone()
738 .unwrap_or_default()
739 .iter()
740 .any(|dep| &dep.name() == name));
741 }
742 };
743 check(&project);
744 let reloaded_project = Project::from_exact(&project_root).unwrap().unwrap();
746 check(&reloaded_project);
747 }
748
749 #[tokio::test]
750 async fn test_extra_rockspec_parsing() {
751 let sample_project: PathBuf = "resources/test/sample-projects/extra-rockspec/".into();
752 let project_root = assert_fs::TempDir::new().unwrap();
753 project_root.copy_from(&sample_project, &["**"]).unwrap();
754 let project_root: PathBuf = project_root.path().into();
755 let project = Project::from_exact(project_root).unwrap().unwrap();
756
757 let extra_rockspec = project.extra_rockspec().unwrap();
758
759 assert!(extra_rockspec.is_some());
760
761 let rocks = project.toml().into_remote(None).unwrap();
762
763 assert_eq!(rocks.package().to_string(), "custom-package");
764 }
765
766 #[tokio::test]
767 async fn test_pin_dependencies() {
768 test_pin_unpin_dependencies(PinnedState::Pinned).await
769 }
770
771 #[tokio::test]
772 async fn test_unpin_dependencies() {
773 test_pin_unpin_dependencies(PinnedState::Unpinned).await
774 }
775
776 async fn test_pin_unpin_dependencies(pin: PinnedState) {
777 let sample_project: PathBuf = "resources/test/sample-projects/dependencies/".into();
778 let project_root = assert_fs::TempDir::new().unwrap();
779 project_root.copy_from(&sample_project, &["**"]).unwrap();
780 let project_root: PathBuf = project_root.path().into();
781 let mut project = Project::from_exact(&project_root).unwrap().unwrap();
782 let lua_cjson = "lua-cjson".into();
783 let plenary_nvim = "plenary.nvim".into();
784 let pin_dependencies = vec![&lua_cjson, &plenary_nvim];
785 project
786 .set_pinned_state(LuaDependencyType::Regular(pin_dependencies.clone()), pin)
787 .await
788 .unwrap();
789 let check = |project: &Project| {
790 for name in &pin_dependencies {
791 assert!(project
792 .toml()
793 .dependencies
794 .clone()
795 .unwrap_or_default()
796 .iter()
797 .any(|dep| &dep.name() == name && dep.pin == pin));
798 }
799 };
800 check(&project);
801 let reloaded_project = Project::from_exact(&project_root).unwrap().unwrap();
803 check(&reloaded_project);
804 }
805
806 #[tokio::test]
807 async fn project_files_includes_cargo_directory() {
808 let project_root = assert_fs::TempDir::new().unwrap();
809 let cargo_dir = project_root.child(".cargo");
810 cargo_dir.create_dir_all().unwrap();
811 let cargo_config = cargo_dir.join("config.toml");
812 tokio::fs::write(&cargo_config, "").await.unwrap();
813 let project_files = project_files(&project_root);
814 assert!(project_files.contains(&cargo_config.to_path_buf()));
815 }
816}