Skip to content

Commit 6cdb4fe

Browse files
committed
feat: output format
1 parent 22b4ece commit 6cdb4fe

File tree

11 files changed

+195
-18
lines changed

11 files changed

+195
-18
lines changed

rkshare-eastmoney/src/basic_org_info.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use arrow_json::ReaderBuilder;
88
use bon::Builder;
99
use rkshare_utils::{
1010
FieldsInfo, Raw, Symbol,
11-
data::{Data, Fetch, TypeHint, TypedBytes},
11+
data::{Data, Fetch, HasTypeHint, TypeHint, TypedBytes},
1212
mapping,
1313
};
1414
use serde::{Deserialize, Serialize, de::DeserializeOwned};
@@ -123,6 +123,12 @@ pub struct Args<Extra = ()> {
123123
_extra: PhantomData<Extra>,
124124
}
125125

126+
impl<E> HasTypeHint for Args<E> {
127+
fn type_hint(&self) -> Option<TypeHint> {
128+
self.raw.as_ref().map(|_| TypeHint::Json)
129+
}
130+
}
131+
126132
use args_builder::State;
127133

128134
#[allow(deprecated)]

rkshare-eastmoney/src/center_gridlist.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ pub struct Args<Extra = ()> {
139139
_extra: PhantomData<Extra>,
140140
}
141141

142+
impl<E> HasTypeHint for Args<E> {
143+
fn type_hint(&self) -> Option<TypeHint> {
144+
self.raw.as_ref().map(|_| TypeHint::Json)
145+
}
146+
}
147+
142148
#[derive(Clone, Debug)]
143149
#[cfg_attr(feature = "cli", derive(clap::Args))]
144150
pub struct RawArgs {
@@ -156,7 +162,7 @@ use args_builder::State;
156162
use bon::Builder;
157163
use rkshare_utils::{
158164
FieldsInfo, Raw,
159-
data::{Data, Fetch},
165+
data::{Data, Fetch, HasTypeHint, TypeHint},
160166
};
161167
use serde::{Serialize, de::DeserializeOwned};
162168

rkshare-eastmoney/src/cli.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use anyhow::Result;
2-
use rkshare_utils::data::{Data, Fetch};
2+
use rkshare_utils::data::{Data, Fetch, HasTypeHint};
33

44
/// 东方财富
55
#[derive(clap::Subcommand, Debug)]
@@ -17,3 +17,12 @@ impl Fetch for Eastmoney {
1717
}
1818
}
1919
}
20+
21+
impl HasTypeHint for Eastmoney {
22+
fn type_hint(&self) -> Option<rkshare_utils::data::TypeHint> {
23+
match self {
24+
Eastmoney::BasicOrgInfo(args) => args.type_hint(),
25+
Eastmoney::CenterGridlist(args) => args.type_hint(),
26+
}
27+
}
28+
}

rkshare-sse/src/cli.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rkshare_utils::data::Fetch;
1+
use rkshare_utils::data::{Fetch, HasTypeHint};
22

33
#[derive(clap::Subcommand, Debug)]
44
/// 上海证券交易所
@@ -32,3 +32,20 @@ impl Fetch for Stock {
3232
}
3333
}
3434
}
35+
36+
impl HasTypeHint for Stock {
37+
fn type_hint(&self) -> Option<rkshare_utils::data::TypeHint> {
38+
match self {
39+
Stock::Summary(args) => args.type_hint(),
40+
Stock::DealDaily(args) => args.type_hint(),
41+
}
42+
}
43+
}
44+
45+
impl HasTypeHint for Sse {
46+
fn type_hint(&self) -> Option<rkshare_utils::data::TypeHint> {
47+
match self {
48+
Sse::Stock(stock) => stock.type_hint(),
49+
}
50+
}
51+
}

rkshare-sse/src/stock/deal_daily.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use arrow::{array::RecordBatch, datatypes::Schema, json::ReaderBuilder};
55
use bon::Builder;
66
use rkshare_utils::{
77
FieldsInfo, Raw,
8-
data::{Data, Fetch, TypeHint, TypedBytes},
8+
data::{Data, Fetch, HasTypeHint, TypeHint, TypedBytes},
99
mapping,
1010
};
1111
use serde::{
@@ -123,3 +123,9 @@ where
123123
})
124124
}
125125
}
126+
127+
impl HasTypeHint for Args {
128+
fn type_hint(&self) -> Option<TypeHint> {
129+
self.raw.as_ref().map(|_| TypeHint::Json)
130+
}
131+
}

