claude: fold in old sow-tools
This commit is contained in:
@@ -0,0 +1,738 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/music"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultBuildPath = "build"
|
||||
DefaultCachePath = ".cache"
|
||||
DefaultToolsPath = "tools"
|
||||
DefaultModuleArchiveTemplate = "{module.resref}.mod"
|
||||
DefaultHAKManifest = "haks.json"
|
||||
DefaultHAKArchiveTemplate = "{hak.name}.hak"
|
||||
DefaultSourceJSONPattern = "{resref}.{extension}.json"
|
||||
DefaultValidationProfile = "nwn_module"
|
||||
DefaultMusicStageRoot = "{paths.cache}/music"
|
||||
DefaultCreditsRoot = "{paths.cache}/credits"
|
||||
DefaultCreditsOverlayFile = "CREDITS.md"
|
||||
DefaultMusicNamingScheme = "nwn_bmu"
|
||||
DefaultMusicOutputExtension = ".bmu"
|
||||
DefaultTopDataBuild = "{paths.build}/topdata"
|
||||
DefaultTopDataCompiled2DADir = "2da"
|
||||
DefaultTopDataCompiledTLK = "sow_tlk.tlk"
|
||||
DefaultTopDataWikiOutputRoot = "wiki"
|
||||
DefaultTopDataWikiPagesDir = "pages"
|
||||
DefaultTopDataWikiStateFile = "state.json"
|
||||
DefaultTopDataWikiSource = "topdata/wiki"
|
||||
DefaultTopDataWikiRenderer = "nodebb_tiptap_html"
|
||||
DefaultTopDataWikiLinkStrategy = "preserve_westgate_wiki_links"
|
||||
DefaultTopDataWikiNamespacesFile = "namespaces.yaml"
|
||||
DefaultTopDataWikiTablesFile = "tables.yaml"
|
||||
DefaultTopDataWikiVisibilityFile = "visibility.yaml"
|
||||
DefaultTopDataWikiDataFile = "data.yaml"
|
||||
DefaultTopDataWikiPagePathsFile = "wiki.yaml"
|
||||
DefaultTopDataWikiTemplatesDir = "templates"
|
||||
DefaultTopDataWikiPageTemplatesDir = "templates/pages"
|
||||
DefaultTopDataWikiManualSectionsDir = "manual-sections"
|
||||
DefaultTopDataWikiManualSectionsFile = "manual-sections/default.yaml"
|
||||
DefaultTopDataWikiPageIndexFile = "page-index.json"
|
||||
DefaultTopDataWikiStaleDefault = "report"
|
||||
DefaultTopDataWikiStaleLiveCleanup = "archive"
|
||||
DefaultTopDataWikiMarkerFormat = "html_comments"
|
||||
DefaultTopDataWikiPageMarkerPrefix = "sow-topdata-wiki"
|
||||
DefaultTopDataWikiTitlePrefixMinLength = 3
|
||||
DefaultTopDataWikiStatusPages = true
|
||||
DefaultTopDataWikiStatusListingScope = "all"
|
||||
DefaultTopDataWikiAlignmentLinkFormat = "wiki_markup"
|
||||
DefaultWikiDeployManifest = ".wiki_deploy_manifest.json"
|
||||
DefaultWikiDeployEditSummary = "Auto-generated from native builder"
|
||||
DefaultTopDataPackageHAK = "sow_top.hak"
|
||||
DefaultTopDataPackageTLK = "sow_tlk.tlk"
|
||||
DefaultAutogenCacheRoot = "{paths.cache}"
|
||||
DefaultAutogenCacheMaxAge = time.Hour
|
||||
DefaultAutogenRefreshEnv = "SOW_AUTOGEN_MANIFEST_REFRESH"
|
||||
DefaultPartsManifestRefreshEnv = "SOW_PARTS_MANIFEST_REFRESH"
|
||||
DefaultAutogenReleaseProvider = "gitea"
|
||||
DefaultAutogenServerURLEnv = "SOW_ASSETS_SERVER_URL"
|
||||
DefaultAutogenRepoEnv = "SOW_ASSETS_REPO"
|
||||
DefaultExtractLayout = "nwn_canonical_json"
|
||||
DefaultExtractHAKDiscovery = "build_glob"
|
||||
)
|
||||
|
||||
type ConfigProvenance map[string]ConfigValueProvenance
|
||||
|
||||
type ConfigValueProvenance struct {
|
||||
Source string `json:"source" yaml:"source"`
|
||||
Detail string `json:"detail,omitempty" yaml:"detail,omitempty"`
|
||||
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
|
||||
}
|
||||
|
||||
type EffectiveConfig struct {
|
||||
ConfigSource ConfigSource `json:"config_source" yaml:"config_source"`
|
||||
Module ModuleConfig `json:"module" yaml:"module"`
|
||||
Paths EffectivePathConfig `json:"paths" yaml:"paths"`
|
||||
Outputs EffectiveOutputConfig `json:"outputs" yaml:"outputs"`
|
||||
Build BuildConfig `json:"build" yaml:"build"`
|
||||
Generated GeneratedConfig `json:"generated_assets" yaml:"generated_assets"`
|
||||
Inventory InventoryConfig `json:"inventory" yaml:"inventory"`
|
||||
Validation ValidationConfig `json:"validation" yaml:"validation"`
|
||||
Music EffectiveMusicConfig `json:"music" yaml:"music"`
|
||||
TopData EffectiveTopDataConfig `json:"topdata" yaml:"topdata"`
|
||||
Extract ExtractConfig `json:"extract" yaml:"extract"`
|
||||
Autogen EffectiveAutogenConfig `json:"autogen" yaml:"autogen"`
|
||||
HAKs []HAKConfig `json:"haks" yaml:"haks"`
|
||||
Provenance ConfigProvenance `json:"provenance" yaml:"provenance"`
|
||||
Overrides []ConfigOverride `json:"overrides,omitempty" yaml:"overrides,omitempty"`
|
||||
}
|
||||
|
||||
type EffectivePathConfig struct {
|
||||
Source string `json:"source" yaml:"source"`
|
||||
Assets string `json:"assets" yaml:"assets"`
|
||||
Build string `json:"build" yaml:"build"`
|
||||
Cache string `json:"cache" yaml:"cache"`
|
||||
Tools string `json:"tools" yaml:"tools"`
|
||||
}
|
||||
|
||||
type EffectiveOutputConfig struct {
|
||||
ModuleArchive string `json:"module_archive" yaml:"module_archive"`
|
||||
HAKManifest string `json:"hak_manifest" yaml:"hak_manifest"`
|
||||
HAKArchive string `json:"hak_archive" yaml:"hak_archive"`
|
||||
}
|
||||
|
||||
type EffectiveMusicConfig struct {
|
||||
Prefixes map[string]string `json:"prefixes" yaml:"prefixes"`
|
||||
Tools MusicToolsConfig `json:"tools" yaml:"tools"`
|
||||
StageRoot string `json:"stage_root" yaml:"stage_root"`
|
||||
CreditsRoot string `json:"credits_root" yaml:"credits_root"`
|
||||
CreditsOverlay string `json:"credits_overlay" yaml:"credits_overlay"`
|
||||
MaxStemLength int `json:"max_stem_length" yaml:"max_stem_length"`
|
||||
NamingScheme string `json:"naming_scheme" yaml:"naming_scheme"`
|
||||
OutputExtension string `json:"output_extension" yaml:"output_extension"`
|
||||
ConvertExtensions []string `json:"convert_extensions" yaml:"convert_extensions"`
|
||||
Datasets map[string]EffectiveMusicDataset `json:"datasets,omitempty" yaml:"datasets,omitempty"`
|
||||
}
|
||||
|
||||
type EffectiveMusicDataset struct {
|
||||
Source string `json:"source" yaml:"source"`
|
||||
Output string `json:"output" yaml:"output"`
|
||||
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
|
||||
StageRoot string `json:"stage_root,omitempty" yaml:"stage_root,omitempty"`
|
||||
CreditsRoot string `json:"credits_root,omitempty" yaml:"credits_root,omitempty"`
|
||||
NamingScheme string `json:"naming_scheme" yaml:"naming_scheme"`
|
||||
OutputExtension string `json:"output_extension" yaml:"output_extension"`
|
||||
MaxStemLength int `json:"max_stem_length" yaml:"max_stem_length"`
|
||||
PackageMode string `json:"package_mode" yaml:"package_mode"`
|
||||
ConvertExtensions []string `json:"convert_extensions" yaml:"convert_extensions"`
|
||||
}
|
||||
|
||||
type EffectiveTopDataConfig struct {
|
||||
Source string `json:"source" yaml:"source"`
|
||||
Build string `json:"build" yaml:"build"`
|
||||
ReferenceBuilder string `json:"reference_builder" yaml:"reference_builder"`
|
||||
Assets string `json:"assets" yaml:"assets"`
|
||||
Compiled2DADir string `json:"compiled_2da_dir" yaml:"compiled_2da_dir"`
|
||||
CompiledTLK string `json:"compiled_tlk" yaml:"compiled_tlk"`
|
||||
PackageHAK string `json:"package_hak" yaml:"package_hak"`
|
||||
PackageTLK string `json:"package_tlk" yaml:"package_tlk"`
|
||||
ValueEncodings []TopDataValueEncodingConfig `json:"value_encodings" yaml:"value_encodings"`
|
||||
ValueDefaults []TopDataValueDefaultConfig `json:"value_defaults" yaml:"value_defaults"`
|
||||
RowGeneration []TopDataRowGenerationConfig `json:"row_generation" yaml:"row_generation"`
|
||||
RowExtensions []TopDataRowExtensionConfig `json:"row_extensions" yaml:"row_extensions"`
|
||||
ClassFeatInjections TopDataClassFeatInjectionConfig `json:"class_feat_injections" yaml:"class_feat_injections"`
|
||||
Wiki TopDataWikiConfig `json:"wiki" yaml:"wiki"`
|
||||
}
|
||||
|
||||
type EffectiveAutogenConfig struct {
|
||||
Producers []AutogenProducerConfig `json:"producers" yaml:"producers"`
|
||||
Consumers []AutogenConsumerConfig `json:"consumers" yaml:"consumers"`
|
||||
Cache EffectiveAutogenCacheConfig `json:"cache" yaml:"cache"`
|
||||
ReleaseSource EffectiveAutogenReleaseSourceConfig `json:"release_source" yaml:"release_source"`
|
||||
}
|
||||
|
||||
type EffectiveAutogenCacheConfig struct {
|
||||
Root string `json:"root" yaml:"root"`
|
||||
MaxAge string `json:"max_age" yaml:"max_age"`
|
||||
RefreshEnv string `json:"refresh_env" yaml:"refresh_env"`
|
||||
PartsManifestRefreshEnv string `json:"parts_manifest_refresh_env" yaml:"parts_manifest_refresh_env"`
|
||||
}
|
||||
|
||||
type EffectiveAutogenReleaseSourceConfig struct {
|
||||
Provider string `json:"provider" yaml:"provider"`
|
||||
ServerURLEnv string `json:"server_url_env" yaml:"server_url_env"`
|
||||
RepoEnv string `json:"repo_env" yaml:"repo_env"`
|
||||
}
|
||||
|
||||
type ConfigOverride struct {
|
||||
Key string `json:"key" yaml:"key"`
|
||||
Value string `json:"value" yaml:"value"`
|
||||
Source string `json:"source" yaml:"source"`
|
||||
}
|
||||
|
||||
func (p *Project) EffectiveConfig() EffectiveConfig {
|
||||
provenance := cloneProvenance(p.Provenance)
|
||||
markMissingDefaults(provenance)
|
||||
|
||||
paths := EffectivePathConfig{
|
||||
Source: strings.TrimSpace(p.Config.Paths.Source),
|
||||
Assets: strings.TrimSpace(p.Config.Paths.Assets),
|
||||
Build: defaultString(p.Config.Paths.Build, DefaultBuildPath),
|
||||
Cache: defaultString(p.Config.Paths.Cache, DefaultCachePath),
|
||||
Tools: defaultString(p.Config.Paths.Tools, DefaultToolsPath),
|
||||
}
|
||||
outputs := EffectiveOutputConfig{
|
||||
ModuleArchive: defaultString(p.Config.Outputs.ModuleArchive, DefaultModuleArchiveTemplate),
|
||||
HAKManifest: defaultString(p.Config.Outputs.HAKManifest, DefaultHAKManifest),
|
||||
HAKArchive: defaultString(p.Config.Outputs.HAKArchive, DefaultHAKArchiveTemplate),
|
||||
}
|
||||
inventory := InventoryConfig{
|
||||
SourceExtensions: defaultStringSlice(p.Config.Inventory.SourceExtensions, SourceExtensions),
|
||||
AssetExtensions: defaultStringSlice(p.Config.Inventory.AssetExtensions, AssetExtensions),
|
||||
SourceJSONPattern: defaultString(p.Config.Inventory.SourceJSONPattern, DefaultSourceJSONPattern),
|
||||
}
|
||||
validation := ValidationConfig{
|
||||
Profile: defaultString(p.Config.Validation.Profile, DefaultValidationProfile),
|
||||
BuiltinScriptPrefixes: normalizeLowerStringSlice(defaultStringSlice(p.Config.Validation.BuiltinScriptPrefixes, DefaultBuiltinScriptPrefixes())),
|
||||
RequiredFields: defaultRequiredFields(p.Config.Validation.RequiredFields),
|
||||
}
|
||||
topBuild := expandPathTemplate(defaultString(p.Config.TopData.Build, DefaultTopDataBuild), paths)
|
||||
top := EffectiveTopDataConfig{
|
||||
Source: strings.TrimSpace(p.Config.TopData.Source),
|
||||
Build: topBuild,
|
||||
ReferenceBuilder: strings.TrimSpace(p.Config.TopData.ReferenceBuilder),
|
||||
Assets: strings.TrimSpace(p.Config.TopData.Assets),
|
||||
Compiled2DADir: defaultString(p.Config.TopData.Compiled2DADir, DefaultTopDataCompiled2DADir),
|
||||
CompiledTLK: defaultString(p.Config.TopData.CompiledTLK, DefaultTopDataCompiledTLK),
|
||||
PackageHAK: defaultString(p.Config.TopData.PackageHAK, DefaultTopDataPackageHAK),
|
||||
PackageTLK: defaultString(p.Config.TopData.PackageTLK, DefaultTopDataPackageTLK),
|
||||
ValueEncodings: cloneTopDataValueEncodings(p.Config.TopData.ValueEncodings),
|
||||
ValueDefaults: cloneTopDataValueDefaults(p.Config.TopData.ValueDefaults),
|
||||
RowGeneration: cloneTopDataRowGeneration(p.Config.TopData.RowGeneration),
|
||||
RowExtensions: cloneTopDataRowExtensions(p.Config.TopData.RowExtensions),
|
||||
ClassFeatInjections: cloneTopDataClassFeatInjections(p.Config.TopData.ClassFeatInjections),
|
||||
Wiki: TopDataWikiConfig{
|
||||
OutputRoot: defaultString(p.Config.TopData.Wiki.OutputRoot, DefaultTopDataWikiOutputRoot),
|
||||
PagesDir: defaultString(p.Config.TopData.Wiki.PagesDir, DefaultTopDataWikiPagesDir),
|
||||
StateFile: defaultString(p.Config.TopData.Wiki.StateFile, DefaultTopDataWikiStateFile),
|
||||
ManagedNamespaces: defaultStringSlice(p.Config.TopData.Wiki.ManagedNamespaces, []string{"classes", "feat", "itemtypes", "races", "skills", "spells", "meta"}),
|
||||
DeployManifest: defaultString(p.Config.TopData.Wiki.DeployManifest, DefaultWikiDeployManifest),
|
||||
DeployEditSummary: defaultString(p.Config.TopData.Wiki.DeployEditSummary, DefaultWikiDeployEditSummary),
|
||||
Source: defaultString(p.Config.TopData.Wiki.Source, DefaultTopDataWikiSource),
|
||||
Renderer: defaultString(p.Config.TopData.Wiki.Renderer, DefaultTopDataWikiRenderer),
|
||||
LinkStrategy: defaultString(p.Config.TopData.Wiki.LinkStrategy, DefaultTopDataWikiLinkStrategy),
|
||||
NamespacesFile: defaultString(p.Config.TopData.Wiki.NamespacesFile, DefaultTopDataWikiNamespacesFile),
|
||||
TablesFile: defaultString(p.Config.TopData.Wiki.TablesFile, DefaultTopDataWikiTablesFile),
|
||||
VisibilityFile: defaultString(p.Config.TopData.Wiki.VisibilityFile, DefaultTopDataWikiVisibilityFile),
|
||||
DataFile: defaultString(p.Config.TopData.Wiki.DataFile, DefaultTopDataWikiDataFile),
|
||||
PagePathsFile: defaultString(p.Config.TopData.Wiki.PagePathsFile, DefaultTopDataWikiPagePathsFile),
|
||||
TemplatesDir: defaultString(p.Config.TopData.Wiki.TemplatesDir, DefaultTopDataWikiTemplatesDir),
|
||||
PageTemplatesDir: defaultWikiPageTemplatesDir(p.Config.TopData.Wiki),
|
||||
ManualSectionsDir: defaultString(p.Config.TopData.Wiki.ManualSectionsDir, DefaultTopDataWikiManualSectionsDir),
|
||||
ManualSectionsFile: defaultWikiManualSectionsFile(p.Config.TopData.Wiki),
|
||||
PageIndexFile: defaultString(p.Config.TopData.Wiki.PageIndexFile, DefaultTopDataWikiPageIndexFile),
|
||||
AlignmentLinks: TopDataWikiAlignmentLinks{
|
||||
TargetPattern: strings.TrimSpace(p.Config.TopData.Wiki.AlignmentLinks.TargetPattern),
|
||||
LinkFormat: defaultString(p.Config.TopData.Wiki.AlignmentLinks.LinkFormat, DefaultTopDataWikiAlignmentLinkFormat),
|
||||
},
|
||||
TitlePrefixMinLength: defaultInt(p.Config.TopData.Wiki.TitlePrefixMinLength, DefaultTopDataWikiTitlePrefixMinLength),
|
||||
StatusPages: defaultBoolPointer(p.Config.TopData.Wiki.StatusPages, DefaultTopDataWikiStatusPages),
|
||||
StatusListingScope: defaultString(p.Config.TopData.Wiki.StatusListingScope, DefaultTopDataWikiStatusListingScope),
|
||||
StalePages: TopDataWikiStalePagesConfig{
|
||||
Default: defaultString(p.Config.TopData.Wiki.StalePages.Default, DefaultTopDataWikiStaleDefault),
|
||||
LiveCleanup: defaultString(p.Config.TopData.Wiki.StalePages.LiveCleanup, DefaultTopDataWikiStaleLiveCleanup),
|
||||
},
|
||||
ManagedRegion: TopDataWikiManagedRegionConfig{
|
||||
MarkerFormat: defaultString(p.Config.TopData.Wiki.ManagedRegion.MarkerFormat, DefaultTopDataWikiMarkerFormat),
|
||||
PageMarkerPrefix: defaultString(p.Config.TopData.Wiki.ManagedRegion.PageMarkerPrefix, DefaultTopDataWikiPageMarkerPrefix),
|
||||
},
|
||||
},
|
||||
}
|
||||
generated := p.Config.Generated
|
||||
for index := range generated.TopData2DA {
|
||||
generated.TopData2DA[index].Output = expandPathTemplate(defaultString(generated.TopData2DA[index].Output, "{paths.cache}/generated-assets/"+generated.TopData2DA[index].ID), paths)
|
||||
}
|
||||
|
||||
extract := p.Config.Extract
|
||||
extract.Layout = defaultString(extract.Layout, DefaultExtractLayout)
|
||||
extract.HAKDiscovery = defaultString(extract.HAKDiscovery, DefaultExtractHAKDiscovery)
|
||||
if len(extract.Archives) == 0 {
|
||||
extract.Archives = []string{outputs.ModuleArchiveName(p.Config.Module)}
|
||||
}
|
||||
if extract.CleanupStale == nil {
|
||||
defaultCleanup := true
|
||||
extract.CleanupStale = &defaultCleanup
|
||||
}
|
||||
if extract.ConsumeArchives == nil {
|
||||
defaultConsume := false
|
||||
extract.ConsumeArchives = &defaultConsume
|
||||
}
|
||||
|
||||
musicStageRoot := expandPathTemplate(DefaultMusicStageRoot, paths)
|
||||
musicCreditsRoot := expandPathTemplate(DefaultCreditsRoot, paths)
|
||||
musicCreditsOverlay := DefaultCreditsOverlayFile
|
||||
musicMaxStemLength := 16
|
||||
musicNamingScheme := DefaultMusicNamingScheme
|
||||
musicOutputExtension := DefaultMusicOutputExtension
|
||||
musicConvertExtensions := music.DefaultConvertExtensions()
|
||||
if d := p.Config.Music.Defaults; d != nil {
|
||||
if d.StageRoot != "" {
|
||||
musicStageRoot = expandPathTemplate(d.StageRoot, paths)
|
||||
}
|
||||
if d.CreditsRoot != "" {
|
||||
musicCreditsRoot = expandPathTemplate(d.CreditsRoot, paths)
|
||||
}
|
||||
if d.CreditsOverlay != "" {
|
||||
musicCreditsOverlay = d.CreditsOverlay
|
||||
}
|
||||
if d.MaxStemLength != nil {
|
||||
musicMaxStemLength = *d.MaxStemLength
|
||||
}
|
||||
if d.NamingScheme != "" {
|
||||
musicNamingScheme = d.NamingScheme
|
||||
}
|
||||
if d.OutputExtension != "" {
|
||||
musicOutputExtension = normalizeExtension(d.OutputExtension)
|
||||
}
|
||||
if len(d.ConvertExtensions) > 0 {
|
||||
musicConvertExtensions = normalizeExtensionSlice(d.ConvertExtensions)
|
||||
}
|
||||
}
|
||||
musicPrefixes := cloneStringMap(p.Config.Music.Prefixes)
|
||||
if len(musicPrefixes) == 0 {
|
||||
musicPrefixes = make(map[string]string, len(p.Config.Music.Datasets))
|
||||
for _, ds := range p.Config.Music.Datasets {
|
||||
if ds.Source != "" && ds.Prefix != "" {
|
||||
musicPrefixes[ds.Source] = ds.Prefix
|
||||
}
|
||||
}
|
||||
}
|
||||
effectiveDatasets := make(map[string]EffectiveMusicDataset, len(p.Config.Music.Datasets))
|
||||
for id, ds := range p.Config.Music.Datasets {
|
||||
dsStageRoot := ds.StageRoot
|
||||
if dsStageRoot == "" {
|
||||
dsStageRoot = musicStageRoot
|
||||
}
|
||||
dsCreditsRoot := ds.CreditsRoot
|
||||
if dsCreditsRoot == "" {
|
||||
dsCreditsRoot = musicCreditsRoot
|
||||
}
|
||||
dsOutput := strings.TrimSpace(ds.Output)
|
||||
if dsOutput == "" {
|
||||
dsOutput = ds.Source
|
||||
}
|
||||
dsNamingScheme := defaultString(ds.NamingScheme, musicNamingScheme)
|
||||
dsOutputExtension := musicOutputExtension
|
||||
if ds.OutputExtension != "" {
|
||||
dsOutputExtension = normalizeExtension(ds.OutputExtension)
|
||||
}
|
||||
dsConvertExtensions := musicConvertExtensions
|
||||
if len(ds.ConvertExtensions) > 0 {
|
||||
dsConvertExtensions = normalizeExtensionSlice(ds.ConvertExtensions)
|
||||
}
|
||||
effectiveDatasets[id] = EffectiveMusicDataset{
|
||||
Source: ds.Source,
|
||||
Output: dsOutput,
|
||||
Prefix: ds.Prefix,
|
||||
StageRoot: dsStageRoot,
|
||||
CreditsRoot: dsCreditsRoot,
|
||||
NamingScheme: dsNamingScheme,
|
||||
OutputExtension: dsOutputExtension,
|
||||
MaxStemLength: musicMaxStemLength,
|
||||
PackageMode: defaultString(ds.PackageMode, "hak_asset"),
|
||||
ConvertExtensions: dsConvertExtensions,
|
||||
}
|
||||
}
|
||||
effective := EffectiveConfig{
|
||||
ConfigSource: p.ConfigSource,
|
||||
Module: p.Config.Module,
|
||||
Paths: paths,
|
||||
Outputs: outputs,
|
||||
Build: p.Config.Build,
|
||||
Generated: generated,
|
||||
Inventory: inventory,
|
||||
Validation: validation,
|
||||
Music: EffectiveMusicConfig{
|
||||
Prefixes: musicPrefixes,
|
||||
Tools: p.Config.Music.Tools,
|
||||
StageRoot: musicStageRoot,
|
||||
CreditsRoot: musicCreditsRoot,
|
||||
CreditsOverlay: musicCreditsOverlay,
|
||||
MaxStemLength: musicMaxStemLength,
|
||||
NamingScheme: musicNamingScheme,
|
||||
OutputExtension: musicOutputExtension,
|
||||
ConvertExtensions: musicConvertExtensions,
|
||||
Datasets: effectiveDatasets,
|
||||
},
|
||||
TopData: top,
|
||||
Extract: extract,
|
||||
Autogen: EffectiveAutogenConfig{
|
||||
Producers: slices.Clone(p.Config.Autogen.Producers),
|
||||
Consumers: slices.Clone(p.Config.Autogen.Consumers),
|
||||
Cache: EffectiveAutogenCacheConfig{
|
||||
Root: expandPathTemplate(defaultString(p.Config.Autogen.Cache.Root, DefaultAutogenCacheRoot), paths),
|
||||
MaxAge: defaultString(p.Config.Autogen.Cache.MaxAge, DefaultAutogenCacheMaxAge.String()),
|
||||
RefreshEnv: defaultString(p.Config.Autogen.Cache.RefreshEnv, DefaultAutogenRefreshEnv),
|
||||
PartsManifestRefreshEnv: DefaultPartsManifestRefreshEnv,
|
||||
},
|
||||
ReleaseSource: EffectiveAutogenReleaseSourceConfig{
|
||||
Provider: defaultString(p.Config.Autogen.ReleaseSource.Provider, DefaultAutogenReleaseProvider),
|
||||
ServerURLEnv: defaultString(p.Config.Autogen.ReleaseSource.ServerURLEnv, DefaultAutogenServerURLEnv),
|
||||
RepoEnv: defaultString(p.Config.Autogen.ReleaseSource.RepoEnv, DefaultAutogenRepoEnv),
|
||||
},
|
||||
},
|
||||
HAKs: slices.Clone(p.Config.HAKs),
|
||||
Provenance: provenance,
|
||||
}
|
||||
effective.Overrides = activeOverrides(effective)
|
||||
return effective
|
||||
}
|
||||
|
||||
func cloneTopDataValueEncodings(values []TopDataValueEncodingConfig) []TopDataValueEncodingConfig {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]TopDataValueEncodingConfig, len(values))
|
||||
copy(out, values)
|
||||
return out
|
||||
}
|
||||
|
||||
func cloneTopDataValueDefaults(values []TopDataValueDefaultConfig) []TopDataValueDefaultConfig {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]TopDataValueDefaultConfig, len(values))
|
||||
copy(out, values)
|
||||
return out
|
||||
}
|
||||
|
||||
func cloneTopDataRowGeneration(values []TopDataRowGenerationConfig) []TopDataRowGenerationConfig {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]TopDataRowGenerationConfig, len(values))
|
||||
copy(out, values)
|
||||
for index := range out {
|
||||
if strings.TrimSpace(out[index].Dataset) == "" {
|
||||
out[index].Dataset = strings.TrimSpace(out[index].Namespace)
|
||||
}
|
||||
if strings.TrimSpace(out[index].Namespace) == "" {
|
||||
out[index].Namespace = strings.TrimSpace(out[index].Dataset)
|
||||
}
|
||||
if strings.TrimSpace(out[index].Mode) == "" {
|
||||
out[index].Mode = "after_base"
|
||||
}
|
||||
out[index].Dataset = filepath.ToSlash(strings.TrimSpace(out[index].Dataset))
|
||||
out[index].Namespace = filepath.ToSlash(strings.TrimSpace(out[index].Namespace))
|
||||
out[index].Mode = strings.TrimSpace(out[index].Mode)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func cloneTopDataRowExtensions(values []TopDataRowExtensionConfig) []TopDataRowExtensionConfig {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]TopDataRowExtensionConfig, len(values))
|
||||
copy(out, values)
|
||||
for index := range out {
|
||||
out[index].Dataset = filepath.ToSlash(strings.TrimSpace(out[index].Dataset))
|
||||
out[index].Mode = strings.TrimSpace(out[index].Mode)
|
||||
out[index].LevelColumn = strings.TrimSpace(out[index].LevelColumn)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func cloneTopDataClassFeatInjections(value TopDataClassFeatInjectionConfig) TopDataClassFeatInjectionConfig {
|
||||
out := TopDataClassFeatInjectionConfig{
|
||||
GlobalFeats: slices.Clone(value.GlobalFeats),
|
||||
ClassSkillMasterfeats: slices.Clone(value.ClassSkillMasterfeats),
|
||||
}
|
||||
for index := range out.GlobalFeats {
|
||||
out.GlobalFeats[index].RequirePresent = slices.Clone(value.GlobalFeats[index].RequirePresent)
|
||||
out.GlobalFeats[index].UnlessPresent = slices.Clone(value.GlobalFeats[index].UnlessPresent)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (p *Project) EffectiveConfigJSON() ([]byte, error) {
|
||||
return json.MarshalIndent(p.EffectiveConfig(), "", " ")
|
||||
}
|
||||
|
||||
func (p *Project) ExplainConfig(key string) (any, ConfigValueProvenance, bool) {
|
||||
effective := p.EffectiveConfig()
|
||||
value, ok := lookupEffectiveValue(effective, key)
|
||||
if !ok {
|
||||
return nil, ConfigValueProvenance{}, false
|
||||
}
|
||||
prov := effective.Provenance[key]
|
||||
if prov.Source == "" {
|
||||
prov = ConfigValueProvenance{Source: "yaml", Detail: p.ConfigSource.Name}
|
||||
}
|
||||
return value, prov, true
|
||||
}
|
||||
|
||||
func lookupEffectiveValue(effective EffectiveConfig, key string) (any, bool) {
|
||||
raw, err := json.Marshal(effective)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
var data any
|
||||
if err := json.Unmarshal(raw, &data); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
current := data
|
||||
for _, part := range strings.Split(key, ".") {
|
||||
object, ok := current.(map[string]any)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
current, ok = object[part]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return current, true
|
||||
}
|
||||
|
||||
func markMissingDefaults(provenance ConfigProvenance) {
|
||||
defaults := map[string]string{
|
||||
"paths.build": "build",
|
||||
"paths.cache": ".cache",
|
||||
"paths.tools": "tools",
|
||||
"outputs.module_archive": DefaultModuleArchiveTemplate,
|
||||
"outputs.hak_manifest": DefaultHAKManifest,
|
||||
"outputs.hak_archive": DefaultHAKArchiveTemplate,
|
||||
"generated_assets.topdata_2da": "no generated topdata 2DA assets",
|
||||
"inventory.source_extensions": "NWN source resource extensions",
|
||||
"inventory.asset_extensions": "NWN asset resource extensions",
|
||||
"inventory.source_json_pattern": DefaultSourceJSONPattern,
|
||||
"validation.profile": DefaultValidationProfile,
|
||||
"validation.builtin_script_prefixes": "NWN built-in script prefixes",
|
||||
"validation.required_fields": "NWN required GFF fields",
|
||||
"music.stage_root": DefaultMusicStageRoot,
|
||||
"music.credits_root": DefaultCreditsRoot,
|
||||
"music.credits_overlay": DefaultCreditsOverlayFile,
|
||||
"music.max_stem_length": "16",
|
||||
"music.naming_scheme": DefaultMusicNamingScheme,
|
||||
"music.output_extension": DefaultMusicOutputExtension,
|
||||
"music.convert_extensions": strings.Join(music.DefaultConvertExtensions(), ", "),
|
||||
"topdata.build": DefaultTopDataBuild,
|
||||
"topdata.compiled_2da_dir": DefaultTopDataCompiled2DADir,
|
||||
"topdata.compiled_tlk": DefaultTopDataCompiledTLK,
|
||||
"topdata.package_hak": DefaultTopDataPackageHAK,
|
||||
"topdata.package_tlk": DefaultTopDataPackageTLK,
|
||||
"topdata.wiki.output_root": DefaultTopDataWikiOutputRoot,
|
||||
"topdata.wiki.pages_dir": DefaultTopDataWikiPagesDir,
|
||||
"topdata.wiki.state_file": DefaultTopDataWikiStateFile,
|
||||
"topdata.wiki.managed_namespaces": "NWN topdata wiki namespaces",
|
||||
"topdata.wiki.deploy_manifest": DefaultWikiDeployManifest,
|
||||
"topdata.wiki.deploy_edit_summary": DefaultWikiDeployEditSummary,
|
||||
"topdata.wiki.source": DefaultTopDataWikiSource,
|
||||
"topdata.wiki.renderer": DefaultTopDataWikiRenderer,
|
||||
"topdata.wiki.link_strategy": DefaultTopDataWikiLinkStrategy,
|
||||
"topdata.wiki.namespaces_file": DefaultTopDataWikiNamespacesFile,
|
||||
"topdata.wiki.tables_file": DefaultTopDataWikiTablesFile,
|
||||
"topdata.wiki.visibility_file": DefaultTopDataWikiVisibilityFile,
|
||||
"topdata.wiki.data_file": DefaultTopDataWikiDataFile,
|
||||
"topdata.wiki.page_paths_file": DefaultTopDataWikiPagePathsFile,
|
||||
"topdata.wiki.templates_dir": DefaultTopDataWikiTemplatesDir,
|
||||
"topdata.wiki.page_templates_dir": DefaultTopDataWikiPageTemplatesDir,
|
||||
"topdata.wiki.manual_sections_dir": DefaultTopDataWikiManualSectionsDir,
|
||||
"topdata.wiki.manual_sections_file": DefaultTopDataWikiManualSectionsFile,
|
||||
"topdata.wiki.page_index_file": DefaultTopDataWikiPageIndexFile,
|
||||
"topdata.wiki.title_prefix_min_length": "3",
|
||||
"topdata.wiki.status_pages": "true",
|
||||
"topdata.wiki.status_listing_scope": DefaultTopDataWikiStatusListingScope,
|
||||
"topdata.wiki.alignment_links.link_format": DefaultTopDataWikiAlignmentLinkFormat,
|
||||
"topdata.wiki.stale_pages.default": DefaultTopDataWikiStaleDefault,
|
||||
"topdata.wiki.stale_pages.live_cleanup": DefaultTopDataWikiStaleLiveCleanup,
|
||||
"topdata.wiki.managed_region.marker_format": DefaultTopDataWikiMarkerFormat,
|
||||
"topdata.wiki.managed_region.page_marker_prefix": DefaultTopDataWikiPageMarkerPrefix,
|
||||
"extract.layout": DefaultExtractLayout,
|
||||
"extract.hak_discovery": DefaultExtractHAKDiscovery,
|
||||
"extract.archives": DefaultModuleArchiveTemplate,
|
||||
"extract.cleanup_stale": "true",
|
||||
"extract.consume_archives": "false",
|
||||
"autogen.cache.root": DefaultAutogenCacheRoot,
|
||||
"autogen.cache.max_age": DefaultAutogenCacheMaxAge.String(),
|
||||
"autogen.cache.refresh_env": DefaultAutogenRefreshEnv,
|
||||
"autogen.cache.parts_manifest_refresh_env": DefaultPartsManifestRefreshEnv,
|
||||
"autogen.release_source.provider": DefaultAutogenReleaseProvider,
|
||||
"autogen.release_source.server_url_env": DefaultAutogenServerURLEnv,
|
||||
"autogen.release_source.repo_env": DefaultAutogenRepoEnv,
|
||||
}
|
||||
for key, detail := range defaults {
|
||||
if _, ok := provenance[key]; !ok {
|
||||
provenance[key] = ConfigValueProvenance{Source: "toolkit default", Detail: detail}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cloneProvenance(input ConfigProvenance) ConfigProvenance {
|
||||
out := ConfigProvenance{}
|
||||
for key, value := range input {
|
||||
out[key] = value
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func defaultString(value, fallback string) string {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed == "" {
|
||||
return fallback
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
|
||||
func defaultInt(value, fallback int) int {
|
||||
if value == 0 {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func defaultBoolPointer(value *bool, fallback bool) *bool {
|
||||
out := fallback
|
||||
if value != nil {
|
||||
out = *value
|
||||
}
|
||||
return &out
|
||||
}
|
||||
|
||||
func defaultStringSlice(value, fallback []string) []string {
|
||||
if len(value) == 0 {
|
||||
return slices.Clone(fallback)
|
||||
}
|
||||
return slices.Clone(value)
|
||||
}
|
||||
|
||||
func defaultWikiPageTemplatesDir(cfg TopDataWikiConfig) string {
|
||||
if strings.TrimSpace(cfg.PageTemplatesDir) != "" {
|
||||
return strings.TrimSpace(cfg.PageTemplatesDir)
|
||||
}
|
||||
if strings.TrimSpace(cfg.TemplatesDir) != "" {
|
||||
return filepath.ToSlash(filepath.Join(filepath.FromSlash(strings.TrimSpace(cfg.TemplatesDir)), "pages"))
|
||||
}
|
||||
return DefaultTopDataWikiPageTemplatesDir
|
||||
}
|
||||
|
||||
func defaultWikiManualSectionsFile(cfg TopDataWikiConfig) string {
|
||||
if strings.TrimSpace(cfg.ManualSectionsFile) != "" {
|
||||
return strings.TrimSpace(cfg.ManualSectionsFile)
|
||||
}
|
||||
if strings.TrimSpace(cfg.ManualSectionsDir) != "" {
|
||||
return filepath.ToSlash(filepath.Join(filepath.FromSlash(strings.TrimSpace(cfg.ManualSectionsDir)), "default.yaml"))
|
||||
}
|
||||
return DefaultTopDataWikiManualSectionsFile
|
||||
}
|
||||
|
||||
func defaultRequiredFields(configured map[string][]string) map[string][]string {
|
||||
if len(configured) > 0 {
|
||||
return cloneStringSliceMap(normalizeRequiredFields(configured))
|
||||
}
|
||||
return DefaultRequiredFields()
|
||||
}
|
||||
|
||||
func cloneStringSliceMap(input map[string][]string) map[string][]string {
|
||||
out := make(map[string][]string, len(input))
|
||||
for key, values := range input {
|
||||
out[key] = slices.Clone(values)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func cloneStringMap(input map[string]string) map[string]string {
|
||||
if len(input) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string]string, len(input))
|
||||
for key, value := range input {
|
||||
out[key] = value
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func expandPathTemplate(template string, paths EffectivePathConfig) string {
|
||||
replacer := strings.NewReplacer(
|
||||
"{paths.source}", paths.Source,
|
||||
"{paths.assets}", paths.Assets,
|
||||
"{paths.build}", paths.Build,
|
||||
"{paths.cache}", paths.Cache,
|
||||
"{paths.tools}", paths.Tools,
|
||||
)
|
||||
return filepath.ToSlash(replacer.Replace(template))
|
||||
}
|
||||
|
||||
func expandPathTemplates(templates []string, paths EffectivePathConfig) []string {
|
||||
out := make([]string, 0, len(templates))
|
||||
for _, template := range templates {
|
||||
out = append(out, expandPathTemplate(template, paths))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (p *Project) ActiveOverrides() []ConfigOverride {
|
||||
return activeOverrides(p.EffectiveConfig())
|
||||
}
|
||||
|
||||
func activeOverrides(effective EffectiveConfig) []ConfigOverride {
|
||||
envs := map[string]string{
|
||||
"build.keep_existing_haks": effectiveBuildKeepExistingEnv,
|
||||
"autogen.cache.refresh": effective.Autogen.Cache.RefreshEnv,
|
||||
"autogen.cache.parts_manifest_refresh": effective.Autogen.Cache.PartsManifestRefreshEnv,
|
||||
"autogen.release_source.server_url": effective.Autogen.ReleaseSource.ServerURLEnv,
|
||||
"autogen.release_source.repo": effective.Autogen.ReleaseSource.RepoEnv,
|
||||
"music.ffmpeg": "SOW_FFMPEG",
|
||||
"music.ffprobe": "SOW_FFPROBE",
|
||||
"wiki.endpoint": "NODEBB_API_ENDPOINT",
|
||||
"wiki.token": "NODEBB_API_TOKEN",
|
||||
"wiki.categories": "NODEBB_WIKI_CATEGORIES",
|
||||
"release.version": "GITHUB_REF_NAME",
|
||||
}
|
||||
keys := make([]string, 0, len(envs))
|
||||
for key := range envs {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
slices.Sort(keys)
|
||||
overrides := make([]ConfigOverride, 0)
|
||||
for _, key := range keys {
|
||||
env := envs[key]
|
||||
if value := strings.TrimSpace(os.Getenv(env)); value != "" {
|
||||
if isSensitiveOverrideKey(key) || isSensitiveOverrideKey(env) {
|
||||
value = "<set>"
|
||||
}
|
||||
overrides = append(overrides, ConfigOverride{Key: key, Value: value, Source: "env:" + env})
|
||||
}
|
||||
}
|
||||
return overrides
|
||||
}
|
||||
|
||||
const effectiveBuildKeepExistingEnv = "SOW_BUILD_HAKS_KEEP_EXISTING"
|
||||
|
||||
func isSensitiveOverrideKey(key string) bool {
|
||||
lower := strings.ToLower(key)
|
||||
return strings.Contains(lower, "token") || strings.Contains(lower, "password")
|
||||
}
|
||||
|
||||
func (o EffectiveOutputConfig) ModuleArchiveName(module ModuleConfig) string {
|
||||
return strings.NewReplacer("{module.resref}", strings.TrimSpace(module.ResRef)).Replace(o.ModuleArchive)
|
||||
}
|
||||
|
||||
func (o EffectiveOutputConfig) HAKArchiveName(name string) string {
|
||||
return strings.NewReplacer("{hak.name}", strings.TrimSpace(name)).Replace(o.HAKArchive)
|
||||
}
|
||||
|
||||
func (p ConfigValueProvenance) String() string {
|
||||
if p.Detail == "" {
|
||||
return p.Source
|
||||
}
|
||||
return fmt.Sprintf("%s (%s)", p.Source, p.Detail)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user