use openjd_model::template::parse;
use openjd_model::template::{JobParameterDefinition, JobTemplate};
use std::path::Path;
pub fn try_context_aware_help(args: &[String]) -> bool {
if args.len() < 3 {
return false;
}
if args[1] != "run" {
return false;
}
if !args.iter().any(|a| a == "-h" || a == "--help") {
return false;
}
let template_path = args[2..]
.iter()
.find(|a| !a.starts_with('-'))
.map(|s| s.as_str());
let Some(path_str) = template_path else {
return false;
};
let path = Path::new(path_str);
let extensions = args
.windows(2)
.find(|w| w[0] == "--extensions")
.map(|w| w[1].as_str());
match generate_template_help(path, extensions) {
Ok(help) => {
print!("{help}");
std::process::exit(0);
}
Err(e) => {
eprint!("Error: {e}");
std::process::exit(1);
}
}
}
fn generate_template_help(
path: &Path,
extensions_arg: Option<&str>,
) -> Result<String, Box<dyn std::error::Error>> {
let content = crate::common::read_input_file(path)?;
let template_value = parse::document_string_to_object(
&content,
crate::common::document_type(path),
&crate::common::caller_limits(),
)?;
let default_exts = vec![
"TASK_CHUNKING",
"REDACTED_ENV_VARS",
"FEATURE_BUNDLE_1",
"EXPR",
];
let supported_exts: Vec<&str> = match extensions_arg {
Some(s) if !s.is_empty() => s.split(',').map(|e| e.trim()).collect(),
Some(_) => vec![],
None => default_exts,
};
let template = parse::decode_job_template(
template_value,
Some(&supported_exts),
&crate::common::caller_limits(),
)
.map_err(|e| format!("Invalid job template: {e}"))?;
Ok(format_help(&template, path))
}
pub fn format_help(template: &JobTemplate, path: &Path) -> String {
let mut out = String::new();
out.push_str(&format!(
"usage: openjd run {} [arguments]\n\n",
path.display()
));
out.push_str(&format!("Job: {}\n", template.name()));
if let Some(desc) = template.description() {
out.push_str(&format!("{}\n", desc));
}
out.push('\n');
let params = template.parameter_definitions_list();
if !params.is_empty() {
out.push_str("Job Parameters (-p/--job-param PARAM_NAME=VALUE):\n");
for param in params {
out.push_str(" ");
out.push_str(&format_parameter(param));
out.push('\n');
}
out.push('\n');
}
out.push_str("Standard Options:\n");
out.push_str(
" -p, --job-param <KEY=VALUE> Job parameters (Key=Value, file://path, or inline JSON)\n",
);
out.push_str(" --environment <PATH> Environment template files\n");
out.push_str(" --verbose Enable verbose logging\n");
out.push_str(
" -h, --help Print help (leave out template to list all options)\n",
);
out
}
fn format_parameter(param: &JobParameterDefinition) -> String {
let name = param.name();
let type_name = param.type_name();
let mut line = format!("{name} ({type_name})");
let default = param.default_value();
let is_string_like = matches!(type_name, "STRING" | "PATH");
match &default {
Some(val) if is_string_like && val.contains('\n') => {
line.push_str(" [default: see below]");
}
Some(val) if is_string_like => {
line.push_str(&format!(" [default: '{val}']"));
}
Some(val) => {
line.push_str(&format!(" [default: {val}]"));
}
None => {
line.push_str(" [required]");
}
}
let mut constraints = Vec::new();
let min_i = param.min_value_i64();
let max_i = param.max_value_i64();
let min_f = param.min_value_f64();
let max_f = param.max_value_f64();
if type_name == "FLOAT" {
match (min_f, max_f) {
(Some(lo), Some(hi)) => constraints.push(format!("range: {lo} to {hi}")),
(Some(lo), None) => constraints.push(format!("minimum: {lo}")),
(None, Some(hi)) => constraints.push(format!("maximum: {hi}")),
_ => {}
}
} else if type_name == "INT" {
match (min_i, max_i) {
(Some(lo), Some(hi)) => constraints.push(format!("range: {lo} to {hi}")),
(Some(lo), None) => constraints.push(format!("minimum: {lo}")),
(None, Some(hi)) => constraints.push(format!("maximum: {hi}")),
_ => {}
}
}
match (param.min_length(), param.max_length()) {
(Some(lo), Some(hi)) => constraints.push(format!("length: {lo} to {hi} characters")),
(Some(lo), None) => constraints.push(format!("minimum length: {lo} characters")),
(None, Some(hi)) => constraints.push(format!("maximum length: {hi} characters")),
_ => {}
}
if let Some(vals) = param.allowed_values_strings() {
if is_string_like {
let formatted: Vec<String> = vals.iter().map(|v| format!("'{v}'")).collect();
constraints.push(format!("allowed: {}", formatted.join(", ")));
} else {
constraints.push(format!("allowed: {}", vals.join(", ")));
}
}
if !constraints.is_empty() {
line.push_str(&format!(" ({})", constraints.join(", ")));
}
if let Some(d) = param.description() {
line.push_str(&format!("\n {d}"));
}
if let Some(val) = &default {
if val.contains('\n') {
line.push_str("\n Default value:");
for dl in val.lines() {
line.push_str(&format!("\n {dl}"));
}
}
}
line
}