├── .gitignore ├── Cargo.toml ├── README.md ├── README_cn.md ├── ToDo.md ├── history.txt ├── src ├── cmd │ ├── cmd_spinoff_sample.rs │ ├── cmdconfig.rs │ ├── cmdloop.rs │ ├── cmdmultilevel.rs │ ├── cmdserver.rs │ ├── cmdtask.rs │ ├── cmdusedifflogger.rs │ ├── mod.rs │ ├── requestsample.rs │ └── rootcmd.rs ├── commons │ ├── mod.rs │ └── subcmdcompleter.rs ├── configure │ ├── config_error.rs │ ├── config_global.rs │ └── mod.rs ├── interact │ ├── cli.rs │ └── mod.rs ├── logger │ ├── logger.rs │ └── mod.rs ├── main.rs ├── request │ ├── mod.rs │ ├── req.rs │ ├── requestmodules.rs │ └── responsepaser.rs └── server │ ├── mod.rs │ └── server.rs └── timestamp /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX leaves these everywhere on SMB shares 2 | ._* 3 | 4 | # OSX trash 5 | .DS_Store 6 | 7 | # Eclipse files 8 | .classpath 9 | .project 10 | .settings/** 11 | 12 | # Vim swap files 13 | *.swp 14 | 15 | # Files generated by JetBrains IDEs, e.g. IntelliJ IDEA 16 | .idea/ 17 | *.iml 18 | out/ 19 | 20 | # Vscode files 21 | .vscode/** 22 | 23 | target 24 | dist 25 | tmp 26 | /bin 27 | logs 28 | log 29 | 30 | # fuzzing hack, see fuzz/cli.rs 31 | fuzz-incremental/ 32 | 33 | # cargo configuration. We presently use this to create custom cargo profiles 34 | # that should not be checked in at this location. 35 | .cargo/ 36 | 37 | 38 | package.json 39 | node_modules/ 40 | create_diffs.js 41 | diffs/ 42 | create_commit.js 43 | rebase_tags.js 44 | 45 | # rust ignore 46 | Cargo.lock 47 | **/target 48 | **/*.rs.bk -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "interactcli-rs" 3 | version = "0.1.0" 4 | authors = ["jiashiwen"] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | # To use 'RUSTFLAGS="$RUSTFLAGS -A dead_code" cargo build' not desplay dead_code warning 9 | 10 | [dependencies] 11 | clap = "4.2.4" 12 | rustyline = "13.0.0" 13 | rustyline-derive = "0.10.0" 14 | shellwords = "1.1.0" 15 | log = "0.4.17" 16 | log4rs = "1.2.0" 17 | serde = { version = "1.0.160", features = ["derive"] } 18 | serde_json = "1.0.96" 19 | serde_yaml = "0.9.21" 20 | lazy_static = "1.4.0" 21 | config = "0.13.3" 22 | reqwest = { version = "0.11.16", features = ["json"] } 23 | tokio = { version = "^1", features = ["full"] } 24 | url = "2.3.1" 25 | prettytable-rs = "0.10.0" 26 | anyhow = "1.0.70" 27 | fork = "0.1.21" 28 | sysinfo = "0.29.11" 29 | chrono = "0.4.24" 30 | signal-hook = "0.3.15" 31 | daemonize = "0.5.0" 32 | spinoff = "0.8.0" 33 | 34 | 35 | [[example]] 36 | name = "sshsample" 37 | path = "examples/sshsample.rs" 38 | 39 | [[example]] 40 | name = "configrs" 41 | path = "examples/configrs.rs" 42 | 43 | [[example]] 44 | name = "yamlparser" 45 | path = "examples/yamlparser.rs" 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # interactcli-rs 2 | 3 | [简体中文](README_cn.md) 4 | 5 | interactcli-rs is a command-line program framework used to solve the problem of the integration of command-line and 6 | interactive modes, including functions such as unification of command-line interactive modes and sub-command prompts. 7 | The framework integrates clap and shellwords. 8 | 9 | ## quick guide 10 | 11 | The frame contains examples of visiting www.baidu.com 12 | 13 | The quick start process is as follows: 14 | 15 | * clone project 16 | 17 | ```shell 18 | git clone https://github.com/jiashiwen/interactcli-rs.git 19 | cd interactcli-rs 20 | ``` 21 | 22 | * Command line mode 23 | 24 | ```shell 25 | cargo run requestsample baidu 26 | ``` 27 | 28 | * Interactive mode 29 | 30 | ```shell 31 | cargo run -- -i 32 | interact-rs> requestsample baidu 33 | ``` 34 | 35 | ## Interactive mode 36 | 37 | Use "Tab" key in interactive mode to prompt command 38 | 39 | ## Development steps 40 | 41 | * Define commands The cmd module is used to define commands and related subcommands 42 | 43 | ```rust 44 | use clap::Command; 45 | 46 | pub fn new_requestsample_cmd() -> Command<'static> { 47 | clap::Command::new("requestsample") 48 | .about("requestsample") 49 | .subcommand(get_baidu_cmd()) 50 | } 51 | 52 | pub fn get_baidu_cmd() -> Command<'static> { 53 | clap::Command::new("baidu").about("request www.baidu.com") 54 | } 55 | ``` 56 | 57 | The new_requestsample_cmd function defines the command "requestsample", and the get_baidu_cmd function defines the 58 | subcommand baidu of requestsample 59 | 60 | * Register order The command tree is defined in the src/cmd/rootcmd.rs file, and the defined subcommands can be 61 | registered here 62 | 63 | ```rust 64 | lazy_static! { 65 | static ref CLIAPP: clap::App<'static> = App::new("interact-rs") 66 | .version("1.0") 67 | .author("Shiwen Jia. ") 68 | .about("command line sample") 69 | .arg( 70 | Arg::new("config") 71 | .short('c') 72 | .long("config") 73 | .value_name("FILE") 74 | .about("Sets a custom config file") 75 | .takes_value(true) 76 | ) 77 | .arg( 78 | Arg::new("interact") 79 | .short('i') 80 | .long("interact") 81 | .about("run as interact mod") 82 | ) 83 | .arg( 84 | Arg::new("v") 85 | .short('v') 86 | .multiple_occurrences(true) 87 | .takes_value(true) 88 | .about("Sets the level of verbosity") 89 | ) 90 | .subcommand(new_requestsample_cmd()) 91 | .subcommand(new_config_cmd()) 92 | .subcommand(new_multi_cmd()) 93 | .subcommand(new_task_cmd()) 94 | .subcommand( 95 | App::new("test") 96 | .about("controls testing features") 97 | .version("1.3") 98 | .author("Someone E. ") 99 | .arg( 100 | Arg::new("debug") 101 | .short('d') 102 | .about("print debug information verbosely") 103 | ) 104 | ); 105 | static ref SUBCMDS: Vec = subcommands(); 106 | } 107 | 108 | ``` 109 | 110 | The defined command does not need other processing, the framework will generate a sub-command tree when the system is 111 | running, for the support of the command prompt 112 | 113 | 114 | * Parse command The cmd_match in src/cmd/rootcmd.rs is responsible for parsing commands, and the parsing logic can be 115 | written in this function 116 | 117 | ```rust 118 | fn cmd_match(matches: &ArgMatches) { 119 | if let Some(ref matches) = matches.subcommand_matches("requestsample") { 120 | if let Some(_) = matches.subcommand_matches("baidu") { 121 | let rt = tokio::runtime::Runtime::new().unwrap(); 122 | let async_req = async { 123 | let result = req::get_baidu().await; 124 | println!("{:?}", result); 125 | }; 126 | rt.block_on(async_req); 127 | }; 128 | } 129 | } 130 | ``` 131 | 132 | * Modify the command prompt in interactive mode The prompt can be defined in src/interact/cli.rs 133 | 134 | ```rust 135 | pub fn run() { 136 | 137 | ... 138 | 139 | loop { 140 | let p = format!("{}> ", "interact-rs"); 141 | rl.helper_mut().expect("No helper").colored_prompt = format!("\x1b[1;32m{}\x1b[0m", p); 142 | 143 | ... 144 | } 145 | 146 | ... 147 | } 148 | 149 | ``` 150 | -------------------------------------------------------------------------------- /README_cn.md: -------------------------------------------------------------------------------- 1 | # interactcli-rs 2 | 3 | [English](README.md) 4 | 5 | interactcli-rs 是一个命令行程序框架,用于解决命令行与交互模式一体化问题,包括命令行交互模式统一、子命令提示等功能。该框架集成了clap和shellwords。 6 | 7 | ## 快速指南 8 | 9 | 框架中包含了访问www.baidu.com的例子 10 | 11 | 快速上手过程如下: 12 | 13 | * clone 项目 14 | 15 | ```shell 16 | git clone https://github.com/jiashiwen/interactcli-rs.git 17 | cd interactcli-rs 18 | ``` 19 | 20 | * 命令行模式 21 | 22 | ```shell 23 | cargo run requestsample baidu 24 | ``` 25 | 26 | * 交互模式 27 | 28 | ```shell 29 | cargo run -- -i 30 | interact-rs> requestsample baidu 31 | ``` 32 | 33 | ## 交互模式 34 | 35 | 交互模式下使用"Tab"键,进行命令提示 36 | 37 | ## 开发步骤 38 | 39 | * 定义命令 40 | cmd 模块用于定义命令以及相关子命令 41 | 42 | ```rust 43 | use clap::Command; 44 | 45 | pub fn new_requestsample_cmd() -> Command<'static> { 46 | clap::Command::new("requestsample") 47 | .about("requestsample") 48 | .subcommand(get_baidu_cmd()) 49 | } 50 | 51 | pub fn get_baidu_cmd() -> Command<'static> { 52 | clap::Command::new("baidu").about("request www.baidu.com") 53 | } 54 | ``` 55 | 56 | new_requestsample_cmd 函数定义了命令 "requestsample",get_baidu_cmd 函数定义了 requestsample 的子命令 baidu 57 | 58 | * 注册命令 59 | src/cmd/rootcmd.rs 文件中定义了命令树,可以在此注册定义好的子命令 60 | 61 | ```rust 62 | lazy_static! { 63 | static ref CLIAPP: clap::Command<'static> = clap::Command::new("interact-rs") 64 | .version("1.0") 65 | .author("Shiwen Jia. ") 66 | .about("command line sample") 67 | .arg_required_else_help(true) 68 | .arg( 69 | Arg::new("config") 70 | .short('c') 71 | .long("config") 72 | .value_name("FILE") 73 | .help("Sets a custom config file") 74 | .takes_value(true) 75 | ) 76 | .arg( 77 | Arg::new("daemon") 78 | .short('d') 79 | .long("daemon") 80 | .help("run as daemon") 81 | ) 82 | .arg( 83 | Arg::new("interact") 84 | .short('i') 85 | .long("interact") 86 | .conflicts_with("daemon") 87 | .help("run as interact mod") 88 | ) 89 | .arg( 90 | Arg::new("v") 91 | .short('v') 92 | .multiple_occurrences(true) 93 | .takes_value(true) 94 | .help("Sets the level of verbosity") 95 | ) 96 | .subcommand(new_requestsample_cmd()) 97 | .subcommand(new_config_cmd()) 98 | .subcommand(new_multi_cmd()) 99 | .subcommand(new_task_cmd()) 100 | .subcommand(new_loop_cmd()) 101 | .subcommand( 102 | clap::Command::new("test") 103 | .about("controls testing features") 104 | .version("1.3") 105 | .author("Someone E. ") 106 | .arg( 107 | Arg::new("debug") 108 | .short('d') 109 | .help("print debug information verbosely") 110 | ) 111 | ); 112 | static ref SUBCMDS: Vec = subcommands(); 113 | } 114 | 115 | pub fn run_app() { 116 | let matches = CLIAPP.clone().get_matches(); 117 | if let Some(c) = matches.value_of("config") { 118 | println!("config path is:{}", c); 119 | set_config_file_path(c.to_string()); 120 | } 121 | set_config(&get_config_file_path()); 122 | cmd_match(&matches); 123 | } 124 | 125 | pub fn run_from(args: Vec) { 126 | match clap_Command::try_get_matches_from(CLIAPP.to_owned(), args.clone()) { 127 | Ok(matches) => { 128 | cmd_match(&matches); 129 | } 130 | Err(err) => { 131 | err.print().expect("Error writing Error"); 132 | } 133 | }; 134 | } 135 | 136 | ``` 137 | 138 | 定义好的命令不需其他处理,框架会在系统运行时生成子命令树,用于命令提示的支持 139 | 140 | 141 | * 命令解析 142 | src/cmd/rootcmd.rs 中的 cmd_match 负责解析命令,可以把解析逻辑写在该函数中 143 | 144 | ```rust 145 | fn cmd_match(matches: &ArgMatches) { 146 | if let Some(ref matches) = matches.subcommand_matches("requestsample") { 147 | if let Some(_) = matches.subcommand_matches("baidu") { 148 | let rt = tokio::runtime::Runtime::new().unwrap(); 149 | let async_req = async { 150 | let result = req::get_baidu().await; 151 | println!("{:?}", result); 152 | }; 153 | rt.block_on(async_req); 154 | }; 155 | } 156 | } 157 | ``` 158 | 159 | * 修改交互模式的命令提示 160 | 提示符可以在src/interact/cli.rs 中定义 161 | 162 | ```rust 163 | pub fn run() { 164 | 165 | ... 166 | 167 | loop { 168 | let p = format!("{}> ", "interact-rs"); 169 | rl.helper_mut().expect("No helper").colored_prompt = format!("\x1b[1;32m{}\x1b[0m", p); 170 | 171 | ... 172 | } 173 | 174 | ... 175 | } 176 | 177 | ``` 178 | -------------------------------------------------------------------------------- /ToDo.md: -------------------------------------------------------------------------------- 1 | # ToDo 2 | 3 | - [x] 关闭 rustyreadline 的log,将日志级别设为info,debug会输出rustyreadline 日志 4 | - [ ] 集成类似golang viper 的配置文件管理组件,config-rs,目前config-rs还不支持多层配置,只支持kv模式 5 | - [x] command autocomplete.思路:利用rustreadline的completer trate 自己实现 -------------------------------------------------------------------------------- /history.txt: -------------------------------------------------------------------------------- 1 | #V2 2 | exit 3 | -------------------------------------------------------------------------------- /src/cmd/cmd_spinoff_sample.rs: -------------------------------------------------------------------------------- 1 | use clap::Command; 2 | 3 | pub fn new_spinoff_sample_cmd() -> Command { 4 | clap::Command::new("spinoff_sample").about("spinoff_sample") 5 | } 6 | -------------------------------------------------------------------------------- /src/cmd/cmdconfig.rs: -------------------------------------------------------------------------------- 1 | use clap::Command; 2 | 3 | pub fn new_config_cmd() -> Command { 4 | clap::Command::new("config") 5 | .about("config") 6 | .subcommand(config_show_cmd()) 7 | } 8 | 9 | fn config_show_cmd() -> Command { 10 | clap::Command::new("show") 11 | .about("show some info ") 12 | .subcommand(config_show_info_cmd()) 13 | .subcommand(config_show_all_cmd()) 14 | } 15 | 16 | fn config_show_info_cmd() -> Command { 17 | clap::Command::new("info").about("show info") 18 | } 19 | 20 | fn config_show_all_cmd() -> Command { 21 | clap::Command::new("all").about("show all ") 22 | } 23 | -------------------------------------------------------------------------------- /src/cmd/cmdloop.rs: -------------------------------------------------------------------------------- 1 | use clap::Command; 2 | 3 | pub fn new_loop_cmd() -> Command { 4 | clap::Command::new("loop").about("loop") 5 | } 6 | -------------------------------------------------------------------------------- /src/cmd/cmdmultilevel.rs: -------------------------------------------------------------------------------- 1 | use clap::Command; 2 | 3 | pub fn new_multi_cmd() -> Command { 4 | clap::Command::new("multi") 5 | .about("multi") 6 | .subcommand(config_level2_cmd1()) 7 | .subcommand(config_level2_cmd2()) 8 | .subcommand(abc_cmd()) 9 | } 10 | 11 | fn config_level2_cmd1() -> Command { 12 | clap::Command::new("level2_cmd1") 13 | .about("level2_cmd1 info ") 14 | .subcommand(config_show_info_cmd()) 15 | } 16 | 17 | fn abc_cmd() -> Command { 18 | clap::Command::new("abc_cmd").about("abc_cmd info") 19 | } 20 | 21 | fn config_level2_cmd2() -> Command { 22 | clap::Command::new("level2_cmd2") 23 | .about("level2_cmd2 info ") 24 | .subcommand(config_level3_cmd1()) 25 | .subcommand(config_level3_cmd2()) 26 | .subcommand(config_level3_cmd3()) 27 | } 28 | 29 | fn config_level3_cmd1() -> Command { 30 | clap::Command::new("level3_cmd1") 31 | .about("level3_cmd1 info ") 32 | .subcommand(config_show_info_cmd()) 33 | .subcommand(config_show_all_cmd()) 34 | } 35 | 36 | fn config_level3_cmd2() -> Command { 37 | clap::Command::new("level3_cmd2") 38 | .about("level3_cmd2 info ") 39 | .subcommand(config_show_info_cmd()) 40 | .subcommand(config_show_all_cmd()) 41 | } 42 | 43 | fn config_level3_cmd3() -> Command { 44 | clap::Command::new("level3_cmd3") 45 | .about("level3_cmd3 info ") 46 | .subcommand(config_show_info_cmd()) 47 | .subcommand(config_show_all_cmd()) 48 | } 49 | 50 | fn config_show_info_cmd() -> Command { 51 | clap::Command::new("info").about("show info") 52 | } 53 | 54 | fn config_show_all_cmd() -> Command { 55 | clap::Command::new("all").about("show all ") 56 | } 57 | -------------------------------------------------------------------------------- /src/cmd/cmdserver.rs: -------------------------------------------------------------------------------- 1 | use clap::{Arg, ArgAction, Command}; 2 | 3 | pub fn new_server_cmd() -> Command { 4 | clap::Command::new("server") 5 | .about("server") 6 | .subcommand(server_start_byfork()) 7 | .subcommand(server_start_bydaemonize()) 8 | } 9 | 10 | pub fn server_start_byfork() -> Command { 11 | clap::Command::new("byfork") 12 | .about("start daemon by fork crate") 13 | .arg( 14 | Arg::new("daemon") 15 | .short('d') 16 | .long("daemon") 17 | .action(ArgAction::SetTrue) 18 | .help("start as daemon") 19 | .required(false), 20 | ) 21 | } 22 | pub fn server_start_bydaemonize() -> Command { 23 | clap::Command::new("bydaemonize") 24 | .about("start daemon by daemonize crate") 25 | .arg( 26 | Arg::new("daemon") 27 | .short('d') 28 | .long("daemon") 29 | .action(ArgAction::SetTrue) 30 | .help("start as daemon") 31 | .required(false), 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /src/cmd/cmdtask.rs: -------------------------------------------------------------------------------- 1 | use clap::{arg, Command}; 2 | 3 | pub fn new_task_cmd() -> Command { 4 | clap::Command::new("task") 5 | .about("command about task") 6 | .subcommand(cmd_task_create()) 7 | .subcommand(cmd_task_start()) 8 | .subcommand(cmd_task_stop()) 9 | .subcommand(cmd_task_remove()) 10 | .subcommand(cmd_task_list()) 11 | } 12 | 13 | fn cmd_task_create() -> Command { 14 | clap::Command::new("create") 15 | .about("create task") 16 | .arg(arg!( "create task json file path")) 17 | } 18 | 19 | fn cmd_task_start() -> Command { 20 | clap::Command::new("start") 21 | .about("start task") 22 | .arg(arg!( "input task id to stop")) 23 | } 24 | 25 | fn cmd_task_stop() -> Command { 26 | clap::Command::new("stop") 27 | .about("stop task") 28 | .arg(arg!( "input task id to stop")) 29 | } 30 | 31 | fn cmd_task_remove() -> Command { 32 | clap::Command::new("remove") 33 | .about("remove task") 34 | .arg(arg!( "input task id to stop")) 35 | } 36 | 37 | fn cmd_task_list() -> Command { 38 | clap::Command::new("list") 39 | .about("list tasks") 40 | .subcommand(cmd_task_list_all()) 41 | .subcommand(cmd_task_list_by_ids()) 42 | .subcommand(cmd_task_list_by_names()) 43 | } 44 | 45 | fn cmd_task_list_all() -> Command { 46 | clap::Command::new("all") 47 | .about("list tasks by task ids") 48 | .arg(arg!([queryid] "input queryid if have")) 49 | } 50 | 51 | fn cmd_task_list_by_ids() -> Command { 52 | clap::Command::new("byid") 53 | .about("list tasks by task ids") 54 | .arg(arg!( "input taskid")) 55 | } 56 | 57 | fn cmd_task_list_by_names() -> Command { 58 | clap::Command::new("bynames") 59 | .about("list tasks by task names") 60 | .arg(arg!( 61 | r"input tasks name if multi use ',' to splite" 62 | )) 63 | } 64 | -------------------------------------------------------------------------------- /src/cmd/cmdusedifflogger.rs: -------------------------------------------------------------------------------- 1 | use clap::Command; 2 | 3 | pub fn new_use_log_cmd() -> Command { 4 | clap::Command::new("uselog") 5 | .about("use diffrent target log") 6 | .subcommand(new_use_sys_log_cmd()) 7 | .subcommand(new_use_business_log_cmd()) 8 | } 9 | 10 | pub fn new_use_sys_log_cmd() -> Command { 11 | clap::Command::new("syslog").about("append to syslog") 12 | } 13 | 14 | pub fn new_use_business_log_cmd() -> Command { 15 | clap::Command::new("businesslog").about("append to business log") 16 | } 17 | -------------------------------------------------------------------------------- /src/cmd/mod.rs: -------------------------------------------------------------------------------- 1 | mod cmd_spinoff_sample; 2 | mod cmdconfig; 3 | mod cmdloop; 4 | mod cmdmultilevel; 5 | mod cmdserver; 6 | mod cmdtask; 7 | pub mod cmdusedifflogger; 8 | mod requestsample; 9 | mod rootcmd; 10 | 11 | pub use cmd_spinoff_sample::new_spinoff_sample_cmd; 12 | pub use cmdconfig::new_config_cmd; 13 | pub use cmdmultilevel::new_multi_cmd; 14 | pub use cmdserver::new_server_cmd; 15 | pub use cmdtask::new_task_cmd; 16 | pub use cmdusedifflogger::new_use_log_cmd; 17 | pub use requestsample::get_baidu_cmd; 18 | pub use rootcmd::get_command_completer; 19 | pub use rootcmd::run_app; 20 | pub use rootcmd::run_from; 21 | -------------------------------------------------------------------------------- /src/cmd/requestsample.rs: -------------------------------------------------------------------------------- 1 | use clap::Command; 2 | 3 | pub fn new_requestsample_cmd() -> Command { 4 | clap::Command::new("requestsample") 5 | .about("requestsample") 6 | .subcommand(get_baidu_cmd()) 7 | } 8 | 9 | pub fn get_baidu_cmd() -> Command { 10 | clap::Command::new("baidu").about("request www.baidu.com") 11 | } 12 | -------------------------------------------------------------------------------- /src/cmd/rootcmd.rs: -------------------------------------------------------------------------------- 1 | use crate::cmd::cmdloop::new_loop_cmd; 2 | use crate::cmd::requestsample::new_requestsample_cmd; 3 | use crate::cmd::{ 4 | new_config_cmd, new_multi_cmd, new_server_cmd, new_spinoff_sample_cmd, new_task_cmd, 5 | new_use_log_cmd, 6 | }; 7 | use crate::commons::CommandCompleter; 8 | use crate::commons::SubCmd; 9 | use crate::configure::{self, generate_default_config, get_config, get_config_file_path, Config}; 10 | use crate::configure::{set_config_file_path, set_config_from_file}; 11 | use crate::interact::{self, INTERACT_STATUS}; 12 | use crate::request::{req, ReqResult, Request, RequestTaskListAll}; 13 | use crate::server::start; 14 | use chrono::prelude::Local; 15 | use clap::{Arg, ArgAction, ArgMatches, Command as clap_Command}; 16 | use daemonize::Daemonize; 17 | use fork::{daemon, Fork}; 18 | use lazy_static::lazy_static; 19 | use spinoff::{spinners, Color, Spinner}; 20 | use std::fs::File; 21 | use std::io::Read; 22 | use std::process::Command; 23 | use std::sync::atomic::AtomicBool; 24 | use std::sync::atomic::Ordering; 25 | use std::sync::Arc; 26 | use std::thread::sleep; 27 | use std::time::Duration; 28 | use std::{env, fs, process, thread}; 29 | use sysinfo::{PidExt, System, SystemExt}; 30 | 31 | lazy_static! { 32 | static ref CLIAPP: clap::Command = clap::Command::new("interact-rs") 33 | .version("1.0") 34 | .author("Shiwen Jia. ") 35 | .about("command line sample") 36 | .arg_required_else_help(true) 37 | .arg( 38 | Arg::new("config") 39 | .short('c') 40 | .long("config") 41 | .value_name("FILE") 42 | .help("Sets a custom config file") 43 | ) 44 | .arg( 45 | Arg::new("daemon") 46 | .short('d') 47 | .long("daemon") 48 | .action(ArgAction::SetTrue) 49 | .help("run as daemon") 50 | ) 51 | .arg( 52 | Arg::new("interact") 53 | .short('i') 54 | .long("interact") 55 | .conflicts_with("daemon") 56 | .action(ArgAction::SetTrue) 57 | .help("run as interact mod") 58 | ) 59 | .subcommand(new_requestsample_cmd()) 60 | .subcommand(new_config_cmd()) 61 | .subcommand(new_multi_cmd()) 62 | .subcommand(new_task_cmd()) 63 | .subcommand(new_loop_cmd()) 64 | .subcommand(new_use_log_cmd()) 65 | .subcommand(new_server_cmd()) 66 | .subcommand(new_spinoff_sample_cmd()) 67 | .subcommand( 68 | clap::Command::new("test") 69 | .about("controls testing features") 70 | .version("1.3") 71 | .author("Someone E. ") 72 | .arg( 73 | Arg::new("debug") 74 | .short('d') 75 | .help("print debug information verbosely") 76 | ) 77 | ); 78 | static ref SUBCMDS: Vec = subcommands(); 79 | } 80 | 81 | pub fn run_app() { 82 | let matches = CLIAPP.clone().get_matches(); 83 | if let Some(c) = matches.get_one::("config") { 84 | // if let Some(c) = matches.value_of("config") { 85 | println!("config path is:{}", c); 86 | set_config_file_path(c.to_string()); 87 | } 88 | cmd_match(&matches); 89 | } 90 | 91 | pub fn run_from(args: Vec) { 92 | match clap_Command::try_get_matches_from(CLIAPP.to_owned(), args.clone()) { 93 | Ok(matches) => { 94 | cmd_match(&matches); 95 | } 96 | Err(err) => { 97 | err.print().expect("Error writing Error"); 98 | } 99 | }; 100 | } 101 | 102 | // 获取全部子命令,用于构建commandcompleter 103 | pub fn all_subcommand(app: &clap_Command, beginlevel: usize, input: &mut Vec) { 104 | let nextlevel = beginlevel + 1; 105 | let mut subcmds = vec![]; 106 | for iterm in app.get_subcommands() { 107 | subcmds.push(iterm.get_name().to_string()); 108 | if iterm.has_subcommands() { 109 | all_subcommand(iterm, nextlevel, input); 110 | } else { 111 | if beginlevel == 0 { 112 | all_subcommand(iterm, nextlevel, input); 113 | } 114 | } 115 | } 116 | let subcommand = SubCmd { 117 | level: beginlevel, 118 | command_name: app.get_name().to_string(), 119 | subcommands: subcmds, 120 | }; 121 | input.push(subcommand); 122 | } 123 | 124 | pub fn get_command_completer() -> CommandCompleter { 125 | CommandCompleter::new(SUBCMDS.to_vec()) 126 | } 127 | 128 | fn subcommands() -> Vec { 129 | let mut subcmds = vec![]; 130 | all_subcommand(&CLIAPP, 0, &mut subcmds); 131 | subcmds 132 | } 133 | 134 | pub fn process_exists(pid: &u32) -> bool { 135 | let mut sys = System::new_all(); 136 | sys.refresh_all(); 137 | for (syspid, _) in sys.processes() { 138 | if syspid.as_u32().eq(pid) { 139 | return true; 140 | } 141 | } 142 | return false; 143 | } 144 | 145 | fn cmd_match(matches: &ArgMatches) { 146 | if let Some(c) = matches.get_one::("config") { 147 | // if let Some(c) = matches.value_of("config") { 148 | set_config_file_path(c.to_string()); 149 | set_config_from_file(&get_config_file_path()); 150 | } else { 151 | set_config_from_file(""); 152 | } 153 | let config = get_config().unwrap(); 154 | let server = config.server; 155 | let req = Request::new(server.clone()); 156 | 157 | if matches.get_flag("daemon") { 158 | // if matches.is_present("daemon") { 159 | let args: Vec = env::args().collect(); 160 | if let Ok(Fork::Child) = daemon(true, true) { 161 | // 启动子进程 162 | let mut cmd = Command::new(&args[0]); 163 | 164 | for idx in 1..args.len() { 165 | let arg = args.get(idx).expect("get cmd arg error!"); 166 | // 去除后台启动参数,避免重复启动 167 | if arg.eq("-d") || arg.eq("-daemon") { 168 | continue; 169 | } 170 | cmd.arg(arg); 171 | } 172 | 173 | let child = cmd.spawn().expect("Child process failed to start."); 174 | fs::write("pid", child.id().to_string()).unwrap(); 175 | println!("process id is:{}", std::process::id()); 176 | println!("child id is:{}", child.id()); 177 | } 178 | println!("{}", "daemon mod"); 179 | process::exit(0); 180 | } 181 | 182 | if matches.get_flag("interact") { 183 | if !INTERACT_STATUS.load(std::sync::atomic::Ordering::SeqCst) { 184 | interact::run(); 185 | return; 186 | } 187 | } 188 | 189 | // 测试 log 写入不同文件 190 | if let Some(ref log) = matches.subcommand_matches("uselog") { 191 | println!("use log"); 192 | if let Some(_) = log.subcommand_matches("syslog") { 193 | log::info!(target:"syslog","Input sys log"); 194 | } 195 | 196 | if let Some(_) = log.subcommand_matches("businesslog") { 197 | log::info!(target:"businesslog","Input business log"); 198 | } 199 | } 200 | 201 | if let Some(ref _matches) = matches.subcommand_matches("loop") { 202 | let term = Arc::new(AtomicBool::new(false)); 203 | let sigint_2 = Arc::new(AtomicBool::new(false)); 204 | signal_hook::flag::register(signal_hook::consts::SIGTERM, Arc::clone(&term)).unwrap(); 205 | signal_hook::flag::register(signal_hook::consts::SIGINT, Arc::clone(&sigint_2)).unwrap(); 206 | loop { 207 | if sigint_2.load(Ordering::Relaxed) { 208 | println!("{}", "singint signal recived"); 209 | break; 210 | } 211 | 212 | thread::sleep(Duration::from_millis(1000)); 213 | if term.load(Ordering::Relaxed) { 214 | println!("{:?}", term); 215 | break; 216 | } 217 | let dt = Local::now(); 218 | let _ = fs::write("timestamp", dt.timestamp_millis().to_string()); 219 | } 220 | } 221 | 222 | // You can check for the existence of subcommands, and if found use their 223 | // matches just as you would the top level app 224 | if let Some(ref matches) = matches.subcommand_matches("test") { 225 | if matches.contains_id("debug") { 226 | // if matches.is_present("debug") { 227 | println!("Printing debug info..."); 228 | } else { 229 | println!("Printing normally..."); 230 | } 231 | } 232 | 233 | if let Some(ref matches) = matches.subcommand_matches("requestsample") { 234 | if let Some(_) = matches.subcommand_matches("baidu") { 235 | let rt = tokio::runtime::Runtime::new().unwrap(); 236 | let async_req = async { 237 | let result = req::get_baidu().await; 238 | println!("{:?}", result); 239 | }; 240 | rt.block_on(async_req); 241 | }; 242 | } 243 | 244 | if let Some(ref matches) = matches.subcommand_matches("task") { 245 | if let Some(create) = matches.subcommand_matches("create") { 246 | let file = File::open(create.get_one::("path").unwrap()); 247 | // let file = File::open(create.value_of("path").unwrap()); 248 | match file { 249 | Ok(mut f) => { 250 | let mut data = String::new(); 251 | if let Err(e) = f.read_to_string(&mut data) { 252 | println!("{}", e); 253 | return; 254 | }; 255 | let rt = tokio::runtime::Runtime::new().unwrap(); 256 | let async_req = async { 257 | let resp = req.create_task(data).await; 258 | let result = ReqResult::new(resp); 259 | result.normal_parsor().await; 260 | }; 261 | rt.block_on(async_req); 262 | } 263 | Err(e) => { 264 | println!("{}", e); 265 | } 266 | } 267 | } 268 | if let Some(start) = matches.subcommand_matches("start") { 269 | if let Some(taskid) = start.get_one::("taskid") { 270 | let rt = tokio::runtime::Runtime::new().unwrap(); 271 | let async_req = async { 272 | let resp = req.task_start(taskid.to_string()).await; 273 | let result = ReqResult::new(resp); 274 | result.normal_parsor().await; 275 | }; 276 | rt.block_on(async_req); 277 | }; 278 | } 279 | if let Some(stop) = matches.subcommand_matches("stop") { 280 | if let Some(taskid) = stop.get_one::("taskid") { 281 | let rt = tokio::runtime::Runtime::new().unwrap(); 282 | let async_req = async { 283 | let resp = req.task_stop(taskid.to_string()).await; 284 | let result = ReqResult::new(resp); 285 | result.normal_parsor().await; 286 | }; 287 | rt.block_on(async_req); 288 | }; 289 | } 290 | if let Some(remove) = matches.subcommand_matches("remove") { 291 | if let Some(taskid) = remove.get_one::("taskid") { 292 | let rt = tokio::runtime::Runtime::new().unwrap(); 293 | let async_req = async { 294 | let resp = req.task_stop(taskid.to_string()).await; 295 | let result = ReqResult::new(resp); 296 | result.normal_parsor().await; 297 | }; 298 | rt.block_on(async_req); 299 | }; 300 | } 301 | if let Some(list) = matches.subcommand_matches("list") { 302 | match list.subcommand_name() { 303 | Some("all") => { 304 | let queryid = list 305 | .subcommand_matches("all") 306 | .unwrap() 307 | .get_one::("queryid"); 308 | let mut module = RequestTaskListAll::default(); 309 | let rt = tokio::runtime::Runtime::new().unwrap(); 310 | let async_req = async { 311 | match queryid { 312 | None => { 313 | let resp = req.task_list_all(module).await; 314 | let result = ReqResult::new(resp); 315 | result.task_list_all_parsor().await; 316 | } 317 | Some(id) => { 318 | module.set_query_id(id.to_string()); 319 | let resp = req.task_list_all(module).await; 320 | let result = ReqResult::new(resp); 321 | result.task_list_all_parsor().await; 322 | } 323 | } 324 | }; 325 | rt.block_on(async_req); 326 | } 327 | Some("byid") => { 328 | let queryid = list 329 | .subcommand_matches("byid") 330 | .unwrap() 331 | .get_one::("taskid"); 332 | let rt = tokio::runtime::Runtime::new().unwrap(); 333 | let async_req = async { 334 | let mut ids = vec![]; 335 | if let Some(id) = queryid { 336 | ids.push(id.to_string()); 337 | let resp = req.task_list_by_ids(ids).await; 338 | let result = ReqResult::new(resp); 339 | result.task_list_byid_parsor().await; 340 | } 341 | }; 342 | rt.block_on(async_req); 343 | } 344 | Some("bynames") => { 345 | let names = list 346 | .subcommand_matches("bynames") 347 | .unwrap() 348 | .get_one::("tasksname"); 349 | let rt = tokio::runtime::Runtime::new().unwrap(); 350 | let async_req = async { 351 | // let mut namearry = names; 352 | if let Some(namesstr) = names { 353 | let namearry = namesstr.split(',').collect::>(); 354 | 355 | let resp = req.task_list_by_names(namearry).await; 356 | let result = ReqResult::new(resp); 357 | result.task_list_bynames_parsor().await; 358 | } 359 | }; 360 | rt.block_on(async_req); 361 | } 362 | 363 | _ => {} 364 | } 365 | } 366 | } 367 | 368 | if let Some(config) = matches.subcommand_matches("config") { 369 | if let Some(show) = config.subcommand_matches("show") { 370 | match show.subcommand_name() { 371 | Some("current") => { 372 | let current = configure::get_config().expect("get current configure error!"); 373 | let yml = 374 | serde_yaml::to_string(¤t).expect("pars configure to yaml error!"); 375 | println!("{}", yml); 376 | } 377 | Some("default") => { 378 | let config = Config::default(); 379 | let yml = serde_yaml::to_string(&config); 380 | match yml { 381 | Ok(y) => { 382 | println!("{}", y); 383 | } 384 | Err(e) => { 385 | log::error!("{}", e); 386 | } 387 | } 388 | } 389 | _ => {} 390 | } 391 | } 392 | if let Some(gen_config) = config.subcommand_matches("gendefault") { 393 | let mut file = String::from(""); 394 | if let Some(path) = gen_config.get_one::("filepath") { 395 | file.push_str(path); 396 | } else { 397 | file.push_str("config_default.yml") 398 | } 399 | if let Err(e) = generate_default_config(file.as_str()) { 400 | log::error!("{}", e); 401 | return; 402 | }; 403 | println!("{} created!", file); 404 | } 405 | } 406 | 407 | if let Some(server) = matches.subcommand_matches("server") { 408 | if let Some(startbyfork) = server.subcommand_matches("byfork") { 409 | println!("start by fork"); 410 | if startbyfork.get_flag("daemon") { 411 | let args: Vec = env::args().collect(); 412 | if let Ok(Fork::Child) = daemon(true, false) { 413 | // 启动子进程 414 | let mut cmd = Command::new(&args[0]); 415 | 416 | for idx in 1..args.len() { 417 | let arg = args.get(idx).expect("get cmd arg error!"); 418 | // 去除后台启动参数,避免重复启动 419 | if arg.eq("-d") || arg.eq("-daemon") { 420 | continue; 421 | } 422 | cmd.arg(arg); 423 | } 424 | 425 | let child = cmd.spawn().expect("Child process failed to start."); 426 | fs::write("pid", child.id().to_string()).unwrap(); 427 | println!("process id is:{}", std::process::id()); 428 | println!("child id is:{}", child.id()); 429 | } 430 | println!("{}", "daemon mod"); 431 | process::exit(0); 432 | } 433 | start("by_fork:".to_string()); 434 | } 435 | if let Some(startbydaemonize) = server.subcommand_matches("bydaemonize") { 436 | println!("start by daemonize"); 437 | let base_dir = env::current_dir().unwrap(); 438 | if startbydaemonize.get_flag("daemon") { 439 | let stdout = File::create("/tmp/daemon.out").unwrap(); 440 | let stderr = File::create("/tmp/daemon.err").unwrap(); 441 | 442 | println!("{:?}", base_dir); 443 | 444 | let daemonize = Daemonize::new() 445 | .pid_file("/tmp/test.pid") // Every method except `new` and `start` 446 | .chown_pid_file(true) // is optional, see `Daemonize` documentation 447 | .working_directory(base_dir.as_path()) // for default behaviour. 448 | // .user("nobody") 449 | // .group("daemon") // Group name 450 | .umask(0o777) // Set umask, `0o027` by default. 451 | .stdout(stdout) // Redirect stdout to `/tmp/daemon.out`. 452 | .stderr(stderr) // Redirect stderr to `/tmp/daemon.err`. 453 | .privileged_action(|| "Executed before drop privileges"); 454 | 455 | match daemonize.start() { 456 | Ok(_) => { 457 | println!("Success, daemonized"); 458 | } 459 | Err(e) => eprintln!("Error, {}", e), 460 | } 461 | } 462 | println!("pid is:{}", std::process::id()); 463 | // let mut path = base_dir.clone(); 464 | // path.push("pid"); 465 | // fs::write(path, process::id().to_string()).unwrap(); 466 | fs::write("pid", process::id().to_string()).unwrap(); 467 | start("by_daemonize:".to_string()); 468 | } 469 | } 470 | 471 | if let Some(spinoff_sample) = matches.subcommand_matches("spinoff_sample") { 472 | let mut spinner = Spinner::new(spinners::Dots, "Loading...", Color::Blue); 473 | // sleep(Duration::from_secs(3)); 474 | let rt = tokio::runtime::Runtime::new().unwrap(); 475 | let async_req = async { 476 | sleep(Duration::from_secs(3)); 477 | }; 478 | rt.block_on(async_req); 479 | 480 | spinner.success("Done!"); 481 | } 482 | } 483 | -------------------------------------------------------------------------------- /src/commons/mod.rs: -------------------------------------------------------------------------------- 1 | mod subcmdcompleter; 2 | 3 | pub use subcmdcompleter::CommandCompleter; 4 | pub use subcmdcompleter::SubCmd; 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/commons/subcmdcompleter.rs: -------------------------------------------------------------------------------- 1 | use rustyline::completion::{Completer, Pair}; 2 | use rustyline::Context; 3 | use rustyline::Result; 4 | 5 | // const DOUBLE_QUOTES_ESCAPE_CHAR: Option = Some('\\'); 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct SubCmd { 9 | pub level: usize, 10 | pub command_name: String, 11 | pub subcommands: Vec, 12 | } 13 | 14 | #[derive(Debug, Clone)] 15 | pub struct CommandCompleter { 16 | subcommands: Vec, 17 | } 18 | 19 | impl CommandCompleter { 20 | pub fn new(subcmds: Vec) -> Self { 21 | Self { 22 | subcommands: subcmds, 23 | } 24 | } 25 | 26 | //获取level下所有可能的子命令 27 | pub fn level_possible_cmd(&self, level: usize) -> Vec { 28 | let mut subcmds = vec![]; 29 | let cmds = self.subcommands.clone(); 30 | for iterm in cmds { 31 | if iterm.level == level { 32 | subcmds.push(iterm.command_name.clone()); 33 | } 34 | } 35 | return subcmds; 36 | } 37 | //获取level下某字符串开头的子命令 38 | pub fn level_prefix_possible_cmd(&self, level: usize, prefix: &str) -> Vec { 39 | let mut subcmds = vec![]; 40 | let cmds = self.subcommands.clone(); 41 | for iterm in cmds { 42 | if iterm.level == level && iterm.command_name.starts_with(prefix) { 43 | subcmds.push(iterm.command_name); 44 | } 45 | } 46 | return subcmds; 47 | } 48 | 49 | //获取某level 下某subcommand的所有子命令 50 | pub fn level_cmd_possible_sub_cmd(&self, level: usize, cmd: String) -> Vec { 51 | let mut subcmds = vec![]; 52 | let cmds = self.subcommands.clone(); 53 | for iterm in cmds { 54 | if iterm.level == level && iterm.command_name == cmd { 55 | subcmds = iterm.subcommands.clone(); 56 | } 57 | } 58 | return subcmds; 59 | } 60 | 61 | //获取某level 下某subcommand的所有prefix子命令 62 | pub fn level_cmd_possible_prefix_sub_cmd( 63 | &self, 64 | level: usize, 65 | cmd: String, 66 | prefix: &str, 67 | ) -> Vec { 68 | let mut subcmds = vec![]; 69 | let cmds = self.subcommands.clone(); 70 | for iterm in cmds { 71 | if iterm.level == level && iterm.command_name == cmd { 72 | for i in iterm.subcommands { 73 | if i.starts_with(prefix) { 74 | subcmds.push(i); 75 | } 76 | } 77 | } 78 | } 79 | return subcmds; 80 | } 81 | 82 | pub fn complete_cmd(&self, line: &str, pos: usize) -> Result<(usize, Vec)> { 83 | let mut entries: Vec = Vec::new(); 84 | let d: Vec<_> = line.split(' ').collect(); 85 | 86 | if d.len() == 1 { 87 | if d.last() == Some(&"") { 88 | for str in self.level_possible_cmd(1) { 89 | let mut replace = str.clone(); 90 | replace.push_str(" "); 91 | entries.push(Pair { 92 | display: str.clone(), 93 | replacement: replace, 94 | }); 95 | } 96 | return Ok((pos, entries)); 97 | } 98 | 99 | if let Some(last) = d.last() { 100 | for str in self.level_prefix_possible_cmd(1, *last) { 101 | let mut replace = str.clone(); 102 | replace.push_str(" "); 103 | entries.push(Pair { 104 | display: str.clone(), 105 | replacement: replace, 106 | }); 107 | } 108 | return Ok((pos - last.len(), entries)); 109 | } 110 | } 111 | 112 | if d.last() == Some(&"") { 113 | for str in self 114 | .level_cmd_possible_sub_cmd(d.len() - 1, d.get(d.len() - 2).unwrap().to_string()) 115 | { 116 | let mut replace = str.clone(); 117 | replace.push_str(" "); 118 | entries.push(Pair { 119 | display: str.clone(), 120 | replacement: replace, 121 | }); 122 | } 123 | return Ok((pos, entries)); 124 | } 125 | 126 | if let Some(last) = d.last() { 127 | for str in self.level_cmd_possible_prefix_sub_cmd( 128 | d.len() - 1, 129 | d.get(d.len() - 2).unwrap().to_string(), 130 | *last, 131 | ) { 132 | let mut replace = str.clone(); 133 | replace.push_str(" "); 134 | entries.push(Pair { 135 | display: str.clone(), 136 | replacement: replace, 137 | }); 138 | } 139 | return Ok((pos - last.len(), entries)); 140 | } 141 | 142 | Ok((pos, entries)) 143 | } 144 | } 145 | 146 | impl Completer for CommandCompleter { 147 | type Candidate = Pair; 148 | 149 | fn complete(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Result<(usize, Vec)> { 150 | self.complete_cmd(line, pos) 151 | } 152 | } 153 | 154 | #[cfg(test)] 155 | mod test { 156 | use super::*; 157 | 158 | #[test] 159 | fn command_completer_test() { 160 | let mut subcmds: Vec = vec![]; 161 | subcmds.push(SubCmd { 162 | level: 0, 163 | command_name: "level0_cmd0".to_string(), 164 | subcommands: vec!["level1_cmd1".to_string(), "level1_cmd2".to_string()], 165 | }); 166 | subcmds.push(SubCmd { 167 | level: 1, 168 | command_name: "level1_cmd1".to_string(), 169 | subcommands: vec![ 170 | "cmd11".to_string(), 171 | "level2_cmd1_1".to_string(), 172 | "level2_cmd1_2".to_string(), 173 | ], 174 | }); 175 | subcmds.push(SubCmd { 176 | level: 1, 177 | command_name: "level1_cmd2".to_string(), 178 | subcommands: vec![ 179 | "letcmd".to_string(), 180 | "level2_cmd2_1".to_string(), 181 | "level2_cmd2_2".to_string(), 182 | ], 183 | }); 184 | 185 | let cmd_completer = CommandCompleter::new(subcmds); 186 | 187 | assert_eq!( 188 | Some(&"level0_cmd0".to_string()), 189 | cmd_completer.level_possible_cmd(0).get(0) 190 | ); 191 | assert_eq!( 192 | Some(&"cmd11".to_string()), 193 | cmd_completer 194 | .level_cmd_possible_sub_cmd(1, String::from("level1_cmd1")) 195 | .get(0) 196 | ); 197 | assert_eq!( 198 | Some(&"letcmd".to_string()), 199 | cmd_completer 200 | .level_cmd_possible_prefix_sub_cmd(1, String::from("level1_cmd2"), "let") 201 | .get(0) 202 | ); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/configure/config_error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | /// 错误的类型 4 | #[derive(Debug)] 5 | pub enum ConfigErrorType { 6 | /// 未知错误 7 | UnknowErr, 8 | } 9 | 10 | /// 应用错误 11 | #[derive(Debug)] 12 | pub struct ConfigError { 13 | /// 错误信息 14 | pub message: Option, 15 | /// 错误原因(上一级的错误) 16 | pub cause: Option, 17 | /// 错误类型 18 | pub error_type: ConfigErrorType, 19 | } 20 | 21 | impl ConfigError { 22 | /// 错误代码 23 | #[allow(dead_code)] 24 | fn code(&self) -> i32 { 25 | match self.error_type { 26 | ConfigErrorType::UnknowErr => 9999, 27 | } 28 | } 29 | /// 从上级错误中创建应用错误 30 | pub(crate) fn from_err(err: impl ToString, error_type: ConfigErrorType) -> Self { 31 | Self { 32 | message: None, 33 | cause: Some(err.to_string()), 34 | error_type: error_type, 35 | } 36 | } 37 | /// 从字符串创建应用错误 38 | #[allow(dead_code)] 39 | fn from_str(msg: &str, error_type: ConfigErrorType) -> Self { 40 | Self { 41 | message: Some(msg.to_string()), 42 | cause: None, 43 | error_type: error_type, 44 | } 45 | } 46 | // /// 数据库错误 47 | // pub fn db_error(err: impl ToString) -> Self { 48 | // Self::from_err(err, AppErrorType::DbError) 49 | // } 50 | // /// 未找到 51 | // pub fn not_found() -> Self { 52 | // Self::from_str("不存在的记录", AppErrorType::NotFound) 53 | // } 54 | } 55 | 56 | impl std::error::Error for ConfigError {} 57 | 58 | impl Display for ConfigError { 59 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 60 | write!(f, "{:?}", self) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/configure/config_global.rs: -------------------------------------------------------------------------------- 1 | use crate::configure::config_error::{ConfigError, ConfigErrorType}; 2 | use anyhow::Result; 3 | use serde::{Deserialize, Serialize}; 4 | use serde_yaml::from_str; 5 | use std::fs; 6 | use std::path::Path; 7 | use std::sync::Mutex; 8 | use std::sync::RwLock; 9 | 10 | #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] 11 | pub struct Config { 12 | pub server: String, 13 | pub token: String, 14 | } 15 | 16 | impl Config { 17 | pub fn default() -> Self { 18 | Self { 19 | server: "http://127.0.0.1:8080".to_string(), 20 | token: "".to_string(), 21 | } 22 | } 23 | 24 | pub fn set_self(&mut self, config: Config) { 25 | self.server = config.server; 26 | self.token = config.token; 27 | } 28 | 29 | pub fn get_config_image(&self) -> Self { 30 | self.clone() 31 | } 32 | 33 | pub fn flush_to_file(&self, path: String) -> Result<()> { 34 | let yml = serde_yaml::to_string(&self)?; 35 | fs::write(path, yml)?; 36 | Ok(()) 37 | } 38 | } 39 | 40 | pub fn generate_default_config(path: &str) -> Result<()> { 41 | let config = Config::default(); 42 | let yml = serde_yaml::to_string(&config)?; 43 | fs::write(path, yml)?; 44 | Ok(()) 45 | } 46 | 47 | lazy_static::lazy_static! { 48 | static ref GLOBAL_CONFIG: Mutex = { 49 | let global_config = Config::default(); 50 | Mutex::new(global_config) 51 | }; 52 | static ref CONFIG_FILE_PATH: RwLock = RwLock::new({ 53 | let path = "".to_string(); 54 | path 55 | }); 56 | } 57 | 58 | pub fn set_config(config: Config) { 59 | GLOBAL_CONFIG.lock().unwrap().set_self(config); 60 | } 61 | 62 | pub fn set_config_from_file(path: &str) { 63 | if path.is_empty() { 64 | if Path::new("config.yml").exists() { 65 | let contents = 66 | fs::read_to_string("config.yml").expect("Read config file config.yml error!"); 67 | let config = from_str::(contents.as_str()).expect("Parse config.yml error!"); 68 | GLOBAL_CONFIG.lock().unwrap().set_self(config); 69 | set_config_file_path("./config.yml".to_string()); 70 | } 71 | return; 72 | } 73 | 74 | let err_str = format!("Read config file {} error!", path); 75 | let contents = fs::read_to_string(path).expect(err_str.as_str()); 76 | let config = from_str::(contents.as_str()).expect("Parse config.yml error!"); 77 | GLOBAL_CONFIG.lock().unwrap().set_self(config); 78 | set_config_file_path(path.to_string()); 79 | } 80 | 81 | pub fn set_config_file_path(path: String) { 82 | CONFIG_FILE_PATH 83 | .write() 84 | .expect("clear config file path error!") 85 | .clear(); 86 | CONFIG_FILE_PATH.write().unwrap().push_str(path.as_str()); 87 | } 88 | 89 | pub fn get_config_file_path() -> String { 90 | CONFIG_FILE_PATH.read().unwrap().clone() 91 | } 92 | 93 | pub fn get_config() -> Result { 94 | let locked_config = GLOBAL_CONFIG.lock().map_err(|e| { 95 | return ConfigError::from_err(e.to_string(), ConfigErrorType::UnknowErr); 96 | })?; 97 | Ok(locked_config.get_config_image()) 98 | } 99 | 100 | pub fn get_current_config_yml() -> Result { 101 | let c = get_config()?; 102 | let yml = serde_yaml::to_string(&c)?; 103 | Ok(yml) 104 | } 105 | -------------------------------------------------------------------------------- /src/configure/mod.rs: -------------------------------------------------------------------------------- 1 | mod config_error; 2 | mod config_global; 3 | 4 | pub use config_global::generate_default_config; 5 | pub use config_global::get_config; 6 | pub use config_global::get_config_file_path; 7 | pub use config_global::get_current_config_yml; 8 | pub use config_global::Config; 9 | 10 | pub use config_global::set_config; 11 | pub use config_global::set_config_file_path; 12 | pub use config_global::set_config_from_file; 13 | -------------------------------------------------------------------------------- /src/interact/cli.rs: -------------------------------------------------------------------------------- 1 | use crate::cmd::{get_command_completer, run_from}; 2 | use crate::commons::CommandCompleter; 3 | use log::error; 4 | use rustyline::completion::{Completer, Pair}; 5 | use rustyline::error::ReadlineError; 6 | use rustyline::highlight::{Highlighter, MatchingBracketHighlighter}; 7 | use rustyline::hint::{Hinter, HistoryHinter}; 8 | use rustyline::validate::{MatchingBracketValidator, Validator}; 9 | // use rustyline::{validate, CompletionType, Config, Context, Editor, OutputStreamType}; 10 | use rustyline::{validate, CompletionType, Config, Context, Editor}; 11 | use rustyline_derive::Helper; 12 | use shellwords::split; 13 | use std::borrow::Cow::{self, Borrowed, Owned}; 14 | use std::sync::atomic::AtomicBool; 15 | 16 | pub static INTERACT_STATUS: AtomicBool = AtomicBool::new(false); 17 | 18 | #[derive(Helper)] 19 | struct MyHelper { 20 | // completer: FileCompleter, 21 | completer: CommandCompleter, 22 | highlighter: MatchingBracketHighlighter, 23 | validator: MatchingBracketValidator, 24 | hinter: HistoryHinter, 25 | colored_prompt: String, 26 | } 27 | 28 | impl Completer for MyHelper { 29 | type Candidate = Pair; 30 | 31 | fn complete( 32 | &self, 33 | line: &str, 34 | pos: usize, 35 | ctx: &Context<'_>, 36 | ) -> Result<(usize, Vec), ReadlineError> { 37 | self.completer.complete(line, pos, ctx) 38 | } 39 | } 40 | 41 | impl Hinter for MyHelper { 42 | type Hint = String; 43 | fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option { 44 | self.hinter.hint(line, pos, ctx); 45 | Some("".to_string()) 46 | } 47 | } 48 | 49 | impl Highlighter for MyHelper { 50 | fn highlight_prompt<'b, 's: 'b, 'p: 'b>( 51 | &'s self, 52 | prompt: &'p str, 53 | default: bool, 54 | ) -> Cow<'b, str> { 55 | if default { 56 | Borrowed(&self.colored_prompt) 57 | } else { 58 | Borrowed(prompt) 59 | } 60 | } 61 | 62 | fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { 63 | Owned("\x1b[1m".to_owned() + hint + "\x1b[m") 64 | } 65 | 66 | fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { 67 | self.highlighter.highlight(line, pos) 68 | } 69 | 70 | fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool { 71 | self.highlighter.highlight_char(line, pos, forced) 72 | } 73 | } 74 | 75 | impl Validator for MyHelper { 76 | fn validate( 77 | &self, 78 | ctx: &mut validate::ValidationContext, 79 | ) -> rustyline::Result { 80 | self.validator.validate(ctx) 81 | } 82 | 83 | fn validate_while_typing(&self) -> bool { 84 | self.validator.validate_while_typing() 85 | } 86 | } 87 | 88 | pub fn run() { 89 | let config = Config::builder() 90 | .history_ignore_space(true) 91 | .completion_type(CompletionType::List) 92 | .build(); 93 | 94 | let h = MyHelper { 95 | completer: get_command_completer(), 96 | highlighter: MatchingBracketHighlighter::new(), 97 | hinter: HistoryHinter {}, 98 | colored_prompt: "".to_owned(), 99 | validator: MatchingBracketValidator::new(), 100 | }; 101 | 102 | let mut rl = match Editor::with_config(config) { 103 | Ok(rl) => rl, 104 | Err(e) => { 105 | log::error!("{}", e); 106 | return; 107 | } 108 | }; 109 | 110 | rl.set_helper(Some(h)); 111 | 112 | if rl.load_history("/tmp/history").is_err() { 113 | println!("No previous history."); 114 | } 115 | 116 | INTERACT_STATUS.store(true, std::sync::atomic::Ordering::SeqCst); 117 | 118 | loop { 119 | let p = format!("{}> ", "interact-rs"); 120 | rl.helper_mut().expect("No helper").colored_prompt = format!("\x1b[1;32m{}\x1b[0m", p); 121 | let readline = rl.readline(&p); 122 | match readline { 123 | Ok(line) => { 124 | if line.trim_start().is_empty() { 125 | continue; 126 | } 127 | 128 | rl.add_history_entry(line.as_str()); 129 | match split(line.as_str()).as_mut() { 130 | Ok(arg) => { 131 | if arg[0] == "exit" { 132 | println!("bye!"); 133 | break; 134 | } 135 | arg.insert(0, "clisample".to_string()); 136 | run_from(arg.to_vec()) 137 | } 138 | Err(err) => { 139 | println!("{}", err) 140 | } 141 | } 142 | } 143 | Err(ReadlineError::Interrupted) => { 144 | println!("CTRL-C"); 145 | break; 146 | } 147 | Err(ReadlineError::Eof) => { 148 | println!("CTRL-D"); 149 | break; 150 | } 151 | Err(err) => { 152 | println!("Error: {:?}", err); 153 | break; 154 | } 155 | } 156 | } 157 | rl.append_history("/tmp/history") 158 | .map_err(|err| error!("{}", err)) 159 | .ok(); 160 | } 161 | -------------------------------------------------------------------------------- /src/interact/mod.rs: -------------------------------------------------------------------------------- 1 | mod cli; 2 | 3 | pub use cli::run; 4 | pub use cli::INTERACT_STATUS; 5 | -------------------------------------------------------------------------------- /src/logger/logger.rs: -------------------------------------------------------------------------------- 1 | use log::LevelFilter; 2 | use log4rs::append::console::ConsoleAppender; 3 | use log4rs::append::file::FileAppender; 4 | use log4rs::append::rolling_file::policy::compound::roll::fixed_window::FixedWindowRoller; 5 | use log4rs::append::rolling_file::policy::compound::trigger::size::SizeTrigger; 6 | use log4rs::append::rolling_file::policy::compound::CompoundPolicy; 7 | use log4rs::append::rolling_file::RollingFileAppender; 8 | use log4rs::config::{Appender, Logger, Root}; 9 | use log4rs::encode::pattern::PatternEncoder; 10 | use log4rs::Config; 11 | 12 | pub fn init_log() { 13 | let window_size = 3; // log0, log1, log2 14 | let fixed_window_roller = FixedWindowRoller::builder() 15 | .build("logs/app-{}.log", window_size) 16 | .unwrap(); 17 | let size_limit = 100 * 1024 * 1024; // 100M as max log file size to roll 18 | let size_trigger = SizeTrigger::new(size_limit); 19 | let compound_policy = 20 | CompoundPolicy::new(Box::new(size_trigger), Box::new(fixed_window_roller)); 21 | let rolling_file = RollingFileAppender::builder() 22 | .encoder(Box::new(PatternEncoder::new("{d} - {m}{n}"))) 23 | .build("logs/app.log", Box::new(compound_policy)) 24 | .unwrap(); 25 | 26 | let file_out = FileAppender::builder() 27 | .encoder(Box::new(PatternEncoder::new("{d} - {m}{n}"))) 28 | .build("logs/app.log") 29 | .unwrap(); 30 | let sys_file = FileAppender::builder() 31 | .encoder(Box::new(PatternEncoder::new("{d} - {m}{n}"))) 32 | .build("logs/sys.log") 33 | .unwrap(); 34 | let business_file = FileAppender::builder() 35 | .encoder(Box::new(PatternEncoder::new("{d} - {m}{n}"))) 36 | .build("logs/business.log") 37 | .unwrap(); 38 | 39 | let stdout = ConsoleAppender::builder().build(); 40 | 41 | let config = Config::builder() 42 | .appender(Appender::builder().build("rolling_file", Box::new(rolling_file))) 43 | .appender(Appender::builder().build("stdout", Box::new(stdout))) 44 | .appender(Appender::builder().build("file_out", Box::new(file_out))) 45 | .appender(Appender::builder().build("sys", Box::new(sys_file))) 46 | .appender(Appender::builder().build("business", Box::new(business_file))) 47 | .logger( 48 | Logger::builder() 49 | .appender("sys") 50 | .build("syslog", LevelFilter::Info), 51 | ) 52 | .logger( 53 | Logger::builder() 54 | .appender("business") 55 | .build("businesslog", LevelFilter::Info), 56 | ) 57 | .build( 58 | Root::builder() 59 | .appender("stdout") 60 | .appender("file_out") 61 | .build(LevelFilter::Info), 62 | ) 63 | .unwrap(); 64 | 65 | let _ = log4rs::init_config(config).unwrap(); 66 | } 67 | -------------------------------------------------------------------------------- /src/logger/mod.rs: -------------------------------------------------------------------------------- 1 | pub use logger::init_log; 2 | 3 | mod logger; 4 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use logger::init_log; 2 | 3 | mod cmd; 4 | mod commons; 5 | mod configure; 6 | mod interact; 7 | mod logger; 8 | mod request; 9 | mod server; 10 | 11 | fn main() { 12 | init_log(); 13 | cmd::run_app(); 14 | } 15 | -------------------------------------------------------------------------------- /src/request/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod req; 2 | mod requestmodules; 3 | mod responsepaser; 4 | 5 | pub use req::get_baidu; 6 | pub use req::Request; 7 | pub use req::Result; 8 | pub use requestmodules::{RequestTaskListAll, RequestTaskListByNodeID}; 9 | pub use responsepaser::ReqResult; 10 | -------------------------------------------------------------------------------- /src/request/req.rs: -------------------------------------------------------------------------------- 1 | use crate::request::requestmodules::{RequestTaskListAll, RequestTaskListByNodeID}; 2 | use reqwest::Client; 3 | use reqwest::Response; 4 | use serde_json::{Map, Value}; 5 | use std::fmt::Debug; 6 | use url::Url; 7 | 8 | //api path const 9 | const API_LOGIN: &str = "/login"; 10 | const API_TASK_CREATE: &str = "/api/task/create"; 11 | const API_TASK_START: &str = "/api/task/start"; 12 | const API_TASK_STOP: &str = "/api/task/stop"; 13 | const API_TASK_REMOVE: &str = "/api/task/remove"; 14 | const API_TASK_LIST_ALL: &str = "/api/task/listall"; 15 | const API_TASK_LIST_BY_IDS: &str = "/api/task/listbyids"; 16 | const API_TASK_LIST_BY_NAMES: &str = "/api/task/listbynames"; 17 | const API_TASK_LIST_BY_GROUPIDS: &str = "/task/listbygroupids"; 18 | const API_TASK_LIST_BY_NODE: &str = "/api/task/listbynode"; 19 | 20 | const API_IMPORT_FILE_PATH: &str = "/api/v2/file/createtask"; 21 | const API_NODE_LIST_ALL: &str = "/api/node/listall"; 22 | 23 | #[derive(Debug)] 24 | pub enum ResponseError { 25 | OptionError(String), 26 | } 27 | 28 | pub type Result = std::result::Result; 29 | 30 | #[derive(Default, Debug)] 31 | pub struct Request { 32 | client: reqwest::Client, 33 | server: String, 34 | } 35 | 36 | impl Request { 37 | pub fn new(server: String) -> Self { 38 | Self { 39 | client: Client::default(), 40 | server, 41 | } 42 | } 43 | 44 | pub async fn send(&self, url: Url, body: String) -> Result { 45 | let resp = self.client.post(url).body(body).send().await.map_err(|e| { 46 | return ResponseError::OptionError(e.to_string()); 47 | })?; 48 | Result::Ok(resp) 49 | } 50 | } 51 | 52 | impl Request { 53 | pub async fn create_task(&self, body: String) -> Result { 54 | let mut server = self.server.clone(); 55 | server.push_str(API_TASK_CREATE); 56 | let url = Url::parse(server.as_str()).map_err(|e| { 57 | return ResponseError::OptionError(e.to_string()); 58 | })?; 59 | let resp = self.send(url, body).await?; 60 | Result::Ok(resp) 61 | } 62 | 63 | pub async fn node_list_all(&self) -> Result { 64 | let mut server = self.server.clone(); 65 | server.push_str(API_NODE_LIST_ALL); 66 | 67 | let url = Url::parse(server.as_str()).map_err(|e| { 68 | return ResponseError::OptionError(e.to_string()); 69 | })?; 70 | let resp = self.send(url, "".to_string()).await?; 71 | Result::Ok(resp) 72 | } 73 | 74 | pub async fn task_list_all(&self, module: RequestTaskListAll) -> Result { 75 | let body = serde_json::to_string(&module).map_err(|e| { 76 | return ResponseError::OptionError(e.to_string()); 77 | })?; 78 | let mut server = self.server.clone(); 79 | server.push_str(API_TASK_LIST_ALL); 80 | let url = Url::parse(server.as_str()).map_err(|e| { 81 | return ResponseError::OptionError(e.to_string()); 82 | })?; 83 | let resp = self.send(url, body).await?; 84 | Result::Ok(resp) 85 | } 86 | 87 | pub async fn task_list_by_groupids(&self, groupids: Vec<&str>) -> Result { 88 | let mut server = self.server.clone(); 89 | server.push_str(API_TASK_LIST_BY_GROUPIDS); 90 | let url = Url::parse(server.as_str()).map_err(|e| { 91 | return ResponseError::OptionError(e.to_string()); 92 | })?; 93 | let mut map = Map::new(); 94 | map.insert("groupIDs".to_string(), Value::from(groupids)); 95 | let json = Value::Object(map); 96 | let resp = self.send(url, json.to_string()).await?; 97 | Result::Ok(resp) 98 | } 99 | 100 | pub async fn task_list_by_ids(&self, ids: Vec) -> Result { 101 | let mut server = self.server.clone(); 102 | server.push_str(API_TASK_LIST_BY_IDS); 103 | let url = Url::parse(server.as_str()).map_err(|e| { 104 | return ResponseError::OptionError(e.to_string()); 105 | })?; 106 | let mut map = Map::new(); 107 | map.insert("taskIDs".to_string(), Value::from(ids)); 108 | let json = Value::Object(map); 109 | let resp = self.send(url, json.to_string()).await?; 110 | Result::Ok(resp) 111 | } 112 | 113 | pub async fn task_list_by_names(&self, names: Vec<&str>) -> Result { 114 | let mut server = self.server.clone(); 115 | server.push_str(API_TASK_LIST_BY_NAMES); 116 | let url = Url::parse(server.as_str()).map_err(|e| { 117 | return ResponseError::OptionError(e.to_string()); 118 | })?; 119 | let mut map = Map::new(); 120 | map.insert("taskNames".to_string(), Value::from(names)); 121 | let json = Value::Object(map); 122 | let resp = self.send(url, json.to_string()).await?; 123 | Result::Ok(resp) 124 | } 125 | 126 | pub async fn task_list_by_nodeids(&self, module: RequestTaskListByNodeID) -> Result { 127 | let mut url_str = self.server.clone(); 128 | url_str.push_str(API_TASK_LIST_BY_NODE); 129 | let url = Url::parse(url_str.as_str()).map_err(|e| { 130 | return ResponseError::OptionError(e.to_string()); 131 | })?; 132 | let body = serde_json::to_string(&module).map_err(|e| { 133 | return ResponseError::OptionError(e.to_string()); 134 | })?; 135 | let resp = self.send(url, body).await?; 136 | Result::Ok(resp) 137 | } 138 | 139 | pub async fn task_remove(&self, task_id: String) -> Result { 140 | let mut server = self.server.clone(); 141 | server.push_str(API_TASK_REMOVE); 142 | let url = Url::parse(server.as_str()).map_err(|e| { 143 | return ResponseError::OptionError(e.to_string()); 144 | })?; 145 | let mut map = Map::new(); 146 | map.insert("taskID".to_string(), Value::from(task_id)); 147 | let json = Value::Object(map); 148 | let resp = self.send(url, json.to_string()).await?; 149 | Result::Ok(resp) 150 | } 151 | 152 | pub async fn task_start(&self, task_id: String) -> Result { 153 | let mut server = self.server.clone(); 154 | server.push_str(API_TASK_START); 155 | let url = Url::parse(server.as_str()).map_err(|e| { 156 | return ResponseError::OptionError(e.to_string()); 157 | })?; 158 | let mut map = Map::new(); 159 | map.insert("taskID".to_string(), Value::from(task_id)); 160 | let json = Value::Object(map); 161 | let resp = self.send(url, json.to_string()).await?; 162 | Result::Ok(resp) 163 | } 164 | 165 | pub async fn task_stop(&self, task_id: String) -> Result { 166 | let mut server = self.server.clone(); 167 | server.push_str(API_TASK_STOP); 168 | let url = Url::parse(server.as_str()).map_err(|e| { 169 | return ResponseError::OptionError(e.to_string()); 170 | })?; 171 | let mut map = Map::new(); 172 | map.insert("taskID".to_string(), Value::from(task_id)); 173 | let json = Value::Object(map); 174 | let resp = self.send(url, json.to_string()).await?; 175 | Result::Ok(resp) 176 | } 177 | } 178 | 179 | pub async fn get_baidu() -> Result { 180 | let client = Client::default(); 181 | let url = Url::parse("https://www.baidu.com").map_err(|e| { 182 | return ResponseError::OptionError(e.to_string()); 183 | })?; 184 | let resp = client.get(url).send().await.map_err(|e| { 185 | return ResponseError::OptionError(e.to_string()); 186 | })?; 187 | Result::Ok(resp) 188 | } 189 | -------------------------------------------------------------------------------- /src/request/requestmodules.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[allow(non_snake_case)] 4 | #[derive(Serialize, Deserialize, Debug, Default)] 5 | pub struct RequestTaskListAll { 6 | batchSize: usize, 7 | queryID: String, 8 | } 9 | 10 | impl RequestTaskListAll { 11 | pub fn default() -> Self { 12 | Self { 13 | batchSize: 10, 14 | queryID: "".to_string(), 15 | } 16 | } 17 | pub fn new(batch_size: usize, query_id: String) -> Self { 18 | Self { 19 | batchSize: batch_size, 20 | queryID: query_id, 21 | } 22 | } 23 | 24 | pub fn set_batch_size(&mut self, batch_size: usize) { 25 | self.batchSize = batch_size; 26 | } 27 | 28 | pub fn set_query_id(&mut self, query_id: String) { 29 | self.queryID = query_id; 30 | } 31 | } 32 | 33 | #[allow(non_snake_case)] 34 | #[derive(Serialize, Deserialize, Debug, Default)] 35 | pub struct RequestTaskListByNodeID { 36 | NodeID: String, 37 | batchSize: usize, 38 | queryID: String, 39 | } 40 | 41 | impl RequestTaskListByNodeID { 42 | pub fn default() -> Self { 43 | Self { 44 | NodeID: "".to_string(), 45 | batchSize: 10, 46 | queryID: "".to_string(), 47 | } 48 | } 49 | pub fn new(node_id: String, batch_size: usize, query_id: String) -> Self { 50 | Self { 51 | NodeID: node_id, 52 | batchSize: batch_size, 53 | queryID: query_id, 54 | } 55 | } 56 | 57 | pub fn set_node_id(&mut self, node_id: String) { 58 | self.NodeID = node_id; 59 | } 60 | 61 | pub fn set_batch_size(&mut self, batch_size: usize) { 62 | self.batchSize = batch_size; 63 | } 64 | 65 | pub fn set_query_id(&mut self, query_id: String) { 66 | self.queryID = query_id; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/request/responsepaser.rs: -------------------------------------------------------------------------------- 1 | use prettytable::{row, Cell, Row, Table}; 2 | use reqwest::Response; 3 | use serde_json::{to_string_pretty, Value}; 4 | 5 | use super::req; 6 | 7 | pub struct ReqResult { 8 | result: req::Result, 9 | } 10 | 11 | impl ReqResult { 12 | pub fn new(result: req::Result) -> Self { 13 | Self { result } 14 | } 15 | } 16 | 17 | impl ReqResult { 18 | //处理一般的response,只解析json 并打印错误 19 | pub async fn normal_parsor(self) { 20 | match self.result { 21 | Ok(resp) => match resp.text().await { 22 | Ok(body) => match serde_json::from_str::(body.clone().as_str()) { 23 | Ok(v) => match serde_json::to_string_pretty(&v) { 24 | Ok(str) => { 25 | println!("{}", str); 26 | } 27 | Err(e) => println!("{}", e), 28 | }, 29 | Err(e) => { 30 | println!("{}", e) 31 | } 32 | }, 33 | Err(e) => { 34 | println!("{}", e); 35 | } 36 | }, 37 | Err(e) => { 38 | println!("{:?}", e); 39 | } 40 | } 41 | } 42 | pub async fn task_list_all_parsor(self) { 43 | match self.result { 44 | Ok(resp) => { 45 | if let Ok(body) = resp.text().await { 46 | match serde_json::from_str::(body.clone().as_str()) { 47 | Ok(body) => { 48 | // assert!(body["errors"].is_null()); 49 | if body["errors"].is_null() { 50 | let mut table = Table::new(); 51 | table.add_row(row!["taskID", "source", "target", "status"]); 52 | // println!("taskStatus is array: {:?}", body["taskStatus"].as_array()); 53 | if let Some(valuse) = body["taskStatus"].as_array() { 54 | for iterm in valuse { 55 | let taskid = iterm["taskId"].as_str().unwrap(); 56 | let source = iterm["taskStatus"]["sourceRedisAddress"] 57 | .as_str() 58 | .unwrap(); 59 | let target = iterm["taskStatus"]["targetRedisAddress"] 60 | .as_str() 61 | .unwrap(); 62 | let status = 63 | iterm["taskStatus"]["status"].as_i64().unwrap(); 64 | table.add_row(Row::new(vec![ 65 | Cell::new(taskid), 66 | Cell::new(source), 67 | Cell::new(target), 68 | Cell::new(status.to_string().as_str()), 69 | ])); 70 | } 71 | }; 72 | 73 | // Print the table to stdout 74 | table.printstd(); 75 | println!("query ID: {}", body["queryID"]); 76 | println!("current Page: {}", body["currentPage"]); 77 | println!("is last page: {}", body["lastPage"]); 78 | } else { 79 | match serde_json::to_string_pretty(&body) { 80 | Ok(str) => { 81 | println!("{}", str); 82 | } 83 | Err(e) => println!("{}", e), 84 | } 85 | } 86 | } 87 | Err(e) => { 88 | println!("{}", e.to_string()); 89 | } 90 | } 91 | }; 92 | } 93 | Err(e) => { 94 | // error!("{:?}", e); 95 | println!("{:?}", e) 96 | } 97 | } 98 | } 99 | 100 | pub async fn task_list_byid_parsor(self) { 101 | match self.result { 102 | Ok(resp) => { 103 | // println!("{:?}", resp); 104 | if let Ok(body) = resp.text().await { 105 | match serde_json::from_str::(body.clone().as_str()) { 106 | Err(e) => { 107 | println!("{}", e.to_string()); 108 | } 109 | Ok(body) => { 110 | if let Some(tasks) = body["result"].as_array() { 111 | println!("{}", to_string_pretty(&tasks.get(0)).unwrap()); 112 | } 113 | } 114 | } 115 | } 116 | } 117 | Err(e) => { 118 | println!("{:?}", e) 119 | } 120 | } 121 | } 122 | 123 | pub async fn task_list_bynames_parsor(self) { 124 | match self.result { 125 | Ok(resp) => { 126 | if let Ok(body) = resp.text().await { 127 | match serde_json::from_str::(body.clone().as_str()) { 128 | Err(e) => { 129 | println!("{}", e.to_string()); 130 | } 131 | Ok(body) => { 132 | if let Some(tasks) = body["result"].as_array() { 133 | let mut table = Table::new(); 134 | table.add_row(row![ 135 | "taskName", "taskID", "source", "target", "status" 136 | ]); 137 | for task in tasks { 138 | if task["errors"].is_null() { 139 | let taskname = task["taskName"].as_str().unwrap(); 140 | let taskid = task["taskStatus"]["taskId"].as_str().unwrap(); 141 | let source = task["taskStatus"]["sourceRedisAddress"] 142 | .as_str() 143 | .unwrap(); 144 | let target = task["taskStatus"]["targetRedisAddress"] 145 | .as_str() 146 | .unwrap(); 147 | let status = task["taskStatus"]["status"].as_i64().unwrap(); 148 | table.add_row(Row::new(vec![ 149 | Cell::new(taskname), 150 | Cell::new(taskid), 151 | Cell::new(source), 152 | Cell::new(target), 153 | Cell::new(status.to_string().as_str()), 154 | ])); 155 | } 156 | } 157 | table.printstd(); 158 | } 159 | } 160 | } 161 | } 162 | } 163 | Err(e) => { 164 | println!("{:?}", e) 165 | } 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/server/mod.rs: -------------------------------------------------------------------------------- 1 | mod server; 2 | 3 | pub use server::start; 4 | -------------------------------------------------------------------------------- /src/server/server.rs: -------------------------------------------------------------------------------- 1 | use std::{thread, time::Duration}; 2 | 3 | pub fn start(prefix: String) { 4 | for i in 0..1000 { 5 | println!("{}", prefix.clone() + &i.to_string()); 6 | thread::sleep(Duration::from_secs(1)); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /timestamp: -------------------------------------------------------------------------------- 1 | 1691464265578 --------------------------------------------------------------------------------