Support for global.json files
This commit is contained in:
+395
-57
@@ -26,6 +26,7 @@ type nativeDataset struct {
|
||||
LockPath string
|
||||
ModulesDir string
|
||||
GeneratedDir string
|
||||
GlobalPaths []string
|
||||
OutputName string
|
||||
Spec datasetTextSpec
|
||||
Columns []string
|
||||
@@ -34,6 +35,7 @@ type nativeDataset struct {
|
||||
RowGeneration string
|
||||
RowGenerationMinRow int
|
||||
CompareReference bool
|
||||
HasGlobalInjections bool
|
||||
}
|
||||
|
||||
type nativeDatasetKind string
|
||||
@@ -865,6 +867,10 @@ func discoverNativeDatasets(dataDir string) ([]nativeDataset, error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
globalPaths, err := collectGlobalJSONPaths(dataDir, rootPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lockPath := filepath.Join(rootPath, "lock.json")
|
||||
if !plainDatasetNeedsLock(lockPath, tableData) {
|
||||
lockPath = ""
|
||||
@@ -879,6 +885,7 @@ func discoverNativeDatasets(dataDir string) ([]nativeDataset, error) {
|
||||
RootPath: rootPath,
|
||||
BasePath: filePath,
|
||||
LockPath: lockPath,
|
||||
GlobalPaths: globalPaths,
|
||||
OutputName: outputName,
|
||||
Spec: specForDataset(filepath.ToSlash(relName)),
|
||||
CompareReference: optionalBool(tableData["compare_reference"], true),
|
||||
@@ -913,6 +920,9 @@ func discoverNativeDatasets(dataDir string) ([]nativeDataset, error) {
|
||||
if !strings.HasSuffix(strings.ToLower(name), ".json") || name == "lock.json" || name == "base.json" {
|
||||
continue
|
||||
}
|
||||
if isGlobalJSONName(name) {
|
||||
continue
|
||||
}
|
||||
filePath := filepath.Join(path, name)
|
||||
if err := appendPlainDataset(path, filePath, strings.TrimSuffix(name, filepath.Ext(name))); err != nil {
|
||||
return err
|
||||
@@ -948,6 +958,7 @@ func discoverNativeDatasets(dataDir string) ([]nativeDataset, error) {
|
||||
LockPath: filepath.Join(path, "lock.json"),
|
||||
ModulesDir: filepath.Join(path, "modules"),
|
||||
GeneratedDir: filepath.Join(path, "generated"),
|
||||
GlobalPaths: collectExistingGlobalJSONPath(path),
|
||||
OutputName: outputName,
|
||||
Spec: specForDataset(filepath.ToSlash(rel)),
|
||||
CompareReference: optionalBool(baseData["compare_reference"], true),
|
||||
@@ -964,7 +975,7 @@ func discoverNativeDatasets(dataDir string) ([]nativeDataset, error) {
|
||||
continue
|
||||
}
|
||||
name := entry.Name()
|
||||
if strings.HasSuffix(strings.ToLower(name), ".json") && name != "lock.json" {
|
||||
if strings.HasSuffix(strings.ToLower(name), ".json") && name != "lock.json" && !isGlobalJSONName(name) {
|
||||
filePath := filepath.Join(path, name)
|
||||
if err := appendPlainDataset(path, filePath, filepath.Join(rel, strings.TrimSuffix(name, filepath.Ext(name)))); err != nil {
|
||||
return err
|
||||
@@ -983,6 +994,47 @@ func discoverNativeDatasets(dataDir string) ([]nativeDataset, error) {
|
||||
return datasets, nil
|
||||
}
|
||||
|
||||
func isGlobalJSONName(name string) bool {
|
||||
return strings.EqualFold(strings.TrimSpace(name), "global.json")
|
||||
}
|
||||
|
||||
func collectExistingGlobalJSONPath(dir string) []string {
|
||||
path := filepath.Join(dir, "global.json")
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return []string{path}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectGlobalJSONPaths(dataDir, datasetDir string) ([]string, error) {
|
||||
rel, err := filepath.Rel(dataDir, datasetDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dirs := []string{dataDir}
|
||||
if rel != "." {
|
||||
current := dataDir
|
||||
for _, segment := range strings.Split(filepath.ToSlash(rel), "/") {
|
||||
if strings.TrimSpace(segment) == "" || segment == "." {
|
||||
continue
|
||||
}
|
||||
current = filepath.Join(current, segment)
|
||||
dirs = append(dirs, current)
|
||||
}
|
||||
}
|
||||
paths := make([]string, 0, len(dirs))
|
||||
for _, dir := range dirs {
|
||||
path := filepath.Join(dir, "global.json")
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
paths = append(paths, path)
|
||||
continue
|
||||
} else if !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func plainDatasetNeedsLock(lockPath string, tableData map[string]any) bool {
|
||||
if _, err := os.Stat(lockPath); err == nil {
|
||||
return true
|
||||
@@ -1076,12 +1128,22 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
||||
Data: obj,
|
||||
})
|
||||
}
|
||||
globalData, err := loadGlobalModules(dataset.GlobalPaths)
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
for _, module := range moduleData {
|
||||
columns, err = extendColumns(columns, module.Data, dataset.Name, module.Name)
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
}
|
||||
for _, module := range globalData {
|
||||
columns, err = extendColumns(columns, module.Data, dataset.Name, module.Name)
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
}
|
||||
rawBaseRows, ok := baseData["rows"].([]any)
|
||||
if !ok {
|
||||
return nativeCollectedDataset{}, fmt.Errorf("dataset %s: rows must be an array", dataset.Name)
|
||||
@@ -1116,7 +1178,7 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
||||
}
|
||||
|
||||
explicitModuleIDs := map[string]int{}
|
||||
for _, module := range moduleData {
|
||||
for _, module := range append(append([]nativeGeneratedModule(nil), moduleData...), globalData...) {
|
||||
explicitIDs, err := collectExplicitModuleIDs(dataset.Name, module.Name, module.Data)
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
@@ -1279,7 +1341,61 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
||||
}
|
||||
return true
|
||||
}
|
||||
applyModuleData := func(sourceLabel string, moduleData map[string]any) error {
|
||||
applyOverrideFields := func(override map[string]any, row map[string]any) (bool, string, error) {
|
||||
candidate := map[string]any{}
|
||||
for field, value := range row {
|
||||
candidate[field] = value
|
||||
}
|
||||
for field, value := range override {
|
||||
if field == "id" || field == "key" || field == "_tlk" || field == "match" {
|
||||
continue
|
||||
}
|
||||
if isMetadataField(field) {
|
||||
meta, err := parseRowMetadata(value)
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("dataset %s: %w", dataset.Name, err)
|
||||
}
|
||||
if meta != nil {
|
||||
existingMeta, _ := parseExistingMetadata(candidate)
|
||||
candidate["meta"] = mergeMetadataMaps(existingMeta, meta)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if isDeprecatedAuthoringOnlyField(dataset.Name, field) {
|
||||
if err := setLegacyWikiMetadata(candidate, field, value); err != nil {
|
||||
return false, "", fmt.Errorf("dataset %s: %w", dataset.Name, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
columnName, ok := canonicalColumn(columns, field)
|
||||
if !ok {
|
||||
if isDeprecatedAuthoringOnlyField(dataset.Name, field) {
|
||||
continue
|
||||
}
|
||||
return false, "", fmt.Errorf("dataset %s: override field %q is not a known column", dataset.Name, field)
|
||||
}
|
||||
candidate[columnName] = value
|
||||
}
|
||||
if rawKey, ok := override["key"]; ok {
|
||||
candidate["key"] = rawKey
|
||||
}
|
||||
expanded, err := expandRowAuthoringSugar(dataset, columns, override, candidate)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
keyChanged, retiredKey, err := updateOverrideRowKey(dataset.Name, row, expanded, rowByKey, lockData)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
for field, value := range expanded {
|
||||
if field == "id" || field == "_tlk" {
|
||||
continue
|
||||
}
|
||||
row[field] = value
|
||||
}
|
||||
return keyChanged, retiredKey, nil
|
||||
}
|
||||
applyModuleData := func(sourceLabel string, moduleData map[string]any, globalSource bool) error {
|
||||
if rawEntries, ok := moduleData["entries"]; ok {
|
||||
entries, ok := rawEntries.(map[string]any)
|
||||
if !ok {
|
||||
@@ -1371,6 +1487,28 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
||||
if !ok {
|
||||
return fmt.Errorf("dataset %s: override %d in %s must be an object", dataset.Name, index, sourceLabel)
|
||||
}
|
||||
if rawMatch, hasMatch := override["match"]; hasMatch {
|
||||
match, ok := rawMatch.(string)
|
||||
if !ok || !strings.EqualFold(strings.TrimSpace(match), "all") {
|
||||
return fmt.Errorf("dataset %s: override %d in %s has unsupported match value %v", dataset.Name, index, sourceLabel, rawMatch)
|
||||
}
|
||||
if !globalSource {
|
||||
return fmt.Errorf("dataset %s: override %d in %s uses match, which is only supported in global.json", dataset.Name, index, sourceLabel)
|
||||
}
|
||||
for _, row := range rows {
|
||||
keyChanged, retiredKey, err := applyOverrideFields(override, row)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if keyChanged {
|
||||
lockModified = true
|
||||
}
|
||||
if retiredKey != "" {
|
||||
retiredKeys[retiredKey] = struct{}{}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
override, err = stripMismatchedLockedOverrideID(override)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1428,48 +1566,7 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
||||
nullifyOverrideRow(row, columns, rowByKey)
|
||||
continue
|
||||
}
|
||||
candidate := map[string]any{}
|
||||
for field, value := range row {
|
||||
candidate[field] = value
|
||||
}
|
||||
for field, value := range override {
|
||||
if field == "id" || field == "key" || field == "_tlk" {
|
||||
continue
|
||||
}
|
||||
if isMetadataField(field) {
|
||||
meta, err := parseRowMetadata(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dataset %s: %w", dataset.Name, err)
|
||||
}
|
||||
if meta != nil {
|
||||
existingMeta, _ := parseExistingMetadata(candidate)
|
||||
candidate["meta"] = mergeMetadataMaps(existingMeta, meta)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if isDeprecatedAuthoringOnlyField(dataset.Name, field) {
|
||||
if err := setLegacyWikiMetadata(candidate, field, value); err != nil {
|
||||
return fmt.Errorf("dataset %s: %w", dataset.Name, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
columnName, ok := canonicalColumn(columns, field)
|
||||
if !ok {
|
||||
if isDeprecatedAuthoringOnlyField(dataset.Name, field) {
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("dataset %s: override field %q is not a known column", dataset.Name, field)
|
||||
}
|
||||
candidate[columnName] = value
|
||||
}
|
||||
if rawKey, ok := override["key"]; ok {
|
||||
candidate["key"] = rawKey
|
||||
}
|
||||
expanded, err := expandRowAuthoringSugar(dataset, columns, override, candidate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keyChanged, retiredKey, err := updateOverrideRowKey(dataset.Name, row, expanded, rowByKey, lockData)
|
||||
keyChanged, retiredKey, err := applyOverrideFields(override, row)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1479,12 +1576,6 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
||||
if retiredKey != "" {
|
||||
retiredKeys[retiredKey] = struct{}{}
|
||||
}
|
||||
for field, value := range expanded {
|
||||
if field == "id" || field == "_tlk" {
|
||||
continue
|
||||
}
|
||||
row[field] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -1508,13 +1599,13 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
||||
if module.ApplyAfterModules {
|
||||
continue
|
||||
}
|
||||
if err := applyModuleData(module.Name, module.Data); err != nil {
|
||||
if err := applyModuleData(module.Name, module.Data, false); err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, module := range moduleData {
|
||||
if err := applyModuleData(module.Name, module.Data); err != nil {
|
||||
if err := applyModuleData(module.Name, module.Data, false); err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
}
|
||||
@@ -1522,7 +1613,12 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
||||
if !module.ApplyAfterModules {
|
||||
continue
|
||||
}
|
||||
if err := applyModuleData(module.Name, module.Data); err != nil {
|
||||
if err := applyModuleData(module.Name, module.Data, false); err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
}
|
||||
for _, module := range globalData {
|
||||
if err := applyModuleData(module.Name, module.Data, true); err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
}
|
||||
@@ -3413,11 +3509,21 @@ func collectPlainDataset(dataset nativeDataset) (nativeCollectedDataset, error)
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
globalData, err := loadGlobalModules(dataset.GlobalPaths)
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
|
||||
columns, err := parseColumns(tableData, dataset.Name)
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
for _, module := range globalData {
|
||||
columns, err = extendColumns(columns, module.Data, dataset.Name, module.Name)
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
}
|
||||
rawRows, ok := tableData["rows"].([]any)
|
||||
if !ok {
|
||||
return nativeCollectedDataset{}, fmt.Errorf("dataset %s: rows must be an array", dataset.Name)
|
||||
@@ -3455,6 +3561,11 @@ func collectPlainDataset(dataset nativeDataset) (nativeCollectedDataset, error)
|
||||
}
|
||||
}
|
||||
|
||||
rows, explicitID, dataset.HasGlobalInjections, err = applyPlainDatasetGlobalInjections(dataset, columns, rows, explicitID, globalData)
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
|
||||
nextID := nextAvailableID(usedIDs)
|
||||
for index, row := range rows {
|
||||
key, hasKey := row["key"].(string)
|
||||
@@ -3491,13 +3602,237 @@ func collectPlainDataset(dataset nativeDataset) (nativeCollectedDataset, error)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func loadGlobalModules(paths []string) ([]nativeGeneratedModule, error) {
|
||||
modules := make([]nativeGeneratedModule, 0, len(paths))
|
||||
for _, path := range paths {
|
||||
obj, err := loadJSONObject(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
modules = append(modules, nativeGeneratedModule{Name: path, Data: obj})
|
||||
}
|
||||
return modules, nil
|
||||
}
|
||||
|
||||
func applyPlainDatasetGlobalInjections(dataset nativeDataset, columns []string, rows []map[string]any, explicitID []bool, modules []nativeGeneratedModule) ([]map[string]any, []bool, bool, error) {
|
||||
if len(modules) == 0 {
|
||||
return rows, explicitID, false, nil
|
||||
}
|
||||
prefixRows := []map[string]any{}
|
||||
prefixExplicit := []bool{}
|
||||
suffixRows := []map[string]any{}
|
||||
suffixExplicit := []bool{}
|
||||
hasInjections := false
|
||||
|
||||
currentRows := func() []map[string]any {
|
||||
out := make([]map[string]any, 0, len(prefixRows)+len(rows)+len(suffixRows))
|
||||
out = append(out, prefixRows...)
|
||||
out = append(out, rows...)
|
||||
out = append(out, suffixRows...)
|
||||
return out
|
||||
}
|
||||
|
||||
for _, module := range modules {
|
||||
rawInjections, ok := module.Data["injections"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
injections, ok := rawInjections.([]any)
|
||||
if !ok {
|
||||
return nil, nil, false, fmt.Errorf("dataset %s: injections in %s must be an array", dataset.Name, module.Name)
|
||||
}
|
||||
position, err := globalManifestPosition(module)
|
||||
if err != nil {
|
||||
return nil, nil, false, fmt.Errorf("dataset %s: %w", dataset.Name, err)
|
||||
}
|
||||
for index, rawInjection := range injections {
|
||||
injection, ok := rawInjection.(map[string]any)
|
||||
if !ok {
|
||||
return nil, nil, false, fmt.Errorf("dataset %s: injection %d in %s must be an object", dataset.Name, index, module.Name)
|
||||
}
|
||||
rawRow, ok := injection["row"].(map[string]any)
|
||||
if !ok {
|
||||
return nil, nil, false, fmt.Errorf("dataset %s: injection %d in %s must contain row object", dataset.Name, index, module.Name)
|
||||
}
|
||||
presentRows := currentRows()
|
||||
matches, err := globalInjectionConditionsMatch(injection, presentRows)
|
||||
if err != nil {
|
||||
return nil, nil, false, fmt.Errorf("dataset %s: injection %d in %s: %w", dataset.Name, index, module.Name, err)
|
||||
}
|
||||
if !matches {
|
||||
continue
|
||||
}
|
||||
if globalRowIdentityPresent(rawRow, presentRows) {
|
||||
continue
|
||||
}
|
||||
row, err := canonicalizeBaseRow(dataset, columns, rawRow, len(presentRows))
|
||||
if err != nil {
|
||||
return nil, nil, false, fmt.Errorf("dataset %s: injection %d in %s: %w", dataset.Name, index, module.Name, err)
|
||||
}
|
||||
explicit := false
|
||||
if _, ok := rawRow["id"]; ok {
|
||||
explicit = true
|
||||
}
|
||||
if position == "prepend" {
|
||||
prefixRows = append(prefixRows, row)
|
||||
prefixExplicit = append(prefixExplicit, explicit)
|
||||
} else {
|
||||
suffixRows = append(suffixRows, row)
|
||||
suffixExplicit = append(suffixExplicit, explicit)
|
||||
}
|
||||
hasInjections = true
|
||||
}
|
||||
}
|
||||
|
||||
outRows := make([]map[string]any, 0, len(prefixRows)+len(rows)+len(suffixRows))
|
||||
outExplicit := make([]bool, 0, len(prefixExplicit)+len(explicitID)+len(suffixExplicit))
|
||||
outRows = append(outRows, prefixRows...)
|
||||
outExplicit = append(outExplicit, prefixExplicit...)
|
||||
outRows = append(outRows, rows...)
|
||||
outExplicit = append(outExplicit, explicitID...)
|
||||
outRows = append(outRows, suffixRows...)
|
||||
outExplicit = append(outExplicit, suffixExplicit...)
|
||||
return outRows, outExplicit, hasInjections, nil
|
||||
}
|
||||
|
||||
func globalManifestPosition(module nativeGeneratedModule) (string, error) {
|
||||
raw, ok := module.Data["position"]
|
||||
if !ok || raw == nil {
|
||||
return "append", nil
|
||||
}
|
||||
position, ok := raw.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("position in %s must be a string", module.Name)
|
||||
}
|
||||
switch strings.ToLower(strings.TrimSpace(position)) {
|
||||
case "", "append":
|
||||
return "append", nil
|
||||
case "prepend":
|
||||
return "prepend", nil
|
||||
default:
|
||||
return "", fmt.Errorf("position in %s must be append or prepend", module.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func globalInjectionConditionsMatch(injection map[string]any, rows []map[string]any) (bool, error) {
|
||||
if rawRequired, ok := injection["require_present"]; ok {
|
||||
matches, err := globalConditionListMatches("require_present", rawRequired, rows, true)
|
||||
if err != nil || !matches {
|
||||
return matches, err
|
||||
}
|
||||
}
|
||||
if rawBlocked, ok := injection["unless_present"]; ok {
|
||||
matches, err := globalConditionListMatches("unless_present", rawBlocked, rows, false)
|
||||
if err != nil || !matches {
|
||||
return matches, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func globalConditionListMatches(name string, raw any, rows []map[string]any, required bool) (bool, error) {
|
||||
conditions, ok := raw.([]any)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("%s must be an array", name)
|
||||
}
|
||||
for index, rawCondition := range conditions {
|
||||
condition, ok := rawCondition.(map[string]any)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("%s[%d] must be an object", name, index)
|
||||
}
|
||||
field, _ := condition["field"].(string)
|
||||
id, _ := condition["id"].(string)
|
||||
if strings.TrimSpace(field) == "" {
|
||||
return false, fmt.Errorf("%s[%d].field is required", name, index)
|
||||
}
|
||||
if strings.TrimSpace(id) == "" {
|
||||
return false, fmt.Errorf("%s[%d].id is required", name, index)
|
||||
}
|
||||
found := globalReferencePresent(rows, field, id)
|
||||
if required && !found {
|
||||
return false, nil
|
||||
}
|
||||
if !required && found {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func globalRowIdentityPresent(row map[string]any, rows []map[string]any) bool {
|
||||
if key, ok := row["key"].(string); ok && strings.TrimSpace(key) != "" {
|
||||
normalized := normalizeGlobalReferenceID(key)
|
||||
for _, existing := range rows {
|
||||
if existingKey, ok := existing["key"].(string); ok && normalizeGlobalReferenceID(existingKey) == normalized {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
for field, value := range row {
|
||||
ref, ok := value.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
id, _ := ref["id"].(string)
|
||||
if strings.TrimSpace(id) == "" {
|
||||
continue
|
||||
}
|
||||
if globalReferencePresent(rows, field, id) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func globalReferencePresent(rows []map[string]any, field, id string) bool {
|
||||
field = strings.TrimSpace(field)
|
||||
id = normalizeGlobalReferenceID(id)
|
||||
if field == "" || id == "" {
|
||||
return false
|
||||
}
|
||||
for _, row := range rows {
|
||||
value, ok := row[field]
|
||||
if !ok {
|
||||
if canonical, exists := canonicalColumn(sortedStringMapKeys(row), field); exists {
|
||||
value = row[canonical]
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
ref, ok := value.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
existingID, _ := ref["id"].(string)
|
||||
if normalizeGlobalReferenceID(existingID) == id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func normalizeGlobalReferenceID(value string) string {
|
||||
trimmed := strings.ToLower(strings.TrimSpace(value))
|
||||
if trimmed == "" {
|
||||
return ""
|
||||
}
|
||||
parts := strings.SplitN(trimmed, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return strings.ReplaceAll(trimmed, "_", "")
|
||||
}
|
||||
return parts[0] + ":" + strings.ReplaceAll(parts[1], "_", "")
|
||||
}
|
||||
|
||||
func resolveNativeDataset(dataset nativeCollectedDataset, keyToID map[string]int, globalRowByKey map[string]map[string]any, tableRegistry resolvedTableRegistry, compiler *tlkCompiler, classFeatInjections project.TopDataClassFeatInjectionConfig, sidecars *nativeSidecarCollector) (map[string]any, error) {
|
||||
rows := dataset.Rows
|
||||
if strings.HasPrefix(filepath.ToSlash(dataset.Dataset.Name), "classes/feats/") {
|
||||
classKey := "classes:" + dataset.Dataset.Name[strings.LastIndex(dataset.Dataset.Name, "/")+1:]
|
||||
featSuccessors := buildFeatSuccessorsIndex(globalRowByKey, keyToID)
|
||||
classSkills := buildClassSkillsIndex(tableRegistry, classKey)
|
||||
expanded, err := expandClassesFeatRows(rows, keyToID, globalRowByKey, featSuccessors, classSkills, globalRowByKey, classKey, classFeatInjections)
|
||||
expanded, err := expandClassesFeatRows(rows, keyToID, globalRowByKey, featSuccessors, classSkills, globalRowByKey, classKey, classFeatInjections, !dataset.Dataset.HasGlobalInjections)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dataset %s: %w", dataset.Dataset.Name, err)
|
||||
}
|
||||
@@ -3546,9 +3881,12 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func expandClassesFeatRows(rows []map[string]any, keyToID map[string]int, rowByKey map[string]map[string]any, featSuccessors map[string]string, classSkills map[string]bool, allRowByKey map[string]map[string]any, classKey string, classFeatInjections project.TopDataClassFeatInjectionConfig) ([]map[string]any, error) {
|
||||
func expandClassesFeatRows(rows []map[string]any, keyToID map[string]int, rowByKey map[string]map[string]any, featSuccessors map[string]string, classSkills map[string]bool, allRowByKey map[string]map[string]any, classKey string, classFeatInjections project.TopDataClassFeatInjectionConfig, useConfiguredInjections bool) ([]map[string]any, error) {
|
||||
|
||||
globalRules, classSkillRules := effectiveClassFeatInjectionRules(classFeatInjections)
|
||||
globalRules, classSkillRules := []project.TopDataClassFeatGlobalRule{}, []project.TopDataClassFeatMasterfeatRule{}
|
||||
if useConfiguredInjections {
|
||||
globalRules, classSkillRules = effectiveClassFeatInjectionRules(classFeatInjections)
|
||||
}
|
||||
injected := make([]map[string]any, 0, len(globalRules)+len(classSkillRules))
|
||||
presentRefIDs := make(map[string]struct{}, len(rows))
|
||||
for _, row := range rows {
|
||||
|
||||
Reference in New Issue
Block a user