tytanic_core/
config.rs

1//! Reading and interpreting tytanic configuration.
2
3use std::{fs, io};
4
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7use tytanic_utils::result::{io_not_found, ResultEx};
8
9/// The key used to configure tytanic in the manifest tool config.
10pub const MANIFEST_TOOL_KEY: &str = crate::TOOL_NAME;
11
12/// The directory name for in which the user config can be found.
13pub const CONFIG_SUB_DIRECTORY: &str = crate::TOOL_NAME;
14
15/// A system config, found in the user's `$XDG_CONFIG_HOME` or globally on the
16/// system.
17#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
18#[serde(deny_unknown_fields)]
19#[serde(rename_all = "kebab-case")]
20pub struct SystemConfig {}
21
22impl SystemConfig {
23    /// Reads the user config at its predefined location.
24    ///
25    /// The location used is [`dirs::config_dir()`].
26    pub fn collect_user() -> Result<Option<Self>, Error> {
27        let Some(config_dir) = dirs::config_dir() else {
28            tracing::warn!("couldn't retrieve user config home");
29            return Ok(None);
30        };
31
32        let config = config_dir.join(CONFIG_SUB_DIRECTORY).join("config.toml");
33        let Some(content) = fs::read_to_string(config).ignore(io_not_found)? else {
34            return Ok(None);
35        };
36
37        Ok(toml::from_str(&content)?)
38    }
39}
40
41/// A project config, read from a project's manifest.
42#[derive(Debug, Clone, PartialEq, Deserialize)]
43#[serde(deny_unknown_fields)]
44#[serde(rename_all = "kebab-case")]
45pub struct ProjectConfig {
46    /// Custom test root directory.
47    ///
48    /// Defaults to `"tests"`.
49    #[serde(rename = "tests", default = "default_unit_tests_root")]
50    pub unit_tests_root: String,
51
52    /// The project wide defaults.
53    #[serde(rename = "default")]
54    pub defaults: ProjectDefaults,
55}
56
57impl Default for ProjectConfig {
58    fn default() -> Self {
59        Self {
60            unit_tests_root: default_unit_tests_root(),
61            defaults: ProjectDefaults::default(),
62        }
63    }
64}
65
66fn default_unit_tests_root() -> String {
67    String::from("tests")
68}
69
70#[derive(Debug, Clone, PartialEq, Deserialize)]
71#[serde(deny_unknown_fields)]
72#[serde(rename_all = "kebab-case")]
73pub struct ProjectDefaults {
74    /// The default direction.
75    #[serde(rename = "dir", default = "default_direction")]
76    pub direction: Direction,
77
78    /// The default pixel per inch for exporting and comparing documents.
79    ///
80    /// Defaults to `144.0`.
81    #[serde(default = "default_ppi")]
82    pub ppi: f32,
83
84    /// The default maximum allowed delta per pixel.
85    ///
86    /// Defaults to `1`.
87    #[serde(default = "default_max_delta")]
88    pub max_delta: u8,
89
90    /// The default maximum allowed deviating pixels for a comparison.
91    ///
92    /// Defaults to `0`.
93    #[serde(default = "default_max_deviations")]
94    pub max_deviations: usize,
95}
96
97impl Default for ProjectDefaults {
98    fn default() -> Self {
99        Self {
100            direction: default_direction(),
101            ppi: default_ppi(),
102            max_delta: default_max_delta(),
103            max_deviations: default_max_deviations(),
104        }
105    }
106}
107
108fn default_direction() -> Direction {
109    Direction::Ltr
110}
111
112fn default_ppi() -> f32 {
113    144.0
114}
115
116fn default_max_delta() -> u8 {
117    1
118}
119
120fn default_max_deviations() -> usize {
121    0
122}
123
124/// The reading direction of a document.
125#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
126#[serde(rename_all = "kebab-case")]
127pub enum Direction {
128    /// The documents are generated left-to-right.
129    #[default]
130    Ltr,
131
132    /// The documents are generated right-to-left.
133    Rtl,
134}
135
136/// Returned by [`SystemConfig::collect_user`].
137#[derive(Debug, Error)]
138pub enum Error {
139    /// The given key is not valid or the config.
140    #[error("a toml parsing error occurred")]
141    Toml(#[from] toml::de::Error),
142
143    /// An io error occurred.
144    #[error("an io error occurred")]
145    Io(#[from] io::Error),
146}