confget/
defs.rs

1/*
2 * SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
3 * SPDX-License-Identifier: BSD-2-Clause
4 */
5//! Common definitions used by the various `confget` modules.
6//!
7//! This module mainly defines the [`Config`] struct that provides
8//! configuration settings for pretty much all of the functions in
9//! the `confget` library. The easiest way to use it is to call
10//! the [`Config::default`] method and override some settings as
11//! needed:
12//!
13//! ```
14//! let config = confget::Config {
15//!     filename: Some("/etc/config.ini".to_string()),
16//!     section: "client".to_string(),
17//!     ..confget::Config::default()
18//! };
19//! ```
20
21use std::collections::HashMap;
22use std::str::FromStr;
23
24use anyhow::Error as AnyError;
25use regex::Error as RegexError;
26use thiserror::Error;
27
28/// An error that occurred during processing the input data.
29#[derive(Debug, Error)]
30#[non_exhaustive]
31pub enum ConfgetError {
32    /// Invalid configuration, probably command-line.
33    #[error("Invalid configuration specified: {0}")]
34    Config(String),
35
36    /// Invalid data read from an input file.
37    #[error("Could not parse the '{0}' file")]
38    FileFormat(String, #[source] AnyError),
39
40    /// Invalid glob/fnmatch pattern.
41    #[error("Could not parse the '{0}' glob pattern")]
42    Glob(String, #[source] AnyError),
43
44    /// Something went really wrong somewhere.
45    #[error("Internal confget error: {0}")]
46    Internal(String),
47
48    /// Could not read the input data.
49    #[error("Could not read the input data")]
50    ReadData(#[source] AnyError),
51
52    /// Invalid regular expression.
53    #[error("Could not compile the '{0}' regular expression")]
54    Regex(String, #[source] RegexError),
55
56    /// Invalid backend type.
57    #[error("Unrecognized backend type '{0}'")]
58    UnknownBackend(String),
59}
60
61/// The configuration backend to use for parsing the input data.
62#[derive(Debug, PartialEq, Eq)]
63#[non_exhaustive]
64pub enum BackendKind {
65    #[cfg(feature = "ini-nom")]
66    /// Parse INI-style files and their sections using the Nom-based backend.
67    IniNom,
68
69    #[cfg(feature = "ini-regex")]
70    /// Parse INI-style files and their sections using the legacy regex-based backend.
71    IniRE,
72}
73
74impl BackendKind {
75    #[cfg(feature = "ini-nom")]
76    /// The name of the Nom-based INI-style file backend.
77    pub const INI_NOM: &'static str = "ini-nom";
78
79    #[cfg(feature = "ini-regex")]
80    /// The name of the legacy regex-based INI-style file backend.
81    pub const INI_RE: &'static str = "ini-regex";
82
83    /// Return the most suitable backend type for INI-style files.
84    #[inline]
85    #[must_use]
86    pub const fn get_preferred_ini_backend() -> Self {
87        #[cfg(feature = "ini-nom")]
88        return Self::IniNom;
89
90        #[cfg(not(feature = "ini-nom"))]
91        #[cfg(feature = "ini-regex")]
92        return Self::IniRE;
93    }
94
95    /// Return the name of the most suitable backend type for INI-style files.
96    #[inline]
97    #[must_use]
98    pub const fn get_preferred_ini_backend_name() -> &'static str {
99        #[cfg(feature = "ini-nom")]
100        return Self::INI_NOM;
101
102        #[cfg(not(feature = "ini-nom"))]
103        #[cfg(feature = "ini-regex")]
104        return Self::INI_RE;
105    }
106}
107
108impl FromStr for BackendKind {
109    type Err = ConfgetError;
110
111    #[inline]
112    fn from_str(s: &str) -> Result<Self, Self::Err> {
113        match s {
114            #[cfg(feature = "ini-nom")]
115            Self::INI_NOM => Ok(Self::IniNom),
116
117            #[cfg(feature = "ini-regex")]
118            Self::INI_RE => Ok(Self::IniRE),
119
120            other => Err(ConfgetError::UnknownBackend(other.to_owned())),
121        }
122    }
123}
124
125impl AsRef<str> for BackendKind {
126    #[inline]
127    fn as_ref(&self) -> &str {
128        match *self {
129            #[cfg(feature = "ini-nom")]
130            Self::IniNom => Self::INI_NOM,
131
132            #[cfg(feature = "ini-regex")]
133            Self::IniRE => Self::INI_RE,
134        }
135    }
136}
137
138/// Configuration settings for the `confget` functions.
139///
140/// This is the main way to control the behavior of a backend's
141/// [`read_file`][`crate::backend::Backend::read_file`] method,
142/// the [`read_ini_file`][`crate::read_ini_file`] function, and
143/// the [`format::filter_vars`][`crate::format::filter_vars`] function:
144/// specify what file to read, what variables to extract from it, and
145/// how to format them.
146#[derive(Debug)]
147#[allow(clippy::exhaustive_structs)]
148#[allow(clippy::struct_excessive_bools)]
149pub struct Config {
150    /// The configuration backend to use.
151    pub backend: BackendKind,
152    /// The encoding the input file is in, or "" for the locale default.
153    pub encoding: String,
154    /// The (backend-specific) filename to read data from.
155    pub filename: Option<String>,
156    /// Formatting: select all the variables in the specified section.
157    pub list_all: bool,
158    /// Formatting: treat the variable match patterns as regular expressions
159    /// instead of glob ones.
160    pub match_regex: bool,
161    /// Formatting: treat `varnames` as a list of patterns, not exact
162    /// variable names.
163    pub match_var_names: bool,
164    /// Formatting: only select variables with values that match a pattern.
165    pub match_var_values: Option<String>,
166    /// Formatting: specify a string to prepend to the variable name.
167    pub name_prefix: String,
168    /// Formatting: specify a string to append to the variable name.
169    pub name_suffix: String,
170    /// Formatting: select variables from the specified section.
171    pub section: String,
172    /// Formatting: read variables from the initial section (""), then
173    /// override their values with variables from the one specified by
174    /// the `section` field.
175    pub section_override: bool,
176    /// Formatting: if `section` is an empty string and there are no
177    /// variables in the initial section, do not select the first section
178    /// defined in the file.
179    pub section_specified: bool,
180    /// Formatting: make the output values suitable for parsing by
181    /// Bourne-like shells.
182    pub shell_escape: bool,
183    /// Formatting: always display the variable name.
184    pub show_var_name: bool,
185    /// Formatting: select variable names or patterns to display.
186    pub varnames: Vec<String>,
187}
188
189impl Default for Config {
190    /// Initialize a [`Config`] object with default values.
191    ///
192    /// This is the recommended way to use the [`Config`] struct:
193    /// only override the settings that must be overridden.
194    #[inline]
195    fn default() -> Self {
196        Self {
197            backend: BackendKind::get_preferred_ini_backend(),
198            encoding: String::new(),
199            filename: None,
200            list_all: false,
201            match_regex: false,
202            match_var_names: false,
203            match_var_values: None,
204            name_prefix: String::new(),
205            name_suffix: String::new(),
206            section: String::new(),
207            section_override: false,
208            section_specified: false,
209            shell_escape: false,
210            show_var_name: false,
211            varnames: Vec::new(),
212        }
213    }
214}
215
216/// Variable/value pairs from a single section.
217pub type SectionData = HashMap<String, String>;
218
219/// Section/variable/value information read from a file.
220pub type FileData = HashMap<String, SectionData>;