rkshare-sse/src/stock/summary.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use arrow::{array::RecordBatch, datatypes::Schema, json::ReaderBuilder};
55
use bon::Builder;
66
use rkshare_utils::{
77
FieldsInfo, Raw,
8-
data::{Data, Fetch, TypeHint, TypedBytes},
8+
data::{Data, Fetch, HasTypeHint, TypeHint, TypedBytes},
99
mapping,
1010
};
1111
use serde::{Serialize, de::DeserializeOwned};
@@ -94,3 +94,9 @@ where
9494
})
9595
}
9696
}
97+
98+
impl HasTypeHint for Args {
99+
fn type_hint(&self) -> Option<TypeHint> {
100+
self.raw.as_ref().map(|_| TypeHint::Json)
101+
}
102+
}

rkshare-tools/src/cli/get.rs

Lines changed: 98 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,33 @@
1+
use std::{
2+
fs::File,
3+
path::{Path, PathBuf},
4+
};
5+
6+
use arrow::json::writer::JsonArray;
17
use rkshare::{
28
eastmoney::cli::Eastmoney,
39
sse::cli::Sse,
410
utils::{
5-
data::{Data, Fetch},
11+
data::{Data, Fetch, HasTypeHint, TypeHint},
612
pretty::pretty_print,
713
},
814
xueqiu::cli::Xueqiu,
915
};
1016

