ui/conf: Add include file feature
Use #include "path/to/file" In configuration file to include other files.jmap
parent
15348fb245
commit
4048eab424
|
@ -40,6 +40,15 @@ Newline means LF (0x0A) or CRLF (0x0D 0x0A).
|
|||
.El
|
||||
.Pp
|
||||
Refer to TOML documentation for valid TOML syntax.
|
||||
|
||||
Thought not valid TOML syntax,
|
||||
.Nm
|
||||
can have nested configuration files by using the following include directive, which though starting with
|
||||
.Em \&#
|
||||
is not a comment:
|
||||
.Bd -literal
|
||||
#include "/path/to/file"
|
||||
.Ed
|
||||
.Sh SECTIONS
|
||||
The top level sections of the config are accounts, shortcuts, notifications, pager, composing, pgp, terminal.
|
||||
.Pp
|
||||
|
|
127
ui/src/conf.rs
127
ui/src/conf.rs
|
@ -49,8 +49,8 @@ use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
|||
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{self, BufRead, Read, Write};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{self, BufRead, Write};
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
|
@ -310,25 +310,15 @@ impl FileSettings {
|
|||
}
|
||||
}
|
||||
|
||||
let mut file = File::open(config_path.to_str().unwrap())?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
let s = toml::from_str(&contents);
|
||||
if let Err(e) = s {
|
||||
return Err(MeliError::new(format!(
|
||||
"Config file contains errors: {}",
|
||||
e.to_string()
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(s.unwrap())
|
||||
FileSettings::validate(config_path.to_str().unwrap())?;
|
||||
let s = pp::pp(config_path.to_str().unwrap()).unwrap();
|
||||
let s: FileSettings = toml::from_str(&s).unwrap();
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub fn validate(path: &str) -> Result<()> {
|
||||
let mut file = File::open(path)?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
let s: FileSettings = toml::from_str(&contents).map_err(|e| {
|
||||
let s = pp::pp(path)?;
|
||||
let s: FileSettings = toml::from_str(&s).map_err(|e| {
|
||||
MeliError::new(format!("Config file contains errors: {}", e.to_string()))
|
||||
})?;
|
||||
let backends = melib::backends::Backends::new();
|
||||
|
@ -598,3 +588,104 @@ pub fn create_config_file(p: &Path) -> Result<()> {
|
|||
file.set_permissions(permissions)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod pp {
|
||||
use melib::{
|
||||
error::{MeliError, Result},
|
||||
parsec::*,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
use std::io::Read;
|
||||
|
||||
fn include_directive<'a>() -> impl Parser<'a, Option<&'a str>> {
|
||||
move |input: &'a str| {
|
||||
enum State {
|
||||
Start,
|
||||
Directive,
|
||||
Path,
|
||||
}
|
||||
use State::*;
|
||||
let mut state = State::Start;
|
||||
|
||||
let mut i = 0;
|
||||
while i < input.len() {
|
||||
match (&state, input.as_bytes()[i]) {
|
||||
(Start, b'#') => {
|
||||
state = Directive;
|
||||
}
|
||||
(Start, b) if (b as char).is_whitespace() => { /* consume */ }
|
||||
(Start, _) => {
|
||||
return Ok(("", None));
|
||||
}
|
||||
(Directive, b) if (b as char).is_whitespace() => { /* consume */ }
|
||||
(Directive, _) if input.as_bytes()[i..].starts_with(b"include") => {
|
||||
i += "include".len();
|
||||
state = Path;
|
||||
continue;
|
||||
}
|
||||
(Directive, _) => {
|
||||
return Ok(("", None));
|
||||
}
|
||||
(Path, b) if (b as char).is_whitespace() => { /* consume */ }
|
||||
(Path, b'"') | (Path, b'\'') => {
|
||||
let mut end = i + 1;
|
||||
while end < input.len() && input.as_bytes()[end] != input.as_bytes()[i] {
|
||||
end += 1;
|
||||
}
|
||||
if end == input.len() {
|
||||
return Err(input);
|
||||
}
|
||||
let ret = &input[i + 1..end];
|
||||
end += 1;
|
||||
while end < input.len() {
|
||||
if !(input.as_bytes()[end] as char).is_whitespace() {
|
||||
/* Nothing else allowed in line */
|
||||
return Err(input);
|
||||
}
|
||||
end += 1;
|
||||
}
|
||||
return Ok(("", Some(ret)));
|
||||
}
|
||||
(Path, _) => return Err(input),
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
return Ok(("", None));
|
||||
}
|
||||
}
|
||||
|
||||
fn pp_helper(path: &str, level: u8) -> Result<Cow<'_, str>> {
|
||||
if level > 7 {
|
||||
return Err(MeliError::new(format!("Maximum recursion limit reached while unfolding include directives in {}. Have you included a config file within itself?", path)));
|
||||
}
|
||||
let mut contents = String::new();
|
||||
let mut file = std::fs::File::open(path)?;
|
||||
file.read_to_string(&mut contents)?;
|
||||
|
||||
let mut includes = Vec::new();
|
||||
for (i, l) in contents.lines().enumerate() {
|
||||
if let (_, Some(path)) = include_directive().parse(l).map_err(|l| {
|
||||
MeliError::new(format!(
|
||||
"Malformed include directive in line {} of file {}: {}",
|
||||
i, path, l
|
||||
))
|
||||
})? {
|
||||
includes.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
if includes.is_empty() {
|
||||
Ok(Cow::from(contents))
|
||||
} else {
|
||||
let mut ret = String::with_capacity(contents.len());
|
||||
for path in includes {
|
||||
ret.extend(pp_helper(path, level + 1)?.chars());
|
||||
}
|
||||
ret.extend(contents.chars());
|
||||
Ok(Cow::from(ret))
|
||||
}
|
||||
}
|
||||
pub fn pp(path: &str) -> Result<Cow<'_, str>> {
|
||||
pp_helper(path, 0)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue