commit 40cb58189a29d8b347ced25f731c5de194405bf8 from: mtmn date: Fri Jun 19 11:52:11 2026 UTC feat: add --all flag and set it to default commit - f61649b9fb73b5e7718ec70b146d07b8608bb99d commit + 40cb58189a29d8b347ced25f731c5de194405bf8 blob - f916ab07c7da1caa5aadd08957dbdea1788a8fed blob + e8e915a1027fed98278a434fd194720909dc6ba5 --- README.md +++ README.md @@ -30,8 +30,8 @@ cargo build --release * `-s`, `--standings`: Show league standings/table. * `-t`, `--token `: API token from football-data.org (required). * `-f`, `--filter `: Filter by team name or abbreviation. -* `--all`: Show all matches (past and future). -* `-l`, `--league `: League to display (default: epl). +* `-a`, `--all`: List all matches across every competition (ignores `--league`). This is the default when no `--league` is given. +* `-l`, `--league `: League to display. When omitted, all competitions are shown. ### Leagues @@ -43,17 +43,20 @@ cargo build --release ### Example ```sh -# Show upcoming Premier League matches +# List all matches across every competition (default) ./target/release/scores -t +# Show upcoming Premier League matches +./target/release/scores -l epl -t + # Show Premier League table ./target/release/scores -s -t -# Show upcoming Arsenal matches +# List all matches involving Arsenal across every competition ./target/release/scores -f arsenal -t -# Show all Arsenal matches this season -./target/release/scores -l epl -f ars -a -t +# Show upcoming Arsenal matches in the Premier League +./target/release/scores -l epl -f arsenal -t # Show La Liga standings ./target/release/scores -s -l laliga -t blob - 74302d576466f2cc19cb76084c57b14ea4e002b0 blob + 331ce3a69004bdec2f44ca1c1e3c5afd7ba36f5d --- src/api.rs +++ src/api.rs @@ -15,8 +15,8 @@ impl ApiClient { } } - async fn fetch serde::Deserialize<'de>>(&self, endpoint: &str) -> Result { - let url = format!("https://api.football-data.org/v4/competitions/{endpoint}"); + async fn fetch serde::Deserialize<'de>>(&self, path: &str) -> Result { + let url = format!("https://api.football-data.org/v4/{path}"); let response = self .client @@ -33,10 +33,17 @@ impl ApiClient { } pub async fn fetch_matches(&self, league: League) -> Result { - self.fetch(&format!("{}/matches", league.code())).await + self.fetch(&format!("competitions/{}/matches", league.code())) + .await } pub async fn fetch_standings(&self, league: League) -> Result { - self.fetch(&format!("{}/standings", league.code())).await + self.fetch(&format!("competitions/{}/standings", league.code())) + .await } + + /// Fetch all matches across every available competition (`GET /v4/matches`). + pub async fn fetch_all_matches(&self) -> Result { + self.fetch("matches").await + } } blob - a8b65c5ed9f1937c3e1892d3ceb6c04fa015e7f5 blob + 5897e9be4594d4f9c867423ddb314c8f960e0657 --- src/config.rs +++ src/config.rs @@ -17,7 +17,7 @@ pub struct Cli { #[arg(long, short = 's')] pub standings: bool, - /// Show all matches (past and future) + /// List all matches across every competition (default; ignores --league) #[arg(long, short = 'a')] pub all: bool, @@ -25,9 +25,9 @@ pub struct Cli { #[arg(long, short = 'f')] pub filter: Option, - /// League to display - #[arg(long, short = 'l', default_value = "epl", value_name = "LEAGUE")] - pub league: String, + /// League to display (defaults to all competitions when omitted) + #[arg(long, short = 'l', value_name = "LEAGUE")] + pub league: Option, } impl Cli { @@ -50,7 +50,7 @@ impl Cli { pub struct Config { pub api_token: String, - pub league: League, + pub league: Option, pub show_all: bool, pub show_standings: bool, pub team_filter: Option, @@ -62,8 +62,12 @@ impl Config { return Err(AppError::InvalidToken); } - let league = League::from_arg(&cli.league) - .ok_or_else(|| AppError::InvalidLeague(cli.league.clone()))?; + let league = match &cli.league { + Some(arg) => { + Some(League::from_arg(arg).ok_or_else(|| AppError::InvalidLeague(arg.clone()))?) + } + None => None, + }; Ok(Self { api_token: cli.token.clone(), @@ -86,25 +90,39 @@ mod tests { standings: false, all: true, filter: Some("arsenal".to_string()), - league: "epl".to_string(), + league: Some("epl".to_string()), }; let config = Config::from_cli(&cli).unwrap(); assert_eq!(config.api_token, "test-token"); - assert_eq!(config.league, League::PremierLeague); + assert_eq!(config.league, Some(League::PremierLeague)); assert!(config.show_all); assert!(!config.show_standings); assert_eq!(config.team_filter, Some("arsenal".to_string())); } #[test] + fn test_config_no_league_defaults_to_all() { + let cli = Cli { + token: "test-token".to_string(), + standings: false, + all: false, + filter: None, + league: None, + }; + + let config = Config::from_cli(&cli).unwrap(); + assert_eq!(config.league, None); + } + + #[test] fn test_config_missing_token() { let cli = Cli { token: String::new(), standings: false, all: false, filter: None, - league: "epl".to_string(), + league: Some("epl".to_string()), }; let result = Config::from_cli(&cli); @@ -118,7 +136,7 @@ mod tests { standings: false, all: false, filter: None, - league: "bundesliga".to_string(), + league: Some("bundesliga".to_string()), }; let result = Config::from_cli(&cli); blob - 347f29874df4b5a0b632dff6605f571c35d709cf blob + 5108e2b76716a655a1dddcb1c3933c17f2419f42 --- src/display.rs +++ src/display.rs @@ -59,7 +59,7 @@ pub fn filter_matches( .collect() } -pub fn display_table(matches: &[Match]) { +pub fn display_table(matches: &[Match], show_competition: bool) { if matches.is_empty() { println!("No matches found."); return; @@ -68,14 +68,20 @@ pub fn display_table(matches: &[Match]) { let mut table = Table::new(); table.set_format(*format::consts::FORMAT_BOX_CHARS); - table.add_row(Row::new(vec![ + let mut header = vec![ Cell::new("MD").style_spec("Fb"), Cell::new("Date & Time").style_spec("Fb"), + ]; + if show_competition { + header.push(Cell::new("Competition").style_spec("Fb")); + } + header.extend([ Cell::new("Home Team").style_spec("Fb"), Cell::new("Score").style_spec("Fb"), Cell::new("Away Team").style_spec("Fb"), Cell::new("Status").style_spec("Fb"), - ])); + ]); + table.add_row(Row::new(header)); for m in matches { let matchday = m @@ -101,14 +107,22 @@ pub fn display_table(matches: &[Match]) { let home_team = m.home_team.name.as_deref().unwrap_or("Unknown"); let away_team = m.away_team.name.as_deref().unwrap_or("Unknown"); - table.add_row(Row::new(vec![ - Cell::new(&matchday), - Cell::new(&date), + let mut cells = vec![Cell::new(&matchday), Cell::new(&date)]; + if show_competition { + let competition = m + .competition + .as_ref() + .and_then(|c| c.name.as_deref()) + .unwrap_or("-"); + cells.push(Cell::new(competition)); + } + cells.extend([ Cell::new(home_team).style_spec("r"), Cell::new(&score).style_spec("Fyc"), Cell::new(away_team).style_spec("l"), Cell::new(&format!("{status_icon} {status}")), - ])); + ]); + table.add_row(Row::new(cells)); } table.printstd(); @@ -185,6 +199,7 @@ mod tests { score: Score { full_time: ScoreDetail::default(), }, + competition: None, } } blob - 57f4ccdad5733bfc9aa180cc26d155087638c2a4 blob + 0480fbe511e4a4c5fd270d5eb5510c849f7f15ed --- src/main.rs +++ src/main.rs @@ -7,6 +7,7 @@ mod models; use config::Cli; use error::Result; +use league::League; #[tokio::main] async fn main() -> Result<()> { @@ -15,16 +16,19 @@ async fn main() -> Result<()> { let api_client = api::ApiClient::new(config.api_token); if config.show_standings { - let standings = api_client.fetch_standings(config.league).await?; + let league = config.league.unwrap_or(League::PremierLeague); + let standings = api_client.fetch_standings(league).await?; display::display_standings(standings); + } else if let (false, Some(league)) = (config.show_all, config.league) { + let response = api_client.fetch_matches(league).await?; + let filtered = + display::filter_matches(response.matches, config.team_filter.as_deref(), false); + display::display_table(&filtered, false); } else { - let response = api_client.fetch_matches(config.league).await?; - let filtered = display::filter_matches( - response.matches, - config.team_filter.as_deref(), - config.show_all, - ); - display::display_table(&filtered); + let response = api_client.fetch_all_matches().await?; + let filtered = + display::filter_matches(response.matches, config.team_filter.as_deref(), true); + display::display_table(&filtered, true); } Ok(()) blob - 51c9addaf1d8677e06267a74af7c42de3076a433 blob + 1097cba7f660902243a139389e3b32ad1f046833 --- src/models.rs +++ src/models.rs @@ -10,9 +10,15 @@ pub struct Match { pub home_team: Team, pub away_team: Team, pub score: Score, + pub competition: Option, } #[derive(Debug, Deserialize, Clone)] +pub struct Competition { + pub name: Option, +} + +#[derive(Debug, Deserialize, Clone)] pub struct Team { pub name: Option, pub tla: Option,