11-
/// 请求数据
17+
/// 从各平台接口获取数据
18+
#[derive(clap::Args, Debug)]
19+
pub struct Get {
20+
// TODO: allow specify format
21+
/// 输出结果到文件
22+
#[arg(long, short, global = true)]
23+
output: Option<PathBuf>,
24+
25+
#[command(subcommand)]
26+
command: Command,
27+
}
28+
1229
#[derive(clap::Subcommand, Debug)]
13-
pub enum Get {
30+
enum Command {
1431
#[command(subcommand)]
1532
Sse(Sse),
1633
#[command(subcommand)]
@@ -19,20 +36,72 @@ pub enum Get {
1936
Xueqiu(Xueqiu),
2037
}
2138

39+
impl HasTypeHint for Get {
40+
fn type_hint(&self) -> Option<TypeHint> {
41+
match &self.command {
42+
Command::Sse(sse) => sse.type_hint(),
43+
Command::Eastmoney(eastmoney) => eastmoney.type_hint(),
44+
Command::Xueqiu(xueqiu) => xueqiu.type_hint(),
45+
}
46+
}
47+
}
48+
2249
impl Get {
2350
pub fn action(self) -> anyhow::Result<()> {
24-
let data = rt()?.block_on(self.fetch())?;
25-
pretty_print(data)?;
51+
let type_hint = self.type_hint();
52+
let Self { output, command } = self;
53+
let format = match &output {
54+
Some(path) => Some(
55+
OutputFormat::infer_from_path(path).ok_or(anyhow::anyhow!("无法从路径推断格式"))?,
56+
),
57+
None => None,
58+
};
59+
// TODO: early validate
60+
61+
let data = rt()?.block_on(command.fetch())?;
62+
63+
match (format, type_hint) {
64+
(None, None) => {
65+
pretty_print(data)?;
66+
}
67+
// TODO: should split pretty print
68+
(None, Some(_)) => {
69+
pretty_print(data)?;
70+
}
71+
(Some(format), None) => {
72+
match format {
73+
// TODO: 输出 JSON
74+
OutputFormat::Json => {
75+
let file = File::create(output.unwrap()).unwrap();
76+
let mut writer =
77+
arrow::json::WriterBuilder::new().build::<_, JsonArray>(file);
78+
writer.write(data.as_arrow().unwrap()).unwrap();
79+
}
80+
// TODO: 输出 CSV
81+
OutputFormat::Csv => {
82+
let file = File::create(output.unwrap()).unwrap();
83+
let mut writer = arrow::csv::WriterBuilder::new()
84+
.with_header(true)
85+
.build(file);
86+
writer.write(data.as_arrow().unwrap()).unwrap();
87+
}
88+
}
89+
}
90+
(Some(_output_format), Some(_data_format)) => {
91+
// TODO: 验证类型匹配
92+
std::fs::write(output.unwrap(), &data.as_raw().unwrap())?;
93+
}
94+
}
2695
Ok(())
2796
}
2897
}
2998

30-
impl Fetch for Get {
99+
impl Fetch for Command {
31100
async fn fetch(self) -> anyhow::Result<Data> {
32101
match self {
33-
Self::Sse(sse) => sse.fetch().await,
34-
Self::Eastmoney(eastmoney) => eastmoney.fetch().await,
35-
Self::Xueqiu(xueqiu) => xueqiu.fetch().await,
102+
Command::Sse(sse) => sse.fetch().await,
103+
Command::Eastmoney(eastmoney) => eastmoney.fetch().await,
104+
Command::Xueqiu(xueqiu) => xueqiu.fetch().await,
36105
}
37106
}
38107
}
@@ -42,3 +111,23 @@ fn rt() -> tokio::io::Result<tokio::runtime::Runtime> {
42111
.enable_all()
43112
.build()
44113
}
114+
115+
// TODO: support options like CSV->TSV, json indent and formatting
116+
/// 输出数据格式。
117+
pub enum OutputFormat {
118+
Json,
119+
Csv,
120+
// TODO: excel
121+
// TODO: parquet
122+
}
123+
124+
impl OutputFormat {
125+
/// 从文件路径推断数据格式。
126+
pub fn infer_from_path(path: impl AsRef<Path>) -> Option<Self> {
127+
match path.as_ref().extension()?.to_str()? {
128+
"json" => Some(Self::Json),
129+
"csv" => Some(Self::Csv),
130+
_ => None,
131+
}
132+
}
133+
}

rkshare-tools/src/cli/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ pub struct Cli {
1616

1717
#[derive(clap::Subcommand, Debug)]
1818
enum Commands {
19-
#[command(subcommand)]
2019
Get(Get),
2120
Serve(Serve),
2221
#[command(subcommand)]

rkshare-utils/src/data.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::ops::Deref;
44

55
use arrow::array::RecordBatch;
6+
use bytes::Bytes;
67
use derive_more::From;
78

89
/// 数据。
@@ -14,6 +15,22 @@ pub enum Data {
1415
Arrow(RecordBatch),
1516
}
1617

18+
impl Data {
19+
pub fn as_raw(&self) -> Option<&Bytes> {
20+
match self {
21+
Data::Raw(typed_bytes) => Some(typed_bytes),
22+
_ => None,
23+
}
24+
}
25+
26+
pub fn as_arrow(&self) -> Option<&RecordBatch> {
27+
match self {
28+
Data::Arrow(record_batch) => Some(record_batch),
29+
_ => None,
30+
}
31+
}
32+
}
33+
1734
// impl From<RecordBatch> f
1835
/// 类型注释
1936
#[derive(Debug, Clone)]
@@ -61,3 +78,11 @@ pub trait Fetch {
6178
// TODO: 未来应该构建自己的错误类型
6279
fn fetch(self) -> impl std::future::Future<Output = anyhow::Result<Data>> + Send;
6380
}
81+
82+
/// 参数类型提示。
83+
///
84+
/// 用于命令行检测输出格式是否正确。
85+
pub trait HasTypeHint {
86+
/// Option 表示结果为 `RecordBatch`.
87+
fn type_hint(&self) -> Option<TypeHint>;
88+
}

rkshare-xueqiu/src/cli.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clap::Subcommand;
2-
use rkshare_utils::data::Fetch;
2+
use rkshare_utils::data::{Fetch, HasTypeHint};
33

44
/// 雪球
55
#[derive(Subcommand, Debug)]
@@ -16,3 +16,11 @@ impl Fetch for Xueqiu {
1616
}
1717
}
1818
}
19+
20+
impl HasTypeHint for Xueqiu {
21+
fn type_hint(&self) -> Option<rkshare_utils::data::TypeHint> {
22+
match self {
23+
Xueqiu::Detail(args) => args.type_hint(),
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)