feat: load configuration from TOML file for calendar settings
6 files changed, 130 insertions(+), 54 deletions(-)
M .envrc → .envrc
@@ -1,10 +1,8 @@ #!/usr/bin/env bash if type -P lorri &>/dev/null; then - eval "$(lorri direnv --flake .)" + eval "$(lorri direnv --flake .)" else - echo 'while direnv evaluated .envrc, could not find the command "lorri" [https://github.com/nix-community/lorri]' - use flake + echo 'while direnv evaluated .envrc, could not find the command "lorri" [https://github.com/nix-community/lorri]' + use flake fi - -dotenv
M flake.nix → flake.nix
@@ -61,10 +61,12 @@ # Uv2nix can only work with what it has, and uv.lock is missing essential metadata to perform some builds. # This is an additional overlay implementing build fixups. # See: # - https://pyproject-nix.github.io/uv2nix/FAQ.html - pyprojectOverrides = _final: _prev: { - # Implement build fixups here. - # Note that uv2nix is _not_ using Nixpkgs buildPythonPackage. - # It's using https://pyproject-nix.github.io/pyproject.nix/build.html + pyprojectOverrides = final: prev: { + toml-dataclass = prev.toml-dataclass.overrideAttrs (old: { + nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ final.resolveBuildSystem { + setuptools = [ ]; + }; + }); }; # Construct package set@@ -89,8 +91,10 @@ }; devShells = { default = pkgs.mkShell { - packages = with pkgs; [ + packages = (with pkgs; [ uv + ]) ++ [ + python ]; inputsFrom = [ self.packages.${pkgs.system}.default ]; env =@@ -119,6 +123,8 @@ cfg = config.services.mycal; pkg = cfg.venv; python = self.packages.${pkgs.system}.python; + settingsFormat = pkgs.formats.toml { }; + inherit (lib) mkEnableOption mkOption mkIf types; in {@@ -149,41 +155,56 @@ Mycal virtual environment package ''; }; - timezone = mkOption { - type = types.str; - default = "Europe/Berlin"; + settings = mkOption { + default = { }; description = '' - Default timezone for the calendar. + Additional settings for Mycal. ''; - }; - - name = mkOption { - type = types.str; - description = '' - Name of person for whom this calendar is created. - ''; - }; + type = types.submodule { + freeformType = settingsFormat.type; - calendar = mkOption { - default = { }; - type = types.submodule { options = { - file = mkOption { - type = with types; nullOr str; - default = null; - example = "/path/to/calendar/file"; + name = mkOption { + type = types.str; description = '' - Path to the calendar file. Preferred over URL. + Name of person for whom this calendar is created. ''; }; - url = mkOption { - type = with types; nullOr str; - default = null; - example = "https://example.com/calendar.ics"; + + timezone = mkOption { + type = types.str; + default = "Europe/Berlin"; description = '' - URL to the calendar file. + Default timezone for the calendar. ''; }; + + calendar = mkOption { + default = { }; + description = '' + Configuration for the calendar. Either a file or a URL must be provided. + ''; + type = types.submodule { + options = { + file = mkOption { + type = types.str; + default = ""; + example = "/path/to/calendar/file"; + description = '' + Path to the calendar file. Preferred over URL. + ''; + }; + url = mkOption { + type = types.str; + default = ""; + example = "https://example.com/calendar.ics"; + description = '' + URL to the calendar file. + ''; + }; + }; + }; + }; }; }; };@@ -194,11 +215,8 @@ systemd.services.mycal = { description = "Mycal Calendar Service"; environment = { - TZ = cfg.timezone; - NAME = cfg.name; - CALENDAR_FILE = cfg.calendar.file; - CALENDAR_URL = cfg.calendar.url; PYTHONPATH = "${python.pkgs.makePythonPath pkg.propagatedBuildInputs}:${pkg}/${python.sitePackages}"; + CONFIG_FILE = settingsFormat.generate "mycal-config.toml" cfg.settings; }; serviceConfig = {
M mycal.py → mycal.py
@@ -1,14 +1,33 @@ -import os from icalendar.cal import Calendar, Event import requests from flask import Flask from datetime import date, datetime, timedelta, time import zoneinfo +from dataclasses import dataclass +from danoan.toml_dataclass import TomlDataClassIO +from os import environ +import calendar + +@dataclass +class CalendarConfig(TomlDataClassIO): + file: str = "" + url: str = "" + +@dataclass +class Config(TomlDataClassIO): + name: str + timezone: str + calendar: CalendarConfig + +def load_config(file_path): + with open(file_path, "r") as fr: + return Config.read(fr) app = Flask(__name__) -user_name = (os.environ.get("NAME") or os.environ.get("USER") or "unknown").title() -tz = zoneinfo.ZoneInfo(os.environ.get("TZ") or "Europe/Berlin") +config = load_config(environ.get("CONFIG_FILE", "config.toml")) +user_name = config.name +tz = zoneinfo.ZoneInfo(config.timezone) def fetch_calendar(calendar_url): return requests.get(calendar_url).content@@ -18,14 +37,13 @@ with open(calendar_file, 'rb') as f: return f.read() def get_calendar(): - file = os.environ.get('CALENDAR_FILE') - if file is not None: - return read_calendar_file(file) + calendar_config = config.calendar + if calendar_config.file != "": + return read_calendar_file(calendar_config.file) else: - calendar_url = os.environ.get("CALENDAR_URL") - if calendar_url is None: - raise ValueError("CALENDAR_URL not set.") - return fetch_calendar(calendar_url) + if calendar_config.url != "": + return fetch_calendar(calendar_config.url) + raise ValueError("Calendar URL not configured.") def fixup_date(dt): if type(dt) == date:
M pyproject.toml → pyproject.toml
@@ -5,10 +5,11 @@ description = "Personal calendar transformation tool" authors = [{ name = "Alan Pearce", email = "alan@alanpearce.eu" }] requires-python = ">=3.12" dependencies = [ - "flask>=3.1.1", - "icalendar>=6.3.1", - "icalendar-compatibility>=0.1.4", - "requests>=2.32.3", + "flask>=3.1.1", + "icalendar>=6.3.1", + "icalendar-compatibility>=0.1.4", + "requests>=2.32.3", + "toml-dataclass>=0.1.0", ] [build-system]
M uv.lock → uv.lock
@@ -77,6 +77,15 @@ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] +name = "dataclasses" +version = "0.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/12/7919c5d8b9c497f9180db15ea8ead6499812ea8264a6ae18766d93c59fe5/dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97", size = 36581, upload-time = "2020-11-13T14:40:30.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ca/75fac5856ab5cfa51bbbcefa250182e50441074fdc3f803f6e76451fab43/dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf", size = 19041, upload-time = "2020-11-13T14:40:29.194Z" }, +] + +[[package]] name = "flask" version = "3.1.1" source = { registry = "https://pypi.org/simple" }@@ -189,12 +198,13 @@ [[package]] name = "mycal" version = "0.1.0" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "flask" }, { name = "icalendar" }, { name = "icalendar-compatibility" }, { name = "requests" }, + { name = "toml-dataclass" }, ] [package.metadata]@@ -203,6 +213,7 @@ { name = "flask", specifier = ">=3.1.1" }, { name = "icalendar", specifier = ">=6.3.1" }, { name = "icalendar-compatibility", specifier = ">=0.1.4" }, { name = "requests", specifier = ">=2.32.3" }, + { name = "toml-dataclass", specifier = ">=0.1.0" }, ] [[package]]@@ -239,6 +250,35 @@ source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + +[[package]] +name = "toml-dataclass" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dataclasses" }, + { name = "toml" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/d9/77574c24d3aef0727a483c5dc3b5e66e8991f7c5e22b073ed4004658e0ba/toml-dataclass-0.1.0.tar.gz", hash = "sha256:64687a14e168c8b3100ed61589e0e1bd7d7f3bae0f7793053cd14e2876b0488a", size = 5599, upload-time = "2024-03-10T14:36:18.639Z" } + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, ] [[package]]