How to override service espanso module for home-manager

Hi,

This is my first attempt to modify a home manager module and I could not find a tutorial around how to do it.

What I want to do is to override the home manager espanso service module

The reason is that it does not support configurations.

As we can see from official doc

The folder structure should look like below.

config/
  default.yml
match/
  base.yml

Because of this problem, when I enable espanso service, I am getting below error.

I am thinking to download originl file, add configs under settings and output two yamls into two different folder. To verify that I need a way to override the home manager module to use my local one. Can anyone please advise how I can override this module?

Thank you

1 Like

I do not have this issue but maybe this:

{ config, ...}:
{
  config.xdg.configFile = {
    "espanso/config/default.yml".source = xdg.configFile."espanso/default.yml".source;
    "espanso/match/base.yml".source     = config.services.espanso.settings.matches
  };
}

edit: now I know what match expects

I am making some progress by putting below on home.nix file.

disabledModules = ["services/espanso.nix"];

Then I can import my local espanso.nix to home manager as

    homeConfigurations = {
      <user> = home-manager.lib.homeManagerConfiguration {
        inherit pkgs;
        modules = [
          ./home.nix
          ./modules/espanso.nix
          {
            nixpkgs.overlays = [
              nur.overlay
            ];
          }
        ];
      };
    };

Now I need to figure out how to write options to create dynamic files under espanso/config folder.

For example nix should generate vscode.yml and vscodium.yml dynamically and put them under the config folder as below.

config/
  vscode.yml
  vscodium.yml

It would be really great to have an updated espanso module in home-manager as well. Are you planning to upstream your module?

For the separate espanso files, you can use functions like mapAttrs’ to convert configs to file attrsets. For instance:

config.xdg.configFile =
let
  espansoConfigs = {
    default = { ... };
    vscode = { ... };
  };
in
lib.mapAttrs' (name: value: {
  name = "espanso/config/${name}.yml";
  value = {
    source = yaml.generate "${name}.yaml" value;
  };
}) espansoConfigs;

Thanks for the reply.

If I could figure out a solution which is compatible with existing user, I might upstream the module…

Unfortunately, given my limited Nix knowledge, this might take a while.

What I am thinking now is very simple. To make this compatible for existing user, we might just add configs under settings like below.

  options = {
    services.espanso = {
      enable = mkEnableOption "Espanso: cross platform text expander in Rust";

      package = mkOption {
        type = types.package;
        description = "Which espanso package to use";
        default = pkgs.espanso;
        defaultText = literalExpression "pkgs.espanso";
      };
      settings = mkOption {
        type = yaml.type;
        default = {
          configs = [];
          matches = [];
        };
      };
    };
  };

When user have configs like below on for their home.espanso.settings , the module should generate 4 yaml files under ~/.config/espanso/config folder.

configs = [
  default = {};
  vscode = {};
  vscodium = {};
  abc = {};
]

Not too sure how to write it though. :sweat_smile:

It could look something like:

config.xdg.configFile = lib.mapAttrs' (name: value: {
  name = "espanso/config/${name}.yml";
  value = {
    source = yaml.generate "${name}.yaml" value;
  };
}) config.services.espanso.settings.configs;

I’d personally make settings split into 2 options: configs and matches. Otherwise you can do something like:

services.espanso.settings.somethingelse = 3;

And have no idea what it should be doing :sweat_smile:

      configs = mkOption {
        type = types.attrsOf yaml.type;
        default = {};
      };

I’m not entirely sure whether matches needs to be in separate files. I think it’s fine to put all matches in a single file:

      matches = mkOption {
        type = yaml.type;
        default = {};
      };

Combining matches and config files can be done like:

config.xdg.configFile =
  let
    configFiles = lib.mapAttrs' (name: value: {
      name = "espanso/config/${name}.yml";
      value = {
        source = yaml.generate "${name}.yaml" value;
      };
    }) config.services.espanso.configs;
    matchesFiles = {
      "espanso/matches/base.yml".source = yaml.generate "base.yaml" config.services.espanso.matches;
    };
  in configFiles // matchesFiles;
1 Like

Thanks for the detail, this certainly generates the file at the desire location.

I thought about splitting matches and configs before but this will be a breaking change. Maybe I should use something like mkIf (cfg.settings != { }) to make the new module compatible with current user.

Now the final issues I run into.

I can’t find the right combination to generate double quote.

For example, below config

    matches = [
      {
        trigger = ":hello";
        replace = ''world'';
      }
    ];

is generated to below on match/base.yaml which does not work…

- replace: world
  trigger: :hello

On the example, I need to add double quotes around world and :hello.

It becomes more complicated if I need multi-line support like below.

  - trigger: "include newlines"
    replace: |
              exactly as you see
              will appear these three
              lines of poetry

I know I can use \n to do it, but it also requires double quotes around the string.

I searched a lot but I could not find a good example on how to manipulate this yaml.generate process.

The problem isn’t quotation. Quotation is not required for these cases, Nix yaml generation only outputs those if they’re actually needed for yaml. Same for multiline strings. Basically: don’t worry about the yaml formatting.

The problem I think is that the yaml files doesn’t include matches:.

In your example you can add it using:

    matches.matches = [
      {
        trigger = ":hello";
        replace = ''world'';
      }
    ];

It looks a bit weird, but the matches/base.yaml may include more sections. See Organizing matches | Espanso

The current espanso module is already incompatible with espanso 2.x. It would be already fine to add an assertion/rename notice in the module about services.espanso.settings. That way the configuration of users will fail with an descriptive error about settings being deprecated and configs/matches should be used.

It could be backwards compatible, but I’m not sure how maintainable it is going forward in the future.

1 Like

Thank you so much for all the guidance. Now I have a working module.

I agree I might be overthinking the backward compatibility issue as the current config is already broken.

I will do a bit more testing to cover more use cases first and then upstream the module when I am satisfied.

1 Like

Awesome :+1: could you link the PR here if/when you have one?

Here is the PR.

https://github.com/nix-community/home-manager/pull/4066

I would like to give credit to you. Can I add your id as maintainer on the module or co-auther on the PR? (I am not familiar with this co-author, maintainer thing so I figured I should ask first)

Thank you.

1 Like

Sure :+1: no problem. I made a small suggestion, but in general it looks like a good or! Thanks for submitting!