diff --git a/src/display/colorful.rs b/src/display/colorful.rs index a2e1305..9d0d173 100644 --- a/src/display/colorful.rs +++ b/src/display/colorful.rs @@ -9,261 +9,333 @@ use crate::api::post::get_one::PostEntry; use crate::api::user::info::UserInfo; use crate::args::TimeStyle; use crate::infra::iter::IteratorExt; +use crate::infra::result::IntoResult; use crate::infra::str::StrExt; use crate::infra::time::display_cnb_time; use anyhow::Result; use colored::Colorize; use std::fmt::Display; +use std::fmt::Write; use std::ops::Not; use std::path::PathBuf; use terminal_size::terminal_size; use unicode_width::UnicodeWidthStr; -pub fn login(cfg_path: &Result) { +#[inline] +pub fn fmt_err(e: &anyhow::Error) -> String { + format!("{}: {}", "Err".red(), e) +} + +#[inline] +pub fn fmt_result(result: &Result) -> String { + match result { + Ok(t) => format!("{}: {}", "Ok".green(), t), + Err(e) => fmt_err(e), + } +} + +pub fn login(cfg_path: &Result) -> String { match cfg_path { - Ok(pb) => println!("PAT was saved in {:?}", pb), - Err(e) => println_err(e), - }; + Ok(pb) => format!("PAT was saved in {:?}", pb), + Err(e) => fmt_err(e), + } } -pub fn logout(cfg_path: &Result) { +pub fn logout(cfg_path: &Result) -> String { match cfg_path { - Ok(pb) => println!("{:?} was successfully removed", pb), - Err(e) => println_err(e), + Ok(pb) => format!("{:?} was successfully removed", pb), + Err(e) => fmt_err(e), } } -pub fn user_info(info: &Result) { - match info { - Ok(info) => { - print!("{}", info.display_name.cyan()); - if info.is_vip { - print!(" {}", " VIP ".on_blue()); - } - println!(); - println!( - "{} Following {} Followers", - info.following_count, info.followers_count - ); - println!("ID {}", info.blog_id); - println!("Joined {}", info.joined); - println!("Blog https://www.cnblogs.com/{}", info.blog_app); +pub fn user_info(info: &Result) -> Result { + let info = match info { + Ok(info) => info, + Err(e) => return fmt_err(e).into_ok(), + }; + + let mut buf = String::new(); + { + let buf = &mut buf; + write!(buf, "{}", info.display_name.cyan())?; + if info.is_vip { + write!(buf, " {}", " VIP ".on_blue())?; } - Err(e) => println_err(e), + writeln!(buf)?; + writeln!( + buf, + "{} Following {} Followers", + info.following_count, info.followers_count + )?; + writeln!(buf, "ID {}", info.blog_id)?; + writeln!(buf, "Joined {}", info.joined)?; + writeln!(buf, "Blog https://www.cnblogs.com/{}", info.blog_app)?; } + buf.into_ok() } +// TODO: rm unnecessary line divider pub fn list_ing( time_style: &TimeStyle, ing_with_comment_list: &Result)>>, rev: bool, align: bool, -) { +) -> Result { let ing_with_comment_list = match ing_with_comment_list { Ok(o) => o, - Err(e) => return println_err(e), + Err(e) => return fmt_err(e).into_ok(), }; - ing_with_comment_list - .iter() - .dyn_rev(rev) - .for_each(|(ing, comment_list)| { - let create_time = display_cnb_time(&ing.create_time, time_style); - print!("{}", create_time.dimmed()); + ing_with_comment_list.iter().dyn_rev(rev).try_fold( + String::new(), + |mut buf, (ing, comment_list)| try { + { + let buf = &mut buf; + let create_time = display_cnb_time(&ing.create_time, time_style); + write!(buf, "{}", create_time.dimmed())?; - let send_from_mark = match ing.send_from { - IngSendFrom::Cli => Some("CLI"), - IngSendFrom::CellPhone => Some("Mobile"), - IngSendFrom::VsCode => Some("VSCode"), - IngSendFrom::Web => Some("Web"), - _ => None, - }; - if let Some(mark) = send_from_mark { - print!(" {}", mark.dimmed()); - } - if ing.is_lucky { - let star_text = ing_star_tag_to_text(&ing.icons); - print!(" {}⭐", star_text.yellow()); - } - println!(" {} {}", "#".dimmed(), ing.id.to_string().dimmed()); - let content = if align { - let user_name_width = ing.user_name.width_cjk(); - let term_width = terminal_size().expect("Can not get terminal size").0 .0 as usize; - let left_width = term_width.saturating_sub(user_name_width + 3); - fmt_content(&ing.content) - .width_split(left_width) - .map_or_else( - || ing.content.clone(), - |lines| { - if comment_list.is_empty().not() { - lines.join("\n").replace( - '\n', - &format!("\n │{}", " ".repeat(user_name_width - 2)), - ) - } else { - lines.join("\n").replace( - '\n', - &format!("\n{}", " ".repeat(user_name_width + 3)), - ) + let send_from_mark = match ing.send_from { + IngSendFrom::Cli => Some("CLI"), + IngSendFrom::CellPhone => Some("Mobile"), + IngSendFrom::VsCode => Some("VSCode"), + IngSendFrom::Web => Some("Web"), + _ => None, + }; + if let Some(mark) = send_from_mark { + write!(buf, " {}", mark.dimmed())?; + } + if ing.is_lucky { + let star_text = ing_star_tag_to_text(&ing.icons); + write!(buf, " {}⭐", star_text.yellow())?; + } + writeln!(buf, " {} {}", "#".dimmed(), ing.id.to_string().dimmed())?; + let content = if align { + let user_name_width = ing.user_name.width_cjk(); + let term_width = + terminal_size().expect("Can not get terminal size").0 .0 as usize; + let left_width = term_width.saturating_sub(user_name_width + 3); + fmt_content(&ing.content) + .width_split(left_width) + .map_or_else( + || ing.content.clone(), + |lines| { + if comment_list.is_empty().not() { + lines.join("\n").replace( + '\n', + &format!("\n │{}", " ".repeat(user_name_width - 2)), + ) + } else { + lines.join("\n").replace( + '\n', + &format!("\n{}", " ".repeat(user_name_width + 3)), + ) + } + }, + ) + } else { + fmt_content(&ing.content) + }; + writeln!(buf, " {} {}", ing.user_name.cyan(), content)?; + + let len = comment_list.len(); + if len != 0 { + let max_i = len - 1; + let comment_list_buf: Result = comment_list + .iter() + .enumerate() + .try_fold(String::new(), |mut buf, (i, entry)| try { + { + let buf = &mut buf; + if i != max_i { + write!(buf, " │ {}", entry.user_name.blue())?; + } else { + write!(buf, " └ {}", entry.user_name.blue())?; + } + let at_user = get_ing_at_user_tag_text(&entry.content); + if at_user.is_empty().not() { + write!( + buf, + " {}{}", + "@".bright_black(), + at_user.bright_black() + )?; + } + let content = { + let content = rm_ing_at_user_tag(&entry.content); + fmt_content(&content) + }; + writeln!(buf, " {}", content.dimmed())?; } - }, - ) - } else { - fmt_content(&ing.content) - }; - println!(" {} {}", ing.user_name.cyan(), content); + buf + }); + write!(buf, "{}", comment_list_buf?)?; + } - let len = comment_list.len(); - if len != 0 { - let max_i = len - 1; - comment_list.iter().enumerate().for_each(|(i, entry)| { - if i != max_i { - print!(" │ {}", entry.user_name.blue()); - } else { - print!(" └ {}", entry.user_name.blue()); - } - let at_user = get_ing_at_user_tag_text(&entry.content); - if at_user.is_empty().not() { - print!(" {}{}", "@".bright_black(), at_user.bright_black()); - } - let content = { - let content = rm_ing_at_user_tag(&entry.content); - fmt_content(&content) - }; - println!(" {}", content.dimmed()); - }); - } - println!(); - }); + writeln!(buf)?; + }; + buf + }, + ) } -pub fn show_post(entry: &Result) { - match entry { - Ok(entry) => { - println!("{}\n", entry.title.cyan().bold()); - if let Some(body) = &entry.body { - println!("{}", body); - } +pub fn show_post(entry: &Result) -> Result { + let entry = match entry { + Ok(entry) => entry, + Err(e) => return fmt_err(e).into_ok(), + }; + + let mut buf = String::new(); + { + let buf = &mut buf; + writeln!(buf, "{}\n", entry.title.cyan().bold())?; + if let Some(body) = &entry.body { + writeln!(buf, "{}", body)?; } - Err(e) => println_err(e), } + buf.into_ok() } -pub fn show_post_meta(time_style: &TimeStyle, entry: &Result) { +pub fn show_post_meta(time_style: &TimeStyle, entry: &Result) -> Result { let entry = match entry { Ok(entry) => entry, - Err(e) => return println_err(e), + Err(e) => return fmt_err(e).into_ok(), }; - println!("Title {}", entry.title.cyan().bold()); + let mut buf = String::new(); { - print!("Status"); - if entry.is_published { - print!(" {}", "Published".green()); - } else { - print!(" {}", "Draft".yellow()); - } - if entry.is_pinned { - print!(" {}", "Pinned".magenta()); - } - println!() - }; - if let Some(body) = &entry.body { - let words_count = words_count::count(body).words; - println!("Words {}", words_count); - } - if let Some(tags) = &entry.tags { - if let Some(tags_text) = tags - .clone() - .into_iter() - .reduce(|acc, tag| format!("{}, {}", acc, tag)) + let buf = &mut buf; + writeln!(buf, "Title {}", entry.title.cyan().bold())?; { - println!("Tags {}", tags_text); + write!(buf, "Status")?; + if entry.is_published { + write!(buf, " {}", "Published".green())?; + } else { + write!(buf, " {}", "Draft".yellow())?; + } + if entry.is_pinned { + write!(buf, " {}", "Pinned".magenta())?; + } + writeln!(buf)?; + }; + if let Some(body) = &entry.body { + let words_count = words_count::count(body).words; + writeln!(buf, "Words {}", words_count)?; } + if let Some(tags) = &entry.tags { + if let Some(tags_text) = tags + .clone() + .into_iter() + .reduce(|acc, tag| format!("{}, {}", acc, tag)) + { + writeln!(buf, "Tags {}", tags_text)?; + } + } + let create_time = display_cnb_time(&entry.create_time, time_style); + writeln!(buf, "Create {}", create_time)?; + let modify_time = display_cnb_time(&entry.create_time, time_style); + writeln!(buf, "Modify {}", modify_time)?; + writeln!(buf, "Link https:{}", entry.url)?; } - let create_time = display_cnb_time(&entry.create_time, time_style); - println!("Create {}", create_time); - let modify_time = display_cnb_time(&entry.create_time, time_style); - println!("Modify {}", modify_time); - println!("Link https:{}", entry.url); + buf.into_ok() } pub fn show_post_comment( time_style: &TimeStyle, comment_list: &Result>, rev: bool, -) { +) -> Result { let comment_list = match comment_list { Ok(entry) => entry, - Err(e) => return println_err(e), + Err(e) => return fmt_err(e).into_ok(), }; - comment_list.iter().dyn_rev(rev).for_each(|comment| { - let create_time = display_cnb_time(&comment.create_time, time_style); - let floor_text = format!("{}F", comment.floor); - println!("{} {}", create_time.dimmed(), floor_text.dimmed()); - println!(" {} {}", comment.user_name.cyan(), comment.content); - }) + comment_list + .iter() + .dyn_rev(rev) + .try_fold(String::new(), |mut buf, comment| try { + { + let buf = &mut buf; + let create_time = display_cnb_time(&comment.create_time, time_style); + let floor_text = format!("{}F", comment.floor); + writeln!(buf, "{} {}", create_time.dimmed(), floor_text.dimmed())?; + writeln!(buf, " {} {}", comment.user_name.cyan(), comment.content)?; + } + buf + }) } -pub fn list_post(result: &Result<(Vec, usize)>, rev: bool) { +pub fn list_post(result: &Result<(Vec, usize)>, rev: bool) -> Result { let (entry_list, total_count) = match result { Ok(o) => o, - Err(e) => return println_err(e), + Err(e) => return fmt_err(e).into_ok(), }; - println!("{}/{}", entry_list.len(), total_count); - entry_list.iter().dyn_rev(rev).for_each(|entry| { - print!("{} {}", "#".dimmed(), entry.id.to_string().dimmed()); - print!(" {}", entry.title.cyan().bold()); - if entry.is_published { - print!(" {}", "Pub".green()); - } else { - print!(" {}", "Dft".yellow()); - } - if entry.is_pinned { - print!(" {}", "Pin".magenta()); - } - println!() - }); + entry_list.iter().dyn_rev(rev).try_fold( + //TODO: formatln! macro + format!("{}/{}\n", entry_list.len(), total_count), + |mut buf, entry| try { + { + let buf = &mut buf; + write!(buf, "{} {}", "#".dimmed(), entry.id.to_string().dimmed())?; + write!(buf, " {}", entry.title.cyan().bold())?; + if entry.is_published { + write!(buf, " {}", "Pub".green())?; + } else { + write!(buf, " {}", "Dft".yellow())?; + } + if entry.is_pinned { + write!(buf, " {}", "Pin".magenta())?; + } + writeln!(buf)?; + } + buf + }, + ) } -pub fn search_post(result: &Result<(Vec, usize)>, rev: bool) { +pub fn search_post(result: &Result<(Vec, usize)>, rev: bool) -> Result { let (id_list, total_count) = match result { Ok(o) => o, - Err(e) => return println_err(e), + Err(e) => return fmt_err(e).into_ok(), }; - println!("{}/{}", id_list.len(), total_count); - id_list - .iter() - .dyn_rev(rev) - .for_each(|id| println!("# {}", id)); -} - -pub fn println_err(e: &anyhow::Error) { - println!("{}: {}", "Err".red(), e) -} - -pub fn println_result(result: &Result) { - match result { - Ok(t) => println!("{}: {}", "Ok".green(), t), - Err(e) => println!("{}: {}", "Err".red(), e), - } + id_list.iter().dyn_rev(rev).try_fold( + format!("{}/{}\n", id_list.len(), total_count), + |mut buf, id| try { + writeln!(&mut buf, "# {}", id)?; + buf + }, + ) } -pub fn list_news(time_style: &TimeStyle, news_list: &Result>, rev: bool) { +// TODO: auto align +pub fn list_news( + time_style: &TimeStyle, + news_list: &Result>, + rev: bool, +) -> Result { let news_list = match news_list { Ok(o) => o, - Err(e) => return println_err(e), + Err(e) => return fmt_err(e).into_ok(), }; - news_list.iter().dyn_rev(rev).for_each(|news| { - let create_time = display_cnb_time(&news.create_time, time_style); - let url = format!("https://news.cnblogs.com/n/{}", news.id); - println!("{} {}", create_time.dimmed(), url.dimmed(),); - println!(" {}", news.title); - println!(" {}{}", news.summary.dimmed(), "...".dimmed()); - println!(); - }); + news_list + .iter() + .dyn_rev(rev) + .map(|news| try { + let mut buf = String::new(); + { + let buf = &mut buf; + let create_time = display_cnb_time(&news.create_time, time_style); + let url = format!("https://news.cnblogs.com/n/{}", news.id); + writeln!(buf, "{} {}", create_time.dimmed(), url.dimmed())?; + writeln!(buf, " {}", news.title)?; + writeln!(buf, " {}{}", news.summary.dimmed(), "...".dimmed())?; + } + buf + }) + .try_fold(String::new(), |mut acc, buf: Result| try { + write!(&mut acc, "\n{}", buf?)?; + acc + }) } diff --git a/src/display/json.rs b/src/display/json.rs index 9c7d1fa..3bab319 100644 --- a/src/display/json.rs +++ b/src/display/json.rs @@ -6,29 +6,42 @@ use crate::api::post::get_one::PostEntry; use crate::api::user::info::UserInfo; use crate::infra::iter::IteratorExt; use crate::infra::json; +use crate::infra::result::IntoResult; use anyhow::Result; use serde::Serialize; use serde_json::json; use std::path::PathBuf; -pub fn login(cfg_path: &Result) { +#[inline] +pub fn fmt_err(e: &anyhow::Error) -> String { + let json = json!({ + "is_ok": false, + "msg": e.to_string() + }); + json.to_string() +} + +pub fn login(cfg_path: &Result) -> String { let json = cfg_path.as_ref().map(|pb| json!({"cfg_path":pb})); - println_result(&json); + fmt_result(&json) } -pub fn logout(cfg_path: &Result) { +pub fn logout(cfg_path: &Result) -> String { let json = cfg_path.as_ref().map(|pb| json!({"cfg_path":pb})); - println_result(&json); + fmt_result(&json) } -pub fn user_info(info: &Result) { - println_result(info); +pub fn user_info(info: &Result) -> String { + fmt_result(info) } -pub fn list_ing(ing_with_comment_list: &Result)>>, rev: bool) { +pub fn list_ing( + ing_with_comment_list: &Result)>>, + rev: bool, +) -> Result { let ing_with_comment_list = match ing_with_comment_list { Ok(o) => o, - Err(e) => return println_err(e), + Err(e) => return fmt_err(e).into_ok(), }; let json_vec = ing_with_comment_list @@ -42,39 +55,40 @@ pub fn list_ing(ing_with_comment_list: &Result>(); - let json = json::serialize(json_vec).expect("Can not serialize json_vec"); - print!("{}", json); + json::serialize(json_vec) } -pub fn show_post(entry: &Result) { +pub fn show_post(entry: &Result) -> String { let json = entry.as_ref().map(|entry| { json!({ "title": entry.title, "body": entry.body }) }); - println_result(&json); + fmt_result(&json) } -pub fn show_post_meta(entry: &Result) { - println_result(entry); +pub fn show_post_meta(entry: &Result) -> String { + fmt_result(entry) } -pub fn show_post_comment(comment_list: &Result>, rev: bool) { +pub fn show_post_comment( + comment_list: &Result>, + rev: bool, +) -> Result { let comment_list = match comment_list { Ok(entry) => entry, - Err(e) => return println_err(e), + Err(e) => return fmt_err(e).into_ok(), }; let comment_vec = comment_list.iter().dyn_rev(rev).collect::>(); - let json = json::serialize(comment_vec).expect("Can not serialize comment_vec"); - print!("{}", json); + json::serialize(comment_vec) } -pub fn list_post(result: &Result<(Vec, usize)>, rev: bool) { +pub fn list_post(result: &Result<(Vec, usize)>, rev: bool) -> String { let (entry_list, total_count) = match result { Ok(o) => o, - Err(e) => return println_err(e), + Err(e) => return fmt_err(e), }; let vec = entry_list.iter().dyn_rev(rev).collect::>(); @@ -83,13 +97,13 @@ pub fn list_post(result: &Result<(Vec, usize)>, rev: bool) { "total_count": total_count, "entry_list": vec, }); - print!("{}", json); + json.to_string() } -pub fn search_post(result: &Result<(Vec, usize)>, rev: bool) { +pub fn search_post(result: &Result<(Vec, usize)>, rev: bool) -> String { let (id_list, total_count) = match result { Ok(o) => o, - Err(e) => return println_err(e), + Err(e) => return fmt_err(e), }; let id_list = id_list.iter().dyn_rev(rev).collect::>(); @@ -98,19 +112,10 @@ pub fn search_post(result: &Result<(Vec, usize)>, rev: bool) { "total_count": total_count, "id_list": id_list, }); - - println!("{}", json); -} - -pub fn println_err(e: &anyhow::Error) { - let json = json!({ - "is_ok": false, - "msg": e.to_string() - }); - println!("{}", json) + json.to_string() } -pub fn println_result(result: &Result) { +pub fn fmt_result(result: &Result) -> String { let json = match result { Ok(t) => json!({ "is_ok": true, @@ -121,18 +126,16 @@ pub fn println_result(result: &Result) { "msg": e.to_string() }), }; - println!("{}", json) + json.to_string() } -pub fn list_news(news_list: &Result>, rev: bool) { +pub fn list_news(news_list: &Result>, rev: bool) -> Result { let news_list = match news_list { Ok(o) => o, - Err(e) => return println_err(e), + Err(e) => return fmt_err(e).into_ok(), }; let vec = news_list.iter().dyn_rev(rev).collect::>(); - let json = - json::serialize(vec.clone()).unwrap_or_else(|_| panic!("Can not serialize: {:?}", vec)); - print!("{}", json); + json::serialize(vec.clone()) } diff --git a/src/display/mod.rs b/src/display/mod.rs index 6c365d8..e1f94df 100644 --- a/src/display/mod.rs +++ b/src/display/mod.rs @@ -5,6 +5,7 @@ use crate::api::post::get_comment_list::PostCommentEntry; use crate::api::post::get_one::PostEntry; use crate::api::user::info::UserInfo; use crate::args::{Style, TimeStyle}; +use crate::infra::result::IntoResult; use anyhow::Result; use std::path::PathBuf; @@ -12,7 +13,7 @@ mod colorful; mod json; mod normal; -pub fn login(style: &Style, cfg_path: &Result) { +pub fn login(style: &Style, cfg_path: &Result) -> String { match style { Style::Colorful => colorful::login(cfg_path), Style::Normal => normal::login(cfg_path), @@ -20,7 +21,7 @@ pub fn login(style: &Style, cfg_path: &Result) { } } -pub fn logout(style: &Style, cfg_path: &Result) { +pub fn logout(style: &Style, cfg_path: &Result) -> String { match style { Style::Colorful => colorful::logout(cfg_path), Style::Normal => normal::logout(cfg_path), @@ -28,11 +29,11 @@ pub fn logout(style: &Style, cfg_path: &Result) { } } -pub fn user_info(style: &Style, user_info: &Result) { +pub fn user_info(style: &Style, user_info: &Result) -> Result { match style { Style::Colorful => colorful::user_info(user_info), Style::Normal => normal::user_info(user_info), - Style::Json => json::user_info(user_info), + Style::Json => json::user_info(user_info).into_ok(), } } @@ -42,7 +43,7 @@ pub fn list_ing( ing_with_comment_list: &Result)>>, rev: bool, align: bool, -) { +) -> Result { match style { Style::Colorful => colorful::list_ing(time_style, ing_with_comment_list, rev, align), Style::Normal => normal::list_ing(time_style, ing_with_comment_list, rev, align), @@ -50,35 +51,39 @@ pub fn list_ing( } } -pub fn publish_ing(style: &Style, result: &Result<&String>) { +pub fn publish_ing(style: &Style, result: &Result<&String>) -> String { match style { - Style::Colorful => colorful::println_result(result), - Style::Normal => normal::println_result(result), - Style::Json => json::println_result(result), + Style::Colorful => colorful::fmt_result(result), + Style::Normal => normal::fmt_result(result), + Style::Json => json::fmt_result(result), } } -pub fn comment_ing(style: &Style, result: &Result<&String>) { +pub fn comment_ing(style: &Style, result: &Result<&String>) -> String { match style { - Style::Colorful => colorful::println_result(result), - Style::Normal => normal::println_result(result), - Style::Json => json::println_result(result), + Style::Colorful => colorful::fmt_result(result), + Style::Normal => normal::fmt_result(result), + Style::Json => json::fmt_result(result), } } -pub fn show_post(style: &Style, entry: &Result) { +pub fn show_post(style: &Style, entry: &Result) -> Result { match style { Style::Colorful => colorful::show_post(entry), Style::Normal => normal::show_post(entry), - Style::Json => json::show_post(entry), + Style::Json => json::show_post(entry).into_ok(), } } -pub fn show_post_meta(style: &Style, time_style: &TimeStyle, entry: &Result) { +pub fn show_post_meta( + style: &Style, + time_style: &TimeStyle, + entry: &Result, +) -> Result { match style { Style::Colorful => colorful::show_post_meta(time_style, entry), Style::Normal => normal::show_post_meta(time_style, entry), - Style::Json => json::show_post_meta(entry), + Style::Json => json::show_post_meta(entry).into_ok(), } } @@ -87,7 +92,7 @@ pub fn show_post_comment( time_style: &TimeStyle, comment_list: &Result>, rev: bool, -) { +) -> Result { match style { Style::Colorful => colorful::show_post_comment(time_style, comment_list, rev), Style::Normal => normal::show_post_comment(time_style, comment_list, rev), @@ -95,43 +100,51 @@ pub fn show_post_comment( } } -pub fn list_post(style: &Style, result: &Result<(Vec, usize)>, rev: bool) { +pub fn list_post( + style: &Style, + result: &Result<(Vec, usize)>, + rev: bool, +) -> Result { match style { Style::Colorful => colorful::list_post(result, rev), Style::Normal => normal::list_post(result, rev), - Style::Json => json::list_post(result, rev), + Style::Json => json::list_post(result, rev).into_ok(), } } -pub fn delete_post(style: &Style, result: &Result) { +pub fn delete_post(style: &Style, result: &Result) -> String { match style { - Style::Colorful => colorful::println_result(result), - Style::Normal => normal::println_result(result), - Style::Json => json::println_result(result), + Style::Colorful => colorful::fmt_result(result), + Style::Normal => normal::fmt_result(result), + Style::Json => json::fmt_result(result), } } -pub fn search_post(style: &Style, result: &Result<(Vec, usize)>, rev: bool) { +pub fn search_post( + style: &Style, + result: &Result<(Vec, usize)>, + rev: bool, +) -> Result { match style { Style::Colorful => colorful::search_post(result, rev), Style::Normal => normal::search_post(result, rev), - Style::Json => json::search_post(result, rev), + Style::Json => json::search_post(result, rev).into_ok(), } } -pub fn create_post(style: &Style, result: &Result) { +pub fn create_post(style: &Style, result: &Result) -> String { match style { - Style::Colorful => colorful::println_result(result), - Style::Normal => normal::println_result(result), - Style::Json => json::println_result(result), + Style::Colorful => colorful::fmt_result(result), + Style::Normal => normal::fmt_result(result), + Style::Json => json::fmt_result(result), } } -pub fn update_post(style: &Style, result: &Result) { +pub fn update_post(style: &Style, result: &Result) -> String { match style { - Style::Colorful => colorful::println_result(result), - Style::Normal => normal::println_result(result), - Style::Json => json::println_result(result), + Style::Colorful => colorful::fmt_result(result), + Style::Normal => normal::fmt_result(result), + Style::Json => json::fmt_result(result), } } @@ -140,7 +153,7 @@ pub fn list_news( time_style: &TimeStyle, news_list: &Result>, rev: bool, -) { +) -> Result { match style { Style::Colorful => colorful::list_news(time_style, news_list, rev), Style::Normal => normal::list_news(time_style, news_list, rev), diff --git a/src/display/normal.rs b/src/display/normal.rs index 3042e06..35603a8 100644 --- a/src/display/normal.rs +++ b/src/display/normal.rs @@ -9,260 +9,327 @@ use crate::api::post::get_one::PostEntry; use crate::api::user::info::UserInfo; use crate::args::TimeStyle; use crate::infra::iter::IteratorExt; +use crate::infra::result::IntoResult; use crate::infra::str::StrExt; use crate::infra::time::display_cnb_time; -use anyhow::Result; -use colored::Colorize; -use std::fmt::Display; +use anyhow::{Context, Result}; +use std::fmt::{Display, Write}; use std::ops::Not; use std::path::PathBuf; use terminal_size::terminal_size; use unicode_width::UnicodeWidthStr; -pub fn login(cfg_path: &Result) { +#[inline] +pub fn fmt_err(e: &anyhow::Error) -> String { + format!("Err: {}", e) +} + +#[inline] +pub fn fmt_result(result: &Result) -> String { + match result { + Ok(t) => format!("Ok: {}", t), + Err(e) => fmt_err(e), + } +} + +pub fn login(cfg_path: &Result) -> String { match cfg_path { - Ok(pb) => println!("PAT was saved in {:?}", pb), - Err(e) => println_err(e), - }; + Ok(pb) => format!("PAT was saved in {:?}", pb), + Err(e) => fmt_err(e), + } } -pub fn logout(cfg_path: &Result) { +pub fn logout(cfg_path: &Result) -> String { match cfg_path { - Ok(pb) => println!("{:?} was successfully removed", pb), - Err(e) => println_err(e), + Ok(pb) => format!("{:?} was successfully removed", pb), + Err(e) => fmt_err(e), } } -pub fn user_info(info: &Result) { - match info { - Ok(info) => { - print!("{}", info.display_name); - if info.is_vip { - print!(" VIP"); - } - println!(); - println!( - "{} Following {} Followers", - info.following_count, info.followers_count - ); - println!("ID {}", info.blog_id); - println!("Joined {}", info.joined); - println!("Blog https://www.cnblogs.com/{}", info.blog_app); +pub fn user_info(info: &Result) -> Result { + let info = match info { + Ok(info) => info, + Err(e) => return fmt_err(e).into_ok(), + }; + + let mut buf = String::new(); + { + let buf = &mut buf; + write!(buf, "{}", info.display_name)?; + if info.is_vip { + write!(buf, " VIP")?; } - Err(e) => println_err(e), + writeln!(buf)?; + writeln!( + buf, + "{} Following {} Followers", + info.following_count, info.followers_count + )?; + writeln!(buf, "ID {}", info.blog_id)?; + writeln!(buf, "Joined {}", info.joined)?; + writeln!(buf, "Blog https://www.cnblogs.com/{}", info.blog_app)?; } + buf.into_ok() } +// TODO: rm unnecessary line divider pub fn list_ing( time_style: &TimeStyle, ing_with_comment_list: &Result)>>, rev: bool, align: bool, -) { +) -> Result { let ing_with_comment_list = match ing_with_comment_list { Ok(o) => o, - Err(e) => return println_err(e), + Err(e) => return fmt_err(e).into_ok(), }; - ing_with_comment_list - .iter() - .dyn_rev(rev) - .for_each(|(ing, comment_list)| { - let create_time = display_cnb_time(&ing.create_time, time_style); - print!("{}", create_time); + ing_with_comment_list.iter().dyn_rev(rev).try_fold( + String::new(), + |mut buf, (ing, comment_list)| try { + { + let buf = &mut buf; + let create_time = display_cnb_time(&ing.create_time, time_style); + write!(buf, "{}", create_time)?; - let send_from_mark = match ing.send_from { - IngSendFrom::Cli => Some("CLI"), - IngSendFrom::CellPhone => Some("Mobile"), - IngSendFrom::VsCode => Some("VSCode"), - IngSendFrom::Web => Some("Web"), - _ => None, - }; - if let Some(mark) = send_from_mark { - print!(" {}", mark.dimmed()); - } - if ing.is_lucky { - let star_text = ing_star_tag_to_text(&ing.icons); - print!(" {}⭐", star_text); - } - println!(" # {}", ing.id); - let content = if align { - let user_name_width = ing.user_name.width_cjk(); - let term_width = terminal_size().expect("Can not get terminal size").0 .0 as usize; - let left_width = term_width.saturating_sub(user_name_width + 3); - fmt_content(&ing.content) - .width_split(left_width) - .map_or_else( - || ing.content.clone(), - |lines| { - if comment_list.is_empty().not() { - lines.join("\n").replace( - '\n', - &format!("\n │{}", " ".repeat(user_name_width - 2)), - ) - } else { - lines.join("\n").replace( - '\n', - &format!("\n{}", " ".repeat(user_name_width + 3)), - ) + let send_from_mark = match ing.send_from { + IngSendFrom::Cli => Some("CLI"), + IngSendFrom::CellPhone => Some("Mobile"), + IngSendFrom::VsCode => Some("VSCode"), + IngSendFrom::Web => Some("Web"), + _ => None, + }; + if let Some(mark) = send_from_mark { + write!(buf, " {}", mark)?; + } + if ing.is_lucky { + let star_text = ing_star_tag_to_text(&ing.icons); + write!(buf, " {}★", star_text)?; + } + writeln!(buf, " # {}", ing.id)?; + let content = if align { + let user_name_width = ing.user_name.width_cjk(); + let term_width = terminal_size() + .with_context(|| "Can not get terminal size")? + .0 + .0 as usize; + let left_width = term_width.saturating_sub(user_name_width + 3); + fmt_content(&ing.content) + .width_split(left_width) + .map_or_else( + || ing.content.clone(), + |lines| { + if comment_list.is_empty().not() { + lines.join("\n").replace( + '\n', + &format!("\n │{}", " ".repeat(user_name_width - 2)), + ) + } else { + lines.join("\n").replace( + '\n', + &format!("\n{}", " ".repeat(user_name_width + 3)), + ) + } + }, + ) + } else { + fmt_content(&ing.content) + }; + writeln!(buf, " {}: {}", ing.user_name, content)?; + + let len = comment_list.len(); + if len != 0 { + let max_i = len - 1; + let comment_list_buf: Result = comment_list + .iter() + .enumerate() + .try_fold(String::new(), |mut buf, (i, entry)| try { + { + let buf = &mut buf; + if i != max_i { + write!(buf, " │ {}", entry.user_name)?; + } else { + write!(buf, " └ {}", entry.user_name)?; + } + let at_user = get_ing_at_user_tag_text(&entry.content); + if at_user.is_empty().not() { + write!(buf, " @{}", at_user)?; + } + let content = { + let content = rm_ing_at_user_tag(&entry.content); + fmt_content(&content) + }; + writeln!(buf, ": {}", content)?; } - }, - ) - } else { - fmt_content(&ing.content) - }; - println!(" {} {}", ing.user_name, content); + buf + }); + write!(buf, "{}", comment_list_buf?)?; + } - let len = comment_list.len(); - if len != 0 { - let max_i = len - 1; - comment_list.iter().enumerate().for_each(|(i, entry)| { - if i != max_i { - print!(" │ {}: ", entry.user_name); - } else { - print!(" └ {}: ", entry.user_name); - } - let at_user = get_ing_at_user_tag_text(&entry.content); - if at_user.is_empty().not() { - print!(" @{}", at_user); - } - let content = { - let content = rm_ing_at_user_tag(&entry.content); - fmt_content(&content) - }; - println!(" {}", content); - }); - } - println!(); - }); + writeln!(buf)?; + }; + buf + }, + ) } -pub fn show_post(entry: &Result) { - match entry { - Ok(entry) => { - println!("{}\n", entry.title); - if let Some(body) = &entry.body { - println!("{}", body); - } +pub fn show_post(entry: &Result) -> Result { + let entry = match entry { + Ok(entry) => entry, + Err(e) => return fmt_err(e).into_ok(), + }; + + let mut buf = String::new(); + { + let buf = &mut buf; + writeln!(buf, "{}\n", entry.title)?; + if let Some(body) = &entry.body { + writeln!(buf, "{}", body)?; } - Err(e) => println_err(e), } + buf.into_ok() } -pub fn show_post_meta(time_style: &TimeStyle, entry: &Result) { +pub fn show_post_meta(time_style: &TimeStyle, entry: &Result) -> Result { let entry = match entry { - Ok(o) => o, - Err(e) => return println_err(e), + Ok(entry) => entry, + Err(e) => return fmt_err(e).into_ok(), }; - println!("Title {}", entry.title); + let mut buf = String::new(); { - print!("Status"); - if entry.is_published { - print!(" Published"); - } else { - print!(" Draft"); - } - if entry.is_pinned { - print!(" Pinned"); - } - println!() - }; - if let Some(body) = &entry.body { - let words_count = words_count::count(body).words; - println!("Words {}", words_count); - } - if let Some(tags) = &entry.tags { - if let Some(tags_text) = tags - .clone() - .into_iter() - .reduce(|acc, tag| format!("{}, {}", acc, tag)) + let buf = &mut buf; + writeln!(buf, "Title {}", entry.title)?; { - println!("Tags {}", tags_text); + write!(buf, "Status")?; + if entry.is_published { + write!(buf, " Published")?; + } else { + write!(buf, " Draft")?; + } + if entry.is_pinned { + write!(buf, " Pinned")?; + } + writeln!(buf)?; + }; + if let Some(body) = &entry.body { + let words_count = words_count::count(body).words; + writeln!(buf, "Words {}", words_count)?; } + if let Some(tags) = &entry.tags { + if let Some(tags_text) = tags + .clone() + .into_iter() + .reduce(|acc, tag| format!("{}, {}", acc, tag)) + { + writeln!(buf, "Tags {}", tags_text)?; + } + } + let create_time = display_cnb_time(&entry.create_time, time_style); + writeln!(buf, "Create {}", create_time)?; + let modify_time = display_cnb_time(&entry.create_time, time_style); + writeln!(buf, "Modify {}", modify_time)?; + writeln!(buf, "Link https:{}", entry.url)?; } - let create_time = display_cnb_time(&entry.create_time, time_style); - println!("Create {}", create_time); - let modify_time = display_cnb_time(&entry.modify_time, time_style); - println!("Modify {}", modify_time); - println!("Link https:{}", entry.url); + buf.into_ok() } pub fn show_post_comment( time_style: &TimeStyle, comment_list: &Result>, rev: bool, -) { +) -> Result { let comment_list = match comment_list { Ok(entry) => entry, - Err(e) => return println_err(e), + Err(e) => return fmt_err(e).into_ok(), }; - comment_list.iter().dyn_rev(rev).for_each(|comment| { - let create_time = display_cnb_time(&comment.create_time, time_style); - println!("{} {}F", create_time, comment.floor); - println!(" {} {}", comment.user_name, comment.content); - }) + comment_list + .iter() + .dyn_rev(rev) + .try_fold(String::new(), |mut buf, comment| try { + { + let buf = &mut buf; + let create_time = display_cnb_time(&comment.create_time, time_style); + writeln!(buf, "{} {}F", create_time, comment.floor)?; + writeln!(buf, " {} {}", comment.user_name, comment.content)?; + } + buf + }) } -pub fn list_post(result: &Result<(Vec, usize)>, rev: bool) { +pub fn list_post(result: &Result<(Vec, usize)>, rev: bool) -> Result { let (entry_list, total_count) = match result { Ok(o) => o, - Err(e) => return println_err(e), + Err(e) => return fmt_err(e).into_ok(), }; - println!("{}/{}", entry_list.len(), total_count); - entry_list.iter().dyn_rev(rev).for_each(|entry| { - print!("# {}", entry.id); - print!(" {}", entry.title); - if entry.is_published { - print!(" Pub"); - } else { - print!(" Dft"); - } - if entry.is_pinned { - print!(" Pin"); - } - println!() - }); + entry_list.iter().dyn_rev(rev).try_fold( + //TODO: formatln! macro + format!("{}/{}\n", entry_list.len(), total_count), + |mut buf, entry| try { + { + let buf = &mut buf; + write!(buf, "# {}", entry.id)?; + write!(buf, " {}", entry.title)?; + if entry.is_published { + write!(buf, " Pub")?; + } else { + write!(buf, " Dft")?; + } + if entry.is_pinned { + write!(buf, " Pin")?; + } + writeln!(buf)?; + } + buf + }, + ) } -pub fn search_post(result: &Result<(Vec, usize)>, rev: bool) { +pub fn search_post(result: &Result<(Vec, usize)>, rev: bool) -> Result { let (id_list, total_count) = match result { Ok(o) => o, - Err(e) => return println_err(e), + Err(e) => return fmt_err(e).into_ok(), }; - println!("{}/{}", id_list.len(), total_count); - id_list - .iter() - .dyn_rev(rev) - .for_each(|id| println!("# {}", id)); -} - -pub fn println_err(e: &anyhow::Error) { - println!("Err: {}", e) -} - -pub fn println_result(result: &Result) { - match result { - Ok(t) => println!("Ok: {}", t), - Err(e) => println!("Err: {}", e), - } + id_list.iter().dyn_rev(rev).try_fold( + format!("{}/{}\n", id_list.len(), total_count), + |mut buf, id| try { + writeln!(&mut buf, "# {}", id)?; + buf + }, + ) } -pub fn list_news(time_style: &TimeStyle, news_list: &Result>, rev: bool) { +// TODO: auto align +pub fn list_news( + time_style: &TimeStyle, + news_list: &Result>, + rev: bool, +) -> Result { let news_list = match news_list { Ok(o) => o, - Err(e) => return println_err(e), + Err(e) => return fmt_err(e).into_ok(), }; - news_list.iter().dyn_rev(rev).for_each(|news| { - let create_time = display_cnb_time(&news.create_time, time_style); - let url = format!("https://news.cnblogs.com/n/{}", news.id); - println!("{} {}", create_time, url); - println!(" {}", news.title); - println!(" {}...", news.summary); - println!(); - }); + news_list + .iter() + .dyn_rev(rev) + .map(|news| try { + let mut buf = String::new(); + { + let buf = &mut buf; + let create_time = display_cnb_time(&news.create_time, time_style); + let url = format!("https://news.cnblogs.com/n/{}", news.id); + writeln!(buf, "{} {}", create_time, url)?; + writeln!(buf, " {}", news.title)?; + writeln!(buf, " {}...", news.summary)?; + } + buf + }) + .try_fold(String::new(), |mut acc, buf: Result| try { + write!(&mut acc, "\n{}", buf?)?; + acc + }) } diff --git a/src/main.rs b/src/main.rs index d28b7b4..697962d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ #![feature(let_chains)] #![feature(type_name_of_val)] #![feature(iterator_try_collect)] +#![feature(iterator_try_reduce)] #![warn(clippy::all, clippy::nursery, clippy::cargo_common_metadata)] use crate::api::auth::session; @@ -27,12 +28,6 @@ pub mod args; pub mod display; pub mod infra; -fn panic_if_err(result: &Result) { - if let Err(e) = result { - panic!("{}", e) - } -} - #[tokio::main(flavor = "multi_thread")] async fn main() -> Result<()> { let args_vec = env::args().collect::>(); @@ -49,24 +44,19 @@ async fn main() -> Result<()> { let style = &args.style; let time_style = &args.time_style; let rev = args.rev; - let foe = args.fail_on_error; - let quiet = args.quiet; - match args { + let output = match args { _ if let Some(pat) = parser::login(&args) => { let cfg_path = session::login(pat); - foe.then(|| panic_if_err(&cfg_path)); - quiet.not().then(|| display::login(style, &cfg_path)); + display::login(style, &cfg_path) } _ if parser::logout(&args) => { let cfg_path = &session::logout(); - foe.then(|| panic_if_err(cfg_path)); - quiet.not().then(|| display::logout(style, cfg_path)); + display::logout(style, cfg_path) } _ if parser::user_info(&args) => { let user_info = User::new(pat?).get_info().await; - foe.then(|| panic_if_err(&user_info)); - quiet.not().then(|| display::user_info(style, &user_info)); + display::user_info(style, &user_info)? } _ if let Some((skip, take, r#type, align)) = parser::list_ing(&args) => { let ing_with_comment_list = try { @@ -82,81 +72,74 @@ async fn main() -> Result<()> { .into_iter() .collect::>>()? }; - foe.then(|| panic_if_err(&ing_with_comment_list)); - quiet.not().then(|| display::list_ing(style, time_style, &ing_with_comment_list, rev, align)); + display::list_ing(style, time_style, &ing_with_comment_list, rev, align)? } _ if let Some(content) = parser::publish_ing(&args) => { let content = try { Ing::new(pat?).publish(content).await?; content }; - foe.then(|| panic_if_err(&content)); - quiet.not().then(|| display::publish_ing(style, &content)); + display::publish_ing(style, &content) } _ if let Some((content, id)) = parser::comment_ing(&args) => { let content = try { Ing::new(pat?).comment(id, content.clone(), None, None).await?; content }; - foe.then(|| panic_if_err(&content)); - quiet.not().then(|| display::comment_ing(style, &content)); + display::comment_ing(style, &content) } _ if let Some(id) = parser::show_post(&args) => { let entry = Post::new(pat?).get_one(id).await; - foe.then(|| panic_if_err(&entry)); - quiet.not().then(|| display::show_post(style, &entry)); + display::show_post(style, &entry)? } _ if let Some(id) = parser::show_post_meta(&args) => { let entry = Post::new(pat?).get_one(id).await; - foe.then(|| panic_if_err(&entry)); - quiet.not().then(|| display::show_post_meta(style, time_style, &entry)); + display::show_post_meta(style, time_style, &entry)? } _ if let Some(id) = parser::show_post_comment(&args) => { let comment_vec = Post::new(pat?).get_comment_list(id).await; - foe.then(|| panic_if_err(&comment_vec)); - quiet.not().then(|| display::show_post_comment(style, time_style, &comment_vec, rev)); + display::show_post_comment(style, time_style, &comment_vec, rev)? } _ if let Some((skip, take)) = parser::list_post(&args) => { let meta_vec = Post::new(pat?).get_meta_list(skip, take).await; - foe.then(|| panic_if_err(&meta_vec)); - quiet.not().then(|| display::list_post(style, &meta_vec, rev)); + display::list_post(style, &meta_vec, rev)? } _ if let Some(id) = parser::delete_post(&args) => { let id = try { Post::new(pat?).del_one(id).await?; id }; - foe.then(|| panic_if_err(&id)); - quiet.not().then(|| display::delete_post(style, &id)); + display::delete_post(style, &id) } _ if let Some((kw, skip, take)) = parser::search_post(&args) => { let result = Post::new(pat?).search(skip, take, kw).await; - foe.then(|| panic_if_err(&result)); - quiet.not().then(|| display::search_post(style, &result, rev)); + display::search_post(style, &result, rev)? } _ if let Some((title, body, publish)) = parser::create_post(&args) => { let id = Post::new(pat?).create(title, body, publish).await; - foe.then(|| panic_if_err(&id)); - quiet.not().then(|| display::create_post(style, &id)); + display::create_post(style, &id) } _ if let Some((id, title, body, publish)) = parser::update_post(&args) => { let id = Post::new(pat?).update(id, title, body, publish).await; - foe.then(|| panic_if_err(&id)); - quiet.not().then(|| display::update_post(style, &id)); + display::update_post(style, &id) } _ if let Some((skip, take)) = parser::list_news(&args) => { let news_vec = News::new(pat?).get_list(skip, take).await; - foe.then(|| panic_if_err(&news_vec)); - quiet.not().then(|| display::list_news(style, time_style, &news_vec, rev)); + display::list_news(style, time_style, &news_vec, rev)? } _ if no_operation(&args) => { Args::command().print_help()?; + return ().into_ok(); } - _ => { - println!("Invalid usage, follow '--help' for more information."); - } + _ => "Invalid usage, follow '--help' for more information".to_owned() }; + if args.fail_on_error { + panic!("{}", output); + } + if args.quiet.not() { + print!("{}", output); + } ().into_ok() }