-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Design the Cli API, move execute file to bin dir, change to lib
- Loading branch information
Showing
5 changed files
with
332 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# Cnblogs 命令行工具 | ||
|
||
[![Build / Release](https://github.com/cnblogs/cli/actions/workflows/build-release.yml/badge.svg)](https://github.com/cnblogs/cli/actions/workflows/build-release.yml) | ||
[![Build / Development](https://github.com/cnblogs/cli/actions/workflows/build-dev.yml/badge.svg)](https://github.com/cnblogs/cli/actions/workflows/build-dev.yml) | ||
|
||
从 CLI 访问 cnblogs。 | ||
|
||
## Cnbogs Cli 设计 | ||
|
||
从Cnblogs的[OpenAPI](https://api.cnblogs.com/help)来说,API主要有以下几类: | ||
|
||
1. Token: 认证 | ||
2. Users: 仅提供当前登录用户信息 | ||
3. Blogs: 博客的CURD及其评论的查看和增加, | ||
4. Marks: 收藏的CURD | ||
5. News: 新闻的查询,新闻评论的CURD | ||
6. Statuses: 闪存CURD。 | ||
7. Questions: 问题相关操作 | ||
8. Edu: 班级相关 | ||
9. Articles: 知识库的查找。 | ||
10. Zzk: 找找看 | ||
|
||
### cli的使用 | ||
|
||
目前cli的使用如下: | ||
|
||
```shell | ||
# Check your post list | ||
cnb post --list | ||
# Check your post | ||
cnb --id 114514 post --show | ||
# Create and publish post | ||
cnb post create --title 'Hello' --body 'world!' --publish | ||
# Change your post body | ||
cnb --id 114514 post update --body 'niconiconiconi' | ||
|
||
# Show ing list | ||
cnb ing list | ||
# Publish ing | ||
cnb ing --publish 'Hello world!' | ||
# Comment to ing | ||
cnb --id 114514 ing --comment 'Awesome!' | ||
|
||
# Check your user infomation | ||
cnb user --info | ||
``` | ||
|
||
大体上使用如上的设计,支持子命令,相关操作的设计按照RESTFUL的思路设计实现,博客的相关操作设计如下: | ||
|
||
```shell | ||
cnb posts [comment] [list,create,query,delete,update] --[id/file/quertset] --[pagesize,pagecount] | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
//! cnblogs 闪存接口模块 | ||
//! | ||
//! 封装[cnblogs Api](https://api.cnblogs.com/Help#0aee001a01835c83a3277a500ffc9040)至以下模块中: | ||
//! | ||
//! - statuses: 闪存相关api。 | ||
//! - blogs: 博客相关 | ||
//! - news: 新闻相关 | ||
//! - questions: 问题相关 | ||
//! - edu: edu 相关 | ||
//! - user: 用户相关 | ||
//! - token: 认证相关 | ||
//! - marks: 收藏相关 | ||
pub mod statuses; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
//! cnblogs 闪存接口模块 | ||
//! | ||
//! 实现封装[cnblogs Api](https://api.cnblogs.com/Help#0aee001a01835c83a3277a500ffc9040)中的`Statuses`。 | ||
//! | ||
//! - 获取最新一条闪存内容 https://api.cnblogs.com/api/statuses/recent | ||
//! - 发布闪存评论 https://api.cnblogs.com/api/statuses/{statusId}/comments | ||
//! - 获取闪存评论 https://api.cnblogs.com/api/statuses/{statusId}/comments | ||
//! - 删除闪存评论 https://api.cnblogs.com/api/statuses/{statusId}/comments/{id} | ||
//! - 发布闪存 https://api.cnblogs.com/api/statuses | ||
//! - 删除闪存 https://api.cnblogs.com/api/statuses/{id} | ||
//! - 根据类型获取闪存列表 https://api.cnblogs.com/api/statuses/@{type}?pageIndex={pageIndex}&pageSize={pageSize}&tag={tag} | ||
//! - 根据Id获取闪存 https://api.cnblogs.com/api/statuses/{id} | ||
//! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
#![feature(try_blocks)] | ||
#![feature(if_let_guard)] | ||
#![feature(let_chains)] | ||
#![feature(iterator_try_collect)] | ||
#![feature(iterator_try_reduce)] | ||
#![warn(clippy::all, clippy::nursery, clippy::cargo_common_metadata)] | ||
|
||
extern crate cnblogs_lib; | ||
|
||
use anyhow::Result; | ||
use clap::Parser; | ||
use clap::{Command, CommandFactory}; | ||
use cnblogs_lib::api::auth::session; | ||
use cnblogs_lib::api::fav::Fav; | ||
use cnblogs_lib::api::ing::Ing; | ||
use cnblogs_lib::api::news::News; | ||
use cnblogs_lib::api::post::Post; | ||
use cnblogs_lib::api::user::User; | ||
use cnblogs_lib::args::cmd::post::{CreateCmd, UpdateCmd}; | ||
use cnblogs_lib::args::parser::no_operation; | ||
use cnblogs_lib::args::{parser, Args}; | ||
use cnblogs_lib::display; | ||
use cnblogs_lib::infra::fp::currying::eq; | ||
use cnblogs_lib::infra::infer::infer; | ||
use cnblogs_lib::infra::iter::{ExactSizeIteratorExt, IntoIteratorExt}; | ||
use cnblogs_lib::infra::option::OptionExt; | ||
use cnblogs_lib::infra::result::WrapResult; | ||
use colored::Colorize; | ||
use std::env; | ||
|
||
fn show_non_printable_chars(text: String) -> String { | ||
#[inline] | ||
fn make_red(str: &str) -> String { | ||
format!("{}", str.red()) | ||
} | ||
|
||
text.replace(' ', &make_red("·")) | ||
.replace('\0', &make_red("␀\0")) | ||
.replace('\t', &make_red("␉\t")) | ||
.replace('\n', &make_red("␊\n")) | ||
.replace('\r', &make_red("␍\r")) | ||
.replace("\r\n", &make_red("␍␊\r\n")) | ||
} | ||
|
||
#[allow(clippy::missing_const_for_fn)] | ||
fn panic_if_err<T>(result: &Result<T>) { | ||
if let Err(e) = result { | ||
panic!("{}", e) | ||
} | ||
} | ||
|
||
#[tokio::main(flavor = "multi_thread")] | ||
async fn main() -> Result<()> { | ||
let args_vec = env::args().collect::<Vec<_>>(); | ||
if args_vec.iter().any(eq(&"--debug".to_owned())) { | ||
dbg!(args_vec); | ||
} | ||
|
||
let args: Args = Args::parse(); | ||
let global_opt = &args.global_opt; | ||
if global_opt.debug { | ||
dbg!(&args); | ||
} | ||
|
||
let pat = global_opt.with_pat.clone().or_eval_result(session::get_pat); | ||
let style = &global_opt.style; | ||
let time_style = &global_opt.time_style; | ||
let rev = args.rev; | ||
let foe = global_opt.fail_on_error; | ||
|
||
let output = match args { | ||
_ if let Some(pat) = parser::user::login(&args) => { | ||
let cfg_path = session::login(pat); | ||
foe.then(|| panic_if_err(&cfg_path)); | ||
display::login(style, &cfg_path) | ||
} | ||
_ if parser::user::logout(&args) => { | ||
let cfg_path = session::logout(); | ||
foe.then(|| panic_if_err(&cfg_path)); | ||
display::logout(style, &cfg_path) | ||
} | ||
_ if parser::user::user_info(&args) => { | ||
let user_info = User::new(pat?).get_info().await; | ||
foe.then(|| panic_if_err(&user_info)); | ||
display::user_info(style, &user_info)? | ||
} | ||
_ if let Some((skip, take, r#type, align)) = parser::ing::list_ing(&args) => { | ||
let ing_with_comment_iter = infer::<Result<_, _>>( | ||
try { | ||
let ing_api = Ing::new(pat?); | ||
let ing_vec = ing_api.get_list(skip, take, &r#type).await?; | ||
ing_vec | ||
.into_iter() | ||
.map(|ing| async { | ||
let result = ing_api.get_comment_list(ing.id).await; | ||
result.map(|comment_vec| (ing, comment_vec)) | ||
}) | ||
.join_all() | ||
.await | ||
.into_iter() | ||
.collect::<Result<Vec<_>>>()? | ||
}, | ||
) | ||
.map(|vec| vec.into_iter().dyn_rev(rev)); | ||
foe.then(|| panic_if_err(&ing_with_comment_iter)); | ||
display::list_ing(style, time_style, ing_with_comment_iter, align)? | ||
} | ||
_ if let Some(content) = parser::ing::publish_ing(&args) => { | ||
let content = try { | ||
Ing::new(pat?).publish(content).await?; | ||
content | ||
}; | ||
foe.then(|| panic_if_err(&content)); | ||
display::publish_ing(style, &content) | ||
} | ||
_ if let Some((content, id)) = parser::ing::comment_ing(&args) => { | ||
let content = try { | ||
Ing::new(pat?) | ||
.comment(id, content.clone(), None, None) | ||
.await?; | ||
content | ||
}; | ||
foe.then(|| panic_if_err(&content)); | ||
display::comment_ing(style, &content) | ||
} | ||
_ if let Some(id) = parser::post::show_post(&args) => { | ||
let entry = Post::new(pat?).get_one(id).await; | ||
foe.then(|| panic_if_err(&entry)); | ||
display::show_post(style, &entry)? | ||
} | ||
_ if let Some(id) = parser::post::show_post_meta(&args) => { | ||
let entry = Post::new(pat?).get_one(id).await; | ||
foe.then(|| panic_if_err(&entry)); | ||
display::show_post_meta(style, time_style, &entry)? | ||
} | ||
_ if let Some(id) = parser::post::show_post_comment(&args) => { | ||
let comment_iter = Post::new(pat?) | ||
.get_comment_list(id) | ||
.await | ||
.map(|vec| vec.into_iter().dyn_rev(rev)); | ||
foe.then(|| panic_if_err(&comment_iter)); | ||
display::show_post_comment(style, time_style, comment_iter)? | ||
} | ||
_ if let Some((skip, take)) = parser::post::list_post(&args) => { | ||
let meta_iter = Post::new(pat?) | ||
.get_meta_list(skip, take) | ||
.await | ||
.map(|(vec, count)| (vec.into_iter().dyn_rev(rev), count)); | ||
foe.then(|| panic_if_err(&meta_iter)); | ||
display::list_post(style, meta_iter)? | ||
} | ||
_ if let Some(id) = parser::post::delete_post(&args) => { | ||
let id = try { | ||
Post::new(pat?).del_one(id).await?; | ||
id | ||
}; | ||
foe.then(|| panic_if_err(&id)); | ||
display::delete_post(style, &id) | ||
} | ||
_ if let Some((kw, skip, take)) = parser::post::search_self_post(&args) => { | ||
let result = Post::new(pat?) | ||
.search_self(skip, take, kw) | ||
.await | ||
.map(|(vec, count)| (vec.into_iter().dyn_rev(rev), count)); | ||
foe.then(|| panic_if_err(&result)); | ||
display::search_self_post(style, result)? | ||
} | ||
_ if let Some((kw, skip, take)) = parser::post::search_site_post(&args) => { | ||
let result = Post::new(pat?) | ||
.search_site(skip, take, kw) | ||
.await | ||
.map(|vec| vec.into_iter().dyn_rev(rev)); | ||
foe.then(|| panic_if_err(&result)); | ||
display::search_site_post(style, time_style, result)? | ||
} | ||
_ if let Some(create_cmd) = parser::post::create_post(&args) => { | ||
let CreateCmd { | ||
title, | ||
body, | ||
publish, | ||
.. | ||
} = create_cmd; | ||
let id = Post::new(pat?).create(title, body, *publish).await; | ||
foe.then(|| panic_if_err(&id)); | ||
display::create_post(style, &id) | ||
} | ||
_ if let Some((id, update_cmd)) = parser::post::update_post(&args) => { | ||
let UpdateCmd { | ||
title, | ||
body, | ||
publish, | ||
.. | ||
} = update_cmd; | ||
let id = Post::new(pat?).update(id, title, body, publish).await; | ||
foe.then(|| panic_if_err(&id)); | ||
display::update_post(style, &id) | ||
} | ||
_ if let Some((skip, take)) = parser::news::list_news(&args) => { | ||
let news_iter = News::new(pat?) | ||
.get_list(skip, take) | ||
.await | ||
.map(|vec| vec.into_iter().dyn_rev(rev)); | ||
foe.then(|| panic_if_err(&news_iter)); | ||
display::list_news(style, time_style, news_iter)? | ||
} | ||
_ if let Some((skip, take)) = parser::fav::list_fav(&args) => { | ||
let fav_iter = Fav::new(pat?) | ||
.get_list(skip, take) | ||
.await | ||
.map(|vec| vec.into_iter().dyn_rev(rev)); | ||
foe.then(|| panic_if_err(&fav_iter)); | ||
display::list_fav(style, time_style, fav_iter)? | ||
} | ||
|
||
_ if no_operation(&args) => infer::<Command>(Args::command()).render_help().to_string(), | ||
_ => "Invalid usage, follow '--help' for more information".to_owned(), | ||
}; | ||
|
||
if global_opt.quiet { | ||
return ().wrap_ok(); | ||
} | ||
|
||
let output = { | ||
let output = if output.ends_with("\n\n") { | ||
output[..output.len() - 1].to_owned() | ||
} else if output.ends_with('\n') { | ||
output | ||
} else { | ||
format!("{}\n", output) | ||
}; | ||
if global_opt.debug { | ||
show_non_printable_chars(output) | ||
} else { | ||
output | ||
} | ||
}; | ||
|
||
print!("{}", output); | ||
|
||
().wrap_ok() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
#![feature(try_blocks)] | ||
#![feature(if_let_guard)] | ||
#![feature(let_chains)] | ||
#![feature(iterator_try_collect)] | ||
#![feature(iterator_try_reduce)] | ||
#![warn(clippy::all, clippy::nursery, clippy::cargo_common_metadata)] | ||
|
||
pub mod api; | ||
pub mod apis; | ||
pub mod args; | ||
pub mod display; | ||
pub mod infra; |