Refactor build and topdata path resolution through project config
This commit is contained in:
+107
-3
@@ -63,10 +63,13 @@ type TopDataConfig struct {
|
||||
Build string `json:"build"`
|
||||
ReferenceBuilder string `json:"reference_builder"`
|
||||
Assets string `json:"assets"`
|
||||
PackageHAK string `json:"package_hak"`
|
||||
PackageTLK string `json:"package_tlk"`
|
||||
}
|
||||
|
||||
type ExtractConfig struct {
|
||||
IgnoreExtensions []string `json:"ignore_extensions"`
|
||||
IgnoreExtensions []string `json:"ignore_extensions"`
|
||||
DeleteModuleArchiveAfterSuccess bool `json:"delete_module_archive_after_success,omitempty"`
|
||||
}
|
||||
|
||||
type Inventory struct {
|
||||
@@ -127,6 +130,17 @@ func (p *Project) ValidateLayout() error {
|
||||
if len(p.Config.Module.ResRef) > 16 {
|
||||
failures = append(failures, fmt.Errorf("module.resref %q exceeds 16 characters", p.Config.Module.ResRef))
|
||||
}
|
||||
if p.HasTopData() {
|
||||
if err := validateOutputFileName("topdata.package_hak", p.TopDataPackageHAKName(), ".hak"); err != nil {
|
||||
failures = append(failures, err)
|
||||
}
|
||||
if err := validateOutputFileName("topdata.package_tlk", p.TopDataPackageTLKName(), ".tlk"); err != nil {
|
||||
failures = append(failures, err)
|
||||
}
|
||||
if err := validateOutputFileName("topdata.compiled_tlk", filepath.Base(p.TopDataCompiledTLKPath()), ".tlk"); err != nil {
|
||||
failures = append(failures, err)
|
||||
}
|
||||
}
|
||||
|
||||
for index, hak := range p.Config.HAKs {
|
||||
if strings.TrimSpace(hak.Name) == "" {
|
||||
@@ -244,6 +258,18 @@ func (p *Project) BuildDir() string {
|
||||
return filepath.Join(p.Root, p.Config.Paths.Build)
|
||||
}
|
||||
|
||||
func (p *Project) ModuleArchivePath() string {
|
||||
return filepath.Join(p.BuildDir(), p.Config.Module.ResRef+".mod")
|
||||
}
|
||||
|
||||
func (p *Project) HAKManifestPath() string {
|
||||
return filepath.Join(p.BuildDir(), "haks.json")
|
||||
}
|
||||
|
||||
func (p *Project) HAKArchivePath(name string) string {
|
||||
return filepath.Join(p.BuildDir(), strings.TrimSpace(name)+".hak")
|
||||
}
|
||||
|
||||
func (p *Project) CloneWithHAKNames(names []string) (*Project, error) {
|
||||
if len(names) == 0 {
|
||||
return p, nil
|
||||
@@ -341,12 +367,70 @@ func (p *Project) TopDataAssetsDir() string {
|
||||
return absPath
|
||||
}
|
||||
|
||||
func (p *Project) TopDataUsesRepoCache() bool {
|
||||
return sameCleanPath(p.TopDataBuildDir(), filepath.Join(p.Root, ".cache"))
|
||||
}
|
||||
|
||||
func (p *Project) TopDataCompiled2DADir() string {
|
||||
return filepath.Join(p.TopDataBuildDir(), "2da")
|
||||
}
|
||||
|
||||
func (p *Project) TopDataCompiledTLKPath() string {
|
||||
if p.TopDataUsesRepoCache() {
|
||||
return filepath.Join(p.BuildDir(), "sow_tlk.tlk")
|
||||
}
|
||||
return filepath.Join(p.TopDataBuildDir(), "sow_tlk.tlk")
|
||||
}
|
||||
|
||||
func (p *Project) TopDataCompiledTLKDir() string {
|
||||
return filepath.Dir(p.TopDataCompiledTLKPath())
|
||||
}
|
||||
|
||||
func (p *Project) TopDataWikiRootDir() string {
|
||||
if p.TopDataUsesRepoCache() {
|
||||
return filepath.Join(p.Root, ".cache", "wiki")
|
||||
}
|
||||
return filepath.Join(p.TopDataBuildDir(), "wiki")
|
||||
}
|
||||
|
||||
func (p *Project) TopDataWikiPagesDir() string {
|
||||
return filepath.Join(p.TopDataWikiRootDir(), "pages")
|
||||
}
|
||||
|
||||
func (p *Project) TopDataWikiStatePath() string {
|
||||
return filepath.Join(p.TopDataWikiRootDir(), "state.json")
|
||||
}
|
||||
|
||||
func (p *Project) TopDataPackageHAKName() string {
|
||||
name := strings.TrimSpace(p.Config.TopData.PackageHAK)
|
||||
if name == "" {
|
||||
name = "sow_top.hak"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func (p *Project) TopDataPackageTLKName() string {
|
||||
name := strings.TrimSpace(p.Config.TopData.PackageTLK)
|
||||
if name == "" {
|
||||
name = "sow_tlk.tlk"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func (p *Project) TopDataPackageHAKPath() string {
|
||||
return filepath.Join(p.BuildDir(), p.TopDataPackageHAKName())
|
||||
}
|
||||
|
||||
func (p *Project) TopDataPackageTLKPath() string {
|
||||
return filepath.Join(p.BuildDir(), p.TopDataPackageTLKName())
|
||||
}
|
||||
|
||||
func (i Inventory) Report() InventoryReport {
|
||||
return InventoryReport{
|
||||
SourceFiles: len(i.SourceFiles),
|
||||
ScriptFiles: len(i.ScriptFiles),
|
||||
AssetFiles: len(i.AssetFiles),
|
||||
Extensions: i.Extensions,
|
||||
Extensions: i.Extensions,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,7 +440,9 @@ func defaultConfig() Config {
|
||||
Build: "build",
|
||||
},
|
||||
TopData: TopDataConfig{
|
||||
Build: ".cache",
|
||||
Build: ".cache",
|
||||
PackageHAK: "sow_top.hak",
|
||||
PackageTLK: "sow_tlk.tlk",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -406,3 +492,21 @@ func scanDir(root string, include func(path string) bool) ([]string, []string, e
|
||||
slices.Sort(exts)
|
||||
return files, exts, nil
|
||||
}
|
||||
|
||||
func validateOutputFileName(field, name, expectedExt string) error {
|
||||
trimmed := strings.TrimSpace(name)
|
||||
if trimmed == "" {
|
||||
return fmt.Errorf("%s is required", field)
|
||||
}
|
||||
if filepath.Base(trimmed) != trimmed {
|
||||
return fmt.Errorf("%s must be a file name, not a path: %q", field, name)
|
||||
}
|
||||
if !strings.EqualFold(filepath.Ext(trimmed), expectedExt) {
|
||||
return fmt.Errorf("%s must use %s extension: %q", field, expectedExt, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sameCleanPath(a, b string) bool {
|
||||
return filepath.Clean(a) == filepath.Clean(b)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -83,6 +84,67 @@ func TestTopDataAssetsDirReturnsEmptyForNoConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleArchivePathUsesConfiguredBuildDirAndResRef(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
mkdirAll(t, filepath.Join(root, "output"))
|
||||
|
||||
proj := &Project{
|
||||
Root: root,
|
||||
Config: Config{
|
||||
Module: ModuleConfig{Name: "Test", ResRef: "testmod"},
|
||||
Paths: PathConfig{Build: "output"},
|
||||
},
|
||||
}
|
||||
|
||||
got := proj.ModuleArchivePath()
|
||||
want := filepath.Join(root, "output", "testmod.mod")
|
||||
if got != want {
|
||||
t.Fatalf("expected %s, got %s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHAKPathsUseConfiguredBuildDir(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
mkdirAll(t, filepath.Join(root, "output"))
|
||||
|
||||
proj := &Project{
|
||||
Root: root,
|
||||
Config: Config{
|
||||
Paths: PathConfig{Build: "output"},
|
||||
},
|
||||
}
|
||||
|
||||
if got, want := proj.HAKManifestPath(), filepath.Join(root, "output", "haks.json"); got != want {
|
||||
t.Fatalf("expected manifest path %s, got %s", want, got)
|
||||
}
|
||||
if got, want := proj.HAKArchivePath("core_01"), filepath.Join(root, "output", "core_01.hak"); got != want {
|
||||
t.Fatalf("expected hak path %s, got %s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopDataPackagePathsUseConfiguredNamesAndBuildDir(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
mkdirAll(t, filepath.Join(root, "output"))
|
||||
|
||||
proj := &Project{
|
||||
Root: root,
|
||||
Config: Config{
|
||||
Paths: PathConfig{Build: "output"},
|
||||
TopData: TopDataConfig{
|
||||
PackageHAK: "custom_top.hak",
|
||||
PackageTLK: "custom_dialog.tlk",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if got, want := proj.TopDataPackageHAKPath(), filepath.Join(root, "output", "custom_top.hak"); got != want {
|
||||
t.Fatalf("expected top hak path %s, got %s", want, got)
|
||||
}
|
||||
if got, want := proj.TopDataPackageTLKPath(), filepath.Join(root, "output", "custom_dialog.tlk"); got != want {
|
||||
t.Fatalf("expected top tlk path %s, got %s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateLayoutAllowsMissingAssetsDir(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
mkdirAll(t, filepath.Join(root, "src"))
|
||||
@@ -104,6 +166,36 @@ func TestValidateLayoutAllowsMissingAssetsDir(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateLayoutRejectsInvalidTopDataPackageOutputNames(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
mkdirAll(t, filepath.Join(root, "src"))
|
||||
mkdirAll(t, filepath.Join(root, "build"))
|
||||
|
||||
proj := &Project{
|
||||
Root: root,
|
||||
Config: Config{
|
||||
Module: ModuleConfig{Name: "Test", ResRef: "test"},
|
||||
Paths: PathConfig{Source: "src", Build: "build"},
|
||||
TopData: TopDataConfig{
|
||||
Source: "topdata",
|
||||
PackageHAK: "nested/custom_top.hak",
|
||||
PackageTLK: "custom_dialog.txt",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := proj.ValidateLayout()
|
||||
if err == nil {
|
||||
t.Fatal("expected topdata output validation error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "topdata.package_hak") {
|
||||
t.Fatalf("expected package_hak validation error, got %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "topdata.package_tlk") {
|
||||
t.Fatalf("expected package_tlk validation error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanAllowsMissingAssetsDir(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
mkdirAll(t, filepath.Join(root, "src"))
|
||||
|
||||
Reference in New Issue
Block a user