Skip to content

Commit

Permalink
Refractor: use enum_dispatch to remove dumplicate code
Browse files Browse the repository at this point in the history
  • Loading branch information
Firstero committed Apr 29, 2024
1 parent d86d9bf commit e6d2332
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 175 deletions.
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ clap = { version = "4.5.4", features = ["derive"] }
csv = "1.3.0"
ed25519 = "2.2.3"
ed25519-dalek = { version = "2.1.1", features = ["rand_core"] }
enum_dispatch = "0.3.13"
rand = "0.8.5"
serde = { version = "1.0.198", features = ["derive"] }
serde_json = "1.0.116"
Expand Down
125 changes: 24 additions & 101 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,103 +1,26 @@
# Geektime Rust 语言训练营

## 环境设置

### 安装 Rust

```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```

### 安装 VSCode 插件

- crates: Rust 包管理
- Even Better TOML: TOML 文件支持
- Better Comments: 优化注释显示
- Error Lens: 错误提示优化
- GitLens: Git 增强
- Github Copilot: 代码提示
- indent-rainbow: 缩进显示优化
- Prettier - Code formatter: 代码格式化
- REST client: REST API 调试
- rust-analyzer: Rust 语言支持
- Rust Test lens: Rust 测试支持
- Rust Test Explorer: Rust 测试概览
- TODO Highlight: TODO 高亮
- vscode-icons: 图标优化
- YAML: YAML 文件支持

### 安装 cargo generate

cargo generate 是一个用于生成项目模板的工具。它可以使用已有的 github repo 作为模版生成新的项目。

```bash
cargo install cargo-generate
```

在我们的课程中,新的项目会使用 `tyr-rust-bootcamp/template` 模版生成基本的代码:

```bash
cargo generate tyr-rust-bootcamp/template
```

### 安装 pre-commit

pre-commit 是一个代码检查工具,可以在提交代码前进行代码检查。

```bash
pipx install pre-commit
```

安装成功后运行 `pre-commit install` 即可。

### 安装 Cargo deny

Cargo deny 是一个 Cargo 插件,可以用于检查依赖的安全性。

```bash
cargo install --locked cargo-deny
```

### 安装 typos

typos 是一个拼写检查工具。

```bash
cargo install typos-cli
```

### 安装 git cliff

git cliff 是一个生成 changelog 的工具。

```bash
cargo install git-cliff
```

### 安装 cargo nextest

cargo nextest 是一个 Rust 增强测试工具。

```bash
cargo install cargo-nextest --locked
```



### 1.12 Http 文件服务器
- 简要了解 Rust 处理 web 相关的库:
- tokio
- hyper, reqwest, axum
- tower, tower-http
- 构建 CLI
rcli http serve -d . => serve 当前目录的文件
- 使用 axum 构建一个简单的 web 服务器
- 使用 axum 和 tokio fs

### 1.13 tower-http service
- 简要了解 tower-http
- 使用 tower-http ServeDir service
- 集成 ServeDir service 至 axum


### 1.14 重构 CLI
## Rcli 开发

### V1-7 rcli csv 添加csv 转换
- ```rcli csv --input in.csv --output out.json --format json```
- ```rcli csv --input in.csv --output out.yaml --format yaml```
- ```rcli csv --header --delimiter , --input in.csv --output out.yaml --format yaml```
### V1-8 rcli genpass
- ```rcli genpass -l 32 --no-lower --no-lower --no-symbol --no-number```
### V1-9 rcli base64
- ```rcli base64 encode --format nopadding/standard/urlsafe --input textfile```
- ```rcli base64 decode --format nopadding/standard/urlsafe --input textfile```
### V1-10/11 rcli text 加密解密
- ```rcli text sign --format blake3 --key keyfile --input textfile```
- ```rcli text verify --format blake3 --key keyfile --input textfile --sig signature```
- ```rcli text generate --format blake3 --output keyfile```
- ```rcli text encrypt --key keyfile --input textfile --nonce noncefile```
- ```rcli text decrypt --key keyfile --input textfile --nonce noncefile```
### V1-12/13 rcli http serve(default dir is current dir, default port is 8080)
- ```rcli http serve```
- ```rcli http serve --dir /tmp --port 8080```

### V1-14 重构 CLI

