ui/conf: Add include file feature

Use

  #include "path/to/file"

In configuration file to include other files.
jmap
Manos Pitsidianakis 2019-11-27 22:13:14 +02:00
parent 15348fb245
commit 4048eab424
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
2 changed files with 118 additions and 18 deletions

View File

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

View File

@ -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)
}
}