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>;