开发案例
1、toto cli
三方库
- clap: 命令行解析
- dirs: 获取用户目录
1.1、安装依赖
shell
cargo add clap
cargo add dirs
1.2、cli.rs
js
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[clap(version, about)]
#[clap(propagate_version = true)]
pub struct Cli {
#[clap(subcommand)]
pub command: Commands,
}
#[derive(Debug, Subcommand)]
pub enum Commands {
#[clap(about = "Show odo info.")]
Info,
#[clap(about = "Add a todo item.")]
Add {
#[clap(help = "The item content to add.")]
content: Option<String>,
},
#[clap(about = "Remove a todo item.")]
#[clap(visible_aliases = & ["rm"])]
Remove {
#[clap(help = "The item id to remove.")]
id: Option<String>,
},
#[clap(about = "List all the todo items.")]
#[clap(visible_aliases = & ["ls", "ll", "la"])]
List,
}
1.3、commands.rs
js
use std::io;
use crate::database::{Database, Record};
pub fn info() -> Result<(), io::Error> {
println!("Todo is a simple todo list manager.");
Ok(())
}
pub fn add(db: &mut Database, content: Option<String>) -> Result<(), io::Error> {
if let Some(content) = content {
let records = db.read_records();
db.add_record(&Record {
id: records.len() as u32 + 1,
content: content.clone(),
})?;
println!("📝 Item added: {}", content);
Ok(())
} else {
eprintln!("You need to specify the content of the todo item.");
std::process::exit(1);
}
}
pub fn remove(db: &mut Database, id: Option<String>) -> Result<(), io::Error> {
if id.is_none() {
println!("You need to specify the id of the todo item.");
std::process::exit(1);
}
println!("Removing a todo item: {}", id.clone().unwrap());
db.remove_record(id.unwrap().parse::<u32>().unwrap())?;
println!(" ❌ Item removed!\n");
Ok(())
}
pub fn list(db: &mut Database) -> Result<(), io::Error> {
let records = db.read_records();
if records.is_empty() {
eprintln!("No records. You can add one with `todo add [content]`");
std::process::exit(1);
}
for record in records {
println!(" ⬜️ {}: {}", record.id, record.content);
}
Ok(())
}
1.4、database.rs
js
use std::fs::{File, OpenOptions};
use std::io::{self, BufRead, BufReader, Seek, Write};
use crate::utils::{check_db_file, get_db_file_path};
pub struct Record {
pub id: u32,
pub content: String,
}
pub struct Database {
file: File,
}
// 解析记录
pub fn parse_record_line(line: &str) -> Record {
let fields: Vec<&str> = line.split(',').collect();
// 处理空行的情况
if fields.len() == 1 {
return Record {
id: 0,
content: "".to_string(),
};
}
let content = fields[1..].join(",");
Record {
id: fields[0].parse::<u32>().unwrap(),
content,
}
}
impl Database {
pub fn open() -> Database {
// 先检查 db 文件是否存在,不存在就创建
check_db_file().unwrap();
// 获取db文件路径
let db_file = get_db_file_path();
let file = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.open(db_file)
.unwrap();
Database { file }
}
pub fn add_record(&mut self, record: &Record) -> Result<(), io::Error> {
let line = format!("{},{}", record.id, record.content);
writeln!(self.file, "{}", line)
}
pub fn read_records(&mut self) -> Vec<Record> {
let reader = BufReader::new(&self.file);
reader
.lines()
.map_while(Result::ok)
.filter(|line| !line.is_empty())
.map(|line| parse_record_line(&line))
.collect()
}
pub fn remove_record(&mut self, id: u32) -> Result<(), io::Error> {
let reader = BufReader::new(&self.file);
let mut lines = reader.lines().enumerate();
// 根据id找出对应的行
let line = lines.find(|(_, line)| {
let record = parse_record_line(line.as_ref().unwrap());
record.id == id
});
match line {
Some((i, _)) => {
// 过滤掉对应的行,这里使用的对应 api 可以查看 Rust 标准库
let new_contents = lines
.filter(|(j, _)| *j != i)
.map(|(_, line)| line.unwrap())
.collect::<Vec<_>>()
.join("\n");
// 这里使用了 std::io::Seek,需要导入
self.file.seek(std::io::SeekFrom::Start(0)).unwrap();
self.file.write_all(new_contents.as_bytes()).unwrap();
self.file.set_len(new_contents.len() as u64).unwrap();
// println!(" ❌ Item removed!\n");
Ok(())
}
None => Err(io::Error::new(
io::ErrorKind::Other,
format!("No such record: {}", id),
)),
}
}
}
1.5、utils.rs
js
use std::{path, fs, io};
use dirs::home_dir;
pub const DB_FILE: &str = ".todo_db";
// 获取db文件路径
pub fn get_db_file_path() -> path::PathBuf {
home_dir().map(|it| it.join(DB_FILE)).unwrap_or_default()
}
// 检查db文件是否存在
pub fn db_exists() -> bool {
let dir = get_db_file_path();
fs::metadata(&dir).is_ok()
}
// 创建db
pub fn create_db_file() -> io::Result<()> {
let dir = get_db_file_path();
fs::File::create(dir)?;
Ok(())
}
// 检查db文件是否存在,不存在就创建
pub fn check_db_file() -> io::Result<()> {
if !db_exists() {
create_db_file()?;
}
Ok(())
}
1.6、main.rs
js
mod cli;
mod commands;
mod database;
mod utils;
use clap::Parser;
use cli::{Cli, Commands};
use database::Database;
// cargo run add -h
fn main() {
let args = Cli::parse();
let mut db = Database::open();
// 匹配命令
let result = match args.command {
Commands::Info => commands::info(),
Commands::Add { content } => commands::add(&mut db, content),
Commands::Remove { id } => commands::remove(&mut db, id),
Commands::List => commands::list(&mut db),
};
// 统一处理错误
if let Err(err) = result {
eprintln!("\x1b[31merror:\x1b[39m {}", err);
std::process::exit(1);
}
}
1.7、安装使用
安装
shell
cargo install
使用
shell
todo-list add -h
2、grep from file
2.1、lib.rs
js
use std::{env, error, fs};
#[derive(Debug)]
pub struct Config {
pub query: String, // 查询关键词
pub filename: String, // 文件名
pub case_sensitive: bool // 是否区分大小写,默认区分
}
impl Config {
pub fn new(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let filename = args[2].clone();
let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
Ok(Config { query, filename, case_sensitive })
}
}
pub fn run(config: Config) -> Result<(), Box<dyn error::Error>> {
let contents = fs::read_to_string(config.filename)?;
let result;
if config.case_sensitive {
result = search(&config.query, &contents)
} else {
result = search_case_insensitive(&config.query, &contents)
}
for line in result {
println!("{}", line);
}
Ok(())
}
// 区分大小写
pub fn search<'a>(query: &'a str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}
// 忽略大小写
pub fn search_case_insensitive<'a>(query: &'a str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
let query = query.to_lowercase();
for line in contents.lines() {
if line.to_lowercase().contains(&query) {
results.push(line);
}
}
results
}
2.2、main.rs
js
use std::{env, process};
use grep_cli::{Config, run};
fn main() {
let args: Vec<String> = env::args().collect();
let config = Config::new(&args).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {}", err);
process::exit(1);
});
if let Err(e) = run(config) {
eprintln!("Application error: {}", e);
process::exit(1);
}
}
3、CaCher(缓存, 有问题)
js
struct CaCher<T,>
where
T: Fn(u32) -> u32,
{
cb: T,
value: Option<u32>,
}
impl<T> CaCher<T>
where
T: Fn(u32) -> u32,
{
fn new(cb: T) -> Self {
Self {
cb,
value: None,
}
}
fn value(&mut self, arg: u32) -> u32 {
match self.value {
Some(v) => v,
None => {
let v = (self.cb)(arg);
self.value = Some(v);
v
}
}
}
}
fn main() {
let mut cacher = CaCher::new(|num| {
num + 1
});
let result = cacher.value(1);
let result2 = cacher.value(2);
println!("{}-{}", result, result2);
}
4、博客发布
js
pub struct Post {
state: Option<Box<dyn State>>,
content: String,
}
impl Post {
pub fn new() -> Post {
Post {
state: Some(Box::new(Draft {})),
content: String::new(),
}
}
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
pub fn content(&self) -> &str {
self.state.as_ref().unwrap().content(self)
}
pub fn request_review(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.request_review())
}
}
pub fn approve(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.approve())
}
}
}
trait State {
fn request_review(self: Box<Self>) -> Box<dyn State>;
fn approve(self: Box<Self>) -> Box<dyn State>;
fn content<'a>(&self, post: &'a Post) -> &'a str {
""
}
}
struct Draft {}
impl State for Draft {
fn request_review(self: Box<Self>) -> Box<dyn State> {
Box::new(PendingReview {})
}
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
}
struct PendingReview {}
impl State for PendingReview {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
fn approve(self: Box<Self>) -> Box<dyn State> {
Box::new(Published {})
}
}
struct Published {}
impl State for Published {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
fn content<'a>(&self, post: &'a Post) -> &'a str {
&post.content
}
}
pub fn generate_post() -> Post {
Post::new()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn post() {
let mut post = generate_post();
post.add_text("I ate a salad for lunch today");
post.request_review();
post.approve();
assert_eq!("I ate a salad for lunch today", post.content());
}
}
5、进度条
js
use std::io::{stdout, Result, Write};
use std::time::Duration;
use std::thread::sleep;
fn main() -> Result<()> {
const BAR_LAB: &str = "-\\|/";
for per in 0..101 {
print!("\r {} \u{1b}[42m{}\u{1b}[0m [ {}% ] ",
BAR_LAB.chars().nth(per % 4).unwrap(),
" ".repeat(per / 2),
per
);
stdout().flush()?;
sleep(Duration::from_micros(60_000));
}
println!();
Ok(())
}
6、转json
6.1、字符串转json
js
use serde_json::Value;
fn main() {
let json = r#"
{
"article": "how to work with json in Rust",
"author": "tdep",
"paragraph": [
{
"name": "untyped"
},
{
"name": "strongly typed"
},
{
"name": "writing json"
}
]
}
"#;
let parsed: Value = serde_json::from_str(json).unwrap();
println!("{}-{}", parsed, parsed["article"]);
}
6.2、字符串转结构体
js
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct Paragraph {
name: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct Article {
article: String,
author: String,
paragraph: Vec<Paragraph>,
}
fn main() {
let json = r#"
{
"article": "how to work with json in Rust",
"author": "tdep",
"paragraph": [
{
"name": "untyped"
},
{
"name": "strongly typed"
},
{
"name": "writing json"
}
]
}
"#;
let parsed: Article = serde_json::from_str(json).unwrap();
println!("{:?}", parsed);
}
6.3、结构体转字符串
js
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct Paragraph {
name: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct Article {
article: String,
author: String,
paragraph: Vec<Paragraph>,
}
fn main() {
let article: Article = Article {
article: String::from("article name"),
author: String::from("author name"),
paragraph: vec![
Paragraph {
name: String::from("paragraph 1")
},
Paragraph {
name: String::from("paragraph 2")
}
],
};
let json = serde_json::to_string(&article).unwrap();
println!("{}", json);
}
7、泛型
7.1、最大值泛型
js
fn largest_number(arr: &[i32]) -> i32 {
let mut largest = arr[0];
for index in 1..arr.len() {
if arr[index] > largest {
largest = arr[index];
}
}
largest
}
fn largest_char(arr: &[char]) -> char {
let mut largest = arr[0];
for index in 1..arr.len() {
if arr[index] > largest {
largest = arr[index];
}
}
largest
}
fn largest<T: std::cmp::PartialOrd + Copy>(arr: &[T]) -> T {
let mut largest = arr[0];
for index in 1..arr.len() {
if arr[index] > largest {
largest = arr[index];
}
}
largest
}
7.2、冒泡
8、端口嗅探器
js
use std::{env, net::{IpAddr, TcpStream}, str::FromStr, process, thread, sync::mpsc::Sender, io::{self, Write}};
const MAX_PORT: u16 = 65535;
#[derive(Debug)]
struct Arguments {
flag: String,
ip_addr: String,
threads: u16
}
impl Arguments {
fn new(args: &[String]) -> Result<Arguments, &'static str> {
if args.len() < 2 {
return Err("not enough arguments");
} else if args.len() > 4 {
return Err("too many arguments");
}
let flag = args[1].clone();
println!("flag: {}", flag);
if let Ok(ip_addr) = IpAddr::from_str(&flag) {
return Ok(Arguments {flag, ip_addr: ip_addr.to_string(), threads: 4});
} else {
// let flag = args[1].clone();
if flag.contains("-h") || flag.contains("-help") && args.len() == 2 {
println!("Usage: -j to select how many threads you want");
println!("Usage: -h or -help to show this help message");
return Err("help");
} else if flag.contains("-h") || flag.contains("-help") {
return Err("too many arguments");
} else if flag.contains("-j") {
let ip_addr = match IpAddr::from_str(&args[3]) {
Ok(ip) => ip,
Err(_) => return Err("invalid IP address")
};
let threads = match args[2].parse::<u16>() {
Ok(t) => t,
Err(_) => return Err("invalid thread number")
};
return Ok(Arguments {flag, ip_addr: ip_addr.to_string(), threads});
} else {
return Err("invalid argument");
}
}
}
}
fn main() {
let args: Vec<String> = env::args().collect();
let program = args[0].clone();
let arguments = Arguments::new(&args).unwrap_or_else(|err| {
if err.contains("-h") || err.contains("-help") {
print!("{}", err);
process::exit(0)
} else {
println!("{} Problem parsing arguments: {}", program, err);
process::exit(0)
}
});
println!("{:#?}", arguments);
let num_threads = arguments.threads;
let (tx, rx) = std::sync::mpsc::channel::<u16>();
for i in 0..num_threads {
let tx = tx.clone();
let addr = arguments.ip_addr.clone();
thread::spawn(move || {
scan(tx, i, addr, num_threads);
});
}
let mut out = vec![];
drop(tx);
for p in rx {
out.push(p);
}
println!();
out.sort();
for v in out {
println!("{} is open", v);
}
}
fn scan(tx: Sender<u16>, start_port: u16, addr: String, num_threads: u16) {
let mut port: u16 = start_port + 1;
loop {
match TcpStream::connect(format!("{}:{}", addr, port)) {
Ok(_) => {
println!("{}:{}", addr, port);
io::stdout().flush().unwrap();
tx.send(port).unwrap();
},
Err(_) => {}
}
if (MAX_PORT - port) <= num_threads {
break;
}
port += num_threads;
}
}