timeglance 1.0.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 (57) hide show
  1. timeglance-1.0.0/.dockerignore +9 -0
  2. timeglance-1.0.0/.github/dependabot.yml +13 -0
  3. timeglance-1.0.0/.github/workflows/bandit.yaml +22 -0
  4. timeglance-1.0.0/.github/workflows/pip-audit.yaml +22 -0
  5. timeglance-1.0.0/.github/workflows/pre-commit.yaml +25 -0
  6. timeglance-1.0.0/.github/workflows/release.yaml +68 -0
  7. timeglance-1.0.0/.github/workflows/tests.yaml +27 -0
  8. timeglance-1.0.0/.gitignore +17 -0
  9. timeglance-1.0.0/.pre-commit-config.yaml +16 -0
  10. timeglance-1.0.0/CHANGELOG.md +7 -0
  11. timeglance-1.0.0/CONTRIBUTING.md +43 -0
  12. timeglance-1.0.0/Dockerfile +29 -0
  13. timeglance-1.0.0/LICENSE +21 -0
  14. timeglance-1.0.0/PKG-INFO +112 -0
  15. timeglance-1.0.0/README.md +80 -0
  16. timeglance-1.0.0/SECURITY.md +14 -0
  17. timeglance-1.0.0/compose.yaml +29 -0
  18. timeglance-1.0.0/docs/configuration.md +46 -0
  19. timeglance-1.0.0/docs/control-panel.md +20 -0
  20. timeglance-1.0.0/docs/images/weekly.png +0 -0
  21. timeglance-1.0.0/docs/images/year.png +0 -0
  22. timeglance-1.0.0/docs/library.md +37 -0
  23. timeglance-1.0.0/docs/motivation.md +32 -0
  24. timeglance-1.0.0/docs/reference.md +61 -0
  25. timeglance-1.0.0/docs/wallpaper.md +13 -0
  26. timeglance-1.0.0/pyproject.toml +108 -0
  27. timeglance-1.0.0/src/timeglance/__init__.py +3 -0
  28. timeglance-1.0.0/src/timeglance/common/__init__.py +1 -0
  29. timeglance-1.0.0/src/timeglance/common/serve.py +507 -0
  30. timeglance-1.0.0/src/timeglance/common/wallpaper.py +194 -0
  31. timeglance-1.0.0/src/timeglance/common/web/app.js +420 -0
  32. timeglance-1.0.0/src/timeglance/common/web/favicon.ico +0 -0
  33. timeglance-1.0.0/src/timeglance/common/web/favicon.svg +14 -0
  34. timeglance-1.0.0/src/timeglance/common/web/icon.png +0 -0
  35. timeglance-1.0.0/src/timeglance/common/web/index.html +110 -0
  36. timeglance-1.0.0/src/timeglance/init.py +29 -0
  37. timeglance-1.0.0/src/timeglance/paths.py +32 -0
  38. timeglance-1.0.0/src/timeglance/weekly/__init__.py +1 -0
  39. timeglance-1.0.0/src/timeglance/weekly/build.py +253 -0
  40. timeglance-1.0.0/src/timeglance/weekly/config.yaml.in +66 -0
  41. timeglance-1.0.0/src/timeglance/weekly/templates/week.html.j2 +112 -0
  42. timeglance-1.0.0/src/timeglance/year/__init__.py +1 -0
  43. timeglance-1.0.0/src/timeglance/year/build.py +204 -0
  44. timeglance-1.0.0/src/timeglance/year/config.yaml.in +95 -0
  45. timeglance-1.0.0/src/timeglance/year/render.py +664 -0
  46. timeglance-1.0.0/src/timeglance/year/sources.py +150 -0
  47. timeglance-1.0.0/tests/examples/sample.ics +17 -0
  48. timeglance-1.0.0/tests/examples/weekly.yaml +18 -0
  49. timeglance-1.0.0/tests/examples/year_aligned.yaml +24 -0
  50. timeglance-1.0.0/tests/examples/year_packed.yaml +16 -0
  51. timeglance-1.0.0/tests/examples/year_sources.yaml +13 -0
  52. timeglance-1.0.0/tests/test_config.py +115 -0
  53. timeglance-1.0.0/tests/test_examples.py +64 -0
  54. timeglance-1.0.0/tests/test_sources.py +142 -0
  55. timeglance-1.0.0/tests/test_weekly.py +112 -0
  56. timeglance-1.0.0/tests/test_year.py +119 -0
  57. timeglance-1.0.0/uv.lock +1588 -0
