devscripts/
config.rs

1//! Devscripts configuration data structures
2
3#![expect(
4    clippy::derive_partial_eq_without_eq,
5    reason = "Non-`Eq` typed attributes may be added \
6              to the configuration in the future."
7)]
8
9#[cfg(feature = "serde")]
10use figment::{
11    providers::{Format, Serialized, Toml},
12    Figment,
13};
14
15#[cfg(feature = "serde")]
16use serde::{Deserialize, Serialize};
17use std::path::PathBuf;
18
19#[cfg(feature = "serde")]
20use crate::path::git_root;
21
22/// Main configuration data structure for devscripts.
23///
24/// Use [`default()`](Self::default) to obtain the default configuration.
25#[cfg_attr(
26    feature = "serde",
27    doc = "[`read()`](Self::read) and [`figment()`](Self::figment) can be used to obtain configuration from files."
28)]
29#[derive(Debug, Default, Clone, PartialEq)]
30#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
31#[cfg_attr(feature = "serde", serde(default))]
32pub struct Config {
33    /// Options specifying paths or lists of paths
34    pub paths: Paths,
35}
36
37/// Options specifying paths or lists of paths
38#[derive(Debug, Default, Clone, PartialEq)]
39#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
40#[cfg_attr(feature = "serde", serde(default))]
41pub struct Paths {
42    /// Search paths for script files
43    pub scripts: ScriptPaths,
44}
45
46/// Search paths for script files
47#[derive(Debug, Clone, PartialEq)]
48#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
49#[cfg_attr(feature = "serde", serde(default))]
50pub struct ScriptPaths {
51    /// List of directories containing system-wide scripts.
52    ///
53    /// (Default: `/usr/share/devscripts`, `/usr/local/share/devscripts`)
54    pub system: Vec<PathBuf>,
55
56    /// List of directories containing user-specific scripts.
57    ///
58    /// (Default: `~/.local/share/devscripts`)
59    pub user: Vec<PathBuf>,
60
61    /// List of directories containing repository-specific scripts.
62    ///
63    /// These paths are expected to be relative to the root directory of a git
64    /// repository if the current working directory is located inside a git
65    /// repository. If not inside of a git repository, this option is ignored.
66    ///
67    /// (Default: `./.devscripts`)
68    pub repository: Vec<PathBuf>,
69}
70
71impl Config {
72    /// Figment for reading this config, provided with default files.
73    ///
74    /// Default configuration files:
75    ///   - `/etc/devscripts/config.toml`
76    ///   - `~/.config/devscripts/config.toml`
77    ///   - `<repository-root>/.devscripts/.config.toml`.
78    #[cfg(feature = "serde")]
79    pub fn figment() -> Figment {
80        let user_home = home::home_dir().unwrap_or_default();
81        let git_root = git_root().unwrap_or_default();
82
83        let mut figment = Figment::new()
84            .merge(Serialized::defaults(Self::default()))
85            .merge(Toml::file("/etc/devscripts/config.toml"))
86            .merge(Toml::file(
87                user_home.join("./.config/devscripts/config.toml"),
88            ));
89
90        if let Some(git_root) = git_root {
91            figment = figment.merge(Toml::file(git_root.join("./.devscripts/.config.toml")));
92        }
93
94        figment
95    }
96
97    /// Read configuration from files.
98    ///
99    /// For a list of default configuration files,
100    /// see [`figment()`](Self::figment).
101    #[cfg(feature = "serde")]
102    pub fn read() -> Result<Self, figment::Error> {
103        Self::figment().extract()
104    }
105}
106
107impl Default for ScriptPaths {
108    fn default() -> Self {
109        Self {
110            system: vec![
111                "/usr/share/devscripts".into(),
112                "/usr/local/share/devscripts".into(),
113            ],
114            user: vec!["~/.local/share/devscripts".into()],
115            repository: vec!["./.devscripts".into()],
116        }
117    }
118}