cfbtui 1.13.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. cfbtui-1.13.1/LICENSE +21 -0
  2. cfbtui-1.13.1/PKG-INFO +193 -0
  3. cfbtui-1.13.1/README.md +167 -0
  4. cfbtui-1.13.1/cfbtui/__init__.py +4 -0
  5. cfbtui-1.13.1/cfbtui/__main__.py +223 -0
  6. cfbtui-1.13.1/cfbtui/app.py +623 -0
  7. cfbtui-1.13.1/cfbtui/clipboard.py +16 -0
  8. cfbtui-1.13.1/cfbtui/config.py +304 -0
  9. cfbtui-1.13.1/cfbtui/espn/__init__.py +1 -0
  10. cfbtui-1.13.1/cfbtui/espn/client.py +207 -0
  11. cfbtui-1.13.1/cfbtui/espn/models.py +352 -0
  12. cfbtui-1.13.1/cfbtui/espn/parse.py +604 -0
  13. cfbtui-1.13.1/cfbtui/espn/periods.py +46 -0
  14. cfbtui-1.13.1/cfbtui/news.py +159 -0
  15. cfbtui-1.13.1/cfbtui/poll.py +336 -0
  16. cfbtui-1.13.1/cfbtui/presence.py +41 -0
  17. cfbtui-1.13.1/cfbtui/selfupdate.py +414 -0
  18. cfbtui-1.13.1/cfbtui/share.py +128 -0
  19. cfbtui-1.13.1/cfbtui/state/__init__.py +1 -0
  20. cfbtui-1.13.1/cfbtui/state/events.py +222 -0
  21. cfbtui-1.13.1/cfbtui/state/store.py +338 -0
  22. cfbtui-1.13.1/cfbtui/state/upset.py +91 -0
  23. cfbtui-1.13.1/cfbtui/ui/__init__.py +0 -0
  24. cfbtui-1.13.1/cfbtui/ui/app.tcss +19 -0
  25. cfbtui-1.13.1/cfbtui/ui/filters.py +122 -0
  26. cfbtui-1.13.1/cfbtui/ui/glyphs.py +256 -0
  27. cfbtui-1.13.1/cfbtui/ui/labels.py +79 -0
  28. cfbtui-1.13.1/cfbtui/ui/pulse.py +45 -0
  29. cfbtui-1.13.1/cfbtui/ui/screens/__init__.py +1 -0
  30. cfbtui-1.13.1/cfbtui/ui/screens/conferences.py +57 -0
  31. cfbtui-1.13.1/cfbtui/ui/screens/detail.py +453 -0
  32. cfbtui-1.13.1/cfbtui/ui/screens/help.py +48 -0
  33. cfbtui-1.13.1/cfbtui/ui/screens/news.py +298 -0
  34. cfbtui-1.13.1/cfbtui/ui/screens/rankings.py +176 -0
  35. cfbtui-1.13.1/cfbtui/ui/screens/settings.py +88 -0
  36. cfbtui-1.13.1/cfbtui/ui/story.py +139 -0
  37. cfbtui-1.13.1/cfbtui/ui/theme.py +118 -0
  38. cfbtui-1.13.1/cfbtui/ui/widgets/__init__.py +0 -0
  39. cfbtui-1.13.1/cfbtui/ui/widgets/game_card.py +392 -0
  40. cfbtui-1.13.1/cfbtui/ui/widgets/score_grid.py +271 -0
  41. cfbtui-1.13.1/cfbtui/ui/widgets/status_bar.py +131 -0
  42. cfbtui-1.13.1/cfbtui/ui/widgets/ticker.py +241 -0
  43. cfbtui-1.13.1/cfbtui/update.py +160 -0
  44. cfbtui-1.13.1/cfbtui/version.py +113 -0
  45. cfbtui-1.13.1/cfbtui.egg-info/PKG-INFO +193 -0
  46. cfbtui-1.13.1/cfbtui.egg-info/SOURCES.txt +50 -0
  47. cfbtui-1.13.1/cfbtui.egg-info/dependency_links.txt +1 -0
  48. cfbtui-1.13.1/cfbtui.egg-info/entry_points.txt +2 -0
  49. cfbtui-1.13.1/cfbtui.egg-info/requires.txt +15 -0
  50. cfbtui-1.13.1/cfbtui.egg-info/top_level.txt +1 -0
  51. cfbtui-1.13.1/pyproject.toml +47 -0
  52. cfbtui-1.13.1/setup.cfg +4 -0
