From dc1de5b344be4adf7d86452581f41151a15945c8 Mon Sep 17 00:00:00 2001 From: Ciphercat <78522797+0xrsydn@users.noreply.github.com> Date: Thu, 5 Mar 2026 18:33:20 +0000 Subject: [PATCH] style: cargo fmt --- src/api/yahoo.rs | 64 ++++++++++++++++++++++++++++++++------------- src/cache.rs | 57 +++++++++++++++++++++++++++++++--------- src/cli/stocks.rs | 38 ++++++++++++++++++--------- src/config.rs | 16 ++++++++---- src/main.rs | 8 +++++- src/output/json.rs | 3 ++- src/output/mod.rs | 17 +++++++++--- src/output/table.rs | 8 ++++-- tests/cli.rs | 7 ++--- 9 files changed, 157 insertions(+), 61 deletions(-) diff --git a/src/api/yahoo.rs b/src/api/yahoo.rs index aa7f022..ccc8e1c 100644 --- a/src/api/yahoo.rs +++ b/src/api/yahoo.rs @@ -2,8 +2,8 @@ use std::time::Duration; use serde::Deserialize; -use crate::api::types::{Interval, Ohlc, Period, Quote}; use crate::api::MarketDataProvider; +use crate::api::types::{Interval, Ohlc, Period, Quote}; use crate::error::IdxError; const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"; @@ -29,15 +29,16 @@ impl YahooProvider { ) } - fn fetch_chart(&self, symbol: &str, period: &Period, interval: &Interval) -> Result { + fn fetch_chart( + &self, + symbol: &str, + period: &Period, + interval: &Interval, + ) -> Result { let mut wait = Duration::from_millis(250); for attempt in 0..3 { let url = Self::chart_url(symbol, period, interval); - let response = self - .agent - .get(&url) - .header("User-Agent", USER_AGENT) - .call(); + let response = self.agent.get(&url).header("User-Agent", USER_AGENT).call(); match response { Ok(ok) => { return ok @@ -72,7 +73,12 @@ impl MarketDataProvider for YahooProvider { parse_quote(symbol, &chart) } - fn history(&self, symbol: &str, period: &Period, interval: &Interval) -> Result, IdxError> { + fn history( + &self, + symbol: &str, + period: &Period, + interval: &Interval, + ) -> Result, IdxError> { let chart = self.fetch_chart(symbol, period, interval)?; parse_history(&chart) } @@ -92,12 +98,15 @@ fn parse_quote(symbol: &str, chart: &ChartResponse) -> Result { .and_then(|r| r.first()) .ok_or(IdxError::ProviderUnavailable)?; let meta = result.meta.as_ref().ok_or(IdxError::ProviderUnavailable)?; - let price = meta.regular_market_price.ok_or(IdxError::SymbolNotFound(symbol.to_string()))?; + let price = meta + .regular_market_price + .ok_or(IdxError::SymbolNotFound(symbol.to_string()))?; let prev_close = meta.previous_close.or(meta.chart_previous_close); let change = prev_close.map_or(0.0, |p| price - p); let change_pct = prev_close.map_or(0.0, |p| if p != 0.0 { (change / p) * 100.0 } else { 0.0 }); - let (week52_position, range_signal) = match (meta.fifty_two_week_low, meta.fifty_two_week_high) { + let (week52_position, range_signal) = match (meta.fifty_two_week_low, meta.fifty_two_week_high) + { (Some(low), Some(high)) if high > low => { let pos = (price - low) / (high - low); let signal = if pos > 0.66 { @@ -141,7 +150,10 @@ fn parse_history(chart: &ChartResponse) -> Result, IdxError> { .as_ref() .and_then(|r| r.first()) .ok_or(IdxError::ProviderUnavailable)?; - let timestamps = result.timestamp.as_ref().ok_or(IdxError::ProviderUnavailable)?; + let timestamps = result + .timestamp + .as_ref() + .ok_or(IdxError::ProviderUnavailable)?; let quote = result .indicators .as_ref() @@ -151,11 +163,23 @@ fn parse_history(chart: &ChartResponse) -> Result, IdxError> { let mut out = Vec::new(); for (i, ts) in timestamps.iter().enumerate() { - let open = quote.open.as_ref().and_then(|v| v.get(i).copied().flatten()); - let high = quote.high.as_ref().and_then(|v| v.get(i).copied().flatten()); + let open = quote + .open + .as_ref() + .and_then(|v| v.get(i).copied().flatten()); + let high = quote + .high + .as_ref() + .and_then(|v| v.get(i).copied().flatten()); let low = quote.low.as_ref().and_then(|v| v.get(i).copied().flatten()); - let close = quote.close.as_ref().and_then(|v| v.get(i).copied().flatten()); - let volume = quote.volume.as_ref().and_then(|v| v.get(i).copied().flatten()); + let close = quote + .close + .as_ref() + .and_then(|v| v.get(i).copied().flatten()); + let volume = quote + .volume + .as_ref() + .and_then(|v| v.get(i).copied().flatten()); if let (Some(open), Some(high), Some(low), Some(close), Some(volume)) = (open, high, low, close, volume) @@ -224,7 +248,9 @@ struct IndicatorQuote { #[cfg(test)] mod tests { - use super::{parse_history, parse_history_from_str, parse_quote, parse_quote_from_str, ChartResponse}; + use super::{ + ChartResponse, parse_history, parse_history_from_str, parse_quote, parse_quote_from_str, + }; const SAMPLE: &str = r#"{ "chart": { @@ -264,8 +290,10 @@ mod tests { #[test] fn parses_realistic_fixture_json() { - let quote_raw = std::fs::read_to_string("tests/fixtures/chart_bbca_1d.json").expect("fixture exists"); - let history_raw = std::fs::read_to_string("tests/fixtures/chart_bbca_3mo.json").expect("fixture exists"); + let quote_raw = + std::fs::read_to_string("tests/fixtures/chart_bbca_1d.json").expect("fixture exists"); + let history_raw = + std::fs::read_to_string("tests/fixtures/chart_bbca_3mo.json").expect("fixture exists"); let quote = parse_quote_from_str("BBCA.JK", "e_raw).expect("fixture quote parsed"); assert_eq!(quote.symbol, "BBCA.JK"); diff --git a/src/cache.rs b/src/cache.rs index 5c4c0a2..046cc04 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -43,23 +43,39 @@ impl Cache { Self { root } } - - pub fn get(&self, data_type: &str, symbol: &str) -> Result, IdxError> { + pub fn get( + &self, + data_type: &str, + symbol: &str, + ) -> Result, IdxError> { let Some(entry): Option> = self.read_entry(data_type, symbol)? else { return Ok(None); }; let age = Utc::now().signed_duration_since(entry.fetched_at); - if age > chrono::Duration::from_std(Duration::from_secs(entry.ttl_secs)).map_err(|e| IdxError::CacheMiss(e.to_string()))? { + if age + > chrono::Duration::from_std(Duration::from_secs(entry.ttl_secs)) + .map_err(|e| IdxError::CacheMiss(e.to_string()))? + { return Ok(None); } Ok(Some(entry.data)) } - pub fn get_stale(&self, data_type: &str, symbol: &str) -> Result, IdxError> { + pub fn get_stale( + &self, + data_type: &str, + symbol: &str, + ) -> Result, IdxError> { Ok(self.read_entry::(data_type, symbol)?.map(|e| e.data)) } - pub fn put(&self, data_type: &str, symbol: &str, data: &T, ttl_secs: u64) -> Result<(), IdxError> { + pub fn put( + &self, + data_type: &str, + symbol: &str, + data: &T, + ttl_secs: u64, + ) -> Result<(), IdxError> { let path = self.entry_path(data_type, symbol); if let Some(parent) = path.parent() { fs::create_dir_all(parent).map_err(|e| IdxError::Io(e.to_string()))?; @@ -70,7 +86,8 @@ impl Cache { schema_version: SCHEMA_VERSION, data, }; - let raw = serde_json::to_string_pretty(&entry).map_err(|e| IdxError::ParseError(e.to_string()))?; + let raw = serde_json::to_string_pretty(&entry) + .map_err(|e| IdxError::ParseError(e.to_string()))?; fs::write(path, raw).map_err(|e| IdxError::Io(e.to_string())) } @@ -88,7 +105,8 @@ impl Cache { files += 1; total_size += meta.len(); if let Ok(raw) = fs::read_to_string(p) - && let Ok(entry) = serde_json::from_str::>(&raw) + && let Ok(entry) = + serde_json::from_str::>(&raw) { oldest = Some(oldest.map_or(entry.fetched_at, |o| o.min(entry.fetched_at))); newest = Some(newest.map_or(entry.fetched_at, |n| n.max(entry.fetched_at))); @@ -132,7 +150,11 @@ impl Cache { Ok(()) } - fn read_entry(&self, data_type: &str, symbol: &str) -> Result>, IdxError> { + fn read_entry( + &self, + data_type: &str, + symbol: &str, + ) -> Result>, IdxError> { let path = self.entry_path(data_type, symbol); if !path.exists() { return Ok(None); @@ -178,20 +200,29 @@ mod tests { let root = tmp(); let cache = Cache::with_root(root.clone()); - cache.put("quote", "BBCA.JK", &T { v: 7 }, 300).expect("cache write"); + cache + .put("quote", "BBCA.JK", &T { v: 7 }, 300) + .expect("cache write"); let fresh: Option = cache.get("quote", "BBCA.JK").expect("cache read fresh"); assert_eq!(fresh, Some(T { v: 7 })); let path = root.join("quote/BBCA.JK.json"); - let mut entry: CacheEntry = serde_json::from_str(&fs::read_to_string(&path).expect("read cache file")) - .expect("parse cache entry"); + let mut entry: CacheEntry = + serde_json::from_str(&fs::read_to_string(&path).expect("read cache file")) + .expect("parse cache entry"); entry.fetched_at = chrono::Utc::now() - chrono::Duration::seconds(1000); - fs::write(&path, serde_json::to_string(&entry).expect("serialize entry")).expect("write old entry"); + fs::write( + &path, + serde_json::to_string(&entry).expect("serialize entry"), + ) + .expect("write old entry"); let expired: Option = cache.get("quote", "BBCA.JK").expect("cache read expired"); assert_eq!(expired, None); - let stale: Option = cache.get_stale("quote", "BBCA.JK").expect("cache read stale"); + let stale: Option = cache + .get_stale("quote", "BBCA.JK") + .expect("cache read stale"); assert_eq!(stale, Some(T { v: 7 })); } } diff --git a/src/cli/stocks.rs b/src/cli/stocks.rs index 3652aed..fdbad69 100644 --- a/src/cli/stocks.rs +++ b/src/cli/stocks.rs @@ -1,7 +1,7 @@ use clap::{Args, Subcommand}; -use crate::api::types::{Interval, Period}; use crate::api::MarketDataProvider; +use crate::api::types::{Interval, Period}; use crate::cache::Cache; use crate::config::IdxConfig; use crate::error::IdxError; @@ -15,7 +15,9 @@ pub struct StocksCmd { #[derive(Debug, Subcommand)] pub enum StocksSubcommand { - Quote { symbols: Vec }, + Quote { + symbols: Vec, + }, History { symbol: String, #[arg(long, value_enum, default_value_t = Period::ThreeMonths)] @@ -39,9 +41,7 @@ pub fn handle( let mut quotes = Vec::new(); for sym in symbols.iter().flat_map(|s| s.split(',')) { let resolved = crate::api::resolve_symbol(sym, &config.exchange); - if !no_cache - && let Some(q) = cache.get("quote", &resolved)? - { + if !no_cache && let Some(q) = cache.get("quote", &resolved)? { quotes.push(q); continue; } @@ -61,10 +61,10 @@ pub fn handle( quotes.push(q); } Err(err) => { - if !no_cache - && let Some(stale) = cache.get_stale("quote", &resolved)? - { - eprintln!("warning: network failed, serving stale cache for {resolved}"); + if !no_cache && let Some(stale) = cache.get_stale("quote", &resolved)? { + eprintln!( + "warning: network failed, serving stale cache for {resolved}" + ); quotes.push(stale); continue; } @@ -82,13 +82,17 @@ pub fn handle( let resolved = crate::api::resolve_symbol(symbol, &config.exchange); let key = format!("{}-{}", period.as_str(), interval.as_str()); if !no_cache - && let Some(history) = cache.get::>("history", &format!("{resolved}-{key}"))? + && let Some(history) = cache + .get::>("history", &format!("{resolved}-{key}"))? { return render_history(&resolved, &history, &config.output); } if offline { let stale = cache - .get_stale::>("history", &format!("{resolved}-{key}"))? + .get_stale::>( + "history", + &format!("{resolved}-{key}"), + )? .ok_or_else(|| IdxError::CacheMiss(format!("history/{resolved}-{key}")))?; return render_history(&resolved, &stale, &config.output); } @@ -96,13 +100,21 @@ pub fn handle( match provider.history(&resolved, period, interval) { Ok(history) => { if !no_cache { - cache.put("history", &format!("{resolved}-{key}"), &history, config.quote_ttl)?; + cache.put( + "history", + &format!("{resolved}-{key}"), + &history, + config.quote_ttl, + )?; } render_history(&resolved, &history, &config.output) } Err(err) => { if !no_cache - && let Some(stale) = cache.get_stale::>("history", &format!("{resolved}-{key}"))? + && let Some(stale) = cache.get_stale::>( + "history", + &format!("{resolved}-{key}"), + )? { eprintln!("warning: network failed, serving stale cache for {resolved}"); return render_history(&resolved, &stale, &config.output); diff --git a/src/config.rs b/src/config.rs index d21bbc1..329507a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -89,7 +89,8 @@ impl IdxConfig { let path = config_path()?; if path.exists() { let raw = fs::read_to_string(&path).map_err(|e| IdxError::Io(e.to_string()))?; - let parsed: FileConfig = toml::from_str(&raw).map_err(|e| IdxError::ConfigError(e.to_string()))?; + let parsed: FileConfig = + toml::from_str(&raw).map_err(|e| IdxError::ConfigError(e.to_string()))?; if let Some(general) = parsed.general { if let Some(exchange) = general.exchange { cfg.exchange = exchange; @@ -142,7 +143,8 @@ pub fn get_config_value(key: &str) -> Result, IdxError> { return Ok(None); } let raw = fs::read_to_string(path).map_err(|e| IdxError::Io(e.to_string()))?; - let value: toml::Value = toml::from_str(&raw).map_err(|e| IdxError::ConfigError(e.to_string()))?; + let value: toml::Value = + toml::from_str(&raw).map_err(|e| IdxError::ConfigError(e.to_string()))?; let mut cur = &value; for part in key.split('.') { @@ -157,7 +159,8 @@ pub fn get_config_value(key: &str) -> Result, IdxError> { pub fn set_config_value(key: &str, value: &str) -> Result<(), IdxError> { let path = ensure_default_config()?; let raw = fs::read_to_string(&path).map_err(|e| IdxError::Io(e.to_string()))?; - let mut root: toml::Value = toml::from_str(&raw).map_err(|e| IdxError::ConfigError(e.to_string()))?; + let mut root: toml::Value = + toml::from_str(&raw).map_err(|e| IdxError::ConfigError(e.to_string()))?; let mut parts = key.split('.').peekable(); let mut current = root @@ -180,8 +183,11 @@ pub fn set_config_value(key: &str, value: &str) -> Result<(), IdxError> { } } - fs::write(&path, toml::to_string_pretty(&root).map_err(|e| IdxError::ConfigError(e.to_string()))?) - .map_err(|e| IdxError::Io(e.to_string())) + fs::write( + &path, + toml::to_string_pretty(&root).map_err(|e| IdxError::ConfigError(e.to_string()))?, + ) + .map_err(|e| IdxError::Io(e.to_string())) } fn parse_toml_value(value: &str) -> toml::Value { diff --git a/src/main.rs b/src/main.rs index 08482d6..8611ea3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,7 +40,13 @@ fn run() -> Result<(), IdxError> { } Commands::Stocks(stocks) => { let provider = default_provider(); - if let Err(err) = cli::stocks::handle(stocks, &config, provider.as_ref(), cli.offline, cli.no_cache) { + if let Err(err) = cli::stocks::handle( + stocks, + &config, + provider.as_ref(), + cli.offline, + cli.no_cache, + ) { emit_error(&err, &config.output); return Err(err); } diff --git a/src/output/json.rs b/src/output/json.rs index aa88aa6..9c094f0 100644 --- a/src/output/json.rs +++ b/src/output/json.rs @@ -3,7 +3,8 @@ use serde::Serialize; use crate::error::IdxError; pub fn print_json(value: &T) -> Result<(), IdxError> { - let out = serde_json::to_string_pretty(value).map_err(|e| IdxError::ParseError(e.to_string()))?; + let out = + serde_json::to_string_pretty(value).map_err(|e| IdxError::ParseError(e.to_string()))?; println!("{out}"); Ok(()) } diff --git a/src/output/mod.rs b/src/output/mod.rs index 0b0db34..9d7a272 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -15,14 +15,22 @@ pub enum OutputFormat { Json, } -pub fn render_quotes(quotes: &[Quote], format: &OutputFormat, no_color: bool) -> Result<(), IdxError> { +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> { +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), @@ -38,7 +46,10 @@ pub fn emit_error(err: &IdxError, format: &OutputFormat) { "code": format!("{:?}", err.code()).to_uppercase(), "message": err.to_string() }); - eprintln!("{}", serde_json::to_string_pretty(&payload).unwrap_or_else(|_| "{}".to_string())); + eprintln!( + "{}", + serde_json::to_string_pretty(&payload).unwrap_or_else(|_| "{}".to_string()) + ); } } } diff --git a/src/output/table.rs b/src/output/table.rs index 7d61769..c389d9f 100644 --- a/src/output/table.rs +++ b/src/output/table.rs @@ -1,4 +1,4 @@ -use comfy_table::{presets::UTF8_FULL, Cell, Color, ContentArrangement, Table}; +use comfy_table::{Cell, Color, ContentArrangement, Table, presets::UTF8_FULL}; use owo_colors::OwoColorize; use crate::api::types::{Ohlc, Quote}; @@ -39,7 +39,11 @@ pub fn print_quotes(quotes: &[Quote], no_color: bool) -> Result<(), IdxError> { 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())), + Cell::new( + q.market_cap + .map(format_idr) + .unwrap_or_else(|| "-".to_string()), + ), ]); } diff --git a/tests/cli.rs b/tests/cli.rs index 48c6f78..f615726 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -1,6 +1,6 @@ use std::fs; -use assert_cmd::{cargo::cargo_bin, Command}; +use assert_cmd::{Command, cargo::cargo_bin}; use predicates::prelude::*; fn bin() -> Command { @@ -16,10 +16,7 @@ fn test_env_dir(name: &str) -> std::path::PathBuf { #[test] fn help_works() { - bin() - .arg("--help") - .assert() - .success(); + bin().arg("--help").assert().success(); } #[test]