1pub mod format;
22mod interp;
23mod ir;
24mod merge;
25mod path;
26mod set;
27pub mod value;
28
29mod env;
30
31use std::io::Read;
32use std::path::{Path, PathBuf};
33
34use anyhow::Context;
35
36pub use env::ProcessEnv;
37pub use format::Format;
38pub use interp::{Cycle, Env, EnvValue, InterpError, Problem, Syntax, interpolate};
39pub use ir::{Map, Number, Value};
40pub use merge::{MergeError, MergeOptions, merge, merge_into};
41pub use path::{PathError, RefPath, Seg, render_path};
42pub use set::{PathLeaf, json_or_string};
43pub use value::{BadDatetime, IntegerOutOfRange, NonFiniteFloat, NullInToml, TomlError};
44
45use format::SourceName;
46
47pub const STDIN: &str = "-";
49
50#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
56pub enum LoadError {
57 #[error("`-` reads stdin, which has no extension")]
59 StdinNeedsFormat,
60 #[error("`{}` is a directory; knf takes files as layers", path.display())]
62 Directory {
63 path: PathBuf,
65 },
66 #[error("cannot infer a format from `{}`", path.display())]
68 UnknownExtension {
69 path: PathBuf,
71 },
72}
73
74impl LoadError {
75 pub fn path(&self) -> Option<&Path> {
77 match self {
78 Self::StdinNeedsFormat => None,
79 Self::Directory { path } | Self::UnknownExtension { path } => Some(path),
80 }
81 }
82}
83
84pub fn load_layers<P: AsRef<Path>>(
97 paths: &[P],
98 input_format: Option<Format>,
99) -> anyhow::Result<(Vec<Value>, Vec<Format>)> {
100 let mut layers: Vec<Value> = Vec::with_capacity(paths.len());
101 let mut input_formats: Vec<Format> = Vec::with_capacity(paths.len());
102
103 for path in paths {
104 let (name, format, text) = read_input(path.as_ref(), input_format)?;
105 let value = format::parse(format, &text, &name)?;
106 input_formats.push(format);
107 layers.push(value);
108 }
109 Ok((layers, input_formats))
110}
111
112fn read_input(
114 path: &Path,
115 override_format: Option<Format>,
116) -> anyhow::Result<(SourceName, Format, String)> {
117 if path.as_os_str() == STDIN {
118 let format = override_format.ok_or(LoadError::StdinNeedsFormat)?;
119 let mut text = String::new();
120 std::io::stdin()
121 .read_to_string(&mut text)
122 .context("reading stdin")?;
123 return Ok((SourceName::Stdin, format, text));
124 }
125
126 if path.is_dir() {
127 return Err(LoadError::Directory {
128 path: path.to_path_buf(),
129 }
130 .into());
131 }
132
133 let format = match override_format {
134 Some(format) => format,
135 None => Format::from_path(path).ok_or_else(|| LoadError::UnknownExtension {
136 path: path.to_path_buf(),
137 })?,
138 };
139 let text =
140 std::fs::read_to_string(path).with_context(|| format!("reading `{}`", path.display()))?;
141 Ok((SourceName::File(path.to_path_buf()), format, text))
142}