wxbrief 0.4.0__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 (192) hide show
  1. wxbrief-0.4.0/LICENSE +21 -0
  2. wxbrief-0.4.0/PKG-INFO +185 -0
  3. wxbrief-0.4.0/README.md +140 -0
  4. wxbrief-0.4.0/pyproject.toml +119 -0
  5. wxbrief-0.4.0/setup.cfg +4 -0
  6. wxbrief-0.4.0/tests/test_app_i18n.py +218 -0
  7. wxbrief-0.4.0/tests/test_app_smoke.py +552 -0
  8. wxbrief-0.4.0/tests/test_app_themes.py +267 -0
  9. wxbrief-0.4.0/tests/test_awc_crosscheck.py +352 -0
  10. wxbrief-0.4.0/tests/test_awc_taf_crosscheck.py +499 -0
  11. wxbrief-0.4.0/tests/test_cache_state.py +144 -0
  12. wxbrief-0.4.0/tests/test_calc.py +315 -0
  13. wxbrief-0.4.0/tests/test_cancelled_groups.py +143 -0
  14. wxbrief-0.4.0/tests/test_cli.py +331 -0
  15. wxbrief-0.4.0/tests/test_cli_polish.py +48 -0
  16. wxbrief-0.4.0/tests/test_client.py +531 -0
  17. wxbrief-0.4.0/tests/test_clipboard.py +81 -0
  18. wxbrief-0.4.0/tests/test_colorstate.py +220 -0
  19. wxbrief-0.4.0/tests/test_context.py +41 -0
  20. wxbrief-0.4.0/tests/test_controls.py +71 -0
  21. wxbrief-0.4.0/tests/test_data_files.py +203 -0
  22. wxbrief-0.4.0/tests/test_demo_mode.py +63 -0
  23. wxbrief-0.4.0/tests/test_derived.py +311 -0
  24. wxbrief-0.4.0/tests/test_discord.py +382 -0
  25. wxbrief-0.4.0/tests/test_first_run_input.py +169 -0
  26. wxbrief-0.4.0/tests/test_flightcat.py +306 -0
  27. wxbrief-0.4.0/tests/test_footer.py +126 -0
  28. wxbrief-0.4.0/tests/test_fuzz.py +358 -0
  29. wxbrief-0.4.0/tests/test_get.py +136 -0
  30. wxbrief-0.4.0/tests/test_goldens.py +61 -0
  31. wxbrief-0.4.0/tests/test_groups.py +452 -0
  32. wxbrief-0.4.0/tests/test_hypothesis_feeds.py +57 -0
  33. wxbrief-0.4.0/tests/test_i18n.py +200 -0
  34. wxbrief-0.4.0/tests/test_i18n_fixes.py +88 -0
  35. wxbrief-0.4.0/tests/test_i18n_locales.py +243 -0
  36. wxbrief-0.4.0/tests/test_last_polish.py +159 -0
  37. wxbrief-0.4.0/tests/test_layout.py +282 -0
  38. wxbrief-0.4.0/tests/test_limits.py +91 -0
  39. wxbrief-0.4.0/tests/test_locale_grammar.py +81 -0
  40. wxbrief-0.4.0/tests/test_metar.py +371 -0
  41. wxbrief-0.4.0/tests/test_phrases.py +382 -0
  42. wxbrief-0.4.0/tests/test_phrases_tail.py +556 -0
  43. wxbrief-0.4.0/tests/test_polish.py +230 -0
  44. wxbrief-0.4.0/tests/test_poll.py +425 -0
  45. wxbrief-0.4.0/tests/test_remarks.py +498 -0
  46. wxbrief-0.4.0/tests/test_render.py +277 -0
  47. wxbrief-0.4.0/tests/test_robustness.py +678 -0
  48. wxbrief-0.4.0/tests/test_round5.py +98 -0
  49. wxbrief-0.4.0/tests/test_screenshot_colour.py +53 -0
  50. wxbrief-0.4.0/tests/test_selftest.py +25 -0
  51. wxbrief-0.4.0/tests/test_smoke.py +39 -0
  52. wxbrief-0.4.0/tests/test_snapshots.py +43 -0
  53. wxbrief-0.4.0/tests/test_solar.py +78 -0
  54. wxbrief-0.4.0/tests/test_split.py +215 -0
  55. wxbrief-0.4.0/tests/test_strip.py +117 -0
  56. wxbrief-0.4.0/tests/test_taf.py +403 -0
  57. wxbrief-0.4.0/tests/test_taf_goldens.py +93 -0
  58. wxbrief-0.4.0/tests/test_tafgroups.py +243 -0
  59. wxbrief-0.4.0/tests/test_tail.py +560 -0
  60. wxbrief-0.4.0/tests/test_themes.py +209 -0
  61. wxbrief-0.4.0/tests/test_timeline.py +336 -0
  62. wxbrief-0.4.0/tests/test_timeline_ui.py +392 -0
  63. wxbrief-0.4.0/tests/test_times.py +79 -0
  64. wxbrief-0.4.0/tests/test_tokens.py +87 -0
  65. wxbrief-0.4.0/tests/test_ui_lifecycle.py +322 -0
  66. wxbrief-0.4.0/tests/test_ui_round2.py +124 -0
  67. wxbrief-0.4.0/tests/test_ui_round3.py +126 -0
  68. wxbrief-0.4.0/tests/test_ui_round4.py +242 -0
  69. wxbrief-0.4.0/tests/test_units.py +58 -0
  70. wxbrief-0.4.0/tests/test_ux_polish.py +146 -0
  71. wxbrief-0.4.0/wxbrief/__init__.py +4 -0
  72. wxbrief-0.4.0/wxbrief/__main__.py +359 -0
  73. wxbrief-0.4.0/wxbrief/app.py +497 -0
  74. wxbrief-0.4.0/wxbrief/calc/__init__.py +24 -0
  75. wxbrief-0.4.0/wxbrief/calc/airport.py +35 -0
  76. wxbrief-0.4.0/wxbrief/calc/altitude.py +87 -0
  77. wxbrief-0.4.0/wxbrief/calc/spread.py +42 -0
  78. wxbrief-0.4.0/wxbrief/calc/wind.py +75 -0
  79. wxbrief-0.4.0/wxbrief/clipboard.py +56 -0
  80. wxbrief-0.4.0/wxbrief/config.py +268 -0
  81. wxbrief-0.4.0/wxbrief/data/__init__.py +1 -0
  82. wxbrief-0.4.0/wxbrief/data/awc.py +254 -0
  83. wxbrief-0.4.0/wxbrief/data/cache.py +87 -0
  84. wxbrief-0.4.0/wxbrief/data/demo/CYYZ.txt +3 -0
  85. wxbrief-0.4.0/wxbrief/data/demo/EGLL.txt +3 -0
  86. wxbrief-0.4.0/wxbrief/data/demo/ETHB.txt +3 -0
  87. wxbrief-0.4.0/wxbrief/data/demo/KDEN.txt +4 -0
  88. wxbrief-0.4.0/wxbrief/data/demo/NZSP.txt +4 -0
  89. wxbrief-0.4.0/wxbrief/data/demo/RJTT.txt +3 -0
  90. wxbrief-0.4.0/wxbrief/data/demo/SBGR.txt +3 -0
  91. wxbrief-0.4.0/wxbrief/data/demo/UUEE.txt +3 -0
  92. wxbrief-0.4.0/wxbrief/data/demo/VIDP.txt +3 -0
  93. wxbrief-0.4.0/wxbrief/data/demo/YSSY.txt +3 -0
  94. wxbrief-0.4.0/wxbrief/data/demo.py +66 -0
  95. wxbrief-0.4.0/wxbrief/data/models.py +233 -0
  96. wxbrief-0.4.0/wxbrief/data/poll.py +273 -0
  97. wxbrief-0.4.0/wxbrief/data/stations.json.gz +0 -0
  98. wxbrief-0.4.0/wxbrief/data/stations.py +156 -0
  99. wxbrief-0.4.0/wxbrief/decode/__init__.py +1 -0
  100. wxbrief-0.4.0/wxbrief/decode/colorstate.py +114 -0
  101. wxbrief-0.4.0/wxbrief/decode/derived.py +308 -0
  102. wxbrief-0.4.0/wxbrief/decode/detect.py +71 -0
  103. wxbrief-0.4.0/wxbrief/decode/flightcat.py +110 -0
  104. wxbrief-0.4.0/wxbrief/decode/groups/__init__.py +6 -0
  105. wxbrief-0.4.0/wxbrief/decode/groups/colorstate.py +76 -0
  106. wxbrief-0.4.0/wxbrief/decode/groups/header.py +63 -0
  107. wxbrief-0.4.0/wxbrief/decode/groups/pressure.py +48 -0
  108. wxbrief-0.4.0/wxbrief/decode/groups/recentwx.py +50 -0
  109. wxbrief-0.4.0/wxbrief/decode/groups/runwaystate.py +159 -0
  110. wxbrief-0.4.0/wxbrief/decode/groups/rvr.py +42 -0
  111. wxbrief-0.4.0/wxbrief/decode/groups/seastate.py +40 -0
  112. wxbrief-0.4.0/wxbrief/decode/groups/sky.py +31 -0
  113. wxbrief-0.4.0/wxbrief/decode/groups/tafgroups.py +303 -0
  114. wxbrief-0.4.0/wxbrief/decode/groups/tail.py +94 -0
  115. wxbrief-0.4.0/wxbrief/decode/groups/tempdew.py +36 -0
  116. wxbrief-0.4.0/wxbrief/decode/groups/trend.py +116 -0
  117. wxbrief-0.4.0/wxbrief/decode/groups/visibility.py +141 -0
  118. wxbrief-0.4.0/wxbrief/decode/groups/weather.py +45 -0
  119. wxbrief-0.4.0/wxbrief/decode/groups/wind.py +78 -0
  120. wxbrief-0.4.0/wxbrief/decode/groups/windshear.py +50 -0
  121. wxbrief-0.4.0/wxbrief/decode/metar.py +415 -0
  122. wxbrief-0.4.0/wxbrief/decode/model.py +1227 -0
  123. wxbrief-0.4.0/wxbrief/decode/remarks.py +891 -0
  124. wxbrief-0.4.0/wxbrief/decode/split.py +224 -0
  125. wxbrief-0.4.0/wxbrief/decode/tables.py +497 -0
  126. wxbrief-0.4.0/wxbrief/decode/taf.py +507 -0
  127. wxbrief-0.4.0/wxbrief/decode/timeline.py +307 -0
  128. wxbrief-0.4.0/wxbrief/decode/times.py +160 -0
  129. wxbrief-0.4.0/wxbrief/decode/tokens.py +109 -0
  130. wxbrief-0.4.0/wxbrief/decode/units.py +162 -0
  131. wxbrief-0.4.0/wxbrief/getcmd.py +149 -0
  132. wxbrief-0.4.0/wxbrief/i18n/__init__.py +200 -0
  133. wxbrief-0.4.0/wxbrief/i18n/keys.py +822 -0
  134. wxbrief-0.4.0/wxbrief/i18n/locales/de.toml +999 -0
  135. wxbrief-0.4.0/wxbrief/i18n/locales/en.toml +992 -0
  136. wxbrief-0.4.0/wxbrief/i18n/locales/es.toml +992 -0
  137. wxbrief-0.4.0/wxbrief/i18n/locales/fr.toml +991 -0
  138. wxbrief-0.4.0/wxbrief/i18n/locales/pt.toml +991 -0
  139. wxbrief-0.4.0/wxbrief/i18n/numbers.py +62 -0
  140. wxbrief-0.4.0/wxbrief/i18n/plurals.py +35 -0
  141. wxbrief-0.4.0/wxbrief/render/__init__.py +1 -0
  142. wxbrief-0.4.0/wxbrief/render/discord.py +504 -0
  143. wxbrief-0.4.0/wxbrief/render/highlight.py +82 -0
  144. wxbrief-0.4.0/wxbrief/render/jsonout.py +39 -0
  145. wxbrief-0.4.0/wxbrief/render/measure.py +191 -0
  146. wxbrief-0.4.0/wxbrief/render/pairs.py +197 -0
  147. wxbrief-0.4.0/wxbrief/render/palette.py +158 -0
  148. wxbrief-0.4.0/wxbrief/render/phrases.py +947 -0
  149. wxbrief-0.4.0/wxbrief/render/plain.py +295 -0
  150. wxbrief-0.4.0/wxbrief/render/richtext.py +108 -0
  151. wxbrief-0.4.0/wxbrief/selftest.py +109 -0
  152. wxbrief-0.4.0/wxbrief/solar.py +46 -0
  153. wxbrief-0.4.0/wxbrief/state.py +319 -0
  154. wxbrief-0.4.0/wxbrief/ui/__init__.py +1 -0
  155. wxbrief-0.4.0/wxbrief/ui/app.tcss +382 -0
  156. wxbrief-0.4.0/wxbrief/ui/bindings.py +64 -0
  157. wxbrief-0.4.0/wxbrief/ui/commands.py +127 -0
  158. wxbrief-0.4.0/wxbrief/ui/context.py +88 -0
  159. wxbrief-0.4.0/wxbrief/ui/copying.py +77 -0
  160. wxbrief-0.4.0/wxbrief/ui/fcat.py +57 -0
  161. wxbrief-0.4.0/wxbrief/ui/glyphs.py +29 -0
  162. wxbrief-0.4.0/wxbrief/ui/screens/__init__.py +1 -0
  163. wxbrief-0.4.0/wxbrief/ui/screens/add_station.py +116 -0
  164. wxbrief-0.4.0/wxbrief/ui/screens/base.py +29 -0
  165. wxbrief-0.4.0/wxbrief/ui/screens/briefing.py +260 -0
  166. wxbrief-0.4.0/wxbrief/ui/screens/help.py +79 -0
  167. wxbrief-0.4.0/wxbrief/ui/screens/paste.py +156 -0
  168. wxbrief-0.4.0/wxbrief/ui/screens/settings.py +76 -0
  169. wxbrief-0.4.0/wxbrief/ui/screens/station_detail.py +63 -0
  170. wxbrief-0.4.0/wxbrief/ui/screens/theme_picker.py +56 -0
  171. wxbrief-0.4.0/wxbrief/ui/screens/too_small.py +46 -0
  172. wxbrief-0.4.0/wxbrief/ui/strip.py +105 -0
  173. wxbrief-0.4.0/wxbrief/ui/style.py +58 -0
  174. wxbrief-0.4.0/wxbrief/ui/themes/__init__.py +11 -0
  175. wxbrief-0.4.0/wxbrief/ui/themes/aviation.py +104 -0
  176. wxbrief-0.4.0/wxbrief/ui/themes/semantic.py +70 -0
  177. wxbrief-0.4.0/wxbrief/ui/widgets/__init__.py +1 -0
  178. wxbrief-0.4.0/wxbrief/ui/widgets/detail.py +507 -0
  179. wxbrief-0.4.0/wxbrief/ui/widgets/key_bar.py +61 -0
  180. wxbrief-0.4.0/wxbrief/ui/widgets/period_detail.py +185 -0
  181. wxbrief-0.4.0/wxbrief/ui/widgets/station_card.py +109 -0
  182. wxbrief-0.4.0/wxbrief/ui/widgets/station_grid.py +170 -0
  183. wxbrief-0.4.0/wxbrief/ui/widgets/station_list.py +157 -0
  184. wxbrief-0.4.0/wxbrief/ui/widgets/status_bar.py +95 -0
  185. wxbrief-0.4.0/wxbrief/ui/widgets/taf_timeline.py +298 -0
  186. wxbrief-0.4.0/wxbrief/ui/wrap.py +61 -0
  187. wxbrief-0.4.0/wxbrief.egg-info/PKG-INFO +185 -0
  188. wxbrief-0.4.0/wxbrief.egg-info/SOURCES.txt +190 -0
  189. wxbrief-0.4.0/wxbrief.egg-info/dependency_links.txt +1 -0
  190. wxbrief-0.4.0/wxbrief.egg-info/entry_points.txt +2 -0
  191. wxbrief-0.4.0/wxbrief.egg-info/requires.txt +34 -0
  192. wxbrief-0.4.0/wxbrief.egg-info/top_level.txt +1 -0
