Utils/tac.ps1

Register-GnuUtil @{
    Name            = 'tac'
    Category        = 'Viewing file contents'
    Synopsis        = 'Concatenate and print files in reverse.'
    Usage           = @('tac [OPTION]... [FILE]...')
    Description     = @('Write each FILE to standard output, last line first.', '', 'With no FILE, or when FILE is -, read standard input.')
    FileOperands    = $true
    OpenErrorFormat = "failed to open '{0}' for reading: {1}"
    ReadErrorFormat = "{0}: read error: {1}"
    Options         = @(
        @{ Short = 'b'; Long = 'before'; Help = 'attach the separator before instead of after' }
        @{ Short = 'r'; Long = 'regex'; Help = 'interpret the separator as a regular expression' }
        @{ Short = 's'; Long = 'separator'; Arg = 'STRING'; Help = 'use STRING as the separator instead of newline' }
    )
    Notes           = @('PowerShell note: -r uses .NET regular expression syntax.')
}



function script:ConvertTo-GnuReversedRecord {
    <#
    .SYNOPSIS
    Reverses the separator-delimited records of a text (tac semantics).
    #>

    param(
        [Parameter(Mandatory)][AllowEmptyString()][string]$Text,
        [Parameter(Mandatory)][string]$Pattern,
        [switch]$Before
    )

    if ([string]::IsNullOrEmpty($Text)) { return '' }
    $records = New-Object 'System.Collections.Generic.List[string]'
    $start = 0
    foreach ($match in [regex]::Matches($Text, $Pattern)) {
        if ($match.Length -eq 0) { continue }
        if ($Before) {
            if ($match.Index -gt $start) { $records.Add($Text.Substring($start, $match.Index - $start)) }
            $start = $match.Index
        }
        else {
            $end = $match.Index + $match.Length
            $records.Add($Text.Substring($start, $end - $start))
            $start = $end
        }
    }
    if ($start -lt $Text.Length) { $records.Add($Text.Substring($start)) }
    $records.Reverse()
    -join $records
}