mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-08-07 09:43:53 +00:00
feat: implement idx-cli MVP foundation, provider abstraction, and core quote/history commands
This commit is contained in:
parent
a4b8a1b64e
commit
2174b42cff
18 changed files with 2678 additions and 0 deletions
9
src/output/json.rs
Normal file
9
src/output/json.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::error::IdxError;
|
||||
|
||||
pub fn print_json<T: Serialize + ?Sized>(value: &T) -> Result<(), IdxError> {
|
||||
let out = serde_json::to_string_pretty(value).map_err(|e| IdxError::ParseError(e.to_string()))?;
|
||||
println!("{out}");
|
||||
Ok(())
|
||||
}
|
||||
44
src/output/mod.rs
Normal file
44
src/output/mod.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
pub mod json;
|
||||
pub mod table;
|
||||
|
||||
use clap::ValueEnum;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::api::types::{Ohlc, Quote};
|
||||
use crate::error::IdxError;
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq, Serialize, serde::Deserialize, Default)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum OutputFormat {
|
||||
#[default]
|
||||
Table,
|
||||
Json,
|
||||
}
|
||||
|
||||
pub fn render_quotes(quotes: &[Quote], format: &OutputFormat, no_color: bool) -> Result<(), IdxError> {
|
||||
match format {
|
||||
OutputFormat::Table => table::print_quotes(quotes, no_color),
|
||||
OutputFormat::Json => json::print_json(quotes),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_history(symbol: &str, history: &[Ohlc], format: &OutputFormat) -> Result<(), IdxError> {
|
||||
match format {
|
||||
OutputFormat::Table => table::print_history(symbol, history),
|
||||
OutputFormat::Json => json::print_json(history),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_error(err: &IdxError, format: &OutputFormat) {
|
||||
match format {
|
||||
OutputFormat::Table => eprintln!("Error: {err}"),
|
||||
OutputFormat::Json => {
|
||||
let payload = serde_json::json!({
|
||||
"error": true,
|
||||
"code": format!("{:?}", err.code()).to_uppercase(),
|
||||
"message": err.to_string()
|
||||
});
|
||||
eprintln!("{}", serde_json::to_string_pretty(&payload).unwrap_or_else(|_| "{}".to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
80
src/output/table.rs
Normal file
80
src/output/table.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
use comfy_table::{presets::UTF8_FULL, Cell, Color, ContentArrangement, Table};
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
use crate::api::types::{Ohlc, Quote};
|
||||
use crate::error::IdxError;
|
||||
|
||||
pub fn format_idr(value: f64) -> String {
|
||||
let rounded = value.round() as i64;
|
||||
let chars: Vec<char> = rounded.to_string().chars().rev().collect();
|
||||
let mut out = String::new();
|
||||
for (i, ch) in chars.iter().enumerate() {
|
||||
if i > 0 && i % 3 == 0 {
|
||||
out.push(',');
|
||||
}
|
||||
out.push(*ch);
|
||||
}
|
||||
out.chars().rev().collect()
|
||||
}
|
||||
|
||||
pub fn print_quotes(quotes: &[Quote], no_color: bool) -> Result<(), IdxError> {
|
||||
let mut table = Table::new();
|
||||
table
|
||||
.load_preset(UTF8_FULL)
|
||||
.set_content_arrangement(ContentArrangement::Dynamic)
|
||||
.set_header(vec!["SYMBOL", "PRICE", "CHG", "CHG%", "VOLUME", "MKT CAP"]);
|
||||
|
||||
for q in quotes {
|
||||
let pct = format!("{:+.2}%", q.change_pct);
|
||||
let pct_cell = if no_color {
|
||||
Cell::new(pct)
|
||||
} else if q.change_pct >= 0.0 {
|
||||
Cell::new(pct).fg(Color::Green)
|
||||
} else {
|
||||
Cell::new(pct).fg(Color::Red)
|
||||
};
|
||||
table.add_row(vec![
|
||||
Cell::new(&q.symbol),
|
||||
Cell::new(format_idr(q.price)),
|
||||
Cell::new(format!("{:+.2}", q.change)),
|
||||
pct_cell,
|
||||
Cell::new(format_idr(q.volume as f64)),
|
||||
Cell::new(q.market_cap.map(format_idr).unwrap_or_else(|| "-".to_string())),
|
||||
]);
|
||||
}
|
||||
|
||||
println!("{table}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_history(symbol: &str, history: &[Ohlc]) -> Result<(), IdxError> {
|
||||
println!("{}", format!("History for {symbol}").bold());
|
||||
let mut table = Table::new();
|
||||
table
|
||||
.load_preset(UTF8_FULL)
|
||||
.set_content_arrangement(ContentArrangement::Dynamic)
|
||||
.set_header(vec!["DATE", "OPEN", "HIGH", "LOW", "CLOSE", "VOLUME"]);
|
||||
for item in history {
|
||||
table.add_row(vec![
|
||||
Cell::new(item.date),
|
||||
Cell::new(format_idr(item.open)),
|
||||
Cell::new(format_idr(item.high)),
|
||||
Cell::new(format_idr(item.low)),
|
||||
Cell::new(format_idr(item.close)),
|
||||
Cell::new(format_idr(item.volume as f64)),
|
||||
]);
|
||||
}
|
||||
println!("{table}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::format_idr;
|
||||
|
||||
#[test]
|
||||
fn formats_idr_numbers() {
|
||||
assert_eq!(format_idr(9875.0), "9,875");
|
||||
assert_eq!(format_idr(1_215_200_000_000_000.0), "1,215,200,000,000,000");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue