Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cosmic: init #6138

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft

cosmic: init #6138

wants to merge 1 commit into from

Conversation

cjshearer
Copy link

Description

A module for storing cosmic settings in Nix.

Cosmic settings are stored in rusty object notation (RON), so the first part of this PR will focus on a generator for RON. Once we are satisfied with the ergonomics of this, I will move on to a module for the cosmic settings themselves.

Note that while this PR is a draft, there may be some extra files I add for my own sake (working notes, .vscode settings, etc.). I do not intend to merge these.

Prior art: #5695

Checklist

  • Change is backwards compatible.

  • Code formatted with ./format.

  • Code tested through nix-shell --pure tests -A run.all or nix develop --ignore-environment .#all using Flakes.

  • Test cases updated/added. See example.

  • Commit messages are formatted like

    {component}: {description}
    
    {long description}
    

    See CONTRIBUTING for more information and recent commit messages for examples.

  • If this PR adds a new module

    • Added myself as module maintainer. See example.

Maintainer CC

@atagen
Copy link

atagen commented Nov 25, 2024

You should look at https://github.com/tristanbeedell/hm-cosmic for mapping out the cosmic settings and so on, I'm not sure if it's PR'd yet but afaik it was eventually intended to be

@tristanbeedell
Copy link
Contributor

Hi @cjshearer, nice work on this module, and thanks for picking it up on your own. Sorry for this long reply.

I should've marked my involvement on that previous PR but instead my comms were mostly limited to the nixos-cosmic Matrix room. I did actually write working implementations based on @atagen's work for cosmic-bg, cosmic-input, cosmic-panel and cosmic-comp, and I just want to write this comment to reflect on that experience.

I'd intended to make a PR of my own, but the work still felt unfinished when I fell ill a few weeks back and didn't really pick it up again. Such is life.

Anyhow, the latest Ron module we had made is here: https://github.com/tristanbeedell/home-manager/blob/cosmic/modules/programs/cosmic/ron.nix, the implementations using it are in that folder, and tests are also available on that branch.


I hadn't considered using functions in the way you suggest as a hack for unsupported types, that was a tricky problem to solve.

Considering ergonomics however, in the context of a nix module, with auto-generated docs and type checking, there were definitely hurdles I had to overcome, and I'm not sure how they would play with this method.

You'll note for example, I mapped out the valid values for a keybind's action and data-type, though I know this goes against some of the contribution guidelines for increasing mainainership burden, so I added the option for free form, non type checked options as well.

The reason I did this however, is that as Ron is strictly typed, if you misname an enum, the whole file will fail to be parsed (all your keybinds break when you test the config, very annoying, especially as the only place these enums and the types they accept are are documented is in the source rust code...)

Consider how you would express in the documentation to a user how to use, for example, the action Spawn("alacritty")

The options are either

  • a string (no checking is possible): ''Spawn("alacritty")'', or with your solution f: ''Spawn("alacritty")''
  • a smart helper function that can do extra checks and mutations Actions.Spawn "alacritty"

I opted for the latter in my implementation, so If I attempt to use Actions.Spawn 2, I get an appropriate error message:

Cosmic action `Spawn` expects value: `non-empty string or package`

Likewise Ron enums are represented as lib.types.enum's in my implementation, for best user feedback.

In a module implementation, you know based on context the Rust type of a value be it enum or string, list or tuple, struct or map, so requiring the user to always explicitly state that I think could be unnecessarily confusing. However, I wasn't sure how far to take it considering, like I said, the maintenance burden (especially targeting an alpha codebase).

Your input will be very valuable here, it'd be great to work together on this. Apologies again for not making my implementation and considerations more widely available sooner.

@cjshearer
Copy link
Author

Hi @tristanbeedell, thanks for letting me know about your work and for all your feedback from your experience! I really appreciate you taking the time and reaching out outside of your normal comm channels; I'm still new-ish to Nix (< 1yr) and didn't even know about lib.types, so that's huge for me!

