#shell #tui #terminal #shell-history #env-var

diysh

Do-It-Yourself SHell 是一个库,允许您创建自己的类似 shell 的文本界面

6 个稳定版本

2.1.3 2023 年 8 月 13 日
2.0.2 2023 年 7 月 31 日
2.0.1 2023 年 7 月 30 日
1.1.1 2023 年 7 月 27 日

#205命令行界面

每月42次下载

MIT 协议

35KB
564

diysh - DIY SHell

diysh 是一个库,允许开发者为他们的 Rust 程序创建类似的 shell 界面。

创建 Shell

Shell 是一个文本界面,您可以从其中读取命令并记录所需的信息。要创建 Shell,您必须遵循以下步骤

let mut shell = Shell::new(); // Mandatory

shell.set_sparse(do_sparse: bool);

shell.set_prompt(prompt: &str); 

shell.register_command(command: CommandDefinition);

shell.register_help();

shell.register_history();

shell.register_exit();

shell.set_log_directory(path: &str);

shell.register_env_var(name:&str, value: &str)

您的 Shell 创建后,您可以访问以下方法

shell.read_and_run();

shell.log(level: LogLevel, text: &str);

shell.get_env_var<T: FromStr>(name: &str) -> Result<T, CommandError>;

shell.help();

shell.history(len: usize);

shell.exit();

设置稀疏

如果设置为 true,将在命令输出后打印一个空行,以便在命令之间提供一些视觉分隔。

help
exit - Exits the program
history len - Shows the len-th last commands 
help - Shows this page
print text:str - Prints the specified text to the terminal

print "Hello World"
Hello World

设置提示

设置要在用户输入之前显示的文本。例如,将其设置为 ">> ",将得到以下结果

>> print "Hello World"
Hello World
>> help
exit - Exits the program
history len:int - Shows the list of the last len-th commands ran 
help - Shows this page
print text:str - Prints the specified text to the terminal

您的提示支持环境变量,因此您可以将提示设置为: "$USER$ ~>>",然后变量将在提示打印到屏幕之前进行评估。实际上,您的提示存储在一个名为 SYSTEM_PROMPT_DEFINITION 的环境变量中。如果您在提示中使用环境变量,请不要在运行时修改此变量。这样做,提示将评估您设置的任何环境变量。

ojarrisonn_ ~>> $USER=rust
rust ~>> $SYSTEM_PROMPT_DEFINITION=<$USER$> ~ 
<rust> ~ $USER=ojarrisonn_
<rust> ~ help
...

注册命令

可能是最重要的方法。它用于使用 CommandDefinition 将新命令注册到您的 Shell 中。CommandDefinition 有:名称、描述、参数和回调函数。以下是一个打印命令的 CommandDefinition 示例

let print_command = CommandDefinition::new("print") // Creates a empty command with given name
    .set_description("text:str - Prints the specified text to the terminal")
    .add_arg(ArgType::Str) // You can add positional arguments of Str, Int, Float and Bool, as many as you wish
    // Here you can both pass the pointer to a function or use a closure that will be called when this command is called
    // Your function must be fn(&Shell, &Vec<EvaluatedArg>)
    .set_callback(|shell, args| {
        let text = args[0].get_str().unwrap();

        println!("{}", text);

        Box::new(Passed())
    })
    .build() // Builds the command

命令名称 必须camelCase,并且仅包含字母和数字(但名称不能以数字开头)。命令名称最好是单个简短的单词。但您也可以创建一个名为 myAwesomeCommandToDoSomethingAmazing 的命令,尽管这对于用户来说可能不太方便。

描述不是必需的,但建议提供描述以帮助用户使用您的 Shell。好的描述应提供参数类型和带有完整命令描述的解释性名称,以便告知用户该命令的功能。

如果命令需要一些参数,好的描述应该看起来像这样:"arg_name:arg_type arg_name2:arg_type2 ... arg_nameN:arg_type - 命令的作用描述""。如果不带参数,只需"命令的作用描述""即可。记住,参数名称只是为了帮助用户理解其含义,它对程序本身没有实际影响。

可以多次调用add_arg方法来添加任何可用的ArgType

设置回调是命令最重要的部分,你可以创建一个不带回调的命令,但它是无用的。回调接收运行中的Shell和一个包含从输入中读取的值的EvaluatedArg向量。

ArgType 和 EvaluatedArg

在指定命令参数时,你需要在命令定义和使用参数的回调函数内部都指定参数类型。

ArgType用于在CommandDefinition中指定类型。一旦定义,当命令被读取和评估时,你将收到一个包含EvaluatedArg的向量,它们的顺序与你定义的顺序相同。它们都可以是StrIntFloatBool

因此,如果你创建以下命令

CommandDefinition::new("print")
    .add_arg(ArgType::Str)
    .add_arg(ArgType::Int)
    .add_arg(ArgType::Bool)
    .set_callback(|shell, args| { ... })
    .build()

args将是一个向量,其中args[0]有一个EvaluatedArg::Strargs[1]有一个EvaluatedArg::Intargs[2]有一个EvaluatedArg::Bool。在函数内部,要获取存储的正确值,只需调用args[0].get_str().unwrap()args[1].get_int().unwrap()args[2].get_bool().unwrap()

方法get_strget_intget_floatget_bool返回一个Option,不要尝试强制类型转换,如果你在EvaluatedArg::Float上调用get_int,你会收到一个None而不是Some。

在命令行传递参数时,如果 Str 没有空格,则可以不加引号;否则,请使用双引号。 Int 是由 0 到 9 的数字组成的常规数字。 Float 是由单个 '.' 分隔的整数和小数部分。最后,一个 Bool 是一个不加引号、区分大小写的 truefalse

