- Python 64.8%
- QML 27.7%
- Shell 3.1%
- PowerShell 2.6%
- JavaScript 1.8%
Co-authored-by: ergosum <ergosum@localhost> Co-committed-by: ergosum <ergosum@localhost> |
||
|---|---|---|
| .forgejo/workflows | ||
| .githooks | ||
| ci | ||
| core | ||
| tests | ||
| ui | ||
| .env.example | ||
| .gitignore | ||
| AGENTS.md | ||
| LICENSE | ||
| main.py | ||
| main.spec | ||
| pyproject.toml | ||
| README.md | ||
| runtime_hook.py | ||
hledger-gui
A native desktop GUI for the hledger accounting tool, built with Python and PySide6 QML.
Requirements
- Python 3.12+
- hledger CLI installed
Setup
pip install -e ".[dev]"
Run
python main.py
Run Tests
pytest
Lint & Format
ruff check .
ruff format --check .
ruff check --fix . # auto-fix
ruff format . # auto-format
Build Executable
pip install pyinstaller
pyinstaller main.spec # onefile: single portable binary
ONEDIR=1 pyinstaller main.spec # onedir: directory with _internal/
Output is in dist/.
CI/CD: Automated Builds
Binaries are built via Forgejo Actions on tag push (v*):
| Platform | Portable | Full install | Update archive |
|---|---|---|---|
| Linux | qt-hledger-<tag>-portable.bin |
qt-hledger-<tag>.tar.gz |
qt-hledger-<tag>-update.tar.gz |
| Windows | qt-hledger-<tag>-portable.exe |
qt-hledger-<tag>.zip |
qt-hledger-<tag>-update.zip |
- Portable: Single self-extracting binary, no install needed
- Full install: Onedir archive with all Qt/Python runtime bundled
- Update archive: Binary + app internals only (no Qt/Python runtime) — extract over existing install to update without re-downloading hundreds of MB
See ci/README.md for runner setup, image building, and workflow documentation.
Tech Stack
- Language: Python 3.14
- GUI Framework: PySide6 6.11 (pure QML, no QtWidgets)
- Data Source: hledger CLI with CSV output
- Styling: QML (no QSS)
- Persistence: JSON (
~/.qt-hledger/settings.json) - Code Style: Ruff (linter + formatter), Python 3.12+, full static typing with
TypedDictandStrEnum, no raw dicts or string enums
Code Style
ruff check . # lint
ruff format . # auto-format
ruff check --fix . # auto-fix lint
The full config is in pyproject.toml. Key rules:
ANN— all functions must have complete type annotationsPTH—pathlib.Pathoveros.pathFBT— boolean params must be keyword-onlyN802/N815— Qt camelCase convention for@Property/@Slot/SignalUP— modern Python 3.12 syntax (PEP 604 unions, builtin generics)RUF— no ambiguous unicode, no mutable class defaults
Data modeling conventions:
TypedDictfor all dict-shaped objects (TableRow,TypeFilterItem,BalanceParamsDict)StrEnumfor all enum types (PeriodType,AccountType,OutputFormat,GroupingType)@dataclassfor domain models (BalanceRow,BalanceData,BalanceParams)Nonesentinel instead of""for optional/unknown values*keyword-only separator for boolean function parameterspathlib.Pathinstead ofos.path
CI enforces: ruff check . + ruff format --check . on every build.
Features
- Balance report with pinned account column
- Pinned Average and Row Total columns (togglable)
- Period options: daily, weekly, monthly, quarterly, yearly
- Configurable account depth
- Accumulation modes: per period (default), cumulative, historical
- Empty accounts, sorting by amount, percentages, invert sign, posting count modes
- Account name text filters (include/exclude)
- Account type filters (assets, liabilities, revenue, expenses, …)
- Filter profiles — save/load/rename/delete named sets of all filter settings
- Russian and English localization
- Persistent window state and settings
- Dark/light/system theme
Filter Profiles
Profiles save and restore the complete filter state — period, grouping, accumulation mode, columns (Average/Row Total), display toggles (empty, sort, percent, invert, count), account type filters, and text filters.
Profiles are disabled by default. Enable them in Settings → Enable filter profiles. Once enabled, they appear in the Balance → Profile submenu and the Profiles sidebar section.
Profile Controls
| Action | Description |
|---|---|
| Save | Save current filter state under the active profile name |
| Load | Switch to a saved profile (prompts if there are unsaved changes) |
| Save As… | Create a new profile with a custom name |
| Rename | Rename the active profile |
| Delete | Delete the active profile (not allowed when only one profile remains) |
| Dirty indicator | A * appears next to the profile name when filters differ from the saved state |
Profile Storage
Profiles are stored in ~/.qt-hledger/profiles.json as a JSON file. Each profile holds a snapshot of all filter parameters (see HledgerParams in state.py).
Roadmap
- more filtering options
- sorting
- loading spinner
- enable selection and copying
- filter profiles
- enable deselect
- refresh button
- export (as csv,
excel, clipboard, etc) - copy hledger command
- disable setting dialog resizing
- check if hledger installed on start
- browse button for hledger executable
- document and check codestyle: type annotations, TypedDict, StrEnum, Ruff CI
- hide empty columns
- ci/cd for commits to master (junit tests)
- use only sync/only async?
- popups parents (rename, save as profiles, etc)
- min window size
- logs
- unify theme/style in windows/linux
- better export buttons (hovering above the table)
- style filters
- style table
- tree mode (--tree)
- tooltips on numbers in table with corresponding transactions
- command palette
- export as excel