2021-09-20 21:37:26 +00:00
|
|
|
|
use miette::Diagnostic;
|
2021-10-01 05:11:49 +00:00
|
|
|
|
use serde::{Deserialize, Serialize};
|
2021-09-20 21:37:26 +00:00
|
|
|
|
use thiserror::Error;
|
|
|
|
|
|
2021-09-05 23:16:27 +00:00
|
|
|
|
use crate::{ast::Operator, Span, Type};
|
2021-09-02 01:29:43 +00:00
|
|
|
|
|
2021-11-03 00:26:09 +00:00
|
|
|
|
/// The fundamental error type for the evaluation engine. These cases represent different kinds of errors
|
|
|
|
|
/// the evaluator might face, along with helpful spans to label. An error renderer will take this error value
|
|
|
|
|
/// and pass it into an error viewer to display to the user.
|
2021-10-01 05:11:49 +00:00
|
|
|
|
#[derive(Debug, Clone, Error, Diagnostic, Serialize, Deserialize)]
|
2021-09-02 01:29:43 +00:00
|
|
|
|
pub enum ShellError {
|
2021-09-20 21:37:26 +00:00
|
|
|
|
#[error("Type mismatch during operation.")]
|
|
|
|
|
#[diagnostic(code(nu::shell::type_mismatch), url(docsrs))]
|
2021-09-02 01:29:43 +00:00
|
|
|
|
OperatorMismatch {
|
2021-09-20 21:37:26 +00:00
|
|
|
|
#[label = "type mismatch for operator"]
|
2021-09-02 01:29:43 +00:00
|
|
|
|
op_span: Span,
|
|
|
|
|
lhs_ty: Type,
|
2021-09-20 21:37:26 +00:00
|
|
|
|
#[label("{lhs_ty}")]
|
2021-09-02 01:29:43 +00:00
|
|
|
|
lhs_span: Span,
|
|
|
|
|
rhs_ty: Type,
|
2021-09-20 21:37:26 +00:00
|
|
|
|
#[label("{rhs_ty}")]
|
2021-09-02 01:29:43 +00:00
|
|
|
|
rhs_span: Span,
|
|
|
|
|
},
|
2021-09-20 21:37:26 +00:00
|
|
|
|
|
2021-10-20 05:58:25 +00:00
|
|
|
|
#[error("Operator overflow.")]
|
|
|
|
|
#[diagnostic(code(nu::shell::operator_overflow), url(docsrs))]
|
|
|
|
|
OperatorOverflow(String, #[label = "{0}"] Span),
|
|
|
|
|
|
2021-10-09 01:02:01 +00:00
|
|
|
|
#[error("Pipeline mismatch.")]
|
|
|
|
|
#[diagnostic(code(nu::shell::pipeline_mismatch), url(docsrs))]
|
2021-12-02 23:11:25 +00:00
|
|
|
|
PipelineMismatch(
|
|
|
|
|
String,
|
|
|
|
|
#[label("expected: {0}")] Span,
|
|
|
|
|
#[label("value originates from here")] Span,
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
#[error("Type mismatch")]
|
|
|
|
|
#[diagnostic(code(nu::shell::type_mismatch), url(docsrs))]
|
2022-02-07 19:54:06 +00:00
|
|
|
|
TypeMismatch(String, #[label = "needs {0}"] Span),
|
2021-10-09 01:02:01 +00:00
|
|
|
|
|
2021-09-20 21:37:26 +00:00
|
|
|
|
#[error("Unsupported operator: {0}.")]
|
|
|
|
|
#[diagnostic(code(nu::shell::unsupported_operator), url(docsrs))]
|
|
|
|
|
UnsupportedOperator(Operator, #[label = "unsupported operator"] Span),
|
|
|
|
|
|
|
|
|
|
#[error("Unsupported operator: {0}.")]
|
|
|
|
|
#[diagnostic(code(nu::shell::unknown_operator), url(docsrs))]
|
|
|
|
|
UnknownOperator(String, #[label = "unsupported operator"] Span),
|
|
|
|
|
|
2021-10-07 22:20:23 +00:00
|
|
|
|
#[error("Missing parameter: {0}.")]
|
|
|
|
|
#[diagnostic(code(nu::shell::missing_parameter), url(docsrs))]
|
|
|
|
|
MissingParameter(String, #[label = "missing parameter: {0}"] Span),
|
|
|
|
|
|
2021-10-10 04:13:15 +00:00
|
|
|
|
// Be cautious, as flags can share the same span, resulting in a panic (ex: `rm -pt`)
|
|
|
|
|
#[error("Incompatible parameters.")]
|
|
|
|
|
#[diagnostic(code(nu::shell::incompatible_parameters), url(docsrs))]
|
|
|
|
|
IncompatibleParameters {
|
|
|
|
|
left_message: String,
|
|
|
|
|
#[label("{left_message}")]
|
|
|
|
|
left_span: Span,
|
|
|
|
|
right_message: String,
|
|
|
|
|
#[label("{right_message}")]
|
|
|
|
|
right_span: Span,
|
|
|
|
|
},
|
|
|
|
|
|
2021-11-09 20:17:37 +00:00
|
|
|
|
#[error("Delimiter error")]
|
|
|
|
|
#[diagnostic(code(nu::shell::delimiter_error), url(docsrs))]
|
|
|
|
|
DelimiterError(String, #[label("{0}")] Span),
|
|
|
|
|
|
2021-10-10 04:13:15 +00:00
|
|
|
|
#[error("Incompatible parameters.")]
|
|
|
|
|
#[diagnostic(code(nu::shell::incompatible_parameters), url(docsrs))]
|
|
|
|
|
IncompatibleParametersSingle(String, #[label = "{0}"] Span),
|
|
|
|
|
|
|
|
|
|
#[error("Feature not enabled.")]
|
|
|
|
|
#[diagnostic(code(nu::shell::feature_not_enabled), url(docsrs))]
|
|
|
|
|
FeatureNotEnabled(#[label = "feature not enabled"] Span),
|
|
|
|
|
|
2022-02-20 21:26:41 +00:00
|
|
|
|
#[error("Running external commands not supported")]
|
2021-09-20 21:37:26 +00:00
|
|
|
|
#[diagnostic(code(nu::shell::external_commands), url(docsrs))]
|
|
|
|
|
ExternalNotSupported(#[label = "external not supported"] Span),
|
|
|
|
|
|
2021-11-30 06:12:19 +00:00
|
|
|
|
#[error("Invalid Probability.")]
|
|
|
|
|
#[diagnostic(code(nu::shell::invalid_probability), url(docsrs))]
|
|
|
|
|
InvalidProbability(#[label = "invalid probability"] Span),
|
|
|
|
|
|
2021-12-02 17:26:12 +00:00
|
|
|
|
#[error("Invalid range {0}..{1}")]
|
|
|
|
|
#[diagnostic(code(nu::shell::invalid_range), url(docsrs))]
|
|
|
|
|
InvalidRange(String, String, #[label = "expected a valid range"] Span),
|
|
|
|
|
|
2021-12-02 23:11:25 +00:00
|
|
|
|
// Only use this one if we Nushell completely falls over and hits a state that isn't possible or isn't recoverable
|
|
|
|
|
#[error("Nushell failed: {0}.")]
|
|
|
|
|
#[diagnostic(code(nu::shell::nushell_failed), url(docsrs))]
|
|
|
|
|
NushellFailed(String),
|
2021-09-20 21:37:26 +00:00
|
|
|
|
|
2022-01-24 19:43:38 +00:00
|
|
|
|
// Only use this one if we Nushell completely falls over and hits a state that isn't possible or isn't recoverable
|
|
|
|
|
#[error("Nushell failed: {0}.")]
|
|
|
|
|
#[diagnostic(code(nu::shell::nushell_failed), url(docsrs))]
|
|
|
|
|
NushellFailedSpanned(String, String, #[label = "{1}"] Span),
|
|
|
|
|
|
2021-12-15 22:56:12 +00:00
|
|
|
|
#[error("Variable not found")]
|
2021-09-20 21:37:26 +00:00
|
|
|
|
#[diagnostic(code(nu::shell::variable_not_found), url(docsrs))]
|
|
|
|
|
VariableNotFoundAtRuntime(#[label = "variable not found"] Span),
|
|
|
|
|
|
2022-01-10 01:39:25 +00:00
|
|
|
|
#[error("Environment variable '{0}' not found")]
|
2022-01-04 22:30:34 +00:00
|
|
|
|
#[diagnostic(code(nu::shell::env_variable_not_found), url(docsrs))]
|
2022-01-10 01:39:25 +00:00
|
|
|
|
EnvVarNotFoundAtRuntime(String, #[label = "environment variable not found"] Span),
|
2021-11-15 23:16:06 +00:00
|
|
|
|
|
|
|
|
|
#[error("Not found.")]
|
|
|
|
|
#[diagnostic(code(nu::parser::not_found), url(docsrs))]
|
|
|
|
|
NotFound(#[label = "did not find anything under this name"] Span),
|
|
|
|
|
|
2021-09-20 21:37:26 +00:00
|
|
|
|
#[error("Can't convert to {0}.")]
|
|
|
|
|
#[diagnostic(code(nu::shell::cant_convert), url(docsrs))]
|
2021-11-06 05:50:33 +00:00
|
|
|
|
CantConvert(String, String, #[label("can't convert {1} to {0}")] Span),
|
2021-09-20 21:37:26 +00:00
|
|
|
|
|
|
|
|
|
#[error("Division by zero.")]
|
|
|
|
|
#[diagnostic(code(nu::shell::division_by_zero), url(docsrs))]
|
|
|
|
|
DivisionByZero(#[label("division by zero")] Span),
|
|
|
|
|
|
|
|
|
|
#[error("Can't convert range to countable values")]
|
|
|
|
|
#[diagnostic(code(nu::shell::range_to_countable), url(docsrs))]
|
|
|
|
|
CannotCreateRange(#[label = "can't convert to countable values"] Span),
|
|
|
|
|
|
|
|
|
|
#[error("Row number too large (max: {0}).")]
|
|
|
|
|
#[diagnostic(code(nu::shell::access_beyond_end), url(docsrs))]
|
|
|
|
|
AccessBeyondEnd(usize, #[label = "too large"] Span),
|
|
|
|
|
|
|
|
|
|
#[error("Row number too large.")]
|
|
|
|
|
#[diagnostic(code(nu::shell::access_beyond_end_of_stream), url(docsrs))]
|
|
|
|
|
AccessBeyondEndOfStream(#[label = "too large"] Span),
|
|
|
|
|
|
|
|
|
|
#[error("Data cannot be accessed with a cell path")]
|
|
|
|
|
#[diagnostic(code(nu::shell::incompatible_path_access), url(docsrs))]
|
|
|
|
|
IncompatiblePathAccess(String, #[label("{0} doesn't support cell paths")] Span),
|
|
|
|
|
|
|
|
|
|
#[error("Cannot find column")]
|
|
|
|
|
#[diagnostic(code(nu::shell::column_not_found), url(docsrs))]
|
2021-10-11 19:51:54 +00:00
|
|
|
|
CantFindColumn(
|
|
|
|
|
#[label = "cannot find column"] Span,
|
|
|
|
|
#[label = "value originates here"] Span,
|
|
|
|
|
),
|
2021-09-20 21:37:26 +00:00
|
|
|
|
|
2021-11-05 03:59:12 +00:00
|
|
|
|
#[error("Not a list value")]
|
|
|
|
|
#[diagnostic(code(nu::shell::not_a_list), url(docsrs))]
|
|
|
|
|
NotAList(
|
|
|
|
|
#[label = "value not a list"] Span,
|
|
|
|
|
#[label = "value originates here"] Span,
|
|
|
|
|
),
|
|
|
|
|
|
2021-09-20 21:37:26 +00:00
|
|
|
|
#[error("External command")]
|
2022-01-22 14:12:34 +00:00
|
|
|
|
#[diagnostic(code(nu::shell::external_command), url(docsrs), help("{1}"))]
|
|
|
|
|
ExternalCommand(String, String, #[label("{0}")] Span),
|
2021-09-24 12:03:39 +00:00
|
|
|
|
|
|
|
|
|
#[error("Unsupported input")]
|
|
|
|
|
#[diagnostic(code(nu::shell::unsupported_input), url(docsrs))]
|
2022-02-20 21:26:41 +00:00
|
|
|
|
UnsupportedInput(String, #[label("{0} not supported")] Span),
|
2021-10-01 21:53:13 +00:00
|
|
|
|
|
2022-01-04 02:01:18 +00:00
|
|
|
|
#[error("Network failure")]
|
|
|
|
|
#[diagnostic(code(nu::shell::network_failure), url(docsrs))]
|
|
|
|
|
NetworkFailure(String, #[label("{0}")] Span),
|
|
|
|
|
|
2021-10-09 01:02:01 +00:00
|
|
|
|
#[error("Command not found")]
|
|
|
|
|
#[diagnostic(code(nu::shell::command_not_found), url(docsrs))]
|
|
|
|
|
CommandNotFound(#[label("command not found")] Span),
|
|
|
|
|
|
2021-10-01 21:53:13 +00:00
|
|
|
|
#[error("Flag not found")]
|
|
|
|
|
#[diagnostic(code(nu::shell::flag_not_found), url(docsrs))]
|
|
|
|
|
FlagNotFound(String, #[label("{0} not found")] Span),
|
2021-10-05 03:43:07 +00:00
|
|
|
|
|
|
|
|
|
#[error("File not found")]
|
|
|
|
|
#[diagnostic(code(nu::shell::file_not_found), url(docsrs))]
|
|
|
|
|
FileNotFound(#[label("file not found")] Span),
|
|
|
|
|
|
2021-10-05 21:08:39 +00:00
|
|
|
|
#[error("File not found")]
|
|
|
|
|
#[diagnostic(code(nu::shell::file_not_found), url(docsrs))]
|
|
|
|
|
FileNotFoundCustom(String, #[label("{0}")] Span),
|
|
|
|
|
|
2021-12-18 15:52:27 +00:00
|
|
|
|
#[error("Plugin failed to load: {0}")]
|
2021-12-05 03:11:19 +00:00
|
|
|
|
#[diagnostic(code(nu::shell::plugin_failed_to_load), url(docsrs))]
|
2021-12-02 23:11:25 +00:00
|
|
|
|
PluginFailedToLoad(String),
|
|
|
|
|
|
2021-12-18 15:52:27 +00:00
|
|
|
|
#[error("Plugin failed to encode: {0}")]
|
2021-12-05 03:11:19 +00:00
|
|
|
|
#[diagnostic(code(nu::shell::plugin_failed_to_encode), url(docsrs))]
|
|
|
|
|
PluginFailedToEncode(String),
|
|
|
|
|
|
2021-12-18 15:52:27 +00:00
|
|
|
|
#[error("Plugin failed to decode: {0}")]
|
2021-12-05 03:11:19 +00:00
|
|
|
|
#[diagnostic(code(nu::shell::plugin_failed_to_decode), url(docsrs))]
|
|
|
|
|
PluginFailedToDecode(String),
|
|
|
|
|
|
2021-12-02 23:11:25 +00:00
|
|
|
|
#[error("I/O error")]
|
2022-01-04 22:30:34 +00:00
|
|
|
|
#[diagnostic(code(nu::shell::io_error), url(docsrs), help("{0}"))]
|
2021-12-02 23:11:25 +00:00
|
|
|
|
IOError(String),
|
|
|
|
|
|
2022-01-23 13:02:12 +00:00
|
|
|
|
#[error("Cannot change to directory")]
|
|
|
|
|
#[diagnostic(code(nu::shell::cannot_cd_to_directory), url(docsrs))]
|
|
|
|
|
NotADirectory(#[label("is not a directory")] Span),
|
|
|
|
|
|
2021-10-05 03:43:07 +00:00
|
|
|
|
#[error("Directory not found")]
|
|
|
|
|
#[diagnostic(code(nu::shell::directory_not_found), url(docsrs))]
|
|
|
|
|
DirectoryNotFound(#[label("directory not found")] Span),
|
|
|
|
|
|
2022-01-05 22:21:26 +00:00
|
|
|
|
#[error("Directory not found")]
|
|
|
|
|
#[diagnostic(code(nu::shell::directory_not_found_custom), url(docsrs))]
|
2021-10-05 21:08:39 +00:00
|
|
|
|
DirectoryNotFoundCustom(String, #[label("{0}")] Span),
|
|
|
|
|
|
2022-01-05 22:21:26 +00:00
|
|
|
|
#[error("Directory not found")]
|
|
|
|
|
#[diagnostic(code(nu::shell::directory_not_found_help), url(docsrs), help("{1}"))]
|
|
|
|
|
DirectoryNotFoundHelp(#[label("directory not found")] Span, String),
|
|
|
|
|
|
2021-10-05 03:43:07 +00:00
|
|
|
|
#[error("Move not possible")]
|
|
|
|
|
#[diagnostic(code(nu::shell::move_not_possible), url(docsrs))]
|
|
|
|
|
MoveNotPossible {
|
|
|
|
|
source_message: String,
|
|
|
|
|
#[label("{source_message}")]
|
|
|
|
|
source_span: Span,
|
|
|
|
|
destination_message: String,
|
|
|
|
|
#[label("{destination_message}")]
|
|
|
|
|
destination_span: Span,
|
|
|
|
|
},
|
2021-10-05 19:54:30 +00:00
|
|
|
|
|
|
|
|
|
#[error("Move not possible")]
|
|
|
|
|
#[diagnostic(code(nu::shell::move_not_possible_single), url(docsrs))]
|
|
|
|
|
MoveNotPossibleSingle(String, #[label("{0}")] Span),
|
2021-10-07 21:18:03 +00:00
|
|
|
|
|
|
|
|
|
#[error("Create not possible")]
|
2021-10-07 21:20:03 +00:00
|
|
|
|
#[diagnostic(code(nu::shell::create_not_possible), url(docsrs))]
|
2021-10-07 21:18:03 +00:00
|
|
|
|
CreateNotPossible(String, #[label("{0}")] Span),
|
2021-10-10 04:13:15 +00:00
|
|
|
|
|
|
|
|
|
#[error("Remove not possible")]
|
|
|
|
|
#[diagnostic(code(nu::shell::remove_not_possible), url(docsrs))]
|
|
|
|
|
RemoveNotPossible(String, #[label("{0}")] Span),
|
2021-10-14 17:54:51 +00:00
|
|
|
|
|
|
|
|
|
#[error("No file to be removed")]
|
|
|
|
|
NoFileToBeRemoved(),
|
|
|
|
|
#[error("No file to be moved")]
|
|
|
|
|
NoFileToBeMoved(),
|
|
|
|
|
#[error("No file to be copied")]
|
|
|
|
|
NoFileToBeCopied(),
|
2021-10-26 19:50:39 +00:00
|
|
|
|
|
2021-11-07 21:48:50 +00:00
|
|
|
|
#[error("Name not found")]
|
|
|
|
|
#[diagnostic(code(nu::shell::name_not_found), url(docsrs))]
|
|
|
|
|
DidYouMean(String, #[label("did you mean '{0}'?")] Span),
|
2021-11-15 23:16:06 +00:00
|
|
|
|
|
2021-11-23 08:14:40 +00:00
|
|
|
|
#[error("Non-UTF8 string")]
|
2021-11-15 23:16:06 +00:00
|
|
|
|
#[diagnostic(code(nu::parser::non_utf8), url(docsrs))]
|
|
|
|
|
NonUtf8(#[label = "non-UTF8 string"] Span),
|
2021-11-23 08:14:40 +00:00
|
|
|
|
|
|
|
|
|
#[error("Casting error")]
|
2021-12-05 03:11:19 +00:00
|
|
|
|
#[diagnostic(code(nu::shell::downcast_not_possible), url(docsrs))]
|
2021-11-23 08:14:40 +00:00
|
|
|
|
DowncastNotPossible(String, #[label("{0}")] Span),
|
2021-11-28 19:35:02 +00:00
|
|
|
|
|
2021-12-17 01:04:54 +00:00
|
|
|
|
#[error("Unsupported config value")]
|
|
|
|
|
#[diagnostic(code(nu::shell::unsupported_config_value), url(docsrs))]
|
|
|
|
|
UnsupportedConfigValue(String, String, #[label = "expected {0}, got {1}"] Span),
|
|
|
|
|
|
|
|
|
|
#[error("Missing config value")]
|
|
|
|
|
#[diagnostic(code(nu::shell::missing_config_value), url(docsrs))]
|
|
|
|
|
MissingConfigValue(String, #[label = "missing {0}"] Span),
|
|
|
|
|
|
2022-02-20 20:20:41 +00:00
|
|
|
|
#[error("Negative value passed when positive one is required")]
|
|
|
|
|
#[diagnostic(code(nu::shell::needs_positive_value), url(docsrs))]
|
|
|
|
|
NeedsPositiveValue(#[label = "use a positive value"] Span),
|
|
|
|
|
|
2021-11-28 19:35:02 +00:00
|
|
|
|
#[error("{0}")]
|
|
|
|
|
#[diagnostic()]
|
2021-12-05 03:11:19 +00:00
|
|
|
|
SpannedLabeledError(String, String, #[label("{1}")] Span),
|
|
|
|
|
|
2021-12-18 17:45:09 +00:00
|
|
|
|
#[error("{0}")]
|
|
|
|
|
#[diagnostic(help("{3}"))]
|
|
|
|
|
SpannedLabeledErrorHelp(String, String, #[label("{1}")] Span, String),
|
|
|
|
|
|
2022-02-21 03:31:50 +00:00
|
|
|
|
#[error("{0}")]
|
|
|
|
|
#[diagnostic()]
|
|
|
|
|
SpannedLabeledErrorRelated(
|
|
|
|
|
String,
|
|
|
|
|
String,
|
|
|
|
|
#[label("{1}")] Span,
|
|
|
|
|
#[related] Vec<ShellError>,
|
|
|
|
|
),
|
|
|
|
|
|
2021-12-05 03:11:19 +00:00
|
|
|
|
#[error("{0}")]
|
2021-12-20 21:19:43 +00:00
|
|
|
|
#[diagnostic(help("{1}"))]
|
2021-12-05 03:11:19 +00:00
|
|
|
|
LabeledError(String, String),
|
2022-02-10 12:55:19 +00:00
|
|
|
|
|
2022-02-21 03:31:50 +00:00
|
|
|
|
#[error("{1}")]
|
|
|
|
|
#[diagnostic()]
|
|
|
|
|
OutsideSpannedLabeledError(#[source_code] String, String, String, #[label("{2}")] Span),
|
|
|
|
|
|
2022-02-10 12:55:19 +00:00
|
|
|
|
#[error("Deprecated command {0}")]
|
|
|
|
|
#[diagnostic(code(nu::shell::deprecated_command), url(docsrs))]
|
|
|
|
|
DeprecatedCommand(
|
|
|
|
|
String,
|
|
|
|
|
String,
|
2022-02-10 23:27:51 +00:00
|
|
|
|
#[label = "'{0}' is deprecated. Please use '{1}' instead."] Span,
|
2022-02-10 12:55:19 +00:00
|
|
|
|
),
|
2021-10-05 19:54:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<std::io::Error> for ShellError {
|
|
|
|
|
fn from(input: std::io::Error) -> ShellError {
|
2021-12-02 23:11:25 +00:00
|
|
|
|
ShellError::IOError(format!("{:?}", input))
|
2021-10-05 19:54:30 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-05 21:08:39 +00:00
|
|
|
|
impl std::convert::From<Box<dyn std::error::Error>> for ShellError {
|
|
|
|
|
fn from(input: Box<dyn std::error::Error>) -> ShellError {
|
2021-12-02 23:11:25 +00:00
|
|
|
|
ShellError::IOError(input.to_string())
|
2021-10-05 21:08:39 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-05 19:54:30 +00:00
|
|
|
|
impl From<Box<dyn std::error::Error + Send + Sync>> for ShellError {
|
|
|
|
|
fn from(input: Box<dyn std::error::Error + Send + Sync>) -> ShellError {
|
2021-12-02 23:11:25 +00:00
|
|
|
|
ShellError::IOError(format!("{:?}", input))
|
2021-10-05 19:54:30 +00:00
|
|
|
|
}
|
2021-09-02 01:29:43 +00:00
|
|
|
|
}
|
2021-11-07 21:48:50 +00:00
|
|
|
|
|
|
|
|
|
pub fn did_you_mean(possibilities: &[String], tried: &str) -> Option<String> {
|
|
|
|
|
let mut possible_matches: Vec<_> = possibilities
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|word| {
|
|
|
|
|
let edit_distance = levenshtein_distance(word, tried);
|
|
|
|
|
(edit_distance, word.to_owned())
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
possible_matches.sort();
|
|
|
|
|
|
|
|
|
|
if let Some((_, first)) = possible_matches.into_iter().next() {
|
|
|
|
|
Some(first)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Borrowed from here https://github.com/wooorm/levenshtein-rs
|
|
|
|
|
pub fn levenshtein_distance(a: &str, b: &str) -> usize {
|
|
|
|
|
let mut result = 0;
|
|
|
|
|
|
|
|
|
|
/* Shortcut optimizations / degenerate cases. */
|
|
|
|
|
if a == b {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let length_a = a.chars().count();
|
|
|
|
|
let length_b = b.chars().count();
|
|
|
|
|
|
|
|
|
|
if length_a == 0 {
|
|
|
|
|
return length_b;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if length_b == 0 {
|
|
|
|
|
return length_a;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Initialize the vector.
|
|
|
|
|
*
|
|
|
|
|
* This is why it’s fast, normally a matrix is used,
|
|
|
|
|
* here we use a single vector. */
|
|
|
|
|
let mut cache: Vec<usize> = (1..).take(length_a).collect();
|
|
|
|
|
let mut distance_a;
|
|
|
|
|
let mut distance_b;
|
|
|
|
|
|
|
|
|
|
/* Loop. */
|
|
|
|
|
for (index_b, code_b) in b.chars().enumerate() {
|
|
|
|
|
result = index_b;
|
|
|
|
|
distance_a = index_b;
|
|
|
|
|
|
|
|
|
|
for (index_a, code_a) in a.chars().enumerate() {
|
|
|
|
|
distance_b = if code_a == code_b {
|
|
|
|
|
distance_a
|
|
|
|
|
} else {
|
|
|
|
|
distance_a + 1
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
distance_a = cache[index_a];
|
|
|
|
|
|
|
|
|
|
result = if distance_a > result {
|
|
|
|
|
if distance_b > result {
|
|
|
|
|
result + 1
|
|
|
|
|
} else {
|
|
|
|
|
distance_b
|
|
|
|
|
}
|
|
|
|
|
} else if distance_b > distance_a {
|
|
|
|
|
distance_a + 1
|
|
|
|
|
} else {
|
|
|
|
|
distance_b
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
cache[index_a] = result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result
|
|
|
|
|
}
|