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