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.
- cfbtui-1.13.1/LICENSE +21 -0
- cfbtui-1.13.1/PKG-INFO +193 -0
- cfbtui-1.13.1/README.md +167 -0
- cfbtui-1.13.1/cfbtui/__init__.py +4 -0
- cfbtui-1.13.1/cfbtui/__main__.py +223 -0
- cfbtui-1.13.1/cfbtui/app.py +623 -0
- cfbtui-1.13.1/cfbtui/clipboard.py +16 -0
- cfbtui-1.13.1/cfbtui/config.py +304 -0
- cfbtui-1.13.1/cfbtui/espn/__init__.py +1 -0
- cfbtui-1.13.1/cfbtui/espn/client.py +207 -0
- cfbtui-1.13.1/cfbtui/espn/models.py +352 -0
- cfbtui-1.13.1/cfbtui/espn/parse.py +604 -0
- cfbtui-1.13.1/cfbtui/espn/periods.py +46 -0
- cfbtui-1.13.1/cfbtui/news.py +159 -0
- cfbtui-1.13.1/cfbtui/poll.py +336 -0
- cfbtui-1.13.1/cfbtui/presence.py +41 -0
- cfbtui-1.13.1/cfbtui/selfupdate.py +414 -0
- cfbtui-1.13.1/cfbtui/share.py +128 -0
- cfbtui-1.13.1/cfbtui/state/__init__.py +1 -0
- cfbtui-1.13.1/cfbtui/state/events.py +222 -0
- cfbtui-1.13.1/cfbtui/state/store.py +338 -0
- cfbtui-1.13.1/cfbtui/state/upset.py +91 -0
- cfbtui-1.13.1/cfbtui/ui/__init__.py +0 -0
- cfbtui-1.13.1/cfbtui/ui/app.tcss +19 -0
- cfbtui-1.13.1/cfbtui/ui/filters.py +122 -0
- cfbtui-1.13.1/cfbtui/ui/glyphs.py +256 -0
- cfbtui-1.13.1/cfbtui/ui/labels.py +79 -0
- cfbtui-1.13.1/cfbtui/ui/pulse.py +45 -0
- cfbtui-1.13.1/cfbtui/ui/screens/__init__.py +1 -0
- cfbtui-1.13.1/cfbtui/ui/screens/conferences.py +57 -0
- cfbtui-1.13.1/cfbtui/ui/screens/detail.py +453 -0
- cfbtui-1.13.1/cfbtui/ui/screens/help.py +48 -0
- cfbtui-1.13.1/cfbtui/ui/screens/news.py +298 -0
- cfbtui-1.13.1/cfbtui/ui/screens/rankings.py +176 -0
- cfbtui-1.13.1/cfbtui/ui/screens/settings.py +88 -0
- cfbtui-1.13.1/cfbtui/ui/story.py +139 -0
- cfbtui-1.13.1/cfbtui/ui/theme.py +118 -0
- cfbtui-1.13.1/cfbtui/ui/widgets/__init__.py +0 -0
- cfbtui-1.13.1/cfbtui/ui/widgets/game_card.py +392 -0
- cfbtui-1.13.1/cfbtui/ui/widgets/score_grid.py +271 -0
- cfbtui-1.13.1/cfbtui/ui/widgets/status_bar.py +131 -0
- cfbtui-1.13.1/cfbtui/ui/widgets/ticker.py +241 -0
- cfbtui-1.13.1/cfbtui/update.py +160 -0
- cfbtui-1.13.1/cfbtui/version.py +113 -0
- cfbtui-1.13.1/cfbtui.egg-info/PKG-INFO +193 -0
- cfbtui-1.13.1/cfbtui.egg-info/SOURCES.txt +50 -0
- cfbtui-1.13.1/cfbtui.egg-info/dependency_links.txt +1 -0
- cfbtui-1.13.1/cfbtui.egg-info/entry_points.txt +2 -0
- cfbtui-1.13.1/cfbtui.egg-info/requires.txt +15 -0
- cfbtui-1.13.1/cfbtui.egg-info/top_level.txt +1 -0
- cfbtui-1.13.1/pyproject.toml +47 -0
- 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
|
+
[](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
|
+

|
|
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
|
+

|
|
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
|
+

|
|
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).
|
cfbtui-1.13.1/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# cfbtui
|
|
2
|
+
|
|
3
|
+
[](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
|
+

|
|
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
|
+

|
|
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
|
+

|
|
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,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())
|