Tholp's bespoke website generator
1use crate::types::InputFile;
2use std::{
3 fs,
4 path::{Path, PathBuf},
5};
6use toml::Table;
7
8pub struct Project {
9 pub filegroups: Vec<FileGroup>,
10 //pub settings: ProjectSettings,
11 //pub context: ProjectContext,
12 pub input_folder: PathBuf,
13 pub output_folder: PathBuf,
14 pub global_pre_insert: PathBuf,
15 pub global_post_insert: PathBuf,
16
17 pub filemap: Vec<PathBuf>, // mapped to index
18 pub section_name_map: Vec<String>,
19}
20
21pub struct FileGroup {
22 pub name: String,
23 pub files: Vec<InputFile>,
24 pub pre_insert: PathBuf,
25 pub post_insert: PathBuf,
26 pub process: bool,
27 pub convert_html: bool,
28}
29
30// pub struct ProjectContext {
31// pub input_folder: PathBuf,
32// pub output_folder: PathBuf,
33// pub global_pre_insert: PathBuf,
34// pub global_post_insert: PathBuf,
35
36// pub filemap: Vec<PathBuf>, // mapped to index
37// }
38
39macro_rules! get_table_bool_or_default {
40 ($table:ident, $key:expr, $default:expr) => {
41 $table
42 .get($key)
43 .unwrap_or(&toml::Value::try_from($default).unwrap())
44 .as_bool()
45 .unwrap_or($default)
46 };
47}
48
49macro_rules! get_table_string_or_default {
50 ($table:ident, $key:expr, $default:expr) => {
51 // $table
52 // .get($key)
53 // .unwrap_or(&toml::Value::try_from($default).unwrap())
54 // .as_str()
55 // .unwrap_or($default)
56 if $table.contains_key($key) {
57 $table.get($key).unwrap().as_str().unwrap()
58 } else {
59 $default
60 }
61 };
62}
63
64pub fn parse_project(tomlpath: &Path) -> Project {
65 let tomlfile = fs::read_to_string(tomlpath).expect("Project file unreadable or missing.");
66
67 let mut project: Project = Project {
68 filegroups: Vec::new(),
69 input_folder: PathBuf::new(),
70 output_folder: PathBuf::new(),
71 global_pre_insert: PathBuf::new(),
72 global_post_insert: PathBuf::new(),
73 filemap: Vec::new(),
74 section_name_map: Vec::new(),
75 };
76 let config = tomlfile
77 .parse::<Table>()
78 .expect("Project file not in propper toml format");
79 let settings_section = config["settings"]
80 .as_table()
81 .expect("Project file missing [settings] section");
82 let filegroups_section = config["fileGroups"]
83 .as_table()
84 .expect("Project file contains no file groups ");
85
86 let project_root = tomlpath
87 .parent()
88 .expect("Project file unreadable or missing.");
89
90 project.input_folder = PathBuf::from(get_table_string_or_default!(
91 settings_section,
92 "inputFolder",
93 "skid"
94 ));
95
96 project.output_folder = PathBuf::from(get_table_string_or_default!(
97 settings_section,
98 "outputFolder",
99 "content"
100 ));
101
102 project.global_pre_insert = project_root.join(get_table_string_or_default!(
103 settings_section,
104 "preInsertGlobal",
105 ""
106 ));
107 project.global_post_insert = project_root.join(get_table_string_or_default!(
108 settings_section,
109 "postInsertGlobal",
110 ""
111 ));
112
113 for (k, v) in filegroups_section {
114 if !v.is_table() {
115 continue;
116 }
117 let filegroup_def: &toml::map::Map<String, toml::Value> = v.as_table().unwrap();
118
119 let pre_insert = get_table_string_or_default!(filegroup_def, "preInsert", "");
120 let post_insert = get_table_string_or_default!(filegroup_def, "postInsert", "");
121 let process = get_table_bool_or_default!(filegroup_def, "process", true);
122 let convert_html = get_table_bool_or_default!(filegroup_def, "convertHTML", true);
123 let extention = get_table_string_or_default!(filegroup_def, "outputExtention", "html");
124
125 let recurse_find = get_table_bool_or_default!(filegroup_def, "recursiveFind", false);
126
127 let dir = get_table_string_or_default!(filegroup_def, "folder", "");
128
129 let mut group = FileGroup {
130 files: Vec::new(),
131 name: k.clone(),
132 pre_insert: pre_insert.into(),
133 post_insert: post_insert.into(),
134 process,
135 convert_html,
136 };
137
138 if filegroup_def.contains_key("files") {
139 let file_array = filegroup_def["files"].as_array().unwrap_or_else(|| {
140 panic!("'files' section of fileGroup.{} needs to be an array", k)
141 });
142 for file in file_array {
143 let filename = file.as_str().unwrap_or_else(|| {
144 panic!(
145 "'files' section of fileGroup.{} needs to only contain strings",
146 k
147 )
148 });
149
150 let mut new_file = crate::types::InputFile::new();
151 new_file.file_input = project.input_folder.clone();
152 new_file.file_input.push(filename);
153
154 new_file.file_out = project.output_folder.clone();
155 new_file.file_out.push(filename);
156 new_file.file_out.set_extension(extention);
157
158 new_file.file_skidout = new_file.file_out.clone();
159 new_file.file_skidout.set_extension("sko");
160
161 group.files.push(new_file);
162 }
163 }
164
165 project.filegroups.push(group);
166 }
167
168 return project;
169}
170
171pub trait Indexing {
172 fn index_of_file(&mut self, f: &PathBuf) -> usize;
173 fn file_for_index(&self, i: usize) -> Option<PathBuf>;
174 fn file_for_index_canonical(&self, i: usize) -> Option<&PathBuf>;
175
176 fn index_of_section_name(&mut self, name: &String) -> usize;
177 fn section_name_for_index(&self, index: usize) -> Option<&String>;
178}
179
180impl Indexing for Project {
181 fn index_of_file(&mut self, f: &PathBuf) -> usize {
182 let cannonical = f.canonicalize().unwrap();
183 let mut index = 0;
184 for p in &self.filemap {
185 if cannonical == *p {
186 return index;
187 }
188 index = index + 1;
189 }
190 self.filemap.push(cannonical);
191 return self.filemap.len() - 1;
192 }
193
194 fn file_for_index(&self, i: usize) -> Option<PathBuf> {
195 if i >= self.filemap.len() {
196 return None;
197 }
198 let path = self.filemap[i].strip_prefix(&self.input_folder.canonicalize().unwrap());
199 return Some(path.unwrap().to_path_buf());
200 }
201
202 fn file_for_index_canonical(&self, i: usize) -> Option<&PathBuf> {
203 if i >= self.filemap.len() {
204 return None;
205 }
206 return Some(&self.filemap[i]);
207 }
208
209 // Some weirdly placed + and - 1 because 0 is the default index
210 fn index_of_section_name(&mut self, name: &String) -> usize {
211 let mut index = 0;
212 while index < self.section_name_map.len() {
213 if *name == self.section_name_map[index] {
214 return index + 1;
215 }
216 index += 1;
217 }
218 self.section_name_map.push(name.clone());
219 return self.section_name_map.len();
220 }
221
222 fn section_name_for_index(&self, index: usize) -> Option<&String> {
223 if (index - 1) >= self.section_name_map.len() {
224 return None;
225 }
226 return Some(&self.section_name_map[index - 1]);
227 }
228}