mirror of https://git.jolheiser.com/nixfig.git
commit
8b2170b2ec
|
@ -0,0 +1,2 @@
|
||||||
|
/nixfig
|
||||||
|
/nixfix.exe
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2023 John Olheiser
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,47 @@
|
||||||
|
# nixfig
|
||||||
|
|
||||||
|
Read a nix file as a config.
|
||||||
|
|
||||||
|
Essentially just wraps `nix eval (--json) --expr`.
|
||||||
|
|
||||||
|
|
||||||
|
Allows parsing the following:
|
||||||
|
```nix
|
||||||
|
let
|
||||||
|
user = "jolheiser";
|
||||||
|
in {
|
||||||
|
log = {
|
||||||
|
level = "warn";
|
||||||
|
# Name the log file after the user....for reasons
|
||||||
|
file = "${user}.log";
|
||||||
|
};
|
||||||
|
http = {
|
||||||
|
host = "0.0.0.0";
|
||||||
|
port = 1234;
|
||||||
|
# Make user an admin, but also make a generic admin user
|
||||||
|
admins = [user "admin"];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Into a struct like:
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
Log struct {
|
||||||
|
Level string // warn
|
||||||
|
File string // jolheiser.log
|
||||||
|
}
|
||||||
|
HTTP struct {
|
||||||
|
Host string // 0.0.0.0
|
||||||
|
Port int // 1234
|
||||||
|
Admins []string // [jolheiser admin]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It can also marshal a struct into a valid (albeit minified) nix expression.
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](LICENSE)
|
|
@ -0,0 +1,53 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"go.jolheiser.com/nixfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
func maine() error {
|
||||||
|
if len(os.Args) != 2 {
|
||||||
|
return errors.New("nixfig requires 1 argument")
|
||||||
|
}
|
||||||
|
fn := os.Args[1]
|
||||||
|
|
||||||
|
data, err := os.ReadFile(fn)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not read file %q: %w", fn, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []byte
|
||||||
|
var a any
|
||||||
|
switch ext := filepath.Ext(fn); ext {
|
||||||
|
case ".json":
|
||||||
|
if err := json.Unmarshal(data, &a); err != nil {
|
||||||
|
return fmt.Errorf("invalid JSON file: %w", err)
|
||||||
|
}
|
||||||
|
out, err = nixfig.Marshal(a)
|
||||||
|
case ".nix":
|
||||||
|
if err := nixfig.Unmarshal(data, &a); err != nil {
|
||||||
|
return fmt.Errorf("invalid nix file: %w", err)
|
||||||
|
}
|
||||||
|
out, err = json.Marshal(a)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown extension %q, must be json or nix", ext)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(string(out))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := maine(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
module go.jolheiser.com/nixfig
|
||||||
|
|
||||||
|
go 1.21.4
|
||||||
|
|
||||||
|
require github.com/alecthomas/assert/v2 v2.4.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/alecthomas/repr v0.3.0 // indirect
|
||||||
|
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,6 @@
|
||||||
|
github.com/alecthomas/assert/v2 v2.4.0 h1:/ZiZ0NnriAWPYYO+4eOjgzNELrFQLaHNr92mHSHFj9U=
|
||||||
|
github.com/alecthomas/assert/v2 v2.4.0/go.mod h1:fw5suVxB+wfYJ3291t0hRTqtGzFYdSwstnRQdaQx2DM=
|
||||||
|
github.com/alecthomas/repr v0.3.0 h1:NeYzUPfjjlqHY4KtzgKJiWd6sVq2eNUPTi34PiFGjY8=
|
||||||
|
github.com/alecthomas/repr v0.3.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
|
@ -0,0 +1,72 @@
|
||||||
|
package nixfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Nix is the command to call for nix
|
||||||
|
Nix string
|
||||||
|
ErrNixNotFound = errors.New("nix was not found or set. You can set it either with `nixfig.Nix` or the `NIXFIG_NIX` environment variable")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
nixPath, _ := exec.LookPath("nix")
|
||||||
|
Nix = nixPath
|
||||||
|
if envPath, ok := os.LookupEnv("NIXFIG_NIX"); ok {
|
||||||
|
Nix = envPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package nixfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alecthomas/assert/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Log struct {
|
||||||
|
Level string
|
||||||
|
File string
|
||||||
|
}
|
||||||
|
HTTP struct {
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
Admins []string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testCfg = Config{
|
||||||
|
Log: struct {
|
||||||
|
Level string
|
||||||
|
File string
|
||||||
|
}{
|
||||||
|
Level: "warn",
|
||||||
|
File: "jolheiser.log",
|
||||||
|
},
|
||||||
|
HTTP: struct {
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
Admins []string
|
||||||
|
}{
|
||||||
|
Host: "0.0.0.0",
|
||||||
|
Port: 1234,
|
||||||
|
Admins: []string{"jolheiser", "admin"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNixNotFound(t *testing.T) {
|
||||||
|
oldNix := Nix
|
||||||
|
Nix = ""
|
||||||
|
|
||||||
|
_, err := Marshal(nil)
|
||||||
|
assert.IsError(t, err, ErrNixNotFound)
|
||||||
|
|
||||||
|
Nix = oldNix
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshal(t *testing.T) {
|
||||||
|
data, err := os.ReadFile("testdata/config.nix")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var cfg Config
|
||||||
|
err = Unmarshal(data, &cfg)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, testCfg, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshal(t *testing.T) {
|
||||||
|
data, err := Marshal(testCfg)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var cfg Config
|
||||||
|
err = Unmarshal(data, &cfg)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, testCfg, cfg)
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
let
|
||||||
|
user = "jolheiser";
|
||||||
|
in {
|
||||||
|
log = {
|
||||||
|
level = "warn";
|
||||||
|
# Name the log file after the user....for reasons
|
||||||
|
file = "${user}.log";
|
||||||
|
};
|
||||||
|
http = {
|
||||||
|
host = "0.0.0.0";
|
||||||
|
port = 1234;
|
||||||
|
# Make user an admin, but also make a generic admin user
|
||||||
|
admins = [user "admin"];
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue