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