commit - f61649b9fb73b5e7718ec70b146d07b8608bb99d
commit + 40cb58189a29d8b347ced25f731c5de194405bf8
blob - f916ab07c7da1caa5aadd08957dbdea1788a8fed
blob + e8e915a1027fed98278a434fd194720909dc6ba5
--- README.md
+++ README.md
* `-s`, `--standings`: Show league standings/table.
* `-t`, `--token <TOKEN>`: API token from football-data.org (required).
* `-f`, `--filter <TEAM>`: Filter by team name or abbreviation.
-* `--all`: Show all matches (past and future).
-* `-l`, `--league <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>`: League to display. When omitted, all competitions are shown.
### Leagues
### Example
```sh
-# Show upcoming Premier League matches
+# List all matches across every competition (default)
./target/release/scores -t <TOKEN>
+# Show upcoming Premier League matches
+./target/release/scores -l epl -t <TOKEN>
+
# Show Premier League table
./target/release/scores -s -t <TOKEN>
-# Show upcoming Arsenal matches
+# List all matches involving Arsenal across every competition
./target/release/scores -f arsenal -t <TOKEN>
-# Show all Arsenal matches this season
-./target/release/scores -l epl -f ars -a -t <TOKEN>
+# Show upcoming Arsenal matches in the Premier League
+./target/release/scores -l epl -f arsenal -t <TOKEN>
# Show La Liga standings
./target/release/scores -s -l laliga -t <TOKEN>
blob - 74302d576466f2cc19cb76084c57b14ea4e002b0
blob + 331ce3a69004bdec2f44ca1c1e3c5afd7ba36f5d
--- src/api.rs
+++ src/api.rs
}
}
- async fn fetch<T: for<'de> serde::Deserialize<'de>>(&self, endpoint: &str) -> Result<T> {
- let url = format!("https://api.football-data.org/v4/competitions/{endpoint}");
+ async fn fetch<T: for<'de> serde::Deserialize<'de>>(&self, path: &str) -> Result<T> {
+ let url = format!("https://api.football-data.org/v4/{path}");
let response = self
.client
}
pub async fn fetch_matches(&self, league: League) -> Result<MatchesResponse> {
- self.fetch(&format!("{}/matches", league.code())).await
+ self.fetch(&format!("competitions/{}/matches", league.code()))
+ .await
}
pub async fn fetch_standings(&self, league: League) -> Result<StandingsResponse> {
- 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<MatchesResponse> {
+ self.fetch("matches").await
+ }
}
blob - a8b65c5ed9f1937c3e1892d3ceb6c04fa015e7f5
blob + 5897e9be4594d4f9c867423ddb314c8f960e0657
--- src/config.rs
+++ src/config.rs
#[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,
#[arg(long, short = 'f')]
pub filter: Option<String>,
- /// 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<String>,
}
impl Cli {
pub struct Config {
pub api_token: String,
- pub league: League,
+ pub league: Option<League>,
pub show_all: bool,
pub show_standings: bool,
pub team_filter: Option<String>,
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(),
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);
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
.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;
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
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();
score: Score {
full_time: ScoreDetail::default(),
},
+ competition: None,
}
}
blob - 57f4ccdad5733bfc9aa180cc26d155087638c2a4
blob + 0480fbe511e4a4c5fd270d5eb5510c849f7f15ed
--- src/main.rs
+++ src/main.rs
use config::Cli;
use error::Result;
+use league::League;
#[tokio::main]
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
pub home_team: Team,
pub away_team: Team,
pub score: Score,
+ pub competition: Option<Competition>,
}
#[derive(Debug, Deserialize, Clone)]
+pub struct Competition {
+ pub name: Option<String>,
+}
+
+#[derive(Debug, Deserialize, Clone)]
pub struct Team {
pub name: Option<String>,
pub tla: Option<String>,