use crate::model::{OptionType, ProbeResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Language {
Rust,
Python,
JavaScript,
TypeScript,
}
impl Language {
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"rust" => Some(Language::Rust),
"python" | "py" => Some(Language::Python),
"javascript" | "js" => Some(Language::JavaScript),
"typescript" | "ts" => Some(Language::TypeScript),
_ => None,
}
}
}
pub fn generate_command_builder(result: &ProbeResult, language: Language) -> String {
match language {
Language::Rust => generate_rust_builder(result),
Language::Python => generate_python_builder(result),
Language::JavaScript => generate_javascript_builder(result),
Language::TypeScript => generate_typescript_builder(result),
}
}
fn generate_rust_builder(result: &ProbeResult) -> String {
let cmd_name = sanitize_identifier(&result.command);
let builder_name = format!("{}Builder", capitalize_first(&cmd_name));
let mut code = String::new();
code.push_str(&format!(
"// Generated command builder for {}\n",
result.command
));
code.push_str("// Generated by help-probe\n\n");
code.push_str("use std::process::Command;\n\n");
code.push_str(&format!("pub struct {} {{\n", builder_name));
code.push_str(" command: String,\n");
code.push_str(" args: Vec<String>,\n");
code.push_str("}\n\n");
code.push_str(&format!("impl {} {{\n", builder_name));
code.push_str(" pub fn new() -> Self {\n");
code.push_str(&format!(" Self {{\n"));
code.push_str(&format!(
" command: \"{}\".to_string(),\n",
result.command
));
code.push_str(" args: Vec::new(),\n");
code.push_str(" }\n");
code.push_str(" }\n\n");
for opt in &result.options {
for long_flag in &opt.long_flags {
let method_name = sanitize_identifier(&long_flag.trim_start_matches("--"));
if opt.takes_argument {
let arg_type = match opt.option_type {
OptionType::Number => "i64",
OptionType::Path => "&str",
OptionType::String => "&str",
OptionType::Choice => "&str",
OptionType::Boolean => "&str",
};
code.push_str(&format!(
" pub fn {}(mut self, value: {}) -> Self {{\n",
method_name, arg_type
));
code.push_str(&format!(
" self.args.push(\"{}\".to_string());\n",
long_flag
));
code.push_str(&format!(" self.args.push(value.to_string());\n"));
code.push_str(" self\n");
code.push_str(" }\n\n");
} else {
code.push_str(&format!(
" pub fn {}(mut self) -> Self {{\n",
method_name
));
code.push_str(&format!(
" self.args.push(\"{}\".to_string());\n",
long_flag
));
code.push_str(" self\n");
code.push_str(" }\n\n");
}
}
}
for (idx, arg) in result.arguments.iter().enumerate() {
let method_name = if arg.variadic {
format!("arg{}", idx + 1)
} else {
sanitize_identifier(&arg.name.to_lowercase())
};
let arg_type = match arg.arg_type {
Some(crate::model::ArgumentType::Number) => "i64",
Some(crate::model::ArgumentType::Path) => "&str",
Some(crate::model::ArgumentType::Url) => "&str",
Some(crate::model::ArgumentType::Email) => "&str",
_ => "&str",
};
if arg.variadic {
code.push_str(&format!(
" pub fn {}(mut self, values: Vec<{}>) -> Self {{\n",
method_name, arg_type
));
code.push_str(" for value in values {\n");
code.push_str(" self.args.push(value.to_string());\n");
code.push_str(" }\n");
code.push_str(" self\n");
code.push_str(" }\n\n");
} else {
code.push_str(&format!(
" pub fn {}(mut self, value: {}) -> Self {{\n",
method_name, arg_type
));
code.push_str(&format!(" self.args.push(value.to_string());\n"));
code.push_str(" self\n");
code.push_str(" }\n\n");
}
}
for subcmd in &result.subcommands {
let method_name = sanitize_identifier(&subcmd.name);
code.push_str(&format!(
" pub fn {}(mut self) -> Self {{\n",
method_name
));
code.push_str(&format!(
" self.args.push(\"{}\".to_string());\n",
subcmd.name
));
code.push_str(" self\n");
code.push_str(" }\n\n");
}
code.push_str(" pub fn build(&self) -> Command {\n");
code.push_str(" let mut cmd = Command::new(&self.command);\n");
code.push_str(" cmd.args(&self.args);\n");
code.push_str(" cmd\n");
code.push_str(" }\n\n");
code.push_str(" pub fn execute(&self) -> std::io::Result<std::process::Output> {\n");
code.push_str(" self.build().output()\n");
code.push_str(" }\n");
code.push_str("}\n");
code
}
fn generate_python_builder(result: &ProbeResult) -> String {
let cmd_name = sanitize_identifier(&result.command);
let builder_name = format!("{}Builder", capitalize_first(&cmd_name));
let mut code = String::new();
code.push_str(&format!(
"# Generated command builder for {}\n",
result.command
));
code.push_str("# Generated by help-probe\n\n");
code.push_str("import subprocess\nfrom typing import List, Optional, Union\n\n");
code.push_str(&format!("class {}:\n", builder_name));
code.push_str(" def __init__(self):\n");
code.push_str(&format!(" self.command = \"{}\"\n", result.command));
code.push_str(" self.args: List[str] = []\n\n");
for opt in &result.options {
for long_flag in &opt.long_flags {
let method_name = sanitize_identifier(&long_flag.trim_start_matches("--"));
if opt.takes_argument {
code.push_str(&format!(
" def {}(self, value: str) -> '{}':\n",
method_name, builder_name
));
code.push_str(&format!(" self.args.append(\"{}\")\n", long_flag));
code.push_str(" self.args.append(str(value))\n");
code.push_str(" return self\n\n");
} else {
code.push_str(&format!(
" def {}(self) -> '{}':\n",
method_name, builder_name
));
code.push_str(&format!(" self.args.append(\"{}\")\n", long_flag));
code.push_str(" return self\n\n");
}
}
}
for (idx, arg) in result.arguments.iter().enumerate() {
let method_name = if arg.variadic {
format!("arg{}", idx + 1)
} else {
sanitize_identifier(&arg.name.to_lowercase())
};
if arg.variadic {
code.push_str(&format!(
" def {}(self, values: List[str]) -> '{}':\n",
method_name, builder_name
));
code.push_str(" for value in values:\n");
code.push_str(" self.args.append(str(value))\n");
code.push_str(" return self\n\n");
} else {
code.push_str(&format!(
" def {}(self, value: str) -> '{}':\n",
method_name, builder_name
));
code.push_str(" self.args.append(str(value))\n");
code.push_str(" return self\n\n");
}
}
for subcmd in &result.subcommands {
let method_name = sanitize_identifier(&subcmd.name);
code.push_str(&format!(
" def {}(self) -> '{}':\n",
method_name, builder_name
));
code.push_str(&format!(" self.args.append(\"{}\")\n", subcmd.name));
code.push_str(" return self\n\n");
}
code.push_str(" def build(self) -> List[str]:\n");
code.push_str(" return [self.command] + self.args\n\n");
code.push_str(" def execute(self) -> subprocess.CompletedProcess:\n");
code.push_str(" return subprocess.run(self.build(), capture_output=True, text=True)\n");
code
}
fn generate_javascript_builder(result: &ProbeResult) -> String {
let cmd_name = sanitize_identifier(&result.command);
let builder_name = format!("{}Builder", capitalize_first(&cmd_name));
let mut code = String::new();
code.push_str(&format!(
"// Generated command builder for {}\n",
result.command
));
code.push_str("// Generated by help-probe\n\n");
code.push_str("const { spawn } = require('child_process');\n\n");
code.push_str(&format!("class {} {{\n", builder_name));
code.push_str(" constructor() {\n");
code.push_str(&format!(" this.command = \"{}\";\n", result.command));
code.push_str(" this.args = [];\n");
code.push_str(" }\n\n");
for opt in &result.options {
for long_flag in &opt.long_flags {
let method_name = sanitize_identifier(&long_flag.trim_start_matches("--"));
if opt.takes_argument {
code.push_str(&format!(" {}(value) {{\n", method_name));
code.push_str(&format!(" this.args.push(\"{}\");\n", long_flag));
code.push_str(" this.args.push(String(value));\n");
code.push_str(" return this;\n");
code.push_str(" }\n\n");
} else {
code.push_str(&format!(" {}() {{\n", method_name));
code.push_str(&format!(" this.args.push(\"{}\");\n", long_flag));
code.push_str(" return this;\n");
code.push_str(" }\n\n");
}
}
}
for (idx, arg) in result.arguments.iter().enumerate() {
let method_name = if arg.variadic {
format!("arg{}", idx + 1)
} else {
sanitize_identifier(&arg.name.to_lowercase())
};
if arg.variadic {
code.push_str(&format!(" {}(values) {{\n", method_name));
code.push_str(" for (const value of values) {\n");
code.push_str(" this.args.push(String(value));\n");
code.push_str(" }\n");
code.push_str(" return this;\n");
code.push_str(" }\n\n");
} else {
code.push_str(&format!(" {}(value) {{\n", method_name));
code.push_str(" this.args.push(String(value));\n");
code.push_str(" return this;\n");
code.push_str(" }\n\n");
}
}
for subcmd in &result.subcommands {
let method_name = sanitize_identifier(&subcmd.name);
code.push_str(&format!(" {}() {{\n", method_name));
code.push_str(&format!(" this.args.push(\"{}\");\n", subcmd.name));
code.push_str(" return this;\n");
code.push_str(" }\n\n");
}
code.push_str(" build() {\n");
code.push_str(" return [this.command, ...this.args];\n");
code.push_str(" }\n\n");
code.push_str(" execute() {\n");
code.push_str(" return new Promise((resolve, reject) => {\n");
code.push_str(" const proc = spawn(this.command, this.args);\n");
code.push_str(" let stdout = '';\n");
code.push_str(" let stderr = '';\n");
code.push_str(" proc.stdout.on('data', (data) => { stdout += data; });\n");
code.push_str(" proc.stderr.on('data', (data) => { stderr += data; });\n");
code.push_str(" proc.on('close', (code) => {\n");
code.push_str(" resolve({ code, stdout, stderr });\n");
code.push_str(" });\n");
code.push_str(" proc.on('error', reject);\n");
code.push_str(" });\n");
code.push_str(" }\n");
code.push_str("}\n\n");
code.push_str(&format!("module.exports = {};\n", builder_name));
code
}
fn generate_typescript_builder(result: &ProbeResult) -> String {
let cmd_name = sanitize_identifier(&result.command);
let builder_name = format!("{}Builder", capitalize_first(&cmd_name));
let mut code = String::new();
code.push_str(&format!(
"// Generated command builder for {}\n",
result.command
));
code.push_str("// Generated by help-probe\n\n");
code.push_str("import { spawn } from 'child_process';\n\n");
code.push_str(&format!("export class {} {{\n", builder_name));
code.push_str(" private command: string;\n");
code.push_str(" private args: string[];\n\n");
code.push_str(" constructor() {\n");
code.push_str(&format!(" this.command = \"{}\";\n", result.command));
code.push_str(" this.args = [];\n");
code.push_str(" }\n\n");
for opt in &result.options {
for long_flag in &opt.long_flags {
let method_name = sanitize_identifier(&long_flag.trim_start_matches("--"));
if opt.takes_argument {
code.push_str(&format!(
" {}(value: string): {} {{\n",
method_name, builder_name
));
code.push_str(&format!(" this.args.push(\"{}\");\n", long_flag));
code.push_str(" this.args.push(value);\n");
code.push_str(" return this;\n");
code.push_str(" }\n\n");
} else {
code.push_str(&format!(" {}(): {} {{\n", method_name, builder_name));
code.push_str(&format!(" this.args.push(\"{}\");\n", long_flag));
code.push_str(" return this;\n");
code.push_str(" }\n\n");
}
}
}
for (idx, arg) in result.arguments.iter().enumerate() {
let method_name = if arg.variadic {
format!("arg{}", idx + 1)
} else {
sanitize_identifier(&arg.name.to_lowercase())
};
if arg.variadic {
code.push_str(&format!(
" {}(values: string[]): {} {{\n",
method_name, builder_name
));
code.push_str(" this.args.push(...values);\n");
code.push_str(" return this;\n");
code.push_str(" }\n\n");
} else {
code.push_str(&format!(
" {}(value: string): {} {{\n",
method_name, builder_name
));
code.push_str(" this.args.push(value);\n");
code.push_str(" return this;\n");
code.push_str(" }\n\n");
}
}
for subcmd in &result.subcommands {
let method_name = sanitize_identifier(&subcmd.name);
code.push_str(&format!(" {}(): {} {{\n", method_name, builder_name));
code.push_str(&format!(" this.args.push(\"{}\");\n", subcmd.name));
code.push_str(" return this;\n");
code.push_str(" }\n\n");
}
code.push_str(" build(): string[] {\n");
code.push_str(" return [this.command, ...this.args];\n");
code.push_str(" }\n\n");
code.push_str(
" execute(): Promise<{ code: number | null; stdout: string; stderr: string }> {\n",
);
code.push_str(" return new Promise((resolve, reject) => {\n");
code.push_str(" const proc = spawn(this.command, this.args);\n");
code.push_str(" let stdout = '';\n");
code.push_str(" let stderr = '';\n");
code.push_str(
" proc.stdout?.on('data', (data) => { stdout += data.toString(); });\n",
);
code.push_str(
" proc.stderr?.on('data', (data) => { stderr += data.toString(); });\n",
);
code.push_str(" proc.on('close', (code) => {\n");
code.push_str(" resolve({ code, stdout, stderr });\n");
code.push_str(" });\n");
code.push_str(" proc.on('error', reject);\n");
code.push_str(" });\n");
code.push_str(" }\n");
code.push_str("}\n");
code
}
fn sanitize_identifier(s: &str) -> String {
s.chars()
.map(|c| {
if c.is_alphanumeric() || c == '_' {
c
} else {
'_'
}
})
.collect::<String>()
.trim_start_matches('_')
.to_string()
}
fn capitalize_first(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
}
}