punktf_lib/profile/source.rs
1//! Provides definitions for the `punktf` source directory.
2//!
3//! The source directory is the central repository used to store
4//! [`Profile`s](`crate::profile::Profile`) and [`Dotfile`s](`crate::profile::dotfile::Dotfile`).
5//! `punktf` will only read data from these directories but never write to them.
6//!
7//! The current structure looks something like this:
8//!
9//! ```text
10//! root/
11//! + profiles/
12//! ...
13//! + dotfiles/
14//! ...
15//! ```
16
17use color_eyre::eyre::Context as _;
18use std::path::{Path, PathBuf};
19
20/// This struct represents the source directory used by `punktf`. The source
21/// directory is the central repository used to store
22/// [`Profile`s](`crate::profile::Profile`) and [`Dotfile`s](`crate::profile::dotfile::Dotfile`).
23/// `punktf` will only read data from these directories but never write to them.
24///
25/// The current structure looks something like this:
26///
27/// ```text
28/// root/
29/// + profiles/
30/// ...
31/// + dotfiles/
32/// ...
33/// ```
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct PunktfSource {
36 /// The absolute root source path.
37 pub root: PathBuf,
38
39 /// The absolute path to the `profiles` directory.
40 pub profiles: PathBuf,
41
42 /// The absolute path to the `dotfiles` directory.
43 pub dotfiles: PathBuf,
44}
45
46impl PunktfSource {
47 /// Creates a instance from a `root` directory. During instantiation it
48 /// checks if the `root` exists and is a directory. These checks will also
49 /// be run for the `root/profiles` and `root/dotfiles` subdirectories. All
50 /// the above mentioned paths will also be resolved by calling
51 /// [`std::path::Path::canonicalize`].
52 ///
53 /// # Errors
54 ///
55 /// If any of the checks fail an error will be returned.
56 pub fn from_root(root: PathBuf) -> color_eyre::Result<Self> {
57 /// Tries to create a directory if it does not exist.
58 /// Bubbles up any error encountered and add some context to it.
59 macro_rules! try_exists {
60 ( $var:ident ) => {
61 // TODO: Replace once `try_exists` becomes stable
62 if $var.exists() {
63 // Should check if read/write is possible
64 } else {
65 let _ = std::fs::create_dir(&$var).wrap_err_with(|| {
66 format!(
67 "{} directory does not exist and could not be created (path: {})",
68 stringify!($var),
69 $var.display()
70 )
71 })?;
72 }
73 };
74 }
75
76 /// Tries to canonicalize/resolve a path.
77 /// Bubbles up any error encountered and add some context to it.
78 macro_rules! try_canonicalize {
79 ($var:ident) => {
80 $var.canonicalize().wrap_err_with(|| {
81 format!(
82 "Failed to resolve punktf's {} directory (path: {})",
83 stringify!($var),
84 $var.display()
85 )
86 })?
87 };
88 }
89
90 // Renames the `root` variable for better error messages
91 let source = root;
92 try_exists!(source);
93 let source = try_canonicalize!(source);
94
95 let profiles = source.join("profiles");
96 try_exists!(profiles);
97 let profiles = try_canonicalize!(profiles);
98
99 let dotfiles = source.join("dotfiles");
100 try_exists!(dotfiles);
101 let dotfiles = try_canonicalize!(dotfiles);
102
103 Ok(Self {
104 root: source,
105 profiles,
106 dotfiles,
107 })
108 }
109
110 /// Returns the absolute path for the `root` directory.
111 pub fn root(&self) -> &Path {
112 &self.root
113 }
114
115 /// Returns the absolute path to the `root/profiles` directory.
116 pub fn profiles(&self) -> &Path {
117 &self.profiles
118 }
119
120 /// Returns the absolute path to the `root/dotfiles` directory.
121 pub fn dotfiles(&self) -> &Path {
122 &self.dotfiles
123 }
124}