package nixfig import ( "bytes" "encoding/json" "errors" "fmt" "os" "os/exec" "strings" ) var ( Nix string // Nix is the command to call for nix Fmt []string // Fmt is the command (and args) to call to format the nix output ErrNixNotFound = errors.New("nix was not found or set. You can set it either with `nixfig.Nix` or the `NIXFIG_NIX` environment variable") ErrFmtNotFound = errors.New("nix formatter was not found or set. You can set it either with `nixfig.Fmt` or the `NIXFIG_FMT` environment variable") ) func init() { nixPath, _ := exec.LookPath("nix") Nix = nixPath if envPath, ok := os.LookupEnv("NIXFIG_NIX"); ok { Nix = envPath } for _, formatter := range []string{"alejandra", "nixfmt-rfc-style", "nixfmt"} { fmtPath, _ := exec.LookPath(formatter) if fmtPath != "" { Fmt = []string{fmtPath, "--quiet"} break } } if envPath, ok := os.LookupEnv("NIXFIG_FMT"); ok { Fmt = strings.Split(envPath, " ") if envPath == "" { Fmt = nil } } } // Unmarshal unmarshals a nix expression as JSON into a struct func Unmarshal(data []byte, v any) error { if Nix == "" { return ErrNixNotFound } var stdout, stderr bytes.Buffer cmd := exec.Command(Nix, "eval", "--json", "--expr", string(data)) cmd.Stdout = &stdout cmd.Stderr = &stderr if err := cmd.Run(); err != nil { return fmt.Errorf("could not run %q: %w", Nix, err) } if stderr.Len() > 0 { return errors.New(stderr.String()) } if err := json.Unmarshal(stdout.Bytes(), v); err != nil { return fmt.Errorf("could not unmarshal nix config as JSON: %w", err) } return nil } // Marshal marshals a struct into a nix expression func Marshal(v any) ([]byte, error) { if Nix == "" { return nil, ErrNixNotFound } data, err := json.Marshal(v) if err != nil { return nil, fmt.Errorf("could not marshal JSON: %w", err) } var stdout, stderr bytes.Buffer cmd := exec.Command(Nix, "eval", "--expr", fmt.Sprintf(`(builtins.fromJSON %q)`, string(data))) cmd.Stdout = &stdout cmd.Stderr = &stderr if err := cmd.Run(); err != nil { return nil, fmt.Errorf("could not run %q: %w", Nix, err) } if stderr.Len() > 0 { return nil, errors.New(stderr.String()) } return stdout.Bytes(), nil } // MarshalFormat marshals a struct into a nix expression and formats it with Fmt func MarshalFormat(v any) ([]byte, error) { if Fmt == nil { return nil, ErrFmtNotFound } data, err := Marshal(v) if err != nil { return nil, err } var stdout, stderr bytes.Buffer cmd := exec.Command(Fmt[0], Fmt[1:]...) cmd.Stdin = bytes.NewBuffer(data) cmd.Stdout = &stdout cmd.Stderr = &stderr if err := cmd.Run(); err != nil { return nil, fmt.Errorf("could not run %v: %w", Fmt, err) } if stderr.Len() > 0 { return nil, errors.New(stderr.String()) } return stdout.Bytes(), nil }