Skip to content

Commit

Permalink
Design the Cli API, move execute file to bin dir, change to lib
Browse files Browse the repository at this point in the history
  • Loading branch information
RocsSun committed Jan 1, 2024
1 parent aaf1792 commit 558c9f7
Show file tree
Hide file tree
Showing 5 changed files with 332 additions and 0 deletions.
52 changes: 52 additions & 0 deletions README.zh-CN.md
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]
```
14 changes: 14 additions & 0 deletions src/apis/mod.rs
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;
13 changes: 13 additions & 0 deletions src/apis/statuses/mod.rs
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}
//!
241 changes: 241 additions & 0 deletions src/bin/cnb.rs
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()
}
12 changes: 12 additions & 0 deletions src/lib.rs
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;

0 comments on commit 558c9f7

Please sign in to comment.