Here is the new LineEditor, it can work on a file or a string. This timing using the power of LINQ.
// Here is the definition of a line edit
public interface ILineEdit
{
string ApplyEdit(string input);
}
/// <summary>
/// Perform a bunch of edits on a text file or string on a line by line basis.
/// Only search and replace type edits are available.
/// </summary>
/// <summary>
/// Perform a bunch of edits on a IEnumerable<string>.
/// </summary>
public class TextLineEditor
{
private List<ILineEdit> _pendingEdits = new List<ILineEdit>();
public TextLineEditor()
{
}
public IEnumerable<string> ApplyEdits(IEnumerable<string> inputs)
{
foreach (var line in inputs)
{
string tmp = line;
foreach (ILineEdit edit in _pendingEdits)
{
tmp = edit.ApplyEdit(tmp);
}
yield return tmp;
}
}
public void AddEdit(ILineEdit edit)
{
Trace.Assert(edit != null);
_pendingEdits.Add(edit);
}
}
We can use this as the basis of a line by line text file ediitor:
/// <summary>
/// Perform a bunch of edits on a text file
/// Only search and replace type edits are available.
/// </summary>
public class FileTextLineEditor : TextLineEditor
{
public void ApplyEditsToFile(string inputPath, bool keepOriginal = true)
{
Debug.Assert(!string.IsNullOrWhiteSpace(inputPath));
var inputFileInfo = new FileInfo(inputPath);
Debug.Assert(inputFileInfo.Exists);
var outputPath = inputPath + ".new";
var outputFileInfo = new FileInfo(outputPath);
Debug.Assert(!outputFileInfo.Exists);
File.WriteAllLines(outputFileInfo.FullName, ApplyEdits(File.ReadLines(inputFileInfo.FullName)));
inputFileInfo.MoveTo(inputPath + ".bak");
outputFileInfo.Refresh();
outputFileInfo.MoveTo(inputPath);
if (!keepOriginal)
{
File.Delete(inputPath + ".bak");
}
}
}
Here is a LineEdit example. Note that it can do deletes by replacing with a blank string.
public class SearchAndReplace : ILineEdit
{
public enum RegExUsage
{
None = 0,
Use
}
public SearchAndReplace()
{
}
public SearchAndReplace(string searchFor, string replaceWith, bool extendedChars = false, RegExUsage regExUse = RegExUsage.None)
{
if (string.IsNullOrEmpty(searchFor))
throw new ArgumentException("Cannot be null or an empty string", "searchFor");
if (replaceWith == null)
throw new ArgumentException("Cannot be null", "replaceWith");
this.SearchFor = searchFor;
this.ReplaceWith = replaceWith;
this.ExtendedChars = extendedChars;
}
public bool IsRegEx { get; set; } = false;
public string SearchFor { get; set; } = "";
public string ReplaceWith { get; set; } = "";
public bool ExtendedChars { get; set; } = false;
public string ApplyEdit(string input)
{
if (ExtendedChars)
{
SearchFor = SearchFor.Replace("\\t","\t").Replace("\\n","\n").Replace("\\r","\r");
}
return (IsRegEx) ? SearchReplaceInString(input) : SearchReplaceInStringRegEx(input);
}
private string SearchReplaceInString(string input)
{
string output = input.Replace(SearchFor, ReplaceWith);
return output;
}
private string SearchReplaceInStringRegEx(string input)
{
string output = Regex.Replace(input, SearchFor, ReplaceWith);
return output;
}
}
Here is an example usage that attempts to replace usage of Moq in a CS file to usage of NSubstitute:
void Main()
{
string inputFile = @"a:/path/SomeFileName.cs";
FileTextLineEditor resEditor = new();
resEditor.AddEdits(this.Edits());
resEditor.ApplyEditsToFile(inputFile, keepOriginal: false);
}
internal IEnumerable<ILineEdit> Edits()
{
yield return new SearchAndReplace("using Moq", "using NSubstitute");
yield return new SearchAndReplace("MockBehavior.Strict", "");
yield return new SearchAndReplace("MockBehavior.Loose", "");
yield return new SearchAndReplace(".Object", "");
yield return new SearchAndReplace("It.IsAny", "Arg.Any");
yield return new SearchAndReplace("It.Is", "Arg.Is");
yield return new SearchAndReplace(@"(new\sMock\<([A-Za-z\<\>\-_]+)\>)", @"Substitute.For<$2>", false, SearchAndReplace.RegExUsage.Use);
yield return new SearchAndReplace(@"(Mock\<([A-Za-z\<\>\-_]+)\>\s([A-Za-z\<\>\-_]+)\s\=\snew)", @"var $3 = Substitute.For<$2>", false, SearchAndReplace.RegExUsage.Use);
// yield return new SearchAndReplace(@" = new()", @" = Substitute.For(??)");
//yield return new SearchAndReplace(@"(^\sMock\<([A-Za-z\<\>\-_]+)", "var", false, SearchAndReplace.RegExUsage.Use);
yield return new SearchAndReplace(@"(Setup\([a-z]+\s\=\>\s[a-z]+\.)", "", false, SearchAndReplace.RegExUsage.Use);
yield return new SearchAndReplace(@"(SetupGet\([a-z]+\s\=\>\s[a-z]+\.)", "", false, SearchAndReplace.RegExUsage.Use);
yield return new SearchAndReplace(@"(Verify\([a-z]+\s\=\>\s[a-z]+)", "Received(?)", false, SearchAndReplace.RegExUsage.Use);
yield return new SearchAndReplace("Times.Once", "1");
yield return new SearchAndReplace("Times.Never", "0");
yield return new SearchAndReplace(@"Returns\(\(\)\s\=\>\s", "Returns(");
}