### V1-15 作业
2 changes: 1 addition & 1 deletion src/cli/csv_opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub struct CsvOpts {
pub delimiter: char,
#[arg(short, long, required = true, value_parser=parse_input_file)]
pub input: String,
#[arg(short, long)] // default_value_t = "output.json".into()
#[arg(short, long)]
pub output: Option<String>,
#[arg(long, default_value = "json", value_parser=OutputFormat::from_str)]
pub format: OutputFormat,
Expand Down
12 changes: 9 additions & 3 deletions src/cli/http_serve.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use clap::Parser;
use enum_dispatch::enum_dispatch;
use std::path::PathBuf;

use crate::{process_http_serve, CmdExcutor};
Expand All @@ -11,6 +12,7 @@ pub struct HttpOpts {
}

#[derive(Debug, Parser)]
#[enum_dispatch(CmdExcutor)]
pub enum HttpSubCommand {
#[command(name = "serve", about = "Serve a directory via HTTP")]
Serve(HttpServeOpts),
Expand All @@ -24,10 +26,14 @@ pub struct HttpServeOpts {
pub port: u16,
}

impl CmdExcutor for HttpServeOpts {
async fn execute(self) -> Result<()> {
process_http_serve(self.dir, self.port).await
}
}

impl CmdExcutor for HttpOpts {
async fn execute(self) -> Result<()> {
match self.subcmd {
HttpSubCommand::Serve(opts) => process_http_serve(opts.dir, opts.port).await,
}
self.subcmd.execute().await
}
}
29 changes: 9 additions & 20 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,30 @@ mod http_serve;
mod text;

use anyhow::Result;
use enum_dispatch::enum_dispatch;
use std::fs;
use std::path::{Path, PathBuf};

use crate::CmdExcutor;

pub use self::base64_opts::{Base64Format, Base64Opts, Base64SubCommand};
pub use self::base64_opts::{Base64Format, Base64Opts};
pub use self::csv_opts::{CsvOpts, OutputFormat};
pub use self::genpass_opts::GenpassOpts;
pub use self::http_serve::{HttpOpts, HttpSubCommand};
pub use self::text::{TextOpts, TextSignFormat, TextSubCommand};
pub use self::http_serve::{HttpOpts, HttpServeOpts, HttpSubCommand};
pub use self::text::{
TextDecryptOpts, TextEncryptOpts, TextKeyGenerateOpts, TextOpts, TextSignFormat, TextSignOpts,
TextSubCommand, TextVerifyOpts,
};

use clap::Parser;

#[derive(Debug, Parser)]
#[command(name = "rcli", version, about, author, long_about=None)]
#[command(name = "rcli", version="0.1.0", about="A Rust Command Line Tool", author="Firstero", long_about=None)]
pub struct Opts {
#[command(subcommand)]
pub subcmd: SubCommand,
}

#[derive(Debug, Parser)]
#[enum_dispatch(CmdExcutor)]
pub enum SubCommand {
// rcli csv --header xx -delimiter , -input /tmp/1.csv -output output.json
#[command(name = "csv", about = "csv file processor")]
Expand All @@ -44,7 +47,6 @@ pub enum SubCommand {
HttpServe(HttpOpts),
}

// 模块级别的函数,共享input file的解析逻辑
pub fn parse_input_file(path: &str) -> Result<String, String> {
if path == "-" || fs::metadata(path).is_ok() {
Ok(path.to_string())
Expand All @@ -62,19 +64,6 @@ pub fn verify_dir(path: &str) -> Result<PathBuf, &'static str> {
}
}

impl CmdExcutor for SubCommand {
async fn execute(self) -> Result<()> {
match self {
SubCommand::Csv(opts) => opts.execute().await,
SubCommand::Genpass(opts) => opts.execute().await,
SubCommand::Base64(opts) => opts.execute().await,
SubCommand::HttpServe(opts) => opts.execute().await,
SubCommand::Text(opts) => opts.execute().await,
}
}
}

// 单元测试
#[cfg(test)]
mod tests {
use super::*;
Expand Down
25 changes: 6 additions & 19 deletions src/cli/text.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, read::DecoderReader, Engine};
use clap::Parser;
use enum_dispatch::enum_dispatch;
use std::{fmt::Display, path::PathBuf, str::FromStr};
use tokio::fs;

Expand All @@ -16,6 +17,7 @@ pub struct TextOpts {
}

#[derive(Debug, Parser)]
#[enum_dispatch(CmdExcutor)]
pub enum TextSubCommand {
#[command(name = "sign", about = "Sign text with private/shared key.")]
Sign(TextSignOpts),
Expand Down Expand Up @@ -141,19 +143,10 @@ impl CmdExcutor for TextVerifyOpts {
impl CmdExcutor for TextKeyGenerateOpts {
async fn execute(self) -> Result<()> {
let keys = process_generate(self.format)?;
match self.format {
TextSignFormat::Blake3 => {
let name = self.output.join("blake3.key");
let keys = process_generate(self.format)?;
fs::write(name, &keys[0]).await?;
Ok(())
}
TextSignFormat::Ed25519 => {
fs::write(self.output.join("ed25519.sk"), &keys[0]).await?;
fs::write(self.output.join("ed25519.pk"), &keys[1]).await?;
Ok(())
}
for (k, v) in keys {
fs::write(self.output.join(k), &v).await?;
}
Ok(())
}
}

Expand Down Expand Up @@ -182,12 +175,6 @@ impl CmdExcutor for TextDecryptOpts {

impl CmdExcutor for TextOpts {
async fn execute(self) -> Result<()> {
match self.subcmd {
TextSubCommand::Sign(opts) => opts.execute().await,
TextSubCommand::Verify(opts) => opts.execute().await,
TextSubCommand::Generate(opts) => opts.execute().await,
TextSubCommand::Encrypt(opts) => opts.execute().await,
TextSubCommand::Decrypt(opts) => opts.execute().await,
}
self.subcmd.execute().await
}
}
14 changes: 5 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,13 @@ mod process;
mod utils;

use anyhow::Result;
pub use cli::{
parse_input_file, verify_dir, Base64SubCommand, HttpSubCommand, Opts, SubCommand,
TextSignFormat, TextSubCommand,
};
pub use process::{
process_b64decode, process_b64encode, process_csv, process_decrypt, process_encrypt,
process_generate, process_genpass, process_http_serve, process_sign, process_verify,
};
pub use utils::{get_content, get_reader};
pub use cli::*;
use enum_dispatch::enum_dispatch;
pub use process::*;
pub use utils::*;

#[allow(async_fn_in_trait)]
#[enum_dispatch]
pub trait CmdExcutor {
async fn execute(self) -> Result<()>;
}
25 changes: 19 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,25 @@ use clap::Parser;

use rcli::{CmdExcutor, Opts};

// usage:
// 使用 rcli -- csv --input input.csv --output output.csv --format json 进行csv转换
// 使用 rcli -- genpass -l 32 --no-lower --no-lower --no-symbol 进行密码生成
// 使用 rcli -- base64 encode/decode --format nopadding/urlsafe/standard 进行base64编码/解码
// 使用 rcli -- text sign/verify/generate --format blake3/ed25519 进行文本签名/验证
// 使用 rcli -- http serve . --port 8080 进行http服务
/// usage:
/// - rcli csv
/// - ```rcli csv --input in.csv --output out.json --format json```
/// - ```rcli csv --input in.csv --output out.yaml --format yaml```
/// - ```rcli csv --header --delimiter , --input in.csv --output out.yaml --format yaml```
/// - rcli genpass
/// - ```rcli genpass -l 32 --no-lower --no-lower --no-symbol --no-number```
/// - rcli base64
/// - ```rcli base64 encode --format nopadding/standard/urlsafe --input textfile```
/// - ```rcli base64 decode --format nopadding/standard/urlsafe --input textfile```
/// - rcli text
/// - ```rcli text sign --format blake3 --key keyfile --input textfile```
/// - ```rcli text verify --format blake3 --key keyfile --input textfile --sig signature```
/// - ```rcli text generate --format blake3 --output keyfile```
/// - ```rcli text encrypt --key keyfile --input textfile --nonce noncefile```
/// - ```rcli text decrypt --key keyfile --input textfile --nonce noncefile```
/// - rcli http serve(default dir is current dir, default port is 8080)
/// - ```rcli http serve```
/// - ```rcli http serve --dir /tmp --port 8080```
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
Expand Down
Loading

0 comments on commit e6d2332

Please sign in to comment.