pub mod format;
mod interp;
mod ir;
mod merge;
mod path;
mod set;
pub mod value;
mod env;
use std::io::Read;
use std::path::{Path, PathBuf};
use anyhow::Context;
pub use env::ProcessEnv;
pub use format::Format;
pub use interp::{Cycle, Env, EnvValue, InterpError, Problem, Syntax, interpolate};
pub use ir::{Map, Number, Value};
pub use merge::{MergeError, MergeOptions, merge, merge_into};
pub use path::{PathError, RefPath, Seg, render_path};
pub use set::{PathLeaf, json_or_string};
pub use value::{BadDatetime, IntegerOutOfRange, NonFiniteFloat, NullInToml, TomlError};
use format::SourceName;
pub const STDIN: &str = "-";
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum LoadError {
#[error("`-` reads stdin, which has no extension")]
StdinNeedsFormat,
#[error("`{}` is a directory; knf takes files as layers", path.display())]
Directory {
path: PathBuf,
},
#[error("cannot infer a format from `{}`", path.display())]
UnknownExtension {
path: PathBuf,
},
}
impl LoadError {
pub fn path(&self) -> Option<&Path> {
match self {
Self::StdinNeedsFormat => None,
Self::Directory { path } | Self::UnknownExtension { path } => Some(path),
}
}
}
pub fn load_layers<P: AsRef<Path>>(
paths: &[P],
input_format: Option<Format>,
) -> anyhow::Result<(Vec<Value>, Vec<Format>)> {
let mut layers: Vec<Value> = Vec::with_capacity(paths.len());
let mut input_formats: Vec<Format> = Vec::with_capacity(paths.len());
for path in paths {
let (name, format, text) = read_input(path.as_ref(), input_format)?;
let value = format::parse(format, &text, &name)?;
input_formats.push(format);
layers.push(value);
}
Ok((layers, input_formats))
}
fn read_input(
path: &Path,
override_format: Option<Format>,
) -> anyhow::Result<(SourceName, Format, String)> {
if path.as_os_str() == STDIN {
let format = override_format.ok_or(LoadError::StdinNeedsFormat)?;
let mut text = String::new();
std::io::stdin()
.read_to_string(&mut text)
.context("reading stdin")?;
return Ok((SourceName::Stdin, format, text));
}
if path.is_dir() {
return Err(LoadError::Directory {
path: path.to_path_buf(),
}
.into());
}
let format = match override_format {
Some(format) => format,
None => Format::from_path(path).ok_or_else(|| LoadError::UnknownExtension {
path: path.to_path_buf(),
})?,
};
let text =
std::fs::read_to_string(path).with_context(|| format!("reading `{}`", path.display()))?;
Ok((SourceName::File(path.to_path_buf()), format, text))
}