cfbtui-1.13.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bill Kane
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
cfbtui-1.13.1/PKG-INFO ADDED
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.4
2
+ Name: cfbtui
3
+ Version: 1.13.1
4
+ Summary: College football scoreboard for your terminal. Live scores, rankings and scoring plays.
5
+ Author: Bill Kane
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/WPTK/cfbtui
8
+ Project-URL: Issues, https://github.com/WPTK/cfbtui/issues
9
+ Project-URL: Changelog, https://github.com/WPTK/cfbtui/blob/main/CHANGELOG.md
10
+ Keywords: college football,scoreboard,tui,terminal,textual,espn
11
+ Requires-Python: >=3.13
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: textual<9,>=8.2
15
+ Requires-Dist: httpx<1,>=0.28
16
+ Requires-Dist: platformdirs<5,>=4
17
+ Requires-Dist: tomli-w<2,>=1
18
+ Requires-Dist: pyperclip<2,>=1.9
19
+ Requires-Dist: tzdata>=2024.1; sys_platform == "win32"
20
+ Provides-Extra: dev
21
+ Requires-Dist: pyinstaller<7,>=6.22; extra == "dev"
22
+ Requires-Dist: build<2,>=1; extra == "dev"
23
+ Provides-Extra: binary
24
+ Requires-Dist: tzdata>=2024.1; extra == "binary"
25
+ Dynamic: license-file
26
+
27
+ # cfbtui
28
+
29
+ [![Latest release](https://img.shields.io/github/v/release/WPTK/cfbtui)](https://github.com/WPTK/cfbtui/releases/latest)
30
+
31
+ cfbtui is a terminal scoreboard for FBS and FCS college football. It reads ESPN's unofficial public JSON endpoints (no API key) and renders live scores, rankings, scoring plays and news in a [Textual](https://textual.textualize.io/) interface.
32
+
33
+ ![The scoreboard](docs/img/grid.png)
34
+
35
+ - Games for the week, live ones first. Scores refresh every 20 seconds while you are at the computer and every 2 minutes once you step away.
36
+ - Live cards show the clock, down and distance, and who has the ball, and flash on a score.
37
+ - A card gets a yellow border and an UPSET label when a ranked team is losing, or lost, to a worse-ranked or unranked team.
38
+ - Game detail has every scoring play and win probability. AP and Coaches polls switch to the CFP ranking once the committee releases it.
39
+ - ESPN's college football news opens in the app.
40
+ - You can star favorites, filter, pick a conference, switch between cards and a list, and open any week including Bowls and the Playoff.
41
+ - You can copy a game, or the whole board, for pasting into Discord.
42
+
43
+ ## Prerequisites
44
+
45
+ - A terminal with color and Unicode: Windows Terminal, iTerm2, Terminal.app or a current Linux terminal. The old Windows Command Prompt window does not qualify.
46
+ - To run the standalone binary: Windows, macOS (Apple silicon or Intel), Linux x86_64 or Linux arm64 with glibc 2.31 or newer (Ubuntu 20.04, Debian 11, Fedora 32, RHEL 9).
47
+ - To run from source or the wheel: Python 3.13 or newer.
48
+ - For Discord copy on Linux: `xclip` (X11) or `wl-clipboard` (Wayland).
49
+ - For the default icon style: a [Nerd Font](https://www.nerdfonts.com/) set as the terminal font. Without one, start with `--glyphs unicode`.
50
+
51
+ ## Quickstart
52
+
53
+ Standalone binary:
54
+
55
+ 1. Download the file for your system from the [latest release](https://github.com/WPTK/cfbtui/releases/latest): `cfbtui-windows-x86_64.exe`, `cfbtui-macos-arm64`, `cfbtui-macos-x86_64`, `cfbtui-linux-x86_64` or `cfbtui-linux-arm64`.
56
+ 2. On macOS and Linux, make it executable. On macOS, also remove the quarantine flag (the binary is not notarized):
57
+
58
+ ```sh
59
+ chmod +x cfbtui-macos-arm64
60
+ xattr -d com.apple.quarantine cfbtui-macos-arm64
61
+ ```
62
+
63
+ 3. Run it. On Windows the exe is not code-signed, so if SmartScreen warns, choose More info, then Run anyway.
64
+
65
+ ```sh
66
+ ./cfbtui-macos-arm64
67
+ ```
68
+
69
+ ```powershell
70
+ .\cfbtui-windows-x86_64.exe
71
+ ```
72
+
73
+ From source:
74
+
75
+ 1. Clone the repository and create a virtual environment:
76
+
77
+ ```sh
78
+ git clone https://github.com/WPTK/cfbtui.git
79
+ cd cfbtui
80
+ python3 -m venv .venv
81
+ ```
82
+
83
+ 2. Install the package. On Windows use `.venv\Scripts\python`; on macOS and Linux use `.venv/bin/python`:
84
+
85
+ ```sh
86
+ .venv/bin/python -m pip install -e .
87
+ ```
88
+
89
+ 3. Run it:
90
+
91
+ ```sh
92
+ .venv/bin/python -m cfbtui
93
+ ```
94
+
95
+ Expected result: the current week's games appear as cards within a few seconds. Press `?` for every key and `q` to quit. If you see "Could not reach ESPN", check your connection; cfbtui keeps retrying.
96
+
97
+ To verify a download, see [docs/verify.md](docs/verify.md).
98
+
99
+ ## Configuration
100
+
101
+ Settings come from three layers. Command-line flags override the settings file for that run only. The settings file overrides the defaults. A bad value is ignored with a warning.
102
+
103
+ ### Flags
104
+
105
+ | Flag | Default | Description |
106
+ |---|---|---|
107
+ | `--week N` | current week | Start on week 1-15, or `bowls` or `playoff`. |
108
+ | `--group fbs\|fcs` | `fbs` | Start on the FBS or FCS board. |
109
+ | `--glyphs NAME` | `nerd` | Icon style: `ascii`, `unicode`, `emoji` or `nerd`. |
110
+ | `--layout grid\|list` | `grid` | Cards, or one line per game. |
111
+ | `--config PATH` | platform default | Read and save this settings file. |
112
+ | `--no-config` | off | Ignore the settings file and never write it. |
113
+ | `--no-update-check` | off | Skip the release check and the download. |
114
+ | `--version` | | Print the version and exit. |
115
+
116
+ ### Settings file
117
+
118
+ cfbtui creates the file the first time you star a team or change a setting. Edit it in any text editor.
119
+
120
+ | System | Path |
121
+ |---|---|
122
+ | Windows | `%LOCALAPPDATA%\cfbtui\config.toml` |
123
+ | macOS | `~/Library/Application Support/cfbtui/config.toml` |
124
+ | Linux | `~/.config/cfbtui/config.toml`, or under `$XDG_CONFIG_HOME` |
125
+
126
+ | Key | Default | Description |
127
+ |---|---|---|
128
+ | `[poll] live` | `20` | Seconds between updates while you are at the computer. |
129
+ | `[poll] idle` | `120` | Seconds between updates once you have walked away. |
130
+ | `[poll] away_after` | `300` | Seconds without keyboard or mouse input before you count as away. |
131
+ | `[poll] backoff_max` | `120` | Longest wait in seconds between retries after failed requests. |
132
+ | `[view] glyph_set` | `"nerd"` | `ascii`, `unicode`, `emoji` or `nerd`. |
133
+ | `[view] layout` | `"grid"` | `grid` or `list`. |
134
+ | `[view] week` | current week | 1-15, 16 for Bowls, 17 for the Playoff. |
135
+ | `[view] group` | `"fbs"` | `fbs` or `fcs`. |
136
+ | `[favorites] team_ids` | `[]` | ESPN team ids, such as `["251"]`. The `1` and `2` keys fill this in. |
137
+ | `[favorites] abbreviations` | `[]` | Teams by abbreviation, such as `["TEX"]`. |
138
+ | `[alerts] enabled` | `true` | Pulse a card on Red Zone, Upset Watch, Overtime or Final. |
139
+ | `[updates] check` | `true` | Look for a newer release at startup. |
140
+ | `[updates] auto` | `true` | Download new minor and patch versions (standalone binaries only). |
141
+
142
+ Update behavior is in [docs/updating.md](docs/updating.md).
143
+
144
+ ## Usage and controls
145
+
146
+ Arrow keys move between cards, `Enter` opens one and `?` lists every key.
147
+
148
+ ![Game detail](docs/img/detail.png)
149
+
150
+ | Key | Action |
151
+ |---|---|
152
+ | arrow keys | Move between games. |
153
+ | `Enter` | Open the game: every scoring play and win probability. |
154
+ | `R` | Rankings. Press `c` there to copy the polls for Discord, one poll per press. |
155
+ | `N` | News. `Enter` reads a story, `o` opens the original in your browser, `r` refreshes. |
156
+ | `[` `]` | Previous or next week. After week 15 come Bowls and the Playoff. |
157
+ | `t` | Back to this week. |
158
+ | `f` | Switch between FBS and FCS. |
159
+ | `l` | Filter: all games, Top 25, live only, favorites. |
160
+ | `o` | Pick a conference. |
161
+ | `v` | Switch between cards and a list. |
162
+ | `S` | Settings. Changes save as you make them. |
163
+ | `1` `2` | Star the away or home team. Starred games move to a Favorites section at the top. |
164
+ | `c` | Copy the selected game for Discord, with the winner in bold. |
165
+ | `C` | Copy the board as a code block. Discord caps messages at 2,000 characters, so a big Saturday copies one part per press. |
166
+ | `r` | Refresh now. |
167
+ | `g` | Cycle the icon style. |
168
+ | `Tab`, then `Up` or `Down` | Move through the ticker. `Enter` or a click shows that game. |
169
+ | `Esc` | Close a screen. |
170
+ | `q` | Quit. |
171
+
172
+ Once the College Football Playoff committee releases its first ranking, `R` shows only the CFP ranking for the rest of the season. The FCS board shows the FCS Coaches poll.
173
+
174
+ ![Rankings](docs/img/rankings.png)
175
+
176
+ ## Troubleshooting
177
+
178
+ - Icons show as empty boxes: set a Nerd Font as the terminal font, or press `g` to cycle to another icon style.
179
+ - "Could not reach ESPN": check your connection. If the rest of the internet works, ESPN's service may be down. cfbtui retries with growing waits.
180
+ - No games listed: the week may have no games, or a filter or conference is hiding them. Press `l` until the bottom bar says all games, then `t`.
181
+ - `c` reports no clipboard on Linux: install `xclip` or `wl-clipboard`.
182
+
183
+ ## Data and privacy
184
+
185
+ Scores and news come from ESPN's public, unofficial and undocumented endpoints, which can change or disappear without notice. cfbtui is an independent project and has no affiliation with ESPN or the NCAA.
186
+
187
+ The news list refreshes every 10 minutes while you are at the computer and stops once you step away (on Windows; on other systems cfbtui cannot detect this and assumes you are there). A story's text is fetched only when you open it, kept in memory for the last 20 stories and never written to disk. Stories show ESPN's name as the source, and `o` opens the original. ESPN's content falls under Disney's [terms of use](https://disneytermsofuse.com/english/), which grant a personal, noncommercial licence and restrict automated access and copying. Fetching a story only when you ask lowers that risk and does not remove it. This is not legal advice.
188
+
189
+ At startup cfbtui requests the latest release from GitHub. The request reveals your IP address and nothing else. Turn it off with `--no-update-check` or `check = false`.
190
+
191
+ ## License
192
+
193
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,167 @@
1
+ # cfbtui
2
+
3
+ [![Latest release](https://img.shields.io/github/v/release/WPTK/cfbtui)](https://github.com/WPTK/cfbtui/releases/latest)
4
+
5
+ cfbtui is a terminal scoreboard for FBS and FCS college football. It reads ESPN's unofficial public JSON endpoints (no API key) and renders live scores, rankings, scoring plays and news in a [Textual](https://textual.textualize.io/) interface.
6
+
7
+ ![The scoreboard](docs/img/grid.png)
8
+
9
+ - Games for the week, live ones first. Scores refresh every 20 seconds while you are at the computer and every 2 minutes once you step away.
10
+ - Live cards show the clock, down and distance, and who has the ball, and flash on a score.
11
+ - A card gets a yellow border and an UPSET label when a ranked team is losing, or lost, to a worse-ranked or unranked team.
12
+ - Game detail has every scoring play and win probability. AP and Coaches polls switch to the CFP ranking once the committee releases it.
13
+ - ESPN's college football news opens in the app.
14
+ - You can star favorites, filter, pick a conference, switch between cards and a list, and open any week including Bowls and the Playoff.
15
+ - You can copy a game, or the whole board, for pasting into Discord.
16
+
17
+ ## Prerequisites
18
+
19
+ - A terminal with color and Unicode: Windows Terminal, iTerm2, Terminal.app or a current Linux terminal. The old Windows Command Prompt window does not qualify.
20
+ - To run the standalone binary: Windows, macOS (Apple silicon or Intel), Linux x86_64 or Linux arm64 with glibc 2.31 or newer (Ubuntu 20.04, Debian 11, Fedora 32, RHEL 9).
21
+ - To run from source or the wheel: Python 3.13 or newer.
22
+ - For Discord copy on Linux: `xclip` (X11) or `wl-clipboard` (Wayland).
23
+ - For the default icon style: a [Nerd Font](https://www.nerdfonts.com/) set as the terminal font. Without one, start with `--glyphs unicode`.
24
+
25
+ ## Quickstart
26
+
27
+ Standalone binary:
28
+
29
+ 1. Download the file for your system from the [latest release](https://github.com/WPTK/cfbtui/releases/latest): `cfbtui-windows-x86_64.exe`, `cfbtui-macos-arm64`, `cfbtui-macos-x86_64`, `cfbtui-linux-x86_64` or `cfbtui-linux-arm64`.
30
+ 2. On macOS and Linux, make it executable. On macOS, also remove the quarantine flag (the binary is not notarized):
31
+
32
+ ```sh
33
+ chmod +x cfbtui-macos-arm64
34
+ xattr -d com.apple.quarantine cfbtui-macos-arm64
35
+ ```
36
+
37
+ 3. Run it. On Windows the exe is not code-signed, so if SmartScreen warns, choose More info, then Run anyway.
38
+
39
+ ```sh
40
+ ./cfbtui-macos-arm64
41
+ ```
42
+
43
+ ```powershell
44
+ .\cfbtui-windows-x86_64.exe
45
+ ```
46
+
47
+ From source:
48
+
49
+ 1. Clone the repository and create a virtual environment:
50
+
51
+ ```sh
52
+ git clone https://github.com/WPTK/cfbtui.git
53
+ cd cfbtui
54
+ python3 -m venv .venv
55
+ ```
56
+
57
+ 2. Install the package. On Windows use `.venv\Scripts\python`; on macOS and Linux use `.venv/bin/python`:
58
+
59
+ ```sh
60
+ .venv/bin/python -m pip install -e .
61
+ ```
62
+
63
+ 3. Run it:
64
+
65
+ ```sh
66
+ .venv/bin/python -m cfbtui
67
+ ```
68
+
69
+ Expected result: the current week's games appear as cards within a few seconds. Press `?` for every key and `q` to quit. If you see "Could not reach ESPN", check your connection; cfbtui keeps retrying.
70
+
71
+ To verify a download, see [docs/verify.md](docs/verify.md).
72
+
73
+ ## Configuration
74
+
75
+ Settings come from three layers. Command-line flags override the settings file for that run only. The settings file overrides the defaults. A bad value is ignored with a warning.
76
+
77
+ ### Flags
78
+
79
+ | Flag | Default | Description |
80
+ |---|---|---|
81
+ | `--week N` | current week | Start on week 1-15, or `bowls` or `playoff`. |
82
+ | `--group fbs\|fcs` | `fbs` | Start on the FBS or FCS board. |
83
+ | `--glyphs NAME` | `nerd` | Icon style: `ascii`, `unicode`, `emoji` or `nerd`. |
84
+ | `--layout grid\|list` | `grid` | Cards, or one line per game. |
85
+ | `--config PATH` | platform default | Read and save this settings file. |
86
+ | `--no-config` | off | Ignore the settings file and never write it. |
87
+ | `--no-update-check` | off | Skip the release check and the download. |
88
+ | `--version` | | Print the version and exit. |
89
+
90
+ ### Settings file
91
+
92
+ cfbtui creates the file the first time you star a team or change a setting. Edit it in any text editor.
93
+
94
+ | System | Path |
95
+ |---|---|
96
+ | Windows | `%LOCALAPPDATA%\cfbtui\config.toml` |
97
+ | macOS | `~/Library/Application Support/cfbtui/config.toml` |
98
+ | Linux | `~/.config/cfbtui/config.toml`, or under `$XDG_CONFIG_HOME` |
99
+
100
+ | Key | Default | Description |
101
+ |---|---|---|
102
+ | `[poll] live` | `20` | Seconds between updates while you are at the computer. |
103
+ | `[poll] idle` | `120` | Seconds between updates once you have walked away. |
104
+ | `[poll] away_after` | `300` | Seconds without keyboard or mouse input before you count as away. |
105
+ | `[poll] backoff_max` | `120` | Longest wait in seconds between retries after failed requests. |
106
+ | `[view] glyph_set` | `"nerd"` | `ascii`, `unicode`, `emoji` or `nerd`. |
107
+ | `[view] layout` | `"grid"` | `grid` or `list`. |
108
+ | `[view] week` | current week | 1-15, 16 for Bowls, 17 for the Playoff. |
109
+ | `[view] group` | `"fbs"` | `fbs` or `fcs`. |
110
+ | `[favorites] team_ids` | `[]` | ESPN team ids, such as `["251"]`. The `1` and `2` keys fill this in. |
111
+ | `[favorites] abbreviations` | `[]` | Teams by abbreviation, such as `["TEX"]`. |
112
+ | `[alerts] enabled` | `true` | Pulse a card on Red Zone, Upset Watch, Overtime or Final. |
113
+ | `[updates] check` | `true` | Look for a newer release at startup. |
114
+ | `[updates] auto` | `true` | Download new minor and patch versions (standalone binaries only). |
115
+
116
+ Update behavior is in [docs/updating.md](docs/updating.md).
117
+
118
+ ## Usage and controls
119
+
120
+ Arrow keys move between cards, `Enter` opens one and `?` lists every key.
121
+
122
+ ![Game detail](docs/img/detail.png)
123
+
124
+ | Key | Action |
125
+ |---|---|
126
+ | arrow keys | Move between games. |
127
+ | `Enter` | Open the game: every scoring play and win probability. |
128
+ | `R` | Rankings. Press `c` there to copy the polls for Discord, one poll per press. |
129
+ | `N` | News. `Enter` reads a story, `o` opens the original in your browser, `r` refreshes. |
130
+ | `[` `]` | Previous or next week. After week 15 come Bowls and the Playoff. |
131
+ | `t` | Back to this week. |
132
+ | `f` | Switch between FBS and FCS. |
133
+ | `l` | Filter: all games, Top 25, live only, favorites. |
134
+ | `o` | Pick a conference. |
135
+ | `v` | Switch between cards and a list. |
136
+ | `S` | Settings. Changes save as you make them. |
137
+ | `1` `2` | Star the away or home team. Starred games move to a Favorites section at the top. |
138
+ | `c` | Copy the selected game for Discord, with the winner in bold. |
139
+ | `C` | Copy the board as a code block. Discord caps messages at 2,000 characters, so a big Saturday copies one part per press. |
140
+ | `r` | Refresh now. |
141
+ | `g` | Cycle the icon style. |
142
+ | `Tab`, then `Up` or `Down` | Move through the ticker. `Enter` or a click shows that game. |
143
+ | `Esc` | Close a screen. |
144
+ | `q` | Quit. |
145
+
146
+ Once the College Football Playoff committee releases its first ranking, `R` shows only the CFP ranking for the rest of the season. The FCS board shows the FCS Coaches poll.
147
+
148
+ ![Rankings](docs/img/rankings.png)
149
+
150
+ ## Troubleshooting
151
+
152
+ - Icons show as empty boxes: set a Nerd Font as the terminal font, or press `g` to cycle to another icon style.
153
+ - "Could not reach ESPN": check your connection. If the rest of the internet works, ESPN's service may be down. cfbtui retries with growing waits.
154
+ - No games listed: the week may have no games, or a filter or conference is hiding them. Press `l` until the bottom bar says all games, then `t`.
155
+ - `c` reports no clipboard on Linux: install `xclip` or `wl-clipboard`.
156
+
157
+ ## Data and privacy
158
+
159
+ Scores and news come from ESPN's public, unofficial and undocumented endpoints, which can change or disappear without notice. cfbtui is an independent project and has no affiliation with ESPN or the NCAA.
160
+
161
+ The news list refreshes every 10 minutes while you are at the computer and stops once you step away (on Windows; on other systems cfbtui cannot detect this and assumes you are there). A story's text is fetched only when you open it, kept in memory for the last 20 stories and never written to disk. Stories show ESPN's name as the source, and `o` opens the original. ESPN's content falls under Disney's [terms of use](https://disneytermsofuse.com/english/), which grant a personal, noncommercial licence and restrict automated access and copying. Fetching a story only when you ask lowers that risk and does not remove it. This is not legal advice.
162
+
163
+ At startup cfbtui requests the latest release from GitHub. The request reveals your IP address and nothing else. Turn it off with `--no-update-check` or `check = false`.
164
+
165
+ ## License
166
+
167
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,4 @@
1
+ """cfbtui: a colorful college football scoreboard for the terminal."""
2
+
3
+ # FibVer. Do not edit by hand; use `python -m cfbtui.version bump <part> --write`.
4
+ __version__ = "1.13.1"
@@ -0,0 +1,223 @@
1
+ """Command-line entry point: ``python -m cfbtui`` or the ``cfbtui`` console script.
2
+
3
+ Flags override the config file for this run only and are never written back.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import argparse
8
+ import contextlib
9
+ import os
10
+ import subprocess
11
+ import sys
12
+ from functools import partial
13
+ from pathlib import Path
14
+
15
+ from cfbtui import __version__
16
+ from cfbtui.config import GROUPS, LAYOUTS, Config, LoadResult, apply_overrides, default_path, load_config
17
+ from cfbtui.espn.periods import LAST_REGULAR_WEEK, MAX_WEEK, MIN_WEEK, NAMED
18
+ from cfbtui.ui.glyphs import GLYPH_SET_NAMES
19
+
20
+
21
+ def _week(text: str) -> int:
22
+ if text.strip().lower() in NAMED:
23
+ return NAMED[text.strip().lower()]
24
+ try:
25
+ value = int(text)
26
+ except ValueError:
27
+ raise argparse.ArgumentTypeError(f"{text!r} is not a week number, 'bowls' or 'playoff'") from None
28
+ if not MIN_WEEK <= value <= MAX_WEEK:
29
+ raise argparse.ArgumentTypeError(f"week must be {MIN_WEEK} to {MAX_WEEK} (16 = bowls, 17 = playoff)")
30
+ return value
31
+
32
+
33
+ def build_parser() -> argparse.ArgumentParser:
34
+ parser = argparse.ArgumentParser(
35
+ prog="cfbtui",
36
+ description="A colorful college football scoreboard for the terminal.",
37
+ )
38
+ parser.add_argument("--version", action="version", version=f"cfbtui {__version__}")
39
+ parser.add_argument(
40
+ "--week",
41
+ type=_week,
42
+ metavar="N",
43
+ help=f"start on week N ({MIN_WEEK}-{LAST_REGULAR_WEEK}), 'bowls' or 'playoff' instead of the current week",
44
+ )
45
+ parser.add_argument("--group", type=str.lower, choices=sorted(GROUPS), help="FBS or FCS board")
46
+ parser.add_argument(
47
+ "--glyphs",
48
+ type=str.lower,
49
+ choices=GLYPH_SET_NAMES,
50
+ metavar="NAME",
51
+ help=f"glyph set: {', '.join(GLYPH_SET_NAMES)}",
52
+ )
53
+ parser.add_argument("--layout", type=str.lower, choices=LAYOUTS, help="card grid or one line per game")
54
+ parser.add_argument("--config", type=Path, metavar="PATH", help=f"config file (default: {default_path()})")
55
+ parser.add_argument("--no-config", action="store_true", help="ignore the config file and do not save to it")
56
+ parser.add_argument("--selftest", action="store_true", help=argparse.SUPPRESS)
57
+ parser.add_argument("--update-test", choices=("apply", "restart"), help=argparse.SUPPRESS)
58
+ parser.add_argument("--no-update-check", action="store_true", help="do not look for a newer release")
59
+ return parser
60
+
61
+
62
+ def resolve(argv: list[str] | None = None) -> tuple[Config, LoadResult, Path | None]:
63
+ """Parse flags, load the file, and layer the flags on top.
64
+
65
+ Returns the effective config, the raw load result (for its warnings) and
66
+ the path favorites will be saved to (``None`` under ``--no-config``).
67
+ """
68
+ args = build_parser().parse_args(argv)
69
+ if args.no_config:
70
+ loaded = LoadResult(Config())
71
+ save_path = None
72
+ else:
73
+ save_path = args.config if args.config is not None else default_path()
74
+ loaded = load_config(save_path)
75
+ config = apply_overrides(
76
+ loaded.config,
77
+ week=args.week,
78
+ group=args.group,
79
+ glyph_set=args.glyphs,
80
+ layout=args.layout,
81
+ check_updates=False if args.no_update_check else None,
82
+ )
83
+ return config, loaded, save_path
84
+
85
+
86
+ def selftest() -> int:
87
+ """Start the app headless against an empty board, so a frozen build proves its imports, CSS and tzdata."""
88
+ import asyncio
89
+ from zoneinfo import ZoneInfo
90
+
91
+ from cfbtui.app import CfbApp
92
+ from cfbtui.espn.models import Scoreboard, Summary
93
+
94
+ class EmptyBoard:
95
+ async def scoreboard(self, *, week: int | None = None, group: int = 80) -> Scoreboard:
96
+ return Scoreboard()
97
+
98
+ async def rankings(self) -> tuple:
99
+ return ()
100
+
101
+ async def summary(self, event_id: str) -> Summary:
102
+ raise NotImplementedError # the selftest never opens a game
103
+
104
+ async def news(self, limit: int = 30) -> tuple:
105
+ return ()
106
+
107
+ async def story(self, url: str) -> str:
108
+ raise NotImplementedError # the selftest never opens a story
109
+
110
+ async def run() -> None:
111
+ app = CfbApp(client=EmptyBoard(), config=Config())
112
+ async with app.run_test(size=(100, 30)) as pilot:
113
+ await pilot.pause()
114
+
115
+ ZoneInfo("America/New_York")
116
+ asyncio.run(run())
117
+ print("selftest ok")
118
+ return 0
119
+
120
+
121
+ def restart(target: Path, args: list[str], execve=os.execve, call=subprocess.call) -> None:
122
+ """Start the installed binary again with the same arguments and a clean PyInstaller environment.
123
+
124
+ Windows has no exec, so it waits for the child.
125
+ """
126
+ from cfbtui.selfupdate import child_env
127
+
128
+ if sys.platform == "win32":
129
+ raise SystemExit(call([str(target), *args], env=child_env()))
130
+ execve(target, [str(target), *args], child_env())
131
+
132
+
133
+ def install_update(updater, staged, app) -> bool:
134
+ """Swap the staged binary in after the app has exited, or discard it. Returns whether it was installed.
135
+
136
+ A staged update is installed only if cfbtui exited normally and auto-update is still on (or the user chose
137
+ Restart now). A failure leaves the old version in place and says why on stderr.
138
+ """
139
+ from cfbtui.selfupdate import UpdateError
140
+
141
+ if app.return_code not in (0, None) or not (app.config.updates.auto or app.restart_requested):
142
+ staged.discard()
143
+ return False
144
+ try:
145
+ updater.apply(staged)
146
+ except UpdateError as exc: # RollbackError says how to recover by hand
147
+ print(f"cfbtui: the update was not installed: {exc}", file=sys.stderr)
148
+ return False
149
+ return True
150
+
151
+
152
+ def update_test(mode: str, updater, argv: list[str]) -> int:
153
+ """Run the update path with no TUI (the end-to-end workflow cannot drive one): check, stage and probe
154
+ a newer release, install it, and for "restart" start the new binary. Exit 1 when the update fails."""
155
+ import asyncio
156
+ from types import SimpleNamespace
157
+
158
+ from cfbtui.selfupdate import UpdateError
159
+ from cfbtui.update import asset_name, check_for_update, decide
160
+
161
+ info = asyncio.run(check_for_update(__version__))
162
+ if info is None:
163
+ print(f"cfbtui {__version__}: up to date")
164
+ return 0
165
+ if decide(updater.kind, auto=True, no_config=False, current=__version__, info=info,
166
+ asset=asset_name()) != "download":
167
+ print("cfbtui: this release would not be downloaded", file=sys.stderr)
168
+ return 2
169
+ try:
170
+ staged = updater.stage(info)
171
+ except UpdateError as exc:
172
+ print(f"cfbtui: could not download {info.version}: {exc}", file=sys.stderr)
173
+ return 1
174
+ app = SimpleNamespace(return_code=0, config=Config(), restart_requested=mode == "restart")
175
+ if not install_update(updater, staged, app):
176
+ return 1
177
+ print(f"cfbtui {info.version} installed")
178
+ if mode == "restart":
179
+ restart(updater.target, argv)
180
+ return 0
181
+
182
+
183
+ def main(argv: list[str] | None = None) -> int:
184
+ if argv is None and "--selftest" in sys.argv[1:]:
185
+ return selftest()
186
+ config, loaded, save_path = resolve(argv)
187
+
188
+ # Imported lazily so `--version` stays instant and never touches the terminal.
189
+ from cfbtui.app import CfbApp
190
+ from cfbtui.selfupdate import Updater, cleanup_old, sweep
191
+ from cfbtui.update import check_for_update
192
+
193
+ update_check = partial(check_for_update, __version__) if config.updates.check else None
194
+ updater = Updater.detect(__version__)
195
+ if updater.kind == "binary":
196
+ with contextlib.suppress(Exception):
197
+ cleanup_old(updater.target) # drop the previous version, or restore it after a crashed swap
198
+ sweep(updater.target.parent) # and any download an earlier run left behind
199
+ mode = build_parser().parse_args(argv).update_test
200
+ if mode is not None:
201
+ if updater.kind != "binary":
202
+ print("cfbtui: --update-test needs a standalone binary", file=sys.stderr)
203
+ return 2
204
+ return update_test(mode, updater, sys.argv[1:] if argv is None else argv)
205
+ app_updater = None if update_check is None else updater # --no-update-check: no checking and no downloading
206
+ app = CfbApp(
207
+ config=config,
208
+ file_config=loaded.config,
209
+ config_path=save_path,
210
+ warnings=loaded.warnings,
211
+ update_check=update_check,
212
+ updater=app_updater,
213
+ )
214
+ app.run()
215
+ if app.staged is not None and app_updater is not None:
216
+ installed = install_update(app_updater, app.staged, app)
217
+ if installed and app.restart_requested:
218
+ restart(app_updater.target, sys.argv[1:] if argv is None else argv)
219
+ return 0
220
+
221
+
222
+ if __name__ == "__main__":
223
+ raise SystemExit(main())