Skip to content

Commit 518e7da

Browse files
committed
[WIP] devconfig: use hujson to allow comments and trailing commas
Update the devbox.json (un)marshaling logic to make surgical changes to devbox.json using the hujson package.
1 parent a3bac39 commit 518e7da

File tree

6 files changed

+275
-198
lines changed

6 files changed

+275
-198
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ require (
3636
github.com/spf13/cobra v1.7.0
3737
github.com/spf13/pflag v1.0.5
3838
github.com/stretchr/testify v1.8.4
39+
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
3940
github.com/wk8/go-ordered-map/v2 v2.1.8
4041
github.com/zealic/go2node v0.1.0
4142
go.jetpack.io/pkg v0.0.0-20231002215645-9afeb0623fd3

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
321321
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
322322
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
323323
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
324+
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
325+
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
324326
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
325327
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
326328
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=

internal/devconfig/config.go

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
package devconfig
55

66
import (
7+
"encoding/json"
78
"io"
89
"net/http"
10+
"os"
911
"path/filepath"
1012
"regexp"
1113
"strings"
1214

1315
"github.com/pkg/errors"
16+
"github.com/tailscale/hujson"
1417
"go.jetpack.io/devbox/internal/boxcli/usererr"
1518
"go.jetpack.io/devbox/internal/cuecfg"
1619
"go.jetpack.io/devbox/internal/impl/shellcmd"
@@ -42,6 +45,8 @@ type Config struct {
4245
// plugin: for built-in plugins
4346
// This is a similar format to nix inputs
4447
Include []string `json:"include,omitempty"`
48+
49+
ast *configAST
4550
}
4651

4752
type shellConfig struct {
@@ -59,23 +64,33 @@ type Stage struct {
5964
Command string `json:"command"`
6065
}
6166

67+
const defaultJSON = `{
68+
"packages": [],
69+
"shell": {
70+
"init_hook": [
71+
"echo 'Welcome to devbox!' > /dev/null"
72+
],
73+
"scripts": {
74+
"test": [
75+
"echo \"Error: no test specified\" && exit 1"
76+
]
77+
}
78+
}
79+
}
80+
`
81+
6282
func DefaultConfig() *Config {
63-
return &Config{
64-
// initialize to empty slice instead of nil for consistent marshalling
65-
Packages: Packages{Collection: []Package{}},
66-
Shell: &shellConfig{
67-
Scripts: map[string]*shellcmd.Commands{
68-
"test": {
69-
Cmds: []string{"echo \"Error: no test specified\" && exit 1"},
70-
},
71-
},
72-
InitHook: &shellcmd.Commands{
73-
Cmds: []string{
74-
"echo 'Welcome to devbox!' > /dev/null",
75-
},
76-
},
77-
},
83+
cfg, err := LoadBytes([]byte(defaultJSON))
84+
if err != nil {
85+
panic(err)
7886
}
87+
cfg.ast.format()
88+
return cfg
89+
}
90+
91+
func (c *Config) Bytes() []byte {
92+
c.ast.format()
93+
return c.ast.root.Pack()
7994
}
8095

8196
func (c *Config) Hash() (string, error) {
@@ -114,21 +129,36 @@ func (c *Config) InitHook() *shellcmd.Commands {
114129

115130
// SaveTo writes the config to a file.
116131
func (c *Config) SaveTo(path string) error {
117-
cfgPath := filepath.Join(path, DefaultName)
118-
return cuecfg.WriteFile(cfgPath, c)
119-
}
120-
121-
func readConfig(path string) (*Config, error) {
122-
cfg := &Config{}
123-
return cfg, errors.WithStack(cuecfg.ParseFile(path, cfg))
132+
return os.WriteFile(path, c.Bytes(), 0o644)
124133
}
125134

126135
// Load reads a devbox config file, and validates it.
127136
func Load(path string) (*Config, error) {
128-
cfg, err := readConfig(path)
137+
b, err := os.ReadFile(path)
138+
if err != nil {
139+
return nil, err
140+
}
141+
return LoadBytes(b)
142+
}
143+
144+
func LoadBytes(b []byte) (*Config, error) {
145+
jsonb, err := hujson.Standardize(b)
129146
if err != nil {
130147
return nil, err
131148
}
149+
150+
ast, err := parseConfig(b)
151+
if err != nil {
152+
return nil, err
153+
}
154+
cfg := &Config{
155+
Packages: Packages{ast: ast},
156+
ast: ast,
157+
}
158+
if err := json.Unmarshal(jsonb, cfg); err != nil {
159+
println(string(jsonb))
160+
return nil, err
161+
}
132162
return cfg, validateConfig(cfg)
133163
}
134164

@@ -153,15 +183,6 @@ func LoadConfigFromURL(url string) (*Config, error) {
153183
return cfg, validateConfig(cfg)
154184
}
155185

156-
// WriteConfig saves a devbox config file.
157-
func WriteConfig(path string, cfg *Config) error {
158-
err := validateConfig(cfg)
159-
if err != nil {
160-
return err
161-
}
162-
return cuecfg.WriteFile(path, cfg)
163-
}
164-
165186
func validateConfig(cfg *Config) error {
166187
fns := []func(cfg *Config) error{
167188
ValidateNixpkg,
@@ -212,3 +233,10 @@ func ValidateNixpkg(cfg *Config) error {
212233
}
213234
return nil
214235
}
236+
237+
func joinNameVersion(name, version string) string {
238+
if version == "" {
239+
return name
240+
}
241+
return name + "@" + version
242+
}

internal/devconfig/init.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,28 @@
44
package devconfig
55

66
import (
7+
"errors"
78
"fmt"
89
"io"
10+
"os"
911
"path/filepath"
1012
"strings"
1113

1214
"github.com/fatih/color"
1315

14-
"go.jetpack.io/devbox/internal/cuecfg"
1516
"go.jetpack.io/devbox/internal/initrec"
1617
)
1718

1819
func Init(dir string, writer io.Writer) (created bool, err error) {
19-
cfgPath := filepath.Join(dir, DefaultName)
20-
21-
config := DefaultConfig()
20+
created, err = initConfigFile(filepath.Join(dir, DefaultName))
21+
if err != nil || !created {
22+
return created, err
23+
}
2224

2325
// package suggestion
2426
pkgsToSuggest, err := initrec.Get(dir)
2527
if err != nil {
26-
return false, err
28+
return created, err
2729
}
2830
if len(pkgsToSuggest) > 0 {
2931
s := fmt.Sprintf("devbox add %s", strings.Join(pkgsToSuggest, " "))
@@ -33,6 +35,30 @@ func Init(dir string, writer io.Writer) (created bool, err error) {
3335
color.HiYellowString(s),
3436
)
3537
}
38+
return created, err
39+
}
40+
41+
func initConfigFile(path string) (created bool, err error) {
42+
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o644)
43+
if errors.Is(err, os.ErrExist) {
44+
return false, nil
45+
}
46+
if err != nil {
47+
return false, err
48+
}
49+
defer func() {
50+
if err != nil {
51+
os.Remove(f.Name())
52+
}
53+
}()
3654

37-
return cuecfg.InitFile(cfgPath, config)
55+
_, err = f.Write(DefaultConfig().Bytes())
56+
if err != nil {
57+
f.Close()
58+
return false, err
59+
}
60+
if err := f.Close(); err != nil {
61+
return false, err
62+
}
63+
return true, nil
3864
}

0 commit comments

Comments
 (0)