use std::collections::{HashMap, HashSet};
use super::{SyntheticConfig, SyntheticGenerator};
use crate::error::Result;
#[derive(Debug, Clone, PartialEq)]
pub struct ShellSample {
history: Vec<String>,
prefix: String,
completion: String,
cwd: String,
}
impl ShellSample {
#[must_use]
pub fn new(prefix: impl Into<String>, completion: impl Into<String>) -> Self {
Self {
history: Vec::new(),
prefix: prefix.into(),
completion: completion.into(),
cwd: String::new(),
}
}
#[must_use]
pub fn with_history(mut self, history: Vec<String>) -> Self {
self.history = history;
self
}
#[must_use]
pub fn with_cwd(mut self, cwd: impl Into<String>) -> Self {
self.cwd = cwd.into();
self
}
#[must_use]
pub fn prefix(&self) -> &str {
&self.prefix
}
#[must_use]
pub fn completion(&self) -> &str {
&self.completion
}
#[must_use]
pub fn history(&self) -> &[String] {
&self.history
}
#[must_use]
pub fn cwd(&self) -> &str {
&self.cwd
}
#[must_use]
pub fn command_name(&self) -> Option<&str> {
self.completion.split_whitespace().next()
}
#[must_use]
pub fn arguments(&self) -> Vec<&str> {
self.completion.split_whitespace().skip(1).collect()
}
#[must_use]
pub fn is_valid_completion(&self) -> bool {
self.completion.starts_with(&self.prefix)
}
}
#[derive(Debug, Clone)]
pub struct ShellGrammar {
commands: HashSet<String>,
subcommands: HashMap<String, HashSet<String>>,
common_options: HashSet<String>,
}
impl ShellGrammar {
#[must_use]
pub fn new() -> Self {
Self {
commands: HashSet::new(),
subcommands: HashMap::new(),
common_options: HashSet::new(),
}
}
#[must_use]
pub fn common_commands() -> Self {
let mut grammar = Self::new();
grammar.add_command("git");
grammar.add_subcommands(
"git",
&[
"status", "commit", "push", "pull", "checkout", "branch", "merge", "rebase", "log",
"diff", "add", "reset", "stash", "fetch", "clone", "init",
],
);
grammar.add_command("cargo");
grammar.add_subcommands(
"cargo",
&[
"build", "run", "test", "check", "clippy", "fmt", "doc", "publish", "new", "init",
"add", "remove", "update", "bench", "clean",
],
);
grammar.add_command("npm");
grammar.add_subcommands(
"npm",
&[
"install", "run", "test", "start", "build", "publish", "init", "update", "audit",
"ci", "pack",
],
);
grammar.add_command("docker");
grammar.add_subcommands(
"docker",
&[
"run", "build", "push", "pull", "ps", "images", "exec", "stop", "start", "rm",
"rmi", "logs", "compose",
],
);
for cmd in &[
"ls", "cd", "cp", "mv", "rm", "mkdir", "rmdir", "cat", "grep", "find", "chmod",
"chown", "curl", "wget", "ssh", "scp", "tar", "zip", "unzip", "make", "python", "node",
] {
grammar.add_command(cmd);
}
grammar.add_common_options(&[
"-h",
"--help",
"-v",
"--version",
"-q",
"--quiet",
"-f",
"--force",
"-r",
"--recursive",
"-n",
"--dry-run",
]);
grammar
}
pub fn add_command(&mut self, command: &str) {
self.commands.insert(command.to_string());
}
pub fn add_subcommands(&mut self, command: &str, subs: &[&str]) {
let entry = self.subcommands.entry(command.to_string()).or_default();
for sub in subs {
entry.insert((*sub).to_string());
}
}
pub fn add_common_options(&mut self, options: &[&str]) {
for opt in options {
self.common_options.insert((*opt).to_string());
}
}
#[must_use]
pub fn is_valid_command(&self, command: &str) -> bool {
let tokens: Vec<&str> = command.split_whitespace().collect();
if tokens.is_empty() {
return false;
}
let cmd_name = tokens[0];
if !self.commands.contains(cmd_name) {
return false;
}
if let Some(subs) = self.subcommands.get(cmd_name) {
if tokens.len() > 1 {
let second = tokens[1];
if !subs.contains(second) && !second.starts_with('-') {
return false;
}
}
}
true
}
#[must_use]
pub fn is_valid_option(&self, option: &str) -> bool {
option.starts_with('-') && (self.common_options.contains(option) || option.len() <= 20)
}
#[must_use]
pub fn commands(&self) -> &HashSet<String> {
&self.commands
}
#[must_use]
pub fn get_subcommands(&self, command: &str) -> Option<&HashSet<String>> {
self.subcommands.get(command)
}
}
impl Default for ShellGrammar {
fn default() -> Self {
Self::common_commands()
}
}
#[derive(Debug, Clone)]
pub struct ShellGeneratorConfig {
pub enable_template: bool,
pub enable_permutation: bool,
pub enable_context_variation: bool,
pub max_permute_args: usize,
}
impl Default for ShellGeneratorConfig {
fn default() -> Self {
Self {
enable_template: true,
enable_permutation: true,
enable_context_variation: true,
max_permute_args: 3,
}
}
}
#[derive(Debug, Clone)]
pub struct ShellSyntheticGenerator {
grammar: ShellGrammar,
config: ShellGeneratorConfig,
substitutions: HashMap<String, Vec<String>>,
}
include!("shell_generator_impl.rs");
include!("shell_tests.rs");