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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
//! The just-config crate supplies a methods for reading, transforming and
//! validating configuration information from multiple sources.
//!
//! Before you read any further let's answer a simple question: Is this
//! configuration library for you?
//!
//! * If you want to read a configuration file created by your user: yes
//! * If you want to flexibly process the information read from a configuration
//!   file: yes
//! * If you want to add environment variables or constants (command line
//!   parameters) to the mix: yes
//! * If you want an easy way to merge multiple ocnfiguration sources: yes
//! * If you want to add defaults or environment variables: yes
//! * If you want to read _and_ write a configuration file: no
//! * If you want your configuration file to specify data types: no
//! * If you want your configuration file to have significant whitespace or be a
//!   mess of brackets: no
//!
//! ## Navigating configuration information
//!
//! ## Configuration paths
//!
//! Configuration information in just-config is represented as a set of nodes.
//! Each node can have sub nodes forming a configuration tree. To not limit the
//! developer to a specific choice of separator character the node tree is
//! represented as an instance of the [ConfPath] struct.
//!
//! ## Configuration pipeline
//!
//! Configuration information in `just-config` is processed using a
//! configuration pipeline. You retrieve a configuration value from the
//! configuration source by calling [`get`](Config::get). Then the
//! configuration information is passed though
//! [processors](processors) and [validators](validators).
//!
//! ### Configuration source
//!
//! A configuration source is any struct that implements the
//! [`Source`] trait. This make the configuration
//! system very flexible. You can read configuration information from text
//! files, the network or from environment variables. All these configuration
//! sources can be mixed and matched.
//!
//! There are some [configuration sources already included](sources)
//! in just-config.
//!
//! Multiple configuration sources can be registered. They are tried in order of
//! their registration. The first configuration source that returns a value for
//! a configuration key is used. That way configuration sources can be layered.
//! See [`add_source`](Config::add_source) for more
//! information and an example.
//!
//! ### Processors
//!
//! The processors allow you to pre-process the value read from the
//! configuration source. Processors always operate on the string representation
//! of the value. Their purpose is to transform the string (trim, unquote,
//! unescape, etc.) and prepare it for conversion into the target data type.
//!
//! ### Validators
//!
//! As soon as the first validator is called the string value is converted into
//! the target data type. All validation takes place after the conversion. That
//! way the properties of the target data type can be used to validate the
//! information. The first validation step is always present, its the call to
//! the [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) method
//! of the target date type. If this conversion fails, the configuration value
//! is returned as invalid.
//!
//! As you might have guessed, all types implementing the `FromStr` trait are
//! able to be used as target data types for the configuration. In most cases
//! type inference does a very good job to provide the necessary type
//! information for `just-config`. Simply assign the configuration value to the
//! target data type and it will use the `FromStr` trait to convert the string
//! value from the configuration source into that type.
//!
//! # Examples
//!
//! ## Basic example
//!
//! ```no_run
//! use justconfig::Config;
//! use justconfig::ConfPath;
//! use justconfig::sources::text::ConfigText;
//! use justconfig::sources::env::Env;
//! use justconfig::sources::defaults::Defaults;
//! use justconfig::processors::Explode;
//! use justconfig::validators::Range;
//! use justconfig::item::ValueExtractor;
//! use std::ffi::OsStr;
//! use std::fs::File;
//!
//! let mut conf = Config::default();
//!
//! // Allow some environment variables to override configuration values read
//! // from the configuration file.
//! let config_env = Env::new(&[
//!   (ConfPath::from(&["searchPath"]), OsStr::new("SEARCH_PATH")),
//! ]);
//!
//! // Open the configuration file
//! let config_file = File::open("myconfig.conf").expect("Could not open config file.");
//! conf.add_source(ConfigText::new(config_file, "myconfig.conf").expect("Loading configuration file failed."));
//!
//! // Read the value `num_frobs` from the configuration file.
//! // Do not allow to use more than 10 frobs.
//! let num_frobs: i32 = conf.get(conf.root().push("num_frobs")).max(10).value()?;
//!
//! // Read a list of tags from the configuration file.
//! let tag_list: Vec<String> = conf.get(conf.root().push("tags")).values(..)?;
//!
//! // Read the paths from the config file and allow it to be overriden by
//! // the environment variable. We split everything at `:` to allow passing
//! // multiple paths using an environment variable. When read from the config
//! // file, multiple values can be set without using the `:` delimiter.
//! // Passing 1.. to values() makes sure at least one search path is set.
//! let search_paths: Vec<String> = conf.get(conf.root().push("searchPath")).explode(':').values(1..)?;
//!
//! # // Hide the error thrown by ?
//! # Ok::<(), justconfig::error::ConfigError>(())
//! ```
//!
//! ## Supplying defaults
//!
//! Often you want to supply default values for configuration items. This can be
//! done in two ways:
//!
//! * Use [`try_value()`](item::ValueExtractor::try_value) and
//!   supply the default by using `or`.
//! * Add a [`Defaults`](sources::defaults) source as the
//!   last configuration source to supply a default value.
//!
//! The second option shortens the pipeline length and allows the defaults to be
//! set at one central location.
//!
//! ```no_run
//! use justconfig::Config;
//! use justconfig::ConfPath;
//! use justconfig::sources::text::ConfigText;
//! use justconfig::sources::defaults::Defaults;
//! use justconfig::item::ValueExtractor;
//! use std::fs::File;
//!
//! let mut conf = Config::default();
//!
//! let config_file = File::open("myconfig.conf").expect("Could not open config file.");
//! conf.add_source(ConfigText::new(config_file, "myconfig.conf").expect("Loading configuration file failed."));
//!
//! // Add defaults for `key1` and `key2` as a fallback if they are not set via
//! // the configuration file.
//! let mut defaults = Defaults::default();
//! defaults.set(conf.root().push_all(&["key1"]), "default value 1", "default");
//! defaults.set(conf.root().push_all(&["key2"]), "default value 2", "default");
//! conf.add_source(defaults);
//! ```
//!
//!
//! ## Enumerating keys
//!
//! Every `ConfPath` keeps track of all items that where created using it. That
//! way configuration sources can create a list of configuration items that can
//! be enumerated. The [`ConfigText` source](sources::text) offers the
//! [`with_path`](sources::text::ConfigText) method
//! that allows you to pass a `ConfPath` into the parser. This `ConfPath` is
//! used to create the `ConfPath` instances for every configuration node. This
//! way the configuration can be enumerated using the passed instance.
//!
//! ```no_run
//! use justconfig::Config;
//! use justconfig::ConfPath;
//! use justconfig::sources::text::ConfigText;
//! use justconfig::sources::defaults::Defaults;
//! use justconfig::item::ValueExtractor;
//! use std::fs::File;
//!
//! let mut conf = Config::default();
//!
//! let config_file = File::open("myconfig.conf").expect("Could not open config file.");
//! let config_file_path = ConfPath::default();
//! conf.add_source(ConfigText::with_path(config_file, "myconfig.conf", &config_file_path).expect("Loading configuration file failed."));
//!
//! for config_node in config_file_path.children() {
//!     print!("{}", config_node.tail_component_name().unwrap())
//! }
//! ```

