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
//! Source supplying static defaults
//! 
//! The 'Defaults' source supplies static values for configuration items. It can
//! be used to add static configuration values as defaults or to overwrite
//! values originating from a configuration file with values supplied at the
//! command line.
//!
//! Multiple `Defaults` sources can be added to the same `Config` instance. The
//! sources are queried from first to last. Depending on the order the
//! `Defaults` source can supply defaults or overwrite values.
//! 
//! ```text
//! +------------+---------------------+
//! | Defaults   | from command line   |
//! +------------+---------------------+
//! | ConfigText | to read config file |
//! +------------+---------------------+
//! | Defaults   | as defaults         |
//! +------------+---------------------+
//! ```
//! 
//! ## Example
//! 
//! ```rust
//! use justconfig::Config;
//! use justconfig::ConfPath;
//! use std::ffi::OsStr;
//! use justconfig::item::ValueExtractor;
//! use justconfig::sources::defaults::Defaults;
//! use justconfig::sources::env::Env;
//!
//! let mut conf = Config::default();
//! let mut defaults = Defaults::default();
//! let mut env = Env::new(&[(ConfPath::from(&["Workdir"]), OsStr::new("WORKDIR"))]);
//! 
//! defaults.set(ConfPath::from(&["Workdir"]), "/tmp", "Default Workdir /tmp");
//! 
//! conf.add_source(defaults);
//! conf.add_source(env);
//!
//! // If the environment variabel `WORKDIR` ist not set use `/tmp´ as a defualt.
//! let path: String = conf.get(ConfPath::from(&["Workdir"])).value().unwrap();
//! assert_eq!(path, "/tmp");
//! ```
use crate::source::Source;
use crate::item::{SourceLocation, StringItem, Value};
use crate::confpath::ConfPath;
use std::rc::Rc;
use std::collections::HashMap;
use std::fmt;

/// Source location for the Defaults configuration source.
/// 
/// This value is used to store the source of every configuration value for
/// use in error messages.
#[derive(Debug)]
pub struct DefaultSourceLocation {
	source: String
}

impl DefaultSourceLocation {
	fn new(source: &str) -> Rc<Self> {
		Rc::new(Self {
			source: source.to_owned()
		})
	}
}

impl fmt::Display for DefaultSourceLocation {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		write!(f, "default from {}", self.source)
	}
}

impl SourceLocation for DefaultSourceLocation {}

/// Implements the Defaults source.
pub struct Defaults {
	items: HashMap<ConfPath, StringItem>
}

impl Defaults {
	/// Creates a new defaults source.
	///
	/// The created `Defaults` instance does not contain any values.
	///
	/// See the [`defaults`](mod@super::defaults) module for more information.
	pub fn default() -> Box<Self> {
		Box::new(Self {
			items: HashMap::default()
		})
	}

	/// Returns a `StringItem` instance that can be used to manipulate the
	/// values for the item referenced by the key. If there is no `StringItem`
	/// instance available for this key a new one is created.
	fn get_item(&mut self, key: ConfPath) -> &mut StringItem {
		self.items.entry(key.clone()).or_insert_with(|| StringItem::new(key))
	}

	/// Clear all values for the given key.
	pub fn empty(&mut self, key: ConfPath) {
		self.get_item(key).clear();
	}

	/// Set the value of this key
	/// 
	/// Sets the value of the given `key` to the passed `value`. All previously
	/// set values are discarded.
	/// 
	/// The `source` parameter specifies a string that is used to identify the
	/// source for this configuration information in error messages.
	/// 
	/// See [`put`](Self::put) for an example.
	pub fn set(&mut self, key: ConfPath, value: &str, source: &str) {
		self.get_item(key).clear().push(Value::new(value.to_owned(), DefaultSourceLocation::new(source)));
	}

	/// Add a value to the configuration values of this key
	/// 
	/// Adds a `value` to the configuration values of the given `key`. This can
	/// be used to add multiple values for a configuration item.
	/// 
	/// If you want to clear all previously set values instead of adding the
	/// value to the list of configuration values use [`set`](Self::set).
	/// 
	/// The `source` parameter specifies a string that is used to identify the
	/// source for this configuration information in error messages.
	/// 
	/// ## Example
	/// 
	/// 
	/// ```rust
	/// use justconfig::Config;
	/// use justconfig::ConfPath;
	/// use justconfig::item::ValueExtractor;
	/// use justconfig::sources::defaults::Defaults;
	///
	/// let mut conf = Config::default();
	/// let mut defaults = Defaults::default();
	/// 
	/// defaults.set(ConfPath::from(&["Destination"]), "/tmp", "Default destination directory");
	/// defaults.set(ConfPath::from(&["Sources"]), "/srv/source/a", "Default source directory A");
	/// defaults.put(ConfPath::from(&["Sources"]), "/srv/source/b", "Default source directory B");
	/// 
	/// conf.add_source(defaults);
	///
	/// let destination: String = conf.get(ConfPath::from(&["Destination"])).value().unwrap();
	/// assert_eq!(destination, "/tmp");
	/// 
	/// let sources: Vec<String> = conf.get(ConfPath::from(&["Sources"])).values(1..).unwrap();
	/// assert_eq!(sources, ["/srv/source/a", "/srv/source/b"]);
	/// ```
	pub fn put(&mut self, key: ConfPath, value: &str, source: &str) {
		self.get_item(key).push(Value::new(value.to_owned(), DefaultSourceLocation::new(source)));
	}
}

impl Source for Defaults {
	fn get(&self, key: ConfPath) -> Option<StringItem> {
		self.items.get(&key).cloned()
	}
}

#[cfg(test)]
mod tests {
	use super::*;
	use crate::Config;
	use crate::ConfPath;
	use crate::error::ConfigError;
	use crate::item::ValueExtractor;

	#[test]
	fn defaults() {
		let mut c = Config::default();
		let mut d = Defaults::default();

		// Simply setting a value
		d.set(ConfPath::from(&["testA"]), "AaA", "sourceA");

		// Setting and putting to have multiple values
		d.set(ConfPath::from(&["testB"]), "BbB", "sourceB.1");
		d.put(ConfPath::from(&["testB"]), "bBb", "sourceB.2");

		// Empty to clear everything already set
		d.set(ConfPath::from(&["testC"]), "cCc", "sourceC.1");
		d.empty(ConfPath::from(&["testC"]));
		d.put(ConfPath::from(&["testC"]), "CcC", "sourceC.2");

		// Setting clears all previous values
		d.set(ConfPath::from(&["testD"]), "dDd", "sourceD.1");
		d.put(ConfPath::from(&["testD"]), "ddD", "sourceD.2");
		d.set(ConfPath::from(&["testD"]), "DdD", "sourceD.3");

		// First put is like set
		d.put(ConfPath::from(&["testE"]), "EeE", "sourceE");

		c.add_source(d);

		assert_eq!((c.get(ConfPath::from(&["testA"])).value() as Result<String, ConfigError>).unwrap(), "AaA");
		assert_eq!((c.get(ConfPath::from(&["testB"])).values(..) as Result<Vec<String>, ConfigError>).unwrap(), ["BbB", "bBb"]);
		assert_eq!((c.get(ConfPath::from(&["testC"])).value() as Result<String, ConfigError>).unwrap(), "CcC");
		assert_eq!((c.get(ConfPath::from(&["testD"])).value() as Result<String, ConfigError>).unwrap(), "DdD");
		assert_eq!((c.get(ConfPath::from(&["testE"])).value() as Result<String, ConfigError>).unwrap(), "EeE");
	}
}