rmskin_builder/
discover.rs1use std::{
2 fs,
3 path::{Path, PathBuf},
4};
5
6#[cfg(feature = "py-binding")]
7use pyo3::prelude::*;
8
9use crate::file_utils::{RMSKIN_BMP_NAME, RMSKIN_INI_NAME};
10
11#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
14#[cfg_attr(
15 feature = "py-binding",
16 pyclass(get_all, set_all, module = "rmskin_builder", eq, from_py_object)
17)]
18pub struct HasComponents {
19 pub rm_skin_ini: bool,
20 pub skins: i32,
21 pub layouts: i32,
22 pub plugins: bool,
23 pub vault: i32,
24 pub rm_skin_bmp: bool,
25}
26
27impl HasComponents {
28 pub const VAULT: &str = "@Vault";
29 pub const PLUGINS: &str = "Plugins";
30 pub const LAYOUTS: &str = "Layouts";
31 pub const SKINS: &str = "Skins";
32
33 pub fn is_valid(&self) -> bool {
34 self.rm_skin_ini && self.skins > 0
35 }
36}
37
38#[cfg(feature = "py-binding")]
39#[cfg_attr(feature = "py-binding", pymethods)]
40impl HasComponents {
41 #[new]
42 pub fn new() -> Self {
43 Self::default()
44 }
45
46 pub fn __repr__(&self) -> String {
47 format!("{self:?}")
48 }
49
50 #[pyo3(name = "is_valid")]
51 pub fn is_valid_py(&self) -> bool {
52 self.is_valid()
53 }
54}
55
56fn count_dir_children(path: PathBuf, dirs: bool, files: bool) -> Result<i32, std::io::Error> {
57 let mut count = 0;
58 for entry in (fs::read_dir(path)?).flatten() {
59 let entry_path = entry.path();
60 if (entry_path.is_dir() && dirs) || (entry_path.is_file() && files) {
61 count += 1;
62 }
63 }
64 Ok(count)
65}
66
67#[cfg_attr(feature = "py-binding", pyfunction(name = "discover_components"))]
69#[cfg(feature = "py-binding")]
70pub fn discover_components_py(path: PathBuf) -> PyResult<HasComponents> {
71 use pyo3::exceptions::PyIOError;
72
73 discover_components(&path).map_err(|e| PyIOError::new_err(e.to_string()))
74}
75
76pub fn discover_components<P: AsRef<Path>>(path: P) -> Result<HasComponents, std::io::Error> {
78 let mut components = HasComponents::default();
79 for entry in fs::read_dir(path)? {
80 let entry_path = entry?.path();
81 if let Some(dir_name) = entry_path.file_name() {
82 let name = dir_name.to_string_lossy().to_string();
83 match name.as_str() {
84 HasComponents::SKINS => {
85 let count = count_dir_children(entry_path, true, false)?;
86 log::info!("Found {count} possible skin(s)");
87 components.skins = count;
88 }
89 HasComponents::VAULT => {
90 let count = count_dir_children(entry_path, true, true)?;
91 log::info!("Found {count} possible @Vault item(s)");
92 components.vault = count;
93 }
94 HasComponents::PLUGINS => {
95 let count = count_dir_children(entry_path, true, false)?;
96 log::info!("Found {count} Plugins folder");
97 components.plugins = true;
98 }
99 HasComponents::LAYOUTS => {
100 let count = count_dir_children(entry_path, true, true)?;
101 log::info!("Found {count} possible layout(s)");
102 components.layouts = count;
103 }
104 RMSKIN_INI_NAME => {
105 log::info!("Found RMSKIN.ini file");
106 components.rm_skin_ini = true;
107 }
108 RMSKIN_BMP_NAME => {
109 log::info!("Found header image file");
110 components.rm_skin_bmp = true;
111 }
112 _ => log::debug!("Skipping {name}"),
113 }
114 }
115 }
116 Ok(components)
117}
118
119#[cfg(test)]
120mod test {
121 use super::{HasComponents, discover_components};
122
123 #[test]
124 fn basic() {
125 let components = discover_components("tests/demo_project").unwrap();
126 assert!(components.is_valid());
127 let expected = HasComponents {
128 rm_skin_ini: true,
129 skins: 1,
130 layouts: 1,
131 plugins: true,
132 vault: 1,
133 rm_skin_bmp: true,
134 };
135 assert_eq!(components, expected);
136 }
137}