1 use crate::common::*; 2 3 #[derive(Debug)] 4 pub(crate) enum Error<'src> { 5 ArgumentCountMismatch { 6 recipe: &'src str, 7 parameters: Vec<Parameter<'src>>, 8 found: usize, 9 min: usize, 10 max: usize, 11 }, 12 Backtick { 13 token: Token<'src>, 14 output_error: OutputError, 15 }, 16 ChooserInvoke { 17 shell_binary: String, 18 shell_arguments: String, 19 chooser: OsString, 20 io_error: io::Error, 21 }, 22 ChooserRead { 23 chooser: OsString, 24 io_error: io::Error, 25 }, 26 ChooserStatus { 27 chooser: OsString, 28 status: ExitStatus, 29 }, 30 ChooserWrite { 31 chooser: OsString, 32 io_error: io::Error, 33 }, 34 Code { 35 recipe: &'src str, 36 line_number: Option<usize>, 37 code: i32, 38 }, 39 CommandInvoke { 40 binary: OsString, 41 arguments: Vec<OsString>, 42 io_error: io::Error, 43 }, 44 CommandStatus { 45 binary: OsString, 46 arguments: Vec<OsString>, 47 status: ExitStatus, 48 }, 49 Compile { 50 compile_error: CompileError<'src>, 51 }, 52 Config { 53 config_error: ConfigError, 54 }, 55 Cygpath { 56 recipe: &'src str, 57 output_error: OutputError, 58 }, 59 DefaultRecipeRequiresArguments { 60 recipe: &'src str, 61 min_arguments: usize, 62 }, 63 Dotenv { 64 dotenv_error: dotenv::Error, 65 }, 66 DumpJson { 67 serde_json_error: serde_json::Error, 68 }, 69 EditorInvoke { 70 editor: OsString, 71 io_error: io::Error, 72 }, 73 EditorStatus { 74 editor: OsString, 75 status: ExitStatus, 76 }, 77 EvalUnknownVariable { 78 variable: String, 79 suggestion: Option<Suggestion<'src>>, 80 }, 81 FormatCheckFoundDiff, 82 FunctionCall { 83 function: Name<'src>, 84 message: String, 85 }, 86 InitExists { 87 justfile: PathBuf, 88 }, 89 Internal { 90 message: String, 91 }, 92 Io { 93 recipe: &'src str, 94 io_error: io::Error, 95 }, 96 Load { 97 path: PathBuf, 98 io_error: io::Error, 99 }, 100 NoChoosableRecipes, 101 NoRecipes, 102 RegexCompile { 103 source: regex::Error, 104 }, 105 Search { 106 search_error: SearchError, 107 }, 108 Shebang { 109 recipe: &'src str, 110 command: String, 111 argument: Option<String>, 112 io_error: io::Error, 113 }, 114 Signal { 115 recipe: &'src str, 116 line_number: Option<usize>, 117 signal: i32, 118 }, 119 TmpdirIo { 120 recipe: &'src str, 121 io_error: io::Error, 122 }, 123 Unknown { 124 recipe: &'src str, 125 line_number: Option<usize>, 126 }, 127 UnknownOverrides { 128 overrides: Vec<String>, 129 }, 130 UnknownRecipes { 131 recipes: Vec<String>, 132 suggestion: Option<Suggestion<'src>>, 133 }, 134 Unstable { 135 message: String, 136 }, 137 WriteJustfile { 138 justfile: PathBuf, 139 io_error: io::Error, 140 }, 141 } 142 143 impl<'src> Error<'src> { code(&self) -> Option<i32>144 pub(crate) fn code(&self) -> Option<i32> { 145 match self { 146 Self::Code { code, .. } 147 | Self::Backtick { 148 output_error: OutputError::Code(code), 149 .. 150 } => Some(*code), 151 Self::ChooserStatus { status, .. } | Self::EditorStatus { status, .. } => status.code(), 152 _ => None, 153 } 154 } 155 context(&self) -> Option<Token<'src>>156 fn context(&self) -> Option<Token<'src>> { 157 match self { 158 Self::Backtick { token, .. } => Some(*token), 159 Self::Compile { compile_error } => Some(compile_error.context()), 160 Self::FunctionCall { function, .. } => Some(function.token()), 161 _ => None, 162 } 163 } 164 internal(message: impl Into<String>) -> Self165 pub(crate) fn internal(message: impl Into<String>) -> Self { 166 Self::Internal { 167 message: message.into(), 168 } 169 } 170 } 171 172 impl<'src> From<CompileError<'src>> for Error<'src> { from(compile_error: CompileError<'src>) -> Self173 fn from(compile_error: CompileError<'src>) -> Self { 174 Self::Compile { compile_error } 175 } 176 } 177 178 impl<'src> From<ConfigError> for Error<'src> { from(config_error: ConfigError) -> Self179 fn from(config_error: ConfigError) -> Self { 180 Self::Config { config_error } 181 } 182 } 183 184 impl<'src> From<dotenv::Error> for Error<'src> { from(dotenv_error: dotenv::Error) -> Error<'src>185 fn from(dotenv_error: dotenv::Error) -> Error<'src> { 186 Self::Dotenv { dotenv_error } 187 } 188 } 189 190 impl<'src> From<SearchError> for Error<'src> { from(search_error: SearchError) -> Self191 fn from(search_error: SearchError) -> Self { 192 Self::Search { search_error } 193 } 194 } 195 196 impl<'src> ColorDisplay for Error<'src> { fmt(&self, f: &mut Formatter, color: Color) -> fmt::Result197 fn fmt(&self, f: &mut Formatter, color: Color) -> fmt::Result { 198 use Error::*; 199 200 write!( 201 f, 202 "{}: {}", 203 color.error().paint("error"), 204 color.message().prefix() 205 )?; 206 207 match self { 208 ArgumentCountMismatch { 209 recipe, 210 found, 211 min, 212 max, 213 .. 214 } => { 215 if min == max { 216 let expected = min; 217 write!( 218 f, 219 "Recipe `{}` got {} {} but {}takes {}", 220 recipe, 221 found, 222 Count("argument", *found), 223 if expected < found { "only " } else { "" }, 224 expected 225 )?; 226 } else if found < min { 227 write!( 228 f, 229 "Recipe `{}` got {} {} but takes at least {}", 230 recipe, 231 found, 232 Count("argument", *found), 233 min 234 )?; 235 } else if found > max { 236 write!( 237 f, 238 "Recipe `{}` got {} {} but takes at most {}", 239 recipe, 240 found, 241 Count("argument", *found), 242 max 243 )?; 244 } 245 } 246 Backtick { output_error, .. } => match output_error { 247 OutputError::Code(code) => { 248 write!(f, "Backtick failed with exit code {}", code)?; 249 } 250 OutputError::Signal(signal) => { 251 write!(f, "Backtick was terminated by signal {}", signal)?; 252 } 253 OutputError::Unknown => { 254 write!(f, "Backtick failed for an unknown reason")?; 255 } 256 OutputError::Io(io_error) => { 257 match io_error.kind() { 258 io::ErrorKind::NotFound => write!( 259 f, 260 "Backtick could not be run because just could not find the shell:\n{}", 261 io_error 262 ), 263 io::ErrorKind::PermissionDenied => write!( 264 f, 265 "Backtick could not be run because just could not run the shell:\n{}", 266 io_error 267 ), 268 _ => write!( 269 f, 270 "Backtick could not be run because of an IO error while launching the shell:\n{}", 271 io_error 272 ), 273 }?; 274 } 275 OutputError::Utf8(utf8_error) => { 276 write!( 277 f, 278 "Backtick succeeded but stdout was not utf8: {}", 279 utf8_error 280 )?; 281 } 282 }, 283 ChooserInvoke { 284 shell_binary, 285 shell_arguments, 286 chooser, 287 io_error, 288 } => { 289 write!( 290 f, 291 "Chooser `{} {} {}` invocation failed: {}", 292 shell_binary, 293 shell_arguments, 294 chooser.to_string_lossy(), 295 io_error, 296 )?; 297 } 298 ChooserRead { chooser, io_error } => { 299 write!( 300 f, 301 "Failed to read output from chooser `{}`: {}", 302 chooser.to_string_lossy(), 303 io_error 304 )?; 305 } 306 ChooserStatus { chooser, status } => { 307 write!( 308 f, 309 "Chooser `{}` failed: {}", 310 chooser.to_string_lossy(), 311 status 312 )?; 313 } 314 ChooserWrite { chooser, io_error } => { 315 write!( 316 f, 317 "Failed to write to chooser `{}`: {}", 318 chooser.to_string_lossy(), 319 io_error 320 )?; 321 } 322 Code { 323 recipe, 324 line_number, 325 code, 326 } => { 327 if let Some(n) = line_number { 328 write!( 329 f, 330 "Recipe `{}` failed on line {} with exit code {}", 331 recipe, n, code 332 )?; 333 } else { 334 write!(f, "Recipe `{}` failed with exit code {}", recipe, code)?; 335 } 336 } 337 CommandInvoke { 338 binary, 339 arguments, 340 io_error, 341 } => { 342 write!( 343 f, 344 "Failed to invoke {}: {}", 345 iter::once(binary) 346 .chain(arguments) 347 .map(|value| Enclosure::tick(value.to_string_lossy()).to_string()) 348 .collect::<Vec<String>>() 349 .join(" "), 350 io_error, 351 )?; 352 } 353 CommandStatus { 354 binary, 355 arguments, 356 status, 357 } => { 358 write!( 359 f, 360 "Command {} failed: {}", 361 iter::once(binary) 362 .chain(arguments) 363 .map(|value| Enclosure::tick(value.to_string_lossy()).to_string()) 364 .collect::<Vec<String>>() 365 .join(" "), 366 status, 367 )?; 368 } 369 Compile { compile_error } => Display::fmt(compile_error, f)?, 370 Config { config_error } => Display::fmt(config_error, f)?, 371 Cygpath { 372 recipe, 373 output_error, 374 } => match output_error { 375 OutputError::Code(code) => { 376 write!( 377 f, 378 "Cygpath failed with exit code {} while translating recipe `{}` shebang interpreter \ 379 path", 380 code, recipe 381 )?; 382 } 383 OutputError::Signal(signal) => { 384 write!( 385 f, 386 "Cygpath terminated by signal {} while translating recipe `{}` shebang interpreter \ 387 path", 388 signal, recipe 389 )?; 390 } 391 OutputError::Unknown => { 392 write!( 393 f, 394 "Cygpath experienced an unknown failure while translating recipe `{}` shebang \ 395 interpreter path", 396 recipe 397 )?; 398 } 399 OutputError::Io(io_error) => { 400 match io_error.kind() { 401 io::ErrorKind::NotFound => write!( 402 f, 403 "Could not find `cygpath` executable to translate recipe `{}` shebang interpreter \ 404 path:\n{}", 405 recipe, io_error 406 ), 407 io::ErrorKind::PermissionDenied => write!( 408 f, 409 "Could not run `cygpath` executable to translate recipe `{}` shebang interpreter \ 410 path:\n{}", 411 recipe, io_error 412 ), 413 _ => write!(f, "Could not run `cygpath` executable:\n{}", io_error), 414 }?; 415 } 416 OutputError::Utf8(utf8_error) => { 417 write!( 418 f, 419 "Cygpath successfully translated recipe `{}` shebang interpreter path, but output was \ 420 not utf8: {}", 421 recipe, utf8_error 422 )?; 423 } 424 }, 425 DefaultRecipeRequiresArguments { 426 recipe, 427 min_arguments, 428 } => { 429 write!( 430 f, 431 "Recipe `{}` cannot be used as default recipe since it requires at least {} {}.", 432 recipe, 433 min_arguments, 434 Count("argument", *min_arguments), 435 )?; 436 } 437 Dotenv { dotenv_error } => { 438 write!(f, "Failed to load environment file: {}", dotenv_error)?; 439 } 440 DumpJson { serde_json_error } => { 441 write!(f, "Failed to dump JSON to stdout: {}", serde_json_error)?; 442 } 443 EditorInvoke { editor, io_error } => { 444 write!( 445 f, 446 "Editor `{}` invocation failed: {}", 447 editor.to_string_lossy(), 448 io_error 449 )?; 450 } 451 EditorStatus { editor, status } => { 452 write!( 453 f, 454 "Editor `{}` failed: {}", 455 editor.to_string_lossy(), 456 status 457 )?; 458 } 459 EvalUnknownVariable { 460 variable, 461 suggestion, 462 } => { 463 write!(f, "Justfile does not contain variable `{}`.", variable,)?; 464 if let Some(suggestion) = *suggestion { 465 write!(f, "\n{}", suggestion)?; 466 } 467 } 468 FormatCheckFoundDiff => { 469 write!(f, "Formatted justfile differs from original.")?; 470 } 471 FunctionCall { function, message } => { 472 write!( 473 f, 474 "Call to function `{}` failed: {}", 475 function.lexeme(), 476 message 477 )?; 478 } 479 InitExists { justfile } => { 480 write!(f, "Justfile `{}` already exists", justfile.display())?; 481 } 482 Internal { message } => { 483 write!( 484 f, 485 "Internal runtime error, this may indicate a bug in just: {} \ 486 consider filing an issue: https://github.com/casey/just/issues/new", 487 message 488 )?; 489 } 490 Io { recipe, io_error } => { 491 match io_error.kind() { 492 io::ErrorKind::NotFound => write!( 493 f, 494 "Recipe `{}` could not be run because just could not find the shell: {}", 495 recipe, io_error 496 ), 497 io::ErrorKind::PermissionDenied => write!( 498 f, 499 "Recipe `{}` could not be run because just could not run the shell: {}", 500 recipe, io_error 501 ), 502 _ => write!( 503 f, 504 "Recipe `{}` could not be run because of an IO error while launching the shell: {}", 505 recipe, io_error 506 ), 507 }?; 508 } 509 Load { io_error, path } => { 510 write!( 511 f, 512 "Failed to read justfile at `{}`: {}", 513 path.display(), 514 io_error 515 )?; 516 } 517 NoChoosableRecipes => { 518 write!(f, "Justfile contains no choosable recipes.")?; 519 } 520 NoRecipes => { 521 write!(f, "Justfile contains no recipes.")?; 522 } 523 RegexCompile { source } => { 524 write!(f, "{}", source)?; 525 } 526 Search { search_error } => Display::fmt(search_error, f)?, 527 Shebang { 528 recipe, 529 command, 530 argument, 531 io_error, 532 } => { 533 if let Some(argument) = argument { 534 write!( 535 f, 536 "Recipe `{}` with shebang `#!{} {}` execution error: {}", 537 recipe, command, argument, io_error 538 )?; 539 } else { 540 write!( 541 f, 542 "Recipe `{}` with shebang `#!{}` execution error: {}", 543 recipe, command, io_error 544 )?; 545 } 546 } 547 Signal { 548 recipe, 549 line_number, 550 signal, 551 } => { 552 if let Some(n) = line_number { 553 write!( 554 f, 555 "Recipe `{}` was terminated on line {} by signal {}", 556 recipe, n, signal 557 )?; 558 } else { 559 write!(f, "Recipe `{}` was terminated by signal {}", recipe, signal)?; 560 } 561 } 562 TmpdirIo { recipe, io_error } => write!( 563 f, 564 "Recipe `{}` could not be run because of an IO error while trying to create a temporary \ 565 directory or write a file to that directory`:{}", 566 recipe, io_error 567 )?, 568 Unknown { 569 recipe, 570 line_number, 571 } => { 572 if let Some(n) = line_number { 573 write!( 574 f, 575 "Recipe `{}` failed on line {} for an unknown reason", 576 recipe, n 577 )?; 578 } else { 579 write!(f, "Recipe `{}` failed for an unknown reason", recipe)?; 580 } 581 } 582 UnknownOverrides { overrides } => { 583 write!( 584 f, 585 "{} {} overridden on the command line but not present in justfile", 586 Count("Variable", overrides.len()), 587 List::and_ticked(overrides), 588 )?; 589 } 590 UnknownRecipes { 591 recipes, 592 suggestion, 593 } => { 594 write!( 595 f, 596 "Justfile does not contain {} {}.", 597 Count("recipe", recipes.len()), 598 List::or_ticked(recipes), 599 )?; 600 if let Some(suggestion) = *suggestion { 601 write!(f, "\n{}", suggestion)?; 602 } 603 } 604 Unstable { message } => { 605 write!( 606 f, 607 "{} Invoke `just` with the `--unstable` flag to enable unstable features.", 608 message 609 )?; 610 } 611 WriteJustfile { justfile, io_error } => { 612 write!( 613 f, 614 "Failed to write justfile to `{}`: {}", 615 justfile.display(), 616 io_error 617 )?; 618 } 619 } 620 621 write!(f, "{}", color.message().suffix())?; 622 623 if let ArgumentCountMismatch { 624 recipe, parameters, .. 625 } = self 626 { 627 writeln!(f)?; 628 write!( 629 f, 630 "{}:\n just {}", 631 color.message().paint("usage"), 632 recipe 633 )?; 634 for param in parameters { 635 write!(f, " {}", param.color_display(color))?; 636 } 637 } 638 639 if let Some(token) = self.context() { 640 writeln!(f)?; 641 write!(f, "{}", token.color_display(color.error()))?; 642 } 643 644 Ok(()) 645 } 646 } 647