use std::io::Write;
use std::path::Path;
use crol::schema::DataRoot;
use crol_eval::{Key, Span, Value, ValueKind, print};
use crol_syntax::format::FormatConfig;
use crate::diagnostics::Reporter;
#[derive(Debug, clap::Args)]
pub(crate) struct Args {
#[arg(value_name = "[LAYER...] [KEY[.PATH]]")]
arguments: Vec<String>,
#[arg(long, default_value_t = FormatConfig::default().max_width)]
max_width: usize,
#[arg(
long,
value_delimiter = ',',
value_name = "VARIANTS",
conflicts_with = "all_contexts"
)]
context: Option<Vec<String>>,
#[arg(long)]
all_contexts: bool,
#[arg(long)]
no_defaults: bool,
#[command(flatten)]
inputs: crate::layers::InputArgs,
}
pub(crate) fn run(args: &Args, reporter: &mut Reporter) -> bool {
let mut layers = args.arguments.clone();
let path = layers.pop_if(|last| crate::layers::is_key(last));
if let Some(document) = crate::value::argument(&layers) {
return run_value(args, document, path.as_deref(), reporter);
}
if let (true, Some(path)) = (layers.is_empty(), &path)
&& let Some(message) = crate::layers::no_layers_for(path, &args.inputs)
{
reporter.error(&message);
return false;
}
let contexts = match (&args.context, args.all_contexts) {
(_, true) => None,
(Some(context), false) => Some(vec![context.clone()]),
(None, false) => Some(Vec::new()),
};
let Some(mut inputs) = crate::layers::inputs(&layers, &args.inputs, contexts, reporter) else {
return false;
};
let evaluation = crate::layers::evaluate(&inputs);
reporter.diagnostics(&inputs.sources, &evaluation.diagnostics);
if evaluation.has_errors() {
return false;
}
let Some(base) = evaluation.value else {
return false;
};
let config = FormatConfig::default().with_max_width(args.max_width);
let struct_root = inputs
.schema
.as_ref()
.is_some_and(|schema| schema.data_root(inputs.root.as_deref()) == DataRoot::Document);
if args.all_contexts {
if path.is_some() {
reporter.error("`--all-contexts` prints all the data; it takes no key");
return false;
}
let write_data = if struct_root {
print::to_all_contexts_of_struct
} else {
print::to_all_contexts
};
let output = write_data(&base, &evaluation.contexts, &evaluation.variants, &config);
return write(&output, reporter);
}
let mut root = match (&args.context, evaluation.contexts.first()) {
(Some(_), Some(data)) => data.root(&base),
_ => base,
};
if !struct_root && let ValueKind::Keyed(keyed) = &mut root.kind {
keyed.defaulted.clear();
}
let filled = Span {
file: inputs.sources.add("<default>", ""),
range: Default::default(),
};
let root = match &inputs.schema {
Some(schema) if !args.no_defaults => {
crol::fill_defaults(&root, schema, inputs.root.as_deref(), filled)
}
_ => root,
};
print_value(&root, path.as_deref(), filled, &config, reporter)
}
fn run_value(args: &Args, document: &Path, path: Option<&str>, reporter: &mut Reporter) -> bool {
if args.all_contexts {
return crate::value::no_contexts(document, "--all-contexts", reporter);
}
if args.context.is_some() {
return crate::value::no_contexts(document, "--context", reporter);
}
let Some(mut document) = crate::value::read(document, &args.inputs, reporter) else {
return false;
};
reporter.diagnostics(&document.sources, &document.evaluation.diagnostics);
let value = match document.evaluation.value.take() {
Some(value) if !document.evaluation.has_errors() => value,
_ => return false,
};
let filled = Span {
file: document.sources.add("<default>", ""),
range: Default::default(),
};
let value = match &document.schema {
Some(schema) if !args.no_defaults => {
crol::fill_defaults_value(&value, schema, document.root.as_deref(), filled)
}
_ => value,
};
let config = FormatConfig::default().with_max_width(args.max_width);
print_value(&value, path, filled, &config, reporter)
}
fn print_value(
root: &Value,
path: Option<&str>,
filled: Span,
config: &FormatConfig,
reporter: &mut Reporter,
) -> bool {
let note = |value: &Value| (value.span == filled).then(|| "default".to_owned());
let value = match path {
None => root,
Some(path) => match select(root, path) {
Ok(value) => value,
Err(message) if Path::new(path).exists() => {
reporter.error(&format!(
"{message}; it is read as a key, so write `./{path}` to name the layer"
));
return false;
}
Err(message) => {
reporter.error(&message);
return false;
}
},
};
let output = print::to_document_noted(value, config, ¬e);
write(&output, reporter)
}
fn write(output: &str, reporter: &mut Reporter) -> bool {
if let Err(error) = std::io::stdout().lock().write_all(output.as_bytes()) {
reporter.error(&format!("cannot write the output: {error}"));
return false;
}
true
}
fn select<'v>(root: &'v Value, path: &str) -> Result<&'v Value, String> {
let (key, steps) = crol_eval::parse_path(path)?;
let missing = || format!("there is no value at `{path}`");
let ValueKind::Keyed(keyed) = &root.kind else {
return Err(missing());
};
let value = keyed
.entries
.get(&Key::Str(key.into()))
.ok_or_else(missing)?;
crol_eval::value_at_path(value, &steps).ok_or_else(missing)
}