mirror of
https://github.com/onyx-and-iris/lottery-tui.git
synced 2026-02-26 11:09:11 +00:00
initial commit
This commit is contained in:
commit
b28b04c603
26
.github/workflows/publish.yml
vendored
Normal file
26
.github/workflows/publish.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
name: Publish to PyPI
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*.*.*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pypi-publish:
|
||||||
|
name: upload release to PyPI
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: pypi
|
||||||
|
permissions:
|
||||||
|
# This permission is needed for private repositories.
|
||||||
|
contents: read
|
||||||
|
# IMPORTANT: this permission is mandatory for trusted publishing
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: pdm-project/setup-pdm@v4
|
||||||
|
|
||||||
|
- name: Publish package distributions to PyPI
|
||||||
|
run: pdm publish
|
||||||
19
.github/workflows/ruff.yml
vendored
Normal file
19
.github/workflows/ruff.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
name: Ruff
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ruff:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: astral-sh/ruff-action@v3
|
||||||
|
with:
|
||||||
|
args: 'format --check --diff'
|
||||||
212
.gitignore
vendored
Normal file
212
.gitignore
vendored
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
# Generated by ignr: github.com/onyx-and-iris/ignr
|
||||||
|
|
||||||
|
## Python ##
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[codz]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py.cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# UV
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
#uv.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
#poetry.toml
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
||||||
|
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
||||||
|
#pdm.lock
|
||||||
|
#pdm.toml
|
||||||
|
.pdm-python
|
||||||
|
.pdm-build/
|
||||||
|
|
||||||
|
# pixi
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
||||||
|
#pixi.lock
|
||||||
|
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
||||||
|
# in the .venv directory. It is recommended not to include this directory in version control.
|
||||||
|
.pixi
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.envrc
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
|
|
||||||
|
# Abstra
|
||||||
|
# Abstra is an AI-powered process automation framework.
|
||||||
|
# Ignore directories containing user credentials, local state, and settings.
|
||||||
|
# Learn more at https://abstra.io/docs
|
||||||
|
.abstra/
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||||
|
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||||
|
# you could uncomment the following to ignore the entire vscode folder
|
||||||
|
# .vscode/
|
||||||
|
|
||||||
|
# Ruff stuff:
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# PyPI configuration file
|
||||||
|
.pypirc
|
||||||
|
|
||||||
|
# Cursor
|
||||||
|
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
||||||
|
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
||||||
|
# refer to https://docs.cursor.com/context/ignore-files
|
||||||
|
.cursorignore
|
||||||
|
.cursorindexingignore
|
||||||
|
|
||||||
|
# Marimo
|
||||||
|
marimo/_static/
|
||||||
|
marimo/_lsp/
|
||||||
|
__marimo__/
|
||||||
|
|
||||||
|
# End of ignr
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Onyx and Iris
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
41
README.md
Normal file
41
README.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Lottery TUI
|
||||||
|
|
||||||
|
[](https://pdm.fming.dev)
|
||||||
|
[](https://github.com/astral-sh/ruff)
|
||||||
|
[](https://pypi.org/project/lottery-tui)
|
||||||
|
[](https://pypi.org/project/q3rcon-tui)
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
*with uv*
|
||||||
|
|
||||||
|
```console
|
||||||
|
uv tool install lottery-tui
|
||||||
|
```
|
||||||
|
|
||||||
|
*with pipx*
|
||||||
|
|
||||||
|
```console
|
||||||
|
pipx install lotter-tui
|
||||||
|
```
|
||||||
|
|
||||||
|
The TUI should now be discoverable as lottery-tui.
|
||||||
|
|
||||||
|
## Use
|
||||||
|
|
||||||
|
Launch the TUI, select a lottery and press the Draw button!
|
||||||
|
|
||||||
|
To exit from the application press *q* or *Ctrl+q*
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
`lottery-tui` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
|
||||||
BIN
img/tui.png
Executable file
BIN
img/tui.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
169
pdm.lock
generated
Normal file
169
pdm.lock
generated
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
# This file is @generated by PDM.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
groups = ["default"]
|
||||||
|
strategy = ["inherit_metadata"]
|
||||||
|
lock_version = "4.5.0"
|
||||||
|
content_hash = "sha256:842afa14523f463c1a73e53c7aeb6d697673d95a2db9adbf935807b1fe5d021a"
|
||||||
|
|
||||||
|
[[metadata.targets]]
|
||||||
|
requires_python = "==3.13.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linkify-it-py"
|
||||||
|
version = "2.0.3"
|
||||||
|
requires_python = ">=3.7"
|
||||||
|
summary = "Links recognition library with FULL unicode support."
|
||||||
|
groups = ["default"]
|
||||||
|
marker = "python_version == \"3.13\""
|
||||||
|
dependencies = [
|
||||||
|
"uc-micro-py",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048"},
|
||||||
|
{file = "linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markdown-it-py"
|
||||||
|
version = "4.0.0"
|
||||||
|
requires_python = ">=3.10"
|
||||||
|
summary = "Python port of markdown-it. Markdown parsing, done right!"
|
||||||
|
groups = ["default"]
|
||||||
|
marker = "python_version == \"3.13\""
|
||||||
|
dependencies = [
|
||||||
|
"mdurl~=0.1",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"},
|
||||||
|
{file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markdown-it-py"
|
||||||
|
version = "4.0.0"
|
||||||
|
extras = ["linkify"]
|
||||||
|
requires_python = ">=3.10"
|
||||||
|
summary = "Python port of markdown-it. Markdown parsing, done right!"
|
||||||
|
groups = ["default"]
|
||||||
|
marker = "python_version == \"3.13\""
|
||||||
|
dependencies = [
|
||||||
|
"linkify-it-py<3,>=1",
|
||||||
|
"markdown-it-py==4.0.0",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"},
|
||||||
|
{file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mdit-py-plugins"
|
||||||
|
version = "0.5.0"
|
||||||
|
requires_python = ">=3.10"
|
||||||
|
summary = "Collection of plugins for markdown-it-py"
|
||||||
|
groups = ["default"]
|
||||||
|
marker = "python_version == \"3.13\""
|
||||||
|
dependencies = [
|
||||||
|
"markdown-it-py<5.0.0,>=2.0.0",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f"},
|
||||||
|
{file = "mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mdurl"
|
||||||
|
version = "0.1.2"
|
||||||
|
requires_python = ">=3.7"
|
||||||
|
summary = "Markdown URL utilities"
|
||||||
|
groups = ["default"]
|
||||||
|
marker = "python_version == \"3.13\""
|
||||||
|
files = [
|
||||||
|
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
|
||||||
|
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "platformdirs"
|
||||||
|
version = "4.9.2"
|
||||||
|
requires_python = ">=3.10"
|
||||||
|
summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
|
||||||
|
groups = ["default"]
|
||||||
|
marker = "python_version == \"3.13\""
|
||||||
|
files = [
|
||||||
|
{file = "platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd"},
|
||||||
|
{file = "platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygments"
|
||||||
|
version = "2.19.2"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "Pygments is a syntax highlighting package written in Python."
|
||||||
|
groups = ["default"]
|
||||||
|
marker = "python_version == \"3.13\""
|
||||||
|
files = [
|
||||||
|
{file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
|
||||||
|
{file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rich"
|
||||||
|
version = "14.3.3"
|
||||||
|
requires_python = ">=3.8.0"
|
||||||
|
summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||||
|
groups = ["default"]
|
||||||
|
marker = "python_version == \"3.13\""
|
||||||
|
dependencies = [
|
||||||
|
"markdown-it-py>=2.2.0",
|
||||||
|
"pygments<3.0.0,>=2.13.0",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d"},
|
||||||
|
{file = "rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textual"
|
||||||
|
version = "8.0.0"
|
||||||
|
requires_python = "<4.0,>=3.9"
|
||||||
|
summary = "Modern Text User Interface framework"
|
||||||
|
groups = ["default"]
|
||||||
|
marker = "python_version == \"3.13\""
|
||||||
|
dependencies = [
|
||||||
|
"markdown-it-py[linkify]>=2.1.0",
|
||||||
|
"mdit-py-plugins",
|
||||||
|
"platformdirs<5,>=3.6.0",
|
||||||
|
"pygments<3.0.0,>=2.19.2",
|
||||||
|
"rich>=14.2.0",
|
||||||
|
"typing-extensions<5.0.0,>=4.4.0",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "textual-8.0.0-py3-none-any.whl", hash = "sha256:8908f4ebe93a6b4f77ca7262197784a52162bc88b05f4ecf50ac93a92d49bb8f"},
|
||||||
|
{file = "textual-8.0.0.tar.gz", hash = "sha256:ce48f83a3d686c0fac0e80bf9136e1f8851c653aa6a4502e43293a151df18809"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.15.0"
|
||||||
|
requires_python = ">=3.9"
|
||||||
|
summary = "Backported and Experimental Type Hints for Python 3.9+"
|
||||||
|
groups = ["default"]
|
||||||
|
marker = "python_version == \"3.13\""
|
||||||
|
files = [
|
||||||
|
{file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
|
||||||
|
{file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uc-micro-py"
|
||||||
|
version = "1.0.3"
|
||||||
|
requires_python = ">=3.7"
|
||||||
|
summary = "Micro subset of unicode data files for linkify-it-py projects."
|
||||||
|
groups = ["default"]
|
||||||
|
marker = "python_version == \"3.13\""
|
||||||
|
files = [
|
||||||
|
{file = "uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a"},
|
||||||
|
{file = "uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5"},
|
||||||
|
]
|
||||||
15
pyproject.toml
Normal file
15
pyproject.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[project]
|
||||||
|
name = "lottery-tui"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "A terminal user interface for lottery games."
|
||||||
|
authors = [{ name = "onyx-and-iris", email = "code@onyxandiris.online" }]
|
||||||
|
dependencies = ["textual>=8.0.0"]
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
readme = "README.md"
|
||||||
|
license = { text = "MIT" }
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
lottery-tui = "lottery_tui.tui:main"
|
||||||
|
|
||||||
|
[tool.pdm]
|
||||||
|
distribution = true
|
||||||
77
ruff.toml
Normal file
77
ruff.toml
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# Exclude a variety of commonly ignored directories.
|
||||||
|
exclude = [
|
||||||
|
".bzr",
|
||||||
|
".direnv",
|
||||||
|
".eggs",
|
||||||
|
".git",
|
||||||
|
".git-rewrite",
|
||||||
|
".hg",
|
||||||
|
".ipynb_checkpoints",
|
||||||
|
".mypy_cache",
|
||||||
|
".nox",
|
||||||
|
".pants.d",
|
||||||
|
".pyenv",
|
||||||
|
".pytest_cache",
|
||||||
|
".pytype",
|
||||||
|
".ruff_cache",
|
||||||
|
".svn",
|
||||||
|
".tox",
|
||||||
|
".venv",
|
||||||
|
".vscode",
|
||||||
|
"__pypackages__",
|
||||||
|
"_build",
|
||||||
|
"buck-out",
|
||||||
|
"build",
|
||||||
|
"dist",
|
||||||
|
"node_modules",
|
||||||
|
"site-packages",
|
||||||
|
"venv",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Same as Black.
|
||||||
|
line-length = 88
|
||||||
|
indent-width = 4
|
||||||
|
|
||||||
|
# Assume Python 3.10
|
||||||
|
target-version = "py310"
|
||||||
|
|
||||||
|
[lint]
|
||||||
|
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||||
|
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
|
||||||
|
# McCabe complexity (`C901`) by default.
|
||||||
|
select = ["E4", "E7", "E9", "F"]
|
||||||
|
ignore = []
|
||||||
|
|
||||||
|
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||||
|
fixable = ["ALL"]
|
||||||
|
unfixable = []
|
||||||
|
|
||||||
|
# Allow unused variables when underscore-prefixed.
|
||||||
|
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||||
|
|
||||||
|
[format]
|
||||||
|
# Unlike Black, use single quotes for strings.
|
||||||
|
quote-style = "single"
|
||||||
|
|
||||||
|
# Like Black, indent with spaces, rather than tabs.
|
||||||
|
indent-style = "space"
|
||||||
|
|
||||||
|
# Like Black, respect magic trailing commas.
|
||||||
|
skip-magic-trailing-comma = false
|
||||||
|
|
||||||
|
# Like Black, automatically detect the appropriate line ending.
|
||||||
|
line-ending = "auto"
|
||||||
|
|
||||||
|
# Enable auto-formatting of code examples in docstrings. Markdown,
|
||||||
|
# reStructuredText code/literal blocks and doctests are all supported.
|
||||||
|
#
|
||||||
|
# This is currently disabled by default, but it is planned for this
|
||||||
|
# to be opt-out in the future.
|
||||||
|
docstring-code-format = false
|
||||||
|
|
||||||
|
# Set the line length limit used when formatting code snippets in
|
||||||
|
# docstrings.
|
||||||
|
#
|
||||||
|
# This only has an effect when the `docstring-code-format` setting is
|
||||||
|
# enabled.
|
||||||
|
docstring-code-line-length = "dynamic"
|
||||||
120
src/lottery_tui/lottery.py
Normal file
120
src/lottery_tui/lottery.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import random
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
|
||||||
|
class Result(NamedTuple):
|
||||||
|
"""A named tuple to hold the results of a lottery draw."""
|
||||||
|
|
||||||
|
kind: str
|
||||||
|
numbers: list[int]
|
||||||
|
bonus: list[int] | None
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
"""Return a string representation of the lottery result."""
|
||||||
|
out = f'Numbers: {", ".join(str(n) for n in self.numbers)}'
|
||||||
|
if self.bonus:
|
||||||
|
match self.kind:
|
||||||
|
case 'EuroMillions':
|
||||||
|
bonus_name = 'Lucky Stars'
|
||||||
|
case 'Set For Life':
|
||||||
|
bonus_name = 'Life Ball'
|
||||||
|
case 'Thunderball':
|
||||||
|
bonus_name = 'Thunderball'
|
||||||
|
case _:
|
||||||
|
bonus_name = 'Bonus Numbers'
|
||||||
|
out += f'\n{bonus_name}: {", ".join(str(n) for n in self.bonus)}'
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
registry = {}
|
||||||
|
|
||||||
|
|
||||||
|
def register_lottery(cls):
|
||||||
|
"""A decorator to register lottery classes in the registry."""
|
||||||
|
registry[cls.__name__.lower()] = cls
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
class Lottery(ABC):
|
||||||
|
"""An abstract base class for different types of lotteries."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def draw(self):
|
||||||
|
"""Perform a lottery draw."""
|
||||||
|
|
||||||
|
|
||||||
|
@register_lottery
|
||||||
|
class UKlotto(Lottery):
|
||||||
|
"""A class representing the UK Lotto lottery.
|
||||||
|
|
||||||
|
Uk Lotto draws 6 numbers from a pool of 1 to 59, without replacement.
|
||||||
|
There is no bonus number in UK Lotto.
|
||||||
|
"""
|
||||||
|
|
||||||
|
POSSIBLE_NUMBERS = range(1, 60)
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
"""Perform a UK Lotto draw."""
|
||||||
|
result = random.sample(UKlotto.POSSIBLE_NUMBERS, 6)
|
||||||
|
return Result(kind='UK Lotto', numbers=result, bonus=None)
|
||||||
|
|
||||||
|
|
||||||
|
@register_lottery
|
||||||
|
class EuroMillions(Lottery):
|
||||||
|
"""A class representing the EuroMillions lottery.
|
||||||
|
|
||||||
|
EuroMillions draws 5 numbers from a pool of 1 to 50, without replacement,
|
||||||
|
and 2 "Lucky Star" numbers from a separate pool of 1 to 12, also without replacement.
|
||||||
|
"""
|
||||||
|
|
||||||
|
POSSIBLE_NUMBERS = range(1, 51)
|
||||||
|
POSSIBLE_BONUS_NUMBERS = range(1, 13)
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
"""Perform a EuroMillions draw."""
|
||||||
|
numbers = random.sample(EuroMillions.POSSIBLE_NUMBERS, 5)
|
||||||
|
bonus = random.sample(EuroMillions.POSSIBLE_BONUS_NUMBERS, 2)
|
||||||
|
return Result(kind='EuroMillions', numbers=numbers, bonus=bonus)
|
||||||
|
|
||||||
|
|
||||||
|
@register_lottery
|
||||||
|
class SetForLife(Lottery):
|
||||||
|
"""A class representing the Set For Life lottery.
|
||||||
|
|
||||||
|
Set For Life draws 5 numbers from a pool of 1 to 39, without replacement,
|
||||||
|
and 1 "Life Ball" number from a separate pool of 1 to 10, also without replacement.
|
||||||
|
"""
|
||||||
|
|
||||||
|
POSSIBLE_NUMBERS = range(1, 40)
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
"""Perform a Set For Life draw."""
|
||||||
|
numbers = random.sample(SetForLife.POSSIBLE_NUMBERS, 5)
|
||||||
|
life_ball = [random.randint(1, 10)]
|
||||||
|
return Result(kind='Set For Life', numbers=numbers, bonus=life_ball)
|
||||||
|
|
||||||
|
|
||||||
|
@register_lottery
|
||||||
|
class Thunderball(Lottery):
|
||||||
|
"""A class representing the Thunderball lottery.
|
||||||
|
|
||||||
|
Thunderball draws 5 numbers from a pool of 1 to 39, without replacement,
|
||||||
|
and 1 "Thunderball" number from a separate pool of 1 to 14, also without replacement.
|
||||||
|
"""
|
||||||
|
|
||||||
|
POSSIBLE_NUMBERS = range(1, 40) # Thunderball numbers range from 1 to 39
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
"""Perform a Thunderball draw."""
|
||||||
|
numbers = random.sample(Thunderball.POSSIBLE_NUMBERS, 5)
|
||||||
|
thunderball = [random.randint(1, 14)]
|
||||||
|
return Result(kind='Thunderball', numbers=numbers, bonus=thunderball)
|
||||||
|
|
||||||
|
|
||||||
|
def request_lottery_obj(lottery_name: str) -> Lottery:
|
||||||
|
"""Return a lottery object based on the provided lottery name."""
|
||||||
|
lottery_cls = registry.get(lottery_name.lower())
|
||||||
|
if lottery_cls is None:
|
||||||
|
raise ValueError(f"Lottery '{lottery_name}' not found.")
|
||||||
|
return lottery_cls()
|
||||||
54
src/lottery_tui/tui.py
Normal file
54
src/lottery_tui/tui.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.containers import Container
|
||||||
|
from textual.widgets import Button, Label, Select, Static
|
||||||
|
|
||||||
|
from .lottery import request_lottery_obj
|
||||||
|
|
||||||
|
|
||||||
|
class LotteryTUI(App):
|
||||||
|
"""A Textual TUI for the Lottery application."""
|
||||||
|
|
||||||
|
CSS_PATH = 'tui.tcss'
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
"""Create child widgets for the app."""
|
||||||
|
yield Container(
|
||||||
|
Static('Welcome to the Lottery TUI!', id='welcome'),
|
||||||
|
Static('Pick a lottery to play:', id='instructions'),
|
||||||
|
Select(
|
||||||
|
options=[
|
||||||
|
('UK Lotto', 'uklotto'),
|
||||||
|
('EuroMillions', 'euromillions'),
|
||||||
|
('Set For Life', 'setforlife'),
|
||||||
|
('Thunderball', 'thunderball'),
|
||||||
|
],
|
||||||
|
id='lottery-select',
|
||||||
|
),
|
||||||
|
Button('Draw', id='draw-button'),
|
||||||
|
Label('', id='result-label'),
|
||||||
|
id='main-container',
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_key(self, event):
|
||||||
|
"""Handle key events."""
|
||||||
|
if event.key == 'q':
|
||||||
|
self.exit()
|
||||||
|
|
||||||
|
def on_button_pressed(self, event):
|
||||||
|
"""Handle button press events."""
|
||||||
|
if event.button.id == 'draw-button':
|
||||||
|
selected_lottery = self.query_one('#lottery-select').value
|
||||||
|
try:
|
||||||
|
lottery_obj = request_lottery_obj(selected_lottery)
|
||||||
|
result = lottery_obj.draw()
|
||||||
|
self.query_one('#result-label').update(f'Result: {result}')
|
||||||
|
except ValueError as e:
|
||||||
|
self.query_one('#result-label').update(str(e))
|
||||||
|
|
||||||
|
self.query_one('#result-label').update(str(result))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Entry point for the Lottery TUI."""
|
||||||
|
app = LotteryTUI()
|
||||||
|
app.run()
|
||||||
276
src/lottery_tui/tui.tcss
Normal file
276
src/lottery_tui/tui.tcss
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
/* Lottery TUI CSS Styling */
|
||||||
|
|
||||||
|
/* Global App Styling */
|
||||||
|
LotteryTUI {
|
||||||
|
background: $surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Container */
|
||||||
|
#main-container {
|
||||||
|
align: center middle;
|
||||||
|
background: #1a1a2e;
|
||||||
|
border: round #ffd700;
|
||||||
|
border-title-align: center;
|
||||||
|
border-title-color: #ffd700;
|
||||||
|
border-title-style: bold;
|
||||||
|
height: auto;
|
||||||
|
layout: vertical;
|
||||||
|
margin: 2;
|
||||||
|
min-height: 20;
|
||||||
|
min-width: 60;
|
||||||
|
padding: 3 5;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Welcome Message */
|
||||||
|
#welcome {
|
||||||
|
color: #ffd700;
|
||||||
|
content-align: center middle;
|
||||||
|
height: auto;
|
||||||
|
margin: 0 0 2 0;
|
||||||
|
padding: 1;
|
||||||
|
text-style: bold;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Instructions */
|
||||||
|
#instructions {
|
||||||
|
color: #a0aec0;
|
||||||
|
content-align: center middle;
|
||||||
|
height: auto;
|
||||||
|
margin: 1 0 0 0;
|
||||||
|
padding: 1;
|
||||||
|
text-style: italic;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lottery Select Styling */
|
||||||
|
#lottery-select {
|
||||||
|
margin: 1 0 2 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover Effects */
|
||||||
|
#welcome:hover {
|
||||||
|
color: #ffed4a;
|
||||||
|
text-style: bold italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
#instructions:hover {
|
||||||
|
color: #cbd5e0;
|
||||||
|
text-style: bold italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Additional styling for potential future widgets */
|
||||||
|
|
||||||
|
/* Button styling for lottery buttons */
|
||||||
|
Button {
|
||||||
|
background: #ffd700;
|
||||||
|
border: round #e6c200;
|
||||||
|
color: #1a1a2e;
|
||||||
|
height: 3;
|
||||||
|
margin: 1;
|
||||||
|
min-width: 12;
|
||||||
|
text-style: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button:hover {
|
||||||
|
background: #ffed4a;
|
||||||
|
border: round #ffd700;
|
||||||
|
color: #16213e;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button:focus {
|
||||||
|
background: #e6c200;
|
||||||
|
border: round #b8860b;
|
||||||
|
color: #1a1a2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input styling for lottery number inputs */
|
||||||
|
Input {
|
||||||
|
background: #2d3748;
|
||||||
|
border: round #4a5568;
|
||||||
|
color: #ffd700;
|
||||||
|
height: 3;
|
||||||
|
margin: 1;
|
||||||
|
padding: 0 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Input:focus {
|
||||||
|
background: #374151;
|
||||||
|
border: round #ffd700;
|
||||||
|
color: #ffed4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Label styling */
|
||||||
|
Label {
|
||||||
|
color: #e2e8f0;
|
||||||
|
margin: 0 0 1 0;
|
||||||
|
text-style: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Draw Button Specific Styling */
|
||||||
|
#draw-button {
|
||||||
|
background: #ffd700;
|
||||||
|
border: round #e6c200;
|
||||||
|
color: #1a1a2e;
|
||||||
|
height: 3;
|
||||||
|
margin: 1 0 0 0;
|
||||||
|
text-style: bold;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#draw-button:hover {
|
||||||
|
background: #ffed4a;
|
||||||
|
border: round #ffd700;
|
||||||
|
color: #16213e;
|
||||||
|
}
|
||||||
|
|
||||||
|
#draw-button:focus {
|
||||||
|
background: #e6c200;
|
||||||
|
border: round #b8860b;
|
||||||
|
color: #1a1a2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Results Label Styling - Enhanced Appearance */
|
||||||
|
#result-label {
|
||||||
|
background: #1a365d;
|
||||||
|
border: thick #ffd700;
|
||||||
|
color: #ffffff;
|
||||||
|
height: auto;
|
||||||
|
margin: 1 0 0 0;
|
||||||
|
min-height: 4;
|
||||||
|
padding: 1 2;
|
||||||
|
text-style: bold;
|
||||||
|
content-align: left middle;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container for lottery number display */
|
||||||
|
.lottery-numbers {
|
||||||
|
align: center middle;
|
||||||
|
background: #2d3748;
|
||||||
|
border: round #ffd700;
|
||||||
|
height: auto;
|
||||||
|
margin: 2;
|
||||||
|
padding: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Individual lottery number balls */
|
||||||
|
.lottery-ball {
|
||||||
|
background: #ffd700;
|
||||||
|
border: round #e6c200;
|
||||||
|
color: #1a1a2e;
|
||||||
|
height: 3;
|
||||||
|
margin: 0 1;
|
||||||
|
text-align: center;
|
||||||
|
text-style: bold;
|
||||||
|
width: 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lottery-ball:hover {
|
||||||
|
background: #ffed4a;
|
||||||
|
color: #16213e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Results display */
|
||||||
|
.results {
|
||||||
|
background: #1a202c;
|
||||||
|
border: round #4a5568;
|
||||||
|
color: #e2e8f0;
|
||||||
|
height: auto;
|
||||||
|
margin: 2;
|
||||||
|
padding: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.winning-number {
|
||||||
|
color: #48bb78;
|
||||||
|
text-style: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.losing-number {
|
||||||
|
color: #f56565;
|
||||||
|
text-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status bar */
|
||||||
|
.status-bar {
|
||||||
|
background: #2d3748;
|
||||||
|
color: #a0aec0;
|
||||||
|
dock: bottom;
|
||||||
|
height: 1;
|
||||||
|
padding: 0 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.header {
|
||||||
|
background: #ffd700;
|
||||||
|
color: #1a1a2e;
|
||||||
|
dock: top;
|
||||||
|
height: 3;
|
||||||
|
text-align: center;
|
||||||
|
text-style: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer {
|
||||||
|
background: #1a1a2e;
|
||||||
|
color: #a0aec0;
|
||||||
|
dock: bottom;
|
||||||
|
height: 1;
|
||||||
|
text-align: center;
|
||||||
|
text-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar styling */
|
||||||
|
.sidebar {
|
||||||
|
background: #2d3748;
|
||||||
|
border-right: solid #4a5568;
|
||||||
|
dock: left;
|
||||||
|
width: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content area */
|
||||||
|
.content {
|
||||||
|
background: $surface;
|
||||||
|
margin: 1;
|
||||||
|
padding: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error messages */
|
||||||
|
.error {
|
||||||
|
background: #fed7d7;
|
||||||
|
border: round #f56565;
|
||||||
|
color: #c53030;
|
||||||
|
margin: 1;
|
||||||
|
padding: 1;
|
||||||
|
text-style: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Success messages */
|
||||||
|
.success {
|
||||||
|
background: #c6f6d5;
|
||||||
|
border: round #48bb78;
|
||||||
|
color: #22543d;
|
||||||
|
margin: 1;
|
||||||
|
padding: 1;
|
||||||
|
text-style: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading spinner */
|
||||||
|
.loading {
|
||||||
|
color: #ffd700;
|
||||||
|
text-align: center;
|
||||||
|
text-style: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prize display */
|
||||||
|
.prize {
|
||||||
|
background: #ffd700;
|
||||||
|
border: round #e6c200;
|
||||||
|
color: #1a1a2e;
|
||||||
|
margin: 1;
|
||||||
|
padding: 2;
|
||||||
|
text-align: center;
|
||||||
|
text-style: bold;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user