1use super::aatxe;
2use super::pkg_info;
3use super::ErrorKind;
4use super::Result;
5use serde_yaml;
6use smallvec::SmallVec;
7use std::fs::File;
8use std::io::prelude::*;
9use std::io::BufReader;
10use std::path::Path;
11use std::sync::Arc;
12
13mod inner {
14 #[derive(Debug, Default, Deserialize)]
18 pub(super) struct Config {
19 pub(super) nickname: String,
20
21 #[serde(default)]
22 pub(super) username: String,
23
24 #[serde(default)]
25 pub(super) realname: String,
26
27 #[serde(default)]
28 pub(super) admins: Vec<super::Admin>,
29
30 pub(super) servers: Vec<super::Server>,
31 }
32}
33
34#[derive(Debug)]
35pub struct Config {
36 pub(crate) nickname: String,
37
38 pub(crate) username: String,
39
40 pub(crate) realname: String,
41
42 pub(crate) admins: SmallVec<[Admin; 8]>,
43
44 pub(crate) servers: SmallVec<[Arc<aatxe::Config>; 8]>,
45}
46
47#[derive(Clone, Debug, Deserialize)]
48pub struct Admin {
49 #[serde(default)]
50 pub nick: Option<String>,
51
52 #[serde(default)]
53 pub user: Option<String>,
54
55 #[serde(default)]
56 pub host: Option<String>,
57}
58
59#[derive(Clone, Debug, Deserialize)]
60struct Server {
61 pub host: String,
62
63 pub port: u16,
64
65 #[serde(default = "mk_true")]
66 pub tls: bool,
67
68 #[serde(default)]
69 pub channels: Vec<String>,
70}
71
72#[derive(Debug)]
73pub struct ConfigBuilder(Result<inner::Config>);
74
75impl Config {
76 pub fn try_from<T>(input: T) -> Result<Config>
77 where
78 T: IntoConfig,
79 {
80 input.into_config()
81 }
82
83 pub fn try_from_path<P>(path: P) -> Result<Config>
84 where
85 P: AsRef<Path>,
86 {
87 Self::try_from(File::open(path)?)
88 }
89
90 pub fn build() -> ConfigBuilder {
91 ConfigBuilder(Ok(Default::default()))
92 }
93}
94
95impl ConfigBuilder {
96 pub fn nickname<S>(self, nickname: S) -> Self
97 where
98 S: Into<String>,
99 {
100 let nickname = nickname.into();
101
102 if nickname.is_empty() {
103 return ConfigBuilder(
104 Err(ErrorKind::Config("nickname".into(), "is empty".into()).into()),
105 );
106 }
107
108 ConfigBuilder(self.0.map(|cfg| inner::Config { nickname, ..cfg }))
109 }
110
111 pub fn username<S>(self, username: S) -> Self
112 where
113 S: Into<String>,
114 {
115 ConfigBuilder(self.0.map(|cfg| inner::Config {
116 username: username.into(),
117 ..cfg
118 }))
119 }
120
121 pub fn realname<S>(self, realname: S) -> Self
122 where
123 S: Into<String>,
124 {
125 ConfigBuilder(self.0.map(|cfg| inner::Config {
126 realname: realname.into(),
127 ..cfg
128 }))
129 }
130}
131
132pub trait IntoConfig {
134 fn into_config(self) -> Result<Config>;
135}
136
137impl IntoConfig for Config {
138 fn into_config(self) -> Result<Config> {
139 Ok(self)
140 }
141}
142
143impl IntoConfig for Result<Config> {
144 fn into_config(self) -> Result<Config> {
145 self
146 }
147}
148
149impl IntoConfig for ConfigBuilder {
150 fn into_config(self) -> Result<Config> {
151 self.0.and_then(cook_config)
152 }
153}
154
155impl<'a> IntoConfig for &'a str {
156 fn into_config(self) -> Result<Config> {
157 read_config(self)
158 }
159}
160
161impl IntoConfig for String {
162 fn into_config(self) -> Result<Config> {
163 read_config(&self)
164 }
165}
166
167impl<R> IntoConfig for BufReader<R>
168where
169 R: Read,
170{
171 fn into_config(mut self) -> Result<Config> {
172 let mut text = String::new();
173 self.read_to_string(&mut text)?;
174 text.into_config()
175 }
176}
177
178impl IntoConfig for File {
179 fn into_config(self) -> Result<Config> {
180 BufReader::new(self).into_config()
181 }
182}
183
184fn read_config(input: &str) -> Result<Config> {
185 serde_yaml::from_str(input)
186 .map_err(Into::into)
187 .and_then(cook_config)
188}
189
190fn cook_config(mut cfg: inner::Config) -> Result<Config> {
191 validate_config(&cfg)?;
192
193 fill_in_config_defaults(&mut cfg)?;
194
195 let nickname = cfg.nickname.to_owned();
196
197 let username = cfg.username.to_owned();
198
199 let realname = cfg.realname.to_owned();
200
201 let admins = cfg.admins.drain(..).collect();
202
203 let servers = cfg.servers
204 .drain(..)
205 .map(|server_cfg| {
206 Arc::new(aatxe::Config {
207 nickname: Some(nickname.clone()),
209 username: Some(username.clone()),
210 realname: Some(realname.clone()),
211 server: Some(server_cfg.host),
212 port: Some(server_cfg.port),
213 use_ssl: Some(server_cfg.tls),
214 channels: Some(server_cfg.channels),
215 ..Default::default()
216 })
217 })
218 .collect();
219
220 Ok(Config {
221 nickname,
222 username,
223 realname,
224 admins,
225 servers,
226 })
227}
228
229fn validate_config(cfg: &inner::Config) -> Result<()> {
230 ensure!(
231 !cfg.nickname.is_empty(),
232 ErrorKind::Config("nickname".into(), "is empty".into())
233 );
234
235 ensure!(
236 !cfg.servers.is_empty(),
237 ErrorKind::Config("servers".into(), "is empty".into())
238 );
239
240 ensure!(
241 cfg.servers.len() == 1,
242 ErrorKind::Config(
243 "servers".into(),
244 "lists multiple servers, which is not yet supported".into(),
245 )
246 );
247
248 Ok(())
249}
250
251fn fill_in_config_defaults(cfg: &mut inner::Config) -> Result<()> {
252 if cfg.username.is_empty() {
253 cfg.username = cfg.nickname.clone();
254 }
255
256 if cfg.realname.is_empty() {
257 cfg.realname = pkg_info::BRIEF_CREDITS_STRING.clone();
258 }
259
260 Ok(())
261}
262
263fn mk_true() -> bool {
264 true
265}