diff --git a/calmerge/__init__.py b/calmerge/__init__.py index 4ae5bdf..e24a657 100644 --- a/calmerge/__init__.py +++ b/calmerge/__init__.py @@ -11,7 +11,7 @@ def get_aiohttp_app(config: Config) -> web.Application: app.add_routes( [ web.get("/.health/", views.healthcheck), - web.get("/{name}.ics", views.calendar), + web.get("/{slug}.ics", views.calendar), ] ) diff --git a/calmerge/__main__.py b/calmerge/__main__.py index 8f4b071..972b6b9 100644 --- a/calmerge/__main__.py +++ b/calmerge/__main__.py @@ -46,7 +46,7 @@ def write_calendars(args: argparse.Namespace) -> None: config = Config.from_file(args.config) for calendar_config in config.calendars: - output_file = output_dir / f"{calendar_config.name}.ics" + output_file = output_dir / f"{calendar_config.slug}.ics" print("Saving", output_file) write_calendar(calendar_config, output_file) diff --git a/calmerge/calendars.py b/calmerge/calendars.py index 82f4892..42d52ac 100644 --- a/calmerge/calendars.py +++ b/calmerge/calendars.py @@ -76,3 +76,17 @@ def create_offset_calendar_events( for component in new_components: calendar.add_component(component) + + +def set_calendar_metadata( + calendar: icalendar.Calendar, calendar_config: CalendarConfig +) -> None: + """ + Mutate a calendar to set metadata based on config + """ + + if calendar_config.name: + calendar.add("X-WR-CALNAME", calendar_config.name) + + if calendar_config.description: + calendar.add("X-WR-CALDESC", calendar_config.description) diff --git a/calmerge/config.py b/calmerge/config.py index 77e8926..b19505f 100644 --- a/calmerge/config.py +++ b/calmerge/config.py @@ -34,7 +34,9 @@ def validate_header(self, auth_header: str) -> bool: class CalendarConfig(BaseModel): - name: str + slug: str + name: str | None = None + description: str | None = None urls: list[HttpUrl] offset_days: list[int] = Field(default_factory=list) auth: AuthConfig | None = None @@ -82,7 +84,7 @@ def from_file(cls, path: Path) -> "Config": with path.open(mode="rb") as f: return Config.model_validate(tomllib.load(f)) - def get_calendar_by_name(self, name: str) -> CalendarConfig | None: + def get_calendar_by_slug(self, slug: str) -> CalendarConfig | None: return next( - (calendar for calendar in self.calendars if calendar.name == name), None + (calendar for calendar in self.calendars if calendar.slug == slug), None ) diff --git a/calmerge/static.py b/calmerge/static.py index 8a1b020..3e6e0ea 100644 --- a/calmerge/static.py +++ b/calmerge/static.py @@ -1,7 +1,11 @@ import asyncio from pathlib import Path -from .calendars import create_offset_calendar_events, fetch_merged_calendar +from .calendars import ( + create_offset_calendar_events, + fetch_merged_calendar, + set_calendar_metadata, +) from .config import CalendarConfig @@ -11,4 +15,6 @@ def write_calendar(calendar_config: CalendarConfig, output_file: Path) -> None: if offset_days := calendar_config.offset_days: create_offset_calendar_events(merged_calendar, offset_days) + set_calendar_metadata(merged_calendar, calendar_config) + output_file.write_bytes(merged_calendar.to_ical()) diff --git a/calmerge/views.py b/calmerge/views.py index 5dcae1c..7788cac 100644 --- a/calmerge/views.py +++ b/calmerge/views.py @@ -1,6 +1,10 @@ from aiohttp import web -from .calendars import create_offset_calendar_events, fetch_merged_calendar +from .calendars import ( + create_offset_calendar_events, + fetch_merged_calendar, + set_calendar_metadata, +) async def healthcheck(request: web.Request) -> web.Response: @@ -10,7 +14,7 @@ async def healthcheck(request: web.Request) -> web.Response: async def calendar(request: web.Request) -> web.Response: config = request.app["config"] - calendar_config = config.get_calendar_by_name(request.match_info["name"]) + calendar_config = config.get_calendar_by_slug(request.match_info["slug"]) if calendar_config is None: raise web.HTTPNotFound() @@ -25,4 +29,6 @@ async def calendar(request: web.Request) -> web.Response: if offset_days := calendar_config.offset_days: create_offset_calendar_events(calendar, offset_days) + set_calendar_metadata(calendar, calendar_config) + return web.Response(body=calendar.to_ical()) diff --git a/tests/calendars.toml b/tests/calendars.toml index 25d493c..d4dab84 100644 --- a/tests/calendars.toml +++ b/tests/calendars.toml @@ -1,18 +1,24 @@ [[calendar]] -name = "python" +slug = "python" +name = "Python" +description = "Python EOL" urls = [ "https://endoflife.date/calendar/python.ics", ] [[calendar]] -name = "python-offset" +slug = "python-offset" +name = "Python (with offset)" +description = "Python EOL (with offset)" urls = [ "https://endoflife.date/calendar/python.ics", ] offset_days = [365] [[calendar]] -name = "python-authed" +slug = "python-authed" +name = "Python (with auth)" +description = "Python EOL (with auth)" urls = [ "https://endoflife.date/calendar/python.ics", ] diff --git a/tests/test_calendar_view.py b/tests/test_calendar_view.py index 7d6d2e5..56f2e61 100644 --- a/tests/test_calendar_view.py +++ b/tests/test_calendar_view.py @@ -12,6 +12,9 @@ async def test_retrieves_calendars(client: TestClient) -> None: calendar = icalendar.Calendar.from_ical(await response.text()) assert not calendar.is_broken + assert calendar["X-WR-CALNAME"] == "Python" + assert calendar["X-WR-CALDESC"] == "Python EOL" + async def test_unknown_calendar(client: TestClient) -> None: response = await client.get("/unknown.ics") diff --git a/tests/test_config.py b/tests/test_config.py index 2324c41..c164378 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -12,7 +12,7 @@ def test_non_unique_urls() -> None: with pytest.raises(ValidationError) as e: - CalendarConfig(name="test", urls=["https://example.com"] * 10) # type: ignore [list-item] + CalendarConfig(slug="test", urls=["https://example.com"] * 10) # type: ignore [list-item] assert e.value.errors()[0]["msg"] == "URLs must be unique" @@ -20,7 +20,7 @@ def test_non_unique_urls() -> None: def test_non_unique_offset_days() -> None: with pytest.raises(ValidationError) as e: CalendarConfig( - name="test", + slug="test", urls=["https://example.com"], # type: ignore [list-item] offset_days=[1, 2, 3, 2, 1], ) @@ -31,7 +31,7 @@ def test_non_unique_offset_days() -> None: def test_invalid_offset_days() -> None: with pytest.raises(ValidationError) as e: CalendarConfig( - name="test", + slug="test", urls=["https://example.com"], # type: ignore [list-item] offset_days=[MAX_OFFSET + 1], ) @@ -46,7 +46,7 @@ def test_urls_expand_env_var(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("FOO", "BAR") calendar_config = CalendarConfig( - name="test", + slug="test", urls=["https://example.com/$FOO"], # type: ignore [list-item] ) diff --git a/tests/test_write.py b/tests/test_write.py index 4482612..e3e780a 100644 --- a/tests/test_write.py +++ b/tests/test_write.py @@ -20,4 +20,4 @@ def test_write_config(tmp_path: Path, config: Config, config_path: Path) -> None assert len(list(tmp_path.glob("*.ics"))) == len(config.calendars) for calendar_config in config.calendars: - assert tmp_path.joinpath(f"{calendar_config.name}.ics").is_file() + assert tmp_path.joinpath(f"{calendar_config.slug}.ics").is_file()