主要内容
从txt文件导入 电影信息(一次性)
基于命令行 的电影管理小程序(CRUD)
txt文件示例:
DVD.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 5. 1987 华尔街 Wall Street.mkv 1998 细细的红线 The Thin Red Line.mkv 1999 星球大战1魅影危机 Star Wars Episode I The Phantom Menace.mkv 2000 猎杀U-571 U-571.mkv 2002 星球大战2克隆人的进攻 Star Wars Episode II Attack Of The Clones.mkv 2005 豺狼帝国 Empire of the Wolves.mkv 2005 翻译风波 The Interpreter.mkv 2005 星球大战3西斯的反击 Star Wars Episode III Revenge Of The Sith.mkv 2005 查理和巧克力工厂 Charlie and the Chocolate Factory.mkv(儿童) 2007 灵魂战车 Ghost Rider Extended Cut.mkv 2008 好好先生 Yes Man.mkv 2009 真人游戏 Gamer.mkv 2009 巫山历险记 Race To Witch Mountain.mkv
从txt文件导入 电影信息 第一步:文件选择 添加原生文件打开/保存对话框:cargo add rfd
使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 use movie_importer::read_txt_to_json;use std::{error::Error, process};use rfd::FileDialog; fn main () -> Result <(), Box <dyn Error>> { if let Some (file_path) = FileDialog::new () .add_filter ("Text Files" , &["txt" ]) .set_title ("⚙Select the DVD text file" ) .set_directory ("E:/Study/RustStudy/Projects/movie_importer" ) .pick_file () { let save_path = read_txt_to_json (&file_path)?; println! ("Data saved to: {save_path:#?}" ); Ok (()) } else { eprintln! ("File not selected." ); process::exit (-1 ); } }
第二步:txt转json 实现函数read_txt_to_json:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 pub fn read_txt_to_json (file_path: &PathBuf) -> Result <PathBuf, Box <dyn Error>> { let txt = fs::read_to_string (file_path)?; let mut disc_no = 0u32 ; let disc_regex = Regex::new (r"^(\d+)\.$" )?; let movie_regex = Regex::new (r"^(\d{4})(.*?)((儿童))?$" )?; let mut movies = Vec ::new (); for line in txt.lines ().map (str ::trim).filter (|l| !l.is_empty ()) { if let Some (no) = disc_number (line, &disc_regex) { disc_no = no; } else if let Some (movie) = parse_movie (disc_no, line, &movie_regex) { movies.push (movie); } } save_to_json (movies) }
实现帮助函数disc_number,parse_movie。这里需要添加正则表达式crate cargo add regex:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 fn parse_movie (disc_no: u32 , line: &str , re: &Regex) -> Option <Movie> { re.captures (line).map (|cap| { Movie::new ( disc_no, cap.get (1 ).unwrap ().as_str ().trim ().to_string (), cap.get (2 ).unwrap ().as_str ().trim ().to_string (), cap.get (3 ).map (|mat| mat.as_str ().trim ().to_string ()), ) }) } fn disc_number (line: &str , regex: &Regex) -> Option <u32 > { regex .captures (line) .map (|cap| cap.get (1 ).unwrap ().as_str ().parse ().unwrap ()) }
补上Movie的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #[derive(Debug, Serialize, Deserialize)] struct Movie { disc: u32 , year: String , title: String , remark: Option <String >, } impl Movie { fn new (disc: u32 , year: String , title: String , remark: Option <String >) -> Movie { Movie { disc, year, title, remark, } } }
最后实现保存为json文件,添加cratecargo add serde -F derive 和cargo add serde_json实现json序列化:
1 2 3 4 5 6 7 8 9 10 11 fn save_to_json (movies: Vec <Movie>) -> Result <PathBuf, Box <dyn Error>> { let json_str = serde_json::to_string_pretty (&movies)?; let path = FileDialog::new () .add_filter ("Json" , &["json" ]) .set_title ("Save Data To Json File" ) .set_directory ("E:/Study/RustStudy/Projects/movie_importer" ) .save_file () .ok_or_else (|| "No Save location selected" .to_string ())?; fs::write (&path, json_str)?; Ok (path) }
基于命令行 的电影管理小程序 命令行程序设计:
movie
movie login -username 然后输入 password
movie logout
moive list
movie add -disc -year -title -remark
movie delete -disc -index
movie edit -disc -index 然后输入四个字段
第一步:添加clap crate 运行命令:cargo add clap -F dervie
关于clap:clap - crates.io: Rust 包注册中心 — clap - crates.io: Rust Package Registry
clap使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 use clap::Parser;#[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { #[arg(short, long)] name: String , #[arg(short, long, default_value_t = 1)] count: u8 , } fn main () { let args = Args::parse (); for _ in 0 ..args.count { println! ("Hello {}!" , args.name); } }
试试它:
1 2 3 4 5 6 7 8 9 10 11 12 13 $ demo --help A simple to use, efficient, and full-featured Command Line Argument Parser Usage: demo[EXE] [OPTIONS] --name <NAME> Options: -n, --name <NAME> Name of the person to greet -c, --count <COUNT> Number of times to greet [default: 1] -h, --help Print help -V, --version Print version $ demo --name Me Hello Me!
第二步:实现命令 我们要有movie login -username,这里的login是一个子命令,所以我们要这么设计:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #[derive(Parser)] #[command(version, about = "Movie app" , long_about = "Movie information app" )] struct Cli { #[command(subcommand)] command: Option <Commands>, } #[derive(Subcommand)] enum Commands { Login { #[arg(short, long)] username: String , }, }
加上其他命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #[derive(Subcommand)] enum Commands { Login { #[arg(short, long)] username: String , }, Logout, List, Add { #[arg(short, long)] disc: usize , #[arg(short, long)] year: String , #[arg(short, long)] title: String , #[arg(short, long)] remark: Option <String >, }, Delete { #[arg(short, long)] disc: usize , #[arg(short, long)] index: usize , }, Edit { #[arg(short, long)] disc: usize , #[arg(short, long)] index: usize , }, }
对命令进行处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 fn main () -> Result <(), Box <dyn Error>> { let cli = Cli::parse (); match &cli.command { Some (Commands::Login { username }) => handle_login (username)?, Some (Commands::Logout) => handle_logout ()?, Some (Commands::List) => handle_list ()?, Some (Commands::Add { disc, year, title, remark, }) => handle_add (disc, year, title, remark)?, Some (Commands::Delete { disc, index }) => handle_delete (disc, index)?, Some (Commands::Edit {disc, index}) => handle_edit (disc, index)?, _ => { println! ("No command provided or command not recoginized." ) } } Ok (()) }
实现handle.rs处理命令行(类似Web MVC 中的controller):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 use crate::models::{Movie, Role};use crate::services::{ get_logged_in_role, get_users, list_movies, login_success, logout, read_from_json, write_to_json, }; use std::cmp::PartialEq ;use std::error::Error;use std::io;use std::io::Write;pub fn handle_login (username: &str ) -> Result <(), Box <dyn Error>> { println! ("Username: {username}" ); if let Some (user) = get_users () .iter () .find (|user| user.username.eq_ignore_ascii_case (username)) { println! ("Please enter the password:" ); match rpassword::read_password () { Ok (password) => { if user.password.eq (&password) { login_success (&user.role)?; println! ("Log in successfully." ); } else { println! ("Incorrect password." ) } } Err (_) => { println! ("Failed to read password." ) } } } else { println! ("User not found." ) } Ok (()) } pub fn handle_logout () -> Result <(), Box <dyn Error>> { match logout () { Ok (_) => { println! ("Log out succsessfully." ); } Err (_) => { println! ("Please Log in before this action." ); } } Ok (()) } pub fn handle_list () -> Result <(), Box <dyn Error>> { match get_logged_in_role ()? { None => { println! ("You need to log in to view the movies." ) } Some (_) => { let movies = read_from_json ()?; list_movies (&movies); } } Ok (()) } pub fn handle_add ( disc: &usize , year: &String , title: &String , remark: &Option <String >, ) -> Result <(), Box <dyn Error>> { match get_logged_in_role ()? { Some (Role::Admin) => { let mut movies = read_from_json ()?; let new_movie = Movie { disc: *disc, year: year.to_string (), title: title.to_string (), remark: remark.clone (), }; movies.push (new_movie); write_to_json (&movies)?; println! ("Movie added." ) } _ => { println! ("You need to log in as Admin to add a movie." ) } } Ok (()) } pub fn handle_delete (disc: &usize , index: &usize ) -> Result <(), Box <dyn Error>> { match get_logged_in_role ()? { Some (Role::Admin) => { let movies = read_from_json ()?; if let Some (movie) = movies .iter () .filter (|m| m.disc == *disc) .enumerate () .find (|(i, _)| *i == *index) .map (|(_, m)| m.clone ()) { let movies = movies .into_iter () .filter (|m| *m != movie) .collect::<Vec <Movie>>(); write_to_json (&movies)?; println! ("Movie deleted." ) } } _ => { println! ("You need to log in as Admin to add a movie." ) } } Ok (()) } pub fn handle_edit (disc: &usize , index: &usize ) -> Result <(), Box <dyn Error>> { match get_logged_in_role ()? { Some (Role::Admin) => { let mut movies = read_from_json ()?; if let Some (movie) = movies .iter_mut () .filter (|m| m.disc == *disc) .enumerate () .find (|(i, _)| *i == *index) .map (|(_, m)| m) { println! ("The movie disc no.: {}" , movie.disc); print! ("Enter the new disc no.: " ); io::stdout ().flush ()?; let mut disc = String ::new (); io::stdin ().read_line (&mut disc)?; let disc = disc.trim (); if let Ok (disc) = disc.parse::<usize >() { movie.disc = disc; } else { println! ("Invalid disc number." ); return Ok (()); } println! ("The movie year: {}" , movie.year); print! ("Enter the new year: " ); io::stdout ().flush ()?; let mut year = String ::new (); io::stdin ().read_line (&mut year)?; let year = year.trim (); if !year.is_empty () { movie.year = year.to_string (); } else { println! ("Invalid year" ); return Ok (()); } println! ("The movie title: {}" , movie.title); print! ("Enter the new title: " ); io::stdout ().flush ()?; let mut title = String ::new (); io::stdin ().read_line (&mut title)?; let title = title.trim (); if !title.is_empty () { movie.title = title.to_string (); } else { println! ("The title cannot be empty." ); return Ok (()); } println! ("The movie remark: {}" , movie.title); print! ("Enter the new remark: " ); io::stdout ().flush ()?; let mut remark = String ::new (); io::stdin ().read_line (&mut remark)?; let remark = remark.trim (); if !remark.is_empty () { movie.remark = Some (remark.to_string ()); } else { movie.remark = None ; } write_to_json (&movies)?; println! ("Movie modified." ) } } _ => { println! ("You need to log in as Admin to add a movie." ) } } Ok (()) }
实现services.rs(类似Web MVC 中的service):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 use crate::models::{Movie, Role, User};use std::error::Error;use std::{fs, io};pub fn get_users () -> Vec <User> { vec! [ User { username: "Admin" .to_string (), password: "admin" .to_string (), role: Role::Admin, }, User { username: "Dave" .to_string (), password: "Mustaine" .to_string (), role: Role::User, }, User { username: "Nick" .to_string (), password: "Carter" .to_string (), role: Role::User, }, ] } pub fn login_success (role: &Role) -> Result <(), Box <dyn Error>> { fs::write (".session" , role.to_string ())?; Ok (()) } pub fn get_logged_in_role () -> Result <Option <Role>, Box <dyn Error>> { let role = fs::read_to_string (".session" ); if let Ok (role) = role { match role.as_str () { "Administrator" => Ok (Some (Role::Admin)), "User" => Ok (Some (Role::User)), _ => Ok (None ), } } else { Ok (None ) } } pub fn logout () -> Result <(), Box <dyn Error>> { fs::remove_file (".session" )?; Ok (()) } pub fn read_from_json () -> Result <Vec <Movie>, Box <dyn Error>> { let file = fs::File::open ("movie.json" )?; let reader = io::BufReader::new (file); let movies = serde_json::from_reader (reader)?; Ok (movies) } pub fn list_movies (movies: &[Movie]) { println! ("{:<5}{:<10}{:<80}{:<15}" , "Disc" , "Year" , "Title" , "Remark" ); println! ("{:-<110}" , "" ); movies.iter ().for_each(|m| { let remark = m.remark.as_deref ().unwrap_or ("" ); let title = pad_display_width (&m.title, 80 ); let remark = pad_display_width (remark, 15 ); println! ("{:<5}{:<7}{}{}" , m.disc, m.year, title, remark); }) } pub fn pad_display_width (text: &str , target_width: usize ) -> String { let width = unicode_width::UnicodeWidthStr::width (text); format! ("{}{}" , text, " " .repeat (target_width.saturating_sub (width))) } pub fn write_to_json (movies: &[Movie]) -> Result <(), Box <dyn Error>> { let file = fs::File::create ("movie.json" )?; let writer = io::BufWriter::new (file); serde_json::to_writer_pretty (writer, movies)?; Ok (()) }
实现models.rs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 use serde::{Deserialize, Serialize};use std::fmt::{Display, Formatter};pub struct User { pub username: String , pub password: String , pub role: Role, } pub enum Role { Admin, User, } impl Display for Role { fn fmt (&self , f: &mut Formatter<'_ >) -> std::fmt::Result { match self { Role::Admin => { write! (f, "Administrator" ) } Role::User => { write! (f, "User" ) } } } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct Movie { pub disc: usize , pub year: String , pub title: String , pub remark: Option <String >, } impl PartialEq for Movie { fn eq (&self , other: &Self ) -> bool { self .disc == other.disc && self .year == other.year && self .title == other.title && self .remark == other.remark } }
这些都需要声明在lib.rs:
1 2 3 pub mod handler;pub mod models;pub mod services;
cargo.toml:
1 2 3 4 5 6 7 8 9 10 11 [package] name = "movie" version = "0.1.0" edition = "2024" [dependencies] clap = { version = "4.5.40" , features = ["derive" ] }rpassword = "7.4.0" serde = { version = "1.0.219" , features = ["derive" ] }serde_json = "1.0.140" unicode-width = "0.2.1"