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
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
Refer to TOML documentation for valid TOML syntax.
|
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
|
.Sh SECTIONS
|
||||||
The top level sections of the config are accounts, shortcuts, notifications, pager, composing, pgp, terminal.
|
The top level sections of the config are accounts, shortcuts, notifications, pager, composing, pgp, terminal.
|
||||||
.Pp
|
.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::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::OpenOptions;
|
||||||
use std::io::{self, BufRead, Read, Write};
|
use std::io::{self, BufRead, Write};
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
@ -310,25 +310,15 @@ impl FileSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut file = File::open(config_path.to_str().unwrap())?;
|
FileSettings::validate(config_path.to_str().unwrap())?;
|
||||||
let mut contents = String::new();
|
let s = pp::pp(config_path.to_str().unwrap()).unwrap();
|
||||||
file.read_to_string(&mut contents)?;
|
let s: FileSettings = toml::from_str(&s).unwrap();
|
||||||
let s = toml::from_str(&contents);
|
Ok(s)
|
||||||
if let Err(e) = s {
|
|
||||||
return Err(MeliError::new(format!(
|
|
||||||
"Config file contains errors: {}",
|
|
||||||
e.to_string()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(s.unwrap())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate(path: &str) -> Result<()> {
|
pub fn validate(path: &str) -> Result<()> {
|
||||||
let mut file = File::open(path)?;
|
let s = pp::pp(path)?;
|
||||||
let mut contents = String::new();
|
let s: FileSettings = toml::from_str(&s).map_err(|e| {
|
||||||
file.read_to_string(&mut contents)?;
|
|
||||||
let s: FileSettings = toml::from_str(&contents).map_err(|e| {
|
|
||||||
MeliError::new(format!("Config file contains errors: {}", e.to_string()))
|
MeliError::new(format!("Config file contains errors: {}", e.to_string()))
|
||||||
})?;
|
})?;
|
||||||
let backends = melib::backends::Backends::new();
|
let backends = melib::backends::Backends::new();
|
||||||
|
@ -598,3 +588,104 @@ pub fn create_config_file(p: &Path) -> Result<()> {
|
||||||
file.set_permissions(permissions)?;
|
file.set_permissions(permissions)?;
|
||||||
Ok(())
|
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