Utils/unexpand.ps1
|
Register-GnuUtil @{ Name = 'unexpand' Category = 'Text processing' Synopsis = 'Convert spaces to tabs.' Usage = @('unexpand [OPTION]... [FILE]...') Description = @('Convert blanks in each FILE to tabs, writing to standard output.', '', 'With no FILE, or when FILE is -, read standard input.') FileOperands = $true NumericOption = 't' Options = @( @{ Short = 'a'; Long = 'all'; Help = 'convert all blanks, instead of just initial blanks' } @{ Long = 'first-only'; Help = 'convert only leading sequences of blanks (overrides -a)' } @{ Short = 't'; Long = 'tabs'; Arg = 'LIST'; Multi = $true; Help = 'use comma separated list of tab positions. The last specified position can be prefixed with ''/'' to specify a tab size to use after the last explicitly specified tab stop. Also a prefix of ''+'' can be used to align remaining tab stops relative to the last specified tab stop instead of the first column (implies -a)' } ) Notes = @('-t N (or -N) sets tabs N characters apart, instead of the default of 8.') } function script:ConvertTo-GnuUnexpandedLine { <# .SYNOPSIS Converts the blanks of one line to tabs, following GNU unexpand's pending-blank algorithm. #> param( [Parameter(Mandatory)][AllowEmptyString()][string]$Line, [Parameter(Mandatory)][hashtable]$TabStops, [switch]$All ) if ($Line.IndexOf(' ', [System.StringComparison]::Ordinal) -lt 0 -and $Line.IndexOf("`t", [System.StringComparison]::Ordinal) -lt 0) { return $Line } $builder = New-Object System.Text.StringBuilder $pending = New-Object System.Text.StringBuilder $convert = $true $column = 0 $oneBlankBeforeTabStop = $false $previousBlank = $true foreach ($char in $Line.ToCharArray()) { if ($convert) { $blank = $char -eq ' ' -or $char -eq "`t" if ($blank) { $next = Get-GnuNextTabStop -TabStops $TabStops -Column $column if ($next -lt 0) { $convert = $false } # past the last tab stop if ($convert) { if ($char -eq "`t") { $column = $next if ($pending.Length -gt 0) { $pending[0] = "`t" } } else { $column++ if (-not ($previousBlank -and $column -eq $next)) { # It is not yet known whether the pending blanks will become a tab. if ($column -eq $next) { $oneBlankBeforeTabStop = $true } [void]$pending.Append($char) $previousBlank = $true continue } # The blanks reach a tab stop: replace them by a tab (or two). if ($pending.Length -eq 0) { [void]$pending.Append(' ') } $pending[0] = "`t" $char = [char]"`t" } # Drop the pending blanks, unless it was a single blank just before the previous tab stop. if ($oneBlankBeforeTabStop) { $pending.Length = 1 } else { $pending.Length = 0 } } } elseif ([int]$char -eq 8) { if ($column -gt 0) { $column-- } } else { $column++ } Add-GnuUnexpandPending -Builder $builder -Pending $pending -OneBlankBeforeTabStop $oneBlankBeforeTabStop $oneBlankBeforeTabStop = $false $previousBlank = $blank $convert = $convert -and ($All -or $blank) } [void]$builder.Append($char) } Add-GnuUnexpandPending -Builder $builder -Pending $pending -OneBlankBeforeTabStop $oneBlankBeforeTabStop $builder.ToString() } function script:Add-GnuUnexpandPending { <# .SYNOPSIS Writes and clears the pending blanks of unexpand. #> param( [Parameter(Mandatory)][System.Text.StringBuilder]$Builder, [Parameter(Mandatory)][System.Text.StringBuilder]$Pending, [Parameter(Mandatory)][bool]$OneBlankBeforeTabStop ) if ($Pending.Length -eq 0) { return } if ($Pending.Length -gt 1 -and $OneBlankBeforeTabStop) { $Pending[0] = "`t" } [void]$Builder.Append($Pending.ToString()) $Pending.Length = 0 } |