diff --git a/configs/commands.json b/configs/commands.json deleted file mode 100644 index 00f8e95..0000000 --- a/configs/commands.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "sh": { - "run": { - "engine": "docker", - "entry": "main.sh", - "steps": [ - { - "box": "alpine", - "command": ["sh", "main.sh"] - } - ] - } - } -} diff --git a/configs/commands/sh.json b/configs/commands/sh.json new file mode 100644 index 0000000..277fabe --- /dev/null +++ b/configs/commands/sh.json @@ -0,0 +1,12 @@ +{ + "run": { + "engine": "docker", + "entry": "main.sh", + "steps": [ + { + "box": "alpine", + "command": ["sh", "main.sh"] + } + ] + } +} diff --git a/internal/config/load.go b/internal/config/load.go index 789b963..ee5f76a 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -4,12 +4,15 @@ import ( "encoding/json" "os" "path/filepath" + "strings" + + "github.com/nalgeon/codapi/internal/fileio" ) const ( - configFilename = "config.json" - boxesFilename = "boxes.json" - commandsFilename = "commands.json" + configFilename = "config.json" + boxesFilename = "boxes.json" + commandsDirname = "commands" ) // Read reads application config from JSON files. @@ -24,7 +27,7 @@ func Read(path string) (*Config, error) { return nil, err } - cfg, err = ReadCommands(cfg, filepath.Join(path, commandsFilename)) + cfg, err = ReadCommands(cfg, filepath.Join(path, commandsDirname)) if err != nil { return nil, err } @@ -71,31 +74,36 @@ func ReadBoxes(cfg *Config, path string) (*Config, error) { // ReadCommands reads commands config from a JSON file. func ReadCommands(cfg *Config, path string) (*Config, error) { - data, err := os.ReadFile(path) - if err != nil { - return nil, err - } - - commands := make(map[string]SandboxCommands) - err = json.Unmarshal(data, &commands) + fnames, err := filepath.Glob(filepath.Join(path, "*.json")) if err != nil { return nil, err } - for _, playCmds := range commands { - for _, cmd := range playCmds { - if cmd.Before != nil { - setStepDefaults(cmd.Before, cfg.Step) - } - for _, step := range cmd.Steps { - setStepDefaults(step, cfg.Step) - } - if cmd.After != nil { - setStepDefaults(cmd.After, cfg.Step) - } + cfg.Commands = make(map[string]SandboxCommands, len(fnames)) + for _, fname := range fnames { + sandbox := strings.TrimSuffix(filepath.Base(fname), ".json") + commands, err := fileio.ReadJson[SandboxCommands](fname) + if err != nil { + break } + setCommandDefaults(commands, cfg) + cfg.Commands[sandbox] = commands } - cfg.Commands = commands return cfg, err } + +// setCommandDefaults applies global defaults to sandbox commands. +func setCommandDefaults(commands SandboxCommands, cfg *Config) { + for _, cmd := range commands { + if cmd.Before != nil { + setStepDefaults(cmd.Before, cfg.Step) + } + for _, step := range cmd.Steps { + setStepDefaults(step, cfg.Step) + } + if cmd.After != nil { + setStepDefaults(cmd.After, cfg.Step) + } + } +} diff --git a/internal/config/testdata/commands.json b/internal/config/testdata/commands.json deleted file mode 100644 index 0e425c3..0000000 --- a/internal/config/testdata/commands.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "python": { - "run": { - "engine": "docker", - "entry": "main.py", - "steps": [ - { - "box": "python", - "command": ["python", "main.py"] - } - ] - }, - "test": { - "engine": "docker", - "entry": "test_main.py", - "steps": [ - { - "box": "python", - "command": ["python", "-m", "unittest"], - "noutput": 8192 - } - ] - } - } -} diff --git a/internal/config/testdata/commands/python.json b/internal/config/testdata/commands/python.json new file mode 100644 index 0000000..9adde23 --- /dev/null +++ b/internal/config/testdata/commands/python.json @@ -0,0 +1,23 @@ +{ + "run": { + "engine": "docker", + "entry": "main.py", + "steps": [ + { + "box": "python", + "command": ["python", "main.py"] + } + ] + }, + "test": { + "engine": "docker", + "entry": "test_main.py", + "steps": [ + { + "box": "python", + "command": ["python", "-m", "unittest"], + "noutput": 8192 + } + ] + } +} diff --git a/internal/fileio/fileio.go b/internal/fileio/fileio.go index 6c77a19..6b672cb 100644 --- a/internal/fileio/fileio.go +++ b/internal/fileio/fileio.go @@ -2,6 +2,7 @@ package fileio import ( + "encoding/json" "io" "os" "path/filepath" @@ -37,3 +38,17 @@ func CopyFiles(pattern string, dstDir string) error { return nil } + +// ReadJson reads the file and decodes it from JSON. +func ReadJson[T any](path string) (T, error) { + var obj T + data, err := os.ReadFile(path) + if err != nil { + return obj, err + } + err = json.Unmarshal(data, &obj) + if err != nil { + return obj, err + } + return obj, err +} diff --git a/internal/fileio/fileio_test.go b/internal/fileio/fileio_test.go index 8ac2f71..c4513ac 100644 --- a/internal/fileio/fileio_test.go +++ b/internal/fileio/fileio_test.go @@ -3,6 +3,7 @@ package fileio import ( "os" "path/filepath" + "reflect" "testing" ) @@ -54,3 +55,30 @@ func TestCopyFiles(t *testing.T) { t.Errorf("unexpected file content: got %q, want %q", data, expected) } } + +func TestReadJson(t *testing.T) { + type Person struct{ Name string } + + t.Run("valid", func(t *testing.T) { + got, err := ReadJson[Person](filepath.Join("testdata", "valid.json")) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + want := Person{"alice"} + if !reflect.DeepEqual(got, want) { + t.Errorf("expected %v, got %v", want, got) + } + }) + t.Run("invalid", func(t *testing.T) { + _, err := ReadJson[Person](filepath.Join("testdata", "invalid.json")) + if err == nil { + t.Fatal("expected error, got nil") + } + }) + t.Run("does not exist", func(t *testing.T) { + _, err := ReadJson[Person](filepath.Join("testdata", "missing.json")) + if err == nil { + t.Fatal("expected error, got nil") + } + }) +} diff --git a/internal/fileio/testdata/invalid.json b/internal/fileio/testdata/invalid.json new file mode 100644 index 0000000..2bcad2f --- /dev/null +++ b/internal/fileio/testdata/invalid.json @@ -0,0 +1 @@ +name: alice diff --git a/internal/fileio/testdata/valid.json b/internal/fileio/testdata/valid.json new file mode 100644 index 0000000..7185646 --- /dev/null +++ b/internal/fileio/testdata/valid.json @@ -0,0 +1,3 @@ +{ + "name": "alice" +}