Browse Source

ui/conf: Add include file feature

Use

  #include "path/to/file"

In configuration file to include other files.
tags/alpha-0.4.1
Manos Pitsidianakis 2 years ago
parent
commit
4048eab424
Signed by: epilys GPG Key ID: 73627C2F690DF710
  1. 9
      meli.conf.5
  2. 127
      ui/src/conf.rs

9
meli.conf.5

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

@ -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…
Cancel
Save