use std::default::Default;

pub mod item;
use item::StringItem;

pub mod error;
use error::ConfigError;

pub mod source;
use source::Source;

mod confpath;
pub use confpath::ConfPath;

pub mod sources;

pub mod validators;
pub mod processors;

/// Main struct representing a loaded configuration.
pub struct Config {
	sources: Vec<Box<dyn Source>>,
	path_root: ConfPath
}

impl Default for Config {
	/// Create a new configuration store.
	fn default() -> Self {
		Self {
			sources: Vec::default(),
			path_root: ConfPath::default()
		}
	}
}

impl Config {
	/// Add a configuration source to the configuration system.
	///
	/// Each configuration source must implement the [`Source`] trait.
	/// Multiple configuration sources can be added and are queried from first to last.
	/// The first configuration source that returns values for a configuration item will be used.
	/// All following configuration sources will be ignored for this configuration item.
	///
	/// ## Example
	///
	/// ```rust
	/// # use justconfig::Config;
	/// # use justconfig::ConfPath;
	/// # use justconfig::item::ValueExtractor;
	/// # use justconfig::sources::defaults::Defaults;
	/// #
	/// let mut conf = Config::default();
	///
	/// let mut source_1 = Defaults::default();
	/// source_1.set(conf.root().push_all(&["myitem_A"]), "source_1", "source 1");
	/// conf.add_source(source_1);
	///
	/// let mut source_2 = Defaults::default();
	/// source_2.set(conf.root().push_all(&["myitem_A"]), "source_2", "source 2");
	/// source_2.set(conf.root().push_all(&["myitem_B"]), "source_2", "source 2");
	/// conf.add_source(source_2);
	///
	/// let value: String = conf.get(ConfPath::from(&["myitem_A"])).value().unwrap();
	/// assert_eq!(value, "source_1");
	///
	/// let value: String = conf.get(ConfPath::from(&["myitem_B"])).value().unwrap();
	/// assert_eq!(value, "source_2");
	/// ```
	pub fn add_source(&mut self, source: Box<dyn Source>) {
		self.sources.push(source);
	}

	/// Convenience method to get a ConfPath instance.
	///
	/// Can be used to get a [`ConfPath`] instance to
	/// build configuration paths. If this `ConfPath` instance is used for all
	/// calls to the configuration library all configuration values can be
	/// enumerated. For details see [`ConfPath::children()`].
	pub fn root(&self) -> ConfPath {
		self.path_root.clone()
	}

	/// Get the configuration value identified by the passed `ConfPath`.
	///
	/// This method is the root of every configuration pipeline. For usage examples
	/// see the [crates documentation](crate).
	pub fn get(&self, key: ConfPath) -> Result<StringItem, ConfigError> {
		self.sources.iter().find_map(|source| source.get(key.clone())).ok_or(ConfigError::ValueNotFound(key))
	}
}