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}