October 16, 2023

Bit Operations

An extension class that allows various bitwise operations on an integer to be performed when dealing with a [Flags] Enum type.

// Bit wise operations on an integer as an extensions class
public static class IntBitwiseOperationsExtender
{
    // Returns bits set in lhs or rhs or both
    public static int BitwiseUnion(this int lhs, int rhs)
    {
        int bitWiseOr = lhs | rhs;
        return bitWiseOr;
    }

    // Return bits set common to both lhs and rhs
    public static int BitwiseIntersection(this int lhs, int rhs)
    {
        return lhs & rhs;
    }

    // Returns bits set in lhs or rhs but not in both
    public static int BitwiseExclusiveOr(this int lhs, int rhs)
    {
        int exclusiveOr = BitwiseUnion(lhs, rhs) - BitwiseIntersection(lhs, rhs);
        return exclusiveOr;
    }

    // Return lhs bits inverted, 0s becomes 1s and vice versa
    public static int BitwiseInvert(this int lhs)
    {
        int bitWiseOr = ~lhs;
        return bitWiseOr;
    }

    // Return lhs bits set minus any rhs bits set
    public static int BitwiseSubtract(this int lhs, int rhs)
    {
        int common = BitwiseIntersection(lhs, rhs); // Find the bits common to both sides
        int res = (int)lhs - (int)common;
        return res;
    }

    // Return a value that has all the bits sets from the lhs and the rhs
    public static int BitwiseAdd(this int lhs, int rhs)
    {
        return BitwiseUnion(lhs, rhs); // Same as a Bitwise Union
    }

    // Return true if lhs contains all the bits set within rhs
    public static bool BitwiseContains(this int lhs, int rhs)
    {
        int common = lhs & rhs;
        return (common == rhs);
    }

    // Return true if lhs contains one of the bits set within rhs
    public static bool BitwiseContainsOneOf(this int lhs, int rhs)
    {
        int common = lhs & rhs;
        return common > 0;
    }
}

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

August 15, 2023

Simple Line Editor

Here is the LineEditor, it can work on a file or a string:

// 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>
public class TextLineEditor
{
    private List<ILineEdit> _pendingEdits = new List<ILineEdit>();

    public TextLineEditor()
    {
    }

    public void ApplyEditsToFile(string inputPath, string outputPath)
    {
        Debug.Assert(!string.IsNullOrWhiteSpace(inputPath));
        Debug.Assert(!string.IsNullOrWhiteSpace(outputPath));
        Debug.Assert(!outputPath.Equals(inputPath)); 
        var inputFileInfo = new FileInfo(inputPath);
        var outputFileInfo = new FileInfo(outputPath);
        Debug.Assert(inputFileInfo.Exists);
        Debug.Assert(!outputFileInfo.Exists);

        using (TextWriter writer = new StreamWriter(outputFileInfo.FullName))
        {
            using (TextReader reader = new StreamReader(inputFileInfo.FullName))
            {
                ApplyEdits(writer, reader);
            }
        }
    }

    public string ApplyEditsToString(string text)
    {
        Trace.Assert(text != null);
        StringBuilder sb = new StringBuilder();
        using (TextWriter writer = new StringWriter(sb))
        {
            using (TextReader reader = new StringReader(text))
            {
                ApplyEdits(writer, reader);
            }
        }
        return sb.ToString();
    }

    private void ApplyEdits(TextWriter writer, TextReader reader)
    {
        string line = null;
        while ((line = reader.ReadLine()) != null)
        {
            foreach (ILineEdit edit in _pendingEdits)
            {
                line = edit.ApplyEdit(line);
            }
            if (line != null)
                writer.WriteLine(line);
        }
    }

    public void AddEdit(ILineEdit edit)
    {
        Trace.Assert(edit != null);
        _pendingEdits.Add(edit);
    }
}

