1use std::io::Write;
2use std::ffi::OsStr;
3use std::marker::PhantomData;
4use std::ops::Add;
5use std::path::{Component, Path, PathBuf};
6use std::time::SystemTime;
7
8use globset::{ GlobBuilder, GlobMatcher };
9
10use super::Resource;
11use super::Set;
12
13#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
18pub enum Unit {
19 Dir(Dir),
20 File(File),
21}
22
23impl Unit {
24
25 pub fn path(&self) -> &Path {
28 match self {
29 Unit::Dir(ref res) => res.path(),
30 Unit::File(ref res) => res.path(),
31 }
32 }
33
34 pub fn link_from_inside(&self, dir: &Dir) {
37 match self {
38 Unit::Dir(ref res) => res.link_from_inside(dir),
39 Unit::File(ref res) => res.link_from_inside(dir),
40 }
41 }
42}
43
44impl Resource for Unit {
45 fn timestamp(&self) -> Option<SystemTime> {
47 match self {
48 Unit::Dir(ref res) => res.timestamp(),
49 Unit::File(ref res) => res.timestamp(),
50 }
51 }
52}
53
54#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
59pub struct File {
60 path: PathBuf
61}
62
63impl File {
64
65 pub fn new<P:AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
67 match normalize(path.as_ref()) {
68 Some(path) if path.is_absolute() => Ok(File { path }),
69 _ => Err(format!("Path {0} is not absolute", path.as_ref().display()).into())
70 }
71 }
72
73 pub fn path(&self) -> &Path {
75 &self.path
76 }
77
78 pub fn created(self) -> Self {
81 self.create();
82 self
83 }
84
85 pub fn create(&self) -> std::fs::File {
88 self.create_result().expect(format!("Creating file {} FAILED", self).as_str())
89 }
90
91 pub fn create_result(&self) -> std::io::Result<std::fs::File> {
93 println!("Creating file: {}", self);
94
95 if let Some(parent) = self.parent() {
96 parent.create_result()?;
97 }
98
99 std::fs::File::create(&self.path)
100 }
101
102 pub fn linked_from_inside(self, dir: &Dir) -> Self {
107 dir.file(self.path().file_name().unwrap()).link_to(&self);
108 self
109 }
110
111 pub fn link_from_inside(&self, dir: &Dir) {
116 dir.file(self.path().file_name().unwrap()).link_to(self);
117 }
118
119 pub fn link_from_inside_result(&self, dir: &Dir, force: bool) -> std::io::Result<()> {
125 dir.file(self.path().file_name().unwrap()).link_to_result(self, force)
126 }
127
128 pub fn linked_to(self, to: &File) -> Self {
133 self.link_to(to);
134 self
135 }
136
137 pub fn link_to(&self, to: &File) {
142 self.link_to_result(to, false)
143 .expect(format!("Creating link {} -> {} FAILED", self, to).as_str())
144 }
145
146 pub fn link_to_result(&self, to: &File, force: bool) -> std::io::Result<()> {
153 println!("Creating link {} -> {}", self, to);
154
155 if let Some(parent) = self.parent() {
156 parent.create_result()?;
157 }
158
159 if self.path.exists() {
160 match std::fs::read_link(&self.path) {
161 Ok(target) if target != to.path && force => std::fs::remove_file(self.path())?,
162 Ok(target) if target == to.path => return Ok(()),
163 _ => return Err(std::io::ErrorKind::AlreadyExists.into()),
164 }
165 }
166
167 File::platform_make_link(&to.path, &self.path)
168 }
169
170 pub fn metadata(&self) -> std::fs::Metadata {
173 self.metadata_result().expect(format!("Metatdata query {} FAILED", self).as_str())
174 }
175
176 pub fn metadata_result(&self) -> std::io::Result<std::fs::Metadata> {
178 std::fs::metadata(&self.path)
179 }
180
181 pub fn open(&self) -> std::fs::File {
184 self.open_result().expect(format!("Opening file {} FAILED", self).as_str())
185 }
186
187 pub fn open_result(&self) -> std::io::Result<std::fs::File> {
189 std::fs::File::open(&self.path)
190 }
191
192 pub fn rewrite<P: AsRef<[u8]>>(&self, bytes: P) {
196 self.rewrite_result(bytes).expect(format!("Writing text {} FAILED", self).as_str())
197 }
198
199 pub fn rewrite_result<P: AsRef<[u8]>>(&self, bytes: P) -> std::io::Result<()> {
203 let bytes = bytes.as_ref();
204 if let Ok(old) = std::fs::read(&self.path) {
205 if old == bytes {
206 return Ok(())
207 }
208 }
209
210 self.create().write_all(bytes)
211 }
212
213 pub fn touched(self) -> Self {
216 self.touch();
217 self
218 }
219
220 pub fn touch(&self) {
223 self.touch_result().expect(format!("Touching file {} FAILED", self).as_str())
224 }
225
226 pub fn touch_result(&self) -> std::io::Result<()> {
229 println!("Touching file: {}", self);
230
231 if !self.path.exists() {
232 return self.create_result().map(|_|());
233 }
234
235 let now = filetime::FileTime::from_system_time(SystemTime::now());
236 filetime::set_file_mtime(self.path.clone(), now)
237 }
238
239 fn parent(&self) -> Option<Dir> {
241 self.path.parent().map(|parent| Dir { path: parent.to_owned() })
242 }
243
244 #[cfg(not(windows))]
245 fn platform_make_link<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> std::io::Result<()> {
246 std::os::unix::fs::symlink(src, dst)
247 }
248
249 #[cfg(windows)]
250 fn platform_make_link<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> std::io::Result<()> {
251 std::os::windows::fs::symlink_file(src, dst)
252 }
253}
254
255impl Resource for File {
256
257 fn timestamp(&self) -> Option<SystemTime> {
258 if let Ok(metadata) = self.metadata_result() {
259 return metadata.modified().ok();
260 }
261
262 None
263 }
264}
265
266impl AsRef<File> for File {
267 fn as_ref(&self) -> &File {
268 self
269 }
270}
271
272impl AsRef<OsStr> for File {
273 fn as_ref(&self) -> &OsStr {
274 &self.path.as_ref()
275 }
276}
277
278impl AsRef<Path> for File {
279 fn as_ref(&self) -> &Path {
280 &self.path.as_ref()
281 }
282}
283
284impl Add<&File> for &File {
285 type Output = Set<File>;
286
287 fn add(self, rhs: &File) -> Self::Output {
288 vec![self.clone(), rhs.clone()].into()
289 }
290}
291
292impl Add<&Dir> for &File {
293 type Output = Set<Unit>;
294
295 fn add(self, rhs: &Dir) -> Self::Output {
296 vec![Unit::File(self.clone()), Unit::Dir(rhs.clone())].into()
297 }
298}
299
300impl std::fmt::Display for File {
301 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
302 self.path.display().fmt(formatter)
303 }
304}
305
306#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
311pub struct Dir {
312 path: PathBuf
313}
314
315impl Dir {
316
317 pub fn new<P:AsRef<Path>>(path: P) -> Self {
319 Dir::new_safe(path).unwrap()
320 }
321
322 pub fn new_safe<P:AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
324 match normalize(path.as_ref()) {
325 Some(path) if path.is_absolute() => Ok(Dir { path }),
326 _ => Err(format!("Path {0} is not absolute", path.as_ref().display()).into())
327 }
328 }
329
330 pub fn path(&self) -> &Path {
332 self.path.as_ref()
333 }
334
335 pub fn created(self) -> Self {
338 self.create();
339 self
340 }
341
342 pub fn create(&self) {
345 self.create_result().expect(format!("Creating directory {} FAILED", self).as_str());
346 }
347
348 pub fn create_result(&self) -> std::io::Result<()> {
350 std::fs::create_dir_all(&self.path)
351 }
352
353 pub fn content<G:AsRef<str>>(&self, glob: G) -> DirContent<Unit> {
355 DirContent::new(self.path.clone(), glob)
356 }
357
358 pub fn dirs<G:AsRef<str>>(&self, glob: G) -> DirContent<Dir> {
360 DirContent::new(self.path.clone(), glob)
361 }
362
363 pub fn files<G:AsRef<str>>(&self, glob: G) -> DirContent<File> {
365 DirContent::new(self.path.clone(), glob)
366 }
367
368 pub fn dir<P:AsRef<Path>>(&self, path: P) -> Dir {
372 self.dir_result(path).unwrap()
373 }
374
375 pub fn dir_result<P:AsRef<Path>>(&self, path: P) -> Result<Self, Box<dyn std::error::Error>> {
377 match normalize(path.as_ref()) {
378 Some(path) if path.is_relative() => Ok(Dir { path: self.path.join(path) }),
379 _ => Err(format!("Path {0} is not relative", path.as_ref().display()).into())
380 }
381 }
382
383 pub fn file<P:AsRef<Path>>(&self, path: P) -> File {
387 self.file_result(path).unwrap()
388 }
389
390 pub fn file_result<P:AsRef<Path>>(&self, path: P) -> Result<File, Box<dyn std::error::Error>> {
392 match normalize(path.as_ref()) {
393 Some(path) if path.is_relative() => Ok(File { path: self.path.join(path) }),
394 _ => Err(format!("Path '{0}' is not relative", path.as_ref().display()).into())
395 }
396 }
397
398 pub fn linked_from_inside(self, dir: &Dir) -> Self {
403 dir.dir(self.path().file_name().unwrap()).link_to(&self);
404 self
405 }
406
407 pub fn link_from_inside(&self, dir: &Dir) {
412 dir.dir(self.path().file_name().unwrap()).link_to(self);
413 }
414
415 pub fn link_from_inside_result(&self, dir: &Dir, force: bool) -> std::io::Result<()> {
421 dir.dir(self.path().file_name().unwrap()).link_to_result(self, force)
422 }
423
424 pub fn linked_to(self, to: &Dir) -> Self {
430 self.link_to(to);
431 self
432 }
433
434 pub fn link_to(&self, to: &Dir) {
439 self.link_to_result(to, false)
440 .expect(format!("Creating link {} -> {} FAILED", self, to).as_str())
441 }
442
443 pub fn link_to_result(&self, to: &Dir, force: bool) -> std::io::Result<()> {
450 println!("Creating link {} -> {}", self, to);
451
452 if let Some(parent) = self.parent() {
453 parent.create_result()?;
454 }
455
456 if self.path.exists() {
457
458 match std::fs::read_link(&self.path) {
459 Ok(target) if target != to.path && force => std::fs::remove_file(self.path())?,
460 Ok(target) if target == to.path => return Ok(()),
461 _ => return Err(std::io::ErrorKind::AlreadyExists.into()),
462 }
463 }
464
465 Dir::platform_make_link(&to.path, &self.path)
466 }
467
468 pub fn touched(self) -> Self {
471 self.touch();
472 self
473 }
474
475 pub fn touch(&self) {
478 self.touch_result().expect(format!("Touching dir {} FAILED", self).as_str())
479 }
480
481 pub fn touch_result(&self) -> std::io::Result<()> {
484 println!("Touching dir: {}", self);
485
486 if !self.path.exists() {
487 return self.create_result();
488 }
489
490 let now = filetime::FileTime::from_system_time(SystemTime::now());
491 filetime::set_file_mtime(self.path.clone(), now)
492 }
493
494 fn parent(&self) -> Option<Dir> {
496 self.path.parent().map(|parent| Dir { path: parent.to_owned() })
497 }
498
499 #[cfg(not(windows))]
500 fn platform_make_link<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> std::io::Result<()> {
501 std::os::unix::fs::symlink(src, dst)
502 }
503
504 #[cfg(windows)]
505 fn platform_make_link<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> std::io::Result<()> {
506 std::os::windows::fs::symlink_dir(src, dst)
507 }
508}
509
510impl AsRef<Dir> for Dir {
511 fn as_ref(&self) -> &Dir {
512 self
513 }
514}
515
516impl AsRef<OsStr> for Dir {
517 fn as_ref(&self) -> &OsStr {
518 &self.path.as_ref()
519 }
520}
521
522impl AsRef<Path> for Dir {
523 fn as_ref(&self) -> &Path {
524 &self.path.as_ref()
525 }
526}
527
528impl Resource for Dir {
529 fn timestamp(&self) -> Option<SystemTime> {
530 if let Ok(metadata) = std::fs::metadata(&self.path) {
531 return metadata.modified().ok();
532 }
533
534 None
535 }
536}
537
538impl Add<&Dir> for &Dir {
539 type Output = Set<Dir>;
540
541 fn add(self, rhs: &Dir) -> Self::Output {
542 vec![self.clone(), rhs.clone()].into()
543 }
544}
545
546impl Add<&File> for &Dir {
547 type Output = Set<Unit>;
548
549 fn add(self, rhs: &File) -> Self::Output {
550 vec![Unit::Dir(self.clone()), Unit::File(rhs.clone())].into()
551 }
552}
553
554impl std::fmt::Display for Dir {
555 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
556 self.path.display().fmt(formatter)
557 }
558}
559
560fn normalize<P:AsRef<Path>>(subpath: P) -> Option<PathBuf> {
563 let result = PathBuf::from(subpath.as_ref()).components().fold(Some((PathBuf::new(), 0)), |r, c| {
564 match r {
565 None => None,
566 Some((mut path, depth)) => match c {
567 Component::Normal(value) => {
568 path.push(value);
569 Some((path, depth + 1))
570 }
571 Component::CurDir => {
572 Some((path, depth))
573 }
574 Component::ParentDir => {
575 if depth == 0 {
576 return None
577 }
578 path.pop();
579 Some((path, depth - 1))
580 }
581 Component::RootDir => {
582 path.push("/");
583 Some((path, 0))
584 }
585 Component::Prefix(prefix) => {
586 path.push(prefix.as_os_str());
587 Some((path, -1))
588 }
589 }
590 }
591 });
592
593 if let Some((path,depth)) = result {
594 if depth > 0 {
595 return Some(path)
596 }
597 }
598
599 return None;
600}
601
602#[derive(Clone, Debug)]
611pub struct DirContent<T> {
612 path: PathBuf,
613 matchers: Vec<(GlobMatcher, bool)>,
614 phantom: PhantomData<T>,
615}
616
617impl<T> DirContent<T> {
618
619 fn new<G:AsRef<str>>(path: PathBuf, glob: G) -> Self {
620 DirContent {
621 phantom: PhantomData,
622 path,
623 matchers: vec![compile(true, glob)],
624 }
625 }
626
627 pub fn exclude<G:AsRef<str>>(mut self, glob: G) -> Self {
629 self.matchers.push(compile(false, glob));
630 self
631 }
632
633 pub fn include<G:AsRef<str>>(mut self, glob: G) -> Self {
635 self.matchers.push(compile(true, glob));
636 self
637 }
638
639 fn walkdir(&self) -> impl Iterator<Item=walkdir::DirEntry> {
640 let root = self.path.clone();
641 let matchers = self.matchers.clone();
642 walkdir::WalkDir::new(&self.path)
643 .follow_links(true)
644 .into_iter()
645 .filter_map(|e| e.ok())
646 .filter(move |e| e.depth() > 0 && {
647 let relative = e.path().strip_prefix(&root).unwrap();
648 let mut matched = false;
649 for matcher in &matchers {
650 if matcher.0.is_match(relative) {
651 matched = matcher.1 || return false;
652 }
653 }
654 matched
655 })
656 }
657}
658
659fn compile<G:AsRef<str>>(incl: bool, glob: G) -> (GlobMatcher, bool) {
660 (
661 GlobBuilder::new(glob.as_ref()).literal_separator(true).build().unwrap().compile_matcher(),
662 incl
663 )
664}
665
666impl DirContent<Unit> {
667 fn iter(&self) -> Box<dyn Iterator<Item=Unit>> {
668 Box::new(self.walkdir().map(|e|
669 if e.file_type().is_dir() {
670 Unit::Dir( Dir { path: e.path().to_owned() })
671 } else {
672 Unit::File( File { path: e.path().to_owned() })
673 }
674 ))
675 }
676}
677
678impl DirContent<Dir> {
679 fn iter(&self) -> Box<dyn Iterator<Item=Dir>> {
680 Box::new(self.walkdir().filter_map(|e|
681 if e.file_type().is_dir() {
682 Some(Dir { path: e.path().to_owned() })
683 } else {
684 None
685 }
686 ))
687 }
688}
689
690impl DirContent<File> {
691 fn iter(&self) -> Box<dyn Iterator<Item=File>> {
692 Box::new(self.walkdir().filter_map(|e|
693 if e.file_type().is_file() {
694 Some(File { path: e.path().to_owned() })
695 } else {
696 None
697 }
698 ))
699 }
700}
701
702impl<T> AsRef<DirContent<T>> for DirContent<T> {
703 fn as_ref(&self) -> &DirContent<T> {
704 self
705 }
706}
707
708impl IntoIterator for DirContent<Unit> {
709 type Item = Unit;
710 type IntoIter = Box<dyn Iterator<Item=Self::Item>>;
711
712 fn into_iter(self) -> Self::IntoIter {
713 self.iter()
714 }
715}
716
717impl IntoIterator for DirContent<Dir> {
718 type Item = Dir;
719 type IntoIter = Box<dyn Iterator<Item=Self::Item>>;
720
721 fn into_iter(self) -> Self::IntoIter {
722 self.iter()
723 }
724}
725
726impl IntoIterator for DirContent<File> {
727 type Item = File;
728 type IntoIter = Box<dyn Iterator<Item=Self::Item>>;
729
730 fn into_iter(self) -> Self::IntoIter {
731 self.iter()
732 }
733}
734
735impl Resource for DirContent<Dir> {
736 fn timestamp(&self) -> Option<SystemTime> {
737 super::res::timestamp(self.iter())
738 }
739}
740
741impl Resource for DirContent<File> {
742 fn timestamp(&self) -> Option<SystemTime> {
743 super::res::timestamp(self.iter())
744 }
745}
746
747impl Resource for DirContent<Unit> {
748 fn timestamp(&self) -> Option<SystemTime> {
749 super::res::timestamp(self.iter())
750 }
751}