From 8eb8c212e50e2fd95af5849585a2eb819add0a1e Mon Sep 17 00:00:00 2001
From: Anton Mosich <anton@mosich.at>
Date: Sat, 12 Aug 2023 22:07:04 +0200
Subject: [PATCH] qcal: add module

---
 modules/misc/news.nix                         |  7 ++
 modules/modules.nix                           |  1 +
 modules/programs/qcal.nix                     | 66 +++++++++++++++++++
 tests/default.nix                             |  1 +
 tests/modules/programs/qcal/default.nix       |  5 ++
 .../programs/qcal/http-calendar.json-expected |  9 +++
 tests/modules/programs/qcal/http-calendar.nix | 18 +++++
 .../modules/programs/qcal/mixed.json-expected | 14 ++++
 tests/modules/programs/qcal/mixed.nix         | 28 ++++++++
 .../qcal/webdav-calendar.json-expected        | 11 ++++
 .../modules/programs/qcal/webdav-calendar.nix | 26 ++++++++
 11 files changed, 186 insertions(+)
 create mode 100644 modules/programs/qcal.nix
 create mode 100644 tests/modules/programs/qcal/default.nix
 create mode 100644 tests/modules/programs/qcal/http-calendar.json-expected
 create mode 100644 tests/modules/programs/qcal/http-calendar.nix
 create mode 100644 tests/modules/programs/qcal/mixed.json-expected
 create mode 100644 tests/modules/programs/qcal/mixed.nix
 create mode 100644 tests/modules/programs/qcal/webdav-calendar.json-expected
 create mode 100644 tests/modules/programs/qcal/webdav-calendar.nix

diff --git a/modules/misc/news.nix b/modules/misc/news.nix
index c27ba8b4d..dfa9b81fa 100644
--- a/modules/misc/news.nix
+++ b/modules/misc/news.nix
@@ -1193,6 +1193,13 @@ in
           A new module is available: 'programs.pqiv'.
         '';
       }
+
+      {
+        time = "2023-08-22T16:06:52+00:00";
+        message = ''
+          A new module is available: 'programs.qcal'.
+        '';
+      }
     ];
   };
 }
diff --git a/modules/modules.nix b/modules/modules.nix
index d4fc5afdc..4c82d2041 100644
--- a/modules/modules.nix
+++ b/modules/modules.nix
@@ -173,6 +173,7 @@ let
     ./programs/pubs.nix
     ./programs/pyenv.nix
     ./programs/pylint.nix
+    ./programs/qcal.nix
     ./programs/qutebrowser.nix
     ./programs/rbw.nix
     ./programs/readline.nix
