From 6073575233a8e72a601924679f339c48a7bccdf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torjus=20H=C3=A5kestad?= Date: Tue, 3 Feb 2026 23:33:13 +0100 Subject: [PATCH] fix: support Home Manager declarations format in parser The options.json parser expected declarations as []string (NixOS format), but Home Manager uses [{name, url}] objects. This caused most HM options to be silently skipped during parsing (27 vs 4880 options). Changes: - Parse declarations as json.RawMessage and try both formats - Handle HM path format in normalizeDeclarationPath - Add /modules/ marker for HM store paths Co-Authored-By: Claude Opus 4.5 --- internal/nixos/parser.go | 63 +++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/internal/nixos/parser.go b/internal/nixos/parser.go index dee621f..9dc1019 100644 --- a/internal/nixos/parser.go +++ b/internal/nixos/parser.go @@ -40,7 +40,7 @@ type ParsedOption struct { // optionJSON is the internal structure for parsing options.json entries. type optionJSON struct { - Declarations []string `json:"declarations"` + Declarations json.RawMessage `json:"declarations"` // Can be []string or []{name, url} Default json.RawMessage `json:"default,omitempty"` Description interface{} `json:"description"` // Can be string or object Example json.RawMessage `json:"example,omitempty"` @@ -58,11 +58,8 @@ func parseOption(name string, data json.RawMessage) (*ParsedOption, error) { // Handle description which can be a string or an object with _type: "mdDoc" description := extractDescription(opt.Description) - // Convert declarations to relative paths - declarations := make([]string, 0, len(opt.Declarations)) - for _, d := range opt.Declarations { - declarations = append(declarations, normalizeDeclarationPath(d)) - } + // Parse declarations - can be []string (NixOS) or []{name, url} (Home Manager) + declarations := parseDeclarations(opt.Declarations) return &ParsedOption{ Name: name, @@ -75,6 +72,39 @@ func parseOption(name string, data json.RawMessage) (*ParsedOption, error) { }, nil } +// parseDeclarations handles both NixOS format ([]string) and Home Manager format ([]{name, url}). +func parseDeclarations(raw json.RawMessage) []string { + if len(raw) == 0 { + return nil + } + + // Try []string first (NixOS format) + var stringDecls []string + if err := json.Unmarshal(raw, &stringDecls); err == nil { + result := make([]string, 0, len(stringDecls)) + for _, d := range stringDecls { + result = append(result, normalizeDeclarationPath(d)) + } + return result + } + + // Try []{name, url} format (Home Manager format) + var objectDecls []struct { + Name string `json:"name"` + URL string `json:"url"` + } + if err := json.Unmarshal(raw, &objectDecls); err == nil { + result := make([]string, 0, len(objectDecls)) + for _, d := range objectDecls { + // Use name field, normalize the path + result = append(result, normalizeDeclarationPath(d.Name)) + } + return result + } + + return nil +} + // extractDescription extracts the description string from various formats. func extractDescription(desc interface{}) string { switch v := desc.(type) { @@ -93,16 +123,29 @@ func extractDescription(desc interface{}) string { return "" } -// normalizeDeclarationPath converts a full store path to a relative nixpkgs path. -// Input: "/nix/store/xxx-source/nixos/modules/services/web-servers/nginx/default.nix" -// Output: "nixos/modules/services/web-servers/nginx/default.nix" +// normalizeDeclarationPath converts a full store path to a relative path. +// NixOS input: "/nix/store/xxx-source/nixos/modules/services/web-servers/nginx/default.nix" +// NixOS output: "nixos/modules/services/web-servers/nginx/default.nix" +// HM input: "" +// HM output: "modules/programs/git.nix" func normalizeDeclarationPath(path string) string { - // Look for common prefixes and strip them + // Handle Home Manager format: or + if len(path) > 2 && path[0] == '<' && path[len(path)-1] == '>' { + inner := path[1 : len(path)-1] + // Strip the prefix (home-manager/, nixpkgs/, etc.) + if idx := findSubstring(inner, "/"); idx >= 0 { + return inner[idx+1:] + } + return inner + } + + // Look for common prefixes and strip them (NixOS store paths) markers := []string{ "/nixos/", "/pkgs/", "/lib/", "/maintainers/", + "/modules/", // For home-manager paths } for _, marker := range markers {