Improve repl autocorrect and error handling
* Combine comparison algorithims for autocorrect
* clear zephyr functions
* remove redundant comments because co-pilot is stupid and i probably will never try to use it again
* implement basic tab completion
* fix unused items
* Make workflow check code quality
* split code quality into its own file
* make action fail on bad formatting
* change workflow to nightly
* f it, code quality is considered breaking
* fix forgetting to set toolchain back to nightly when rewriting workflow (😔)
* Add condition for too little arguments
* run cargo fmt
* remove unneeded feature directive
This commit is contained in:
parent
55ed41ee73
commit
497953f758
8 changed files with 170 additions and 42 deletions
25
.github/workflows/code-quality.yml
vendored
Normal file
25
.github/workflows/code-quality.yml
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
name: Code Quality
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
code-quality:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
override: true
|
||||
components: clippy, rustfmt
|
||||
|
||||
- name: Check formatting
|
||||
run: cargo fmt -- --check
|
||||
|
||||
- name: Run Clippy
|
||||
run: cargo clippy -- -D warnings
|
||||
|
||||
- name: Compilation Check
|
||||
run: cargo check
|
|
@ -5,8 +5,9 @@ use parking_lot::Mutex;
|
|||
|
||||
use super::COMMAND_LIST;
|
||||
use crate::core::repl::exec::evaluate_command;
|
||||
const MAX_RECURSION_DEPTH: usize = 500; // increasing this value WILL cause a stack overflow. attempt at your own risk -
|
||||
// Caz
|
||||
// increasing this value WILL cause a stack overflow
|
||||
// attempt at your own risk - Caz
|
||||
const MAX_RECURSION_DEPTH: usize = 500;
|
||||
|
||||
lazy_static! {
|
||||
static ref RECURSION_DEPTH: Mutex<usize> = parking_lot::Mutex::new(0);
|
||||
|
|
|
@ -9,9 +9,9 @@ use log::debug;
|
|||
use parking_lot::Mutex;
|
||||
use regex::Regex;
|
||||
use rustyline::{
|
||||
error::ReadlineError, highlight::Highlighter, hint::HistoryHinter, history::DefaultHistory,
|
||||
Cmd, Completer, ConditionalEventHandler, Editor, Event, EventContext, EventHandler, Helper,
|
||||
Hinter, KeyEvent, RepeatCount, Validator,
|
||||
completion::Completer, error::ReadlineError, highlight::Highlighter, hint::HistoryHinter,
|
||||
history::DefaultHistory, Cmd, Completer, ConditionalEventHandler, Editor, Event, EventContext,
|
||||
EventHandler, Helper, Hinter, KeyEvent, RepeatCount, Validator,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
@ -19,8 +19,44 @@ use crate::{
|
|||
utils::logger::LOGGER,
|
||||
};
|
||||
|
||||
struct CommandCompleter;
|
||||
impl CommandCompleter {
|
||||
fn new() -> Self {
|
||||
CommandCompleter {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for CommandCompleter {
|
||||
type Candidate = String;
|
||||
|
||||
fn complete(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
_ctx: &rustyline::Context<'_>,
|
||||
) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
|
||||
let binding = COMMAND_LIST.commands.read();
|
||||
let filtered_commands: Vec<_> = binding
|
||||
.iter()
|
||||
.filter(|command| command.name.starts_with(line))
|
||||
.collect();
|
||||
|
||||
let completions: Vec<String> = filtered_commands
|
||||
.iter()
|
||||
.filter(|command| command.name.starts_with(&line[..pos]))
|
||||
.map(|command| command.name[pos..].to_string())
|
||||
.collect();
|
||||
Ok((pos, completions))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Completer, Helper, Hinter, Validator)]
|
||||
struct MyHelper(#[rustyline(Hinter)] HistoryHinter);
|
||||
struct MyHelper {
|
||||
#[rustyline(Hinter)]
|
||||
hinter: HistoryHinter,
|
||||
#[rustyline(Completer)]
|
||||
completer: CommandCompleter,
|
||||
}
|
||||
|
||||
impl Highlighter for MyHelper {
|
||||
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
|
||||
|
@ -142,6 +178,11 @@ fn tokenize(command: &str) -> Vec<String> {
|
|||
tokens
|
||||
}
|
||||
|
||||
pub fn parse_command(input: &str) -> anyhow::Result<Vec<String>> {
|
||||
let pattern = Regex::new(r"[;|\n]").unwrap();
|
||||
let commands: Vec<String> = pattern.split(input).map(|s| String::from(s)).collect();
|
||||
Ok(commands)
|
||||
}
|
||||
pub fn evaluate_command(input: &str) -> anyhow::Result<()> {
|
||||
if input.trim().is_empty() {
|
||||
println!("Empty command, skipping. type 'help' for a list of commands.");
|
||||
|
@ -176,7 +217,10 @@ pub fn evaluate_command(input: &str) -> anyhow::Result<()> {
|
|||
|
||||
pub async fn handle_repl() -> anyhow::Result<()> {
|
||||
let mut rl = Editor::<MyHelper, DefaultHistory>::new()?;
|
||||
rl.set_helper(Some(MyHelper(HistoryHinter::new())));
|
||||
rl.set_helper(Some(MyHelper {
|
||||
hinter: HistoryHinter::new(),
|
||||
completer: CommandCompleter::new(),
|
||||
}));
|
||||
|
||||
rl.bind_sequence(
|
||||
KeyEvent::from('`'),
|
||||
|
|
|
@ -26,8 +26,22 @@ pub struct Command {
|
|||
function: Callable,
|
||||
pub arg_count: u8,
|
||||
}
|
||||
|
||||
#[allow(private_interfaces)]
|
||||
impl Command {
|
||||
pub fn new(
|
||||
name: &'static str,
|
||||
description: Option<&'static str>,
|
||||
function: Callable,
|
||||
arg_count: Option<u8>,
|
||||
) -> Self {
|
||||
Command {
|
||||
name,
|
||||
description,
|
||||
function,
|
||||
arg_count: arg_count.unwrap_or(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute(&self, args: Option<Vec<String>>) -> anyhow::Result<()> {
|
||||
match &self.function {
|
||||
Callable::Simple(f) => {
|
||||
|
@ -69,21 +83,63 @@ pub struct CommandList {
|
|||
pub aliases: RwLock<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
fn check_similarity(target: &str, strings: &[String]) -> Option<String> {
|
||||
strings
|
||||
.iter()
|
||||
.filter(|s| target.chars().zip(s.chars()).any(|(c1, c2)| c1 == c2))
|
||||
.min_by_key(|s| {
|
||||
let mut diff_count = 0;
|
||||
for (c1, c2) in target.chars().zip(s.chars()) {
|
||||
if c1 != c2 {
|
||||
diff_count += 1;
|
||||
}
|
||||
fn hamming_distance(a: &str, b: &str) -> Option<usize> {
|
||||
if a.len() != b.len() {
|
||||
return None;
|
||||
}
|
||||
Some(
|
||||
a.chars()
|
||||
.zip(b.chars())
|
||||
.filter(|(char_a, char_b)| char_a != char_b)
|
||||
.count(),
|
||||
)
|
||||
}
|
||||
|
||||
fn edit_distance(a: &str, b: &str) -> usize {
|
||||
let m = a.len();
|
||||
let n = b.len();
|
||||
|
||||
let mut dp = vec![vec![0; n + 1]; m + 1];
|
||||
|
||||
for i in 0..=m {
|
||||
for j in 0..=n {
|
||||
if i == 0 {
|
||||
dp[i][j] = j;
|
||||
} else if j == 0 {
|
||||
dp[i][j] = i;
|
||||
} else if a.chars().nth(i - 1) == b.chars().nth(j - 1) {
|
||||
dp[i][j] = dp[i - 1][j - 1];
|
||||
} else {
|
||||
dp[i][j] = 1 + dp[i - 1][j - 1].min(dp[i - 1][j]).min(dp[i][j - 1]);
|
||||
}
|
||||
diff_count += target.len().abs_diff(s.len());
|
||||
diff_count
|
||||
})
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
dp[m][n]
|
||||
}
|
||||
|
||||
fn check_similarity(target: &str, strings: &[String]) -> Option<String> {
|
||||
let max_hamming_distance: usize = 2;
|
||||
let max_edit_distance: usize = 2;
|
||||
let mut best_match: Option<String> = None;
|
||||
let mut best_distance = usize::MAX;
|
||||
|
||||
for s in strings {
|
||||
if let Some(hamming_dist) = hamming_distance(target, s) {
|
||||
if hamming_dist <= max_hamming_distance && hamming_dist < best_distance {
|
||||
best_distance = hamming_dist;
|
||||
best_match = Some(s.clone());
|
||||
}
|
||||
} else {
|
||||
let edit_dist = edit_distance(target, s);
|
||||
if edit_dist <= max_edit_distance && edit_dist < best_distance {
|
||||
best_distance = edit_dist;
|
||||
best_match = Some(s.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
best_match
|
||||
}
|
||||
|
||||
impl CommandList {
|
||||
|
@ -153,6 +209,13 @@ impl CommandList {
|
|||
);
|
||||
Ok(())
|
||||
}
|
||||
(expected, None) => {
|
||||
eprintln!(
|
||||
"Command: '{}' expected {} arguments but received none",
|
||||
name, expected
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
(_, _) => command.execute(args),
|
||||
}
|
||||
} else {
|
||||
|
@ -172,7 +235,10 @@ impl CommandList {
|
|||
eprintln!("Did you mean: '{}'?", similar.green().italic().bold());
|
||||
Ok(())
|
||||
}
|
||||
None => Ok(()),
|
||||
None => {
|
||||
println!("Type 'help' for a list of commands");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#![deny(clippy::unwrap_in_result)]
|
||||
|
||||
use anyhow::Result;
|
||||
use log::LevelFilter;
|
||||
use plugin_api::plugin_imports::*;
|
||||
|
|
|
@ -30,7 +30,6 @@ impl DynamicLogger {
|
|||
pub fn write_to_file(&self, file_path: &str) {
|
||||
let file = OpenOptions::new()
|
||||
.create(true)
|
||||
|
||||
.append(true)
|
||||
.open(file_path)
|
||||
.expect("Failed to open log file");
|
||||
|
|
|
@ -1,14 +1 @@
|
|||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
|
|
15
test.zensh
15
test.zensh
|
@ -1,4 +1,11 @@
|
|||
echo ping
|
||||
echo pong
|
||||
exec test.zensh
|
||||
break
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
echo "Hello World"
|
||||
echo "hello world"; hello
|
Loading…
Add table
Add a link
Reference in a new issue