diff --git a/modules/programs/qcal.nix b/modules/programs/qcal.nix
new file mode 100644
index 000000000..c70a6ea38
--- /dev/null
+++ b/modules/programs/qcal.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  cfg = config.programs.qcal;
+
+  qcalAccounts = lib.attrValues
+    (lib.filterAttrs (_: a: a.qcal.enable) config.accounts.calendar.accounts);
+
+  rename = oldname:
+    builtins.getAttr oldname {
+      url = "Url";
+      userName = "Username";
+      passwordCommand = "PasswordCmd";
+    };
+
+  filteredAccounts = let
+    mkAccount = account:
+      lib.filterAttrs (_: v: v != null) (with account.remote; {
+        Url = url;
+        Username = if userName == null then null else userName;
+        PasswordCmd =
+          if passwordCommand == null then null else toString passwordCommand;
+      });
+  in map mkAccount qcalAccounts;
+
+in {
+  meta.maintainers = with lib.maintainers; [ antonmosich ];
+
+  options = {
+    programs.qcal = {
+      enable = lib.mkEnableOption "qcal, a CLI calendar application";
+
+      timezone = lib.mkOption {
+        type = lib.types.singleLineStr;
+        default = "Local";
+        example = "Europe/Vienna";
+        description = "Timezone to display calendar entries in";
+      };
+
+      defaultNumDays = lib.mkOption {
+        type = lib.types.ints.positive;
+        default = 30;
+        description = "Default number of days to show calendar entries for";
+      };
+    };
+
+    accounts.calendar.accounts = lib.mkOption {
+      type = with lib.types;
+        attrsOf
+        (submodule { options.qcal.enable = lib.mkEnableOption "qcal access"; });
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    home.packages = [ pkgs.qcal ];
+
+    xdg.configFile."qcal/config.json".source =
+      let jsonFormat = pkgs.formats.json { };
+      in jsonFormat.generate "qcal.json" {
+        DefaultNumDays = cfg.defaultNumDays;
+        Timezone = cfg.timezone;
+        Calendars = filteredAccounts;
+      };
+  };
+}
diff --git a/tests/default.nix b/tests/default.nix
index 615146e02..b407f5fb4 100644
--- a/tests/default.nix
+++ b/tests/default.nix
@@ -120,6 +120,7 @@ import nmt {
     ./modules/programs/powerline-go
     ./modules/programs/pubs
     ./modules/programs/pyenv
+    ./modules/programs/qcal
     ./modules/programs/qutebrowser
     ./modules/programs/readline
     ./modules/programs/ripgrep
diff --git a/tests/modules/programs/qcal/default.nix b/tests/modules/programs/qcal/default.nix
new file mode 100644
index 000000000..3a177e364
--- /dev/null
+++ b/tests/modules/programs/qcal/default.nix
@@ -0,0 +1,5 @@
+{
+  qcal-http = ./http-calendar.nix;
+  qcal-webdav = ./webdav-calendar.nix;
+  qcal-mixed = ./mixed.nix;
+}
diff --git a/tests/modules/programs/qcal/http-calendar.json-expected b/tests/modules/programs/qcal/http-calendar.json-expected
new file mode 100644
index 000000000..7b39cdf9a
--- /dev/null
+++ b/tests/modules/programs/qcal/http-calendar.json-expected
@@ -0,0 +1,9 @@
+{
+  "Calendars": [
+    {
+      "Url": "https://example.com/events.ical"
+    }
+  ],
+  "DefaultNumDays": 30,
+  "Timezone": "Local"
+}
diff --git a/tests/modules/programs/qcal/http-calendar.nix b/tests/modules/programs/qcal/http-calendar.nix
new file mode 100644
index 000000000..c32440601
--- /dev/null
+++ b/tests/modules/programs/qcal/http-calendar.nix
@@ -0,0 +1,18 @@
+{ pkgs, lib, ... }:
+
+{
+  programs.qcal.enable = true;
+  accounts.calendar.accounts.test = {
+    qcal.enable = true;
+    remote = { url = "https://example.com/events.ical"; };
+  };
+
+  test.stubs = { qcal = { }; };
+
+  nmt.script = ''
+    assertFileExists home-files/.config/qcal/config.json
+    assertFileContent home-files/.config/qcal/config.json ${
+      ./http-calendar.json-expected
+    }
+  '';
+}
diff --git a/tests/modules/programs/qcal/mixed.json-expected b/tests/modules/programs/qcal/mixed.json-expected
new file mode 100644
index 000000000..6546efda3
--- /dev/null
+++ b/tests/modules/programs/qcal/mixed.json-expected
@@ -0,0 +1,14 @@
+{
+  "Calendars": [
+    {
+      "Url": "https://example.com/events.ical"
+    },
+    {
+      "PasswordCmd": "pass show calendar",
+      "Url": "https://cal.example.com/anton/work",
+      "Username": "anton"
+    }
+  ],
+  "DefaultNumDays": 30,
+  "Timezone": "Local"
+}
diff --git a/tests/modules/programs/qcal/mixed.nix b/tests/modules/programs/qcal/mixed.nix
new file mode 100644
index 000000000..d2241bdcb
--- /dev/null
+++ b/tests/modules/programs/qcal/mixed.nix
@@ -0,0 +1,28 @@
+{ pkgs, lib, ... }:
+
+{
+  programs.qcal.enable = true;
+  accounts.calendar.accounts = {
+    http-test = {
+      remote = { url = "https://example.com/events.ical"; };
+      qcal.enable = true;
+    };
+    webdav-test = {
+      remote = {
+        url = "https://cal.example.com/anton/work";
+        userName = "anton";
+        passwordCommand = [ "pass" "show" "calendar" ];
+      };
+      qcal.enable = true;
+    };
+  };
+
+  test.stubs = { qcal = { }; };
+
+  nmt.script = ''
+    assertFileExists home-files/.config/qcal/config.json
+    assertFileContent home-files/.config/qcal/config.json ${
+      ./mixed.json-expected
+    }
+  '';
+}
diff --git a/tests/modules/programs/qcal/webdav-calendar.json-expected b/tests/modules/programs/qcal/webdav-calendar.json-expected
new file mode 100644
index 000000000..c7426cb33
--- /dev/null
+++ b/tests/modules/programs/qcal/webdav-calendar.json-expected
@@ -0,0 +1,11 @@
+{
+  "Calendars": [
+    {
+      "PasswordCmd": "pass show calendar",
+      "Url": "https://cal.example.com/anton/work",
+      "Username": "anton"
+    }
+  ],
+  "DefaultNumDays": 23,
+  "Timezone": "Europe/Berlin"
+}
diff --git a/tests/modules/programs/qcal/webdav-calendar.nix b/tests/modules/programs/qcal/webdav-calendar.nix
new file mode 100644
index 000000000..caefe1f16
--- /dev/null
+++ b/tests/modules/programs/qcal/webdav-calendar.nix
@@ -0,0 +1,26 @@
+{ pkgs, lib, ... }:
+
+{
+  programs.qcal = {
+    enable = true;
+    defaultNumDays = 23;
+    timezone = "Europe/Berlin";
+  };
+  accounts.calendar.accounts.test = {
+    qcal.enable = true;
+    remote = {
+      url = "https://cal.example.com/anton/work";
+      userName = "anton";
+      passwordCommand = [ "pass" "show" "calendar" ];
+    };
+  };
+
+  test.stubs = { qcal = { }; };
+
+  nmt.script = ''
+    assertFileExists home-files/.config/qcal/config.json
+    assertFileContent home-files/.config/qcal/config.json ${
+      ./webdav-calendar.json-expected
+    }
+  '';
+}