wxbrief-0.4.0/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.
wxbrief-0.4.0/PKG-INFO ADDED
@@ -0,0 +1,185 @@
1
+ Metadata-Version: 2.4
2
+ Name: wxbrief
3
+ Version: 0.4.0
4
+ Summary: Terminal METAR/TAF briefing with colorways and plain-language translation
5
+ Author: Bill Kane
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/WPTK/wxbrief
8
+ Project-URL: Issues, https://github.com/WPTK/wxbrief/issues
9
+ Project-URL: Changelog, https://github.com/WPTK/wxbrief/blob/main/CHANGELOG.md
10
+ Keywords: metar,taf,aviation,weather,tui,terminal,textual
11
+ Requires-Python: >=3.12
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: hishel[async]<2,>=1.4
17
+ Requires-Dist: anysqlite<1,>=0.0.5
18
+ Requires-Dist: stamina<27,>=26
19
+ Requires-Dist: platformdirs<5,>=4
20
+ Requires-Dist: tomli-w<2,>=1
21
+ Requires-Dist: pyperclip<2,>=1.9
22
+ Requires-Dist: tzdata; sys_platform == "win32"
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=8; extra == "dev"
25
+ Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
26
+ Requires-Dist: respx>=0.22; extra == "dev"
27
+ Requires-Dist: pytest-xdist>=3.6; extra == "dev"
28
+ Requires-Dist: pytest-timeout>=2.3; extra == "dev"
29
+ Requires-Dist: psutil>=6; extra == "dev"
30
+ Requires-Dist: pytest-cov>=5; extra == "dev"
31
+ Requires-Dist: hypothesis>=6; extra == "dev"
32
+ Requires-Dist: pytest-textual-snapshot==1.1.*; extra == "dev"
33
+ Requires-Dist: timezonefinder<10,>=9; extra == "dev"
34
+ Requires-Dist: pyinstaller<7,>=6.22; extra == "dev"
35
+ Requires-Dist: build<2,>=1; extra == "dev"
36
+ Requires-Dist: ruff>=0.8; extra == "dev"
37
+ Requires-Dist: pillow>=10; extra == "dev"
38
+ Requires-Dist: vulture>=2.14; extra == "dev"
39
+ Requires-Dist: mypy>=1.13; extra == "dev"
40
+ Requires-Dist: types-pyperclip; extra == "dev"
41
+ Requires-Dist: tui-test==0.1.1; extra == "dev"
42
+ Provides-Extra: binary
43
+ Requires-Dist: tzdata>=2024.1; extra == "binary"
44
+ Dynamic: license-file
45
+
46
+ # wxbrief
47
+
48
+ [![CI](https://github.com/WPTK/wxbrief/actions/workflows/ci.yml/badge.svg)](https://github.com/WPTK/wxbrief/actions/workflows/ci.yml)
49
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
50
+
51
+ A terminal app for reading METARs and TAFs. It fetches the reports from aviationweather.gov, decodes them with its own
52
+ parser, and shows each station with the raw report, a plain-language translation and a TAF timeline. The colours are
53
+ switchable, and the text is available in English, Spanish, French, German and Portuguese.
54
+
55
+ ![wxbrief on its bundled demo reports: the station list, decoded METAR, TAF timeline, grid layout and a colorway change](docs/img/demo.gif)
56
+
57
+ *Recorded from the real app running on its bundled `--demo` reports (fixed demo data, not live weather).*
58
+
59
+ ![Station list on the left, the decoded METAR and the start of the TAF on the right](docs/img/briefing.png)
60
+
61
+ Version 0.4.0. The project uses ZeroVer, so the version stays 0.x and anything may still change.
62
+
63
+ ## What it does
64
+
65
+ - Watches a list of stations and refreshes their METAR/SPECI and TAF reports within a polling budget that stays under
66
+ the AWC rate limit. Reports are cached on disk.
67
+ - Decodes the body, trends, remarks (including colour states, runway state, wind shear and sea state) and TAF change
68
+ groups. Anything it cannot decode is shown as it was reported, not guessed.
69
+ - Shows a TAF timeline per station. A cursor moves through the periods and the period under it is decoded below.
70
+ - Has a list layout and a grid layout (`v` switches), and works down to 60 columns by 16 rows.
71
+ - Offers 18 colorways (`t` cycles them, `T` opens a picker with live preview). See [docs/themes.md](docs/themes.md).
72
+ - Decodes pasted reports offline (`p`).
73
+ - Copies a report as a Discord code block (`c`), or prints one from the command line.
74
+ - Prints plain text or JSON from the `decode` and `get` commands.
75
+ - Runs with no network at all in `--demo` mode, on ten bundled stations.
76
+
77
+ ## What wxbrief is not
78
+
79
+ wxbrief is not a substitute for an official weather briefing, and not for flight planning alone. Get an official briefing
80
+ before you fly. The same notice is in `wxbrief --help` and the in-app help (`?`).
81
+
82
+ ![The TAF timeline with the cursor on the first period, in paper-chart](docs/img/forecast.png)
83
+
84
+ ![The grid layout: one card per station with a small forecast strip](docs/img/grid.png)
85
+
86
+ ## Install
87
+
88
+ There is no PyPI package. Each [release](https://github.com/WPTK/wxbrief/releases) has these files:
89
+
90
+ | File | For |
91
+ | --- | --- |
92
+ | `wxbrief-0.4.0-windows-x86_64.exe` | Windows, no Python needed |
93
+ | `wxbrief-0.4.0-linux-x86_64.tar.gz`, `wxbrief-0.4.0-linux-arm64.tar.gz` | Linux; the archive holds one `wxbrief` executable |
94
+ | `wxbrief-0.4.0-macos-arm64.tar.gz`, `wxbrief-0.4.0-macos-x86_64.tar.gz` | macOS; the archive holds one `wxbrief` executable |
95
+ | `wxbrief-0.4.0-py3-none-any.whl` | any platform with Python 3.12 or newer: `pip install wxbrief-0.4.0-py3-none-any.whl` |
96
+ | `SHA256SUMS` | checking the downloads; each file also has a build provenance attestation |
97
+
98
+ To run from source instead, you need Python 3.12 or newer:
99
+
100
+ ```sh
101
+ git clone https://github.com/WPTK/wxbrief.git
102
+ cd wxbrief
103
+ python -m venv .venv
104
+ .venv/bin/python -m pip install -e .
105
+ .venv/bin/wxbrief --version
106
+ ```
107
+
108
+ On Windows the venv paths are `.venv\Scripts\python` and `.venv\Scripts\wxbrief`. The commands below assume `wxbrief` is
109
+ on your path; otherwise use `python -m wxbrief`.
110
+
111
+ ## Usage
112
+
113
+ ```sh
114
+ wxbrief --demo # ten bundled stations, offline
115
+ wxbrief KDEN EGLL ETHB # watch these stations for this run
116
+ wxbrief # your saved stations
117
+ ```
118
+
119
+ Press `?` in the app for the keys. The main ones:
120
+
121
+ | Key | Does |
122
+ | --- | --- |
123
+ | `a`, `d` | add or remove a station (by ICAO, IATA or FAA code or by name) |
124
+ | `r` | refresh |
125
+ | `v` | list or grid |
126
+ | `1` `2` `3` | raw, decoded and forecast sections |
127
+ | `p` | paste METARs or TAFs and decode them |
128
+ | `c` | copy the report as a Discord block |
129
+ | `t`, `T` | next colorway, colorway picker |
130
+ | `g`, `u`, `z` | glyph set, units, time mode (station, local, zulu) |
131
+ | `S` | settings, including the language |
132
+ | `q` | quit |
133
+
134
+ Settings you change in the app are saved to `config.toml` in your user config folder. A command-line flag such as
135
+ `--theme`, `--glyphs`, `--units`, `--time` or `--lang` applies to that run only and is never saved. `--no-config`
136
+ reads and writes no config file. `WXBRIEF_CACHE_DIR` moves the cache folder. `wxbrief --help` lists every flag.
137
+
138
+ Stations given on the command line or in the config are passed through as typed. A station that the bundled database
139
+ does not know can still be watched; it just has no name or time zone.
140
+
141
+ ### Without the interface
142
+
143
+ ```sh
144
+ wxbrief decode "METAR KDEN 131453Z 06007KT 3/4SM BR OVC003 09/08 A3001"
145
+ wxbrief get KDEN EGLL --plain
146
+ echo "METAR KDEN 131453Z 06007KT 3/4SM BR OVC003 09/08 A3001" | wxbrief decode --lang de
147
+ ```
148
+
149
+ `decode` reads reports from the arguments or from standard input, including several reports in one blob, and prints
150
+ them with colour, or with `--plain`, `--json` or `--discord`. `--lang`, `--units` and `--date` change the language, the
151
+ unit order and the date used to resolve the day of the month. `get` fetches the latest reports for the given stations
152
+ once and takes the same output flags. `decode` and `get` never read the config file.
153
+
154
+ ## Data
155
+
156
+ Reports come from the [Aviation Weather Center](https://aviationweather.gov/) data API. The demo reports are bundled
157
+ with the program.
158
+
159
+ The client follows the [API usage guidance](https://aviationweather.gov/data/api/): it identifies itself with a
160
+ `User-Agent` naming wxbrief, its version and this repository, polls METARs every 10 minutes and TAFs every 30, stays
161
+ under 100 requests a minute, honours `Retry-After` on a 429, and asks the server whether a report has changed when it
162
+ sends `ETag` or `Last-Modified`, and reuses the stored one only on a 304. It never reuses a response on its own. A request that fails in transit or with a 5xx is tried up to three
163
+ times, waiting a doubling, randomly jittered time between tries.
164
+
165
+ The bundled station database (`wxbrief/data/stations.json.gz`) takes station positions from the AWC station list and
166
+ assigns each station its IANA time zone with [timezonefinder](https://github.com/jannikmi/timezonefinder), which uses
167
+ the [timezone-boundary-builder](https://github.com/evansiroky/timezone-boundary-builder) data. That data is
168
+ © OpenStreetMap contributors and is available under the
169
+ [Open Database License (ODbL) 1.0](https://opendatacommons.org/licenses/odbl/1-0/). The `tz` column of the station
170
+ database is a database derived from it and is shared under the same licence; the rest of wxbrief is MIT.
171
+
172
+ wxbrief is a reading aid. Do not use it as the only source for a flight decision.
173
+
174
+ ## More
175
+
176
+ - [docs/themes.md](docs/themes.md): the colorways and how the weather colours are chosen.
177
+ - [docs/i18n.md](docs/i18n.md): languages, and how to add or fix a translation.
178
+ - [docs/decoding.md](docs/decoding.md): how the decoder works and how to add a test report.
179
+ - [docs/development.md](docs/development.md): setup, checks, tests, snapshots and screenshots.
180
+ - [docs/release.md](docs/release.md): cutting a release.
181
+ - [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), [CHANGELOG.md](CHANGELOG.md).
182
+
183
+ ## License
184
+
185
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,140 @@
1
+ # wxbrief
2
+
3
+ [![CI](https://github.com/WPTK/wxbrief/actions/workflows/ci.yml/badge.svg)](https://github.com/WPTK/wxbrief/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+
6
+ A terminal app for reading METARs and TAFs. It fetches the reports from aviationweather.gov, decodes them with its own
7
+ parser, and shows each station with the raw report, a plain-language translation and a TAF timeline. The colours are
8
+ switchable, and the text is available in English, Spanish, French, German and Portuguese.
9
+
10
+ ![wxbrief on its bundled demo reports: the station list, decoded METAR, TAF timeline, grid layout and a colorway change](docs/img/demo.gif)
11
+
12
+ *Recorded from the real app running on its bundled `--demo` reports (fixed demo data, not live weather).*
13
+
14
+ ![Station list on the left, the decoded METAR and the start of the TAF on the right](docs/img/briefing.png)
15
+
16
+ Version 0.4.0. The project uses ZeroVer, so the version stays 0.x and anything may still change.
17
+
18
+ ## What it does
19
+
20
+ - Watches a list of stations and refreshes their METAR/SPECI and TAF reports within a polling budget that stays under
21
+ the AWC rate limit. Reports are cached on disk.
22
+ - Decodes the body, trends, remarks (including colour states, runway state, wind shear and sea state) and TAF change
23
+ groups. Anything it cannot decode is shown as it was reported, not guessed.
24
+ - Shows a TAF timeline per station. A cursor moves through the periods and the period under it is decoded below.
25
+ - Has a list layout and a grid layout (`v` switches), and works down to 60 columns by 16 rows.
26
+ - Offers 18 colorways (`t` cycles them, `T` opens a picker with live preview). See [docs/themes.md](docs/themes.md).
27
+ - Decodes pasted reports offline (`p`).
28
+ - Copies a report as a Discord code block (`c`), or prints one from the command line.
29
+ - Prints plain text or JSON from the `decode` and `get` commands.
30
+ - Runs with no network at all in `--demo` mode, on ten bundled stations.
31
+
32
+ ## What wxbrief is not
33
+
34
+ wxbrief is not a substitute for an official weather briefing, and not for flight planning alone. Get an official briefing
35
+ before you fly. The same notice is in `wxbrief --help` and the in-app help (`?`).
36
+
37
+ ![The TAF timeline with the cursor on the first period, in paper-chart](docs/img/forecast.png)
38
+
39
+ ![The grid layout: one card per station with a small forecast strip](docs/img/grid.png)
40
+
41
+ ## Install
42
+
43
+ There is no PyPI package. Each [release](https://github.com/WPTK/wxbrief/releases) has these files:
44
+
45
+ | File | For |
46
+ | --- | --- |
47
+ | `wxbrief-0.4.0-windows-x86_64.exe` | Windows, no Python needed |
48
+ | `wxbrief-0.4.0-linux-x86_64.tar.gz`, `wxbrief-0.4.0-linux-arm64.tar.gz` | Linux; the archive holds one `wxbrief` executable |
49
+ | `wxbrief-0.4.0-macos-arm64.tar.gz`, `wxbrief-0.4.0-macos-x86_64.tar.gz` | macOS; the archive holds one `wxbrief` executable |
50
+ | `wxbrief-0.4.0-py3-none-any.whl` | any platform with Python 3.12 or newer: `pip install wxbrief-0.4.0-py3-none-any.whl` |
51
+ | `SHA256SUMS` | checking the downloads; each file also has a build provenance attestation |
52
+
53
+ To run from source instead, you need Python 3.12 or newer:
54
+
55
+ ```sh
56
+ git clone https://github.com/WPTK/wxbrief.git
57
+ cd wxbrief
58
+ python -m venv .venv
59
+ .venv/bin/python -m pip install -e .
60
+ .venv/bin/wxbrief --version
61
+ ```
62
+
63
+ On Windows the venv paths are `.venv\Scripts\python` and `.venv\Scripts\wxbrief`. The commands below assume `wxbrief` is
64
+ on your path; otherwise use `python -m wxbrief`.
65
+
66
+ ## Usage
67
+
68
+ ```sh
69
+ wxbrief --demo # ten bundled stations, offline
70
+ wxbrief KDEN EGLL ETHB # watch these stations for this run
71
+ wxbrief # your saved stations
72
+ ```
73
+
74
+ Press `?` in the app for the keys. The main ones:
75
+
76
+ | Key | Does |
77
+ | --- | --- |
78
+ | `a`, `d` | add or remove a station (by ICAO, IATA or FAA code or by name) |
79
+ | `r` | refresh |
80
+ | `v` | list or grid |
81
+ | `1` `2` `3` | raw, decoded and forecast sections |
82
+ | `p` | paste METARs or TAFs and decode them |
83
+ | `c` | copy the report as a Discord block |
84
+ | `t`, `T` | next colorway, colorway picker |
85
+ | `g`, `u`, `z` | glyph set, units, time mode (station, local, zulu) |
86
+ | `S` | settings, including the language |
87
+ | `q` | quit |
88
+
89
+ Settings you change in the app are saved to `config.toml` in your user config folder. A command-line flag such as
90
+ `--theme`, `--glyphs`, `--units`, `--time` or `--lang` applies to that run only and is never saved. `--no-config`
91
+ reads and writes no config file. `WXBRIEF_CACHE_DIR` moves the cache folder. `wxbrief --help` lists every flag.
92
+
93
+ Stations given on the command line or in the config are passed through as typed. A station that the bundled database
94
+ does not know can still be watched; it just has no name or time zone.
95
+
96
+ ### Without the interface
97
+
98
+ ```sh
99
+ wxbrief decode "METAR KDEN 131453Z 06007KT 3/4SM BR OVC003 09/08 A3001"
100
+ wxbrief get KDEN EGLL --plain
101
+ echo "METAR KDEN 131453Z 06007KT 3/4SM BR OVC003 09/08 A3001" | wxbrief decode --lang de
102
+ ```
103
+
104
+ `decode` reads reports from the arguments or from standard input, including several reports in one blob, and prints
105
+ them with colour, or with `--plain`, `--json` or `--discord`. `--lang`, `--units` and `--date` change the language, the
106
+ unit order and the date used to resolve the day of the month. `get` fetches the latest reports for the given stations
107
+ once and takes the same output flags. `decode` and `get` never read the config file.
108
+
109
+ ## Data
110
+
111
+ Reports come from the [Aviation Weather Center](https://aviationweather.gov/) data API. The demo reports are bundled
112
+ with the program.
113
+
114
+ The client follows the [API usage guidance](https://aviationweather.gov/data/api/): it identifies itself with a
115
+ `User-Agent` naming wxbrief, its version and this repository, polls METARs every 10 minutes and TAFs every 30, stays
116
+ under 100 requests a minute, honours `Retry-After` on a 429, and asks the server whether a report has changed when it
117
+ sends `ETag` or `Last-Modified`, and reuses the stored one only on a 304. It never reuses a response on its own. A request that fails in transit or with a 5xx is tried up to three
118
+ times, waiting a doubling, randomly jittered time between tries.
119
+
120
+ The bundled station database (`wxbrief/data/stations.json.gz`) takes station positions from the AWC station list and
121
+ assigns each station its IANA time zone with [timezonefinder](https://github.com/jannikmi/timezonefinder), which uses
122
+ the [timezone-boundary-builder](https://github.com/evansiroky/timezone-boundary-builder) data. That data is
123
+ © OpenStreetMap contributors and is available under the
124
+ [Open Database License (ODbL) 1.0](https://opendatacommons.org/licenses/odbl/1-0/). The `tz` column of the station
125
+ database is a database derived from it and is shared under the same licence; the rest of wxbrief is MIT.
126
+
127
+ wxbrief is a reading aid. Do not use it as the only source for a flight decision.
128
+
129
+ ## More
130
+
131
+ - [docs/themes.md](docs/themes.md): the colorways and how the weather colours are chosen.
132
+ - [docs/i18n.md](docs/i18n.md): languages, and how to add or fix a translation.
133
+ - [docs/decoding.md](docs/decoding.md): how the decoder works and how to add a test report.
134
+ - [docs/development.md](docs/development.md): setup, checks, tests, snapshots and screenshots.
135
+ - [docs/release.md](docs/release.md): cutting a release.
136
+ - [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), [CHANGELOG.md](CHANGELOG.md).
137
+
138
+ ## License
139
+
140
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,119 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "wxbrief"
7
+ dynamic = ["version"]
8
+ description = "Terminal METAR/TAF briefing with colorways and plain-language translation"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.12"
12
+ authors = [{ name = "Bill Kane" }]
13
+ keywords = ["metar", "taf", "aviation", "weather", "tui", "terminal", "textual"]
14
+
15
+ dependencies = [
16
+ "textual>=8.2,<9",
17
+ "httpx>=0.28,<1",
18
+ "hishel[async]>=1.4,<2",
19
+ "anysqlite>=0.0.5,<1",
20
+ "stamina>=26,<27",
21
+ "platformdirs>=4,<5",
22
+ "tomli-w>=1,<2",
23
+ "pyperclip>=1.9,<2",
24
+ "tzdata; sys_platform == 'win32'",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/WPTK/wxbrief"
29
+ Issues = "https://github.com/WPTK/wxbrief/issues"
30
+ Changelog = "https://github.com/WPTK/wxbrief/blob/main/CHANGELOG.md"
31
+
32
+ [project.optional-dependencies]
33
+ dev = [
34
+ "pytest>=8",
35
+ "pytest-asyncio>=0.24",
36
+ "respx>=0.22",
37
+ "pytest-xdist>=3.6",
38
+ "pytest-timeout>=2.3",
39
+ "psutil>=6",
40
+ "pytest-cov>=5",
41
+ "hypothesis>=6",
42
+ "pytest-textual-snapshot==1.1.*",
43
+ "timezonefinder>=9,<10",
44
+ "pyinstaller>=6.22,<7",
45
+ "build>=1,<2",
46
+ "ruff>=0.8",
47
+ "pillow>=10",
48
+ "vulture>=2.14",
49
+ "mypy>=1.13",
50
+ "types-pyperclip",
51
+ "tui-test==0.1.1",
52
+ ]
53
+ # Installed only when building a standalone binary, so every binary carries its own zone files.
54
+ binary = ["tzdata>=2024.1"]
55
+
56
+ [project.scripts]
57
+ wxbrief = "wxbrief.__main__:main"
58
+
59
+ [tool.setuptools.dynamic]
60
+ version = { attr = "wxbrief.__version__" }
61
+
62
+ [tool.setuptools.packages.find]
63
+ include = ["wxbrief*"]
64
+
65
+ [tool.setuptools.package-data]
66
+ wxbrief = [
67
+ "ui/*.tcss",
68
+ "data/stations.json.gz",
69
+ "data/demo/*.txt",
70
+ "i18n/locales/*.toml",
71
+ ]
72
+
73
+ [tool.pytest.ini_options]
74
+ asyncio_mode = "auto"
75
+ asyncio_default_fixture_loop_scope = "function"
76
+ testpaths = ["tests"]
77
+ # A test that has not finished in two minutes is hung. The slowest test in the suite takes a few seconds.
78
+ timeout = 120
79
+ timeout_method = "thread"
80
+ markers = [
81
+ "pty: real-terminal journeys (pseudo-terminal + pyte); skipped when the PTY layer is unavailable",
82
+ "slow: UAT journeys that take several seconds (deselect with -m \"not slow\")"]
83
+
84
+ [tool.ruff]
85
+ line-length = 120
86
+ target-version = "py312"
87
+
88
+ [tool.ruff.lint]
89
+ select = ["E", "F", "W", "I", "B", "UP", "SIM", "C4", "RUF"]
90
+ ignore = [
91
+ # Textual widgets declare BINDINGS/DEFAULT_CSS as plain class attributes by convention.
92
+ "RUF012",
93
+ ]
94
+
95
+ [tool.ruff.lint.per-file-ignores]
96
+ # Long lines in tests are inline report fixtures; wrapping them hurts readability.
97
+ "tests/**" = ["E501"]
98
+
99
+ [tool.mypy]
100
+ python_version = "3.12"
101
+ warn_unused_configs = true
102
+ warn_redundant_casts = true
103
+ warn_unused_ignores = true
104
+ check_untyped_defs = true
105
+
106
+ # The decoder and the translation layer are typed end to end: the per-module flags that make up `--strict`.
107
+ # (`strict = true` inside an override is applied to every module by mypy, so the flags are spelled out.)
108
+ [[tool.mypy.overrides]]
109
+ module = ["wxbrief.decode.*", "wxbrief.i18n.*", "wxbrief.calc.*"]
110
+ disallow_any_generics = true
111
+ disallow_subclassing_any = true
112
+ disallow_untyped_calls = true
113
+ disallow_untyped_defs = true
114
+ disallow_incomplete_defs = true
115
+ disallow_untyped_decorators = true
116
+ warn_return_any = true
117
+ implicit_reexport = false
118
+ strict_equality = true
119
+ extra_checks = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+