This TextLineEditor could be replaced to work with IEnumerab<string> for line input and returning a IEnumerable<string> as an output. 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:
{
	var inputResFilePath = ...
	var outputResNewCsvFilePath ...
	var outputResCsvFilePath = ...

	var inputCnbFilePath = ...
	var outputCnbNewCsvFilePath ...
	var outputCnbCsvFilePath = ...


	var replaceCommentChars = new SearchAndReplace("#", "*");
    {
        var replaceTabsWithGT = new SearchAndReplace("\t", " > ");

        TextLineEditor resEditor = new();
        resEditor.AddEdit(replaceTabsWithGT);
        resEditor.AddEdit(replaceCommentChars);
        
        resEditor.ApplyEditsToFile(inputResFilePath, outputResNewCsvFilePath);

        File.Delete(outputResCsvFilePath);
        File.Move(outputResNewCsvFilePath, outputResCsvFilePath);
    }
    {
        var replaceExclamationChars = new SearchAndReplace("!", "*");
        var replaceTabWithHyphen = new SearchAndReplace("\t", " - ");

        TextLineEditor cnbEditor = new();
        cnbEditor.AddEdit(replaceTabWithHyphen);
        cnbEditor.AddEdit(replaceCommentChars);
        cnbEditor.AddEdit(replaceExclamationChars);

        cnbEditor.ApplyEditsToFile(inputCnbFilePath, outputCnbNewCsvFilePath);

        File.Delete(outputCnbCsvFilePath);
        File.Move(outputCnbNewCsvFilePath, outputCnbCsvFilePath);
    }
}

July 21, 2023

Startup For Dependency Injection, Settings and Logging

The Startup format for a Console program with a Serilog logger

You need quite a few packages:
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="7.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.TraceSource" Version="7.0.0" />
    <PackageReference Include="Serilog" Version="2.12.0" />
    <PackageReference Include="Serilog.Enrichers.Environment" Version="2.2.0" />
    <PackageReference Include="Serilog.Extensions.Hosting" Version="7.0.0" />
    <PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />
    <PackageReference Include="Serilog.Formatting.Compact" Version="1.1.0" />
    <PackageReference Include="Serilog.Settings.Configuration" Version="7.0.0" />
    <PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
    <PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
    <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />

Here is a Startup class:

using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using Serilog;
using Serilog.Core;
using Serilog.Formatting.Compact;


public class Startup
{
  IConfigurationRoot? Configuration { get; init; }
 
  public AppSettings AppSettings { get; private set; } = new AppSettings();
 
  public Startup()
  {
     Configuration = BuildConfiguration();
  }
 
  public IServiceCollection ConfigureServices(string loggingFilePath)
  {
    IServiceCollection services = new ServiceCollection();
    services.AddLogging(builder => builder.AddSerilog(
        new LoggerConfiguration()
#if DEBUG
        .MinimumLevel.Debug() // Log Debug level and higher
#else
        .MinimumLevel.Information() // Log Information level and higher
#endif
        // To read the settings from the configuration file:
        //.ReadFrom.Configuration(this.Configuration!)
       .WriteTo.Console() // To make the console output window a logger sink
       // To write to a custom logging file and render object properties in JSon format
       .WriteTo.File(new RenderedCompactJsonFormatter(), loggingFilePath)
       .CreateLogger()));
 
    RegisterDependencyInjectedServices(services);
    return services;
  }
 
  private static IServiceCollection RegisterDependencyInjectedServices(IServiceCollection services)
  {
    // Use this format: services.AddScoped/Transient/Singleton<ISomething, Something>();
    // For example
    services.AddSingleton<IResDataRepo, ResDataRepo>();
    services.AddSingleton<ICnbDataRepo, CnbDataRepo>();
    services.AddTransient<IGuiInputListener, GuiListener>();    
    return services;
  }

  private IConfigurationRoot BuildConfiguration()
  {
     // To create a custom AppSetting file:
     const string settingsFileName = "{MyApplicationName}.AppSettings.json";
     string currentDirectory = Directory.GetCurrentDirectory();
     var builder = new ConfigurationBuilder()
          .SetBasePath(currentDirectory)
          .AddJsonFile(settingsFileName, optional: false);
     var config = builder.Build();
 
     var settings = config.GetSection("AppSettings").Get<AppSettings>();
     if (settings == null)
     {
         Console.WriteLine($"ERR: {settingsFileName} not found in current directory {currentDirectory}, using defaults!");
     }
     AppSettings = settings ?? new AppSettings();
     return config;
  }
}

February 13, 2023

File Renamer

Rename a file when you have a path. This was written as a LinqPad script

