tytanic_core/config.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
//! Reading and interpreting tytanic configuration.
use std::collections::BTreeMap;
use std::{fs, io};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use typst::syntax::package::PackageManifest;
use tytanic_utils::result::ResultEx;
// TODO(tinger): add proper test set collecting and parsing, test sets should be
// overridable in local configs but still fail on duplicate definitions.
/// All valid keys for this config.
pub static KEYS: &[&str] = &["test-set"];
/// The key used to configure tytanic in the manifest tool config.
pub const MANIFEST_TOOL_KEY: &str = crate::TOOL_NAME;
/// The directory name for in which the user config can be found.
pub const CONFIG_SUB_DIRECTORY: &str = crate::TOOL_NAME;
/// A set of config layers used to retrieve options, configs are looked up in
/// the following order:
/// - `override`: supplied at runtime from the command line, envars or other
/// means
/// - `project`: found in the typst.toml manifest
/// - `user`: found in a user config directory
///
/// If none of these configs contain a setting the default is used.
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
pub struct Config {
/// The override config.
pub override_: Option<ConfigLayer>,
/// The project config.
pub project: Option<ConfigLayer>,
/// The user config.
pub user: Option<ConfigLayer>,
}
impl Config {
/// Create a new config with the given overrides.
pub fn new(override_: Option<ConfigLayer>) -> Self {
Self {
override_,
project: None,
user: None,
}
}
}
/// A single layer within all configs, a set of values which can be
/// overridden by more granular configs.
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "kebab-case")]
pub struct ConfigLayer {
/// Custom test set definitions.
pub test_sets: Option<BTreeMap<String, String>>,
}
impl ConfigLayer {
/// Reads the user config at its predefined location.
///
/// The location used is [`dirs::config_dir()`].
pub fn collect_user() -> Result<Option<Self>, ReadError> {
let Some(config_dir) = dirs::config_dir() else {
tracing::warn!("couldn't retrieve user config home");
return Ok(None);
};
let config = config_dir.join(CONFIG_SUB_DIRECTORY).join("config.toml");
let Some(content) =
fs::read_to_string(config).ignore(|err| err.kind() == io::ErrorKind::NotFound)?
else {
return Ok(None);
};
Ok(toml::from_str(&content)?)
}
/// Parses a config from the tool section of a manifest.
pub fn from_manifest(manifest: &PackageManifest) -> Result<Option<Self>, ReadError> {
let Some(section) = manifest.tool.sections.get(MANIFEST_TOOL_KEY) else {
return Ok(None);
};
Self::deserialize(section.clone())
.map(Some)
.map_err(ReadError::Toml)
}
}
/// Returned by [`ConfigLayer::collect_user`] and
/// [`ConfigLayer::from_manifest`].
#[derive(Debug, Error)]
pub enum ReadError {
/// The given key is not valid or the config.
#[error("a toml parsing error occurred")]
Toml(#[from] toml::de::Error),
/// An io error occurred.
#[error("an io error occurred")]
Io(#[from] io::Error),
}