rust從0開始寫項目-06-如何接受命令行參數(shù)clap-01

寫web項目或者app等衫嵌,必不可少的要接受參數(shù)和校驗參數(shù)的準(zhǔn)確性,基本也是項目開始的第一步狱杰,那么我們今天來看下rust提供了哪些優(yōu)秀的crates 關(guān)注 vx golang技術(shù)實驗室搬味,獲取更多golang章郁、rust好文 # 一、clap_v3 本來是想用structOpt制市,但是看文檔是這樣描述的 由于 clap v3 現(xiàn)已發(fā)布诗越,并且 structopt 功能已集成(幾乎按原樣),因此 structopt 現(xiàn)在處于維護模式:不會添加新功能息堂。 錯誤將被修復(fù)嚷狞,文檔改進將被接受。 ## 1. 1 添加依賴 ``` [dependencies] clap = { version = "4.2.7", features = ["derive","cargo"] } features = "0.10.0" cargo = "0.70.1" 或者 cargo add clap -- features cargo 需要注意:如果不啟用 cargo feature 荣堰,則會報如下錯誤床未。 requires `cargo` feature ``` 如果使用`command!`、`arg!` 必須在features 中添加cargo ## 1.2 快速啟動 ``` use std::env::Args; /////////////////////////////////////// clap_v3 原來的structOpt ////////////////////////////////// use std::path::PathBuf; use clap::{arg, command, value_parser, ArgAction, Command}; fn test() { let matches = command!() // requires `cargo` feature .arg(arg!([name] "Optional name to operate on")) .arg( arg!( -c --config "Sets a custom config file" ) // We don't have syntax yet for optional options, so manually calling `required` .required(false) .value_parser(value_parser!(PathBuf)), ) .arg(arg!( -d --debug ... "Turn debugging information on" )) .subcommand( Command::new("test") .about("does testing things") .arg(arg!(-l --list "lists test values").action(ArgAction::SetTrue)), ) .get_matches(); // You can check the value provided by positional arguments, or option arguments if let Some(name) = matches.get_one::("name") { println!("Value for name: {name}"); } if let Some(config_path) = matches.get_one::("config") { println!("Value for config: {}", config_path.display()); } // You can see how many times a particular flag or argument occurred // Note, only flags can have multiple occurrences match matches .get_one::("debug") .expect("Count's are defaulted") { 0 => println!("Debug mode is off"), 1 => println!("Debug mode is kind of on"), 2 => println!("Debug mode is on"), _ => println!("Don't be crazy"), } // You can check for the existence of subcommands, and if found use their // matches just as you would the top level cmd if let Some(matches) = matches.subcommand_matches("test") { // "$ myapp test" was run if matches.get_flag("list") { // "$ myapp test -l" was run println!("Printing testing lists..."); } else { println!("Not printing testing lists..."); } } // Continued program logic goes here... } pub fn claps(){ test() } ``` 1振坚、默認執(zhí)行情況 ``` cargo run Debug mode is off ``` 2薇搁、參看幫助文檔 ``` cargo run --help Run a binary or example of the local package Usage: cargo run [OPTIONS] [args]... Arguments: [args]... Arguments for the binary or example to run Options: -q, --quiet Do not print cargo log messages --bin [] Name of the bin target to run --example [] Name of the example target to run -p, --package [] Package with the target to run -j, --jobs Number of parallel jobs, defaults to # of CPUs. --keep-going Do not abort the build as soon as there is an error (unstable) -r, --release Build artifacts in release mode, with optimizations --profile Build artifacts with the specified profile -F, --features Space or comma separated list of features to activate --all-features Activate all available features --no-default-features Do not activate the `default` feature --target Build for the target triple --target-dir Directory for all generated artifacts --manifest-path Path to Cargo.toml --message-format Error format --unit-graph Output build graph in JSON (unstable) --ignore-rust-version Ignore `rust-version` specification in packages --timings[=] Timing output formats (unstable) (comma separated): html, json -h, --help Print help -v, --verbose... Use verbose output (-vv very verbose/build.rs output) --color Coloring: auto, always, never --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date --offline Run without accessing the network --config Override a configuration value -Z Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details Run `cargo help run` for more detailed information. ``` 3、使用 -dd 參數(shù) ``` cargo run -- -dd test Debug mode is on Not printing testing lists... ``` ## 1.3 command 解析器 ### 1.3.1 基本使用 ``` fn command(){ let matches = Command::new("MyApp") .version("1.0") .author("ZHangQL Z ") .about("this is the test project") .args(&[//次數(shù)是args渡八,如果單個的的arg arg!(--config "a required file for the configuration and no short"). required(true)//必須包含 .require_equals(true)//要求使用等號賦值 // .default_value() //設(shè)置默認值 , arg!(-d --debug ... "turns on debugging information and allows multiples"), arg!([input] "an optional input file to use") ]) .arg(arg!(--two ).required(true))//單個的 .get_matches(); println!( "config: {:?}", matches.get_one::("config").expect("required") ); println!( "debug: {:?}", matches.get_one::("debug") ); println!( "input: {:?}", matches.get_one::("input") ); } ``` 查看help ``` RUST_BACKTRACE=1 cargo run -- --help this is the test project Usage: my_test [OPTIONS] --config= --two [input] Arguments: [input] an optional input file to use Options: --config= a required file for the configuration and no short -d, --debug... turns on debugging information and allows multiples --two -h, --help Print help -V, --version Print version ``` 運行 ``` RUST_BACKTRACE=1 cargo run -- --config=./config.yaml --two rrr lllll config: "./config.yaml" two: Some("rrr") input: Some("lllll") ``` ### 1.3.2 使用command!構(gòu)建解析器 你也可以使用 command! 宏 構(gòu)建解析器啃洋,不過要想使用 command! 宏,你需要開啟 cargo feature屎鳍。 ``` use clap::{arg, command}; fn main() { // requires `cargo` feature, reading name, version, author, and description from `Cargo.toml` let matches = command!() .arg(arg!(--two ).required(true)) .arg(arg!(--one ).required(true)) .get_matches(); println!( "two: {:?}", matches.get_one::("two").expect("required") ); println!( "one: {:?}", matches.get_one::("one").expect("required") ); } ``` ### 1.3.3 Command::next_line_help 使用 Command::next_line_help 方法 可以修改參數(shù)打印行為 ``` use clap::{arg, command, ArgAction}; fn main() { let matches = command!() // requires `cargo` feature .next_line_help(true) .arg(arg!(--two ).required(true).action(ArgAction::Set)) .arg(arg!(--one ).required(true).action(ArgAction::Set)) .get_matches(); println!( "two: {:?}", matches.get_one::("two").expect("required") ); println!( "one: {:?}", matches.get_one::("one").expect("required") ); } ``` ``` Usage: my_test [OPTIONS] --config= --two [input] Arguments: [input] an optional input file to use Options: --config= a required file for the configuration and no short -d, --debug... turns on debugging information and allows multiples --two -h, --help Print help -V, --version Print version ``` 效果就是:參數(shù)的描述和參數(shù)是分行的宏娄,描述信息在參數(shù)下一行。 ## 1.4 添加命令行參數(shù)(Adding Arguments) 我們可以使用 Command::arg 方法來添加 Arg 對象來添加命令行參數(shù) ``` fn adding_arg(){ let matches = command!() .arg(Arg::new("name")) .get_matches(); println!("name: {:?}", matches.get_one::("name")); } ``` 查看help ``` RUST_BACKTRACE=1 cargo run -- --help Usage: my_test [name] Arguments: [name] Options: -h, --help Print help -V, --version Print version ``` 2逮壁、使用 name 參數(shù):默認 ```bash cargo run name: None ``` 3孵坚、使用 name 參數(shù):blob 注意定義的時候沒有是直接使用的 不需要key的 ```bash cargo run bob name: Some("bob") ``` ### 1.4.2 設(shè)置參數(shù)行為 需要注意:參數(shù)默認值是一個 Set 類型 我們可以使用 Command::action 方法來設(shè)置 參數(shù)行為。如果可以添加多個只,我們可以使用 ArgAction::Append ```rust use clap::{command, Arg, ArgAction}; fn main() { let matches = command!() // requires `cargo` feature .arg(Arg::new("name").action(ArgAction::Append)) .get_matches(); let args = matches .get_many::("name") .unwrap_or_default() .map(|v| v.as_str()) .collect::>(); println!("names: {:?}", &args); } ``` ## 1.5 參數(shù)選項 一個參數(shù)行為的標(biāo)志: - 順序無關(guān) - 可選參數(shù) - 意圖清晰 ``` fn arg_switch(){ let matches = command!() .arg(Arg::new("name") .short('n') .long("name") ).get_matches(); println!("name: {:?}", matches.get_one::("name")); } ``` 上述代碼:我們定義了一個name參數(shù)卖宠,縮寫是n巍杈,全拼是name,也就是如下形式 ``` -n, --name ``` 我們使用方式就有如下幾種 ``` cargo run -- --name blo cargo run -- --name=blob cargo run -- -n blob cargo run -- -n=blob cargo run -- -nblob ``` ### 1.5.1 開啟/關(guān)閉標(biāo)志 我們可以是 ArgAction::SetTrue 開啟參數(shù) ``` use clap::{command, Arg, ArgAction}; fn main() { let matches = command!() // requires `cargo` feature .arg( Arg::new("verbose") .short('v') .long("verbose") .action(ArgAction::SetTrue), ) .get_matches(); println!("verbose: {:?}", matches.get_flag("verbose")); } ``` ### 1.5.2參數(shù)調(diào)用計數(shù) 我們可以使用 ArgAction::Count ``` use clap::{command, Arg, ArgAction}; fn main() { let matches = command!() // requires `cargo` feature .arg( Arg::new("verbose") .short('v') .long("verbose") .action(ArgAction::Count), ) .get_matches(); println!("verbose: {:?}", matches.get_count("verbose")); } ``` 默認值是0扛伍,多次使用參數(shù)就會計數(shù) ``` cargo run -- --verbose --verbose ``` ### 1.5.3 默認值 ``` fn default_value(){ let matches = command!() // requires `cargo` feature .arg( arg!([PORT]) .value_parser(value_parser!(u16)) .default_value("2023"), ) .get_matches(); println!( "port: {:?}", matches .get_one::("PORT") .expect("default ensures there is always a value") ); } ``` ``` cargo run port: 2023 cargo run 897 port: 897 ``` ### 1.5.4 參數(shù)校驗 默認情況下筷畦,參數(shù)被認為是 String,并且使用 UTF-8 校驗刺洒。 **枚舉值** ``` fn enum_check(){ let matches = command!() // requires `cargo` feature .arg( arg!() .help("What mode to run the program in") .value_parser(["fast", "slow"]), ) .get_matches(); // Note, it's safe to call unwrap() because the arg is required match matches .get_one::("MODE") .expect("'MODE' is required and parsing will fail if its missing") .as_str() { "fast" => { println!("Hare"); } "slow" => { println!("Tortoise"); } _ => unreachable!(), } } ``` ``` cargo run rrr error: invalid value 'rrr' for '' [possible values: fast, slow] cargo run fast Hare ``` 如果我們開啟了 derive feature汁咏, 則我們也可以實現(xiàn) ValueEnum 特征實現(xiàn)相同的功能 ``` use clap::{arg, builder::PossibleValue, command, value_parser, ValueEnum}; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] enum Mode { Fast, Slow, } // Can also be derived with feature flag `derive` impl ValueEnum for Mode { fn value_variants<'a>() -> &'a [Self] { &[Mode::Fast, Mode::Slow] } fn to_possible_value<'a>(&self) -> Option { Some(match self { Mode::Fast => PossibleValue::new("fast").help("Run swiftly"), Mode::Slow => PossibleValue::new("slow").help("Crawl slowly but steadily"), }) } } impl std::fmt::Display for Mode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.to_possible_value() .expect("no values are skipped") .get_name() .fmt(f) } } impl std::str::FromStr for Mode { type Err = String; fn from_str(s: &str) -> Result { for variant in Self::value_variants() { if variant.to_possible_value().unwrap().matches(s, false) { return Ok(*variant); } } Err(format!("invalid variant: {s}")) } } fn main() { let matches = command!() // requires `cargo` feature .arg( arg!() .help("What mode to run the program in") .value_parser(value_parser!(Mode)), ) .get_matches(); // Note, it's safe to call unwrap() because the arg is required match matches .get_one::("MODE") .expect("'MODE' is required and parsing will fail if its missing") { Mode::Fast => { println!("Hare"); } Mode::Slow => { println!("Tortoise"); } } } ``` ### 1.5.5 校驗值 我們可以使用 Arg::value_parser 驗證并解析成我們需要的任何類型。 ``` fn validated(){ let matches = command!() // requires `cargo` feature .arg( arg!() .help("Network port to use") .value_parser(value_parser!(u16).range(1..)), ) .get_matches(); // Note, it's safe to call unwrap() because the arg is required let port: u16 = *matches .get_one::("PORT") .expect("'PORT' is required and parsing will fail if its missing"); println!("PORT = {port}"); } cargo run 0 error: invalid value '0' for '': 0 is not in 1..=65535 cargo run 1 PORT = 10 ``` ### 1.5.6 自定義解析器 我們也可以使用自定義解析器用于改進錯誤信息提示和額外的驗證作媚。 ``` use std::ops::RangeInclusive; use clap::{arg, command}; fn main() { let matches = command!() // requires `cargo` feature .arg( arg!() .help("Network port to use") .value_parser(port_in_range), ) .get_matches(); // Note, it's safe to call unwrap() because the arg is required let port: u16 = *matches .get_one::("PORT") .expect("'PORT' is required and parsing will fail if its missing"); println!("PORT = {port}"); } const PORT_RANGE: RangeInclusive = 1..=65535; fn port_in_range(s: &str) -> Result { let port: usize = s .parse() .map_err(|_| format!("`{s}` isn't a port number"))?; if PORT_RANGE.contains(&port) { Ok(port as u16) } else { Err(format!( "port not in range {}-{}", PORT_RANGE.start(), PORT_RANGE.end() )) } } ``` ### 1.5攘滩。7 參數(shù)關(guān)系(Argument Relations) 我們可以聲明 Arg 和 ArgGroup。ArgGroup 用于聲明參數(shù)關(guān)系纸泡。 ``` use std::path::PathBuf; use clap::{arg, command, value_parser, ArgAction, ArgGroup}; fn main() { // Create application like normal let matches = command!() // requires `cargo` feature // Add the version arguments .arg(arg!(--"set-ver" "set version manually")) .arg(arg!(--major "auto inc major").action(ArgAction::SetTrue)) .arg(arg!(--minor "auto inc minor").action(ArgAction::SetTrue)) .arg(arg!(--patch "auto inc patch").action(ArgAction::SetTrue)) // Create a group, make it required, and add the above arguments .group( ArgGroup::new("vers") .required(true) .args(["set-ver", "major", "minor", "patch"]), ) // Arguments can also be added to a group individually, these two arguments // are part of the "input" group which is not required .arg( arg!([INPUT_FILE] "some regular input") .value_parser(value_parser!(PathBuf)) .group("input"), ) .arg( arg!(--"spec-in" "some special input argument") .value_parser(value_parser!(PathBuf)) .group("input"), ) // Now let's assume we have a -c [config] argument which requires one of // (but **not** both) the "input" arguments .arg( arg!(config: -c ) .value_parser(value_parser!(PathBuf)) .requires("input"), ) .get_matches(); // Let's assume the old version 1.2.3 let mut major = 1; let mut minor = 2; let mut patch = 3; // See if --set-ver was used to set the version manually let version = if let Some(ver) = matches.get_one::("set-ver") { ver.to_owned() } else { // Increment the one requested (in a real program, we'd reset the lower numbers) let (maj, min, pat) = ( matches.get_flag("major"), matches.get_flag("minor"), matches.get_flag("patch"), ); match (maj, min, pat) { (true, _, _) => major += 1, (_, true, _) => minor += 1, (_, _, true) => patch += 1, _ => unreachable!(), }; format!("{major}.{minor}.{patch}") }; println!("Version: {version}"); // Check for usage of -c if matches.contains_id("config") { let input = matches .get_one::("INPUT_FILE") .unwrap_or_else(|| matches.get_one::("spec-in").unwrap()) .display(); println!( "Doing work using input {} and config {}", input, matches.get_one::("config").unwrap().display() ); } } ``` 此時 --set-ver |--major|--minor|--patch 是一個組的參數(shù)漂问。 ### 1.5.8 自定義校驗(Custom Validation) 我們可以創(chuàng)建自定義校驗錯誤 Command::error 方法可以返回指定錯誤 Error和自定義錯誤信息 ``` use std::path::PathBuf; use clap::error::ErrorKind; use clap::{arg, command, value_parser, ArgAction}; fn main() { // Create application like normal let mut cmd = command!() // requires `cargo` feature // Add the version arguments .arg(arg!(--"set-ver" "set version manually")) .arg(arg!(--major "auto inc major").action(ArgAction::SetTrue)) .arg(arg!(--minor "auto inc minor").action(ArgAction::SetTrue)) .arg(arg!(--patch "auto inc patch").action(ArgAction::SetTrue)) // Arguments can also be added to a group individually, these two arguments // are part of the "input" group which is not required .arg(arg!([INPUT_FILE] "some regular input").value_parser(value_parser!(PathBuf))) .arg( arg!(--"spec-in" "some special input argument") .value_parser(value_parser!(PathBuf)), ) // Now let's assume we have a -c [config] argument which requires one of // (but **not** both) the "input" arguments .arg(arg!(config: -c ).value_parser(value_parser!(PathBuf))); let matches = cmd.get_matches_mut(); // Let's assume the old version 1.2.3 let mut major = 1; let mut minor = 2; let mut patch = 3; // See if --set-ver was used to set the version manually let version = if let Some(ver) = matches.get_one::("set-ver") { if matches.get_flag("major") || matches.get_flag("minor") || matches.get_flag("patch") { cmd.error( ErrorKind::ArgumentConflict, "Can't do relative and absolute version change", ) .exit(); } ver.to_string() } else { // Increment the one requested (in a real program, we'd reset the lower numbers) let (maj, min, pat) = ( matches.get_flag("major"), matches.get_flag("minor"), matches.get_flag("patch"), ); match (maj, min, pat) { (true, false, false) => major += 1, (false, true, false) => minor += 1, (false, false, true) => patch += 1, _ => { cmd.error( ErrorKind::ArgumentConflict, "Can only modify one version field", ) .exit(); } }; format!("{major}.{minor}.{patch}") }; println!("Version: {version}"); // Check for usage of -c if matches.contains_id("config") { let input = matches .get_one::("INPUT_FILE") .or_else(|| matches.get_one::("spec-in")) .unwrap_or_else(|| { cmd.error( ErrorKind::MissingRequiredArgument, "INPUT_FILE or --spec-in is required when using --config", ) .exit() }) .display(); println!( "Doing work using input {} and config {}", input, matches.get_one::("config").unwrap().display() ); } } ``` ## 1.6、子命令(Subcommand) 我們可以使用 Command::subcommand 方法添加子命令女揭。每一個子命令都自己的版本蚤假、作者、參數(shù)和它的子命令吧兔。 ``` use clap::{arg, command, Command}; fn main() { let matches = command!() // requires `cargo` feature .propagate_version(true) .subcommand_required(true) .arg_required_else_help(true) .subcommand( Command::new("add") .about("Adds files to myapp") .arg(arg!([NAME])), ) .get_matches(); match matches.subcommand() { Some(("add", sub_matches)) => println!( "'myapp add' was used, name is: {:?}", sub_matches.get_one::("NAME") ), _ => unreachable!("Exhausted list of subcommands and subcommand_required prevents `None`"), } } ``` 我們使用 Command::arg_required_else_help 如果參數(shù)不存在磷仰,優(yōu)雅的退出。 使用 Command::propagate_version 可以打印命令的版本號 ## 1.7境蔼、測試 我們可以使用 debug_assert! 宏 或者 使用 Command::debug_assert 方法灶平。 ``` use clap::{arg, command, value_parser}; fn main() { let matches = cmd().get_matches(); // Note, it's safe to call unwrap() because the arg is required let port: usize = *matches .get_one::("PORT") .expect("'PORT' is required and parsing will fail if its missing"); println!("PORT = {port}"); } fn cmd() -> clap::Command { command!() // requires `cargo` feature .arg( arg!() .help("Network port to use") .value_parser(value_parser!(usize)), ) } #[test] fn verify_cmd() { cmd().debug_assert(); } ``` 本文由[mdnice](https://mdnice.com/?platform=6)多平臺發(fā)布
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市箍土,隨后出現(xiàn)的幾起案子逢享,更是在濱河造成了極大的恐慌,老刑警劉巖吴藻,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞒爬,死亡現(xiàn)場離奇詭異,居然都是意外死亡沟堡,警方通過查閱死者的電腦和手機侧但,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來航罗,“玉大人禀横,你說我怎么就攤上這事∩瞬福” “怎么了燕侠?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵者祖,是天一觀的道長立莉。 經(jīng)常有香客問我绢彤,道長,這世上最難降的妖魔是什么蜓耻? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任茫舶,我火速辦了婚禮,結(jié)果婚禮上刹淌,老公的妹妹穿的比我還像新娘饶氏。我一直安慰自己,他們只是感情好有勾,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布疹启。 她就那樣靜靜地躺著,像睡著了一般蔼卡。 火紅的嫁衣襯著肌膚如雪喊崖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天雇逞,我揣著相機與錄音荤懂,去河邊找鬼。 笑死塘砸,一個胖子當(dāng)著我的面吹牛节仿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掉蔬,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼廊宪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了女轿?” 一聲冷哼從身側(cè)響起挤忙,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谈喳,沒想到半個月后册烈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡婿禽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年赏僧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扭倾。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡淀零,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出膛壹,到底是詐尸還是另有隱情驾中,我是刑警寧澤唉堪,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站肩民,受9級特大地震影響唠亚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜持痰,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一灶搜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧工窍,春花似錦割卖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至淹仑,卻和暖如春丙挽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背攻人。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工取试, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人怀吻。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓瞬浓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蓬坡。 傳聞我的和親對象是個殘疾皇子猿棉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容