ECL, default scale, and preferred alignment entries

This commit is contained in:
2026-05-24 17:58:33 +02:00
parent cc9996cd69
commit a0c3c89049
9 changed files with 551 additions and 2 deletions
+177
View File
@@ -29,6 +29,7 @@ type nativeDataset struct {
Spec datasetTextSpec
Columns []string
ValueEncodings map[string]project.TopDataValueEncodingConfig
ValueDefaults map[string]any
CompareReference bool
}
@@ -238,6 +239,7 @@ func buildNativeUnchecked(p *project.Project, opts NativeBuildOptions, progress
return BuildResult{}, err
}
datasets = applyTopDataValueEncodings(datasets, p.Config.TopData.ValueEncodings)
datasets = applyTopDataValueDefaults(datasets, p.Config.TopData.ValueDefaults)
registryDatasets, err := collectGeneratedRegistryDatasets(dataDir)
if err != nil {
return BuildResult{}, err
@@ -427,6 +429,25 @@ func applyTopDataValueEncodings(datasets []nativeDataset, encodings []project.To
return out
}
func applyTopDataValueDefaults(datasets []nativeDataset, defaults []project.TopDataValueDefaultConfig) []nativeDataset {
if len(defaults) == 0 {
return datasets
}
out := append([]nativeDataset(nil), datasets...)
for index := range out {
for _, valueDefault := range defaults {
if filepath.ToSlash(strings.TrimSpace(valueDefault.Dataset)) != out[index].Name {
continue
}
if out[index].ValueDefaults == nil {
out[index].ValueDefaults = map[string]any{}
}
out[index].ValueDefaults[strings.ToLower(strings.TrimSpace(valueDefault.Column))] = valueDefault.Value
}
}
return out
}
func nativeCompileGroup(datasetName string) string {
head, _, ok := strings.Cut(datasetName, "/")
if !ok {
@@ -3682,6 +3703,17 @@ func (r *valueResolver) resolveField(row map[string]any, field string, stack []s
}
value, ok := lookupField(row, field)
if !ok {
if defaultValue, hasDefault := r.valueDefaultForField(field); hasDefault {
value = defaultValue
ok = true
}
}
if ok && isNullLike(value) {
if defaultValue, hasDefault := r.valueDefaultForField(field); hasDefault {
value = defaultValue
}
}
if !ok {
resolved := tlkCompiledValue{Value: nullValue}
r.cache[rowKey][field] = resolved
@@ -3712,6 +3744,13 @@ func (r *valueResolver) resolveValue(row map[string]any, field string, value any
} else if ok {
return r.resolveTLKPayload(row, field, payload, stack)
}
if encoding, ok := r.valueEncodingForField(field); ok && strings.TrimSpace(encoding.Mode) == "alignment_hex_list" {
encoded, err := encodeConfiguredAlignmentHexList(value, encoding)
if err != nil {
return tlkCompiledValue{}, fmt.Errorf("field %s: %w", field, err)
}
return tlkCompiledValue{Value: encoded}, nil
}
switch typed := value.(type) {
case map[string]any:
@@ -3790,6 +3829,14 @@ func (r *valueResolver) valueEncodingForField(field string) (project.TopDataValu
return encoding, ok
}
func (r *valueResolver) valueDefaultForField(field string) (any, bool) {
if len(r.dataset.ValueDefaults) == 0 {
return nil, false
}
value, ok := r.dataset.ValueDefaults[strings.ToLower(strings.TrimSpace(field))]
return value, ok
}
func encodeConfiguredValueList(obj map[string]any, encoding project.TopDataValueEncodingConfig) (string, error) {
if strings.TrimSpace(encoding.Mode) != "packed_hex_list" {
return "", fmt.Errorf("unsupported value encoding mode %q", encoding.Mode)
@@ -3826,6 +3873,136 @@ func encodeConfiguredValueList(obj map[string]any, encoding project.TopDataValue
return formatConfiguredPackedHexList(values, encoding), nil
}
func encodeConfiguredAlignmentHexList(raw any, encoding project.TopDataValueEncodingConfig) (string, error) {
values, err := expandConfiguredAlignmentList(raw)
if err != nil {
return "", err
}
return formatConfiguredPackedHexList(values, encoding), nil
}
func expandConfiguredAlignmentList(raw any) ([]int, error) {
if isNullLike(raw) {
return []int{0}, nil
}
switch typed := raw.(type) {
case map[string]any:
for key := range typed {
if key != "list" {
return nil, fmt.Errorf("alignment list object contains unsupported key %q", key)
}
}
return expandConfiguredAlignmentList(typed["list"])
case []any:
values := make([]int, 0, len(typed))
for _, item := range typed {
value, err := parseConfiguredAlignmentValue(item)
if err != nil {
return nil, err
}
values = append(values, value)
}
if len(values) == 0 {
return []int{0}, nil
}
return values, nil
case string:
trimmed := strings.TrimSpace(typed)
if trimmed == "" {
return []int{0}, nil
}
if strings.Contains(trimmed, ",") {
parts := strings.Split(trimmed, ",")
values := make([]int, 0, len(parts))
for _, part := range parts {
value, err := parseConfiguredAlignmentValue(part)
if err != nil {
return nil, err
}
values = append(values, value)
}
return values, nil
}
value, err := parseConfiguredAlignmentValue(trimmed)
if err != nil {
return nil, err
}
return []int{value}, nil
default:
value, err := parseConfiguredAlignmentValue(raw)
if err != nil {
return nil, err
}
return []int{value}, nil
}
}
func parseConfiguredAlignmentValue(raw any) (int, error) {
switch typed := raw.(type) {
case int:
return validateConfiguredAlignmentNumeric(typed)
case float64:
if typed != float64(int(typed)) {
return 0, fmt.Errorf("alignment value %v must be an integer", typed)
}
return validateConfiguredAlignmentNumeric(int(typed))
case string:
trimmed := strings.TrimSpace(typed)
if trimmed == "" || trimmed == "0" || strings.EqualFold(trimmed, "0x00") {
return 0, nil
}
if strings.HasPrefix(strings.ToLower(trimmed), "0x") {
parsed, err := strconv.ParseInt(trimmed[2:], 16, 0)
if err != nil {
return 0, fmt.Errorf("alignment value %q must be a two-letter code or hex value", typed)
}
return validateConfiguredAlignmentNumeric(int(parsed))
}
code := strings.ToUpper(trimmed)
if code == "TN" {
return 0x01, nil
}
if len(code) != 2 {
return 0, fmt.Errorf("alignment code %q must use two letters", typed)
}
first, ok := configuredAlignmentAxisValue(code[0], true)
if !ok {
return 0, fmt.Errorf("alignment code %q has invalid law-chaos letter %q", typed, string(code[0]))
}
second, ok := configuredAlignmentAxisValue(code[1], false)
if !ok {
return 0, fmt.Errorf("alignment code %q has invalid good-evil letter %q", typed, string(code[1]))
}
return first | second, nil
default:
return 0, fmt.Errorf("alignment value %v must be a two-letter code or integer", raw)
}
}
func validateConfiguredAlignmentNumeric(value int) (int, error) {
if value < 0 || value > 31 {
return 0, fmt.Errorf("alignment value %d outside range 0-31", value)
}
return value, nil
}
func configuredAlignmentAxisValue(letter byte, lawChaos bool) (int, bool) {
switch letter {
case 'N':
return 0x01, true
case 'L':
return 0x02, lawChaos
case 'C':
return 0x04, lawChaos
case 'G':
return 0x08, !lawChaos
case 'E':
return 0x10, !lawChaos
default:
return 0, false
}
}
func formatConfiguredPackedHexList(values []int, encoding project.TopDataValueEncodingConfig) string {
if len(values) == 0 {
return ""