libscilo/config/
root_dirs.rs

1use std::path::PathBuf;
2
3use crate::{config::file::ConfigFileRootDirs, project_root};
4
5/// Expected top-level directories to organize the project folder.
6#[derive(Clone, Debug)]
7pub struct RootDirs {
8    /// Top-level path for code.
9    ///
10    /// This folder contains all the code used to process files from [`data`][Self::data],
11    /// perform statistical analyses, create visualizations, and save the outputs
12    /// in [`results`][Self::results].
13    /// Generally, this directory contains a series of subdirectories, each of
14    /// which contain a set of related scripts for a single research question.
15    /// For example, each subdirectory may contain:
16    /// - a workflow file (e.g. [`Snakefile`](https://snakemake.github.io/) or [`Nextflow`](https://www.nextflow.io/))
17    /// - a data pre-processing script
18    /// - a statistical analysis and calculation script
19    /// - a plotting script
20    pub(crate) code: Option<PathBuf>,
21
22    /// Top-level path for data.
23    ///
24    /// All raw and processed data should be found within this directory.
25    /// The datasets located here will then be sourced by code within the [`code`][Self::code]
26    /// directory and used to generate outputs in the [`results`][Self::results]
27    /// directory.
28    pub(crate) data: Option<PathBuf>,
29
30    /// Top-level path for results.
31    ///
32    /// Code and scripts in the [`code`][Self::code] directory should create outputs
33    /// in this directory to ensure a separation of inputs, code, and outputs.
34    /// Generally, this directory contains a series of subdirectories, each of
35    /// which will contain all the outputs originating from the code in the
36    /// corresponding [`code`][Self::code] subdirectory.
37    pub(crate) results: Option<PathBuf>,
38
39    /// Top-level path for vendored external code.
40    ///
41    /// In many research projects you will need a copy of some external code,
42    /// such as a [git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules),
43    /// or a package manifest of some kind, like a [Nix derivation](https://nixos.org/)
44    /// or an [Anaconda recipe](https://anaconda.org/).
45    /// This folder can be used to house all of the relevant code so as to not
46    /// populate the custom code present in [`code`][Self::code].
47    pub(crate) external: Option<PathBuf>,
48
49    /// Top-level path for documentation.
50    ///
51    /// Documentation such as a manuscript, experimental descriptions from contract
52    /// research organizations, or interactive HTML notebooks displaying the data
53    /// and its results can go here.
54    /// Data that belongs in [`data`][Self::data] or notebooks which process data
55    /// that belong in [`code`][Self::code] should not be placed in here.
56    pub(crate) docs: Option<PathBuf>,
57}
58
59impl Default for RootDirs {
60    fn default() -> Self {
61        let root = project_root();
62
63        Self {
64            code: Some(root.join("code")),
65            data: Some(root.join("data")),
66            results: Some(root.join("results")),
67            external: Some(root.join("pkgs")),
68            docs: Some(root.join("docs")),
69        }
70    }
71}
72
73impl From<Option<ConfigFileRootDirs>> for RootDirs {
74    fn from(value: Option<ConfigFileRootDirs>) -> Self {
75        let defaults = Self::default();
76
77        match value {
78            Some(dirs) => {
79                let code = parse_root_dir_name(dirs.code.as_deref(), defaults.code);
80                let data = parse_root_dir_name(dirs.data.as_deref(), defaults.data);
81                let docs = parse_root_dir_name(dirs.docs.as_deref(), defaults.docs);
82                let external = parse_root_dir_name(dirs.external.as_deref(), defaults.external);
83                let results = parse_root_dir_name(dirs.results.as_deref(), defaults.results);
84
85                Self {
86                    code,
87                    data,
88                    docs,
89                    external,
90                    results,
91                }
92            }
93            None => RootDirs::default(),
94        }
95    }
96}
97
98impl IntoIterator for RootDirs {
99    type Item = Option<PathBuf>;
100
101    type IntoIter = std::array::IntoIter<Option<PathBuf>, 5>;
102
103    fn into_iter(self) -> Self::IntoIter {
104        [self.code, self.data, self.docs, self.external, self.results].into_iter()
105    }
106}
107
108/// A helper function to parse the directory name for a root directory.
109///
110/// If the directory specified in the configuration file is an empty string,
111/// then it will be considered as `None`.
112fn parse_root_dir_name(s: Option<&str>, default: Option<PathBuf>) -> Option<PathBuf> {
113    match s {
114        Some("") => None,
115        Some(dir_name) => Some(PathBuf::from(dir_name)),
116        None => default,
117    }
118}