@@ -0,0 +1,9 @@
1
+ .git/
2
+ .venv/
3
+ **/__pycache__/
4
+ **/*.pyc
5
+ **/out/
6
+ state/
7
+ docs/
8
+ *.md
9
+ !README.md
@@ -0,0 +1,13 @@
1
+ version: 2
2
+ updates:
3
+ # Maintain dependencies for Python packages
4
+ - package-ecosystem: "uv"
5
+ directory: "/"
6
+ schedule:
7
+ interval: "weekly"
8
+
9
+ # Maintain dependencies for GitHub Actions
10
+ - package-ecosystem: "github-actions"
11
+ directory: "/"
12
+ schedule:
13
+ interval: "weekly"
@@ -0,0 +1,22 @@
1
+ name: bandit
2
+
3
+ on:
4
+ pull_request:
5
+ branches: ["main*"]
6
+ push:
7
+ branches: ["main"]
8
+
9
+ jobs:
10
+ bandit:
11
+ runs-on: ubuntu-24.04
12
+ steps:
13
+ - uses: actions/checkout@v7.0.1
14
+ - uses: actions/setup-python@v7
15
+ with:
16
+ python-version: "3.13"
17
+ - name: Install uv and sync dependencies
18
+ run: |
19
+ pip install uv
20
+ uv sync --frozen
21
+ - name: Run bandit
22
+ run: uv run bandit -r src/timeglance -c pyproject.toml
@@ -0,0 +1,22 @@
1
+ name: pip-audit
2
+
3
+ on:
4
+ pull_request:
5
+ branches: ["main*"]
6
+ push:
7
+ branches: ["main"]
8
+
9
+ jobs:
10
+ pip-audit:
11
+ runs-on: ubuntu-24.04
12
+ steps:
13
+ - uses: actions/checkout@v7.0.1
14
+ - uses: actions/setup-python@v7
15
+ with:
16
+ python-version: "3.13"
17
+ - name: Install uv and sync dependencies
18
+ run: |
19
+ pip install uv
20
+ uv sync --frozen
21
+ - name: Run pip-audit
22
+ run: uv run pip-audit
@@ -0,0 +1,25 @@
1
+ name: pre-commit
2
+
3
+ on:
4
+ pull_request:
5
+ branches: ["main*"]
6
+ workflow_call:
7
+
8
+ jobs:
9
+ pre-commit:
10
+ runs-on: ubuntu-24.04
11
+ steps:
12
+ - uses: actions/checkout@v7.0.1
13
+ - uses: actions/setup-python@v7
14
+ with:
15
+ python-version: "3.13"
16
+ - name: Get python version
17
+ run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
18
+ - uses: actions/cache@v6
19
+ with:
20
+ path: ~/.cache/pre-commit
21
+ key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }}
22
+ - name: Install pre-commit
23
+ run: pip install pre-commit
24
+ - name: Run pre-commit
25
+ run: pre-commit run --all-files --show-diff-on-failure --color=always
@@ -0,0 +1,68 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ # semantic-release pushes its `RELEASE: {version}` commit with a PAT, which re-triggers this
8
+ # workflow. That commit has nothing to test or release, so skip every job on it (the alternative,
9
+ # `[skip ci]` in the commit message, works too but is less explicit). On the RELEASE commit all
10
+ # jobs skip; `release` needs the skipped jobs, so it skips with them.
11
+ jobs:
12
+ pre-commit:
13
+ if: ${{ !startsWith(github.event.head_commit.message, 'RELEASE:') }}
14
+ uses: ./.github/workflows/pre-commit.yaml
15
+ tests:
16
+ if: ${{ !startsWith(github.event.head_commit.message, 'RELEASE:') }}
17
+ uses: ./.github/workflows/tests.yaml
18
+ release:
19
+ name: Release
20
+ needs: [pre-commit, tests] # a broken build never reaches the release step
21
+ if: ${{ !startsWith(github.event.head_commit.message, 'RELEASE:') }}
22
+ runs-on: ubuntu-24.04
23
+ concurrency:
24
+ group: ${{ github.workflow }}-release-${{ github.ref_name }}
25
+ cancel-in-progress: false
26
+ permissions:
27
+ contents: write
28
+ outputs:
29
+ released: ${{ steps.release.outputs.released }}
30
+ version: ${{ steps.release.outputs.version }}
31
+ steps:
32
+ - uses: actions/checkout@v7.0.1
33
+ with:
34
+ fetch-depth: 0
35
+ token: ${{ secrets.SEMANTIC_RELEASE_TOKEN }}
36
+ - name: Python Semantic Release
37
+ id: release
38
+ uses: python-semantic-release/python-semantic-release@v10
39
+ with:
40
+ github_token: ${{ secrets.SEMANTIC_RELEASE_TOKEN }}
41
+
42
+ # Publish to PyPI only when a release was actually cut (gated on the action's structured
43
+ # `released` output, never log-grepping). It runs in the `pypi` environment, so the OIDC
44
+ # token - and thus publishing rights - is scoped to this job rather than the whole workflow.
45
+ # Trusted publishing means no API token, and gh-action-pypi-publish attaches sigstore
46
+ # attestations by default. Dormant until the first release (1.0.0) is cut.
47
+ publish:
48
+ name: Publish to PyPI
49
+ needs: release
50
+ if: needs.release.outputs.released == 'true'
51
+ runs-on: ubuntu-24.04
52
+ environment:
53
+ name: pypi
54
+ url: https://pypi.org/p/timeglance
55
+ permissions:
56
+ id-token: write
57
+ contents: read # checkout needs read access while the repo is private
58
+ steps:
59
+ - uses: actions/checkout@v7.0.1
60
+ with:
61
+ ref: ${{ needs.release.outputs.version }} # the tag semantic-release just pushed
62
+ - uses: actions/setup-python@v7
63
+ with:
64
+ python-version: "3.13"
65
+ - name: Build distributions
66
+ run: pip install uv && uv build
67
+ - name: Publish to PyPI
68
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,27 @@
1
+ name: tests
2
+
3
+ on:
4
+ pull_request:
5
+ branches: ["main*"]
6
+ workflow_call:
7
+
8
+ jobs:
9
+ tests:
10
+ runs-on: ubuntu-24.04
11
+ steps:
12
+ - uses: actions/checkout@v7.0.1
13
+ - uses: actions/setup-python@v7
14
+ with:
15
+ python-version: "3.13"
16
+ - name: Install system libraries for WeasyPrint & CairoSVG
17
+ run: |
18
+ sudo apt-get update
19
+ sudo apt-get install -y --no-install-recommends \
20
+ libcairo2 libpango-1.0-0 libpangocairo-1.0-0 libgdk-pixbuf-2.0-0 \
21
+ libffi8 shared-mime-info fonts-dejavu-core fonts-noto-core
22
+ - name: Install uv and sync dependencies
23
+ run: |
24
+ pip install uv
25
+ uv sync --frozen
26
+ - name: Run tests
27
+ run: uv run pytest
@@ -0,0 +1,17 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ .ruff_cache/
5
+ .pytest_cache/
6
+ dist/
7
+ weekly/out/
8
+ year/out/
9
+ state/
10
+
11
+ # Private working configs; scaffold them with `timeglance-init`. The year config can hold
12
+ # calendar share links / secrets; the weekly config is your personal schedule.
13
+ weekly/config.yaml
14
+ year/config.yaml
15
+
16
+ # Backups written before a "reset to template" (config.yaml.<stamp>.bak etc.)
17
+ *.yaml.*.bak
@@ -0,0 +1,16 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.16.9
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+ - repo: https://github.com/rbubley/mirrors-prettier
9
+ rev: v3.3.3
10
+ hooks:
11
+ - id: prettier
12
+ types_or: [markdown]
13
+ # CHANGELOG.md is generated by semantic-release; prettier would reformat it and fail
14
+ # the release commit's CI. `exclude` (not .prettierignore) also avoids prettier's
15
+ # "No matching files" error when a commit touches only the changelog.
16
+ exclude: ^CHANGELOG\.md$
@@ -0,0 +1,7 @@
1
+ # CHANGELOG
2
+
3
+ <!-- version list -->
4
+
5
+ ## v1.0.0 (2026-10-03)
6
+
7
+ - Initial Release
@@ -0,0 +1,43 @@
1
+ # Contributing
2
+
3
+ Patches welcome! Open an issue for bugs and ideas, or a PR for changes.
4
+
5
+ ## Setup
6
+
7
+ ```sh
8
+ uv sync
9
+ pre-commit install
10
+ ```
11
+
12
+ Hooks: **ruff** (lint + format) for Python, **prettier** for Markdown.
13
+
14
+ The code lives under `src/timeglance/` (`common`, `year`, `weekly` subpackages); packaged assets (web UI, Jinja
15
+ templates, the `config.yaml.in` files) ship inside it. Working data - each tool's `config.yaml` and `out/`, plus
16
+ shared `state/` - resolves from the current working directory (override with `TIMEGLANCE_HOME`).
17
+
18
+ ## Before you push
19
+
20
+ `uv run pytest` and `pre-commit run --all-files` should pass. Commit messages follow
21
+ [Conventional Commits](https://www.conventionalcommits.org) (`feat:`, `fix:`, `docs:`, …) - they drive the automated
22
+ version bumps and changelog.
23
+
24
+ ## Continuous integration
25
+
26
+ Every pull request runs **pre-commit** (ruff + prettier) and the **pytest** suite (with the WeasyPrint/CairoSVG
27
+ system libraries installed), plus **pip-audit** (dependency CVEs) and **bandit** (security lint). Pushes to `main`
28
+ run those again and then the release job.
29
+
30
+ ## Releasing
31
+
32
+ Versioning is automated with [python-semantic-release](https://python-semantic-release.readthedocs.io), driven by
33
+ [Conventional Commits](https://www.conventionalcommits.org):
34
+
35
+ - `feat: …` → minor bump, `fix: …` → patch bump, breaking change (`feat!: …` or a `BREAKING CHANGE:` footer) →
36
+ major bump; `docs:` / `refactor:` / `perf:` show up in the changelog.
37
+ - An optional scope narrows the area, e.g. `feat(weekly): …` or `fix(year): …`.
38
+
39
+ On every push to `main`, CI runs semantic-release. When the new commits warrant a bump (per the rules above) it
40
+ raises the version in `pyproject.toml` (and `src/timeglance/__init__.py:__version__`), updates the changelog, tags,
41
+ and cuts a GitHub Release; otherwise it does nothing. This needs a `SEMANTIC_RELEASE_TOKEN` secret (a PAT with
42
+ `contents: write`). That same version bump then publishes the build to PyPI via
43
+ [trusted publishing](https://docs.pypi.org/trusted-publishers/) (OIDC, no API token) from the `pypi` environment.
@@ -0,0 +1,29 @@
1
+ # timeglance: renders weekly sheets (WeasyPrint) and the year planner (CairoSVG).
2
+ FROM python:3.13-slim-bookworm
3
+
4
+ ENV PYTHONUNBUFFERED=1 \
5
+ UV_LINK_MODE=copy \
6
+ UV_COMPILE_BYTECODE=1 \
7
+ PATH="/app/.venv/bin:$PATH"
8
+
9
+ # Native libraries for WeasyPrint + CairoSVG, plus the fonts the templates reference.
10
+ RUN apt-get update && apt-get install -y --no-install-recommends \
11
+ libcairo2 libpango-1.0-0 libpangocairo-1.0-0 libgdk-pixbuf-2.0-0 \
12
+ libffi8 shared-mime-info fonts-dejavu-core fonts-noto-core \
13
+ && rm -rf /var/lib/apt/lists/*
14
+
15
+ COPY --from=ghcr.io/astral-sh/uv:0.8.2 /uv /uvx /bin/
16
+
17
+ WORKDIR /app
18
+
19
+ # Install dependencies first (cached from the lockfile), then the project itself.
20
+ COPY pyproject.toml uv.lock README.md ./
21
+ RUN uv sync --frozen --no-dev --no-install-project
22
+ COPY src ./src
23
+ RUN uv sync --frozen --no-dev
24
+
25
+ EXPOSE 8753
26
+
27
+ # Default: the timeglance control panel (both tools). Override for a one-shot render, e.g.
28
+ # docker compose run --rm timeglance timeglance-weekly
29
+ CMD ["timeglance", "--host", "0.0.0.0"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Danny W. Adair
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.
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.5
2
+ Name: timeglance
3
+ Version: 1.0.0
4
+ Summary: Printable weekly routine sheets and a year-at-a-glance planner / desktop wallpaper.
5
+ Project-URL: Homepage, https://github.com/dannyadair/timeglance
6
+ Project-URL: Repository, https://github.com/dannyadair/timeglance
7
+ Project-URL: Issues, https://github.com/dannyadair/timeglance/issues
8
+ Author-email: "Danny W. Adair" <danny.adair@unfold.nz>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: calendar,ical,kde,pdf,planner,plasma,schedule,svg,wallpaper,yaml
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Environment :: Web Environment
15
+ Classifier: Intended Audience :: End Users/Desktop
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Multimedia :: Graphics
19
+ Classifier: Topic :: Office/Business :: Scheduling
20
+ Requires-Python: >=3.13
21
+ Requires-Dist: cairosvg>=2.7.1
22
+ Requires-Dist: holidays>=0.105
23
+ Requires-Dist: icalendar>=7.3.0
24
+ Requires-Dist: jinja2>=3.1.6
25
+ Requires-Dist: pillow>=10.0.0
26
+ Requires-Dist: pypdfium2>=4.30.0
27
+ Requires-Dist: python-dateutil>=2.9.0.post0
28
+ Requires-Dist: pyyaml>=6.0.2
29
+ Requires-Dist: recurring-ical-events>=3.8.2
30
+ Requires-Dist: weasyprint>=62.3
31
+ Description-Content-Type: text/markdown
32
+
33
+ # timeglance
34
+
35
+ [![Python](https://img.shields.io/python/required-version-toml?color=blue&tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2Fdannyadair%2Ftimeglance%2Fmain%2Fpyproject.toml)](https://github.com/dannyadair/timeglance/blob/main/pyproject.toml)
36
+ [![tests](https://github.com/dannyadair/timeglance/actions/workflows/tests.yaml/badge.svg)](https://github.com/dannyadair/timeglance/actions/workflows/tests.yaml)
37
+ [![pre-commit](https://img.shields.io/github/actions/workflow/status/dannyadair/timeglance/pre-commit.yaml?label=pre-commit&logo=pre-commit&logoColor=white)](https://github.com/dannyadair/timeglance/actions/workflows/pre-commit.yaml)
38
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
39
+ [![pip-audit](https://img.shields.io/github/actions/workflow/status/dannyadair/timeglance/pip-audit.yaml?label=pip-audit&logo=python)](https://github.com/dannyadair/timeglance/actions/workflows/pip-audit.yaml)
40
+ [![bandit](https://img.shields.io/github/actions/workflow/status/dannyadair/timeglance/bandit.yaml?label=bandit&logo=python)](https://github.com/dannyadair/timeglance/actions/workflows/bandit.yaml)
41
+ [![Last commit](https://img.shields.io/github/last-commit/dannyadair/timeglance?color=lightgrey)](https://github.com/dannyadair/timeglance/commits/main)
42
+ [![License](https://img.shields.io/github/license/dannyadair/timeglance?color=lightgrey)](https://github.com/dannyadair/timeglance/blob/main/LICENSE)
43
+
44
+ <!-- Add once published to PyPI (and if you wire up these services), matching pdfbaker:
45
+ [![PyPI version](https://img.shields.io/pypi/v/timeglance?color=blue)](https://pypi.org/project/timeglance/)
46
+ [![Downloads](https://img.shields.io/pypi/dw/timeglance?color=blue)](https://pypistats.org/packages/timeglance)
47
+ [![sigstore](https://img.shields.io/badge/sigstore-signed-blue)](https://github.com/dannyadair/timeglance/releases)
48
+ [![codecov](https://img.shields.io/codecov/c/github/dannyadair/timeglance)](https://codecov.io/gh/dannyadair/timeglance)
49
+ [![OSSF Scorecard](https://img.shields.io/ossf-scorecard/github.com/dannyadair/timeglance?label=OSSF%20Scorecard)](https://scorecard.dev/viewer/?uri=github.com/dannyadair/timeglance)
50
+ -->
51
+
52
+ Config-driven time planning at a glance - one web UI controls two tools:
53
+
54
+ - **Weekly** - your routine sheet with activity layers. Print it or set it as a desktop wallpaper.
55
+ - **Year** - a wide year-at-a-glance planner, with layers fed by your own calendars, public holidays, and manually
56
+ added events. Print it or set it as a desktop wallpaper.
57
+
58
+ ![Weekly routine sheet](docs/images/weekly.png)
59
+
60
+ ![Year-at-a-glance planner](docs/images/year.png)
61
+
62
+ ## Quick start (Docker)
63
+
64
+ ```sh
65
+ docker compose run --rm timeglance timeglance-init # first run: scaffold year/ + weekly/ config.yaml
66
+ docker compose up # then open http://localhost:8753
67
+ ```
68
+
69
+ Config, outputs and scheduler state are bind-mounted under `./year`, `./weekly` and `./state`, so the UI edits
70
+ files on your host. Outputs are written as your host user — the compose file defaults to `1000:1000` (the usual
71
+ first Linux user), so it just works for most; if your uid differs, run `UID=$(id -u) GID=$(id -g) docker compose up`.
72
+
73
+ > Wallpaper-setting needs a live Plasma session, so it works only with the **local install** (next section), not in
74
+ > Docker; the container UI auto-hides it.
75
+
76
+ ## Quick start (local)
77
+
78
+ Needs Python 3.13 and [uv](https://docs.astral.sh/uv/). WeasyPrint and CairoSVG load native libraries (Cairo, Pango,
79
+ GDK-PixBuf) at runtime; most Linux desktops already have these, but on a minimal or server install add them first
80
+ (Debian/Ubuntu):
81
+
82
+ ```sh
83
+ sudo apt-get install libcairo2 libpango-1.0-0 libpangocairo-1.0-0 libgdk-pixbuf-2.0-0
84
+ uv sync
85
+ uv run timeglance-init # scaffold year/ + weekly/ config.yaml from the templates
86
+ uv run timeglance # the control panel at http://localhost:8753
87
+ ```
88
+
89
+ Headless renders (each flag has a YAML equivalent):
90
+
91
+ ```sh
92
+ uv run timeglance-year --theme light --layout vertical # -> year/out/
93
+ uv run timeglance-weekly --format both # -> weekly/out/
94
+ ```
95
+
96
+ ## Docs
97
+
98
+ - [Motivation](docs/motivation.md) - why this exists, and how it fits a GTD/calendar setup
99
+ - [The control panel](docs/control-panel.md) - preview, config editor, scheduler & activity log
100
+ - [Configuration](docs/configuration.md) - year & weekly YAML, calendar (`.ics`) sources
101
+ - [Reference](docs/reference.md) - every CLI flag and YAML key
102
+ - [Wallpaper](docs/wallpaper.md) - KDE Plasma, per-screen native-resolution rendering
103
+ - [Use as a library](docs/library.md) - import and render PNG/PDF/SVG from Python
104
+ - [Contributing](CONTRIBUTING.md) - setup, CI, releasing
105
+
106
+ ## Limitations
107
+
108
+ - **Wallpaper-setting is host-only.** The container can't reach a Plasma session, so inside Docker the feature
109
+ self-hides and a scheduled "set wallpaper" renders the files but skips the set (logged as
110
+ `wallpaper skipped (no backend)`). Run the server/CLI on the host to set wallpapers.
111
+ - **Weekly blocks can't overlap in time.** Two activities over the same slot on the same day render on top of each
112
+ other, so only one reads clearly.
@@ -0,0 +1,80 @@
1
+ # timeglance
2
+
3
+ [![Python](https://img.shields.io/python/required-version-toml?color=blue&tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2Fdannyadair%2Ftimeglance%2Fmain%2Fpyproject.toml)](https://github.com/dannyadair/timeglance/blob/main/pyproject.toml)
4
+ [![tests](https://github.com/dannyadair/timeglance/actions/workflows/tests.yaml/badge.svg)](https://github.com/dannyadair/timeglance/actions/workflows/tests.yaml)
5
+ [![pre-commit](https://img.shields.io/github/actions/workflow/status/dannyadair/timeglance/pre-commit.yaml?label=pre-commit&logo=pre-commit&logoColor=white)](https://github.com/dannyadair/timeglance/actions/workflows/pre-commit.yaml)
6
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
7
+ [![pip-audit](https://img.shields.io/github/actions/workflow/status/dannyadair/timeglance/pip-audit.yaml?label=pip-audit&logo=python)](https://github.com/dannyadair/timeglance/actions/workflows/pip-audit.yaml)
8
+ [![bandit](https://img.shields.io/github/actions/workflow/status/dannyadair/timeglance/bandit.yaml?label=bandit&logo=python)](https://github.com/dannyadair/timeglance/actions/workflows/bandit.yaml)
9
+ [![Last commit](https://img.shields.io/github/last-commit/dannyadair/timeglance?color=lightgrey)](https://github.com/dannyadair/timeglance/commits/main)
10
+ [![License](https://img.shields.io/github/license/dannyadair/timeglance?color=lightgrey)](https://github.com/dannyadair/timeglance/blob/main/LICENSE)
11
+
12
+ <!-- Add once published to PyPI (and if you wire up these services), matching pdfbaker:
13
+ [![PyPI version](https://img.shields.io/pypi/v/timeglance?color=blue)](https://pypi.org/project/timeglance/)
14
+ [![Downloads](https://img.shields.io/pypi/dw/timeglance?color=blue)](https://pypistats.org/packages/timeglance)
15
+ [![sigstore](https://img.shields.io/badge/sigstore-signed-blue)](https://github.com/dannyadair/timeglance/releases)
16
+ [![codecov](https://img.shields.io/codecov/c/github/dannyadair/timeglance)](https://codecov.io/gh/dannyadair/timeglance)
17
+ [![OSSF Scorecard](https://img.shields.io/ossf-scorecard/github.com/dannyadair/timeglance?label=OSSF%20Scorecard)](https://scorecard.dev/viewer/?uri=github.com/dannyadair/timeglance)
18
+ -->
19
+
20
+ Config-driven time planning at a glance - one web UI controls two tools:
21
+
22
+ - **Weekly** - your routine sheet with activity layers. Print it or set it as a desktop wallpaper.
23
+ - **Year** - a wide year-at-a-glance planner, with layers fed by your own calendars, public holidays, and manually
24
+ added events. Print it or set it as a desktop wallpaper.
25
+
26
+ ![Weekly routine sheet](docs/images/weekly.png)
27
+
28
+ ![Year-at-a-glance planner](docs/images/year.png)
29
+
30
+ ## Quick start (Docker)
31
+
32
+ ```sh
33
+ docker compose run --rm timeglance timeglance-init # first run: scaffold year/ + weekly/ config.yaml
34
+ docker compose up # then open http://localhost:8753
35
+ ```
36
+
37
+ Config, outputs and scheduler state are bind-mounted under `./year`, `./weekly` and `./state`, so the UI edits
38
+ files on your host. Outputs are written as your host user — the compose file defaults to `1000:1000` (the usual
39
+ first Linux user), so it just works for most; if your uid differs, run `UID=$(id -u) GID=$(id -g) docker compose up`.
40
+
41
+ > Wallpaper-setting needs a live Plasma session, so it works only with the **local install** (next section), not in
42
+ > Docker; the container UI auto-hides it.
43
+
44
+ ## Quick start (local)
45
+
46
+ Needs Python 3.13 and [uv](https://docs.astral.sh/uv/). WeasyPrint and CairoSVG load native libraries (Cairo, Pango,
47
+ GDK-PixBuf) at runtime; most Linux desktops already have these, but on a minimal or server install add them first
48
+ (Debian/Ubuntu):
49
+
50
+ ```sh
51
+ sudo apt-get install libcairo2 libpango-1.0-0 libpangocairo-1.0-0 libgdk-pixbuf-2.0-0
52
+ uv sync
53
+ uv run timeglance-init # scaffold year/ + weekly/ config.yaml from the templates
54
+ uv run timeglance # the control panel at http://localhost:8753
55
+ ```
56
+
57
+ Headless renders (each flag has a YAML equivalent):
58
+
59
+ ```sh
60
+ uv run timeglance-year --theme light --layout vertical # -> year/out/
61
+ uv run timeglance-weekly --format both # -> weekly/out/
62
+ ```
63
+
64
+ ## Docs
65
+
66
+ - [Motivation](docs/motivation.md) - why this exists, and how it fits a GTD/calendar setup
67
+ - [The control panel](docs/control-panel.md) - preview, config editor, scheduler & activity log
68
+ - [Configuration](docs/configuration.md) - year & weekly YAML, calendar (`.ics`) sources
69
+ - [Reference](docs/reference.md) - every CLI flag and YAML key
70
+ - [Wallpaper](docs/wallpaper.md) - KDE Plasma, per-screen native-resolution rendering
71
+ - [Use as a library](docs/library.md) - import and render PNG/PDF/SVG from Python
72
+ - [Contributing](CONTRIBUTING.md) - setup, CI, releasing
73
+
74
+ ## Limitations
75
+
76
+ - **Wallpaper-setting is host-only.** The container can't reach a Plasma session, so inside Docker the feature
77
+ self-hides and a scheduled "set wallpaper" renders the files but skips the set (logged as
78
+ `wallpaper skipped (no backend)`). Run the server/CLI on the host to set wallpapers.
79
+ - **Weekly blocks can't overlap in time.** Two activities over the same slot on the same day render on top of each
80
+ other, so only one reads clearly.
@@ -0,0 +1,14 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Please report security issues privately via GitHub's
6
+ [private vulnerability reporting](https://github.com/dannyadair/timeglance/security/advisories/new)
7
+ rather than opening a public issue.
8
+
9
+ ## Scope
10
+
11
+ timeglance renders local YAML/ICS config to SVG/PDF/PNG and, on KDE Plasma, sets the desktop
12
+ wallpaper via `kscreen-doctor`/`dbus-send`. It fetches the `.ics` URLs you configure and starts a
13
+ control-panel server bound to localhost (`0.0.0.0` only inside the container you run). It exposes no
14
+ other network service.
@@ -0,0 +1,29 @@
1
+ # The whole app is one service: the timeglance control panel.
2
+ #
3
+ # docker compose run --rm timeglance timeglance-init # first time: scaffold the configs
4
+ # docker compose up # open http://localhost:8753
5
+ #
6
+ # Config and output are bind-mounted, so the UI (and the scheduler) edit files on your
7
+ # host and results appear in ./*/out. One-shot CLI renders still work, e.g.
8
+ # docker compose run --rm timeglance timeglance-year
9
+ # docker compose run --rm timeglance timeglance-weekly --format both
10
+ #
11
+ # Wallpaper-setting is host-only (needs a Plasma session); it is auto-hidden in the UI here.
12
+
13
+ # Output and state are written as your host user (defaults to 1000:1000); override with
14
+ # UID=$(id -u) GID=$(id -g) docker compose up
15
+ x-user: &user "${UID:-1000}:${GID:-1000}"
16
+
17
+ services:
18
+ timeglance:
19
+ build: .
20
+ command: timeglance --host 0.0.0.0
21
+ user: *user
22
+ environment:
23
+ XDG_CACHE_HOME: /tmp # writable fontconfig cache when running as a non-root uid
24
+ ports:
25
+ - "8753:8753"
26
+ volumes:
27
+ - ./year:/app/year
28
+ - ./weekly:/app/weekly
29
+ - ./state:/app/state
@@ -0,0 +1,46 @@
1
+ # Configuration
2
+
3
+ Working configs (`year/config.yaml`, `weekly/config.yaml`) are git-ignored (personal schedule / calendar
4
+ secrets); the packaged `config.yaml.in` files are annotated templates. Run `timeglance-init` to scaffold the
5
+ working configs from them. Precedence is **built-in defaults < YAML < CLI flags**, so you can comment defaults out
6
+ rather than delete them.
7
+
8
+ See the [Reference](reference.md) for the full list of flags and YAML keys.
9
+
10
+ ## Year (`year/config.yaml`)
11
+
12
+ - **`range`** - `start`/`end` accept `today`, `today±N` (months), `year-start`, `year-end`, or `YYYY-MM`.
13
+ A shorter range renders larger. Or set a `span` preset instead of `start`/`end`: `this_year`,
14
+ `upcoming_year` (rolling 12 from this month), or `custom` with `before`/`after` month counts.
15
+ - **`holidays`** - auto-populated via the [`holidays`](https://pypi.org/project/holidays/) package
16
+ (`country` + `subdiv`, e.g. `NZ` / `WGN`).
17
+ - **`sources`** - external iCalendar feeds (see below).
18
+ - **`layers`** / **`events`** - a single day uses `date:`, a span uses `start:`+`end:` (inclusive); recurrence via
19
+ `repeat: yearly|weekly|monthly` or a full `rrule:` (RFC 5545 / dateutil).
20
+
21
+ ### Calendar sources (.ics)
22
+
23
+ Let your existing calendars _be_ the editor. Any number of `.ics` feeds are merged into the event stream and mapped
24
+ to a layer:
25
+
26
+ ```yaml
27
+ sources:
28
+ - url: "https://calendar.proton.me/api/calendar/v1/url/…/calendar.ics" # a Proton share link
29
+ layer: birthdays
30
+ - path: "~/calendars/my-year.ics" # a local file
31
+ label: My Year # creates a layer if `layer:` is omitted
32
+ color: "#7A7F87"
33
+ ```
34
+
35
+ Recurrences, `EXDATE`s and all-day/timed spans are expanded within the render window. URL feeds are TTL-cached
36
+ (`refresh:` seconds) under `state/ics-cache`, and a failed refresh falls back to the last good copy. Nice trick:
37
+ keep a private "year" calendar you never show online and only export here.
38
+
39
+ > **A share link is a secret.** Proton links embed a passphrase key. Keep them in your git-ignored `config.yaml`,
40
+ > never in the tracked `config.yaml.in`.
41
+
42
+ ## Weekly (`weekly/config.yaml`)
43
+
44
+ `activities` (label + colour; `visible: false` hides one by default) and a flat list of `blocks`. Toggle any
45
+ activity in the UI (or with `--hide`/`--only`); comment out a single block to drop a one-off. `wallpaper:` tunes
46
+ the canvas `background`, `margin`, `shadow`, and `screens`.
@@ -0,0 +1,20 @@
1
+ # The control panel
2
+
3
+ Open <http://localhost:8753>. Switch **Weekly / Year**, and within each, switch the view:
4
+
5
+ - **Preview** - live render with quick controls (year: theme, layout, week start, range, size, layer toggles;
6
+ weekly: paper, activity toggles). **Export** (PNG / PDF) and **Set as wallpaper** act on the current controls.
7
+ - **Config** - the tool's full YAML in an editor. **Save** validates and writes it; **Reset to template** restores
8
+ the annotated template. This is the source of truth - anything not in the quick controls lives here.
9
+
10
+ Two shared, app-wide views sit alongside them:
11
+
12
+ - **Schedule** - a daily re-render loop: pick a time and which tools to refresh (optionally setting the wallpaper),
13
+ **Start / Stop / Run now**. With more than one screen you can assign each tool to specific outputs (e.g. weekly on
14
+ the laptop, year on the ultrawide); leave all ticked to target every screen. Runs are isolated so one failing feed
15
+ can't sink the rest.
16
+ - **Log** - a persistent activity log of every render, export and wallpaper change, scheduled or manual (live
17
+ previews aren't logged). **Clear log** wipes it (with confirmation). Schedule config and the log persist under
18
+ `state/`.
19
+
20
+ The YAML is re-read on every request, so external edits show up on reload.
Binary file
Binary file