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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
/*
* SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
* SPDX-License-Identifier: BSD-2-Clause
*/
//! Common definitions used by the various `confget` modules.
//!
//! This module mainly defines the [`Config`] struct that provides
//! configuration settings for pretty much all of the functions in
//! the `confget` library. The easiest way to use it is to call
//! the [`Config::default`] method and override some settings as
//! needed:
//!
//! ```
//! let config = confget::Config {
//! filename: Some("/etc/config.ini".to_string()),
//! section: "client".to_string(),
//! ..confget::Config::default()
//! };
//! ```
use std::collections::HashMap;
use std::str::FromStr;
use anyhow::Error as AnyError;
use regex::Error as RegexError;
use thiserror::Error;
/// An error that occurred during processing the input data.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ConfgetError {
/// Invalid configuration, probably command-line.
#[error("Invalid configuration specified: {0}")]
Config(String),
/// Invalid data read from an input file.
#[error("Could not parse the '{0}' file")]
FileFormat(String, #[source] AnyError),
/// Invalid glob/fnmatch pattern.
#[error("Could not parse the '{0}' glob pattern")]
Glob(String, #[source] AnyError),
/// Something went really wrong somewhere.
#[error("Internal confget error: {0}")]
Internal(String),
/// Could not read the input data.
#[error("Could not read the input data")]
ReadData(#[source] AnyError),
/// Invalid regular expression.
#[error("Could not compile the '{0}' regular expression")]
Regex(String, #[source] RegexError),
/// Invalid backend type.
#[error("Unrecognized backend type '{0}'")]
UnknownBackend(String),
}
/// The configuration backend to use for parsing the input data.
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum BackendKind {
#[cfg(feature = "ini-nom")]
/// Parse INI-style files and their sections using the Nom-based backend.
IniNom,
#[cfg(feature = "ini-regex")]
/// Parse INI-style files and their sections using the legacy regex-based backend.
IniRE,
}
impl BackendKind {
#[cfg(feature = "ini-nom")]
/// The name of the Nom-based INI-style file backend.
pub const INI_NOM: &'static str = "ini-nom";
#[cfg(feature = "ini-regex")]
/// The name of the legacy regex-based INI-style file backend.
pub const INI_RE: &'static str = "ini-regex";
/// Return the most suitable backend type for INI-style files.
#[inline]
#[must_use]
pub const fn get_preferred_ini_backend() -> Self {
#[cfg(feature = "ini-nom")]
return Self::IniNom;
#[cfg(not(feature = "ini-nom"))]
#[cfg(feature = "ini-regex")]
return Self::IniRE;
}
/// Return the name of the most suitable backend type for INI-style files.
#[inline]
#[must_use]
pub const fn get_preferred_ini_backend_name() -> &'static str {
#[cfg(feature = "ini-nom")]
return Self::INI_NOM;
#[cfg(not(feature = "ini-nom"))]
#[cfg(feature = "ini-regex")]
return Self::INI_RE;
}
}
impl FromStr for BackendKind {
type Err = ConfgetError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
#[cfg(feature = "ini-nom")]
Self::INI_NOM => Ok(Self::IniNom),
#[cfg(feature = "ini-regex")]
Self::INI_RE => Ok(Self::IniRE),
other => Err(ConfgetError::UnknownBackend(other.to_owned())),
}
}
}
impl AsRef<str> for BackendKind {
#[inline]
fn as_ref(&self) -> &str {
match *self {
#[cfg(feature = "ini-nom")]
Self::IniNom => Self::INI_NOM,
#[cfg(feature = "ini-regex")]
Self::IniRE => Self::INI_RE,
}
}
}
/// Configuration settings for the `confget` functions.
///
/// This is the main way to control the behavior of a backend's
/// [`read_file`][`crate::backend::Backend::read_file`] method,
/// the [`read_ini_file`][`crate::read_ini_file`] function, and
/// the [`format::filter_vars`][`crate::format::filter_vars`] function:
/// specify what file to read, what variables to extract from it, and
/// how to format them.
#[derive(Debug)]
#[allow(clippy::exhaustive_structs)]
#[allow(clippy::struct_excessive_bools)]
pub struct Config {
/// The configuration backend to use.
pub backend: BackendKind,
/// The encoding the input file is in, or "" for the locale default.
pub encoding: String,
/// The (backend-specific) filename to read data from.
pub filename: Option<String>,
/// Formatting: select all the variables in the specified section.
pub list_all: bool,
/// Formatting: treat the variable match patterns as regular expressions
/// instead of glob ones.
pub match_regex: bool,
/// Formatting: treat `varnames` as a list of patterns, not exact
/// variable names.
pub match_var_names: bool,
/// Formatting: only select variables with values that match a pattern.
pub match_var_values: Option<String>,
/// Formatting: specify a string to prepend to the variable name.
pub name_prefix: String,
/// Formatting: specify a string to append to the variable name.
pub name_suffix: String,
/// Formatting: select variables from the specified section.
pub section: String,
/// Formatting: read variables from the initial section (""), then
/// override their values with variables from the one specified by
/// the `section` field.
pub section_override: bool,
/// Formatting: if `section` is an empty string and there are no
/// variables in the initial section, do not select the first section
/// defined in the file.
pub section_specified: bool,
/// Formatting: make the output values suitable for parsing by
/// Bourne-like shells.
pub shell_escape: bool,
/// Formatting: always display the variable name.
pub show_var_name: bool,
/// Formatting: select variable names or patterns to display.
pub varnames: Vec<String>,
}
impl Default for Config {
/// Initialize a [`Config`] object with default values.
///
/// This is the recommended way to use the [`Config`] struct:
/// only override the settings that must be overridden.
#[inline]
fn default() -> Self {
Self {
backend: BackendKind::get_preferred_ini_backend(),
encoding: String::new(),
filename: None,
list_all: false,
match_regex: false,
match_var_names: false,
match_var_values: None,
name_prefix: String::new(),
name_suffix: String::new(),
section: String::new(),
section_override: false,
section_specified: false,
shell_escape: false,
show_var_name: false,
varnames: Vec::new(),
}
}
}
/// Variable/value pairs from a single section.
pub type SectionData = HashMap<String, String>;
/// Section/variable/value information read from a file.
pub type FileData = HashMap<String, SectionData>;