October 14, 2023

Simple Line Editor 2

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(");
} 

No comments: