use crate::manifest_lookup::resolve_plugin_ref;
use colored::Colorize;
use r2x_logger as logger;
use r2x_manifest::types::Manifest;
use std::collections::BTreeSet;
pub fn show_run_help() -> Result<(), String> {
let manifest = Manifest::load().map_err(|e| format!("Failed to load manifest: {}", e))?;
println!();
println!("{}", "No pipeline or plugin specified.".bold());
println!();
if manifest.is_empty() {
println!("{}", "No plugins installed.".yellow());
println!("Install plugins with: r2x install <package>");
println!();
} else {
println!("{}", "Installed plugins:".bold());
for pkg in &manifest.packages {
for plugin in &pkg.plugins {
let plugin_type = format!("{:?}", plugin.plugin_type);
println!(
" {} {} - from package {}",
plugin.name.as_ref().cyan(),
format!("({})", plugin_type).dimmed(),
pkg.name.as_ref().dimmed()
);
}
}
println!();
}
println!("{}", "Usage:".bold());
println!(" Run a pipeline:");
println!(" r2x run <pipeline.yaml> [pipeline-name]");
println!();
println!(" Run a plugin directly:");
println!(" r2x run plugin <plugin-name> [OPTIONS]");
println!(" (use --output <FILE> for portable UTF-8 JSON files; use -q for quiet logs, -q -q to suppress plugin stdout)");
println!();
println!(" Get plugin help:");
println!(" r2x run plugin <plugin-name> --show-help");
println!();
println!(" List pipelines in YAML:");
println!(" r2x run <pipeline.yaml> --list");
println!();
println!(" Print resolved pipeline config:");
println!(" r2x run <pipeline.yaml> --print <pipeline-name>");
println!();
Ok(())
}
pub fn show_plugin_help(plugin_name: &str) -> Result<(), String> {
let manifest = Manifest::load().map_err(|e| format!("Failed to load manifest: {}", e))?;
let resolved = resolve_plugin_ref(&manifest, plugin_name).map_err(|e| e.to_string())?;
let plugin = resolved.plugin;
logger::step(&format!("Plugin: {}", plugin_name));
println!("\nType: {:?}", plugin.plugin_type);
println!("Module: {}", plugin.module);
if let Some(ref desc) = plugin.description {
println!("Description: {}", desc);
}
if let Some(ref class_name) = plugin.class_name {
println!("Class: {}", class_name);
}
if let Some(ref function_name) = plugin.function_name {
println!("Function: {}", function_name);
}
if let Some(ref config_class) = plugin.config_class {
print!("\nConfiguration Class: {}", config_class);
if let Some(ref config_module) = plugin.config_module {
print!(" ({})", config_module);
}
println!();
}
let required_options = required_plugin_options(plugin);
let usage_options = if required_options.is_empty() {
" [OPTIONS]".to_string()
} else {
format_option_usage(&required_options)
};
let example_options = if required_options.is_empty() {
plugin_option_names(plugin)
.iter()
.next()
.map(|name| format!(" --{} <value>", name))
.unwrap_or_default()
} else {
format_option_usage(&required_options)
};
println!("\nUsage:");
println!(" r2x run plugin {}{}", plugin_name, usage_options);
println!(" (add --output <FILE> for UTF-8 JSON files, or -q to silence logs, -q -q to hide stdout)");
if !plugin.parameters.is_empty() {
println!("\nPlugin options:");
for param in &plugin.parameters {
let module_str = param
.module
.as_ref()
.map(|m| format!(" ({})", m))
.unwrap_or_default();
let req_marker = if required_param_is_user_supplied(
plugin,
param.name.as_ref(),
param.required && param.default.is_none(),
) {
" (required)"
} else {
""
};
println!(
" --{:<20} {}{}{}",
cli_flag_name(param.name.as_ref()),
param.format_types(),
module_str,
req_marker
);
if param.name.contains('_') {
println!(" Alias: --{}", param.name);
}
if let Some(ref desc) = param.description {
println!(" {}", desc);
}
}
}
if !plugin.config_schema.is_empty() {
println!("\nConfiguration options:");
for (field_name, field) in plugin.config_schema.iter() {
let req_marker = if field.required && field.default.is_none() {
" (required)"
} else {
""
};
println!(
" --{:<20} {:?}{}",
cli_flag_name(field_name.as_ref()),
field.field_type,
req_marker
);
if field_name.contains('_') {
println!(" Alias: --{}", field_name);
}
}
}
println!("\nCompatibility:");
println!(" key=value arguments are also supported:");
println!(" {}", compatibility_example(plugin));
println!(" --set key=value is accepted as an explicit key/value form.");
println!("\nExamples:");
println!(" r2x run plugin {} --show-help", plugin_name);
println!(" r2x run plugin {}{}", plugin_name, example_options);
Ok(())
}
fn required_plugin_options(plugin: &r2x_manifest::types::Plugin) -> BTreeSet<String> {
let mut options = BTreeSet::new();
for param in &plugin.parameters {
if required_param_is_user_supplied(
plugin,
param.name.as_ref(),
param.required && param.default.is_none(),
) {
options.insert(cli_flag_name(param.name.as_ref()));
}
}
for (field_name, field) in plugin.config_schema.iter() {
if field.required && field.default.is_none() {
options.insert(cli_flag_name(field_name.as_ref()));
}
}
options
}
fn plugin_option_names(plugin: &r2x_manifest::types::Plugin) -> BTreeSet<String> {
let mut options = BTreeSet::new();
for param in &plugin.parameters {
options.insert(cli_flag_name(param.name.as_ref()));
}
for (field_name, _) in plugin.config_schema.iter() {
options.insert(cli_flag_name(field_name.as_ref()));
}
options
}
fn compatibility_example(plugin: &r2x_manifest::types::Plugin) -> String {
let mut keys: BTreeSet<String> = BTreeSet::new();
for param in &plugin.parameters {
keys.insert(param.name.to_string());
}
for (field_name, _) in plugin.config_schema.iter() {
keys.insert(field_name.to_string());
}
if keys.is_empty() {
return "key=value".to_string();
}
keys.iter()
.map(|key| format!("{}=<value>", key))
.collect::<Vec<_>>()
.join(" ")
}
fn format_option_usage(options: &BTreeSet<String>) -> String {
options.iter().fold(String::new(), |mut usage, name| {
usage.push_str(" --");
usage.push_str(name);
usage.push_str(" <value>");
usage
})
}
fn required_param_is_user_supplied(
plugin: &r2x_manifest::types::Plugin,
param_name: &str,
has_no_default: bool,
) -> bool {
if !has_no_default {
return false;
}
if param_name == "config" && plugin.config_class.is_some() && plugin.config_module.is_some() {
return false;
}
!matches!(param_name, "store" | "data_store")
}
fn cli_flag_name(key: &str) -> String {
key.replace('_', "-")
}