assemble_core/project/
finder.rs1use crate::identifier::{TaskId, ID_SEPARATOR};
4use crate::prelude::ProjectId;
5use crate::project;
6use crate::project::shared::SharedProject;
7use crate::project::{GetProjectId, ProjectError, ProjectResult};
8use crate::task::HasTaskId;
9use itertools::Itertools;
10use std::borrow::Borrow;
11use std::collections::VecDeque;
12use std::fmt::{Display, Formatter};
13use std::iter::FusedIterator;
14use std::mem::transmute;
15use std::ops::Deref;
16
17#[derive(Debug)]
37pub struct ProjectFinder {
38 project: SharedProject,
39}
40
41impl ProjectFinder {
42 pub fn new(project: &SharedProject) -> Self {
44 Self {
45 project: project.clone(),
46 }
47 }
48 pub fn find<S: AsRef<ProjectPath>>(&self, id: S) -> Option<SharedProject> {
52 let path = id.as_ref();
53
54 let mut project_ptr = Some(self.project.clone());
55 let mut at_root = |ptr: &Option<SharedProject>| -> bool {
56 ptr.as_ref().map(|p| p.is_root()).unwrap_or(false)
57 };
58
59 for component in path.components() {
60 match component {
61 PathComponent::Root => {
62 project_ptr = Some(self.project.with(|p| p.root_project()));
63 }
64 PathComponent::Normal(normal) => {
65 if at_root(&project_ptr) && project_ptr.as_ref().unwrap().project_id() == normal
66 {
67 continue;
68 }
69 project_ptr = project_ptr.and_then(|s| s.get_subproject(normal).ok());
70 }
71 }
72 }
73
74 project_ptr
75 }
76}
77
78#[derive(Debug, Eq, PartialEq, Hash)]
80#[repr(transparent)]
81pub struct ProjectPath {
82 path: str,
83}
84
85impl ProjectPath {
86 pub fn new(path: &str) -> &Self {
88 unsafe { transmute(path) }
89 }
90
91 pub fn is_empty(&self) -> bool {
93 self.components().count() == 0
94 }
95
96 pub fn components(&self) -> PathComponents<'_> {
98 let mut comp = vec![];
99 let mut path = &self.path;
100 if path.starts_with(ID_SEPARATOR) {
101 comp.push(PathComponent::Root);
102 path = &path[1..];
103 }
104
105 comp.extend(
106 path.split_terminator(ID_SEPARATOR)
107 .map(|s| PathComponent::Normal(s)),
108 );
109
110 PathComponents {
111 comps: comp,
112 index: 0,
113 }
114 }
115}
116
117impl AsRef<ProjectPath> for ProjectPath {
118 fn as_ref(&self) -> &ProjectPath {
119 self
120 }
121}
122
123impl Borrow<ProjectPath> for ProjectPathBuf {
124 fn borrow(&self) -> &ProjectPath {
125 self.as_ref()
126 }
127}
128
129impl ToOwned for ProjectPath {
130 type Owned = ProjectPathBuf;
131
132 fn to_owned(&self) -> Self::Owned {
133 ProjectPathBuf::new(self.path.to_string())
134 }
135}
136
137impl<'a> IntoIterator for &'a ProjectPath {
138 type Item = PathComponent<'a>;
139 type IntoIter = PathComponents<'a>;
140
141 fn into_iter(self) -> Self::IntoIter {
142 self.components()
143 }
144}
145
146#[derive(Debug, Clone, Eq, PartialEq)]
148pub enum PathComponent<'a> {
149 Root,
150 Normal(&'a str),
151}
152
153#[derive(Debug, Clone, Eq, PartialEq, Hash)]
155pub struct ProjectPathBuf {
156 string: String,
157}
158
159impl ProjectPathBuf {
160 pub fn new(path: String) -> ProjectPathBuf {
162 ProjectPathBuf { string: path }
163 }
164}
165
166impl From<&ProjectPath> for ProjectPathBuf {
167 fn from(value: &ProjectPath) -> Self {
168 ProjectPathBuf::new(value.path.to_string())
169 }
170}
171
172impl AsRef<ProjectPath> for ProjectPathBuf {
173 fn as_ref(&self) -> &ProjectPath {
174 ProjectPath::new(&self.string)
175 }
176}
177
178impl AsRef<ProjectPath> for &str {
179 fn as_ref(&self) -> &ProjectPath {
180 ProjectPath::new(*self)
181 }
182}
183
184impl AsRef<ProjectPath> for String {
185 fn as_ref(&self) -> &ProjectPath {
186 ProjectPath::new(&self)
187 }
188}
189
190impl<S: AsRef<str>> From<S> for ProjectPathBuf {
191 fn from(value: S) -> Self {
192 Self::new(value.as_ref().to_string())
193 }
194}
195
196impl From<ProjectId> for ProjectPathBuf {
197 fn from(value: ProjectId) -> Self {
198 ProjectPathBuf::new(value.to_string())
199 }
200}
201
202impl Deref for ProjectPathBuf {
203 type Target = ProjectPath;
204
205 fn deref(&self) -> &Self::Target {
206 self.as_ref()
207 }
208}
209
210#[derive(Debug)]
212pub struct PathComponents<'a> {
213 comps: Vec<PathComponent<'a>>,
214 index: usize,
215}
216
217impl<'a> Iterator for PathComponents<'a> {
218 type Item = PathComponent<'a>;
219
220 fn next(&mut self) -> Option<Self::Item> {
221 let out = self.comps.get(self.index).cloned();
222 if out.is_some() {
223 self.index += 1;
224 }
225 out
226 }
227}
228
229impl FusedIterator for PathComponents<'_> {}
230
231#[derive(Debug, Eq, PartialEq, Hash)]
233#[repr(transparent)]
234pub struct TaskPath {
235 path: str,
236}
237
238impl TaskPath {
239 pub fn new(path: &str) -> &Self {
241 unsafe { transmute(path) }
242 }
243
244 fn split(&self) -> (&ProjectPath, &str) {
245 let mut sep: VecDeque<&str> = self.path.rsplitn(2, ID_SEPARATOR).collect();
246 let task = sep.pop_front().expect("one always expected");
247 if let Some(rest) = sep.pop_front() {
248 let project_path = if rest.is_empty() {
249 ProjectPath::new(":")
250 } else {
251 ProjectPath::new(rest)
252 };
253 (project_path, task)
254 } else {
255 (ProjectPath::new(""), task)
256 }
257 }
258
259 pub fn project(&self) -> &ProjectPath {
261 self.split().0
262 }
263
264 pub fn task(&self) -> &str {
266 self.split().1
267 }
268}
269
270impl ToOwned for TaskPath {
271 type Owned = TaskPathBuf;
272
273 fn to_owned(&self) -> Self::Owned {
274 TaskPathBuf::from(self)
275 }
276}
277
278impl Borrow<TaskPath> for TaskPathBuf {
279 fn borrow(&self) -> &TaskPath {
280 self.as_ref()
281 }
282}
283
284impl AsRef<TaskPath> for TaskPath {
285 fn as_ref(&self) -> &TaskPath {
286 self
287 }
288}
289impl AsRef<str> for TaskPath {
290 fn as_ref(&self) -> &str {
291 &self.path
292 }
293}
294
295impl Display for TaskPath {
296 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
297 write!(f, "{}", &self.path)
298 }
299}
300
301#[derive(Debug, Clone, Eq, PartialEq, Hash)]
303pub struct TaskPathBuf {
304 src: String,
305}
306
307impl TaskPathBuf {
308 pub fn new(src: String) -> Self {
310 Self { src }
311 }
312}
313
314impl AsRef<str> for TaskPathBuf {
315 fn as_ref(&self) -> &str {
316 &self.src
317 }
318}
319
320impl AsRef<TaskPath> for TaskPathBuf {
321 fn as_ref(&self) -> &TaskPath {
322 TaskPath::new(&self.src)
323 }
324}
325
326impl AsRef<TaskPath> for &str {
327 fn as_ref(&self) -> &TaskPath {
328 TaskPath::new(*self)
329 }
330}
331
332impl AsRef<TaskPath> for String {
333 fn as_ref(&self) -> &TaskPath {
334 TaskPath::new(&self)
335 }
336}
337
338impl From<&TaskPath> for TaskPathBuf {
339 fn from(value: &TaskPath) -> Self {
340 TaskPathBuf::new(value.path.to_string())
341 }
342}
343
344impl From<String> for TaskPathBuf {
345 fn from(value: String) -> Self {
346 Self::new(value)
347 }
348}
349
350impl From<&str> for TaskPathBuf {
351 fn from(value: &str) -> Self {
352 Self::new(value.to_string())
353 }
354}
355
356impl From<TaskId> for TaskPathBuf {
357 fn from(value: TaskId) -> Self {
358 Self::from(value.to_string())
359 }
360}
361
362impl Deref for TaskPathBuf {
363 type Target = TaskPath;
364
365 fn deref(&self) -> &Self::Target {
366 self.as_ref()
367 }
368}
369
370#[derive(Debug)]
372pub struct TaskFinder {
373 project: SharedProject,
374}
375
376impl TaskFinder {
377 pub fn new(project: &SharedProject) -> Self {
379 Self {
380 project: project.clone(),
381 }
382 }
383
384 pub fn find<T: AsRef<TaskPath>>(&self, task_path: T) -> ProjectResult<Option<Vec<TaskId>>> {
386 let (project, task) = task_path.as_ref().split();
387 trace!(
388 "searching for ({:?}, {:?}) from {}",
389 project,
390 task,
391 self.project
392 );
393 let proj_finder = ProjectFinder::new(&self.project);
394 let proj = proj_finder
395 .find(project)
396 .ok_or(ProjectError::ProjectNotFound(project.to_owned()))?;
397 trace!("found proj: {}", proj);
398
399 let mut output = vec![];
400
401 if let Ok(task_id) = proj.task_id_factory().create(task) {
402 trace!("checking if {} exists", task_id);
403 if let Ok(task) = proj.get_task(&task_id) {
404 if project.is_empty() && !task.only_current() {
405 output.push(task.task_id());
406 } else {
407 trace!("exiting immediately with {}", task.task_id());
408 return Ok(Some(vec![task.task_id()]));
409 }
410 }
411 }
412
413 for registered_task in proj.task_container().get_tasks() {
414 trace!("registered task: {}", registered_task);
415 }
417
418 if project.is_empty() {
419 self.project.with(|p| {
420 for subproject in p.subprojects() {
421 let finder = TaskFinder::new(subproject);
422 if let Ok(Some(tasks)) = finder.find(task) {
423 output.extend(tasks);
424 }
425 }
426 });
427 }
428
429 if output.is_empty() {
430 Ok(None)
431 } else {
432 Ok(Some(output))
433 }
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440 use crate::defaults::tasks::Empty;
441 use crate::project::dev::quick_create;
442 use crate::Project;
443 use std::cell::{Cell, Ref, RefCell};
444 use toml::toml;
445
446 fn init() -> SharedProject {
447 quick_create(
448 r"
449 root:
450 - sub1:
451 - sub2:
452 ",
453 )
454 .expect("couldn't create a project")
455 }
456
457 #[test]
458 fn project_relative_search() {
459 let project = init();
460 let sub1 = project.get_subproject("sub1").unwrap();
461 let finder = ProjectFinder::new(&sub1);
462
463 assert_eq!(finder.find("").unwrap().project_id(), ":root:sub1");
464 assert_eq!(finder.find("sub2").unwrap().project_id(), ":root:sub1:sub2");
465 }
466
467 #[test]
468 fn project_absolute_search() {
469 let project = init();
470 let sub1 = project.get_subproject("sub1").unwrap();
471 let finder = ProjectFinder::new(&sub1);
472 assert_eq!(finder.find(":").unwrap().project_id(), ":root");
475 assert_eq!(finder.find(":root").unwrap().project_id(), ":root");
476 assert_eq!(finder.find(":sub1").unwrap().project_id(), ":root:sub1");
477 assert_eq!(
478 finder.find(":sub1:sub2").unwrap().project_id(),
479 ":root:sub1:sub2"
480 );
481 assert!(
482 finder.find(":sub2").is_none(),
483 "sub2 is not a child of the root"
484 );
485 }
486
487 #[test]
488 fn collect_relative_tasks() -> ProjectResult {
489 let project = quick_create(
490 r"
491 parent:
492 - mid1:
493 - child1
494 - child2
495 - mid2:
496 - child4
497 ",
498 )?;
499
500 let mut count = RefCell::new(0_usize);
501 project.allprojects_mut(|project| {
502 project
503 .task_container_mut()
504 .register_task::<Empty>("taskName")
505 .expect("couldnt register task");
506 *count.borrow_mut() += 1;
507 });
508
509 let finder = TaskFinder::new(&project);
510 let found = finder.find("taskName").unwrap().unwrap_or_default();
511 println!("found: {:#?}", found);
512
513 assert_eq!(
514 found.len(),
515 *count.borrow(),
516 "all registered tasks of taskName should be found"
517 );
518
519 Ok(())
520 }
521
522 #[test]
523 fn abs_works() -> ProjectResult {
524 let project = quick_create(
525 r"
526 parent:
527 - mid1:
528 - child1
529 - child2
530 ",
531 )?;
532
533 let mut count = RefCell::new(0_usize);
534 project.allprojects_mut(|project| {
535 project
536 .task_container_mut()
537 .register_task::<Empty>("taskName")
538 .expect("couldnt register task");
539 *count.borrow_mut() += 1;
540 });
541
542 let finder = TaskFinder::new(&project.get_subproject(":mid1")?);
543 let found = finder.find(":taskName").unwrap().unwrap_or_default();
544 println!("found: {:#?}", found);
545
546 assert_eq!(found.len(), 1, "only one returned");
547 assert_eq!(&found[0], &TaskId::new(":parent:taskName").unwrap());
548
549 Ok(())
550 }
551}