读取环境变量

在回调函数内部,您可以通过调用 shell.get_env_var::<T>(name: &str) 来获取您环境变量的值。传入变量的名称(例如 USER),以及类型(例如 String)。

let user = shell.get_env_var::<String>("USER");

它将返回一个 Result<T, EnvVarError>。如果是 Ok,则无需担心;但如果它是 Err,我们有两种情况

  1. Unset 变量没有被设置,这就是为什么您可能需要在创建 shell 时使用 register_env_var 方法的原因。
  2. Mismatch 用户设置的变量值无法解析为您希望的类型。也许您应该记录一个错误并通知用户设置正确的变量值。这是由您决定的。

注册帮助、历史和退出命令

注册一个 help、一个 history len:int 和一个 exit 命令。

以下是相应的 CommandDefinition

CommandDefinition::new("help")
    .set_description("- Shows this page")
    .set_callback(|shell, _args| {
        shell.help();
    })
    .build();

CommandDefinition::new("history")
    .add_arg(ArgType::Int)
    .set_description("len:int - Shows the list of the last len-th commands ran")
    .set_callback(|shell, args| {
        let len = args[0].get_int().unwrap();

        shell.history(len);
    })
    .build();

CommandDefinition::new("exit")
    .set_description("- Exists the program")
    .set_callback(|shell, _args| {
        shell.exit();
    })
    .build()

重要的是要知道 helphistoryexit 是公共方法,所以您可以创建自己的这些命令定义,同时仍然可以使用我们提供的方法。

设置日志目录

设置一个写入日志的目录

注册环境变量

diysh 支持环境变量,此命令允许您预定义一些。这并不意味着您希望使用的所有环境变量都需要注册,但如果知道某些命令需要特殊的环境变量,您可以注册一些。

环境变量

在 diysh 中,您可以设置和使用自己的环境变量。这些变量在每次会话中都会重置。您可以在 shell 定义中定义其中一些,但用户也可以在运行时定义他们的环境变量(也可以重新定义所有环境变量)。

要在运行时创建环境变量,只需输入:$VAR_NAME=THE VALUE。变量赋值必须以 $ 开始,然后是一个名称(仅限于大写字母和下划线),然后是 =,最后是值,可以是您可以在单行中写入的任何内容。

>> $USER=ojarrisonn_
>> $YEAR=2023
>> $DOES_IT_WORK=true
>> $MESSAGE=Hello World this is a string

之后,您可以在某些命令的回调中使用 get_env_var 收集这些值,并解析为 Rust 类型(如果可能)。

一个好的习惯是,任何在 = 之后的都将作为变量的值传递。因此,你可以写带空格的字符串,而无需使用引号。同时,请记住,$ 不是变量名称的一部分。

要在壳中使用你的变量,只需将其用 $...$ 包围,然后你按下 Enter 键后它就会被评估。

>> $MESSAGE=Hello World
>> print "$MESSAGE$"
Hello World
>> $CMD=print
>> $CMD$ "$MESSAGE$"
Hello World

读取并运行

这种方法是要求用户输入命令的方法。它的默认行为是打印定义的提示符,等待用户输入,尝试将用户输入解析为命令,然后运行相应的回调,并将用户传递的参数传递过去。此外,此函数将记录每个错误、警告和信息。

通常,在循环中使用 read_and_run。

日志

diysh 日志系统相当简单。你只需要调用当前壳的 log 方法,并传递 LogLevel,它可以是以 INFOWARNERROR 的形式,然后传递一个包含所需消息的 &str。警告和错误被记录到日志文件和屏幕上,但只有信息被记录到日志文件。

完整示例

这里是一个实现了默认命令、打印和求和命令的壳的完整示例

fn main() {
    let mut shell = Shell::new(); // Creates the shell

    // Now, let's set it up
    shell
        .set_sparse(true) // Enable sparsing
        .set_prompt("$USER$ ~>>") // Set the prompt to one which uses an environment variable
        .set_log_directory("/tmp/diysh/") // Set the log folder
        .register_env_var("USER", "ojarrisonn_") // Registers the variable to be used in the prompt
        .register_help()
        .register_history()
        .register_exit()
        
        // Our print command
        .register_command( CommandDefinition::new("print")
            .set_description("text:str - Prints the specified text to the terminal")
            .add_arg(ArgType::Str)
            .set_callback(|shell, args| {
                let text = args[0].get_str().unwrap();

                shell.log(LogLevel::INFO, &text);
            })
            .build()
        )
        
        // Our sum command
        .register_command( CommandDefinition::new("sum")
            .set_description("a:int b:int - Prints the result of the sum of a + b")
            .add_arg(ArgType::Int)
            .add_arg(ArgType::Int)
            .set_callback(|shell, args| {
                let a = args[0].get_int().unwrap();
                let b = args[1].get_int().unwrap();

                shell.log(LogLevel::INFO, &format!("The sum is {}", a + b));
            })
            .build()
        )
        
        // A command that prints the value stored in the TO_PRINT variable
        .register_command(CommandDefinition::new("echoEnv")
            .set_description("- Prints the value stored in $TO_PRINT")
            .set_callback(|shell, _args| {
                match shell.get_env_var::<String>("TO_PRINT") {
                    Ok(text) => { println!("{}", text); shell.log(LogLevel::INFO, &text)},
                    Err(e) => shell.log(LogLevel::ERROR, &format!("{}", e)),
                }
            })
            .build()
    
        );

    loop {
        shell.read_and_run();
    }
}

依赖项

~3–4.5MB
~71K SLoC