{ config, pkgs, lib, ... }: let anki = let fetchaddon = { id, pname, version, hash, minor ? "66", settings ? null, }: { inherit id; package = pkgs.stdenv.mkDerivation { inherit pname version; src = pkgs.fetchzip { inherit hash; url = "https://ankiweb.net/shared/download/${id}?v=2.1&p=${minor}"; stripRoot = false; # The archive is a list of files. extension = "zip"; # Overrides unsupported '.ankiaddon' }; dontConfigure = true; dontBuild = true; installPhase = '' mkdir -p $out cp -r $src/* $out '' + (if ! builtins.isNull settings then '' rm -f $out/config.json echo '${builtins.toJSON settings}' > $out/config.json '' else ""); }; }; in rec { conf = "${config.xdg.dataHome}/Anki2"; backup = "${conf}/backup"; addons = { anki-connect = fetchaddon { id = "2055492159"; pname = "anki-connect"; version = "23.10.29.0"; hash = "sha256-QsooyJ3tilJP4aKy6MLUEn0G2K5CVJH/PyKFdr4h1R8="; }; fill-the-blanks = fetchaddon { id = "1933645497"; pname = "fill-the-blanks"; version = "1.7"; hash = "sha256-zojiKxToqKfElhZjLk79OR4nfH+lfamnLXub8i3oR1Q="; settings = { enable-new-filter = false; experimental-asian-chars = false; feedback-enabled = false; feedback-ignore-accents = false; feedback-ignore-case = false; input-len-times = 62; override-type-cloze = true; }; }; review-heatmap = fetchaddon { id = "1771074083"; pname = "review-heatmap"; version = "1.0.1"; hash = "sha256-4fh9x4w8fkDJcy/TROtXolKn2EBvJeJ11ht7l945nWI="; }; }; }; zettel = pkgs.writeShellApplication { name = "zettel"; text = builtins.readFile ./config/grimoire/zettel.bash; }; z2a = pkgs.stdenv.mkDerivation rec { pname = "z2a"; version = "3.4.2"; src = pkgs.fetchFromGitHub { owner = "Pseudonium"; repo = "Obsidian_to_Anki"; rev = version; hash = "sha256-sNFztri8f1Q5Lq+shreVxRtMGYZsbE0N7mVCLfk5AQY="; }; buildInputs = with pkgs; [ (python311.withPackages (p: with p; [ # Dependences taken from # https://github.com/Pseudonium/Obsidian_to_Anki/blob/3.4.2/requirements.txt markdown #gooey # Not present in 'nixpkgs' and only relevant for GUI. # The script gracefully falls back to CLI only if not # present. ])) ]; dontConfigure = true; dontBuild = true; patches = [ ./config/grimoire/z2a.0.patch ./config/grimoire/z2a.1.patch ]; installPhase = '' install -Dm744 ./obsidian_to_anki.py $out/bin/${pname} ''; }; in { home.packages = [ pkgs.anki-bin zettel z2a ]; home.activation.setupAnki = let deployAddon = name: addon: let link = "${anki.conf}/addons21/${name}-${addon.id}"; in '' [ -L "${link}" ] && $DRY_RUN_CMD ${pkgs.coreutils}/bin/unlink "${link}" $DRY_RUN_CMD ${pkgs.coreutils}/bin/ln $VERBOSE_ARG -fs "${addon.package}" "${link}" ''; in lib.hm.dag.entryAfter ["writeBoundary"] '' $DRY_RUN_CMD mkdir $VERBOSE_ARG -p "${anki.backup}" "${config.xdg.dataHome}/z2a" ${builtins.concatStringsSep "" (lib.attrsets.mapAttrsToList deployAddon anki.addons)} ''; services.backup.paths = [ anki.backup ]; xdg.configFile."z2a/config.ini" = { target = "z2a/config.ini"; text = lib.generators.toINI {} { Defaults = { "Tag" = "z2a"; "Deck" = "default"; "CurlyCloze" = true; "GUI" = false; "Regex" = false; "ID Comments" = true; "Anki Path" = "${pkgs.anki-bin}/bin/anki"; "Anki Profile" = config.home.username; }; Syntax = { "Begin Note" = ":::: ANKI"; "End Note" = "::::"; "Begin Inline Note" = "::ANKI"; "End Inline Note" = "::"; "Target Deck Line" = "deck"; "File Tags Line" = "keywords"; "Delete Regex Note Line" = "DELETE"; "Delete Note Line" = "DELETE"; "Frozen Fields Line" = "FROZEN"; }; "Custom Regexps" = { "Basic" = ""; "Basic (and reversed card)" = ""; "Basic (optional reversed card)" = ""; "Basic (type in the answer)" = ""; "Cloze" = ""; }; Obsidian = { "Vault name" = ""; "Add file link" = false; }; }; }; }