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次下载
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
的向量,它们的顺序与你定义的顺序相同。它们都可以是Str
、Int
、Float
或Bool
。
因此,如果你创建以下命令
CommandDefinition::new("print")
.add_arg(ArgType::Str)
.add_arg(ArgType::Int)
.add_arg(ArgType::Bool)
.set_callback(|shell, args| { ... })
.build()
args
将是一个向量,其中args[0]
有一个EvaluatedArg::Str
,args[1]
有一个EvaluatedArg::Int
,args[2]
有一个EvaluatedArg::Bool
。在函数内部,要获取存储的正确值,只需调用args[0].get_str().unwrap()
或args[1].get_int().unwrap()
或args[2].get_bool().unwrap()
。
方法get_str
、get_int
、get_float
和get_bool
返回一个Option
,不要尝试强制类型转换,如果你在EvaluatedArg::Float
上调用get_int
,你会收到一个None而不是Some。
在命令行传递参数时,如果 Str
没有空格,则可以不加引号;否则,请使用双引号。 Int
是由 0 到 9 的数字组成的常规数字。 Float
是由单个 '.
' 分隔的整数和小数部分。最后,一个 Bool
是一个不加引号、区分大小写的 true
或 false
。
读取环境变量
在回调函数内部,您可以通过调用 shell.get_env_var::<T>(name: &str)
来获取您环境变量的值。传入变量的名称(例如 USER
),以及类型(例如 String
)。
let user = shell.get_env_var::<String>("USER");
它将返回一个 Result<T, EnvVarError>
。如果是 Ok
,则无需担心;但如果它是 Err
,我们有两种情况
Unset
变量没有被设置,这就是为什么您可能需要在创建 shell 时使用register_env_var
方法的原因。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()
重要的是要知道 help
、history
和 exit
是公共方法,所以您可以创建自己的这些命令定义,同时仍然可以使用我们提供的方法。
设置日志目录
设置一个写入日志的目录
注册环境变量
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
,它可以是以 INFO
、WARN
或 ERROR
的形式,然后传递一个包含所需消息的 &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