From 52a1aa7bdf8a627564e26c0ec2b666881aeab427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=B9=BF?= Date: Wed, 2 Jul 2025 18:04:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0Rust=E7=A4=BA=E4=BE=8B?= =?UTF-8?q?=E9=A1=B9=E7=9B=AEex02=EF=BC=8C=E5=AE=9E=E7=8E=B0=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E5=AD=97=E7=AC=A6=E5=A4=84=E7=90=86=E5=99=A8=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E8=A1=8C=E5=B7=A5=E5=85=B7=EF=BC=8C=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E6=89=80=E6=9C=89=E6=9D=83=E5=92=8C=E7=94=9F=E5=91=BD=E5=91=A8?= =?UTF-8?q?=E6=9C=9F=E7=89=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/ex02/Cargo.lock | 380 +++++++++++++++++++++++++++++++++ examples/ex02/Cargo.toml | 14 ++ examples/ex02/src/config.rs | 111 ++++++++++ examples/ex02/src/main.rs | 160 ++++++++++++++ examples/ex02/src/processor.rs | 228 ++++++++++++++++++++ examples/ex02/src/utils.rs | 179 ++++++++++++++++ examples/ex02/test.txt | 3 + 7 files changed, 1075 insertions(+) create mode 100644 examples/ex02/Cargo.lock create mode 100644 examples/ex02/Cargo.toml create mode 100644 examples/ex02/src/config.rs create mode 100644 examples/ex02/src/main.rs create mode 100644 examples/ex02/src/processor.rs create mode 100644 examples/ex02/src/utils.rs create mode 100644 examples/ex02/test.txt diff --git a/examples/ex02/Cargo.lock b/examples/ex02/Cargo.lock new file mode 100644 index 0000000..67b5f8c --- /dev/null +++ b/examples/ex02/Cargo.lock @@ -0,0 +1,380 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "clap" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "ex02" +version = "0.1.0" +dependencies = [ + "anyhow", + "atty", + "clap", + "once_cell", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/examples/ex02/Cargo.toml b/examples/ex02/Cargo.toml new file mode 100644 index 0000000..6ce364a --- /dev/null +++ b/examples/ex02/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "ex02" +version = "0.1.0" +edition = "2021" +description = "安全字符处理器命令行工具,展示Rust的所有权和生命周期特性" + +[dependencies] +clap = { version = "4.4", features = ["derive"] } # 命令行参数解析 +anyhow = "1.0" # 错误处理 +thiserror = "1.0" # 自定义错误类型 +once_cell = "1.18" # 用于单例模式实现 +serde = { version = "1.0", features = ["derive"] } # 序列化/反序列化 +serde_json = "1.0" # JSON处理 +atty = "0.2" # 检测终端类型 \ No newline at end of file diff --git a/examples/ex02/src/config.rs b/examples/ex02/src/config.rs new file mode 100644 index 0000000..3edef59 --- /dev/null +++ b/examples/ex02/src/config.rs @@ -0,0 +1,111 @@ +use std::collections::HashMap; +use std::sync::{OnceLock, Mutex}; +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::Path; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Config { + settings: HashMap, +} + +impl Config { + // 获取配置实例 + pub fn get_instance() -> &'static Mutex { + static INSTANCE: OnceLock> = OnceLock::new(); + INSTANCE.get_or_init(|| Mutex::new(Config::default())) + } + + // 从文件加载配置 + pub fn load_from_file>(path: P) -> Result<()> { + let config_str = fs::read_to_string(path)?; + let new_config: Config = serde_json::from_str(&config_str)?; + + let instance = Self::get_instance().lock().unwrap(); + let mut instance = instance; + *instance = new_config; + + Ok(()) + } + + // 获取配置项 + pub fn get_setting(&self, key: &str) -> Option<&String> { + self.settings.get(key) + } + + // 设置配置项 + pub fn set_setting(&mut self, key: String, value: String) { + self.settings.insert(key, value); + } + + // 保存配置到文件 + pub fn save_to_file>(&self, path: P) -> Result<()> { + let config_str = serde_json::to_string_pretty(self)?; + fs::write(path, config_str)?; + Ok(()) + } +} + +// 默认配置 +impl Default for Config { + fn default() -> Self { + let mut settings = HashMap::new(); + settings.insert("case_sensitive".to_string(), "true".to_string()); + settings.insert("max_word_length".to_string(), "100".to_string()); + settings.insert("min_word_length".to_string(), "1".to_string()); + + Config { settings } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use std::io::Write; + use tempfile::NamedTempFile; + + #[test] + fn test_singleton_pattern() { + let instance1 = Config::get_instance(); + let instance2 = Config::get_instance(); + + // 验证两个实例是同一个 + assert_eq!( + format!("{:p}", instance1), + format!("{:p}", instance2) + ); + } + + #[test] + fn test_config_operations() { + let instance = Config::get_instance(); + let mut config = instance.lock().unwrap(); + + // 测试设置和获取配置 + config.set_setting("test_key".to_string(), "test_value".to_string()); + assert_eq!( + config.get_setting("test_key"), + Some(&"test_value".to_string()) + ); + } + + #[test] + fn test_file_operations() -> Result<()> { + // 创建临时文件 + let mut temp_file = NamedTempFile::new()?; + let test_config = r#"{"settings":{"test_key":"test_value"}}"#; + write!(temp_file, "{}", test_config)?; + + // 测试加载配置 + Config::load_from_file(temp_file.path())?; + let config = Config::get_instance().lock().unwrap(); + assert_eq!( + config.get_setting("test_key"), + Some(&"test_value".to_string()) + ); + + Ok(()) + } +} \ No newline at end of file diff --git a/examples/ex02/src/main.rs b/examples/ex02/src/main.rs new file mode 100644 index 0000000..e75185f --- /dev/null +++ b/examples/ex02/src/main.rs @@ -0,0 +1,160 @@ +mod config; +mod processor; +mod utils; + +use clap::{Parser, ValueEnum}; +use anyhow::{Result, Context}; +use std::path::PathBuf; +use std::process; + +use crate::config::Config; +use crate::processor::{Operation, TextProcessor}; +use crate::utils::{read_input, InputSource, safe_print}; + +/// 安全字符处理器 - 展示Rust所有权和生命周期特性的命令行工具 +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// 要处理的文本(如果不提供,将从标准输入或文件读取) + #[arg(index = 1)] + text: Option, + + /// 处理操作类型 + #[arg(short, long, value_enum, default_value = "analyze")] + operation: OperationType, + + /// 输入文件路径 + #[arg(short, long)] + file: Option, + + /// 配置文件路径 + #[arg(short, long)] + config: Option, + + /// 是否显示详细输出 + #[arg(short, long)] + verbose: bool, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, ValueEnum)] +enum OperationType { + /// 转换为大写 + Upper, + /// 转换为小写 + Lower, + /// 移除重复字符 + Dedup, + /// 统计单词 + Count, + /// 分析文本 + Analyze, +} + +impl From for Operation { + fn from(op_type: OperationType) -> Self { + match op_type { + OperationType::Upper => Operation::ToUpper, + OperationType::Lower => Operation::ToLower, + OperationType::Dedup => Operation::RemoveDuplicates, + OperationType::Count => Operation::CountWords, + OperationType::Analyze => Operation::Analyze, + } + } +} + +fn main() -> Result<()> { + // 解析命令行参数 + let args = Args::parse(); + + // 如果提供了配置文件,加载配置 + if let Some(config_path) = &args.config { + Config::load_from_file(config_path) + .with_context(|| format!("加载配置文件失败: {:?}", config_path))?; + + if args.verbose { + println!("已加载配置文件: {:?}", config_path); + } + } + + // 确定输入源 + let input_source = match (&args.text, &args.file) { + (Some(text), _) => InputSource::Text(text), + (None, Some(file)) => InputSource::File(file.to_string_lossy().to_string()), + (None, None) => { + if atty::is(atty::Stream::Stdin) { + eprintln!("错误: 未提供输入。请提供文本参数、输入文件或通过管道传入内容。"); + eprintln!("使用 --help 查看帮助信息。"); + process::exit(1); + } + InputSource::StdIn + } + }; + + // 读取输入 + let input = read_input(input_source) + .context("读取输入失败")?; + + if args.verbose { + println!("输入长度: {} 字符", input.len()); + } + + // 处理文本 + let processor = TextProcessor; + let operation: Operation = args.operation.into(); + + if args.verbose { + println!("执行操作: {:?}", operation); + } + + // 使用生命周期标注来确保引用安全 + let result = processor.process_text(&input, operation) + .context("处理文本失败")?; + + // 输出结果 + safe_print(&result); + + Ok(()) +} + +// 演示生命周期和所有权的示例函数 +// 这些函数不会被直接调用,但展示了Rust的所有权系统 +#[allow(dead_code)] +fn ownership_examples() { + // 示例1: 移动语义 + let s1 = String::from("hello"); + let s2 = s1; + // println!("{}", s1); // 编译错误: s1的所有权已移动到s2 + + // 示例2: 借用 + let s3 = String::from("world"); + let len = calculate_length(&s3); + println!("'{}' 的长度是 {}", s3, len); // s3仍然有效 + + // 示例3: 可变借用 + let mut s4 = String::from("hello"); + change(&mut s4); + println!("修改后: {}", s4); + + // 示例4: 生命周期 + let string1 = String::from("长字符串"); + { + let string2 = String::from("短"); + let result = longest(string1.as_str(), string2.as_str()); + println!("最长的字符串是: {}", result); + } +} + +// 借用示例 +fn calculate_length(s: &String) -> usize { + s.len() +} + +// 可变借用示例 +fn change(s: &mut String) { + s.push_str(", world"); +} + +// 生命周期标注示例 +fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { + if x.len() > y.len() { x } else { y } +} \ No newline at end of file diff --git a/examples/ex02/src/processor.rs b/examples/ex02/src/processor.rs new file mode 100644 index 0000000..075f55f --- /dev/null +++ b/examples/ex02/src/processor.rs @@ -0,0 +1,228 @@ +use std::collections::{HashMap, HashSet}; +use anyhow::Result; +use thiserror::Error; +use crate::config::Config; + +#[derive(Debug, Error)] +pub enum ProcessorError { + #[error("无效的输入文本")] + InvalidInput, + + #[error("处理操作失败: {0}")] + ProcessingFailed(String), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Operation { + ToUpper, + ToLower, + RemoveDuplicates, + CountWords, + Analyze, +} + +impl std::str::FromStr for Operation { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "upper" => Ok(Operation::ToUpper), + "lower" => Ok(Operation::ToLower), + "dedup" => Ok(Operation::RemoveDuplicates), + "count" => Ok(Operation::CountWords), + "analyze" => Ok(Operation::Analyze), + _ => Err(format!("未知的操作类型: {}", s)), + } + } +} + +pub struct TextProcessor; + +impl TextProcessor { + // 处理文本的主函数,根据操作类型调用不同的处理函数 + pub fn process_text<'a>(&self, input: &'a str, operation: Operation) -> Result { + if input.is_empty() { + return Err(ProcessorError::InvalidInput.into()); + } + + match operation { + Operation::ToUpper => Ok(self.to_upper(input)), + Operation::ToLower => Ok(self.to_lower(input)), + Operation::RemoveDuplicates => Ok(self.remove_duplicates(input)), + Operation::CountWords => Ok(self.count_words(input)), + Operation::Analyze => Ok(self.analyze_text(input)), + } + } + + // 转换为大写 + pub fn to_upper(&self, text: &str) -> String { + text.to_uppercase() + } + + // 转换为小写 + pub fn to_lower(&self, text: &str) -> String { + text.to_lowercase() + } + + // 移除重复字符(保持顺序) + pub fn remove_duplicates(&self, text: &str) -> String { + let mut seen = HashSet::new(); + let mut result = String::with_capacity(text.len()); + + for c in text.chars() { + if seen.insert(c) { + result.push(c); + } + } + + result + } + + // 统计单词数量 + pub fn count_words(&self, text: &str) -> String { + let config = Config::get_instance().lock().unwrap(); + let case_sensitive = config.get_setting("case_sensitive") + .map(|s| s == "true") + .unwrap_or(true); + + let min_length = config.get_setting("min_word_length") + .and_then(|s| s.parse::().ok()) + .unwrap_or(1); + + let max_length = config.get_setting("max_word_length") + .and_then(|s| s.parse::().ok()) + .unwrap_or(100); + + let mut word_counts = HashMap::new(); + + // 分割文本为单词并计数 + for word in text.split_whitespace() { + let word = word.trim_matches(|c: char| !c.is_alphanumeric()); + + if word.len() < min_length || word.len() > max_length { + continue; + } + + let key = if case_sensitive { + word.to_string() + } else { + word.to_lowercase() + }; + + *word_counts.entry(key).or_insert(0) += 1; + } + + // 格式化输出结果 + let mut result = String::new(); + result.push_str("单词统计结果:\n"); + + let mut words: Vec<(&String, &i32)> = word_counts.iter().collect(); + words.sort_by(|a, b| b.1.cmp(a.1)); + + for (word, count) in words { + result.push_str(&format!("{}: {}\n", word, count)); + } + + result + } + + // 分析文本(字符频率、平均单词长度等) + pub fn analyze_text(&self, text: &str) -> String { + let char_count = text.chars().count(); + let byte_count = text.len(); + let line_count = text.lines().count(); + let word_count = text.split_whitespace().count(); + + // 字符频率分析 + let mut char_freq = HashMap::new(); + for c in text.chars() { + *char_freq.entry(c).or_insert(0) += 1; + } + + // 计算平均单词长度 + let avg_word_length = if word_count > 0 { + let total_word_length: usize = text.split_whitespace() + .map(|w| w.chars().count()) + .sum(); + total_word_length as f64 / word_count as f64 + } else { + 0.0 + }; + + // 格式化输出结果 + let mut result = String::new(); + result.push_str("文本分析结果:\n"); + result.push_str(&format!("字符数: {}\n", char_count)); + result.push_str(&format!("字节数: {}\n", byte_count)); + result.push_str(&format!("行数: {}\n", line_count)); + result.push_str(&format!("单词数: {}\n", word_count)); + result.push_str(&format!("平均单词长度: {:.2}\n", avg_word_length)); + + result.push_str("\n最常见的字符:\n"); + let mut chars: Vec<(&char, &i32)> = char_freq.iter().collect(); + chars.sort_by(|a, b| b.1.cmp(a.1)); + + for (i, (c, count)) in chars.iter().take(10).enumerate() { + let display_char = if c.is_whitespace() { + match **c { + ' ' => "空格".to_string(), + '\n' => "换行".to_string(), + '\t' => "制表符".to_string(), + _ => format!("Unicode({})", **c as u32), + } + } else { + c.to_string() + }; + + result.push_str(&format!("{}. '{}': {} ({:.2}%)\n", + i + 1, + display_char, + count, + (**count as f64 / char_count as f64) * 100.0 + )); + } + + result + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_to_upper() { + let processor = TextProcessor; + assert_eq!(processor.to_upper("hello world"), "HELLO WORLD"); + } + + #[test] + fn test_to_lower() { + let processor = TextProcessor; + assert_eq!(processor.to_lower("HELLO WORLD"), "hello world"); + } + + #[test] + fn test_remove_duplicates() { + let processor = TextProcessor; + assert_eq!(processor.remove_duplicates("hello"), "helo"); + assert_eq!(processor.remove_duplicates("aabbcc"), "abc"); + } + + #[test] + fn test_count_words() { + let processor = TextProcessor; + let result = processor.count_words("hello world hello"); + assert!(result.contains("hello: 2")); + assert!(result.contains("world: 1")); + } + + #[test] + fn test_analyze_text() { + let processor = TextProcessor; + let result = processor.analyze_text("hello world\nhello"); + assert!(result.contains("字符数: 17")); + assert!(result.contains("行数: 2")); + assert!(result.contains("单词数: 3")); + } +} \ No newline at end of file diff --git a/examples/ex02/src/utils.rs b/examples/ex02/src/utils.rs new file mode 100644 index 0000000..a500901 --- /dev/null +++ b/examples/ex02/src/utils.rs @@ -0,0 +1,179 @@ +use std::fs; +use std::io::{self, Read}; +use std::path::Path; +use anyhow::{Result, Context}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum InputError { + #[error("文件不存在: {0}")] + FileNotFound(String), + + #[error("读取文件失败: {0}")] + ReadError(#[from] io::Error), + + #[error("无法处理输入: {0}")] + ProcessingError(String), +} + +/// 表示文本输入的来源 +pub enum InputSource<'a> { + /// 从文件读取 + File(String), + /// 从标准输入读取 + StdIn, + /// 直接使用字符串 + Text(&'a str), +} + +/// 从不同来源读取文本 +pub fn read_input(source: InputSource) -> Result { + match source { + InputSource::File(path) => { + read_from_file(&path) + .with_context(|| format!("读取文件 '{}' 失败", path)) + }, + InputSource::StdIn => { + read_from_stdin() + .context("从标准输入读取失败") + }, + InputSource::Text(text) => { + Ok(text.to_string()) + }, + } +} + +/// 从文件读取文本 +pub fn read_from_file>(path: P) -> Result { + let path_str = path.as_ref().to_string_lossy().to_string(); + + if !path.as_ref().exists() { + return Err(InputError::FileNotFound(path_str).into()); + } + + fs::read_to_string(path) + .map_err(|e| InputError::ReadError(e).into()) +} + +/// 从标准输入读取文本 +pub fn read_from_stdin() -> Result { + let mut buffer = String::new(); + io::stdin().read_to_string(&mut buffer)?; + Ok(buffer) +} + +/// 安全地打印结果 +pub fn safe_print(text: &str) { + println!("{}", text); +} + +/// 检查文本是否为有效的UTF-8 +pub fn is_valid_utf8(bytes: &[u8]) -> bool { + String::from_utf8(bytes.to_vec()).is_ok() +} + +/// 安全地处理文件路径 +pub fn sanitize_path(path: &str) -> String { + // 移除可能导致路径遍历的模式 + let path = path.replace("..", ""); + + // 规范化路径分隔符 + #[cfg(target_os = "windows")] + let path = path.replace("/", "\\"); + + #[cfg(not(target_os = "windows"))] + let path = path.replace("\\", "/"); + + path +} + +/// 创建一个带有生命周期的文本引用 +pub struct TextRef<'a> { + content: &'a str, +} + +impl<'a> TextRef<'a> { + /// 创建一个新的文本引用 + pub fn new(content: &'a str) -> Self { + TextRef { content } + } + + /// 获取内容 + pub fn content(&self) -> &'a str { + self.content + } + + /// 获取内容的一部分 + pub fn substring(&self, start: usize, end: usize) -> &'a str { + let start = start.min(self.content.len()); + let end = end.min(self.content.len()); + + if start >= end { + return ""; + } + + // 确保我们在字符边界上切割 + let mut char_indices = self.content.char_indices(); + + let start_idx = char_indices + .find(|(i, _)| *i >= start) + .map(|(i, _)| i) + .unwrap_or(self.content.len()); + + let end_idx = char_indices + .find(|(i, _)| *i >= end) + .map(|(i, _)| i) + .unwrap_or(self.content.len()); + + &self.content[start_idx..end_idx] + } +} + +/// 实现Drop trait,用于演示资源清理 +impl<'a> Drop for TextRef<'a> { + fn drop(&mut self) { + // 在实际应用中,这里可能会执行一些清理操作 + // 这里只是为了演示Drop trait的使用 + println!("TextRef被释放,长度为: {}", self.content.len()); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::File; + use std::io::Write; + use tempfile::NamedTempFile; + + #[test] + fn test_read_from_file() -> Result<()> { + let mut temp_file = NamedTempFile::new()?; + write!(temp_file, "测试内容")?; + + let content = read_from_file(temp_file.path())?; + assert_eq!(content, "测试内容"); + + Ok(()) + } + + #[test] + fn test_is_valid_utf8() { + assert!(is_valid_utf8("你好".as_bytes())); + assert!(!is_valid_utf8(&[0xFF, 0xFE, 0xFD])); + } + + #[test] + fn test_sanitize_path() { + assert_eq!(sanitize_path("../dangerous/path"), "/dangerous/path"); + } + + #[test] + fn test_text_ref() { + let text = "Hello, world!"; + let text_ref = TextRef::new(text); + + assert_eq!(text_ref.content(), "Hello, world!"); + assert_eq!(text_ref.substring(0, 5), "Hello"); + assert_eq!(text_ref.substring(7, 12), "world"); + } +} \ No newline at end of file diff --git a/examples/ex02/test.txt b/examples/ex02/test.txt new file mode 100644 index 0000000..a626576 --- /dev/null +++ b/examples/ex02/test.txt @@ -0,0 +1,3 @@ +Hello Rust! +This is a test file. +Multiple lines for testing. \ No newline at end of file