As soon as I saw your shortcuts example, the way it provides the available enums, I realized my approach was not very user-friendly (if I'm being honest with myself, I probably realized that once I started using functions as a hack). Your points about RON's type safety and the lack of good error messages for my implementation are well taken. I'll start looking at using lib.types.enum and seeing what else is available under lib.type; I think a general purpose RON generator for home-manager may still be feasible and may even gain demand if Cosmic increases RONs popularity.

I've also started digging into your implementation and will probably move my efforts to build off of yours. If nothing else, I could submit my simplifications to @atagen's original RON serializer (without the goofy function hack). I also added handling for indentation of nested structures, which would make writing the tests nicer.

I look forward to working with you more on this!

@cjshearer
Copy link
Author

cjshearer commented Nov 30, 2024

(the following may be obvious to others, but I'm gathering my thoughts)

I've been mulling this over and I'm thinking my focus in the above comment about type-safety in a toRON generator is misplaced. In this regard, I think hm-cosmic takes the right approach, enforcing type safety at the module level through mkOption (e.g. options.programs.cosmic.input.binds and passing the result to a serializer (which doesn't care about type safety).

The only key differences in approaches are where and how the serializer is implemented (or in my case, is intended to be implemented). In hm-cosmic, the serializer is placed such that it is not intended for reuse (modules/programs/cosmic/ron.nix), where helper functions are used to create the various data-types in RON (e.g. ron.assoc). My hope is to implement this same functionality as a general-purpose library (modules/lib/generators.nix), where RON is serialized from a single function call (toRON).


As for how to encode types which don't align with nix, we could also take inspiration from toKDL by using "special" attributes. For example, KDL needs to be able to represent structures like:

extraAttrs 2 true arg1=1 arg2=false {
nested {
a 1
b null
}
}

which it does by providing _args and _props like:

extraAttrs = {
_args = [ 2 true ];
_props = {
arg1 = 1;
arg2 = false;
};
nested = {
a = 1;
b = null;
};
};

We could try doing the same thing, accepting something like a _type to distinguish between structs and maps.

@tristanbeedell
Copy link
Contributor

Sounds like some great ideas. When writing my module, I wasn't even aware of lib.generators, but yes that does seem like the better destination for the toRon serialiser. On reflection I think you are right, having a generic Ron generator primarily intended as a backend for modules, or to generate configs for applications that don't have modules providing type checking would be useful.

I agree using a _type property would be the best avenue. This was actually something I considered but never implemented, since just serialising everything inline worked just fine, except for the actions, which I had to find in a nested tree of options.


Some thoughts about how this should work:

Lists and Tuples

["a", "list"]

["a" "list"]

("a", "tuple")

{_type = "tuple"; value = ["a" "tuple"];}

Structs and Maps

{foo: "bar"}

{foo = "bar";}
# or
{_type = "struct"; foo = "bar";}

(foo: "bar")

{_type = "map"; foo = "bar";}

Enums

We should consider the different types of enums

https://github.com/ron-rs/ron/blob/master/docs/grammar.md#enum

we need to differentiate between Enumerator("many", "values"), Enumerator(["a", "list"]), and Enumerator(("a", "tuple"))

Here's a proposal:

  • the name property is the identifier
  • the value property is the content
    • a string is just put in as a quoted string
    • a list type are used for multiple values
    • a set can define named fields

Enumerator("string")

{ _type = "enum"; name = "Enumerator"; values = "string";}

Enumerator

{_type = "enum"; name = "Enumerator"; values = null;} # or values = []

Enumerator("a", "list")

{_type = "enum"; name = "Enumerator"; values = ["a" "list"]; }

Enumerator(["a", "list"])

{_type = "enum"; name = "Enumerator"; values = [["a" "list"]]; }

Enumerator(("a", "tuple"))

{_type = "enum"; name = "Enumerator"; values = [{_type = "tuple", value = ["a" "tuple"]}]; }

Enumerator(named: "field")

{_type = "enum"; name = "Enumerator"; values = {named = "field";}; }

Enumerator({nested: "struct"})

{_type = "enum"; name = "Enumerator"; values = [{nested = "struct";}]; }

Enumerator((nested: "map"))

{_type = "enum"; name = "Enumerator"; values = [{_type = "map"; nested = "map";}]; }

@cjshearer
Copy link
Author

I've added my WIP for the above proposal. It has a failing test for nested enums, but I thought I should gather some feedback.

$ nix develop --ignore-environment .#generators-toron
warning: Git tree '/home/cjshearer/repos/home-manager' is dirty
generators-toron: FAILED
Expected home-files/toron-result.ron to be same as /nix/store/fy19n2a1vwsqb5h719nhxclxds23sg6v-toron-result.ron but were different:
--- actual
+++ expected
@@ -30,11 +30,7 @@
         boolean: true
     },
     {
-        enum_nested: Some(
-            _name: "Some",
-            _type: "enum",
-            _value: 10
-        ),
+        enum_nested: Some(Some(10)),
         option_none_explicit: None,
         option_none_implicit: None,
         option_some: Some(10)

@HeitorAugustoLN
Copy link
Member

Hey @cjshearer and everyone working on this,

Loving the progress here and the effort to make COSMIC configurations manageable through Home Manager! I wanted to share something I’ve been working on: cosmic-manager.

For the past three months, I’ve been focused on this project, starting with cosmic-ctl, a CLI tool for managing COSMIC settings. cosmic-ctl can read, write, and delete configuration entries, and can perform these operations defined in a JSON file. It’s designed to be the backend for cosmic-manager, or any other software that manages COSMIC configurations.

Once cosmic-ctl was ready, I moved on to building cosmic-manager. So far, it includes:

  • A Nix to RON conversion tool with custom types to handle RON-specific types.
  • Custom nix module types for RON types, making options type safe.
  • Custom libraries for creating libcosmic application modules that follow Nix’s RFC 042.
  • Low-level modules for writing the required configuration files.
  • Modules to reset non-declared files, ensuring configurations stay clean.
  • A module for cosmic-term, which I used to test the library.

Right now, these are the modules I’ve implemented, but there’s still more to come. I paused work on additional modules to focus on getting the custom types right, which I’ve just wrapped up.

It’s important to note that cosmic-manager is meant to stay as a separate module and isn’t designed to be upstreamed into Home Manager itself. Instead, it provides a more complete solution for managing COSMIC configurations in a way that complements, but doesn’t rely solely on, Home Manager.

Also, if this project resonates with you, feel free to contribute!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants