Skip to content

Commit c2bc380

Browse files
refactor: remove find_key_colon and extract_inline_comment to match yamlpatch behavior
Simplify find_key_colon to naive str::find(':'), consistent with yamlpatch's own Replace implementation. Remove extract_inline_comment entirely since yamlpatch doesn't preserve inline comments during Replace either. Removes ~90 lines of quote-aware parsing logic. When yamlpatch fixes these limitations upstream, yamltrip will inherit the fixes uniformly across both scalar and complex replaces.
1 parent 4d7aac4 commit c2bc380

2 files changed

Lines changed: 12 additions & 100 deletions

File tree

src/document.rs

Lines changed: 12 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -247,11 +247,10 @@ fn apply_complex_replace(
247247
// Find the colon separating key from value
248248
let colon_pos = find_key_colon(content_with_ws);
249249

250-
let (key_part, value_first_line) = match colon_pos {
250+
let key_part = match colon_pos {
251251
Some(pos) => {
252252
let key = &content_with_ws[..pos + 1]; // through the colon
253-
let rest = &content_with_ws[pos + 1..];
254-
(key.to_string(), rest.to_string())
253+
key.to_string()
255254
}
256255
None => {
257256
// No colon found — bare value (e.g. sequence item)
@@ -281,10 +280,6 @@ fn apply_complex_replace(
281280
}
282281
};
283282

284-
// Detect inline comment on the first line of the value part
285-
let first_line = value_first_line.lines().next().unwrap_or("");
286-
let comment = extract_inline_comment(first_line);
287-
288283
// Compute base indentation from the feature's actual position
289284
let feat_start = feature.location.byte_span.0;
290285
let line_start = source[..feat_start]
@@ -304,11 +299,10 @@ fn apply_complex_replace(
304299
// Re-indent each line of the serialized value
305300
let indented_value = indent_block(trimmed, &value_indent);
306301

307-
// Assemble: key: [# comment]\n indented_value
308-
let replacement = match comment {
309-
Some(c) => format!("{} {}\n{}", key_part, c, indented_value),
310-
None => format!("{}\n{}", key_part, indented_value),
311-
};
302+
// Assemble: key:\n indented_value
303+
// NOTE: Inline comments on the value line are not preserved, consistent
304+
// with yamlpatch's own Replace behavior for scalar values.
305+
let replacement = format!("{}\n{}", key_part, indented_value);
312306

313307
// Replace in source
314308
let mut result = source.to_string();
@@ -321,91 +315,14 @@ fn apply_complex_replace(
321315
yamlpath::Document::new(result).map_err(|e| format!("Failed to re-parse YAML: {e}"))
322316
}
323317

324-
/// Find the first structural colon (key-value separator) in a YAML fragment,
325-
/// skipping colons inside single- or double-quoted strings.
318+
/// Find the first colon (key-value separator) in a YAML fragment.
326319
///
327-
/// Precondition: `content` must be valid YAML text extracted from a yamlpath
328-
/// feature. Unterminated quotes cannot occur in practice because yamlpath
329-
/// only produces features from successfully parsed documents.
320+
/// Uses a naive `find(':')`, consistent with yamlpatch's own Replace
321+
/// implementation. This means colons inside quoted keys will be
322+
/// misidentified — a known yamlpatch limitation that will be fixed
323+
/// uniformly when yamlpatch addresses it.
330324
fn find_key_colon(content: &str) -> Option<usize> {
331-
let bytes = content.as_bytes();
332-
let mut i = 0;
333-
while i < bytes.len() {
334-
match bytes[i] {
335-
b'\'' => {
336-
// YAML '' escape (literal single quote) is handled correctly here:
337-
// the first ' closes, the second immediately reopens, producing
338-
// the same result as explicit escape handling since '' is the
339-
// only escape sequence in single-quoted YAML strings.
340-
i += 1;
341-
while i < bytes.len() && bytes[i] != b'\'' {
342-
i += 1;
343-
}
344-
if i < bytes.len() {
345-
i += 1;
346-
}
347-
}
348-
b'"' => {
349-
i += 1;
350-
while i < bytes.len() {
351-
if bytes[i] == b'\\' {
352-
i = (i + 2).min(bytes.len());
353-
} else if bytes[i] == b'"' {
354-
break;
355-
} else {
356-
i += 1;
357-
}
358-
}
359-
if i < bytes.len() {
360-
i += 1;
361-
}
362-
}
363-
b':' => {
364-
let next = bytes.get(i + 1);
365-
if matches!(next, Some(b' ') | Some(b'\n') | Some(b'\r') | None) {
366-
return Some(i);
367-
}
368-
i += 1;
369-
}
370-
_ => {
371-
i += 1;
372-
}
373-
}
374-
}
375-
None
376-
}
377-
378-
fn extract_inline_comment(line: &str) -> Option<&str> {
379-
let bytes = line.as_bytes();
380-
let mut i = 0;
381-
let mut in_single_quote = false;
382-
let mut in_double_quote = false;
383-
384-
while i < bytes.len() {
385-
match bytes[i] {
386-
b'\'' if !in_double_quote => {
387-
// YAML '' escape: two toggles (true→false→true) is a no-op,
388-
// which is correct since '' is the only escape in single-quoted strings.
389-
in_single_quote = !in_single_quote;
390-
}
391-
b'"' if !in_single_quote => {
392-
in_double_quote = !in_double_quote;
393-
}
394-
b'\\' if in_double_quote => {
395-
i += 1;
396-
}
397-
b'#' if !in_single_quote
398-
&& !in_double_quote
399-
&& i > 0
400-
&& (bytes[i - 1] == b' ' || bytes[i - 1] == b'\t') =>
401-
{
402-
return Some(&line[i..]);
403-
}
404-
_ => {}
405-
}
406-
i += 1;
407-
}
408-
None
325+
content.find(':')
409326
}
410327

411328
fn indent_block(content: &str, indent: &str) -> String {

tests/test_document.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -294,11 +294,6 @@ def test_replace_flow_mapping_with_dict(self):
294294
doc2 = doc.replace("config", value={"x": 10})
295295
assert doc2["config"] == {"x": 10}
296296

297-
def test_replace_quoted_key_with_colon(self):
298-
doc = Document('"host:port": old\n')
299-
doc2 = doc.replace("host:port", value={"h": "localhost", "p": 8080})
300-
assert doc2["host:port"] == {"h": "localhost", "p": 8080}
301-
302297
def test_replace_key_with_hash_in_value(self):
303298
doc = Document("color: '#ff0000'\n")
304299
doc2 = doc.replace("color", value={"r": 255, "g": 0, "b": 0})

0 commit comments

Comments
 (0)