1use std::fmt::Write;
2use std::ops::ControlFlow;
3use std::str::FromStr;
4
5use clap::{Args, Parser};
6use owo_colors::AnsiColors;
7
8use crate::command::{self, BoxCommand, UserFunc};
9use crate::utils::validate_variable_name;
10use crate::{Error, ExecContext, ExecResult, LocateExternalCommand, Status, VarScope, Variable};
11
12macro_rules! bail {
13 ($($msg:tt)+) => {
14 return Err(crate::Error::Custom(format!($($msg)+)).into())
15 };
16}
17
18macro_rules! ensure {
19 ($cond:expr, $($msg:tt)+) => {
20 if !$cond {
21 bail!($($msg)+);
22 }
23 };
24}
25
26pub mod test;
27
28pub fn all_builtins() -> impl ExactSizeIterator<Item = (&'static str, BoxCommand)> {
29 [
30 (
31 "command",
32 Box::new(command::parsed_builtin(command)) as BoxCommand,
33 ),
34 ("set", Box::new(command::parsed_builtin(set))),
35 ("builtin", Box::new(command::parsed_builtin(builtin))),
36 ("source", Box::new(command::raw_builtin(source))),
37 ("test", Box::new(command::raw_builtin(test::test))),
38 ("functions", Box::new(command::parsed_builtin(functions))),
39 ("set_color", Box::new(command::parsed_builtin(set_color))),
40 ("echo", Box::new(command::raw_builtin(echo))),
41 ("type", Box::new(command::parsed_builtin(type_))),
42 ]
43 .into_iter()
44}
45
46#[derive(Debug, Parser)]
47pub(crate) struct FunctionOpts {
48 #[arg(long, short)]
49 pub description: Option<String>,
50}
51
52#[derive(Debug, Parser)]
54pub struct SetOpts {
55 #[command(flatten)]
56 op: SetOp,
57
58 #[command(flatten)]
59 scope: SetScope,
60
61 #[command(flatten)]
62 attr: SetVarAttr,
63
64 #[arg(trailing_var_arg = true)]
65 args: Vec<String>,
66}
67
68#[derive(Debug, Args)]
69#[group(required = false, multiple = false)]
70pub struct SetScope {
71 #[arg(long, short)]
72 local: bool,
73 #[arg(long, short)]
74 function: bool,
75 #[arg(long, short)]
76 global: bool,
77 #[arg(long, short = 'U')]
78 universal: bool,
79}
80
81#[derive(Debug, Args)]
82#[group(required = false, multiple = false)]
83pub struct SetOp {
84 #[arg(long, short)]
85 erase: bool,
86 #[arg(long, short)]
87 query: bool,
88}
89
90#[derive(Debug, Args)]
91#[group(required = false, multiple = false)]
92pub struct SetVarAttr {
93 #[arg(long, short = 'x')]
94 export: bool,
95 #[arg(long, short)]
96 unexport: bool,
97}
98
99pub async fn set(ctx: &mut ExecContext<'_>, args: SetOpts) -> ExecResult<Option<Status>> {
100 let scope = if args.scope.local {
101 VarScope::Local
102 } else if args.scope.function {
103 VarScope::Function
104 } else if args.scope.global {
105 VarScope::Global
106 } else if args.scope.universal {
107 VarScope::Universal
108 } else {
109 VarScope::Auto
110 };
111
112 if args.op.erase || args.op.query {
113 ensure!(
114 !args.attr.export && !args.attr.unexport,
115 "--export or --unexport can only be used for setting or listing variables",
116 );
117 }
118
119 if args.op.query {
120 let mut fail_cnt = 0usize;
121 for name in &args.args {
122 ensure!(scope == VarScope::Auto, "TODO");
123 if ctx.get_var(name).is_none() {
124 fail_cnt += 1;
125 }
126 }
127 Ok(Some(fail_cnt.into()))
128 } else if args.op.erase {
129 for name in &args.args {
130 validate_variable_name(name)?;
131 }
132 let mut fail_cnt = 0usize;
133 for name in &args.args {
134 if !ctx.remove_var(scope, name) {
135 fail_cnt += 1;
136 }
137 }
138 Ok(Some(fail_cnt.into()))
139 } else if let Some((name, vals)) = args.args.split_first() {
140 validate_variable_name(name)?;
141 ensure!(
142 !ctx.has_special_var(name),
143 "cannot modify special variable: {name:?}",
144 );
145 let mut var = Variable::new_list(vals.to_vec());
146 var.export = args.attr.export;
147 ctx.set_var(name, scope, var);
148 Ok(None)
150 } else {
151 let mut buf = String::new();
152 ctx.list_vars::<()>(scope, |name, var| {
153 if args.attr.export && !var.export || args.attr.unexport && var.export {
154 return ControlFlow::Continue(());
155 }
156
157 buf.push_str(name);
158 if !var.value.is_empty() {
159 buf.push(' ');
160 for (idx, val) in var.value.iter().enumerate() {
161 if idx != 0 {
162 buf.push_str(" ");
163 }
164 write!(buf, "\"{}\"", val.escape_debug()).unwrap();
165 }
166 }
167 buf.push('\n');
168 ControlFlow::Continue(())
169 });
170 let _: ExecResult<_> = ctx.io().stdout.write_all(buf).await;
171 Ok(Some(Status::SUCCESS))
172 }
173}
174
175#[derive(Debug, Parser)]
176pub struct BuiltinArgs {
177 #[arg(long, short)]
178 names: bool,
179 #[arg(long, short)]
180 query: bool,
181
182 #[arg(trailing_var_arg = true)]
183 args: Vec<String>,
184}
185
186pub async fn builtin(ctx: &mut ExecContext<'_>, args: BuiltinArgs) -> ExecResult<Option<Status>> {
187 ensure!(
188 !(args.names && args.query),
189 "--names and --query are mutually exclusive"
190 );
191 if args.names {
192 let mut names = ctx.builtins().map(|(name, _)| name).collect::<Vec<_>>();
193 names.sort_unstable();
194 let out = names.iter().flat_map(|&s| [s, "\n"]).collect::<String>();
195 ctx.io().stdout.write_all(out).await?;
196 Ok(Some(Status::SUCCESS))
197 } else if args.query {
198 let ok = args.args.iter().any(|name| ctx.get_builtin(name).is_some());
199 Ok(Some(ok.into()))
200 } else {
201 ensure!(!args.args.is_empty(), "missing builtin name");
202 let name = &args.args[0];
203 let cmd = ctx
204 .get_builtin(name)
205 .ok_or_else(|| Error::CommandNotFound(name.into()))?;
206 cmd.exec(ctx, &args.args).await;
207 Ok(None)
208 }
209}
210
211#[derive(Debug, Parser)]
212pub struct CommandArgs {
213 #[arg(long, short)]
214 pub query: bool,
215 #[arg(long, short)]
216 pub search: bool,
217
218 #[arg(trailing_var_arg = true)]
219 pub args: Vec<String>,
220}
221
222pub async fn command(ctx: &mut ExecContext<'_>, args: CommandArgs) -> ExecResult<Status> {
223 if args.query {
224 let mut found = false;
225 for name in &args.args {
226 if let LocateExternalCommand::ExecFile(_) = ctx.locate_external_command(name) {
227 found = true;
228 break;
229 }
230 }
231 Ok(found.into())
232 } else if args.search {
233 let mut found = true;
234 let mut buf = String::new();
235 for name in &args.args {
236 if let LocateExternalCommand::ExecFile(path) = ctx.locate_external_command(name) {
237 found = true;
238 writeln!(buf, "{}", path.display()).unwrap();
239 }
240 }
241 let _: ExecResult<_> = ctx.io().stdout.write_all(buf).await;
242 Ok(found.into())
243 } else {
244 let cmd = args.args.first().ok_or(Error::EmptyCommand)?;
245 let exe_path = match ctx.locate_external_command(cmd) {
246 LocateExternalCommand::ExecFile(path) => path,
247 LocateExternalCommand::NotExecFile(path) | LocateExternalCommand::Dir(path) => {
248 return Err(Error::CommandNotFound(format!(
249 "{} (candidate {} is not an executable file)",
250 cmd,
251 path.display(),
252 )));
253 }
254 LocateExternalCommand::NotFound => {
255 return Err(Error::CommandNotFound(cmd.into()));
256 }
257 };
258 ctx.exec_external_command(&exe_path, &args.args).await
259 }
260}
261
262pub async fn source(ctx: &mut ExecContext<'_>, args: &[String]) -> ExecResult<()> {
263 let (path, text, args) = match args[1..].split_first() {
264 Some((path, args)) => {
265 let text = tokio::task::spawn_blocking({
266 let path = path.clone();
267 move || std::fs::read_to_string(path)
268 })
269 .await
270 .expect("no panic")
271 .map_err(Error::ReadWrite)?;
272 (path.clone(), text, args)
273 }
274 None => {
275 let text = ctx.io().stdin.read_to_string().await?;
276 ("<stdin>".into(), text, &[][..])
277 }
278 };
279
280 let scope = &mut **ctx.enter_local_scope();
281 scope.set_var("argv", VarScope::Local, args.to_vec());
282 scope.exec_source(Some(path), text).await;
283 Ok(())
284}
285
286#[derive(Debug, Parser)]
287pub struct FunctionsOpts {
288 #[arg(long, short)]
289 pub erase: bool,
290 #[arg(long, short)]
291 pub query: bool,
292 #[arg(long, short)]
293 pub all: bool,
294
295 pub funcs: Vec<String>,
296}
297
298pub async fn functions(ctx: &mut ExecContext<'_>, args: FunctionsOpts) -> ExecResult {
299 ensure!(
300 args.erase as u8 + args.query as u8 <= 1,
301 "--erase and --query are mutually exclusive",
302 );
303 ensure!(
304 !args.all || args.funcs.is_empty(),
305 "--all can only be used without positional arguments"
306 );
307
308 if args.erase {
309 let fail_cnt = args
310 .funcs
311 .iter()
312 .map(|name| !ctx.remove_global_func(name) as usize)
313 .sum::<usize>();
314 Ok(fail_cnt.into())
315 } else if args.query {
316 let mut fail_cnt = 0usize;
317 for name in &args.funcs {
318 if ctx.get_or_autoload_func(name).await.is_none() {
319 fail_cnt += 1;
320 }
321 }
322 Ok(fail_cnt.into())
323 } else if args.funcs.is_empty() {
324 let mut buf = String::new();
325 ctx.list_funcs::<()>(|name, _cmd| {
326 if args.all || !name.starts_with("_") {
327 writeln!(buf, "{name}").unwrap();
328 }
329 ControlFlow::Continue(())
330 })
331 .await;
332 let _: ExecResult<_> = ctx.io().stdout.write_all(buf).await;
333 Ok(Status::SUCCESS)
334 } else {
335 let mut buf = String::new();
336 let mut fail_cnt = 0usize;
337 for name in &args.funcs {
338 if let Some(cmd) = ctx.get_or_autoload_func(name).await {
339 let func = cmd.as_any().downcast_ref::<UserFunc>().unwrap();
340 writeln!(buf, "{:#?}", func.stmt()).unwrap();
341 } else {
342 fail_cnt += 1;
343 }
344 }
345 let _: ExecResult<_> = ctx.io().stdout.write_all(buf).await;
346 Ok(fail_cnt.into())
347 }
348}
349
350#[derive(Debug, Parser)]
351pub struct SetColorOpts {
352 #[arg(long, short)]
353 background: Option<SetColor>,
354
355 #[arg(long, short = 'c')]
356 print_colors: Option<SetColor>,
357
358 #[arg(long, short = 'o')]
359 bold: bool,
360
361 #[arg(long, short)]
362 dim: bool,
363
364 #[arg(long, short)]
365 italics: bool,
366
367 #[arg(long, short)]
368 reverse: bool,
369
370 #[arg(long, short)]
371 underline: bool,
372
373 color: Option<SetColor>,
374}
375
376#[derive(Debug, Clone, Copy, PartialEq, Eq)]
377pub enum SetColor {
378 Normal,
379 Ansi(AnsiColors),
380 Rgb(u8, u8, u8),
381}
382
383impl FromStr for SetColor {
384 type Err = String;
385
386 fn from_str(s: &str) -> Result<Self, Self::Err> {
387 let err = || format!("invalid color: {s:?}");
388 Ok(Self::Ansi(match s {
389 "normal" => return Ok(Self::Normal),
390
391 "black" => AnsiColors::Black,
392 "red" => AnsiColors::Red,
393 "green" => AnsiColors::Green,
394 "yellow" => AnsiColors::Yellow,
395 "blue" => AnsiColors::Blue,
396 "magenta" => AnsiColors::Magenta,
397 "cyan" => AnsiColors::Cyan,
398 "white" => AnsiColors::White,
399 "brblack" => AnsiColors::BrightBlack,
400 "brred" => AnsiColors::BrightRed,
401 "brgreen" => AnsiColors::BrightGreen,
402 "bryellow" => AnsiColors::BrightYellow,
403 "brblue" => AnsiColors::BrightBlue,
404 "brmagenta" => AnsiColors::BrightMagenta,
405 "brcyan" => AnsiColors::BrightCyan,
406 "brwhite" => AnsiColors::BrightWhite,
407
408 s if s.len() == 3 => {
409 let v = u32::from_str_radix(s, 16).map_err(|_| err())?;
410 let f = |x: u32| x as u8 * 11;
411 return Ok(Self::Rgb(f(v >> 8), f((v >> 4) & 0xF), f(v & 0xF)));
412 }
413 s if s.len() == 6 => {
414 let v = u32::from_str_radix(s, 16).map_err(|_| err())?;
415 let f = |x: u32| x as u8;
416 return Ok(Self::Rgb(f(v >> 16), f((v >> 8) & 0xFF), f(v & 0xFF)));
417 }
418 _ => return Err(err()),
419 }))
420 }
421}
422
423pub async fn set_color(ctx: &mut ExecContext<'_>, args: SetColorOpts) -> ExecResult {
424 let mut s = owo_colors::Style::new();
425 match args.background {
426 Some(SetColor::Normal) => s = s.on_default_color(),
427 Some(SetColor::Ansi(c)) => s = s.on_color(c),
428 Some(SetColor::Rgb(r, g, b)) => s = s.on_truecolor(r, g, b),
429 None => {}
430 }
431 if args.bold {
432 s = s.bold();
433 }
434 if args.dim {
435 s = s.dimmed();
436 }
437 if args.italics {
438 s = s.italic();
439 }
440 if args.reverse {
441 s = s.reversed();
442 }
443 if args.underline {
444 s = s.underline();
445 }
446
447 match args.color {
448 Some(SetColor::Normal) => s = s.default_color(),
449 Some(SetColor::Ansi(c)) => s = s.color(c),
450 Some(SetColor::Rgb(r, g, b)) => s = s.truecolor(r, g, b),
451 None => {}
452 }
453
454 let reset = if args.color == Some(SetColor::Normal) || args.background == Some(SetColor::Normal)
455 {
456 "\x1B[m"
457 } else {
458 ""
459 };
460
461 let s = format!("{reset}{}", s.prefix_formatter());
462 if !s.is_empty() {
463 let _: ExecResult<_> = ctx.io().stdout.write_all(s).await;
464 Ok(Status::SUCCESS)
465 } else {
466 Ok(Status::FAILURE)
467 }
468}
469
470pub async fn echo(ctx: &mut ExecContext<'_>, args: &[String]) -> ExecResult {
471 let mut newline = true;
472 let mut unescape = false;
473 let mut space = true;
474
475 let mut iter = args[1..].iter();
476 let mut buf = String::new();
477 let mut first = true;
478 for el in iter.by_ref() {
479 match &**el {
480 "-n" => newline = false,
481 "-s" => space = false,
482 "-E" => unescape = false,
483 "-e" => unescape = true,
484 "--" => break,
485 _ => {
486 buf.push_str(el);
487 first = false;
488 break;
489 }
490 }
491 }
492
493 for el in iter {
494 if first {
495 first = false;
496 } else if space {
497 buf.push(' ');
498 }
499 buf.push_str(el);
500 }
501 if newline {
502 buf.push('\n');
503 }
504
505 ensure!(!unescape, "TODO");
506
507 ctx.io().stdout.write_all(buf).await
508}
509
510#[derive(Debug, Parser)]
511pub struct TypeArgs {
512 pub names: Vec<String>,
513}
514
515pub async fn type_(ctx: &mut ExecContext<'_>, args: TypeArgs) -> ExecResult {
516 let mut buf = String::new();
517 let mut failed = 0usize;
518 for name in &args.names {
519 if ctx.get_global_func(name).is_some() {
520 writeln!(buf, "{name} is a function").unwrap();
522 } else if ctx.get_builtin(name).is_some() {
523 writeln!(buf, "{name} is a builtin").unwrap();
524 } else {
525 match ctx.locate_external_command(name) {
526 LocateExternalCommand::ExecFile(path) => {
527 writeln!(buf, "{name} is {}", path.display()).unwrap();
528 }
529 LocateExternalCommand::NotExecFile(_)
530 | LocateExternalCommand::Dir(_)
531 | LocateExternalCommand::NotFound => {
532 writeln!(buf, "type: cannot find {name:?}").unwrap();
533 failed += 1;
534 }
535 }
536 }
537 }
538
539 let _: ExecResult<_> = ctx.io().stdout.write_all(buf).await;
540 Ok(failed.into())
541}