# SPDX-FileCopyrightText: 2025 Ethan Reece # # SPDX-License-Identifier: LGPL-3.0-or-later { module-name, ... }: { flake-parts-lib, lib, ... }: { options.perSystem = flake-parts-lib.mkPerSystemOption ( { pkgs, config, ... }: let valePackages = rec { # SPDX-SnippetBegin # SPDX-License-Identifier: MIT # SPDX-SnippetCopyrightText: 2003-2025 Eelco Dolstra and the Nixpkgs/NixOS contributors buildStyle = { name, stylePath ? name, ... }@args: pkgs.stdenvNoCC.mkDerivation ( { pname = "vale-style-${lib.toLower name}"; dontConfigure = true; dontBuild = true; doCheck = false; dontFixup = true; installPhase = '' runHook preInstall mkdir -p $out/share/vale/styles cp -R ${stylePath} "$out/share/vale/styles/${name}" runHook postInstall ''; # passthru.updateScript = nix-update-script { }; # TODO: https://github.com/Mic92/nix-update meta = { platforms = lib.platforms.all; maintainers = with lib.maintainers; [ katexochen ]; } // (args.meta or { }); } // removeAttrs args [ "meta" "name" ] ); # SPDX-SnippetEnd # https://vale.sh/explorer packages = { Google = pkgs.valeStyles.google; Joblint = pkgs.valeStyles.joblint; Microsoft = pkgs.valeStyles.microsoft; proselint = pkgs.valeStyles.proselint; write-good = pkgs.valeStyles.write-good; alex = pkgs.valeStyles.alex; Readability = pkgs.valeStyles.readability; RedHat = buildStyle rec { name = "RedHat"; version = "608"; stylePath = ".vale/styles/${name}"; src = pkgs.fetchFromGitHub { owner = "redhat-documentation"; repo = "vale-at-red-hat"; rev = "v${version}"; hash = "sha256-jAAWb0QHhk7UyfqKa9tUByyG11W/hlHESgywqinFiT4="; }; meta = { description = "Vale-compatible implementation of the Red Hat Style Guide"; homepage = "https://github.com/vale-at-red-hat/redhat-documentation"; license = lib.licenses.mit; }; }; }; }; in { options.languages.${module-name} = { enable = lib.mkEnableOption "en_US"; dictionary = lib.mkOption { description = '' Words to add to dictionary. ''; type = lib.types.listOf lib.types.string; default = [ ]; }; excludes = lib.mkOption { description = '' Files to exclude from `typos`. ''; type = lib.types.listOf lib.types.string; default = [ ]; }; vale = lib.mkOption { description = '' Per-glob options for Vale. ''; default = { }; type = lib.types.attrsOf ( lib.types.submodule { options = { packages = lib.mkOption { description = '' Vale packages to enable. ''; default = { }; type = lib.types.submodule { options = builtins.mapAttrs ( name: value: lib.mkOption { description = '' Options for ${name} Vale package. ''; default = { }; type = lib.types.submodule { options = { enable = lib.mkEnableOption name; }; }; } ) valePackages.packages; }; }; }; } ); }; }; config = lib.mkIf config.languages.${module-name}.enable ( let tomlFormatter = pkgs.formats.toml { }; vale = { dictionary = rec { name = "accept.txt"; text = builtins.concatStringsSep "\n" config.languages.${module-name}.dictionary; path = pkgs.writeTextFile { inherit name text; }; }; styles = builtins.mapAttrs (name: value: rec { enabled = builtins.attrNames (lib.filterAttrs (_: pkg: pkg.enable) value.packages); text = '' [${name}] BasedOnStyles = Vale${ lib.optionalString (enabled != [ ]) (", " + lib.concatStringsSep ", " enabled) } ''; }) config.languages.${module-name}.vale; config = rec { name = ".vale.ini"; text = '' StylesPath = styles MinAlertLevel = suggestion Vocab = General Packages = ${builtins.concatStringsSep ", " (builtins.attrNames valePackages.packages)} ${lib.foldlAttrs ( acc: _name: value: acc + value.text ) "" vale.styles} ''; path = pkgs.writeTextFile { inherit name text; }; }; directory = { name = "share/vale"; path = pkgs.buildEnv { name = "vale-config"; paths = [ (pkgs.linkFarm vale.directory.name [ { name = "${vale.directory.name}/${vale.config.name}"; path = vale.config.path; } { name = "${vale.directory.name}/styles/config/vocabularies/General/${vale.dictionary.name}"; path = vale.dictionary.path; } ]) ] ++ (lib.mapAttrsToList (name: value: value) valePackages.packages); }; }; }; typos = rec { # https://github.com/crate-ci/typos/blob/master/docs/reference.md content = { default = { extend-words = builtins.listToAttrs ( builtins.map (x: { name = x; value = x; }) config.languages.${module-name}.dictionary ); extend-ignore-re = [ "(?Rm)^.*(#|//)\\s*spellchecker:disable-line$" "(?s)(#|//)\\s*spellchecker:off.*?\\n\\s*(#|//)\\s*spellchecker:on" "(#|//)\\s*spellchecker:ignore-next-line\\n.*" "(?Rm)^.*.*$" "(?s).*?" "\\n.*" ]; }; files.extend-exclude = config.languages.${module-name}.excludes; }; file = tomlFormatter.generate "typos.toml" content; }; harper = { dictionary = rec { name = "dictionary.txt"; text = builtins.concatStringsSep "\n" config.languages.${module-name}.dictionary; path = pkgs.writeTextFile { inherit name text; }; }; }; in { languages.${module-name}.dictionary = [ "aarch64" "Asahi" "Catppuccin" "Devshell" "Dolstra" "Eelco" "FileCopyrightText" "Flake" "formatters" "https" "GmbH" "Jujutsu" "LanguageTool" "LLMs" "LTeX" "Nix" "NixOS" "Nixpkgs" "Nushell" "qutebrowser" "REUSE" "SnippetBegin" "SnippetCopyrightText" "SnippetEnd" "SOPS" "SPDX" "spellchecker" "Stylix" "TODO" ]; editors.helix.languages.language-server = { # https://ltex-plus.github.io/ltex-plus/supported-languages.html ltex-ls-plus = { command = "${pkgs.ltex-ls-plus}/bin/ltex-ls-plus"; config.ltex = { dictionary.en-US = config.languages.${module-name}.dictionary; additionalRules.enablePickyRules = true; }; }; # https://vale.sh/docs/formats/code vale-ls = { command = "${pkgs.vale-ls}/bin/vale-ls"; config.configPath = "${vale.directory.path}/${vale.directory.name}/${vale.config.name}"; }; # https://writewithharper.com/docs/integrations/language-server#Supported-Languages harper-ls = { command = "${pkgs.harper}/bin/harper-ls"; config.harper-ls.userDictPath = harper.dictionary.path; }; typos = { command = "${pkgs.typos-lsp}/bin/typos-lsp"; config.config = typos.file.outPath; }; }; pre-commit.settings = { hooks = { typos = { enable = true; settings = { configPath = typos.file.outPath; locale = "en-us"; }; excludes = config.languages.${module-name}.excludes; }; vale = { enable = true; settings.configPath = "${vale.directory.path}/${vale.directory.name}/${vale.config.name}"; }; }; excludes = [ "languages/en-us/.vale" ]; # TODO: Move this to only this flake }; treefmt = { # TODO: https://github.com/numtide/treefmt/issues/596 # programs.typos = { # enable = true; # configFile = typos.file.outPath; # locale = "en-us"; # excludes = config.languages.${module-name}.excludes; # }; settings = { global.excludes = [ "languages/en-us/.vale/*" ]; # formatter.typos.options = [ "--force-exclude" ]; }; }; } ); } ); }