From 2b560ee5ea837739721b24df4210f5df6f8cb02c Mon Sep 17 00:00:00 2001 From: vickydotbat Date: Mon, 13 Apr 2026 14:12:24 +0200 Subject: [PATCH] Default 2da-to-json to suffix --- internal/topdata/convert.go | 8 +-- internal/topdata/convert_test.go | 110 +++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 4 deletions(-) diff --git a/internal/topdata/convert.go b/internal/topdata/convert.go index 95c52be..2196c51 100644 --- a/internal/topdata/convert.go +++ b/internal/topdata/convert.go @@ -35,7 +35,7 @@ func RunConvertCommand(args []string, stdout io.Writer) error { _, _ = fmt.Fprintln(stdout, "usage: convert-topdata 2da-to-json [flags] ") return nil } - opts, input, output, err := parseConvertArgs(args[1:], false) + opts, input, output, err := parseConvertArgs(args[1:], false, "suffix") if err != nil { return err } @@ -53,7 +53,7 @@ func RunConvertCommand(args []string, stdout io.Writer) error { _, _ = fmt.Fprintln(stdout, "usage: convert-topdata 2da-to-module [--namespace ] [--name ] [--type entries|override] [flags] [output.json]") return nil } - opts, input, output, err := parseConvertArgs(args[1:], true) + opts, input, output, err := parseConvertArgs(args[1:], true, "error") if err != nil { return err } @@ -115,9 +115,9 @@ type parsed2DA struct { Rows []map[string]any `json:"rows"` } -func parseConvertArgs(args []string, allowFormat bool) (convertOptions, string, string, error) { +func parseConvertArgs(args []string, allowFormat bool, defaultCollision string) (convertOptions, string, string, error) { opts := convertOptions{ - CollisionMode: "error", + CollisionMode: defaultCollision, Type: "entries", } positional := make([]string, 0, 2) diff --git a/internal/topdata/convert_test.go b/internal/topdata/convert_test.go index 9befb4e..d65597d 100644 --- a/internal/topdata/convert_test.go +++ b/internal/topdata/convert_test.go @@ -177,6 +177,116 @@ func TestRunConvertCommand2DAToJSONInfersOutputDatasetKeysFromConfig(t *testing. } } +func TestRunConvertCommand2DAToJSONInfersAmbientTemplateKeysFromResource(t *testing.T) { + root := testProjectRoot(t) + writeFile(t, filepath.Join(root, "nwn-tool.json"), `{ + "module": {"name": "Test", "resref": "test"}, + "paths": {"source": "src", "assets": "assets", "build": "build"}, + "topdata": {"source": "topdata", "build": "build/topdata"} +}`+"\n") + mkdirAll(t, filepath.Join(root, "topdata", "templates")) + writeFile(t, filepath.Join(root, "topdata", "templates", "config.json"), `{ + "tables": { + "template_ambientmusic": { + "namespace": "ambientmusic", + "key_fields": ["Resource"] + } + } +}`+"\n") + inputPath := filepath.Join(root, "topdata", "templates", "template_ambientmusic.2da") + writeFile(t, inputPath, "2DA V2.0\n\nDescription DisplayName Resource Stinger1 Stinger2 Stinger3\n0 61901 **** **** **** **** ****\n1 61842 **** mus_ruralday1 **** **** ****\n2 61843 **** mus_ruralday2 **** **** ****\n") + + oldCwd, err := os.Getwd() + if err != nil { + t.Fatalf("getwd: %v", err) + } + t.Cleanup(func() { + _ = os.Chdir(oldCwd) + }) + if err := os.Chdir(root); err != nil { + t.Fatalf("chdir: %v", err) + } + + outputPath := filepath.Join(root, "out", "ambientmusic.json") + if err := RunConvertCommand([]string{ + "2da-to-json", + "topdata/templates/template_ambientmusic.2da", + outputPath, + }, &bytes.Buffer{}); err != nil { + t.Fatalf("RunConvertCommand failed: %v", err) + } + + raw, err := os.ReadFile(outputPath) + if err != nil { + t.Fatalf("read output: %v", err) + } + var payload struct { + Rows []map[string]any `json:"rows"` + } + if err := json.Unmarshal(raw, &payload); err != nil { + t.Fatalf("unmarshal output: %v", err) + } + if _, ok := payload.Rows[0]["key"]; ok { + t.Fatalf("expected null Resource row to remain unkeyed, got %#v", payload.Rows[0]["key"]) + } + if got := payload.Rows[1]["key"]; got != "ambientmusic:mus_ruralday1" { + t.Fatalf("expected first populated resource row key, got %#v", got) + } + if got := payload.Rows[2]["key"]; got != "ambientmusic:mus_ruralday2" { + t.Fatalf("expected second populated resource row key, got %#v", got) + } +} + +func TestRunConvertCommand2DAToJSONSuffixesDuplicateGeneratedKeys(t *testing.T) { + root := testProjectRoot(t) + writeFile(t, filepath.Join(root, "nwn-tool.json"), `{ + "module": {"name": "Test", "resref": "test"}, + "paths": {"source": "src", "assets": "assets", "build": "build"}, + "topdata": {"source": "topdata", "build": "build/topdata"} +}`+"\n") + inputPath := filepath.Join(root, "dup.2da") + writeFile(t, inputPath, "2DA V2.0\n\nResource\n1 cmp_reserved\n2 cmp_reserved\n") + + oldCwd, err := os.Getwd() + if err != nil { + t.Fatalf("getwd: %v", err) + } + t.Cleanup(func() { + _ = os.Chdir(oldCwd) + }) + if err := os.Chdir(root); err != nil { + t.Fatalf("chdir: %v", err) + } + + outputPath := filepath.Join(root, "out", "ambientmusic.json") + if err := RunConvertCommand([]string{ + "2da-to-json", + "--namespace", "ambientmusic", + "--key-field", "Resource", + inputPath, + outputPath, + }, &bytes.Buffer{}); err != nil { + t.Fatalf("RunConvertCommand failed: %v", err) + } + + raw, err := os.ReadFile(outputPath) + if err != nil { + t.Fatalf("read output: %v", err) + } + var payload struct { + Rows []map[string]any `json:"rows"` + } + if err := json.Unmarshal(raw, &payload); err != nil { + t.Fatalf("unmarshal output: %v", err) + } + if got := payload.Rows[0]["key"]; got != "ambientmusic:cmp_reserved" { + t.Fatalf("expected first duplicate key to remain unsuffixed, got %#v", got) + } + if got := payload.Rows[1]["key"]; got != "ambientmusic:cmp_reserved_2" { + t.Fatalf("expected second duplicate key to gain row-id suffix, got %#v", got) + } +} + func TestConvert2DAToJSONSkipsConfiguredKeysWhenAnyKeyFieldIsNull(t *testing.T) { result, err := convert2DAToJSON(parsed2DA{ Columns: []string{"LABEL", "RESREF", "STRREF"},