1use std::path::{Path, PathBuf};
4
5use nifty_syntax::project::{self, ENTRY_FILE, SOURCE_EXTENSION};
6
7use crate::error::CliError;
8
9#[derive(Debug, PartialEq, Eq)]
11pub(crate) struct ProjectFile {
12 pub path: PathBuf,
14 pub relative: PathBuf,
17}
18
19pub(crate) fn source_files(target: &Path) -> Result<Vec<ProjectFile>, CliError> {
22 if target.is_dir() {
23 if !target.join(ENTRY_FILE).is_file() {
24 return Err(CliError::new(format!(
25 "{} has no {ENTRY_FILE}; create one or run `nf create <name>`",
26 target.display()
27 )));
28 }
29 let files = project::source_files(target)
30 .map_err(|error| CliError::new(format!("can't list {}: {error}", target.display())))?;
31 return Ok(files
32 .into_iter()
33 .map(|relative| ProjectFile {
34 path: target.join(&relative),
35 relative,
36 })
37 .collect());
38 }
39 if target.is_file() && project::is_source(target) {
40 return Ok(vec![ProjectFile {
41 path: target.to_path_buf(),
42 relative: PathBuf::from(ENTRY_FILE),
43 }]);
44 }
45 Err(CliError::new(format!(
46 "{} is not a Nifty project folder or .{SOURCE_EXTENSION} file",
47 target.display()
48 )))
49}
50
51#[cfg(test)]
52mod tests {
53 use std::fs;
54
55 use super::*;
56 use crate::test_support::scratch_dir;
57
58 #[test]
59 fn a_folder_resolves_to_all_its_source_files_main_first() {
60 let dir = scratch_dir("folder");
61 fs::create_dir(dir.join("ui")).expect("create ui");
62 for file in [ENTRY_FILE, "ui/header.nf", "app.nf"] {
63 fs::write(dir.join(file), "").expect("write a source file");
64 }
65 let files = source_files(&dir).expect("folder has main.nf");
66 assert_eq!(files[0].path, dir.join(ENTRY_FILE));
67 let relative: Vec<&Path> = files.iter().map(|file| file.relative.as_path()).collect();
68 assert_eq!(
69 relative,
70 [ENTRY_FILE, "app.nf", "ui/header.nf"].map(Path::new)
71 );
72 }
73
74 #[test]
75 fn a_folder_without_main_is_an_error() {
76 let dir = scratch_dir("empty");
77 fs::write(dir.join("app.nf"), "").expect("write app.nf");
78 let error = source_files(&dir).expect_err("no main.nf");
79 assert!(error.to_string().contains("has no main.nf"));
80 }
81
82 #[test]
83 fn a_single_file_stays_on_its_own() {
84 let dir = scratch_dir("files");
85 let source = dir.join("app.nf");
86 let other = dir.join("notes.txt");
87 fs::write(dir.join(ENTRY_FILE), "").expect("write main.nf");
88 fs::write(&source, "").expect("write app.nf");
89 fs::write(&other, "").expect("write notes.txt");
90
91 let files = source_files(&source).expect("an .nf file");
92 assert_eq!(
93 files,
94 [ProjectFile {
95 path: source,
96 relative: PathBuf::from(ENTRY_FILE)
97 }]
98 );
99 assert!(source_files(&other).is_err());
100 assert!(source_files(&dir.join("missing.nf")).is_err());
101 }
102}