stundu 0.3.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.
- stundu-0.3.0/.gitignore +36 -0
- stundu-0.3.0/.gitlab-ci.yml +33 -0
- stundu-0.3.0/CONTRIBUTING.md +133 -0
- stundu-0.3.0/LICENSE +21 -0
- stundu-0.3.0/PKG-INFO +484 -0
- stundu-0.3.0/README.md +459 -0
- stundu-0.3.0/__init__.py +0 -0
- stundu-0.3.0/billable_screenshot.png +0 -0
- stundu-0.3.0/build-snap.sh +27 -0
- stundu-0.3.0/cli/__init__.py +36 -0
- stundu-0.3.0/cli/config.py +182 -0
- stundu-0.3.0/cli/formats.py +179 -0
- stundu-0.3.0/cli/i18n_setup.py +94 -0
- stundu-0.3.0/cli/locales/de.json +174 -0
- stundu-0.3.0/cli/locales/en.json +174 -0
- stundu-0.3.0/cli/locales/es.json +174 -0
- stundu-0.3.0/cli/locales/et.json +174 -0
- stundu-0.3.0/cli/locales/fr.json +174 -0
- stundu-0.3.0/cli/locales/it.json +174 -0
- stundu-0.3.0/cli/locales/lt.json +174 -0
- stundu-0.3.0/cli/locales/lv.json +174 -0
- stundu-0.3.0/cli/locales/pl.json +174 -0
- stundu-0.3.0/cli/locales/pt.json +174 -0
- stundu-0.3.0/cli/locales/uk.json +174 -0
- stundu-0.3.0/core/__init__.py +32 -0
- stundu-0.3.0/core/billing.py +481 -0
- stundu-0.3.0/core/engine.py +85 -0
- stundu-0.3.0/core/entry_references.py +182 -0
- stundu-0.3.0/core/invoice.py +152 -0
- stundu-0.3.0/core/models.py +94 -0
- stundu-0.3.0/core/rates.py +304 -0
- stundu-0.3.0/core/time_segments.py +161 -0
- stundu-0.3.0/docs/concepts.md +188 -0
- stundu-0.3.0/docs/examples.md +145 -0
- stundu-0.3.0/docs/release.md +97 -0
- stundu-0.3.0/exporters/__init__.py +93 -0
- stundu-0.3.0/exporters/ledger.py +223 -0
- stundu-0.3.0/exporters/timewarrior.py +159 -0
- stundu-0.3.0/importers/__init__.py +116 -0
- stundu-0.3.0/importers/ledger.py +207 -0
- stundu-0.3.0/importers/timewarrior.py +192 -0
- stundu-0.3.0/logo.png +0 -0
- stundu-0.3.0/logo.svg +417 -0
- stundu-0.3.0/logo1.png +0 -0
- stundu-0.3.0/pyproject.toml +47 -0
- stundu-0.3.0/requirements-dev.txt +4 -0
- stundu-0.3.0/requirements-snap.txt +7 -0
- stundu-0.3.0/requirements.txt +8 -0
- stundu-0.3.0/setup.cfg +21 -0
- stundu-0.3.0/snap/snapcraft.yaml +30 -0
- stundu-0.3.0/storage/__init__.py +4 -0
- stundu-0.3.0/storage/base.py +26 -0
- stundu-0.3.0/storage/csv_storage.py +381 -0
- stundu-0.3.0/stundu.egg-info/PKG-INFO +484 -0
- stundu-0.3.0/stundu.egg-info/SOURCES.txt +78 -0
- stundu-0.3.0/stundu.egg-info/dependency_links.txt +1 -0
- stundu-0.3.0/stundu.egg-info/entry_points.txt +2 -0
- stundu-0.3.0/stundu.egg-info/requires.txt +15 -0
- stundu-0.3.0/stundu.egg-info/top_level.txt +6 -0
- stundu-0.3.0/stundu.py +1781 -0
- stundu-0.3.0/templates/invoice.html +314 -0
- stundu-0.3.0/tests/__init__.py +1 -0
- stundu-0.3.0/tests/conftest.py +8 -0
- stundu-0.3.0/tests/test_billing.py +591 -0
- stundu-0.3.0/tests/test_csv_storage.py +534 -0
- stundu-0.3.0/tests/test_engine.py +216 -0
- stundu-0.3.0/tests/test_entry_references.py +200 -0
- stundu-0.3.0/tests/test_exporter_ledger.py +396 -0
- stundu-0.3.0/tests/test_exporter_timewarrior.py +305 -0
- stundu-0.3.0/tests/test_exporters.py +57 -0
- stundu-0.3.0/tests/test_formats.py +173 -0
- stundu-0.3.0/tests/test_importer_ledger.py +281 -0
- stundu-0.3.0/tests/test_importer_timewarrior.py +204 -0
- stundu-0.3.0/tests/test_importers.py +68 -0
- stundu-0.3.0/tests/test_invoice.py +355 -0
- stundu-0.3.0/tests/test_models.py +128 -0
- stundu-0.3.0/tests/test_rates.py +255 -0
- stundu-0.3.0/tests/test_report.py +219 -0
- stundu-0.3.0/tests/test_time_segments.py +246 -0
stundu-0.3.0/.gitignore
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
.Python
|
|
7
|
+
|
|
8
|
+
# Virtual environment
|
|
9
|
+
venv/
|
|
10
|
+
.venv/
|
|
11
|
+
|
|
12
|
+
# Testing
|
|
13
|
+
.pytest_cache/
|
|
14
|
+
.coverage
|
|
15
|
+
htmlcov/
|
|
16
|
+
|
|
17
|
+
# User data (don't commit personal time tracking data)
|
|
18
|
+
billable_data.csv
|
|
19
|
+
*.bak
|
|
20
|
+
|
|
21
|
+
# IDE
|
|
22
|
+
.idea/
|
|
23
|
+
.vscode/
|
|
24
|
+
*.swp
|
|
25
|
+
*.swo
|
|
26
|
+
|
|
27
|
+
# Distribution
|
|
28
|
+
dist/
|
|
29
|
+
build/
|
|
30
|
+
*.egg-info/
|
|
31
|
+
|
|
32
|
+
# Snapcraft build artifacts
|
|
33
|
+
parts/
|
|
34
|
+
stage/
|
|
35
|
+
prime/
|
|
36
|
+
*.snap
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# You can override the included template(s) by including variable overrides
|
|
2
|
+
# SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
|
|
3
|
+
# Secret Detection customization: https://docs.gitlab.com/user/application_security/secret_detection/pipeline/configure
|
|
4
|
+
# Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings
|
|
5
|
+
# Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings
|
|
6
|
+
# Note that environment variables can be set in several places
|
|
7
|
+
# See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence
|
|
8
|
+
stages:
|
|
9
|
+
- test
|
|
10
|
+
- secret-detection
|
|
11
|
+
|
|
12
|
+
python-test:
|
|
13
|
+
stage: test
|
|
14
|
+
image: python:3.11
|
|
15
|
+
before_script:
|
|
16
|
+
- pip install -r requirements-dev.txt
|
|
17
|
+
script:
|
|
18
|
+
- flake8 stundu.py core/ storage/ importers/ exporters/ tests/ --ignore=E203,E501,W503,E231
|
|
19
|
+
- pytest -q --cov --cov-report=xml:coverage.xml
|
|
20
|
+
- curl -Os https://cli.codecov.io/latest/linux/codecov
|
|
21
|
+
- chmod +x codecov
|
|
22
|
+
- ./codecov --verbose upload-process --fail-on-error -t $CODECOV_TOKEN -r mrtmednis/stundu -F unittests -f ./coverage.xml
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
sast:
|
|
26
|
+
stage: test
|
|
27
|
+
include:
|
|
28
|
+
- template: Security/SAST.gitlab-ci.yml
|
|
29
|
+
- template: Security/Secret-Detection.gitlab-ci.yml
|
|
30
|
+
variables:
|
|
31
|
+
SECRET_DETECTION_ENABLED: 'true'
|
|
32
|
+
secret_detection:
|
|
33
|
+
stage: secret-detection
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing to Stundu!
|
|
4
|
+
|
|
5
|
+
## Development Setup
|
|
6
|
+
|
|
7
|
+
1. Clone the repository and navigate to the project directory:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git clone <repository-url>
|
|
11
|
+
cd stundu
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
2. Create a virtual environment:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
python3 -m venv venv
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
3. Activate the virtual environment:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Linux/macOS
|
|
24
|
+
source venv/bin/activate
|
|
25
|
+
|
|
26
|
+
# Windows
|
|
27
|
+
venv\Scripts\activate
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
4. Install dependencies:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install -r requirements.txt
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
5. Install development dependencies:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install pytest pytest-cov flake8 black
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Running Tests
|
|
43
|
+
|
|
44
|
+
Run the linter:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
flake8 stundu.py core/ storage/ cli/ importers/ tests/
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Run the tests:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pytest
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Run tests with coverage:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pytest --cov
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Code Style
|
|
63
|
+
|
|
64
|
+
This project uses [Black](https://black.readthedocs.io/) for code formatting with default settings (88 character line length).
|
|
65
|
+
|
|
66
|
+
Format code before committing:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
black stundu.py core/ storage/ cli/ importers/ tests/
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Pull Requests
|
|
73
|
+
|
|
74
|
+
1. Fork the repository
|
|
75
|
+
2. Create a feature branch (`git checkout -b feature/my-feature`)
|
|
76
|
+
3. Make your changes
|
|
77
|
+
4. Run tests and linter
|
|
78
|
+
5. Commit your changes
|
|
79
|
+
6. Push to your fork
|
|
80
|
+
7. Open a pull request
|
|
81
|
+
|
|
82
|
+
## Adding Translations
|
|
83
|
+
|
|
84
|
+
Translation files are located in `cli/locales/`. To add a new language:
|
|
85
|
+
|
|
86
|
+
1. Copy `cli/locales/en.json` to `cli/locales/<code>.json`
|
|
87
|
+
2. Translate all strings
|
|
88
|
+
3. Add the locale mapping in `cli/formats.py` (`LOCALE_MAP`)
|
|
89
|
+
4. Update the Available Translations table in `README.md`
|
|
90
|
+
|
|
91
|
+
## Building the Snap Package
|
|
92
|
+
|
|
93
|
+
Snap builds are done inside a [Multipass](https://multipass.run/) VM to keep the host clean.
|
|
94
|
+
|
|
95
|
+
### One-time: create the VM
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
multipass launch --name snap-build --disk 20G 24.04
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The default 5 GB disk fills up during the build, so 20 GB is safer.
|
|
102
|
+
|
|
103
|
+
### Transfer source and build
|
|
104
|
+
|
|
105
|
+
Commit all changes first, then bundle the repo and transfer it to the VM:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
git bundle create ~/stundu.bundle --all
|
|
109
|
+
multipass transfer ~/stundu.bundle snap-build:/home/ubuntu/stundu.bundle
|
|
110
|
+
multipass exec snap-build -- sudo rm -rf /home/ubuntu/build
|
|
111
|
+
multipass exec snap-build -- git clone /home/ubuntu/stundu.bundle /home/ubuntu/build
|
|
112
|
+
multipass exec snap-build -- bash -c "cd /home/ubuntu/build && sudo snapcraft pack --destructive-mode"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Using a git bundle avoids sshfs permission issues and ensures git tags are available for versioning. `--destructive-mode` builds directly in the VM, bypassing the nested LXD container that snapcraft uses by default.
|
|
116
|
+
|
|
117
|
+
### Retrieve and install
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
multipass transfer 'snap-build:/home/ubuntu/build/stundu_<version>_amd64.snap' .
|
|
121
|
+
sudo snap install --dangerous stundu_<version>_amd64.snap
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`--dangerous` is required for locally built, unsigned snaps. Note that fish shell does not expand wildcards in remote paths — use the exact filename.
|
|
125
|
+
|
|
126
|
+
## Adding Importers
|
|
127
|
+
|
|
128
|
+
To add support for importing from a new time tracker:
|
|
129
|
+
|
|
130
|
+
1. Create a new file in `importers/` (e.g., `importers/mytool.py`)
|
|
131
|
+
2. Implement the `Importer` protocol from `importers/base.py`
|
|
132
|
+
3. Register the importer in `importers/registry.py`
|
|
133
|
+
4. Add tests in `tests/test_import.py`
|
stundu-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Martins Mednis
|
|
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.
|
stundu-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stundu
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Time tracking library with billing calculations
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://gitlab.com/mrtmednis/stundu
|
|
7
|
+
Project-URL: Repository, https://gitlab.com/mrtmednis/stundu
|
|
8
|
+
Project-URL: Issues, https://gitlab.com/mrtmednis/stundu/-/issues
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: mathjson-solver==1.18.0
|
|
13
|
+
Requires-Dist: tals>=0.1.0
|
|
14
|
+
Requires-Dist: jinja2==3.1.6
|
|
15
|
+
Provides-Extra: cli
|
|
16
|
+
Requires-Dist: typer==0.19.2; extra == "cli"
|
|
17
|
+
Requires-Dist: tabulate==0.9.0; extra == "cli"
|
|
18
|
+
Requires-Dist: python-i18n==0.3.9; extra == "cli"
|
|
19
|
+
Requires-Dist: babel==2.17.0; extra == "cli"
|
|
20
|
+
Provides-Extra: pdf
|
|
21
|
+
Requires-Dist: weasyprint==65.1; extra == "pdf"
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest==8.3.5; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# Stundu
|
|
27
|
+
|
|
28
|
+
[](https://codecov.io/gl/mrtmednis/stundu)
|
|
29
|
+
|
|
30
|
+
> **Note:** This project was previously published as **billable** on the Snap Store and GitLab. The name was too generic and heavily overused across the web, making the project hard to find. The old `billable` snap is deprecated — please install `stundu` instead.
|
|
31
|
+
|
|
32
|
+
A command-line time tracking application with built-in billing calculations. Track work entries with timestamps and tags, calculate durations, compute billing based on configurable hourly rates with support for afterhours and holiday/weekend modifiers, generate professional invoices, and import data from other time trackers.
|
|
33
|
+
|
|
34
|
+
## Table of Contents
|
|
35
|
+
|
|
36
|
+
- [Installation](#installation)
|
|
37
|
+
- [Quick Start](#quick-start)
|
|
38
|
+
- [Configuration](#configuration)
|
|
39
|
+
- [Expression-Based Rates](#expression-based-rates)
|
|
40
|
+
- [Localization](#localization)
|
|
41
|
+
- [Commands](#commands)
|
|
42
|
+
- [Supported Importers](#supported-importers)
|
|
43
|
+
- [Data Storage](#data-storage)
|
|
44
|
+
- [Documentation](#documentation)
|
|
45
|
+
- [Contributing](#contributing)
|
|
46
|
+
- [License](#license)
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
There are three ways to get Stundu depending on your use case:
|
|
51
|
+
|
|
52
|
+
| Method | Best for |
|
|
53
|
+
|--------|----------|
|
|
54
|
+
| [Snap](#snap) | End users on Linux who want the CLI |
|
|
55
|
+
| [pip](#pip) | Developers who want to use the core library |
|
|
56
|
+
| [From source](#from-source) | Contributing or hacking on the project |
|
|
57
|
+
|
|
58
|
+
### Snap
|
|
59
|
+
|
|
60
|
+
The easiest way to install the CLI on Linux:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
sudo snap install stundu
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Or visit [snapcraft.io/stundu](https://snapcraft.io/stundu).
|
|
67
|
+
|
|
68
|
+
### pip
|
|
69
|
+
|
|
70
|
+
Install the core library (no CLI dependencies):
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install stundu
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Install with the CLI included:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install stundu[cli]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Install with PDF invoice support:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install stundu[cli,pdf]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The core library exposes the billing engine, data models, storage backend, and importers/exporters. The `stundu` command requires the `[cli]` extra.
|
|
89
|
+
|
|
90
|
+
### From Source
|
|
91
|
+
|
|
92
|
+
#### Prerequisites
|
|
93
|
+
|
|
94
|
+
- Python 3.11 or higher
|
|
95
|
+
|
|
96
|
+
#### Setup
|
|
97
|
+
|
|
98
|
+
1. Clone the repository and navigate to the project directory:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
cd stundu
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
2. Create a virtual environment:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
python3 -m venv venv
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
3. Activate the virtual environment:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Linux/macOS
|
|
114
|
+
source venv/bin/activate
|
|
115
|
+
|
|
116
|
+
# Windows
|
|
117
|
+
venv\Scripts\activate
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
4. Install dependencies:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pip install -r requirements.txt
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Quick Start
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
python stundu.py start "Acme Corp" "Development"
|
|
130
|
+
```
|
|
131
|
+
```
|
|
132
|
+
Started tracking: Acme Corp, Development
|
|
133
|
+
Started at: 09:00
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
python stundu.py status
|
|
138
|
+
```
|
|
139
|
+
```
|
|
140
|
+
Currently tracking: Acme Corp, Development
|
|
141
|
+
Started at: 09:00
|
|
142
|
+
Elapsed: 2h 15m
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
python stundu.py stop
|
|
147
|
+
```
|
|
148
|
+
```
|
|
149
|
+
Stopped tracking: Acme Corp, Development
|
|
150
|
+
Duration: 3h 0m
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
python stundu.py log
|
|
155
|
+
```
|
|
156
|
+
```
|
|
157
|
+
@ Start End Duration Tags Status
|
|
158
|
+
-- ------- ------- ---------- ---------------------- --------
|
|
159
|
+
@1 09:00 12:00 3h 0m Acme Corp, Development
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
python stundu.py configure rate "Acme Corp" 50 EUR
|
|
164
|
+
python stundu.py report
|
|
165
|
+
```
|
|
166
|
+
```
|
|
167
|
+
Report for 'Acme Corp' (Jan 1, 2026 to Jan 31, 2026)
|
|
168
|
+
────────────────────────────────────────────────────────────
|
|
169
|
+
╭──────────────┬───────┬───────┬──────────┬─────────────┬────────────╮
|
|
170
|
+
│ Date │ Start │ End │ Duration │ Tags │ Cost │
|
|
171
|
+
├──────────────┼───────┼───────┼──────────┼─────────────┼────────────┤
|
|
172
|
+
│ Jan 15, 2026 │ 09:00 │ 12:00 │ 3h 0m │ Development │ 150.00 EUR │
|
|
173
|
+
│ │ 14:00 │ 18:00 │ 4h 0m │ Development │ 200.00 EUR │
|
|
174
|
+
│ │ 18:00 │ 20:00 │ 2h 0m │ Development │ 150.00 EUR │
|
|
175
|
+
├──────────────┼───────┼───────┼──────────┼─────────────┼────────────┤
|
|
176
|
+
│ Jan 18, 2026 │ 10:00 │ 14:00 │ 4h 0m │ Development │ 400.00 EUR │
|
|
177
|
+
╰──────────────┴───────┴───────┴──────────┴─────────────┴────────────╯
|
|
178
|
+
|
|
179
|
+
────────────────────────────────────────────────────────────
|
|
180
|
+
Total time: 13h 0m
|
|
181
|
+
|
|
182
|
+
Total:
|
|
183
|
+
900.00 EUR
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
See [docs/examples.md](docs/examples.md) for more detailed examples.
|
|
187
|
+
|
|
188
|
+
## Configuration
|
|
189
|
+
|
|
190
|
+
### First Run
|
|
191
|
+
|
|
192
|
+
On first run, Stundu displays a welcome message and prompts you to run the setup wizard:
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
Welcome to Stundu!
|
|
196
|
+
This appears to be your first run.
|
|
197
|
+
Run 'stundu.py setup' to configure.
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Run `stundu.py setup` to create a configuration file interactively.
|
|
201
|
+
|
|
202
|
+
### Config File
|
|
203
|
+
|
|
204
|
+
The default config file location is `~/.config/stundu/config.json`:
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"locale": "lv",
|
|
209
|
+
"style": "rounded_grid",
|
|
210
|
+
"data_file": "/path/to/my/timesheet.csv",
|
|
211
|
+
"formats": {
|
|
212
|
+
"date": "short",
|
|
213
|
+
"time": "short",
|
|
214
|
+
"datetime": "medium"
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Run `configure` without arguments to see which config file is in use and what values are active.
|
|
220
|
+
|
|
221
|
+
#### Table Style
|
|
222
|
+
|
|
223
|
+
The `style` key controls how tables are rendered in `report`, `log`, and `rates`. Set it with:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
python stundu.py configure style rounded_grid
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Available styles (run `configure style --help` for the full list):
|
|
230
|
+
|
|
231
|
+
| Tier | Styles | Divider |
|
|
232
|
+
|------|--------|---------|
|
|
233
|
+
| ASCII | `plain`, `simple`, `grid`, `github`, `psql`, `pipe`, `presto`, `pretty` | `-` |
|
|
234
|
+
| Light | `simple_grid`, `rounded_grid`, `outline`, `simple_outline`, `rounded_outline` | `─` |
|
|
235
|
+
| Heavy | `heavy_grid`, `double_grid`, `fancy_grid`, `heavy_outline`, `double_outline`, `fancy_outline` | `━` |
|
|
236
|
+
|
|
237
|
+
The default is `simple`. The Quick Start section shows an example report rendered with `rounded_grid`.
|
|
238
|
+
|
|
239
|
+
#### Date/Time Format Options
|
|
240
|
+
|
|
241
|
+
The `formats` section controls how dates and times are displayed. You can use:
|
|
242
|
+
|
|
243
|
+
**Preset styles** (locale-aware):
|
|
244
|
+
- `short` - Compact format (e.g., "1/15/26" in English, "15.01.26" in German)
|
|
245
|
+
- `medium` - Default format (e.g., "Jan 15, 2026")
|
|
246
|
+
- `long` - Full month name (e.g., "January 15, 2026")
|
|
247
|
+
- `full` - Complete format (e.g., "Thursday, January 15, 2026")
|
|
248
|
+
|
|
249
|
+
**Custom patterns** (using Unicode CLDR format):
|
|
250
|
+
```json
|
|
251
|
+
{
|
|
252
|
+
"formats": {
|
|
253
|
+
"date": "dd.MM.yyyy",
|
|
254
|
+
"time": "HH:mm",
|
|
255
|
+
"datetime": "yyyy-MM-dd HH:mm"
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Environment Variables
|
|
261
|
+
|
|
262
|
+
| Variable | Description |
|
|
263
|
+
|----------|-------------|
|
|
264
|
+
| `STUNDU_CONFIG` | Custom config file path |
|
|
265
|
+
| `STUNDU_DATA` | Custom data file path |
|
|
266
|
+
|
|
267
|
+
### CLI Options
|
|
268
|
+
|
|
269
|
+
| Option | Description |
|
|
270
|
+
|--------|-------------|
|
|
271
|
+
| `--lang`, `-L` | Override language (e.g., `--lang=de`) |
|
|
272
|
+
| `--data`, `-d` | Override data file path for this command |
|
|
273
|
+
|
|
274
|
+
### Precedence
|
|
275
|
+
|
|
276
|
+
**Data file location** (highest to lowest priority):
|
|
277
|
+
1. `--data` CLI option
|
|
278
|
+
2. `STUNDU_DATA` environment variable
|
|
279
|
+
3. `data_file` in config file
|
|
280
|
+
4. Default: `stundu_data.csv` in current directory
|
|
281
|
+
|
|
282
|
+
**Config file location**:
|
|
283
|
+
1. `STUNDU_CONFIG` environment variable
|
|
284
|
+
2. Default: `~/.config/stundu/config.json`
|
|
285
|
+
|
|
286
|
+
## Expression-Based Rates
|
|
287
|
+
|
|
288
|
+
Beyond fixed hourly rates, Billable supports MathJSON expressions as the `hourly_rate`. This lets you implement billing models where the effective rate depends on how much time has already been billed — retainers, volume discounts, loyalty pricing, and more. Entries that cross a rate-change threshold mid-session are automatically split at the exact minute the threshold is reached.
|
|
289
|
+
|
|
290
|
+
Expression-based rates are set by editing the data file directly (they are an advanced feature):
|
|
291
|
+
|
|
292
|
+
```
|
|
293
|
+
SET_HOURLY_RATE "Client" <expression> <currency>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### First Hour Free, Then 50 EUR/h (Monthly Retainer)
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
SET_HOURLY_RATE "Acme Corp" ["If",[["Less",["Sum","[Acme Corp][0M:+1M][hours]"],1],0],50] EUR
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
The expression sums all logged hours for "Acme Corp" in the current calendar month. While the total is under 1 hour the rate is 0; after that it's 50 EUR/h. The counter resets automatically at the start of each month.
|
|
303
|
+
|
|
304
|
+
### Volume Discount — Full Rate for First 20 h, Lower Rate After
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
SET_HOURLY_RATE "Acme Corp" ["If",[["Less",["Sum","[Acme Corp][0M:+1M][hours]"],20],80],60] EUR
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
First 20 hours each month: 80 EUR/h. Everything beyond: 60 EUR/h.
|
|
311
|
+
|
|
312
|
+
### Loyalty Rate — Cheaper After 100 Lifetime Hours
|
|
313
|
+
|
|
314
|
+
```
|
|
315
|
+
SET_HOURLY_RATE "Acme Corp" ["If",[["Less",["Sum","[Acme Corp][:-1][hours]"],100],75],50] EUR
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
`[:-1]` sums all entries *before* the current one. The rate starts at 75 EUR/h and drops to 50 EUR/h once the client has accumulated 100 hours of lifetime work.
|
|
319
|
+
|
|
320
|
+
See [docs/concepts.md](docs/concepts.md) for the full reference, including the entry reference syntax, available fields, and how expression-based rates compose with afterhours and holiday modifiers.
|
|
321
|
+
|
|
322
|
+
## Localization
|
|
323
|
+
|
|
324
|
+
The CLI supports multiple languages with full localization of messages and date/time formats. The language is detected automatically from your system locale, or you can override it with `--lang`:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
python stundu.py --lang=de status
|
|
328
|
+
python stundu.py --lang=es log
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Date and time formatting automatically adapts to your locale using [Babel](https://babel.pocoo.org/):
|
|
332
|
+
|
|
333
|
+
| Locale | Date Example | Time |
|
|
334
|
+
|--------|----------------------|---------|
|
|
335
|
+
| en | Jan 15, 2026 | 2:30 PM |
|
|
336
|
+
| de | 15.01.2026 | 14:30 |
|
|
337
|
+
| fr | 15 janv. 2026 | 14:30 |
|
|
338
|
+
| lv | 2026. gada 15. janv. | 14:30 |
|
|
339
|
+
|
|
340
|
+
### Available Translations
|
|
341
|
+
|
|
342
|
+
| Code | Language |
|
|
343
|
+
|------|------------|
|
|
344
|
+
| en | English |
|
|
345
|
+
| de | German |
|
|
346
|
+
| es | Spanish |
|
|
347
|
+
| et | Estonian |
|
|
348
|
+
| fr | French |
|
|
349
|
+
| it | Italian |
|
|
350
|
+
| lt | Lithuanian |
|
|
351
|
+
| lv | Latvian |
|
|
352
|
+
| pl | Polish |
|
|
353
|
+
| pt | Portuguese |
|
|
354
|
+
| uk | Ukrainian |
|
|
355
|
+
|
|
356
|
+
## Commands
|
|
357
|
+
|
|
358
|
+
### Time Tracking
|
|
359
|
+
|
|
360
|
+
| Command | Description |
|
|
361
|
+
|---------|-------------|
|
|
362
|
+
| `start <tags...>` | Start tracking time with one or more tags |
|
|
363
|
+
| `stop` | Stop the currently active entry |
|
|
364
|
+
| `status` | Show the currently active entry |
|
|
365
|
+
| `continue` | Resume tracking with the same tags as the last entry |
|
|
366
|
+
| `cancel` | Discard the currently active entry without saving |
|
|
367
|
+
|
|
368
|
+
### Viewing Entries
|
|
369
|
+
|
|
370
|
+
| Command | Description |
|
|
371
|
+
|---------|-------------|
|
|
372
|
+
| `log` | Show all time entries in a table |
|
|
373
|
+
| `report` | Show billing report (defaults to current month) |
|
|
374
|
+
| `rates` | Show configured rates for all tags |
|
|
375
|
+
|
|
376
|
+
Report options:
|
|
377
|
+
- `--tag`, `-t` - Filter by tag
|
|
378
|
+
- `--month`, `-m` - Current month (default when no filter is given)
|
|
379
|
+
- `--last-month`, `-l` - Previous month
|
|
380
|
+
- `--from` - Start date filter (YYYY-MM-DD)
|
|
381
|
+
- `--to` - End date filter (YYYY-MM-DD)
|
|
382
|
+
|
|
383
|
+
When no date filter is specified, the report shows the current month. If there are no entries in the current month, it falls back to all entries from the last day that has data.
|
|
384
|
+
|
|
385
|
+
### Configuration
|
|
386
|
+
|
|
387
|
+
All configuration commands are grouped under the `configure` command:
|
|
388
|
+
|
|
389
|
+
| Command | Description |
|
|
390
|
+
|---------|-------------|
|
|
391
|
+
| `configure` | Show active config sources and available subcommands |
|
|
392
|
+
| `configure rate <tag> <amount> <currency>` | Set hourly rate for a tag |
|
|
393
|
+
| `configure workday <hours> [tag]` | Set workday hours (e.g., 9:00-18:00) |
|
|
394
|
+
| `configure afterhours <tag> <multiplier>` | Set afterhours rate multiplier |
|
|
395
|
+
| `configure holiday <tag> <multiplier>` | Set holiday/weekend rate multiplier |
|
|
396
|
+
| `configure week-start <0-6>` | Set week start day for calendar expressions (0=Mon, 6=Sun) |
|
|
397
|
+
| `configure style <name>` | Set table display style |
|
|
398
|
+
|
|
399
|
+
Personal information (for invoices):
|
|
400
|
+
|
|
401
|
+
| Command | Description |
|
|
402
|
+
|---------|-------------|
|
|
403
|
+
| `configure my-name <name>` | Set your name |
|
|
404
|
+
| `configure my-address <address>` | Set your address |
|
|
405
|
+
| `configure my-email <email>` | Set your email |
|
|
406
|
+
| `configure my-vat <vat>` | Set your VAT number |
|
|
407
|
+
| `configure my-bank <bank>` | Set your bank name |
|
|
408
|
+
| `configure my-bank-account <account>` | Set your bank account number |
|
|
409
|
+
| `configure my-bic <bic>` | Set your bank BIC/SWIFT code |
|
|
410
|
+
| `configure my-note <note>` | Set a note to appear on invoices |
|
|
411
|
+
|
|
412
|
+
Client information (for invoices):
|
|
413
|
+
|
|
414
|
+
| Command | Description |
|
|
415
|
+
|---------|-------------|
|
|
416
|
+
| `configure client-address <tag> <address>` | Set client address |
|
|
417
|
+
| `configure client-vat <tag> <vat>` | Set client VAT number |
|
|
418
|
+
| `configure client-note <tag> <note>` | Set client-specific note |
|
|
419
|
+
|
|
420
|
+
### Entry Management
|
|
421
|
+
|
|
422
|
+
| Command | Description |
|
|
423
|
+
|---------|-------------|
|
|
424
|
+
| `modify <@N> [options]` | Modify an existing entry |
|
|
425
|
+
| `delete <@N or tag>` | Delete entries by reference or tag |
|
|
426
|
+
|
|
427
|
+
### Invoicing
|
|
428
|
+
|
|
429
|
+
| Command | Description |
|
|
430
|
+
|---------|-------------|
|
|
431
|
+
| `invoice <tag> [options]` | Generate invoice for a tag |
|
|
432
|
+
|
|
433
|
+
Invoice options:
|
|
434
|
+
- `--output`, `-o` - Output file path (default: `invoice_<tag>_<date>.html`)
|
|
435
|
+
- `--format`, `-f` - Output format: `html` or `pdf` (default: html)
|
|
436
|
+
- `--from` - Start date filter (YYYY-MM-DD)
|
|
437
|
+
- `--to` - End date filter (YYYY-MM-DD)
|
|
438
|
+
|
|
439
|
+
### Importing Data
|
|
440
|
+
|
|
441
|
+
| Command | Description |
|
|
442
|
+
|---------|-------------|
|
|
443
|
+
| `import <source> <file> [options]` | Import time entries from external tools |
|
|
444
|
+
|
|
445
|
+
Import options:
|
|
446
|
+
- `--dry-run` - Preview import without saving
|
|
447
|
+
- `--yes`, `-y` - Skip confirmation prompt
|
|
448
|
+
|
|
449
|
+
### Other
|
|
450
|
+
|
|
451
|
+
| Command | Description |
|
|
452
|
+
|---------|-------------|
|
|
453
|
+
| `setup` | Run the setup wizard to configure stundu |
|
|
454
|
+
|
|
455
|
+
## Supported Importers
|
|
456
|
+
|
|
457
|
+
Stundu can import data from other time tracking tools:
|
|
458
|
+
|
|
459
|
+
| Source | Description |
|
|
460
|
+
|--------|-------------|
|
|
461
|
+
| `timewarrior` | [Timewarrior](https://timewarrior.net/) data files |
|
|
462
|
+
|
|
463
|
+
## Data Storage
|
|
464
|
+
|
|
465
|
+
All data is stored in a single CSV file (`stundu_data.csv`) containing:
|
|
466
|
+
|
|
467
|
+
- Time entries with start/end timestamps and tags
|
|
468
|
+
- Rate configuration commands (`SET_HOURLY_RATE`, `SET_WORKDAY`, `SET_WEEK_START`, etc.)
|
|
469
|
+
- Personal and client information (`SET_MY_*`, `SET_CLIENT_*`)
|
|
470
|
+
|
|
471
|
+
This makes it easy to backup, version control, or transfer your time tracking data.
|
|
472
|
+
|
|
473
|
+
## Documentation
|
|
474
|
+
|
|
475
|
+
- [Examples](docs/examples.md) - Detailed usage examples
|
|
476
|
+
- [Concepts](docs/concepts.md) - Tags, rate types, MathJSON expressions
|
|
477
|
+
|
|
478
|
+
## Contributing
|
|
479
|
+
|
|
480
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, testing, and contribution guidelines.
|
|
481
|
+
|
|
482
|
+
## License
|
|
483
|
+
|
|
484
|
+
MIT
|