extractNestedYAMLValue extracts the scalar value of a direct child key under a parent key in raw YAML. It finds the parent key's block (by indentation), determines the direct-child indent level from the first non-blank line inside the block, and only matches keys at that exact indent level. This pre
(yamlContent, parentKey, childKey string)
| 519 | // the first non-blank line inside the block, and only matches keys at that exact indent level. |
| 520 | // This prevents false matches against grandchildren that share the same key name. |
| 521 | func extractNestedYAMLValue(yamlContent, parentKey, childKey string) string { |
| 522 | lines := strings.Split(yamlContent, "\n") |
| 523 | |
| 524 | escapedParent := regexp.QuoteMeta(parentKey) |
| 525 | parentPattern := regexp.MustCompile(`^(\s*)` + escapedParent + `[ \t]*:`) |
| 526 | escapedChild := regexp.QuoteMeta(childKey) |
| 527 | |
| 528 | parentIndent := -1 |
| 529 | childIndent := -1 // indent of direct children (set on first non-blank line inside the block) |
| 530 | inParentBlock := false |
| 531 | |
| 532 | for _, line := range lines { |
| 533 | if !inParentBlock { |
| 534 | if match := parentPattern.FindStringSubmatch(line); match != nil { |
| 535 | parentIndent = len(match[1]) |
| 536 | inParentBlock = true |
| 537 | } |
| 538 | continue |
| 539 | } |
| 540 | |
| 541 | // Inside parent block: skip blank lines |
| 542 | if strings.TrimSpace(line) == "" { |
| 543 | continue |
| 544 | } |
| 545 | lineIndent := len(line) - len(strings.TrimLeft(line, " \t")) |
| 546 | |
| 547 | // Left parent block if indentation returned to parent level or less |
| 548 | if lineIndent <= parentIndent { |
| 549 | break |
| 550 | } |
| 551 | |
| 552 | // Establish the direct-child indentation from the first non-blank child line |
| 553 | if childIndent == -1 { |
| 554 | childIndent = lineIndent |
| 555 | } |
| 556 | |
| 557 | // Only match keys at the direct-child indent level (not grandchildren deeper) |
| 558 | if lineIndent != childIndent { |
| 559 | continue |
| 560 | } |
| 561 | |
| 562 | // Try to match child key with its value (single-quoted, double-quoted, unquoted). |
| 563 | childPrefix := `^\s+` + escapedChild + `[ \t]*:[ \t]*` |
| 564 | reSingle := regexp.MustCompile(childPrefix + `'([^'\n]+)'`) |
| 565 | if match := reSingle.FindStringSubmatch(line); len(match) >= 2 { |
| 566 | return strings.TrimSpace(match[1]) |
| 567 | } |
| 568 | reDouble := regexp.MustCompile(childPrefix + `"([^"\n]+)"`) |
| 569 | if match := reDouble.FindStringSubmatch(line); len(match) >= 2 { |
| 570 | return strings.TrimSpace(match[1]) |
| 571 | } |
| 572 | reUnquoted := regexp.MustCompile(childPrefix + `([^'"\n#][^\n#]*?)(?:[ \t]*#.*)?$`) |
| 573 | if match := reUnquoted.FindStringSubmatch(line); len(match) >= 2 { |
| 574 | return strings.TrimSpace(match[1]) |
| 575 | } |
| 576 | } |
| 577 | |
| 578 | return "" |
no test coverage detected