void Main()
{
    Console.WriteLine(FileRenamer. CalcNewFilePath (@"c:/some/Directory/File.pdf"));
    Console.WriteLine(FileRenamer. CalcNewFilePath (@"File.pdf"));
    Console.WriteLine(FileRenamer. CalcNewFilePath (@"C:File.pdf"));
    Console.WriteLine(FileRenamer. CalcNewFilePath (@"/some/Directory/File.pdf"));
    Console.WriteLine(FileRenamer. CalcNewFilePath (@"/some\Directory/File.pdf"));
    Console.WriteLine(FileRenamer. CalcNewFilePath (@"e:\zdump\some\Directory\File.pdf"));
}

public static class FileRenamer
{
    const string Suffix = @"-xx";

    /// <summary>
    /// Calculate the file path of the file, changing the file name
    /// in the process
    /// </summary>
    /// <param name="filePath"></param>
    /// <returns></returns>
    public static string CalcNewFilePath(string filePath)
    {
        if (string.IsNullOrWhiteSpace(filePath))
            return "";
        var ext = Path.GetExtension(filePath);
        var bareFileName = Path.GetFileNameWithoutExtension(filePath);
       
        var path = GetFilePathStem(filePath);

    var fileNameTemplate = $"{path}{bareFileName}{Suffix}{ext}";
    return fileNameTemplate;
}

/// <summary>
/// Get the stem of the file path, everything before the actual filename.
/// It assumes that the last part of the path after the 
/// final Directory Seperator character is the filename
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public static string GetFilePathStem(string filePath)
{
    var path = "";
    var endofPath1 = filePath.LastIndexOf(Path.DirectorySeparatorChar);
    var endofPath2 = Path.AltDirectorySeparatorChar != 
      Path.DirectorySeparatorChar ? filePath.LastIndexOf(Path.AltDirectorySeparatorChar) : -1;
    var endofPath = Math.Max(endofPath1, endofPath2);
    if (endofPath > 0)
    {
        path = filePath.Substring(0, endofPath + 1);
    }
    else // When the root does not use a directory separator char eg. "c:filename.pdf"
    {
        var root = Path.GetPathRoot(filePath);
        path = root;
    }
    return path;
}


 

January 8, 2023

Powershell AfterLogin & CheckIpAddressChange

AfterLogin.ps1
#$host.UI.RawUI.ForegroundColor = "Green"
Write-Host "*** After Login Prepartion ***"  -Foregroundcolor Green
$host.UI.RawUI.WindowTitle =  "*** After Login Prepartion ***"
Set-Variable -Name "AzureStorageEmulatorExe" -Value "C:\Program Files (x86)\Microsoft SDKs\Azure\Storage Emulator\AzureStorageEmulator.exe"
Write-Output "*** Start Azure Storage Emulator ***"
& $AzureStorageEmulatorExe start
#Write-Host "*** LoggING into Azure ***"  -Foregroundcolor Green
#& az login --tenant 12345567-8910-47ff-0a65-2765a99ebbce
#Write-Host "*** LoggED into Azure ***"  -Foregroundcolor Green
& $AzureStorageEmulatorExe status
& "C:\Users\...\Documents\BatchFiles\CheckIpAddressChange.ps1"
Pause
CheckIpAddressChange.ps1
$currIpAddress = (Invoke-WebRequest -uri https://ifconfig.me/ip).Content
$prevIpAddress = "155.15.12.31"
$arkpdFirewallAccess = https://.../resource/subscriptions/resourceGroups/networking

#$host.UI.RawUI.ForegroundColor = "Green"
Write-Output "*** Checking for IP Address Change ***"  -Foregroundcolor Green
Write-Output "My current  IP address: $($currIpAddress)"
Write-Output "My previous IP address: $($prevIpAddress)"
$lastDifferentIpAddress = "15.17.107.14"
Write-Output "My last different IP address: $($lastDifferentIpAddress)"
Write-Output "if it changes, re-run ~\Scripts\IpRestrictionTool\allow-my-ip.ps1"
Write-Output "Also navigate to '$($arkpdFirewallAccess)' to update the firewall rule"

Powershell - Find My Ip Address

Clear
$currIpAddress = (Invoke-WebRequest -uri https://ifconfig.me/ip).Content
$prevIpAddress = "155.171.12.2"
Write-Output ""
Write-Output "My current  IP address: $($currIpAddress)"
Write-Output "My previous IP address: $($prevIpAddress)"
Write-Output "if it changes, re-run ~\Scripts\IpRestrictionTool\allow-my-ip.ps1"