Skip to content

Commit af9e42b

Browse files
committed
feat: Add ReplaceMultipleInFile function and fix string array deserialization bug
- Add ReplaceMultipleInFile function for atomic multi-pattern replacement in single file - Fix bug in FunctionFactory.cs where string arrays included quotes (GetRawText vs GetString) - Apply fix to Array, List, and Tuple deserialization - Clean, concise output: 'File X updated: replaced N occurrences.' - Validates all patterns before making any changes (atomic operation) - Supports undo via EditHistory
1 parent 72544aa commit af9e42b

File tree

2 files changed

+75
-3
lines changed

2 files changed

+75
-3
lines changed

src/cycod/FunctionCalling/FunctionFactory.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,8 @@ private static object CreateGenericCollectionFromJsonArray(string parameterValue
318318
var collection = Array.CreateInstance(elementType, array.Length);
319319
for (int i = 0; i < array.Length; i++)
320320
{
321-
var parsed = ParseParameterValue(array[i].GetRawText(), elementType);
321+
var elementValue = array[i].ValueKind == JsonValueKind.String ? array[i].GetString()! : array[i].GetRawText();
322+
var parsed = ParseParameterValue(elementValue, elementType);
322323
if (parsed != null) collection.SetValue(parsed, i);
323324
}
324325
return collection;
@@ -329,7 +330,8 @@ private static object CreateGenericCollectionFromJsonArray(string parameterValue
329330
var list = collection as IList;
330331
foreach (var item in array)
331332
{
332-
var parsed = ParseParameterValue(item.GetRawText(), elementType);
333+
var elementValue = item.ValueKind == JsonValueKind.String ? item.GetString()! : item.GetRawText();
334+
var parsed = ParseParameterValue(elementValue, elementType);
333335
if (parsed != null) list!.Add(parsed);
334336
}
335337
return collection!;
@@ -349,7 +351,8 @@ private static object CreateTuppleTypeFromJsonArray(string parameterValue, Type
349351

350352
foreach (var item in array)
351353
{
352-
var parsed = ParseParameterValue(item.GetRawText(), elementType);
354+
var elementValue = item.ValueKind == JsonValueKind.String ? item.GetString()! : item.GetRawText();
355+
var parsed = ParseParameterValue(elementValue, elementType);
353356
if (parsed != null) list!.Add(parsed);
354357
}
355358

src/cycod/FunctionCallingTools/StrReplaceEditorHelperFunctions.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,75 @@ public string ReplaceOneInFile(
415415
return $"File {path} updated: replaced 1 occurrence of specified text.";
416416
}
417417

418+
[ReadOnly(false)]
419+
[Description("Replaces multiple text patterns in a single file atomically. All replacements must be unique occurrences.")]
420+
public string ReplaceMultipleInFile(
421+
[Description("Absolute or relative path to file.")] string path,
422+
[Description("Array of old strings to be replaced. Each must match exactly one occurrence.")] string[] oldStrings,
423+
[Description("Array of new strings to replace with. Must be same length as oldStrings.")] string[] newStrings)
424+
{
425+
if (!File.Exists(path))
426+
{
427+
return $"File {path} does not exist.";
428+
}
429+
430+
if (oldStrings.Length != newStrings.Length)
431+
{
432+
return $"Error: oldStrings array length ({oldStrings.Length}) must match newStrings array length ({newStrings.Length}).";
433+
}
434+
435+
if (oldStrings.Length == 0)
436+
{
437+
return "Error: No replacements specified.";
438+
}
439+
440+
var originalText = FileHelpers.ReadAllText(path);
441+
var currentText = originalText;
442+
443+
// Validate all patterns exist and are unique before making any changes
444+
for (int i = 0; i < oldStrings.Length; i++)
445+
{
446+
var oldStr = oldStrings[i];
447+
var newStr = newStrings[i];
448+
449+
var testReplacement = StringHelpers.ReplaceOnce(currentText, oldStr, newStr, out var countFound);
450+
if (countFound != 1)
451+
{
452+
var message = countFound == 0
453+
? $"Replacement {i + 1}: No occurrences of specified text found."
454+
: $"Replacement {i + 1}: Multiple matches found for specified text; must be unique.";
455+
return $"{message}\nNo changes made to {path}.";
456+
}
457+
}
458+
459+
// All validations passed, now perform the replacements
460+
for (int i = 0; i < oldStrings.Length; i++)
461+
{
462+
var oldStr = oldStrings[i];
463+
var newStr = newStrings[i];
464+
465+
var replacedText = StringHelpers.ReplaceOnce(currentText, oldStr, newStr, out var countFound);
466+
if (replacedText != null && countFound == 1)
467+
{
468+
currentText = replacedText;
469+
}
470+
else
471+
{
472+
// This shouldn't happen since we validated, but handle gracefully
473+
return $"Unexpected error during replacement {i + 1}. File may be in inconsistent state.";
474+
}
475+
}
476+
477+
// Save original content for undo before writing
478+
if (!EditHistory.ContainsKey(path))
479+
{
480+
EditHistory[path] = originalText;
481+
}
482+
483+
File.WriteAllText(path, currentText);
484+
485+
return $"File {path} updated: replaced {oldStrings.Length} occurrences.";
486+
}
418487
[ReadOnly(false)]
419488
[Description("Inserts the specified string `newStr` into the file at `path` after the specified line number (`insertLine`). Use 0 to insert at the beginning of the file.")]
420489
public string Insert(

0 commit comments

Comments
 (0)