diff --git a/internal/app/app.go b/internal/app/app.go index 1563ab8..de95a5d 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -184,6 +184,15 @@ func runBuildHAKs(ctx context) error { return err } + filteredHAKs, err := parseBuildHAKArgs(ctx.args[1:]) + if err != nil { + return err + } + p, err = p.CloneWithHAKNames(filteredHAKs) + if err != nil { + return err + } + result, err := pipeline.BuildHAKsWithProgress(p, func(message string) { fmt.Fprintf(ctx.stdout, "[build-haks] %s\n", message) }) @@ -200,6 +209,42 @@ func runBuildHAKs(ctx context) error { return nil } +func parseBuildHAKArgs(args []string) ([]string, error) { + if len(args) == 0 { + return nil, nil + } + + var filtered []string + for index := 0; index < len(args); index++ { + arg := args[index] + switch arg { + case "-h", "--help": + return nil, errors.New("usage: build-haks [--hak ...]") + case "--hak": + index++ + if index >= len(args) { + return nil, errors.New("--hak requires a value") + } + filtered = append(filtered, args[index]) + default: + if value, ok := parseInlineFlagValue(arg, "--hak"); ok { + filtered = append(filtered, value) + continue + } + return nil, fmt.Errorf("unknown build-haks argument %q", arg) + } + } + return filtered, nil +} + +func parseInlineFlagValue(arg, name string) (string, bool) { + prefix := name + "=" + if !strings.HasPrefix(arg, prefix) { + return "", false + } + return strings.TrimSpace(strings.TrimPrefix(arg, prefix)), true +} + func runExtract(ctx context) error { p, err := loadProject(ctx.cwd) if err != nil { diff --git a/internal/project/project.go b/internal/project/project.go index d36618c..a944a95 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -220,6 +220,55 @@ func (p *Project) BuildDir() string { return filepath.Join(p.Root, p.Config.Paths.Build) } +func (p *Project) CloneWithHAKNames(names []string) (*Project, error) { + if len(names) == 0 { + return p, nil + } + + wanted := make(map[string]struct{}, len(names)) + for _, name := range names { + trimmed := strings.TrimSpace(strings.ToLower(name)) + if trimmed == "" { + continue + } + wanted[trimmed] = struct{}{} + } + if len(wanted) == 0 { + return p, nil + } + + filtered := make([]HAKConfig, 0, len(p.Config.HAKs)) + found := make(map[string]struct{}, len(wanted)) + for _, hak := range p.Config.HAKs { + key := strings.ToLower(strings.TrimSpace(hak.Name)) + if _, ok := wanted[key]; !ok { + continue + } + filtered = append(filtered, hak) + found[key] = struct{}{} + } + + var missing []string + for _, name := range names { + key := strings.TrimSpace(strings.ToLower(name)) + if key == "" { + continue + } + if _, ok := found[key]; !ok { + missing = append(missing, name) + } + } + if len(missing) > 0 { + return nil, fmt.Errorf("unknown hak name(s): %s", strings.Join(missing, ", ")) + } + + clone := *p + clone.Config = p.Config + clone.Config.HAKs = filtered + clone.Inventory = p.Inventory + return &clone, nil +} + func (p *Project) HasTopData() bool { return strings.TrimSpace(p.Config.TopData.Source) != "" } diff --git a/internal/project/project_test.go b/internal/project/project_test.go index 9dca147..a18bc4a 100644 --- a/internal/project/project_test.go +++ b/internal/project/project_test.go @@ -3,6 +3,7 @@ package project import ( "os" "path/filepath" + "slices" "testing" ) @@ -87,3 +88,54 @@ func mkdirAll(t *testing.T, path string) { t.Fatalf("mkdir %s: %v", path, err) } } + +func TestCloneWithHAKNamesFiltersConfiguredHAKs(t *testing.T) { + proj := &Project{ + Root: "/tmp/test", + Config: Config{ + Module: ModuleConfig{Name: "Test", ResRef: "test"}, + Paths: PathConfig{Source: "src", Assets: "assets", Build: "build"}, + HAKs: []HAKConfig{ + {Name: "sow_over", Priority: 1, Include: []string{"over/**"}}, + {Name: "sow_core", Priority: 2, Include: []string{"core/**"}}, + {Name: "sow_item", Priority: 3, Include: []string{"item/**"}}, + }, + }, + } + + filtered, err := proj.CloneWithHAKNames([]string{"sow_core", "sow_item"}) + if err != nil { + t.Fatalf("CloneWithHAKNames returned error: %v", err) + } + + if filtered == proj { + t.Fatalf("expected filtered clone, got original pointer") + } + + got := make([]string, 0, len(filtered.Config.HAKs)) + for _, hak := range filtered.Config.HAKs { + got = append(got, hak.Name) + } + want := []string{"sow_core", "sow_item"} + if !slices.Equal(got, want) { + t.Fatalf("unexpected filtered haks: got %v want %v", got, want) + } + + if len(proj.Config.HAKs) != 3 { + t.Fatalf("original project config should remain unchanged, got %d haks", len(proj.Config.HAKs)) + } +} + +func TestCloneWithHAKNamesRejectsUnknownHAKs(t *testing.T) { + proj := &Project{ + Config: Config{ + HAKs: []HAKConfig{ + {Name: "sow_core", Priority: 1, Include: []string{"core/**"}}, + }, + }, + } + + if _, err := proj.CloneWithHAKNames([]string{"missing_hak"}); err == nil { + t.Fatal("expected error for unknown hak name") + } +}