@@ -76,55 +76,96 @@ func (u *DefaultConfigUpdater) AddExtension(path string, extension config.Extens
7676// if the key was not present.
7777func replaceYAMLSection (content , key , replacement string ) (string , bool ) {
7878 lines := strings .Split (content , "\n " )
79- prefix := key + ":"
8079
81- startIdx := - 1
80+ startIdx := findTopLevelKeyIndex (lines , key )
81+ if startIdx == - 1 {
82+ return content , false
83+ }
84+
85+ endIdx := findSectionEnd (lines , startIdx )
86+
87+ return buildReplacedContent (lines , startIdx , endIdx , replacement ), true
88+ }
89+
90+ // findTopLevelKeyIndex returns the line index of the first top-level YAML key
91+ // matching key, or -1 if not found. A top-level key has no leading whitespace
92+ // and is followed by a colon.
93+ func findTopLevelKeyIndex (lines []string , key string ) int {
94+ prefix := key + ":"
8295 for i , line := range lines {
96+ if ! isTopLevelLine (line ) {
97+ continue
98+ }
8399 trimmed := strings .TrimSpace (line )
84- // Match only top-level keys (no leading whitespace) that start with
85- // the key name followed by a colon.
86- if len (line ) > 0 && line [0 ] != ' ' && line [0 ] != '\t' &&
87- (trimmed == prefix || strings .HasPrefix (trimmed , prefix + " " ) || strings .HasPrefix (trimmed , prefix + "\n " )) {
88- startIdx = i
89- break
100+ if trimmed == prefix || strings .HasPrefix (trimmed , prefix + " " ) || strings .HasPrefix (trimmed , prefix + "\n " ) {
101+ return i
90102 }
91103 }
104+ return - 1
105+ }
92106
93- if startIdx == - 1 {
94- return content , false
95- }
107+ // isTopLevelLine reports whether a line is non-empty and has no leading
108+ // whitespace, indicating a top-level YAML key.
109+ func isTopLevelLine (line string ) bool {
110+ return len (line ) > 0 && line [0 ] != ' ' && line [0 ] != '\t'
111+ }
112+
113+ // isBlankLine reports whether a line is empty or contains only whitespace.
114+ func isBlankLine (line string ) bool {
115+ return strings .TrimSpace (line ) == ""
116+ }
96117
97- // Determine the end of the section: all subsequent lines that are indented
98- // or blank belong to this section. A non-indented, non-blank line marks
99- // the start of the next section.
118+ // isIndentedLine reports whether a non-empty line starts with whitespace.
119+ func isIndentedLine (line string ) bool {
120+ return len (line ) > 0 && (line [0 ] == ' ' || line [0 ] == '\t' )
121+ }
122+
123+ // findSectionEnd returns the index of the first line after the section that
124+ // starts at startIdx. Indented and blank lines belong to the section. A blank
125+ // line followed by a non-indented line (or EOF) marks the section boundary.
126+ func findSectionEnd (lines []string , startIdx int ) int {
100127 endIdx := startIdx + 1
101128 for endIdx < len (lines ) {
102129 line := lines [endIdx ]
103- if line == "" || strings .TrimSpace (line ) == "" {
104- // Blank lines might be part of the section or a separator.
105- // Look ahead to see if the next non-blank line is still indented.
106- ahead := endIdx + 1
107- for ahead < len (lines ) && strings .TrimSpace (lines [ahead ]) == "" {
108- ahead ++
109- }
110- if ahead < len (lines ) && len (lines [ahead ]) > 0 && (lines [ahead ][0 ] == ' ' || lines [ahead ][0 ] == '\t' ) {
111- // Still inside the section.
130+ if isBlankLine (line ) {
131+ if ahead := skipBlankLines (lines , endIdx + 1 ); isIndentedLine (safeLineAt (lines , ahead )) {
112132 endIdx = ahead + 1
113133 continue
114134 }
115- // Blank line(s) followed by a top-level key or EOF: section ends here.
116135 break
117136 }
118- if line [0 ] != ' ' && line [0 ] != '\t' {
119- // Next top-level key: section ends before this line.
137+ if ! isIndentedLine (line ) {
120138 break
121139 }
122140 endIdx ++
123141 }
142+ return endIdx
143+ }
144+
145+ // skipBlankLines advances from index start past any consecutive blank lines
146+ // and returns the index of the first non-blank line (or len(lines) if none).
147+ func skipBlankLines (lines []string , start int ) int {
148+ i := start
149+ for i < len (lines ) && isBlankLine (lines [i ]) {
150+ i ++
151+ }
152+ return i
153+ }
124154
125- // Build the result: lines before the section + replacement + lines after.
155+ // safeLineAt returns lines[i] if i is within bounds, or an empty string
156+ // otherwise. This avoids index-out-of-range checks at call sites.
157+ func safeLineAt (lines []string , i int ) string {
158+ if i < len (lines ) {
159+ return lines [i ]
160+ }
161+ return ""
162+ }
163+
164+ // buildReplacedContent assembles the final content by concatenating lines
165+ // before startIdx, the replacement text, and lines from endIdx onward.
166+ func buildReplacedContent (lines []string , startIdx , endIdx int , replacement string ) string {
126167 var result strings.Builder
127- for i := 0 ; i < startIdx ; i ++ {
168+ for i := range startIdx {
128169 result .WriteString (lines [i ])
129170 result .WriteString ("\n " )
130171 }
@@ -136,6 +177,5 @@ func replaceYAMLSection(content, key, replacement string) (string, bool) {
136177 result .WriteString ("\n " )
137178 }
138179 }
139-
140- return result .String (), true
180+ return result .String ()
141181}
0 commit comments