1#![allow(clippy::write_with_newline)] 2 3use std::fmt::Write; 4 5// Internal 6use clap::*; 7use clap_complete::*; 8 9/// Generate fig completion file 10pub struct Fig; 11 12impl Generator for Fig { 13 fn file_name(&self, name: &str) -> String { 14 format!("{name}.ts") 15 } 16 17 fn generate(&self, cmd: &Command, buf: &mut dyn std::io::Write) { 18 let command = cmd.get_bin_name().unwrap(); 19 let mut buffer = String::new(); 20 21 write!( 22 &mut buffer, 23 "const completion: Fig.Spec = {{\n name: \"{}\",\n", 24 escape_string(command) 25 ) 26 .unwrap(); 27 28 write!( 29 &mut buffer, 30 " description: \"{}\",\n", 31 escape_string(&cmd.get_about().unwrap_or_default().to_string()) 32 ) 33 .unwrap(); 34 35 gen_fig_inner(&[], 2, cmd, &mut buffer); 36 37 write!(&mut buffer, "}};\n\nexport default completion;\n").unwrap(); 38 39 buf.write_all(buffer.as_bytes()) 40 .expect("Failed to write to generated file"); 41 } 42} 43 44// Escape string inside double quotes and convert whitespace 45fn escape_string(string: &str) -> String { 46 string 47 .replace('\\', "\\\\") 48 .replace('\"', "\\\"") 49 .replace('\t', " ") 50 .replace('\n', " ") 51 .replace('\r', "") 52} 53 54fn gen_fig_inner(parent_commands: &[&str], indent: usize, cmd: &Command, buffer: &mut String) { 55 if cmd.has_subcommands() { 56 write!(buffer, "{:indent$}subcommands: [\n", "", indent = indent).unwrap(); 57 // generate subcommands 58 for subcommand in cmd.get_subcommands() { 59 let mut aliases: Vec<&str> = subcommand.get_all_aliases().collect(); 60 if !aliases.is_empty() { 61 aliases.insert(0, subcommand.get_name()); 62 63 write!( 64 buffer, 65 "{:indent$}{{\n{:indent$} name: [", 66 "", 67 "", 68 indent = indent + 2 69 ) 70 .unwrap(); 71 72 buffer.push_str( 73 &aliases 74 .iter() 75 .map(|name| format!("\"{}\"", escape_string(name))) 76 .collect::<Vec<_>>() 77 .join(", "), 78 ); 79 80 write!(buffer, "],\n").unwrap(); 81 } else { 82 write!( 83 buffer, 84 "{:indent$}{{\n{:indent$} name: \"{}\",\n", 85 "", 86 "", 87 escape_string(subcommand.get_name()), 88 indent = indent + 2 89 ) 90 .unwrap(); 91 } 92 93 if let Some(data) = subcommand.get_about() { 94 write!( 95 buffer, 96 "{:indent$}description: \"{}\",\n", 97 "", 98 escape_string(&data.to_string()), 99 indent = indent + 4 100 ) 101 .unwrap(); 102 } 103 104 if subcommand.is_hide_set() { 105 write!(buffer, "{:indent$}hidden: true,\n", "", indent = indent + 4).unwrap(); 106 } 107 108 let mut parent_commands: Vec<_> = parent_commands.into(); 109 parent_commands.push(subcommand.get_name()); 110 gen_fig_inner(&parent_commands, indent + 4, subcommand, buffer); 111 112 write!(buffer, "{:indent$}}},\n", "", indent = indent + 2).unwrap(); 113 } 114 write!(buffer, "{:indent$}],\n", "", indent = indent).unwrap(); 115 } 116 117 buffer.push_str(&gen_options(cmd, indent)); 118 119 let args = cmd.get_positionals().collect::<Vec<_>>(); 120 121 match args.len() { 122 0 => {} 123 1 => { 124 write!(buffer, "{:indent$}args: ", "", indent = indent).unwrap(); 125 126 buffer.push_str(&gen_args(args[0], indent)); 127 } 128 _ => { 129 write!(buffer, "{:indent$}args: [\n", "", indent = indent).unwrap(); 130 for arg in args { 131 write!(buffer, "{:indent$}", "", indent = indent + 2).unwrap(); 132 buffer.push_str(&gen_args(arg, indent + 2)); 133 } 134 write!(buffer, "{:indent$}]\n", "", indent = indent).unwrap(); 135 } 136 }; 137} 138 139fn gen_options(cmd: &Command, indent: usize) -> String { 140 let mut buffer = String::new(); 141 142 let flags = generator::utils::flags(cmd); 143 144 if cmd.get_opts().next().is_some() || !flags.is_empty() { 145 write!(&mut buffer, "{:indent$}options: [\n", "", indent = indent).unwrap(); 146 147 for option in cmd.get_opts() { 148 write!(&mut buffer, "{:indent$}{{\n", "", indent = indent + 2).unwrap(); 149 150 let mut names = vec![]; 151 152 if let Some(shorts) = option.get_short_and_visible_aliases() { 153 names.extend( 154 shorts 155 .iter() 156 .map(|short| format!("-{}", escape_string(&short.to_string()))), 157 ); 158 } 159 160 if let Some(longs) = option.get_long_and_visible_aliases() { 161 names.extend( 162 longs 163 .iter() 164 .map(|long| format!("--{}", escape_string(long))), 165 ); 166 } 167 168 if names.len() > 1 { 169 write!(&mut buffer, "{:indent$}name: [", "", indent = indent + 4).unwrap(); 170 171 buffer.push_str( 172 &names 173 .iter() 174 .map(|name| format!("\"{}\"", escape_string(name))) 175 .collect::<Vec<_>>() 176 .join(", "), 177 ); 178 179 buffer.push_str("],\n"); 180 } else { 181 write!( 182 &mut buffer, 183 "{:indent$}name: \"{}\",\n", 184 "", 185 escape_string(&names[0]), 186 indent = indent + 4 187 ) 188 .unwrap(); 189 } 190 191 if let Some(data) = option.get_help() { 192 write!( 193 &mut buffer, 194 "{:indent$}description: \"{}\",\n", 195 "", 196 escape_string(&data.to_string()), 197 indent = indent + 4 198 ) 199 .unwrap(); 200 } 201 202 if option.is_hide_set() { 203 write!( 204 &mut buffer, 205 "{:indent$}hidden: true,\n", 206 "", 207 indent = indent + 4 208 ) 209 .unwrap(); 210 } 211 212 let conflicts = arg_conflicts(cmd, option); 213 214 if !conflicts.is_empty() { 215 write!( 216 &mut buffer, 217 "{:indent$}exclusiveOn: [\n", 218 "", 219 indent = indent + 4 220 ) 221 .unwrap(); 222 223 for conflict in conflicts { 224 write!( 225 &mut buffer, 226 "{:indent$}\"{}\",\n", 227 "", 228 escape_string(&conflict), 229 indent = indent + 6 230 ) 231 .unwrap(); 232 } 233 234 write!(&mut buffer, "{:indent$}],\n", "", indent = indent + 4).unwrap(); 235 } 236 237 if let ArgAction::Set | ArgAction::Append | ArgAction::Count = option.get_action() { 238 write!( 239 &mut buffer, 240 "{:indent$}isRepeatable: true,\n", 241 "", 242 indent = indent + 4 243 ) 244 .unwrap(); 245 } 246 247 if option.is_require_equals_set() { 248 write!( 249 &mut buffer, 250 "{:indent$}requiresEquals: true,\n", 251 "", 252 indent = indent + 4 253 ) 254 .unwrap(); 255 } 256 257 write!(&mut buffer, "{:indent$}args: ", "", indent = indent + 4).unwrap(); 258 259 buffer.push_str(&gen_args(option, indent + 4)); 260 261 write!(&mut buffer, "{:indent$}}},\n", "", indent = indent + 2).unwrap(); 262 } 263 264 for flag in generator::utils::flags(cmd) { 265 write!(&mut buffer, "{:indent$}{{\n", "", indent = indent + 2).unwrap(); 266 267 let mut flags = vec![]; 268 269 if let Some(shorts) = flag.get_short_and_visible_aliases() { 270 flags.extend(shorts.iter().map(|s| format!("-{s}"))); 271 } 272 273 if let Some(longs) = flag.get_long_and_visible_aliases() { 274 flags.extend(longs.iter().map(|s| format!("--{s}"))); 275 } 276 277 if flags.len() > 1 { 278 write!(&mut buffer, "{:indent$}name: [", "", indent = indent + 4).unwrap(); 279 280 buffer.push_str( 281 &flags 282 .iter() 283 .map(|name| format!("\"{}\"", escape_string(name))) 284 .collect::<Vec<_>>() 285 .join(", "), 286 ); 287 288 buffer.push_str("],\n"); 289 } else { 290 write!( 291 &mut buffer, 292 "{:indent$}name: \"{}\",\n", 293 "", 294 escape_string(&flags[0]), 295 indent = indent + 4 296 ) 297 .unwrap(); 298 } 299 300 if let Some(data) = flag.get_help() { 301 write!( 302 &mut buffer, 303 "{:indent$}description: \"{}\",\n", 304 "", 305 escape_string(&data.to_string()).as_str(), 306 indent = indent + 4 307 ) 308 .unwrap(); 309 } 310 311 let conflicts = arg_conflicts(cmd, &flag); 312 313 if !conflicts.is_empty() { 314 write!( 315 &mut buffer, 316 "{:indent$}exclusiveOn: [\n", 317 "", 318 indent = indent + 4 319 ) 320 .unwrap(); 321 322 for conflict in conflicts { 323 write!( 324 &mut buffer, 325 "{:indent$}\"{}\",\n", 326 "", 327 escape_string(&conflict), 328 indent = indent + 6 329 ) 330 .unwrap(); 331 } 332 333 write!(&mut buffer, "{:indent$}],\n", "", indent = indent + 4).unwrap(); 334 } 335 336 if let ArgAction::Set | ArgAction::Append | ArgAction::Count = flag.get_action() { 337 write!( 338 &mut buffer, 339 "{:indent$}isRepeatable: true,\n", 340 "", 341 indent = indent + 4 342 ) 343 .unwrap(); 344 } 345 346 write!(&mut buffer, "{:indent$}}},\n", "", indent = indent + 2).unwrap(); 347 } 348 349 write!(&mut buffer, "{:indent$}],\n", "", indent = indent).unwrap(); 350 } 351 352 buffer 353} 354 355fn gen_args(arg: &Arg, indent: usize) -> String { 356 if !arg.get_num_args().expect("built").takes_values() { 357 return "".to_string(); 358 } 359 360 let mut buffer = String::new(); 361 362 write!( 363 &mut buffer, 364 "{{\n{:indent$} name: \"{}\",\n", 365 "", 366 escape_string(arg.get_id().as_str()), 367 indent = indent 368 ) 369 .unwrap(); 370 371 let num_args = arg.get_num_args().expect("built"); 372 if num_args != builder::ValueRange::EMPTY && num_args != builder::ValueRange::SINGLE { 373 write!( 374 &mut buffer, 375 "{:indent$}isVariadic: true,\n", 376 "", 377 indent = indent + 2 378 ) 379 .unwrap(); 380 } 381 382 if !arg.is_required_set() { 383 write!( 384 &mut buffer, 385 "{:indent$}isOptional: true,\n", 386 "", 387 indent = indent + 2 388 ) 389 .unwrap(); 390 } 391 392 if let Some(data) = generator::utils::possible_values(arg) { 393 write!( 394 &mut buffer, 395 "{:indent$}suggestions: [\n", 396 "", 397 indent = indent + 2 398 ) 399 .unwrap(); 400 401 for value in data { 402 if let Some(help) = value.get_help() { 403 write!( 404 &mut buffer, 405 "{:indent$}{{\n{:indent$} name: \"{}\",\n", 406 "", 407 "", 408 escape_string(value.get_name()), 409 indent = indent + 4, 410 ) 411 .unwrap(); 412 413 write!( 414 &mut buffer, 415 "{:indent$}description: \"{}\",\n", 416 "", 417 escape_string(&help.to_string()), 418 indent = indent + 6 419 ) 420 .unwrap(); 421 422 write!(&mut buffer, "{:indent$}}},\n", "", indent = indent + 4).unwrap(); 423 } else { 424 write!( 425 &mut buffer, 426 "{:indent$}\"{}\",\n", 427 "", 428 escape_string(value.get_name()), 429 indent = indent + 4, 430 ) 431 .unwrap(); 432 } 433 } 434 435 write!(&mut buffer, "{:indent$}],\n", "", indent = indent + 2).unwrap(); 436 } else { 437 match arg.get_value_hint() { 438 ValueHint::AnyPath | ValueHint::FilePath | ValueHint::ExecutablePath => { 439 write!( 440 &mut buffer, 441 "{:indent$}template: \"filepaths\",\n", 442 "", 443 indent = indent + 2 444 ) 445 .unwrap(); 446 } 447 ValueHint::DirPath => { 448 write!( 449 &mut buffer, 450 "{:indent$}template: \"folders\",\n", 451 "", 452 indent = indent + 2 453 ) 454 .unwrap(); 455 } 456 ValueHint::CommandString | ValueHint::CommandName | ValueHint::CommandWithArguments => { 457 write!( 458 &mut buffer, 459 "{:indent$}isCommand: true,\n", 460 "", 461 indent = indent + 2 462 ) 463 .unwrap(); 464 } 465 // Disable completion for others 466 _ => (), 467 }; 468 }; 469 470 write!(&mut buffer, "{:indent$}}},\n", "", indent = indent).unwrap(); 471 472 buffer 473} 474 475fn arg_conflicts(cmd: &Command, arg: &Arg) -> Vec<String> { 476 let mut res = vec![]; 477 478 for conflict in cmd.get_arg_conflicts_with(arg) { 479 if let Some(s) = conflict.get_short() { 480 res.push(format!("-{}", escape_string(&s.to_string()))); 481 } 482 483 if let Some(l) = conflict.get_long() { 484 res.push(format!("--{}", escape_string(l))); 485 } 486 } 487 488 res 489} 490