init
This commit is contained in:
169
src/judge.rs
Normal file
169
src/judge.rs
Normal file
@@ -0,0 +1,169 @@
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
|
||||
const SHELL_BUILTINS: &[&str] = &[
|
||||
".", ":", "alias", "bg", "bind", "break", "builtin", "caller", "cd",
|
||||
"command", "compgen", "complete", "compopt", "continue", "declare",
|
||||
"dirs", "disown", "echo", "enable", "eval", "exec", "exit", "export",
|
||||
"false", "fc", "fg", "getopts", "hash", "help", "history", "jobs",
|
||||
"kill", "let", "local", "logout", "mapfile", "popd", "printf", "pushd",
|
||||
"pwd", "read", "readarray", "readonly", "return", "set", "shift",
|
||||
"shopt", "source", "suspend", "test", "times", "trap", "true", "type",
|
||||
"typeset", "ulimit", "umask", "unalias", "unset", "wait",
|
||||
];
|
||||
|
||||
/// Cached command set for fast lookup without fork.
|
||||
pub struct CommandCache {
|
||||
commands: HashSet<String>,
|
||||
}
|
||||
|
||||
impl CommandCache {
|
||||
pub fn new() -> Self {
|
||||
let mut commands = HashSet::new();
|
||||
|
||||
// Shell builtins
|
||||
for &b in SHELL_BUILTINS {
|
||||
commands.insert(b.to_string());
|
||||
}
|
||||
|
||||
// Scan PATH directories
|
||||
if let Ok(path_var) = env::var("PATH") {
|
||||
for dir in path_var.split(':') {
|
||||
if let Ok(entries) = fs::read_dir(dir) {
|
||||
for entry in entries.flatten() {
|
||||
if let Some(name) = entry.file_name().to_str() {
|
||||
commands.insert(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self { commands }
|
||||
}
|
||||
|
||||
pub fn contains(&self, cmd: &str) -> bool {
|
||||
self.commands.contains(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine whether user input should be executed as a shell command.
|
||||
pub fn is_command(input: &str, cache: &CommandCache) -> bool {
|
||||
let trimmed = input.trim();
|
||||
if trimmed.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Shell operators: pipe, redirect, background, semicolon, logical operators
|
||||
if contains_shell_operator(trimmed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Variable assignment: FOO=bar
|
||||
if is_variable_assignment(trimmed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Extract first token
|
||||
let first_token = match trimmed.split_whitespace().next() {
|
||||
Some(t) => t,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
// Lookup in cached command set (no fork)
|
||||
cache.contains(first_token)
|
||||
}
|
||||
|
||||
fn contains_shell_operator(input: &str) -> bool {
|
||||
let mut in_single = false;
|
||||
let mut in_double = false;
|
||||
let mut prev = '\0';
|
||||
|
||||
for ch in input.chars() {
|
||||
match ch {
|
||||
'\'' if !in_double && prev != '\\' => in_single = !in_single,
|
||||
'"' if !in_single && prev != '\\' => in_double = !in_double,
|
||||
'|' | ';' if !in_single && !in_double => return true,
|
||||
'>' | '<' if !in_single && !in_double => return true,
|
||||
'&' if !in_single && !in_double => return true,
|
||||
_ => {}
|
||||
}
|
||||
prev = ch;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn is_variable_assignment(input: &str) -> bool {
|
||||
if let Some(eq_pos) = input.find('=') {
|
||||
if eq_pos == 0 {
|
||||
return false;
|
||||
}
|
||||
let name = &input[..eq_pos];
|
||||
if name.contains(' ') {
|
||||
return false;
|
||||
}
|
||||
let first = name.chars().next().unwrap();
|
||||
if !first.is_alphabetic() && first != '_' {
|
||||
return false;
|
||||
}
|
||||
return name.chars().all(|c| c.is_alphanumeric() || c == '_');
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn cache() -> CommandCache {
|
||||
CommandCache::new()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builtins() {
|
||||
let c = cache();
|
||||
assert!(is_command("cd /tmp", &c));
|
||||
assert!(is_command("export FOO=bar", &c));
|
||||
assert!(is_command("echo hello", &c));
|
||||
assert!(is_command("pwd", &c));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_commands() {
|
||||
let c = cache();
|
||||
assert!(is_command("ls", &c));
|
||||
assert!(is_command("ls -la", &c));
|
||||
assert!(is_command("cat /etc/hostname", &c));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shell_operators() {
|
||||
let c = cache();
|
||||
assert!(is_command("ls | grep foo", &c));
|
||||
assert!(is_command("echo hello > /tmp/out", &c));
|
||||
assert!(is_command("cat file1 && cat file2", &c));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_variable_assignment() {
|
||||
let c = cache();
|
||||
assert!(is_command("FOO=bar", &c));
|
||||
assert!(is_command("MY_VAR=hello", &c));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ai_input() {
|
||||
let c = cache();
|
||||
assert!(!is_command("macbookがフリーズする原因を調べて", &c));
|
||||
assert!(!is_command("hello world what is this", &c));
|
||||
assert!(!is_command("Rustでhello worldを書いて", &c));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty() {
|
||||
let c = cache();
|
||||
assert!(!is_command("", &c));
|
||||
assert!(!is_command(" ", &c));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user