Automated parts sorting

This commit is contained in:
2026-05-17 14:21:05 +02:00
parent d7704c5f68
commit 574349b929
7 changed files with 414 additions and 8 deletions
+107
View File
@@ -291,10 +291,34 @@ type AutogenConsumerConfig struct {
Root string `json:"root" yaml:"root"`
Include []string `json:"include" yaml:"include"`
Derive AutogenDeriveConfig `json:"derive" yaml:"derive"`
PartsRows PartsRowsConfig `json:"parts_rows" yaml:"parts_rows"`
Manifest AutogenManifestConfig `json:"manifest" yaml:"manifest"`
LocalOverrideRoot string `json:"local_override_root,omitempty" yaml:"local_override_root,omitempty"`
}
type PartsRowsConfig struct {
RowDefaults map[string]string `json:"row_defaults" yaml:"row_defaults"`
ACBonus PartsRowsACBonusConfig `json:"acbonus" yaml:"acbonus"`
Datasets map[string]PartsRowsDatasetConfig `json:"datasets" yaml:"datasets"`
}
type PartsRowsDatasetConfig struct {
ACBonus PartsRowsACBonusPolicy `json:"acbonus" yaml:"acbonus"`
}
type PartsRowsACBonusConfig struct {
Default PartsRowsACBonusPolicy `json:"default" yaml:"default"`
Datasets map[string]PartsRowsACBonusPolicy `json:"datasets" yaml:"datasets"`
}
type PartsRowsACBonusPolicy struct {
Strategy string `json:"strategy" yaml:"strategy"`
MaxRowID int `json:"max_row_id" yaml:"max_row_id"`
Divisor int `json:"divisor" yaml:"divisor"`
Format string `json:"format" yaml:"format"`
Value string `json:"value" yaml:"value"`
}
type AutogenDeriveConfig struct {
Kind string `json:"kind" yaml:"kind"`
GroupFrom string `json:"group_from,omitempty" yaml:"group_from,omitempty"`
@@ -1363,10 +1387,93 @@ func validateGeneratedConfig(cfg GeneratedConfig) []error {
}
failures = append(failures, validateGlobList(fieldPrefix+".autogen.include", top2da.Autogen.Include)...)
failures = append(failures, validateAutogenDeriveConfig(fieldPrefix+".autogen.derive", top2da.Autogen.Derive)...)
if strings.TrimSpace(top2da.Autogen.Mode) == "parts_rows" && partsRowsConfigConfigured(top2da.Autogen.PartsRows) {
failures = append(failures, validatePartsRowsConfig(fieldPrefix+".autogen.parts_rows", top2da.Autogen.PartsRows)...)
}
}
return failures
}
func partsRowsConfigConfigured(cfg PartsRowsConfig) bool {
if len(cfg.RowDefaults) > 0 {
return true
}
if strings.TrimSpace(cfg.ACBonus.Default.Strategy) != "" {
return true
}
if len(cfg.ACBonus.Datasets) > 0 {
return true
}
if len(cfg.Datasets) > 0 {
return true
}
return false
}
func validatePartsRowsConfig(fieldPrefix string, cfg PartsRowsConfig) []error {
var failures []error
failures = append(failures, validatePartsRowsACBonusPolicy(fieldPrefix+".acbonus.default", cfg.ACBonus.Default, false)...)
for dataset, policy := range cfg.ACBonus.Datasets {
dataset = strings.TrimSpace(dataset)
if dataset == "" {
failures = append(failures, fmt.Errorf("%s.acbonus.datasets contains an empty dataset key", fieldPrefix))
continue
}
failures = append(failures, validatePartsRowsACBonusPolicy(fieldPrefix+".acbonus.datasets."+dataset, policy, true)...)
}
for dataset, datasetCfg := range cfg.Datasets {
dataset = strings.TrimSpace(dataset)
if dataset == "" {
failures = append(failures, fmt.Errorf("%s.datasets contains an empty dataset key", fieldPrefix))
continue
}
failures = append(failures, validatePartsRowsACBonusPolicy(fieldPrefix+".datasets."+dataset+".acbonus", datasetCfg.ACBonus, true)...)
}
return failures
}
func validatePartsRowsACBonusPolicy(fieldPrefix string, policy PartsRowsACBonusPolicy, allowEmpty bool) []error {
strategy := strings.TrimSpace(policy.Strategy)
if strategy == "" {
if allowEmpty {
return nil
}
return []error{fmt.Errorf("%s.strategy is required", fieldPrefix)}
}
switch strategy {
case "descending_row_id_sort_key":
var failures []error
if policy.MaxRowID <= 0 {
failures = append(failures, fmt.Errorf("%s.max_row_id must be greater than 0", fieldPrefix))
}
if policy.Divisor <= 0 {
failures = append(failures, fmt.Errorf("%s.divisor must be greater than 0", fieldPrefix))
}
if err := validatePartsRowsFormat(policy.Format); err != nil {
failures = append(failures, fmt.Errorf("%s.format %q is invalid: %w", fieldPrefix, policy.Format, err))
}
return failures
case "fixed":
if strings.TrimSpace(policy.Value) == "" {
return []error{fmt.Errorf("%s.value is required for fixed strategy", fieldPrefix)}
}
return nil
default:
return []error{fmt.Errorf("%s.strategy %q is not supported", fieldPrefix, policy.Strategy)}
}
}
func validatePartsRowsFormat(format string) error {
if strings.TrimSpace(format) == "" {
return fmt.Errorf("must not be empty")
}
formatted := fmt.Sprintf(format, 1.25)
if strings.Contains(formatted, "%!") {
return fmt.Errorf("must format a numeric value")
}
return nil
}
func validateMusicConfig(cfg MusicConfig) []error {
var failures []error
if cfg.Defaults != nil {