1use std::collections::HashSet;
3use std::io;
4use std::path::{Path, PathBuf};
5
6#[derive(Default)]
8pub struct InputFiles {
9 pub files: Vec<PathBuf>,
10 pub errors: Vec<(PathBuf, io::Error)>,
11}
12
13pub fn discover_inputs(paths: &[PathBuf]) -> InputFiles {
17 let mut result = InputFiles::default();
18 let mut directories = HashSet::new();
19 let mut files = HashSet::new();
20 for path in paths {
21 visit(path, true, &mut directories, &mut files, &mut result);
22 }
23 result
24}
25
26fn visit(
27 path: &Path,
28 explicit: bool,
29 directories: &mut HashSet<PathBuf>,
30 files: &mut HashSet<PathBuf>,
31 result: &mut InputFiles,
32) {
33 let metadata = match std::fs::metadata(path) {
34 Ok(metadata) => metadata,
35 Err(error) => {
36 result.errors.push((path.to_owned(), error));
37 return;
38 }
39 };
40 if metadata.is_dir() && path.file_name().is_some_and(|name| name == ".git") {
41 return;
42 }
43 if !metadata.is_dir() && !explicit && !source_extension(path) {
44 return;
45 }
46 let canonical = match path.canonicalize() {
47 Ok(canonical) => canonical,
48 Err(error) => {
49 result.errors.push((path.to_owned(), error));
50 return;
51 }
52 };
53 if metadata.is_dir() {
54 if !directories.insert(canonical) {
55 return;
56 }
57 let entries = match std::fs::read_dir(path) {
58 Ok(entries) => entries,
59 Err(error) => {
60 result.errors.push((path.to_owned(), error));
61 return;
62 }
63 };
64 let mut children = Vec::new();
65 for entry in entries {
66 match entry {
67 Ok(entry) if entry.file_name() != ".git" => children.push(entry.path()),
68 Ok(_) => {}
69 Err(error) => result.errors.push((path.to_owned(), error)),
70 }
71 }
72 children.sort();
73 for child in children {
74 visit(&child, false, directories, files, result);
75 }
76 } else if metadata.is_file() && files.insert(canonical) {
77 result.files.push(path.to_owned());
78 } else if explicit && !metadata.is_file() {
79 result.errors.push((
80 path.to_owned(),
81 io::Error::new(io::ErrorKind::InvalidInput, "input is not a regular file"),
82 ));
83 }
84}
85
86fn source_extension(path: &Path) -> bool {
87 path.extension()
88 .and_then(|extension| extension.to_str())
89 .is_some_and(|extension| {
90 extension.eq_ignore_ascii_case("mzn") || extension.eq_ignore_ascii_case("dzn")
91 })
92}