網站首頁 編程語言 正文
一個迷你 todo 應用
該文章將使用 Rust 從零去做一個入門級別的 TODO 命令行應用
你將學會什么?
- 基本的命令行操作
- 文件讀寫和文件結構組織
我們將會通過命令行輸入來實現對根目錄下 state.json
文件的編輯,如:
cargo run create 買菜
cargo run get 買菜
cargo run delete 買菜
cargo run edit 買菜
我們的 todo 狀態會有 pending 和 done 這兩種,create 操作將創建一個 {"買菜":"pending"} 的狀態,edit 操作主要就是將 pending 狀態轉變為 done 狀態
需要安裝的依賴
我們要操作 json 數據結構,所以要安裝下面這個 crate
[package] edition = "2021" name = "todo_app" version = "0.1.0" [dependencies] serde_json = {default-feature = false, version = "1.0", feature = ["alloc"]}
文件目錄組織
| main.rs 主文件 | process.rs 處理 todo 命令行輸入 | state.rs 讀寫文件 | \---todo | mod.rs 類似于 js 里面的 index.js | \---structs struct 數據結構組織 | base.rs 基礎參數的數據結構 | done.rs 完成狀態的數據結構 | mod.rs 類似于 js 里面的 index.js | pending.rs 處理中狀態的數據結構 | \---traits (特征)類似于 ts 里面的 interface create.rs 創建操作 delete.rs 刪除操作 edit.rs 編輯操作 get.rs 查詢操作 mod.rs 類似于 js 里面的 index.js
主文件
// 有一點點像 import process from './process.rs' mod process; mod state; mod todo; // 使用外部的庫、標準庫或自己定義的工具 use process::process_input; use serde_json::Map; use serde_json::Value; use state::read_file; use std::env; use todo::todo_factory; fn main() { // 收集所有命令行的參數,轉換成數組 let args: Vec<String> = env::args().collect(); // 拿到第一個參數如 get、delete、edit、create let command: &String = &args[1]; // 第二個參數是用來記錄事件的 let title: &String = &args[2]; // 讀取根目錄的 state.json 文件并轉換成 map json 結構 let state: Map<String, Value> = read_file("./state.json"); // 如果事件已經存取過了,那就直接獲取,沒有就將其狀態設置為 pending let status: String; match &state.get(title) { Some(result) => status = result.to_string().replace('\"', ""), None => status = "pending".to_string(), } // todo_factory 工廠函數用于處理 pending 和 done 狀態的輸入 let item = todo_factory(&status, title).expect(&status); // 將狀態輸入到本地文件中 process_input(item, command.to_string(), &state); }
上面我們看到了 3 個主要我們需要自己編寫的函數:
- read_file 讀取文件
- todo_factory 狀態工廠函數
- process_input 處理輸入
接下來讓我們一個一個來看下這幾個函數
讀取文件
read_file 函數位于 src/state.rs
路徑下,該文件主要是用來存儲狀態操作的,里面包含讀取和寫入兩個函數,讓我們主要看下 read_file 這個函數,它的功能:
- 打開文件
- 讀取文件
- 將讀取到的文件內容轉成 json 對象
- 返回讀取到的 json 對象
use std::fs::{self, File}; use std::io::Read; use serde_json::json; use serde_json::value::Value; use serde_json::Map; /** 讀取文件 */ pub fn read_file(file_name: &str) -> Map<String, Value> { // 打開文件 let mut file = File::open(file_name).unwrap(); // 創建一個 string buffer 用于存儲數據 let mut data = String::new(); // 讀取數據寫入到 buffer file.read_to_string(&mut data).unwrap(); // 將讀取到的字符串轉換成 json 文本格式 Value 類型 let json: Value = serde_json::from_str(&data).unwrap(); // 將 json 文本格式轉換成 json 對象 let state: Map<String, Value> = json.as_object().unwrap().clone(); // 將這個對象返回出去 return state; } /** 寫入文件 */ pub fn write_to_file(file_name: &str, state: &mut Map<String, Value>) { // json! 這個宏用于將 json 字面量對象轉換回 json 文本 Value 格式 let new_data = json!(state); // 將 json 文本寫入到文件中 fs::write(file_name, new_data.to_string()).expect("unable to write to file"); }
狀態處理工廠函數
看完文件讀取操作我們再來看下第二個主要函數 todo_factory,這個函數主要是根據事件的狀態和通過命令行輸入的 title 事件名稱,然后構建出一個相應的數據結構
該文件位于 src/todo/mod.rs
路徑下
它的作用主要是根據輸入的 pending/done 狀態,然后創建出一個對應的數據結構
// 將 structs 向外部導出 pub mod structs; use structs::done::Done; use structs::pending::Pending; // 創建一個包含 pending 和 done 狀態的枚舉 pub enum ItemTypes { Pending(Pending), Done(Done), } /** 狀態處理工廠函數 */ pub fn todo_factory(item_type: &str, item_title: &str) -> Result<ItemTypes, &'static str> { match item_type { "pending" => { // 創建一個 pending 狀態的數據結構,然后返回出去 let pending_item = Pending::new(item_title); Ok(ItemTypes::Pending(pending_item)) } "done" => { // 創建一個 done 狀態的數據結構,然后返回出去 let done_item = Done::new(item_title); Ok(ItemTypes::Done(done_item)) } // 如果不是這兩個狀態就返回一個錯誤 _ => Err("This is not accepted!"), } }
從上面的代碼中我們可以看到下面這兩行代碼,這是我們主要需要定義的兩個狀態數據結構,他們位于 src/todo/structs
路徑下
use structs::done::Done; use structs::pending::Pending;
不過在看上面兩個數據結構之前,我們需要先來看下另一個基礎的數據結構,就是 base,因為上面兩個數據結構是基于 base 的,實現一個類似于面向對象語言里繼承的做法,base 是它們倆的基類
該文件位于 src/todo/structs/base.rs
路徑下
pub struct Base { pub title: String, pub status: String, } // 為這個 Base 數據結構實現一個 new 方法,并返回一個實例化后的數據結構 impl Base { pub fn new(input_title: &str, input_status: &str) -> Base { Base { title: input_title.to_string(), status: input_status.to_string(), } } }
現在我們用 Pending 和 Done 兩個 struct 來 "繼承" Base
該文件位于 src/todo/structs/pending.rs
路徑下
use super::base::Base; pub struct Pending { pub super_struct: Base, } impl Pending { pub fn new(input_title: &str) -> Pending { Pending { super_struct: Base::new(input_title, "pending"), } } }
該文件位于 src/todo/structs/done.rs
路徑下
use super::base::Base; pub struct Done { pub super_struct: Base, } impl Done { pub fn new(input_title: &str) -> Done { Done { super_struct: Base::new(input_title, "done"), } } }
Trait(特征)
Trait 類似于 TS 里的 interface 接口,現在我們要為我們的 struct 來實現一些在 Trait 里面定義的方法,文件路徑在 src/todo/structs/traits
Create trait
文件路徑在 src/todo/structs/traits/create
use serde_json::{json, Map, Value}; use crate::state::write_to_file; pub trait Create { // 為這個 trait 實現一個默認的 create 方法 fn create(&self, title: &String, status: &String, state: &mut Map<String, Value>) { state.insert(title.to_string(), json!(status)); write_to_file("./state.json", state); println!("\n\n{} is being created\n\n", title); } }
Get trait
文件路徑在 src/todo/structs/traits/get
use serde_json::{Map, Value}; pub trait Get { fn get(&self, title: &String, state: &Map<String, Value>) { let item = state.get(title); match item { Some(result) => { println!("\n\nItem: {}", title); println!("Status: {} \n\n", result); } None => println!("item: {} was not find", title), } } }
Delete trait
文件路徑在 src/todo/structs/traits/delete
use serde_json::{Map, Value}; use crate::state::write_to_file; pub trait Delete { fn delete(&self, title: &String, state: &mut Map<String, Value>) { state.remove(title); write_to_file("./state.json", state); println!("\n\n{} is being deleted\n\n", title); } }
Edit trait
該 Trait 主要實現兩個方法,一個是將事件設置為 pending 狀態,一個將事件設置為 done 狀態
文件路徑在 src/todo/structs/traits/edit
use serde_json::{json, Map, Value}; use crate::state::write_to_file; pub trait Edit { fn set_to_done(&self, title: &String, state: &mut Map<String, Value>) { state.insert(title.to_string(), json!("done".to_string())); write_to_file("./state.json", state); println!("\n\n{} is being set to done\n\n", title); } fn set_to_pending(&self, title: &String, state: &mut Map<String, Value>) { state.insert(title.to_string(), json!("pending".to_string())); write_to_file("./state.json", state); println!("\n\n{} is being set to pending\n\n", title); } }
導出 trait
文件路徑在 src/todo/structs/traits/mod
pub mod create; pub mod delete; pub mod edit; pub mod get;
為 struct 實現 trait
接下來我們為 Pending 和 Done 兩個 struct 來實現這幾個 trait
Pending
我們為 Pending 實現所有的 4 個 trait
use super::base::Base; use super::traits::create::Create; use super::traits::delete::Delete; use super::traits::edit::Edit; use super::traits::get::Get; pub struct Pending { pub super_struct: Base, } impl Pending { pub fn new(input_title: &str) -> Pending { Pending { super_struct: Base::new(input_title, "pending"), } } } impl Delete for Pending {} impl Create for Pending {} impl Edit for Pending {} impl Get for Pending {}
Done
為 Done 實現除了 Create 以外的 trait
文件路徑在 src/todo/structs/done
use super::base::Base; use super::traits::delete::Delete; use super::traits::edit::Edit; use super::traits::get::Get; pub struct Done { pub super_struct: Base, } impl Done { pub fn new(input_title: &str) -> Done { Done { super_struct: Base::new(input_title, "done"), } } } impl Delete for Done {} impl Edit for Done {} impl Get for Done {}
導出 struct
mod base; pub mod done; pub mod pending; pub mod traits;
Process 輸入處理
文件位于 src/process.rs
路徑下
use serde_json::{Map, Value}; use crate::todo::{ structs::{ done::Done, pending::Pending, traits::{create::Create, delete::Delete, edit::Edit, get::Get}, }, ItemTypes, }; // 處理 pending 狀態 fn process_pending(item: Pending, command: String, state: &Map<String, Value>) { let mut state = state.clone(); // 根據用戶的輸入來調用不同的方法 match command.as_str() { "get" => item.get(&item.super_struct.title, &state), "create" => item.create( &item.super_struct.title, &item.super_struct.status, &mut state, ), "delete" => item.delete(&item.super_struct.title, &mut state), "edit" => item.set_to_done(&item.super_struct.title, &mut state), _ => println!("command: {} is not supported", command), } } // 處理 done 狀態 fn process_done(item: Done, command: String, state: &Map<String, Value>) { let mut state = state.clone(); match command.as_str() { "get" => item.get(&item.super_struct.title, &state), "delete" => item.delete(&item.super_struct.title, &mut state), "edit" => item.set_to_pending(&item.super_struct.title, &mut state), _ => println!("command: {} is not supported", command), } } // 處理用戶的輸入,根據輸入來匹配枚舉,然后執行不同的操作 pub fn process_input(item: ItemTypes, command: String, state: &Map<String, Value>) { match item { ItemTypes::Pending(item) => process_pending(item, command, state), ItemTypes::Done(item) => process_done(item, command, state), } }
最后
按照上面的代碼一步一步來完成就可以執行程序了,在根目錄下新建一個 state.json
文件,寫入一個空對象,不然會報錯(代碼沒做處理)
{}
最后再在控制臺上去執行
cargo run create shopping
然后就能看到 state.json 中多了一條記錄
{ "shopping": "pending" }
其它的方法就交由你們自己去嘗試好了~
原文鏈接:https://juejin.cn/post/7186665177704726588
相關推薦
- 2022-05-23 vmware增加新硬盤無需重啟即可生效的命令腳本_VMware
- 2023-02-04 基于Python的GUI圖形用戶界面編程詳細講解_python
- 2022-08-03 Python的Web框架Django介紹與安裝方法_python
- 2022-12-14 Flutter實現手勢識別功能詳解方法_Android
- 2022-06-25 Android開發實現圖片大小與質量壓縮及保存_Android
- 2022-09-15 python安裝whl文件的實戰步驟_python
- 2022-11-19 C語言數據結構不掛科指南之隊列詳解_C 語言
- 2023-11-23 寶塔數據庫過大導入失效解決方案
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支