├── CHANGELOG ├── ConvertFrom-SourceTable.ps1 ├── LICENSE ├── README.md └── Tests └── ConvertFrom-SourceTable.Tests.ps1 /CHANGELOG: -------------------------------------------------------------------------------- 1 | [Version] [DateTime]Date Author Comments 2 | --------- -------------- ------ -------- 3 | 0.0.10 2018-05-03 Ronald Bode First design 4 | 0.0.20 2018-05-09 Ronald Bode Pester ready version 5 | 0.0.21 2018-05-09 Ronald Bode removed support for String[] types, like: {One, Two}, in string mode --> use expression mode 6 | 0.0.22 2018-05-24 Ronald Bode Better "right aligned" definition 7 | 0.0.23 2018-05-25 Ronald Bode Resolved single column bug 8 | 0.0.24 2018-05-26 Ronald Bode Treating markdown table input as an option 9 | 0.0.25 2018-05-27 Ronald Bode Resolved error due to blank top lines 10 | 0.0.26 2018-05-27 Ronald Bode Fixed: line not displayed in error message 11 | 0.0.30 2018-05-31 Ronald Bode Complement equally aligned columns 12 | 0.0.31 2018-05-31 Ronald Bode Outside fields no longer return null but an empty string (same appearance) 13 | 0.0.32 2018-05-31 Ronald Bode Nameless type columns will get the name of the type 14 | 0.0.33 2018-06-03 Ronald Bode Prevent error when casting strings with embedded quotes 15 | 0.0.40 2018-06-09 Ronald Bode Optimized code 16 | 0.0.41 2018-07-06 Ronald Bode Resolved justified alignment bug 17 | 0.0.42 2018-07-11 Ronald Bode Retain the original property order 18 | 0.0.43 2018-08-05 Ronald Bode Liberated from Invoke-Expression 19 | 0.0.44 2018-09-12 Ronald Bode Fixed Trim error in PSv2 when the script is invoked a second time 20 | 0.0.50 2018-10-25 Ronald Bode Fixed Table Columns Being Truncated 21 | 0.1.0 2018-10-31 Ronald Bode Improved algorithms 22 | 0.1.1 2018-11-13 Ronald Bode Added -Ruler parameter to add alignment info 23 | 0.1.2 2018-11-14 Ronald Bode Added -Header parameter to add header info 24 | 0.1.3 2018-11-15 Ronald Bode Added -Literal parameter to prevent any evaluation 25 | 0.1.4 2018-11-15 Ronald Bode A manual supplied ruler (-Ruler ) suppresses all embedded rulers 26 | 0.1.5 2018-11-16 Ronald Bode Improved performance avoiding regex MaskString 27 | 0.1.6 2018-11-16 Ronald Bode Depleted PSCustomObject alias 'O' and DateTime alias 'D' wrappers 28 | 0.2.0 2018-11-18 Ronald Bode Shortened example 29 | 0.2.1 2018-11-28 Ronald Bode Corrected -Ruler and -Markdown PSBoundParameters check 30 | 0.2.2 2019-02-06 Ronald Bode Compliant with StrictMode -Version 2 31 | 0.2.3 2019-03-13 Ronald Bode Resolved (Slice) bug: single width column at position 0, results in empty name 32 | 0.2.4 2019-05-01 Ronald Bode Separate data provided via the pipeline from an array provided through a parameter 33 | 0.2.5 2019-05-03 Ronald Bode Added -Floating feature 34 | 0.2.6 2019-07-05 Ronald Bode Fixed issue with here table without ruler with spaces in header 35 | 0.2.7 2019-07-05 Ronald Bode Fixed bug were the last line contains white spaces and data is floating 36 | 0.2.8 2019-07-19 Ronald Bode Fixed issue were a markdown table has no outer borders 37 | 0.2.9 2019-07-20 Ronald Bode Changed error handling 38 | 0.3.2 2019-10-04 Ronald Bode Renamed -HorizontalRuler parameter to -HorizontalDash (alias HDash) 39 | Renamed -VerticalRuler parameter to -VerticalDash (alias VDash) 40 | Depleted -Markdown parameter (use -VerticalDash parameters instead) 41 | Depleted -Floating parameter (use -VerticalDash/-HorizontalDash parameters instead) 42 | Improved (unstructured) table/data recognition algorithm 43 | Support for delimited (non fixed width) CSV files (no support for quoted data) 44 | 0.3.3 2019-10-04 Ronald Bode Minor help correction 45 | 0.3.4 2019-10-04 Ronald Bode Trimming (string) values in distorted rows 46 | 0.3.5 2019-10-04 Ronald Bode Added -Junction parameter (default: "+") for tables with ruler junction characters 47 | 0.3.6 2019-10-04 Ronald Bode Added -Anchor parameter (default: ":") for tables with alignment anchors 48 | 0.3.7 2020-01-02 Ronald Bode Using the first occurrence of a column in case of a duplicate column name 49 | 0.3.8 2020-01-20 Ronald Bode Connect (rulerless) single spaced headers where either column is empty 50 | 0.3.9 2020-03-27 Ronald Bode In -Literal mode type syntax (e.g. [Int]5) are left untouched 51 | 0.3.10 2020-03-27 Ronald Bode Remove preceding empty lines from -Ruler (no here string required) 52 | 0.3.11 2020-03-27 Ronald Bode Added -Omit parameter (Each omitted character will be replaced with a space) 53 | 0.3.12 2020-07-23 Ronald Bode Fixed bug #2 catch (unaligned) floating data 54 | 0.3.13 2020-07-29 Ronald Bode Minor code improvements 55 | 0.3.14 2020-07-30 Ronald Bode Reformatted code (using https://github.com/DTW-DanWard/PowerShell-Beautifier) 56 | 0.3.15 2020-10-08 Ronald Bode Compliant with Set-StrictMode -Version 3 57 | 0.4.0 2021-09-09 Ronald Bode Function file: No dot-sourcing required, just: .\ConvertFrom-SourceTable.ps1 $Table 58 | 0.4.1 2023-02-01 Ronald Bode Updated inline help for: Get-MarkdownHelp 59 | 0.5.0 2023-02-06 Ronald Bode Implemented #7 -ParseRightAligned option (break change) 60 | 0.5.1 2024-10-30 Ronald Bode Implemented #9 Suppress columns with empty -Header name -------------------------------------------------------------------------------- /ConvertFrom-SourceTable.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | .VERSION 0.5.1 3 | .GUID 0019a810-97ea-4f9a-8cd5-4babecdc916b 4 | .AUTHOR iRon 5 | .COMPANYNAME 6 | .COPYRIGHT 7 | .TAGS Read Input Convert Resource Table Format MarkDown 8 | .LICENSE https://github.com/iRon7/ConvertFrom-SourceTable/LICENSE.txt 9 | .PROJECTURI https://github.com/iRon7/ConvertFrom-SourceTable 10 | .ICON https://raw.githubusercontent.com/iRon7/Join-Object/master/ConvertFrom-SourceTable.png 11 | .EXTERNALMODULEDEPENDENCIES 12 | .REQUIREDSCRIPTS 13 | .EXTERNALSCRIPTDEPENDENCIES 14 | .RELEASENOTES 15 | .PRIVATEDATA 16 | #> 17 | 18 | <# 19 | .SYNOPSIS 20 | Converts a fixed column table to objects. 21 | 22 | .DESCRIPTION 23 | The [ConvertFrom-SourceTable] cmdlet creates objects from a fixed column 24 | source table possibly surrounded by horizontal and/or vertical rulers. 25 | 26 | **Definitions:** 27 | 28 | * The width of a source table column is outlined by the header width, 29 | the ruler width and the width of the data. 30 | 31 | * Column and Data alignment (none, left, right or justified) is defined 32 | by the existence of any (non-separator) character at the start or end 33 | of a column. 34 | 35 | * Column alignment (which is used for a default field alignment) is 36 | defined by the first and last character or space of the header and 37 | the ruler of the outlined column. 38 | 39 | .PARAMETER InputObject 40 | Specifies the source table strings to be converted to objects. 41 | Enter a variable that contains the source table strings or type a 42 | command or expression that gets the source table strings. 43 | You might also pipe the source table strings to [ConvertFrom-SourceTable]. 44 | 45 | Note that streamed table rows are intermediately processed and 46 | released for the next cmdlet. In this mode, there is a higher 47 | possibility that floating tables or column data cannot be determined 48 | to be part of a specific column (as there is no overview of the table 49 | data that follows). To resolve this, provide all rows in once or use 50 | one of the following [-Header] and/or [-Ruler] parameters. 51 | 52 | .PARAMETER Header 53 | A string that defines the header line of an headless table or a multiple 54 | strings where each item represents the column name. 55 | In case the header contains a single string, it is used to define the 56 | (property) names, the size and alignment of the column, therefore it is 57 | key that the columns names are properly aligned with the rest of the 58 | column (including any table indents). 59 | If the header contains multiple strings, each string will be used to 60 | define the property names of each object. In this case, column alignment 61 | is based on the rest of the data and possible ruler. 62 | 63 | > [!TIP] 64 | > To skip a column, set the header name of the concerned column index to 65 | > an empty string (or `$Null`). 66 | 67 | .PARAMETER Ruler 68 | A string that replaces any (horizontal) ruler in the input table which 69 | helps to define character columns in occasions where the table column 70 | margins are indefinable. 71 | 72 | .PARAMETER HorizontalDash 73 | This parameter (Alias `-HDash`) defines the horizontal ruler character. 74 | By default, each streamed table row (or a total raw table) will be 75 | searched for a ruler existing out of horizontal dash characters (`-`), 76 | spaces and possible vertical dashes. If the ruler is found, the prior 77 | line is presumed to be the header. If the ruler is not found within 78 | the first (two) streamed data lines, the first line is presumed the 79 | header line. 80 | If `-HorizontalDash` explicitly defined, all (streamed) lines will be 81 | searched for a matching ruler. 82 | If `-HorizontalDash` is set to `$Null`, the first data line is presumed 83 | the header line (unless the [-VerticalDash] parameter is set). 84 | 85 | .PARAMETER VerticalDash 86 | This parameter (Alias `-VDash`) defines the vertical ruler character. 87 | By default, each streamed table row (or a total raw table) will be 88 | searched for a header with vertical dash characters (`|`). If the 89 | header is not found within the first streamed data line, the first 90 | line is presumed the header line. 91 | If `-VerticalDash` explicitly defined, all (streamed) lines will be 92 | searched for a header with a vertical dash character. 93 | If `-VerticalDash` is set to `$Null`, the first data line is presumed 94 | the header line (unless the [-HorizontalDash] parameter is set). 95 | 96 | .PARAMETER Junction 97 | The -Junction parameter (default: "+") defines the character used for 98 | the junction between the horizontal ruler and vertical ruler. 99 | 100 | .PARAMETER Anchor 101 | The -Anchor parameter (default: `:`) defines the character used for 102 | the alignment anchor. If used in the header row, it will be used to 103 | define the default alignment, meaning that justified (full width) 104 | values will be parsed. 105 | 106 | .PARAMETER Omit 107 | A string of characters to omit from the header and data. Each omitted 108 | character will be replaced with a space. 109 | 110 | .PARAMETER ParseRightAligned 111 | This parameter will cause any right aligned data to be parsed according 112 | to the following formatting and alignment rules: 113 | 114 | * Data that is left aligned will be parsed to the generic column type 115 | which is a string by default. 116 | 117 | * Data that is right aligned will be parsed. 118 | 119 | * Data that is justified (using the full column with) is following the 120 | the header alignment and parsed if the header is right aligned. 121 | 122 | * The default column type can be set by prefixing the column name with 123 | a standard (PowerShell) cast operator (a data type enclosed in 124 | square brackets, e.g.: `[Int]ID`) 125 | 126 | #### Caution 127 | 128 | Take reasonable precautions when using the `-ParseRightAligned` parameter 129 | in scripts. When using the `-ParseRightAligned` parameter to convert data 130 | from a table, verify that the data is safe to be parsed before running it. 131 | 132 | .EXAMPLE 133 | # Restore objects from a Format-Table output 134 | 135 | The following loads the file properties from general PowerShell output table: 136 | 137 | $Files = ConvertFrom-SourceTable ' 138 | Mode LastWriteTime Length Name 139 | ---- ------------- ------ ---- 140 | d----l 11/16/2018 8:30 PM Archive 141 | -a---l 5/22/2018 12:05 PM (726) Build-Expression.ps1 142 | -a---l 11/16/2018 7:38 PM 2143 CHANGELOG 143 | -a---l 11/17/2018 10:42 AM 14728 ConvertFrom-SourceTable.ps1 144 | -a---l 11/17/2018 11:04 AM 23909 ConvertFrom-SourceTable.Tests.ps1 145 | -a---l 8/4/2018 11:04 AM (6237) Import-SourceTable.ps1' 146 | 147 | > Note that it is generally not a good practice to use console output cmdlets 148 | > as e.g. [Format-Table] for anything else than displaying the results. 149 | 150 | .EXAMPLE 151 | # Convert from a markdown table 152 | 153 | The following command loads a list of employee objects from a markdown table: 154 | 155 | $Employees = ConvertFrom-SourceTable ' 156 | | Department | Name | Country | 157 | | ----------- | ------- | ------- | 158 | | Sales | Aerts | Belgium | 159 | | Engineering | Bauer | Germany | 160 | | Sales | Cook | England | 161 | | Engineering | Duval | France | 162 | | Marketing | Evans | England | 163 | | Engineering | Fischer | Germany |' 164 | 165 | .EXAMPLE 166 | # Parse right aligned data 167 | 168 | In the following example each item in the (hexadecimal) `Value` column will be parsed 169 | to integer value and each item in the `RGB` column to an array with three values. 170 | 171 | $Colors = ConvertFrom-SourceTable -Parse ' 172 | Name Value RGB 173 | ---- ----- --- 174 | Black 0x000000 0,0,0 175 | White 0xFFFFFF 255,255,255 176 | Red 0xFF0000 255,0,0 177 | Lime 0x00FF00 0,255,0 178 | Blue 0x0000FF 0,0,255 179 | Yellow 0xFFFF00 255,255,0 180 | Cyan 0x00FFFF 0,255,255 181 | Magenta 0xFF00FF 255,0,255 182 | Silver 0xC0C0C0 192,192,192 183 | Gray 0x808080 128,128,128 184 | Maroon 0x800000 128,0,0 185 | Olive 0x808000 128,128,0 186 | Green 0x008000 0,128,0 187 | Purple 0x800080 128,0,128 188 | Teal 0x008080 0,128,128 189 | Navy 0x000080 0,0,128' 190 | 191 | $Colors | Where {$_.Name -eq "Red"} 192 | 193 | Name Value RGB 194 | ---- ----- --- 195 | Red 16711680 {255, 0, 0} 196 | 197 | .EXAMPLE 198 | # Custom column types 199 | 200 | In the following example each item in the first column is casted to a `version` object 201 | and each item in the second column to a `DateTime` object.\ 202 | Notice that the type name is used as a property name in case column name is omitted. 203 | 204 | $ChangeLog = ConvertFrom-SourceTable -Parse ' 205 | [Version] [DateTime]Date Author Comments 206 | --------- -------------- ------ -------- 207 | 0.0.10 2018-05-03 Ronald Bode First design 208 | 0.0.20 2018-05-09 Ronald Bode Pester ready version 209 | 0.0.21 2018-05-09 Ronald Bode removed support for String[] types 210 | 0.0.22 2018-05-24 Ronald Bode Better "right aligned" definition 211 | 0.0.23 2018-05-25 Ronald Bode Resolved single column bug 212 | 0.0.24 2018-05-26 Ronald Bode Treating markdown table input as an option 213 | 0.0.25 2018-05-27 Ronald Bode Resolved error due to blank top lines' 214 | 215 | .LINK 216 | https://github.com/iRon7/ConvertFrom-SourceTable 217 | #> 218 | [CmdletBinding()][OutputType([Object[]])] param( 219 | [Parameter(ValueFromPipeLine = $True)] [String[]]$InputObject, 220 | [String[]]$Header, 221 | [string]$Ruler, 222 | [Alias("HDash")] [char]$HorizontalDash = '-', 223 | [Alias("VDash")] [char]$VerticalDash = '|', 224 | [char]$Junction = '+', 225 | [char]$Anchor = ':', 226 | [string]$Omit, 227 | [switch]$ParseRightAligned, 228 | [ValidateScript({ Throw [System.Management.Automation.ValidationMetadataException]'The -Literal parameter is depreciated and enabled by default, use -ParseRightAligned to disable.' })][switch]$Literal 229 | ) 230 | begin { 231 | enum Alignment{ None; Left; Right; Justified } 232 | enum Mask{ All = 8; Header = 4; Ruler = 2; Data = 1 } 233 | $Auto = !$PSBoundParameters.ContainsKey('HorizontalDash') -and !$PSBoundParameters.ContainsKey('VerticalDash') 234 | $HRx = if ($HorizontalDash) { '\x{0:X2}' -f [int]$HorizontalDash } 235 | $VRx = if ($VerticalDash) { '\x{0:X2}' -f [int]$VerticalDash } 236 | $JNx = if ($Junction) { '\x{0:X2}' -f [int]$Junction } 237 | $ANx = if ($Anchor) { '\x{0:X2}' -f [int]$Anchor } 238 | $RulerPattern = if ($VRx) { "^[$HRx$VRx$JNx$ANx\s]*$HRx[$HRx$VRx$JNx$ANx\s]*$" } elseif ($HRx) { "^[$HRx\s]*$HRx[$HRx\s]*$" } else { '\A(?!x)x' } 239 | if (!$PSBoundParameters.ContainsKey('Ruler') -and $HRx) { Remove-Variable 'Ruler'; $Ruler = $Null } 240 | if (!$Ruler -and !$HRx -and !$VRx) { $Ruler = '' } 241 | if ($Ruler) { $Ruler = $Ruler -split '[\r\n]+' | Where-Object { $_.Trim() } | Select-Object -First 1 } 242 | $HeaderLine = if ($Header -is [Array] -and $Header.Count -gt 1) { '' } elseif ($Header) { $Header } 243 | $TopLine = if ($HeaderLine) { '' } 244 | $LastLine,$OuterLeftColumn,$OuterRightColumn,$Mask = $Null 245 | $RowIndex = 0; $Padding = 0; $Columns = [System.Collections.Generic.List[HashTable]]::new() 246 | $Property = [System.Collections.Specialized.OrderedDictionary]::new() # Include support from PSv2 247 | function Null { $Null }; function True { $True }; function False { $False }; # Wrappers 248 | function Debug-Column { 249 | if ($VRx) { Write-Debug $Mask } 250 | else { Write-Debug (($Mask | ForEach-Object { if ($_) { '{0:x}' -f $_ } else { ' ' } }) -join '') } 251 | $CharArray = (' ' * ($Columns[-1].End + 1)).ToCharArray() 252 | for ($i = 0; $i -lt $Columns.Count; $i++) { $Column = $Columns[$i] 253 | for ($c = $Column.Start + $Padding; $c -le $Column.End - $Padding; $c++) { $CharArray[$c] = '-' } 254 | $CharArray[($Column.Start + $Column.End) / 2] = "$i"[-1] 255 | if ($Column.Alignment -band [Alignment]::Left) { $CharArray[$Column.Start + $Padding] = ':' } 256 | if ($Column.Alignment -band [Alignment]::Right) { $CharArray[$Column.End - $Padding] = ':' } 257 | } 258 | Write-Debug ($CharArray -join '') 259 | } 260 | function Mask ([string]$Line,[byte]$Or = [Mask]::Data) { 261 | $Init = [Mask]::All * ($Null -eq $Mask) 262 | if ($Init) { ([ref]$Mask).Value = [System.Collections.Generic.List[Byte]]::new() } 263 | for ($i = 0; $i -lt ([math]::Max($Mask.Count,$Line.Length)); $i++) { 264 | if ($i -ge $Mask.Count) { ([ref]$Mask).Value.Add($Init) } 265 | $Mask[$i] = if ($i -lt $Line.Length -and $Line[$i] -match '\S') { $Mask[$i] -bor $Or } else { $Mask[$i] -band (0xFF -bxor [Mask]::All) } 266 | } 267 | } 268 | function Slice ([string]$String,[int]$Start,[int]$End = [int]::MaxValue) { 269 | if ($Start -lt 0) { $End += $Start; $Start = 0 } 270 | if ($End -ge 0 -and $Start -lt $String.Length) { 271 | if ($End -lt $String.Length) { $String.Substring($Start,$End - $Start + 1) } else { $String.Substring($Start) } 272 | } else { $Null } 273 | } 274 | function TypeName ([string]$TypeName) { 275 | if ($ParseRightAligned) { 276 | $Null = $TypeName.Trim() -match '(\[(.*)\])?\s*(.*)' 277 | $Matches[2] 278 | if ($Matches[3]) { $Matches[3] } else { $Matches[2] } 279 | } else { 280 | $Null,$TypeName.Trim() 281 | } 282 | } 283 | function ErrorRecord ($Line,$Start,$End,$Message) { 284 | $Exception = New-Object System.InvalidOperationException " 285 | $Message 286 | + $($Line -Replace '[\s]', ' ') 287 | + $(' ' * $Start)$('~' * ($End - $Start + 1)) 288 | " 289 | New-Object Management.Automation.ErrorRecord $Exception, 290 | $_.Exception.ErrorRecord.FullyQualifiedErrorId, 291 | $_.Exception.ErrorRecord.CategoryInfo.Category, 292 | $_.Exception.ErrorRecord.TargetObject 293 | } 294 | } 295 | process { 296 | $Lines = $InputObject -split '[\r\n]+' 297 | if ($Omit) { 298 | $Lines = @( 299 | foreach ($Line in $Lines) { 300 | foreach ($Char in [Char[]]$Omit) { $Line = $Line.Replace($Char,' ') } 301 | $Line 302 | } 303 | ) 304 | } 305 | $NextIndex,$DataIndex = $Null 306 | if (!$Columns) { 307 | for ($Index = 0; $Index -lt $Lines.Length; $Index++) { 308 | $Line = $Lines[$Index] 309 | if ($Line.Trim()) { 310 | if ($Null -ne $HeaderLine) { 311 | if ($Null -ne $Ruler) { 312 | if ($Line -notmatch $RulerPattern) { $DataIndex = $Index } 313 | } else { 314 | if ($Line -match $RulerPattern) { $Ruler = $Line } 315 | else { 316 | $Ruler = '' 317 | $DataIndex = $Index 318 | } 319 | } 320 | } else { 321 | if ($Null -ne $Ruler) { 322 | if ($LastLine -and (!$VRx -or $Ruler -notmatch $VRx -or $LastLine -match $VRx) -and $Line -notmatch $RulerPattern) { 323 | $HeaderLine = $LastLine 324 | $DataIndex = $Index 325 | } 326 | } else { 327 | if (!$RulerPattern) { 328 | $HeaderLine = $Line 329 | } elseif ($LastLine -and (!$VRx -or $Line -notmatch $VRx -or $LastLine -match $VRx) -and $Line -match $RulerPattern) { 330 | $HeaderLine = $LastLine 331 | if (!$Ruler) { $Ruler = $Line } 332 | } 333 | } 334 | } 335 | if ($Line -notmatch $RulerPattern) { 336 | if ($VRx -and $Line -match $VRx -and $TopLine -notmatch $VRx) { $TopLine = $Line; $NextIndex = $Null } 337 | elseif ($Null -eq $TopLine) { $TopLine = $Line } 338 | elseif ($Null -eq $NextIndex) { $NextIndex = $Index } 339 | $LastLine = $Line 340 | } 341 | if ($DataIndex) { break } 342 | } 343 | } 344 | if (($Auto -or ($VRx -and $TopLine -match $VRx)) -and $Null -ne $NextIndex) { 345 | if ($Null -eq $HeaderLine) { 346 | $HeaderLine = $TopLine 347 | if ($Null -eq $Ruler) { $Ruler = '' } 348 | $DataIndex = $NextIndex 349 | } elseif ($Null -eq $Ruler) { 350 | $Ruler = '' 351 | $DataIndex = $NextIndex 352 | } 353 | } 354 | if ($Null -ne $DataIndex) { 355 | $HeaderLine = $HeaderLine.TrimEnd() 356 | if ($TopLine -notmatch $VRx) { 357 | $VRx = '' 358 | if ($Ruler -notmatch $ANx) { $ANx = '' } 359 | } 360 | if ($VRx) { 361 | $Index = 0; $Start = 0; $Length = $Null; $Padding = [int]::MaxValue 362 | if ($Ruler) { 363 | $Start = $Ruler.Length - $Ruler.TrimStart().Length 364 | if ($Ruler.Length -gt $HeaderLine.Length) { $HeaderLine += ' ' * ($Ruler.Length - $HeaderLine.Length) } 365 | } 366 | $Mask = '?' * $Start 367 | foreach ($Column in ($HeaderLine.Substring($Start) -split $VRx)) { 368 | if ($Null -ne $Length) { $Mask += '?' * $Length + $VerticalDash } 369 | $Length = $Column.Length 370 | $Type,$Name = if (@($Header).Count -le 1) { TypeName $Column.Trim() } 371 | elseif ($Index -lt @($Header).Count) { TypeName $Header[$Index] } 372 | # if ($Name) { 373 | $End = $Start + $Length - 1 374 | if ($Type -or $Name) { 375 | $Padding = [math]::Min($Padding,$Column.Length - $Column.TrimStart().Length) 376 | if ($Ruler -or $End -lt $HeaderLine.Length - 1) { $Padding = [math]::Min($Padding,$Column.Length - $Column.TrimEnd().Length) } 377 | $Property.Add($Name, $Null) 378 | } 379 | $Columns.Add(@{ Index = $Index; Name = $Column; Type = $Null; Start = $Start; End = $End }) 380 | # } 381 | $Index++; $Start += $Column.Length + 1 382 | } 383 | $Mask += '*' 384 | foreach ($Column in $Columns) { 385 | if (-not $Column.Type -and -not $Column.Name) { continue } 386 | $Anchored = $Ruler -and $ANx -and $Ruler -match $ANx 387 | if (!$Ruler) { 388 | if ($Column.Start -eq 0) { 389 | $Column.Start = [math]::Max($HeaderLine.Length - $HeaderLine.TrimStart().Length - $Padding,0) 390 | $OuterLeftColumn = $Column 391 | } elseif ($Column.End -eq $HeaderLine.Length - 1) { 392 | $Column.End = $HeaderLine.TrimEnd().Length + $Padding 393 | $OuterRightColumn = $Column 394 | } 395 | } 396 | $Column.Type,$Column.Name = TypeName $Column.Name.Trim() 397 | if ($Anchored) { 398 | $Column.Alignment = [Alignment]::None 399 | if ($Ruler[$Column.Start] -match $ANx) { $Column.Alignment = $Column.Alignment -bor [Alignment]::Left } 400 | if ($Ruler[$Column.End] -match $ANx) { $Column.Alignment = $Column.Alignment -bor [Alignment]::Right } 401 | } else { 402 | $Column.Alignment = [Alignment]::Justified 403 | if ($HeaderLine[$Column.Start + $Padding] -notmatch '\S') { $Column.Alignment = $Column.Alignment -band -bnot [Alignment]::Left } 404 | if ($Column.End - $Padding -ge $HeaderLine.Length -or $HeaderLine[$Column.End - $Padding] -notmatch '\S') { $Column.Alignment = $Column.Alignment -band -bnot [Alignment]::Right } 405 | } 406 | } 407 | } else { 408 | Mask $HeaderLine ([Mask]::Header) 409 | if ($Ruler) { Mask $Ruler ([Mask]::Ruler) } 410 | $Lines | Select-Object -Skip $DataIndex | Where-Object { $_.Trim() } | ForEach-Object { Mask $_ } 411 | if (!$Ruler -and $HRx) { # Connect (ruler-less) single spaced headers where either column is empty 412 | $InWord = $False; $WordMask = 0 413 | for ($i = 0; $i -le $Mask.Count; $i++) { 414 | if ($i -lt $Mask.Count) { $WordMask = $WordMask -bor $Mask[$i] } 415 | $Masked = $i -lt $Mask.Count -and $Mask[$i] 416 | if ($Masked -and !$InWord) { $InWord = $True; $Start = $i } 417 | elseif (!$Masked -and $InWord) { 418 | $InWord = $False; $End = $i - 1 419 | if ([Mask]::Header -eq $WordMask -band 7) { # only header 420 | if ($Start -ge 2 -and $Mask[$Start - 2] -band [Mask]::Header) { $Mask[$Start - 1] = [Mask]::Header } 421 | elseif (($End + 2) -lt $Mask.Count -and $Mask[$End + 2] -band [Mask]::Header) { $Mask[$End + 1] = [Mask]::Header } 422 | } 423 | $WordMask = 0 424 | } 425 | } 426 | } 427 | $InWord = $False; $Index = 0; $Start,$Left = $Null 428 | for ($i = 0; $i -le $Mask.Count; $i++) { 429 | $Masked = $i -lt $Mask.Count -and $Mask[$i] 430 | if ($Masked -and !$InWord) { $InWord = $True; $Start = $i; $WordMask = 0 } 431 | elseif ($InWord) { 432 | if ($i -lt $Mask.Count) {$WordMask = $WordMask -bor $Mask[$i]} 433 | if (!$Masked) { 434 | $InWord = $False; $End = $i - 1 435 | if ($i -lt $Mask.Count) {$WordMask = $WordMask -bor $Mask[$i]} 436 | $Type,$Name = if (@($Header).Count -le 1) { TypeName "$(Slice -String $HeaderLine -Start $Start -End $End)".Trim() } 437 | elseif ($Index -lt @($Header).Count) { TypeName $Header[$Index] } 438 | if ($HeaderLine -and -not $Name) { continue } 439 | if ($Name -and $Columns.Where{ $_.Name -eq $Name }) { Write-Warning "Duplicate column name: $Name." } 440 | if ($Type) { 441 | $Type = try { [type]$Type } catch { 442 | Write-Error -ErrorRecord (ErrorRecord -Line $HeaderLine -Start $Start -End $End -Message ( 443 | "Unknown type {0} in header at column '{1}'" -f $Type,$Name 444 | )) 445 | } 446 | } 447 | $Column = @{ 448 | Index = $Index 449 | Name = $Name 450 | Type = $Type 451 | Start = $Start 452 | End = $End 453 | Alignment = $Null 454 | Left = $Left 455 | Right = $Null 456 | Mask = $WordMask 457 | } 458 | $Columns.Add($Column) 459 | if ($Left) { $Left.Right = $Column } 460 | $Left = $Column 461 | if ($Name) { $Property.Add($Name, $Null) } 462 | $Index++ 463 | } 464 | } 465 | } 466 | } 467 | $RulerPattern = if ($Ruler) { '^' + ($Ruler -replace "[^$HRx]","[$VRx$JNx$ANx\s]" -replace "[$HRx]","[$HRx]") } else { '\A(?!x)x' } 468 | } 469 | } 470 | if ($Columns) { 471 | if ($VRx) { 472 | foreach ($Line in ($Lines | Where-Object { $_ -like $Mask })) { 473 | if ($OuterLeftColumn) { 474 | $Start = [math]::Max($Line.Length - $Line.TrimStart().Length - $Padding,0) 475 | if ($Start -lt $OuterLeftColumn.Start) { 476 | $OuterLeftColumn.Start = $Start 477 | $OuterLeftColumn.Alignment = $Column.Alignment -band -bnot [Alignment]::Left 478 | } 479 | } elseif ($OuterRightColumn) { 480 | $End = $Line.TrimEnd().Length + $Padding 481 | if ($End -gt $OuterRightColumn.End) { 482 | $OuterRightColumn.End = $End 483 | $OuterRightColumn.Alignment = $Column.Alignment -band -bnot [Alignment]::Right 484 | } 485 | } 486 | } 487 | } else { 488 | $HeadMask = if ($Ruler) { [Mask]::Header -bor [Mask]::Ruler } else { [Mask]::Header } 489 | $Lines | Select-Object -Skip (0 + $DataIndex) | Where-Object { $_.Trim() } | ForEach-Object { Mask $_ } 490 | 491 | if (!$RowIndex) { 492 | for ($c = $Columns.Count - 1; $c -ge 0; $c --) { 493 | $Column = $Columns[$c] 494 | $MaskStart = $Mask[$Column.Start]; $MaskEnd = $Mask[$Column.End] 495 | $HeadStart = $MaskStart -band $HeadMask; $HeadEnd = $MaskEnd -band $HeadMask 496 | $AllStart = $MaskStart -band [Mask]::All; $AllEnd = $MaskEnd -band [Mask]::All 497 | $IsLeftAligned = ($HeadStart -eq $HeadMask -and $HeadEnd -ne $HeadMask) -or ($AllStart -and !$AllEnd) 498 | $IsRightAligned = ($HeadStart -ne $HeadMask -and $HeadEnd -eq $HeadMask) -or (!$AllStart -and $AllEnd) 499 | if ($IsLeftAligned) { $Column.Alignment = $Column.Alignment -bor [Alignment]::Left } 500 | if ($IsRightAligned) { $Column.Alignment = $Column.Alignment -bor [Alignment]::Right } 501 | } 502 | if ($DebugPreference -ne 'SilentlyContinue' -and !$RowIndex) { Write-Debug ($HeaderLine -replace '\s',' '); Debug-Column } 503 | } 504 | 505 | foreach ($Column in $Columns) { # Include any consecutive characters at te right 506 | $MaxEnd = if ($Column.Right) { $Column.Right.Start - 2 } else { $Mask.Count - 1 } 507 | for ($i = $Column.End + 1; $i -le $MaxEnd; $i++) { 508 | if ($Mask[$i]) { 509 | $Column.End = $i 510 | $Column.Alignment = $Column.Alignment -band -bnot [Alignment]::Right 511 | } else { break } 512 | } 513 | } 514 | 515 | foreach ($Column in $Columns) { # Include any consecutive characters at te left 516 | $MinStart = if ($Column.Left) { $Column.Left.End + 2 } else { 0 } 517 | for ($i = $Column.Start - 1; $i -ge $MinStart; $i --) { 518 | if ($Mask[$i]) { 519 | $Column.Start = $i 520 | $Column.Alignment = $Column.Alignment -band -bnot [Alignment]::Left 521 | } else { break } 522 | } 523 | } 524 | 525 | foreach ($Column in $Columns) { # Include any floating characters at the right 526 | if ($Column.Alignment -ne [Alignment]::Right) { # unless the column is right aligned 527 | $MaxEnd = if ($Column.Right) { $Column.Right.Start - 2 } else { $Mask.Count - 1 } 528 | for ($i = $Column.End + 1; $i -le $MaxEnd; $i++) { 529 | if ($Mask[$i]) { 530 | $Column.End = $i 531 | $Column.Alignment = $Column.Alignment -band -bnot [Alignment]::Right 532 | } 533 | } 534 | } 535 | } 536 | 537 | foreach ($Column in $Columns) { # Include any floating characters at the left 538 | if ($Column.Alignment -ne [Alignment]::Left) { # unless the column is left aligned 539 | $MinStart = if ($Column.Left) { $Column.Left.End + 2 } else { 0 } 540 | for ($i = $Column.Start - 1; $i -ge $MinStart; $i --) { 541 | if ($Mask[$i]) { 542 | $Column.Start = $i 543 | $Column.Alignment = $Column.Alignment -band -bnot [Alignment]::Left 544 | } 545 | } 546 | } 547 | } 548 | 549 | foreach ($Column in $Columns) { # Include any leftover floating characters at the right 550 | if ($Column.Alignment -ne [Alignment]::Right) { # where the column is right aligned 551 | $MaxEnd = if ($Column.Right) { $Column.Right.Start - 2 } else { $Mask.Count - 1 } 552 | for ($i = $Column.End + 1; $i -le $MaxEnd; $i++) { 553 | if ($Mask[$i]) { 554 | $Column.End = $i 555 | $Column.Alignment = $Column.Alignment -band -bnot [Alignment]::Right 556 | } 557 | } 558 | } 559 | } 560 | } 561 | if ($DebugPreference -ne 'SilentlyContinue' -and !$RowIndex) { Write-Debug ($HeaderLine -replace '\s',' '); Debug-Column } 562 | foreach ($Line in ($Lines | Select-Object -Skip ([int]$DataIndex))) { 563 | if ($Line.Trim() -and ($Line -notmatch $RulerPattern)) { 564 | $RowIndex++ 565 | if ($DebugPreference -ne 'SilentlyContinue') { Write-Debug ($Line -replace '\s',' ') } 566 | $Fields = if ($VRx -and $Line -notlike $Mask) { $Line -split $VRx } 567 | foreach ($Column in $Columns) { 568 | if (-not $Column.Type -and -not $Column.Name) { continue } 569 | $Property[$Column.Name] = if ($Fields) { 570 | $Fields[$Column.Index].Trim() 571 | } else { 572 | $Field = Slice -String $Line -Start $Column.Start -End $Column.End 573 | if ($Field -is [string]) { 574 | $Tail = $Field.TrimStart() 575 | $Value = $Tail.TrimEnd() 576 | if ($ParseRightAligned -and $Value -gt '') { 577 | $IsLeftAligned = $Field.Length - $Tail.Length -eq $Padding 578 | $IsRightAligned = $Tail.Length - $Value.Length -eq $Padding 579 | $Alignment = if ($IsLeftAligned -ne $IsRightAligned) { 580 | if ($IsLeftAligned) { [Alignment]::Left } else { [Alignment]::Right } 581 | } else { $Column.Alignment } 582 | if ($Alignment -eq [Alignment]::Right) { 583 | try { & ([scriptblock]::Create($Value)) } 584 | catch { $Value 585 | Write-Error -ErrorRecord (ErrorRecord -Line $Line -Start $Column.Start -End $Column.End -Message ( 586 | "The expression '{0}' in row {1} at column '{2}' can't be parsed." -f $Value,$RowIndex,$Column.Name 587 | )) 588 | } 589 | } elseif ($Column.Type) { 590 | try { & ([scriptblock]::Create("[$($Column.Type)]`$Value")) } 591 | catch { $Value 592 | Write-Error -ErrorRecord (ErrorRecord -Line $Line -Start $Column.Start -End $Column.End -Message ( 593 | "The value '{0}' in row {1} at column '{2}' can't be converted to type {1}." -f $Value,$RowIndex,$Column.Name,$Column.Type 594 | )) 595 | } 596 | } else { $Value } 597 | } else { $Value } 598 | } else { '' } 599 | } 600 | } 601 | [PSCustomObject]$Property 602 | } 603 | } 604 | if ($DebugPreference -ne 'SilentlyContinue' -and $RowIndex) { Debug-Column } 605 | } 606 | } 607 | 608 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT NON-AI License 2 | 3 | Copyright (c) 2023, iRon7 (Ronald Bode) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of the software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions. 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | In addition, the following restrictions apply: 10 | 11 | 1. The Software and any modifications made to it may not be used for the purpose of training or improving machine learning algorithms, including but not limited to artificial intelligence, natural language processing, or data mining. This condition applies to any derivatives, modifications, or updates based on the Software code. Any usage of the Software in an AI-training dataset is considered a breach of this License. 12 | 13 | 2. The Software may not be included in any dataset used for training or improving machine learning algorithms, 14 | including but not limited to artificial intelligence, natural language processing, or data mining. 15 | 16 | 3. Any person or organization found to be in violation of these restrictions will be subject to legal action and may be held liable for any damages resulting from such use. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # ConvertFrom-SourceTable 3 | 4 | Converts a fixed column table to objects. 5 | 6 | ## Syntax 7 | 8 | ```PowerShell 9 | ConvertFrom-SourceTable 10 | [-InputObject ] 11 | [-Header ] 12 | [-Ruler ] 13 | [-HorizontalDash = '-'] 14 | [-VerticalDash = '|'] 15 | [-Junction = '+'] 16 | [-Anchor = ':'] 17 | [-Omit ] 18 | [-ParseRightAligned] 19 | [-Literal] 20 | [] 21 | ``` 22 | 23 | ## Description 24 | 25 | The [`ConvertFrom-SourceTable`](https://github.com/iRon7/ConvertFrom-SourceTable) cmdlet creates objects from a fixed column 26 | source table possibly surrounded by horizontal and/or vertical rulers. 27 | 28 | **Definitions:** 29 | 30 | * The width of a source table column is outlined by the header width, 31 | the ruler width and the width of the data. 32 | 33 | * Column and Data alignment (none, left, right or justified) is defined 34 | by the existence of any (non-separator) character at the start or end 35 | of a column. 36 | 37 | * Column alignment (which is used for a default field alignment) is 38 | defined by the first and last character or space of the header and 39 | the ruler of the outlined column. 40 | 41 | ## Examples 42 | 43 | ### Example 1: Restore objects from a Format-Table output 44 | 45 | 46 | The following loads the file properties from general PowerShell output table: 47 | 48 | ```PowerShell 49 | $Files = ConvertFrom-SourceTable ' 50 | Mode LastWriteTime Length Name 51 | ---- ------------- ------ ---- 52 | d----l 11/16/2018 8:30 PM Archive 53 | -a---l 5/22/2018 12:05 PM (726) Build-Expression.ps1 54 | -a---l 11/16/2018 7:38 PM 2143 CHANGELOG 55 | -a---l 11/17/2018 10:42 AM 14728 ConvertFrom-SourceTable.ps1 56 | -a---l 11/17/2018 11:04 AM 23909 ConvertFrom-SourceTable.Tests.ps1 57 | -a---l 8/4/2018 11:04 AM (6237) Import-SourceTable.ps1' 58 | ``` 59 | 60 | > Note that it is generally not a good practice to use console output cmdlets 61 | > as e.g. [`Format-Table`](https://go.microsoft.com/fwlink/?LinkID=2096703) for anything else than displaying the results. 62 | 63 | ### Example 2: Convert from a markdown table 64 | 65 | 66 | The following command loads a list of employee objects from a markdown table: 67 | 68 | ```PowerShell 69 | $Employees = ConvertFrom-SourceTable ' 70 | | Department | Name | Country | 71 | | ----------- | ------- | ------- | 72 | | Sales | Aerts | Belgium | 73 | | Engineering | Bauer | Germany | 74 | | Sales | Cook | England | 75 | | Engineering | Duval | France | 76 | | Marketing | Evans | England | 77 | | Engineering | Fischer | Germany |' 78 | ``` 79 | 80 | ### Example 3: Parse right aligned data 81 | 82 | 83 | In the following example each item in the (hexadecimal) `Value` column will be parsed 84 | to integer value and each item in the `RGB` column to an array with three values. 85 | 86 | ```PowerShell 87 | $Colors = ConvertFrom-SourceTable -Parse ' 88 | Name Value RGB 89 | ---- ----- --- 90 | Black 0x000000 0,0,0 91 | White 0xFFFFFF 255,255,255 92 | Red 0xFF0000 255,0,0 93 | Lime 0x00FF00 0,255,0 94 | Blue 0x0000FF 0,0,255 95 | Yellow 0xFFFF00 255,255,0 96 | Cyan 0x00FFFF 0,255,255 97 | Magenta 0xFF00FF 255,0,255 98 | Silver 0xC0C0C0 192,192,192 99 | Gray 0x808080 128,128,128 100 | Maroon 0x800000 128,0,0 101 | Olive 0x808000 128,128,0 102 | Green 0x008000 0,128,0 103 | Purple 0x800080 128,0,128 104 | Teal 0x008080 0,128,128 105 | Navy 0x000080 0,0,128' 106 | 107 | $Colors | Where {$_.Name -eq "Red"} 108 | 109 | Name Value RGB 110 | ---- ----- --- 111 | Red 16711680 {255, 0, 0} 112 | ``` 113 | 114 | ### Example 4: Custom column types 115 | 116 | 117 | In the following example each item in the first column is casted to a `version` object 118 | and each item in the second column to a `DateTime` object. 119 | Notice that the type name is used as a property name in case column name is omitted. 120 | 121 | ```PowerShell 122 | $ChangeLog = ConvertFrom-SourceTable -Parse ' 123 | [Version] [DateTime]Date Author Comments 124 | --------- -------------- ------ -------- 125 | 0.0.10 2018-05-03 Ronald Bode First design 126 | 0.0.20 2018-05-09 Ronald Bode Pester ready version 127 | 0.0.21 2018-05-09 Ronald Bode removed support for String[] types 128 | 0.0.22 2018-05-24 Ronald Bode Better "right aligned" definition 129 | 0.0.23 2018-05-25 Ronald Bode Resolved single column bug 130 | 0.0.24 2018-05-26 Ronald Bode Treating markdown table input as an option 131 | 0.0.25 2018-05-27 Ronald Bode Resolved error due to blank top lines' 132 | ``` 133 | 134 | ## Parameters 135 | 136 | ### **`-InputObject `** 137 | 138 | Specifies the source table strings to be converted to objects. 139 | Enter a variable that contains the source table strings or type a 140 | command or expression that gets the source table strings. 141 | You might also pipe the source table strings to [`ConvertFrom-SourceTable`](https://github.com/iRon7/ConvertFrom-SourceTable). 142 | 143 | Note that streamed table rows are intermediately processed and 144 | released for the next cmdlet. In this mode, there is a higher 145 | possibility that floating tables or column data cannot be determined 146 | to be part of a specific column (as there is no overview of the table 147 | data that follows). To resolve this, provide all rows in once or use 148 | one of the following [-Header](#-header) and/or [-Ruler](#-ruler) parameters. 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 |
Type:String[]
Mandatory:False
Position:Named
Default value:
Accept pipeline input:False
Accept wildcard characters:False
158 | 159 | ### **`-Header `** 160 | 161 | A string that defines the header line of an headless table or a multiple 162 | strings where each item represents the column name. 163 | In case the header contains a single string, it is used to define the 164 | (property) names, the size and alignment of the column, therefore it is 165 | key that the columns names are properly aligned with the rest of the 166 | column (including any table indents). 167 | If the header contains multiple strings, each string will be used to 168 | define the property names of each object. In this case, column alignment 169 | is based on the rest of the data and possible ruler. 170 | 171 | > [!TIP] 172 | > To skip a column, set the header name of the concerned column index to 173 | > an empty string (or `$Null`). 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 |
Type:String[]
Mandatory:False
Position:Named
Default value:
Accept pipeline input:False
Accept wildcard characters:False
183 | 184 | ### **`-Ruler `** 185 | 186 | A string that replaces any (horizontal) ruler in the input table which 187 | helps to define character columns in occasions where the table column 188 | margins are indefinable. 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 |
Type:String
Mandatory:False
Position:Named
Default value:
Accept pipeline input:False
Accept wildcard characters:False
198 | 199 | ### **`-HorizontalDash `** 200 | 201 | This parameter (Alias `-HDash`) defines the horizontal ruler character. 202 | By default, each streamed table row (or a total raw table) will be 203 | searched for a ruler existing out of horizontal dash characters (`-`), 204 | spaces and possible vertical dashes. If the ruler is found, the prior 205 | line is presumed to be the header. If the ruler is not found within 206 | the first (two) streamed data lines, the first line is presumed the 207 | header line. 208 | If `-HorizontalDash` explicitly defined, all (streamed) lines will be 209 | searched for a matching ruler. 210 | If `-HorizontalDash` is set to `$Null`, the first data line is presumed 211 | the header line (unless the [-VerticalDash](#-verticaldash) parameter is set). 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 |
Type:Char
Mandatory:False
Position:Named
Default value:'-'
Accept pipeline input:False
Accept wildcard characters:False
221 | 222 | ### **`-VerticalDash `** 223 | 224 | This parameter (Alias `-VDash`) defines the vertical ruler character. 225 | By default, each streamed table row (or a total raw table) will be 226 | searched for a header with vertical dash characters (`|`). If the 227 | header is not found within the first streamed data line, the first 228 | line is presumed the header line. 229 | If `-VerticalDash` explicitly defined, all (streamed) lines will be 230 | searched for a header with a vertical dash character. 231 | If `-VerticalDash` is set to `$Null`, the first data line is presumed 232 | the header line (unless the [-HorizontalDash](#-horizontaldash) parameter is set). 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 |
Type:Char
Mandatory:False
Position:Named
Default value:'|'
Accept pipeline input:False
Accept wildcard characters:False
242 | 243 | ### **`-Junction `** 244 | 245 | The -Junction parameter (default: "+") defines the character used for 246 | the junction between the horizontal ruler and vertical ruler. 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 |
Type:Char
Mandatory:False
Position:Named
Default value:'+'
Accept pipeline input:False
Accept wildcard characters:False
256 | 257 | ### **`-Anchor `** 258 | 259 | The -Anchor parameter (default: `:`) defines the character used for 260 | the alignment anchor. If used in the header row, it will be used to 261 | define the default alignment, meaning that justified (full width) 262 | values will be parsed. 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 |
Type:Char
Mandatory:False
Position:Named
Default value:':'
Accept pipeline input:False
Accept wildcard characters:False
272 | 273 | ### **`-Omit `** 274 | 275 | A string of characters to omit from the header and data. Each omitted 276 | character will be replaced with a space. 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 |
Type:String
Mandatory:False
Position:Named
Default value:
Accept pipeline input:False
Accept wildcard characters:False
286 | 287 | ### **`-ParseRightAligned`** 288 | 289 | This parameter will cause any right aligned data to be parsed according 290 | to the following formatting and alignment rules: 291 | 292 | * Data that is left aligned will be parsed to the generic column type 293 | which is a string by default. 294 | 295 | * Data that is right aligned will be parsed. 296 | 297 | * Data that is justified (using the full column with) is following the 298 | the header alignment and parsed if the header is right aligned. 299 | 300 | * The default column type can be set by prefixing the column name with 301 | a standard (PowerShell) cast operator (a data type enclosed in 302 | square brackets, e.g.: `[Int]ID`) 303 | 304 | #### Caution 305 | 306 | Take reasonable precautions when using the `-ParseRightAligned` parameter 307 | in scripts. When using the `-ParseRightAligned` parameter to convert data 308 | from a table, verify that the data is safe to be parsed before running it. 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 |
Type:SwitchParameter
Mandatory:False
Position:Named
Default value:
Accept pipeline input:False
Accept wildcard characters:False
318 | 319 | ### **`-Literal`** 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 |
Type:SwitchParameter
Mandatory:False
Position:Named
Default value:
Accept pipeline input:False
Accept wildcard characters:False
329 | 330 | ## Related Links 331 | 332 | * https://github.com/iRon7/ConvertFrom-SourceTable 333 | 334 | [comment]: <> (Created with Get-MarkdownHelp: Install-Script -Name Get-MarkdownHelp) 335 | -------------------------------------------------------------------------------- /Tests/ConvertFrom-SourceTable.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules @{ModuleName="Pester"; ModuleVersion="5.0.0"} 2 | 3 | Set-StrictMode -Version Latest 4 | Set-Alias ConvertFrom-SourceTable .\ConvertFrom-SourceTable.ps1 5 | 6 | 7 | BeforeAll { 8 | 9 | Function Differentiate { 10 | Param ( 11 | [Parameter(Position=0)][Object[]]$Expected, [Parameter(ValueFromPipeLine = $True)][Object[]]$Actual 12 | ) 13 | $Property = ($Actual | Select-Object -First 1).PSObject.Properties | Select-Object -Expand Name 14 | $DifferenceCount = @(Compare-Object $Expected $Actual -Property $Property).Count 15 | If ($DifferenceCount) { 16 | Write-Host 'Expected:' ($Expected | Out-String) 17 | Write-Host 'Actual:' ($Actual | Out-String) 18 | } 19 | Write-Output $DifferenceCount 20 | } 21 | 22 | $SimpleObject = @( 23 | [PSCustomObject]@{'Country' = 'Belgium'; 'Department' = 'Sales'; 'Name' = 'Aerts'}, 24 | [PSCustomObject]@{'Country' = 'Germany'; 'Department' = 'Engineering'; 'Name' = 'Bauer'}, 25 | [PSCustomObject]@{'Country' = 'England'; 'Department' = 'Sales'; 'Name' = 'Cook'}, 26 | [PSCustomObject]@{'Country' = 'France'; 'Department' = 'Engineering'; 'Name' = 'Duval'}, 27 | [PSCustomObject]@{'Country' = 'England'; 'Department' = 'Marketing'; 'Name' = 'Evans'}, 28 | [PSCustomObject]@{'Country' = 'Germany'; 'Department' = 'Engineering'; 'Name' = 'Fischer'} 29 | ) 30 | 31 | $ColorObject = @( 32 | [PSCustomObject]@{'Name' = 'Black'; 'RGB' = @(0,0,0); 'Value' = 0}, 33 | [PSCustomObject]@{'Name' = 'White'; 'RGB' = @(255,255,255); 'Value' = 16777215}, 34 | [PSCustomObject]@{'Name' = 'Red'; 'RGB' = @(255,0,0); 'Value' = 16711680}, 35 | [PSCustomObject]@{'Name' = 'Lime'; 'RGB' = @(0,255,0); 'Value' = 65280}, 36 | [PSCustomObject]@{'Name' = 'Blue'; 'RGB' = @(0,0,255); 'Value' = 255}, 37 | [PSCustomObject]@{'Name' = 'Yellow'; 'RGB' = @(255,255,0); 'Value' = 16776960}, 38 | [PSCustomObject]@{'Name' = 'Cyan'; 'RGB' = @(0,255,255); 'Value' = 65535}, 39 | [PSCustomObject]@{'Name' = 'Magenta'; 'RGB' = @(255,0,255); 'Value' = 16711935}, 40 | [PSCustomObject]@{'Name' = 'Silver'; 'RGB' = @(192,192,192); 'Value' = 12632256}, 41 | [PSCustomObject]@{'Name' = 'Gray'; 'RGB' = @(128,128,128); 'Value' = 8421504}, 42 | [PSCustomObject]@{'Name' = 'Maroon'; 'RGB' = @(128,0,0); 'Value' = 8388608}, 43 | [PSCustomObject]@{'Name' = 'Olive'; 'RGB' = @(128,128,0); 'Value' = 8421376}, 44 | [PSCustomObject]@{'Name' = 'Green'; 'RGB' = @(0,128,0); 'Value' = 32768}, 45 | [PSCustomObject]@{'Name' = 'Purple'; 'RGB' = @(128,0,128); 'Value' = 8388736}, 46 | [PSCustomObject]@{'Name' = 'Teal'; 'RGB' = @(0,128,128); 'Value' = 32896}, 47 | [PSCustomObject]@{'Name' = 'Navy'; 'RGB' = @(0,0,128); 'Value' = 128} 48 | ) 49 | 50 | $VersionObject = @( 51 | [PSCustomObject]@{ 52 | 'Author' = 'Ronald Bode' 53 | 'Comments' = 'First design' 54 | 'Date' = [datetime]'2018-05-03' 55 | 'Version' = [version]'0.0.10' 56 | }, 57 | [PSCustomObject]@{ 58 | 'Author' = 'Ronald Bode' 59 | 'Comments' = 'Pester ready version' 60 | 'Date' = [datetime]'2018-05-09' 61 | 'Version' = [version]'0.0.20' 62 | }, 63 | [PSCustomObject]@{ 64 | 'Author' = 'Ronald Bode' 65 | 'Comments' = 'removed support for String[] types' 66 | 'Date' = [datetime]'2018-05-09' 67 | 'Version' = [version]'0.0.21' 68 | }, 69 | [PSCustomObject]@{ 70 | 'Author' = 'Ronald Bode' 71 | 'Comments' = 'Better "right aligned" definition' 72 | 'Date' = [datetime]'2018-05-24' 73 | 'Version' = [version]'0.0.22' 74 | }, 75 | [PSCustomObject]@{ 76 | 'Author' = 'Ronald Bode' 77 | 'Comments' = 'Resolved single column bug' 78 | 'Date' = [datetime]'2018-05-25' 79 | 'Version' = [version]'0.0.23' 80 | }, 81 | [PSCustomObject]@{ 82 | 'Author' = 'Ronald Bode' 83 | 'Comments' = 'Treating markdown table input as an option' 84 | 'Date' = [datetime]'2018-05-26' 85 | 'Version' = [version]'0.0.24' 86 | }, 87 | [PSCustomObject]@{ 88 | 'Author' = 'Ronald Bode' 89 | 'Comments' = 'Resolved error due to blank top lines' 90 | 'Date' = [datetime]'2018-05-27' 91 | 'Version' = [version]'0.0.25' 92 | } 93 | ) 94 | 95 | $TypeObject = @( 96 | [PSCustomObject]@{ 97 | 'Type' = 'String' 98 | 'Value' = 'Hello World' 99 | 'PowerShell' = 'Hello World' 100 | 'Output' = 'Hello World' 101 | }, 102 | [PSCustomObject]@{ 103 | 'Type' = 'Number' 104 | 'Value' = 123 105 | 'PowerShell' = 123 106 | 'Output' = 123 107 | }, 108 | [PSCustomObject]@{ 109 | 'Type' = 'Null' 110 | 'Value' = $Null 111 | 'PowerShell' = $Null 112 | 'Output' = '' 113 | }, 114 | [PSCustomObject]@{ 115 | 'Type' = 'Boolean' 116 | 'Value' = $True 117 | 'PowerShell' = $True 118 | 'Output' = 'True' 119 | }, 120 | [PSCustomObject]@{ 121 | 'Type' = 'Boolean' 122 | 'Value' = $False 123 | 'PowerShell' = $False 124 | 'Output' = 'False' 125 | }, 126 | [PSCustomObject]@{ 127 | 'Type' = 'DateTime' 128 | 'Value' = [DateTime]'1963-10-07T21:47:00.0000000' 129 | 'PowerShell' = [DateTime]'1963-10-07T21:47:00.0000000' 130 | 'Output' = '1963-10-07 9:47:00 PM' 131 | }, 132 | [PSCustomObject]@{ 133 | 'Type' = 'Array' 134 | 'Value' = @(1, 'Two') 135 | 'PowerShell' = @(1, 'Two') 136 | 'Output' = '{1, two}' 137 | }, 138 | [PSCustomObject]@{ 139 | 'Type' = 'HashTable' 140 | 'Value' = @{'One' = 1; 'Two' = 2} 141 | 'PowerShell' = @{'One' = 1; 'Two' = 2} 142 | 'Output' = '{One, Two}' 143 | }, 144 | [PSCustomObject]@{ 145 | 'Type' = 'Object' 146 | 'Value' = [PSCustomObject]@{'One' = 1; 'Two' = 2} 147 | 'PowerShell' = [PSCustomObject]@{'One' = 1; 'Two' = 2} 148 | 'Output' = '@{One=1; Two=2}' 149 | } 150 | ) 151 | 152 | $DirObject = @( 153 | [PSCustomObject]@{'LastWriteTime' = '11/16/2018 8:30 PM'; 'Length' = ''; 'Mode' = 'd----l'; 'Name' = 'Archive'} 154 | [PSCustomObject]@{'LastWriteTime' = '5/22/2018 12:05 PM'; 'Length' = '(726)'; 'Mode' = '-a---l'; 'Name' = 'Build-Expression.ps1'} 155 | [PSCustomObject]@{'LastWriteTime' = '11/16/2018 7:38 PM'; 'Length' = '2143'; 'Mode' = '-a---l'; 'Name' = 'CHANGELOG'} 156 | [PSCustomObject]@{'LastWriteTime' = '11/17/2018 10:42 AM'; 'Length' = '14728'; 'Mode' = '-a---l'; 'Name' = 'ConvertFrom-SourceTable.ps1'} 157 | [PSCustomObject]@{'LastWriteTime' = '11/17/2018 11:04 AM'; 'Length' = '23909'; 'Mode' = '-a---l'; 'Name' = 'ConvertFrom-SourceTable.Tests.ps1'} 158 | [PSCustomObject]@{'LastWriteTime' = '8/4/2018 11:04 AM'; 'Length' = '(6237)'; 'Mode' = '-a---l'; 'Name' = 'Import-SourceTable.ps1'} 159 | ) 160 | } 161 | 162 | Describe 'ConvertFrom-Table' { 163 | 164 | Context 'Simple string table' { 165 | 166 | BeforeAll { 167 | $Table = ' 168 | Department Name Country 169 | ---------- ---- ------- 170 | Sales Aerts Belgium 171 | Engineering Bauer Germany 172 | Sales Cook England 173 | Engineering Duval France 174 | Marketing Evans England 175 | Engineering Fischer Germany 176 | ' 177 | $Object = $SimpleObject 178 | } 179 | 180 | It 'Raw table as argument' { 181 | $Actual = ConvertFrom-SourceTable $Table 182 | ,$Actual | Differentiate $Object | Should -Be 0 183 | } 184 | 185 | It 'Raw table from pipeline' { 186 | $Actual = $Table | ConvertFrom-SourceTable 187 | ,$Actual | Differentiate $Object | Should -Be 0 188 | } 189 | 190 | It 'Streamed table lines from pipeline' { 191 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable 192 | ,$Actual | Differentiate $Object | Should -Be 0 193 | } 194 | } 195 | 196 | Context 'Markdown table' { 197 | 198 | BeforeAll { 199 | $Table = ' 200 | | Department | Name | Country | 201 | | ----------- | ------- | ------- | 202 | | Sales | Aerts | Belgium | 203 | | Engineering | Bauer | Germany | 204 | | Sales | Cook | England | 205 | | Engineering | Duval | France | 206 | | Marketing | Evans | England | 207 | | Engineering | Fischer | Germany | 208 | ' 209 | $Object = $SimpleObject 210 | } 211 | 212 | It 'Raw table as argument' { 213 | $Actual = ConvertFrom-SourceTable $Table 214 | ,$Actual | Differentiate $Object | Should -Be 0 215 | } 216 | 217 | It 'Raw table from pipeline' { 218 | $Actual = $Table | ConvertFrom-SourceTable 219 | ,$Actual | Differentiate $Object | Should -Be 0 220 | } 221 | 222 | It 'Streamed table lines from pipeline' { 223 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable 224 | ,$Actual | Differentiate $Object | Should -Be 0 225 | } 226 | } 227 | 228 | Context 'Color table with casted values' { 229 | 230 | BeforeAll { 231 | $Table = ' 232 | Name Value RGB 233 | ---- ----- --- 234 | Black 0x000000 0,0,0 235 | White 0xFFFFFF 255,255,255 236 | Red 0xFF0000 255,0,0 237 | Lime 0x00FF00 0,255,0 238 | Blue 0x0000FF 0,0,255 239 | Yellow 0xFFFF00 255,255,0 240 | Cyan 0x00FFFF 0,255,255 241 | Magenta 0xFF00FF 255,0,255 242 | Silver 0xC0C0C0 192,192,192 243 | Gray 0x808080 128,128,128 244 | Maroon 0x800000 128,0,0 245 | Olive 0x808000 128,128,0 246 | Green 0x008000 0,128,0 247 | Purple 0x800080 128,0,128 248 | Teal 0x008080 0,128,128 249 | Navy 0x000080 0,0,128 250 | ' 251 | $Object = $ColorObject 252 | } 253 | 254 | It 'Raw table as argument' { 255 | $Actual = ConvertFrom-SourceTable -Parse $Table 256 | ,$Actual | Differentiate $Object | Should -Be 0 257 | } 258 | 259 | It 'Raw table from pipeline' { 260 | $Actual = $Table | ConvertFrom-SourceTable -Parse 261 | ,$Actual | Differentiate $Object | Should -Be 0 262 | } 263 | 264 | It 'Streamed table lines from pipeline' { 265 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable -Parse 266 | ,$Actual | Differentiate $Object | Should -Be 0 267 | } 268 | } 269 | 270 | Context 'Markdown color table with casted values' { 271 | 272 | BeforeAll { 273 | $Table = ' 274 | |---------|----------|---------------| 275 | | Name | Value | RGB | 276 | |---------|----------|---------------| 277 | | Black | 0x000000 | 0, 0, 0 | 278 | | White | 0xFFFFFF | 255, 255, 255 | 279 | | Red | 0xFF0000 | 255, 0, 0 | 280 | | Lime | 0x00FF00 | 0, 255, 0 | 281 | | Blue | 0x0000FF | 0, 0, 255 | 282 | | Yellow | 0xFFFF00 | 255, 255, 0 | 283 | | Cyan | 0x00FFFF | 0, 255, 255 | 284 | | Magenta | 0xFF00FF | 255, 0, 255 | 285 | | Silver | 0xC0C0C0 | 192, 192, 192 | 286 | | Gray | 0x808080 | 128, 128, 128 | 287 | | Maroon | 0x800000 | 128, 0, 0 | 288 | | Olive | 0x808000 | 128, 128, 0 | 289 | | Green | 0x008000 | 0, 128, 0 | 290 | | Purple | 0x800080 | 128, 0, 128 | 291 | | Teal | 0x008080 | 0, 128, 128 | 292 | | Navy | 0x000080 | 0, 0, 128 | 293 | |---------|----------|---------------| 294 | ' 295 | $Object = $ColorObject 296 | } 297 | 298 | It 'Raw table as argument' { 299 | $Actual = ConvertFrom-SourceTable $Table -Parse 300 | ,$Actual | Differentiate $Object | Should -Be 0 301 | } 302 | 303 | It 'Raw table from pipeline' { 304 | $Actual = $Table | ConvertFrom-SourceTable -Parse 305 | ,$Actual | Differentiate $Object | Should -Be 0 306 | } 307 | 308 | It 'Streamed table lines from pipeline' { 309 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable -Parse 310 | ,$Actual | Differentiate $Object | Should -Be 0 311 | } 312 | } 313 | 314 | Context 'Color table with only vertical rulers and casted values' { 315 | 316 | BeforeAll { 317 | $Table = ' 318 | | Name | Value | RGB | 319 | | Black | 0x000000 | 0, 0, 0 | 320 | | White | 0xFFFFFF | 255, 255, 255 | 321 | | Red | 0xFF0000 | 255, 0, 0 | 322 | | Lime | 0x00FF00 | 0, 255, 0 | 323 | | Blue | 0x0000FF | 0, 0, 255 | 324 | | Yellow | 0xFFFF00 | 255, 255, 0 | 325 | | Cyan | 0x00FFFF | 0, 255, 255 | 326 | | Magenta | 0xFF00FF | 255, 0, 255 | 327 | | Silver | 0xC0C0C0 | 192, 192, 192 | 328 | | Gray | 0x808080 | 128, 128, 128 | 329 | | Maroon | 0x800000 | 128, 0, 0 | 330 | | Olive | 0x808000 | 128, 128, 0 | 331 | | Green | 0x008000 | 0, 128, 0 | 332 | | Purple | 0x800080 | 128, 0, 128 | 333 | | Teal | 0x008080 | 0, 128, 128 | 334 | | Navy | 0x000080 | 0, 0, 128 | 335 | ' 336 | $Object = $ColorObject 337 | } 338 | 339 | It 'Raw table as argument' { 340 | $Actual = ConvertFrom-SourceTable $Table -Parse 341 | ,$Actual | Differentiate $Object | Should -Be 0 342 | } 343 | 344 | It 'Raw table from pipeline' { 345 | $Actual = $Table | ConvertFrom-SourceTable -Parse 346 | ,$Actual | Differentiate $Object | Should -Be 0 347 | } 348 | 349 | It 'Streamed table lines from pipeline' { 350 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable -Parse 351 | ,$Actual | Differentiate $Object | Should -Be 0 352 | } 353 | } 354 | 355 | Context 'Markdown color table with junction characters' { 356 | 357 | BeforeAll { 358 | $Table = ' 359 | +---------+----------+---------------+ 360 | | Name | Value | RGB | 361 | +---------+----------+---------------+ 362 | | Black | 0x000000 | 0, 0, 0 | 363 | | White | 0xFFFFFF | 255, 255, 255 | 364 | | Red | 0xFF0000 | 255, 0, 0 | 365 | | Lime | 0x00FF00 | 0, 255, 0 | 366 | | Blue | 0x0000FF | 0, 0, 255 | 367 | | Yellow | 0xFFFF00 | 255, 255, 0 | 368 | | Cyan | 0x00FFFF | 0, 255, 255 | 369 | | Magenta | 0xFF00FF | 255, 0, 255 | 370 | | Silver | 0xC0C0C0 | 192, 192, 192 | 371 | | Gray | 0x808080 | 128, 128, 128 | 372 | | Maroon | 0x800000 | 128, 0, 0 | 373 | | Olive | 0x808000 | 128, 128, 0 | 374 | | Green | 0x008000 | 0, 128, 0 | 375 | | Purple | 0x800080 | 128, 0, 128 | 376 | | Teal | 0x008080 | 0, 128, 128 | 377 | | Navy | 0x000080 | 0, 0, 128 | 378 | +---------+----------+---------------+ 379 | ' 380 | $Object = $ColorObject 381 | } 382 | 383 | It 'Raw table as argument' { 384 | $Actual = ConvertFrom-SourceTable $Table -Parse 385 | ,$Actual | Differentiate $Object | Should -Be 0 386 | } 387 | 388 | It 'Raw table from pipeline' { 389 | $Actual = $Table | ConvertFrom-SourceTable -Parse 390 | ,$Actual | Differentiate $Object | Should -Be 0 391 | } 392 | 393 | It 'Streamed table lines from pipeline' { 394 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable -Parse 395 | ,$Actual | Differentiate $Object | Should -Be 0 396 | } 397 | } 398 | 399 | Context 'Table without Ruler' { 400 | 401 | BeforeAll { 402 | $Table = ' 403 | Department Name Country 404 | Sales Aerts Belgium 405 | Engineering Bauer Germany 406 | Sales Cook England 407 | Engineering Duval France 408 | Marketing Evans England 409 | Engineering Fischer Germany 410 | ' 411 | $Object = $SimpleObject 412 | } 413 | 414 | It 'Raw table as argument' { 415 | $Actual = ConvertFrom-SourceTable $Table 416 | ,$Actual | Differentiate $Object | Should -Be 0 417 | } 418 | 419 | It 'Raw table from pipeline' { 420 | $Actual = $Table | ConvertFrom-SourceTable 421 | ,$Actual | Differentiate $Object | Should -Be 0 422 | } 423 | 424 | It 'Streamed table lines from pipeline' { 425 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable 426 | ,$Actual | Differentiate $Object | Should -Be 0 427 | } 428 | } 429 | 430 | Context 'Markdown table without ruler' { 431 | 432 | BeforeAll { 433 | $Table = ' 434 | | Department | Name | Country | 435 | | Sales | Aerts | Belgium | 436 | | Engineering | Bauer | Germany | 437 | | Sales | Cook | England | 438 | | Engineering | Duval | France | 439 | | Marketing | Evans | England | 440 | | Engineering | Fischer | Germany | 441 | ' 442 | $Object = $SimpleObject 443 | } 444 | 445 | It 'Raw table as argument' { 446 | $Actual = ConvertFrom-SourceTable $Table 447 | ,$Actual | Differentiate $Object | Should -Be 0 448 | } 449 | 450 | It 'Raw table from pipeline' { 451 | $Actual = $Table | ConvertFrom-SourceTable 452 | ,$Actual | Differentiate $Object | Should -Be 0 453 | } 454 | 455 | It 'Streamed table lines from pipeline' { 456 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable 457 | ,$Actual | Differentiate $Object | Should -Be 0 458 | } 459 | } 460 | 461 | Context 'Markdown table without ruler and side borders' { 462 | 463 | BeforeAll { 464 | $Table = ' 465 | Department | Name | Country 466 | Sales | Aerts | Belgium 467 | Engineering | Bauer | Germany 468 | Sales | Cook | England 469 | Engineering | Duval | France 470 | Marketing | Evans | England 471 | Engineering | Fischer | Germany 472 | ' 473 | $Object = $SimpleObject 474 | } 475 | 476 | It 'Raw table as argument' { 477 | $Actual = ConvertFrom-SourceTable $Table 478 | ,$Actual | Differentiate $Object | Should -Be 0 479 | } 480 | 481 | It 'Raw table from pipeline' { 482 | $Actual = $Table | ConvertFrom-SourceTable 483 | ,$Actual | Differentiate $Object | Should -Be 0 484 | } 485 | 486 | It 'Streamed table lines from pipeline' { 487 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable 488 | ,$Actual | Differentiate $Object | Should -Be 0 489 | } 490 | } 491 | 492 | Context 'CSV file' { 493 | 494 | BeforeAll { 495 | $Table = ' 496 | Department,Name,Country 497 | Sales,Aerts,Belgium 498 | Engineering,Bauer,Germany 499 | Sales,Cook,England 500 | Engineering,Duval,France 501 | Marketing,Evans,England 502 | Engineering,Fischer,Germany 503 | ' 504 | $Object = $SimpleObject 505 | } 506 | 507 | It 'Raw table as argument' { 508 | $Actual = ConvertFrom-SourceTable -VDash ',' $Table 509 | ,$Actual | Differentiate $Object | Should -Be 0 510 | } 511 | 512 | It 'Raw table from pipeline' { 513 | $Actual = $Table | ConvertFrom-SourceTable -VDash ',' 514 | ,$Actual | Differentiate $Object | Should -Be 0 515 | } 516 | 517 | It 'Streamed table lines from pipeline' { 518 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable -VDash ',' 519 | ,$Actual | Differentiate $Object | Should -Be 0 520 | } 521 | } 522 | 523 | Context 'Version table with default column types' { 524 | 525 | BeforeAll { 526 | $Table = ' 527 | [Version] [DateTime]Date Author Comments 528 | --------- -------------- ------ -------- 529 | 0.0.10 2018-05-03 Ronald Bode First design 530 | 0.0.20 2018-05-09 Ronald Bode Pester ready version 531 | 0.0.21 2018-05-09 Ronald Bode removed support for String[] types 532 | 0.0.22 2018-05-24 Ronald Bode Better "right aligned" definition 533 | 0.0.23 2018-05-25 Ronald Bode Resolved single column bug 534 | 0.0.24 2018-05-26 Ronald Bode Treating markdown table input as an option 535 | 0.0.25 2018-05-27 Ronald Bode Resolved error due to blank top lines 536 | ' 537 | $Object = $VersionObject 538 | } 539 | 540 | It 'Raw table as argument' { 541 | $Actual = ConvertFrom-SourceTable $Table -Parse 542 | ,$Actual | Differentiate $Object | Should -Be 0 543 | } 544 | 545 | It 'Raw table from pipeline' { 546 | $Actual = $Table | ConvertFrom-SourceTable -Parse 547 | ,$Actual | Differentiate $Object | Should -Be 0 548 | } 549 | 550 | It 'Streamed table lines from pipeline' { 551 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable -Parse 552 | ,$Actual | Differentiate $Object | Should -Be 0 553 | } 554 | } 555 | 556 | Context '(Narrow) markdown version table with and default column types' { 557 | 558 | BeforeAll { 559 | $Table = ' 560 | [Version]|[DateTime]Date|Author |Comments 561 | ---------|--------------|-----------|------------------------------------------ 562 | 0.0.10 |2018-05-03 |Ronald Bode|First design 563 | 0.0.20 |2018-05-09 |Ronald Bode|Pester ready version 564 | 0.0.21 |2018-05-09 |Ronald Bode|removed support for String[] types 565 | 0.0.22 |2018-05-24 |Ronald Bode|Better "right aligned" definition 566 | 0.0.23 |2018-05-25 |Ronald Bode|Resolved single column bug 567 | 0.0.24 |2018-05-26 |Ronald Bode|Treating markdown table input as an option 568 | 0.0.25 |2018-05-27 |Ronald Bode|Resolved error due to blank top lines 569 | ' 570 | $Object = $VersionObject 571 | } 572 | 573 | It 'Raw table as argument' { 574 | $Actual = ConvertFrom-SourceTable -Parse $Table 575 | ,$Actual | Differentiate $Object 576 | } 577 | 578 | It 'Raw table from pipeline' { 579 | $Actual = $Table | ConvertFrom-SourceTable -Parse 580 | ,$Actual | Differentiate $Object 581 | } 582 | 583 | It 'Streamed table lines from pipeline' { 584 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable -Parse 585 | ,$Actual | Differentiate $Object 586 | } 587 | } 588 | 589 | Context 'Type table with mixed value types' { 590 | 591 | BeforeAll { 592 | $Table = ' 593 | Type Value PowerShell Output 594 | ---- ------------------------------- ----------- --------------------- 595 | String Hello World "Hello World" Hello World 596 | Number 123 123 123 597 | Null Null $Null 598 | Boolean True $True True 599 | Boolean False $False False 600 | DateTime [DateTime]"1963-10-07T21:47" [DateTime]"1963-10-07 21:47" 1963-10-07 9:47:00 PM 601 | Array 1, "Two" @(1, "Two") {1, two} 602 | HashTable @{One=1; Two=2} @{One=1; Two=2} {One, Two} 603 | Object [PSCustomObject]@{One=1; Two=2} [PSCustomObject]@{One=1; Two=2} @{One=1; Two=2} 604 | ' 605 | $Object = $TypeObject 606 | } 607 | 608 | It 'Raw table as argument' { 609 | $Actual = ConvertFrom-SourceTable $Table -Parse 610 | ,$Actual | Differentiate $Object | Should -Be 0 611 | } 612 | 613 | It 'Raw table from pipeline' { 614 | $Actual = $Table | ConvertFrom-SourceTable -Parse 615 | ,$Actual | Differentiate $Object | Should -Be 0 616 | } 617 | 618 | It 'Streamed table lines from pipeline' { 619 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable -Parse 620 | ,$Actual | Differentiate $Object | Should -Be 0 621 | } 622 | } 623 | 624 | Context 'Markdown type table with mixed value types' { 625 | 626 | BeforeAll { 627 | $Table = ' 628 | |-----------|---------------------------------|---------------------------------|-----------------------| 629 | | Type | Value | PowerShell | Output | 630 | |-----------|---------------------------------|---------------------------------|-----------------------| 631 | | String | Hello World | "Hello World" | Hello World | 632 | | Number | 123 | 123 | 123 | 633 | | Null | Null | $Null | | 634 | | Boolean | True | $True | True | 635 | | Boolean | False | $False | False | 636 | | DateTime | [DateTime]"1963-10-07 21:47" | [DateTime]"1963-10-07 21:47" | 1963-10-07 9:47:00 PM | 637 | | Array | 1, "Two" | @(1, "Two") | {1, two} | 638 | | HashTable | @{One=1; Two=2} | @{One=1; Two=2} | {One, Two} | 639 | | Object | [PSCustomObject]@{One=1; Two=2} | [PSCustomObject]@{One=1; Two=2} | @{One=1; Two=2} | 640 | |-----------|---------------------------------|---------------------------------|-----------------------| 641 | ' 642 | $Object = $TypeObject 643 | } 644 | 645 | It 'Raw table as argument' { 646 | $Actual = ConvertFrom-SourceTable $Table -Parse 647 | ,$Actual | Differentiate $Object | Should -Be 0 648 | } 649 | 650 | It 'Raw table from pipeline' { 651 | $Actual = $Table | ConvertFrom-SourceTable -Parse 652 | ,$Actual | Differentiate $Object | Should -Be 0 653 | } 654 | 655 | It 'Streamed table lines from pipeline' { 656 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable -Parse 657 | ,$Actual | Differentiate $Object | Should -Be 0 658 | } 659 | } 660 | 661 | Context 'directory list' { 662 | 663 | BeforeAll { 664 | $Table = ' 665 | Mode LastWriteTime Length Name 666 | ---- ------------- ------ ---- 667 | d----l 11/16/2018 8:30 PM Archive 668 | -a---l 5/22/2018 12:05 PM (726) Build-Expression.ps1 669 | -a---l 11/16/2018 7:38 PM 2143 CHANGELOG 670 | -a---l 11/17/2018 10:42 AM 14728 ConvertFrom-SourceTable.ps1 671 | -a---l 11/17/2018 11:04 AM 23909 ConvertFrom-SourceTable.Tests.ps1 672 | -a---l 8/4/2018 11:04 AM (6237) Import-SourceTable.ps1 673 | ' 674 | $Object = $DirObject 675 | } 676 | 677 | It 'Raw table as argument' { 678 | $Actual = ConvertFrom-SourceTable $Table 679 | ,$Actual | Differentiate $Object | Should -Be 0 680 | } 681 | 682 | It 'Raw table from pipeline' { 683 | $Actual = $Table | ConvertFrom-SourceTable 684 | ,$Actual | Differentiate $Object | Should -Be 0 685 | } 686 | 687 | It 'Streamed table lines from pipeline' { 688 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable 689 | ,$Actual | Differentiate $Object | Should -Be 0 690 | } 691 | } 692 | 693 | 694 | Context 'Floating table with horizontal ruler' { 695 | 696 | BeforeAll { 697 | $Table = ' 698 | 699 | Information 700 | on the table 701 | 702 | Name Value 703 | ---- ----- 704 | Black 0 705 | White 255 706 | 707 | ' 708 | } 709 | 710 | It 'Raw table, floating: AUTO' { 711 | $Actual = ConvertFrom-SourceTable $Table 712 | ,$Actual | Differentiate @( 713 | [PSCustomObject]@{'Name' = 'Black'; 'Value' = 0} 714 | [PSCustomObject]@{'Name' = 'White'; 'Value' = 255} 715 | ) | Should -Be 0 716 | } 717 | 718 | It 'Raw table, floating: ON' { 719 | $Actual = ConvertFrom-SourceTable $Table -HorizontalDash '-' 720 | ,$Actual | Differentiate @( 721 | [PSCustomObject]@{'Name' = 'Black'; 'Value' = 0} 722 | [PSCustomObject]@{'Name' = 'White'; 'Value' = 255} 723 | ) | Should -Be 0 724 | } 725 | 726 | It 'Raw table, floating: OFF' { 727 | $Actual = ConvertFrom-SourceTable $Table -HorizontalDash $Null -VerticalDash $Null 728 | ,$Actual | Differentiate @( 729 | [PSCustomObject]@{'Information' = 'on the table'} 730 | [PSCustomObject]@{'Information' = 'Name Value'} 731 | [PSCustomObject]@{'Information' = '---- -----'} 732 | [PSCustomObject]@{'Information' = 'Black 0'} 733 | [PSCustomObject]@{'Information' = 'White 255'} 734 | ) | Should -Be 0 735 | } 736 | 737 | It 'Streamed table lines, floating: AUTO' { 738 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable 739 | ,$Actual | Differentiate @( 740 | [PSCustomObject]@{'Information' = 'on the table'} 741 | [PSCustomObject]@{'Information' = 'Name Value'} 742 | [PSCustomObject]@{'Information' = '---- -----'} 743 | [PSCustomObject]@{'Information' = 'Black 0'} 744 | [PSCustomObject]@{'Information' = 'White 255'} 745 | ) | Should -Be 0 746 | } 747 | 748 | It 'Streamed table lines, floating: ON' { 749 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable -HorizontalDash '-' 750 | ,$Actual | Differentiate @( 751 | [PSCustomObject]@{'Name' = 'Black'; 'Value' = 0} 752 | [PSCustomObject]@{'Name' = 'White'; 'Value' = 255} 753 | ) | Should -Be 0 754 | } 755 | 756 | It 'Streamed table lines, floating: OFF' { 757 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable -HorizontalDash $Null -VerticalDash $Null 758 | ,$Actual | Differentiate @( 759 | [PSCustomObject]@{'Information' = 'on the table'} 760 | [PSCustomObject]@{'Information' = 'Name Value'} 761 | [PSCustomObject]@{'Information' = '---- -----'} 762 | [PSCustomObject]@{'Information' = 'Black 0'} 763 | [PSCustomObject]@{'Information' = 'White 255'} 764 | ) | Should -Be 0 765 | } 766 | } 767 | 768 | Context 'Floating table with vertical ruler' { 769 | 770 | BeforeAll { 771 | $Table = ' 772 | 773 | Information 774 | on the table 775 | 776 | Name | Value 777 | Black | 0 778 | White | 255 779 | 780 | ' 781 | } 782 | 783 | It 'Raw table, floating: AUTO' { 784 | $Actual = ConvertFrom-SourceTable $Table 785 | ,$Actual | Differentiate @( 786 | [PSCustomObject]@{'Name' = 'Black'; 'Value' = 0} 787 | [PSCustomObject]@{'Name' = 'White'; 'Value' = 255} 788 | ) | Should -Be 0 789 | } 790 | 791 | It 'Raw table, floating: ON' { 792 | $Actual = ConvertFrom-SourceTable $Table -VerticalDash '|' 793 | ,$Actual | Differentiate @( 794 | [PSCustomObject]@{'Name' = 'Black'; 'Value' = 0} 795 | [PSCustomObject]@{'Name' = 'White'; 'Value' = 255} 796 | ) | Should -Be 0 797 | } 798 | 799 | It 'Raw table, floating: OFF' { 800 | $Actual = ConvertFrom-SourceTable $Table -HorizontalDash $Null -VerticalDash $Null 801 | ,$Actual | Differentiate @( 802 | [PSCustomObject]@{'Information' = 'on the table'} 803 | [PSCustomObject]@{'Information' = 'Name | Value'} 804 | [PSCustomObject]@{'Information' = 'Black | 0'} 805 | [PSCustomObject]@{'Information' = 'White | 255'} 806 | ) | Should -Be 0 807 | } 808 | 809 | It 'Streamed table lines, floating: AUTO' { 810 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable 811 | ,$Actual | Differentiate @( 812 | [PSCustomObject]@{'Information' = 'on the table'} 813 | [PSCustomObject]@{'Information' = 'Name | Value'} 814 | [PSCustomObject]@{'Information' = 'Black | 0'} 815 | [PSCustomObject]@{'Information' = 'White | 255'} 816 | ) | Should -Be 0 817 | } 818 | 819 | It 'Streamed table lines, floating: ON' { 820 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable -VerticalDash '|' 821 | ,$Actual | Differentiate @( 822 | [PSCustomObject]@{'Name' = 'Black'; 'Value' = 0} 823 | [PSCustomObject]@{'Name' = 'White'; 'Value' = 255} 824 | ) | Should -Be 0 825 | } 826 | 827 | It 'Streamed table lines, floating: OFF' { 828 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable -HorizontalDash $Null -VerticalDash $Null 829 | ,$Actual | Differentiate @( 830 | [PSCustomObject]@{'Information' = 'on the table'} 831 | [PSCustomObject]@{'Information' = 'Name | Value'} 832 | [PSCustomObject]@{'Information' = 'Black | 0'} 833 | [PSCustomObject]@{'Information' = 'White | 255'} 834 | ) | Should -Be 0 835 | } 836 | } 837 | 838 | Context 'Floating markdown table' { 839 | 840 | BeforeAll { 841 | $Table = ' 842 | 843 | Information 844 | on the table 845 | 846 | |-------|-------| 847 | | Name | Value | 848 | |-------|-------| 849 | | Black | 0 | 850 | | White | 255 | 851 | |-------|-------| 852 | 853 | ' 854 | } 855 | 856 | It 'Raw table, floating: AUTO' { 857 | $Actual = ConvertFrom-SourceTable $Table 858 | ,$Actual | Differentiate @( 859 | [PSCustomObject]@{'Name' = 'Black'; 'Value' = 0} 860 | [PSCustomObject]@{'Name' = 'White'; 'Value' = 255} 861 | ) | Should -Be 0 862 | } 863 | 864 | It 'Raw table, floating: forced ON with -HorizontalDash' { 865 | $Actual = ConvertFrom-SourceTable $Table -HorizontalDash '-' 866 | ,$Actual | Differentiate @( 867 | [PSCustomObject]@{'Name' = 'Black'; 'Value' = 0} 868 | [PSCustomObject]@{'Name' = 'White'; 'Value' = 255} 869 | ) | Should -Be 0 870 | } 871 | 872 | It 'Raw table, floating: forced ON with -VerticalDash' { 873 | $Actual = ConvertFrom-SourceTable $Table -VerticalDash '|' 874 | ,$Actual | Differentiate @( 875 | [PSCustomObject]@{'Name' = 'Black'; 'Value' = 0} 876 | [PSCustomObject]@{'Name' = 'White'; 'Value' = 255} 877 | ) | Should -Be 0 878 | } 879 | 880 | It 'Raw table, floating: OFF' { 881 | $Actual = ConvertFrom-SourceTable $Table -HorizontalDash $Null -VerticalDash $Null 882 | ,$Actual | Differentiate @( 883 | [pscustomobject]@{'Information' = 'on the table'} 884 | [pscustomobject]@{'Information' = '|-------|-------|'} 885 | [pscustomobject]@{'Information' = '| Name | Value |'} 886 | [pscustomobject]@{'Information' = '|-------|-------|'} 887 | [pscustomobject]@{'Information' = '| Black | 0 |'} 888 | [pscustomobject]@{'Information' = '| White | 255 |'} 889 | [pscustomobject]@{'Information' = '|-------|-------|'} 890 | ) | Should -Be 0 891 | } 892 | 893 | It 'Streamed table lines, floating: AUTO' { 894 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable 895 | ,$Actual | Differentiate @( 896 | [pscustomobject]@{'Information' = 'on the table'} 897 | [pscustomobject]@{'Information' = '|-------|-------|'} 898 | [pscustomobject]@{'Information' = '| Name | Value |'} 899 | [pscustomobject]@{'Information' = '|-------|-------|'} 900 | [pscustomobject]@{'Information' = '| Black | 0 |'} 901 | [pscustomobject]@{'Information' = '| White | 255 |'} 902 | [pscustomobject]@{'Information' = '|-------|-------|'} 903 | ) | Should -Be 0 904 | } 905 | 906 | It 'RStreamed table lines, floating: forced ON with -HorizontalDash' { 907 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable -HorizontalDash '-' 908 | ,$Actual | Differentiate @( 909 | [pscustomobject]@{'Name' = 'Black'; 'Value' = 0} 910 | [pscustomobject]@{'Name' = 'White'; 'Value' = 255} 911 | ) | Should -Be 0 912 | } 913 | 914 | It 'RStreamed table lines, floating: forced ON with -VerticalDash' { 915 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable -VerticalDash '|' 916 | ,$Actual | Differentiate @( 917 | [pscustomobject]@{'Name' = 'Black'; 'Value' = 0} 918 | [pscustomobject]@{'Name' = 'White'; 'Value' = 255} 919 | ) | Should -Be 0 920 | } 921 | 922 | It 'Streamed table lines, floating: OFF' { 923 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable -HorizontalDash $Null -VerticalDash $Null 924 | ,$Actual | Differentiate @( 925 | [pscustomobject]@{'Information' = 'on the table'} 926 | [pscustomobject]@{'Information' = '|-------|-------|'} 927 | [pscustomobject]@{'Information' = '| Name | Value |'} 928 | [pscustomobject]@{'Information' = '|-------|-------|'} 929 | [pscustomobject]@{'Information' = '| Black | 0 |'} 930 | [pscustomobject]@{'Information' = '| White | 255 |'} 931 | [pscustomobject]@{'Information' = '|-------|-------|'} 932 | ) | Should -Be 0 933 | } 934 | } 935 | 936 | Context 'Alignment tests' { 937 | 938 | It 'Left - Center - Left' { 939 | 940 | $Actual = ConvertFrom-SourceTable ' 941 | C1 C2 C3 942 | Left Center Left 943 | ' 944 | 945 | $Actual.C1 | Should -Be 'Left' 946 | $Actual.C2 | Should -Be 'Center' 947 | $Actual.C3 | Should -Be 'Left' 948 | } 949 | 950 | It 'Left - Center - Left' { 951 | 952 | $Actual = ConvertFrom-SourceTable ' 953 | C1 C2 C3 954 | Left Center Right 955 | ' 956 | 957 | $Actual.C1 | Should -Be 'Left' 958 | $Actual.C2 | Should -Be 'Center' 959 | $Actual.C3 | Should -Be 'Right' 960 | } 961 | 962 | It 'Left - Center - Center' { 963 | 964 | $Actual = ConvertFrom-SourceTable ' 965 | C1 C2 C3 966 | Left Center Center 967 | ' 968 | 969 | $Actual.C1 | Should -Be 'Left' 970 | $Actual.C2 | Should -Be 'Center' 971 | $Actual.C3 | Should -Be 'Center' 972 | } 973 | 974 | It 'Right - Center - Left' { 975 | 976 | $Actual = ConvertFrom-SourceTable ' 977 | C1 C2 C3 978 | Right Center Left 979 | ' 980 | 981 | $Actual.C1 | Should -Be 'Right' 982 | $Actual.C2 | Should -Be 'Center' 983 | $Actual.C3 | Should -Be 'Left' 984 | } 985 | 986 | It 'Right - Center - Right' { 987 | 988 | $Actual = ConvertFrom-SourceTable ' 989 | C1 C2 C3 990 | Right Center Right 991 | ' 992 | 993 | $Actual.C1 | Should -Be 'Right' 994 | $Actual.C2 | Should -Be 'Center' 995 | $Actual.C3 | Should -Be 'Right' 996 | } 997 | 998 | It 'Right - Center - Center' { 999 | 1000 | $Actual = ConvertFrom-SourceTable ' 1001 | C1 C2 C3 1002 | Right Center Center 1003 | ' 1004 | 1005 | $Actual.C1 | Should -Be 'Right' 1006 | $Actual.C2 | Should -Be 'Center' 1007 | $Actual.C3 | Should -Be 'Center' 1008 | } 1009 | 1010 | It 'Center - Center - Left' { 1011 | 1012 | $Actual = ConvertFrom-SourceTable ' 1013 | C1 C2 C3 1014 | Center Center Left 1015 | ' 1016 | 1017 | $Actual.C1 | Should -Be 'Center' 1018 | $Actual.C2 | Should -Be 'Center' 1019 | $Actual.C3 | Should -Be 'Left' 1020 | } 1021 | 1022 | It 'Center - Center - Right' { 1023 | 1024 | $Actual = ConvertFrom-SourceTable ' 1025 | C1 C2 C3 1026 | Center Center Right 1027 | ' 1028 | 1029 | $Actual.C1 | Should -Be 'Center' 1030 | $Actual.C2 | Should -Be 'Center' 1031 | $Actual.C3 | Should -Be 'Right' 1032 | } 1033 | 1034 | It 'Center - Center - Center' { 1035 | 1036 | $Actual = ConvertFrom-SourceTable ' 1037 | C1 C2 C3 1038 | Center Center Center 1039 | ' 1040 | 1041 | $Actual.C1 | Should -Be 'Center' 1042 | $Actual.C2 | Should -Be 'Center' 1043 | $Actual.C3 | Should -Be 'Center' 1044 | } 1045 | } 1046 | 1047 | Context 'Alignment challenges' { 1048 | 1049 | It 'Left aligned by (otherwise) data indent' { 1050 | 1051 | $Actual = ConvertFrom-SourceTable ' 1052 | C1 C2 1053 | -- -- 1054 | 1 2 1 2 1055 | ' 1056 | 1057 | $Actual.C1 | Should -Be '1 2' 1058 | $Actual.C2 | Should -Be '1 2' 1059 | } 1060 | 1061 | It 'Left aligned by extended ruler' { 1062 | 1063 | $Actual = ConvertFrom-SourceTable ' 1064 | C1 C2 1065 | --- --- 1066 | 11 2 11 2 1067 | ' 1068 | 1069 | $Actual.C1 | Should -Be '11 2' 1070 | $Actual.C2 | Should -Be '11 2' 1071 | } 1072 | 1073 | It 'Left aligned by indented ruler' { 1074 | 1075 | $Actual = ConvertFrom-SourceTable ' 1076 | C1 C2 1077 | - - 1078 | 11 2 11 2 1079 | ' 1080 | 1081 | $Actual.C1 | Should -Be '11 2' 1082 | $Actual.C2 | Should -Be '11 2' 1083 | } 1084 | 1085 | It 'Left aligned by extended data' { 1086 | 1087 | $Actual = ConvertFrom-SourceTable ' 1088 | C1 C2 1089 | -- -- 1090 | 111 2 111 2 1091 | ' 1092 | 1093 | $Actual.C1 | Should -Be '111 2' 1094 | $Actual.C2 | Should -Be '111 2' 1095 | } 1096 | 1097 | It 'Left aligned (to be implemented)' { 1098 | 1099 | $Actual = ConvertFrom-SourceTable ' 1100 | C1 C2 1101 | -- -- 1102 | 11 2 11 2 1103 | ' 1104 | $Actual.C1 | Should -Be '11 2' 1105 | $Actual.C2 | Should -Be '11 2' 1106 | 1107 | } 1108 | 1109 | It 'Right aligned by (otherwise) data indent' { 1110 | 1111 | $Actual = ConvertFrom-SourceTable ' 1112 | C1 C2 1113 | -- -- 1114 | 1 2 1 2 1115 | ' 1116 | 1117 | $Actual.C1 | Should -Be '1 2' 1118 | $Actual.C2 | Should -Be '1 2' 1119 | } 1120 | 1121 | It 'Right aligned by extended ruler' { 1122 | 1123 | $Actual = ConvertFrom-SourceTable ' 1124 | C1 C2 1125 | --- --- 1126 | 1 22 1 22 1127 | ' 1128 | 1129 | $Actual.C1 | Should -Be '1 22' 1130 | $Actual.C2 | Should -Be '1 22' 1131 | } 1132 | 1133 | It 'Right aligned by indented ruler' { 1134 | 1135 | $Actual = ConvertFrom-SourceTable ' 1136 | C1 C2 1137 | - - 1138 | 1 22 1 22 1139 | ' 1140 | 1141 | $Actual.C1 | Should -Be '1 22' 1142 | $Actual.C2 | Should -Be '1 22' 1143 | } 1144 | 1145 | It 'Right aligned by extended data' { 1146 | 1147 | $Actual = ConvertFrom-SourceTable ' 1148 | C1 C2 1149 | -- -- 1150 | 1 222 1 222 1151 | ' 1152 | 1153 | $Actual.C1 | Should -Be '1 222' 1154 | $Actual.C2 | Should -Be '1 222' 1155 | } 1156 | 1157 | It 'Right aligned (to be implemented)' { 1158 | 1159 | $Actual = ConvertFrom-SourceTable ' 1160 | C1 C2 1161 | -- -- 1162 | 1 22 1 22 1163 | ' 1164 | 1165 | $Actual.C1 | Should -Be '1 22 1' 1166 | $Actual.C2 | Should -Be '22' 1167 | 1168 | } 1169 | 1170 | It 'Mixed alignment (determind by spaces)' { 1171 | 1172 | $Actual = ConvertFrom-SourceTable ' 1173 | C1 C2 1174 | -- -- 1175 | 1 2 1 2 1176 | ' 1177 | 1178 | $Actual.C1 | Should -Be '1 2' 1179 | $Actual.C2 | Should -Be '1 2' 1180 | } 1181 | 1182 | It 'Conflicting alignment' { 1183 | 1184 | $Actual = ConvertFrom-SourceTable ' 1185 | C1 C2 1186 | -- -- 1187 | 1 2 1 2 1188 | ' 1189 | 1190 | $Actual.C1 | Should -Be '1 2 1' 1191 | $Actual.C2 | Should -Be '2' 1192 | } 1193 | 1194 | It 'Indefinable alignment' { 1195 | 1196 | $Actual = ConvertFrom-SourceTable ' 1197 | C1 C2 1198 | -- -- 1199 | 11 2 1 22 1200 | ' 1201 | 1202 | $Actual.C1 | Should -Be '11 2 1' 1203 | $Actual.C2 | Should -Be '22' 1204 | } 1205 | 1206 | It 'Fixing indefinable alignment with ruler parameter' { 1207 | 1208 | $Actual = ConvertFrom-SourceTable -Ruler ' ---- ----' ' 1209 | C1 C2 1210 | -- -- 1211 | 11 2 1 22 1212 | ' 1213 | 1214 | $Actual.C1 | Should -Be '11 2' 1215 | $Actual.C2 | Should -Be '1 22' 1216 | } 1217 | 1218 | It 'Fixing headerless indefinable alignment with header and ruler parameter' { 1219 | 1220 | $Actual = ConvertFrom-SourceTable -Header ' C1 C2' -Ruler ' ---- ----' ' 1221 | 11 2 1 22 1222 | ' 1223 | 1224 | $Actual.C1 | Should -Be '11 2' 1225 | $Actual.C2 | Should -Be '1 22' 1226 | } 1227 | } 1228 | 1229 | Context 'Header and ruler challenges' { 1230 | 1231 | It 'Source table without ruler' { 1232 | 1233 | $Actual = ConvertFrom-SourceTable ' 1234 | Name Value 1235 | A 1 1236 | B 2 1237 | ' 1238 | $Actual[0].Name | Should -Be "A"; $Actual[0].Value | Should -Be 1 1239 | $Actual[1].Name | Should -Be "B"; $Actual[1].Value | Should -Be 2 1240 | } 1241 | 1242 | It 'Source table with space in header name' { 1243 | 1244 | $Actual = ConvertFrom-SourceTable ' 1245 | Name Value 1246 | ---------- 1247 | A 1 1248 | B 2 1249 | ' 1250 | $Actual[0].'Name Value' | Should -Be 'A 1' 1251 | $Actual[1].'Name Value' | Should -Be 'B 2' 1252 | } 1253 | 1254 | It 'Source table with assigned ruler' { 1255 | 1256 | $Actual = ConvertFrom-SourceTable -Ruler ' ----------' ' 1257 | Name Value 1258 | A 1 1259 | B 2 1260 | ' 1261 | $Actual[0].'Name Value' | Should -Be 'A 1' 1262 | $Actual[1].'Name Value' | Should -Be 'B 2' 1263 | } 1264 | 1265 | It 'Source table with assigned header' { 1266 | 1267 | $Actual = ConvertFrom-SourceTable -Header ' Name Value' ' 1268 | A 1 1269 | B 2 1270 | ' 1271 | $Actual[0].Name | Should -Be "A"; $Actual[0].Value | Should -Be 1 1272 | $Actual[1].Name | Should -Be "B"; $Actual[1].Value | Should -Be 2 1273 | } 1274 | 1275 | It 'Markdown table without ruler' { 1276 | 1277 | $Actual = ConvertFrom-SourceTable ' 1278 | |------------| 1279 | | Name Value | 1280 | |------------| 1281 | | A 1 | 1282 | | B 2 | 1283 | |------------| 1284 | ' 1285 | $Actual[0].'Name Value' | Should -Be 'A 1' 1286 | $Actual[1].'Name Value' | Should -Be 'B 2' 1287 | } 1288 | } 1289 | 1290 | Context 'Single column source table' { 1291 | 1292 | BeforeAll { 1293 | $RGB = @( 1294 | [pscustomobject]@{'Color' = 'Red'}, 1295 | [pscustomobject]@{'Color' = 'Green'}, 1296 | [pscustomobject]@{'Color' = 'Yellow'} 1297 | ) 1298 | } 1299 | 1300 | It 'Left aligned' { 1301 | 1302 | $Actual = ConvertFrom-SourceTable ' 1303 | Color 1304 | ----- 1305 | Red 1306 | Green 1307 | Yellow 1308 | ' 1309 | ,$Actual | Differentiate $RGB | Should -Be 0 1310 | } 1311 | 1312 | It 'Right aligned' { 1313 | 1314 | $Actual = ConvertFrom-SourceTable -Parse ' 1315 | Color 1316 | ----- 1317 | "Red" 1318 | "Green" 1319 | "Yellow" 1320 | ' 1321 | ,$Actual | Differentiate $RGB | Should -Be 0 1322 | } 1323 | 1324 | It 'Blank lines at the top and bottom' { 1325 | 1326 | $Actual = ConvertFrom-SourceTable ' 1327 | 1328 | 1329 | Color 1330 | ----- 1331 | Red 1332 | Green 1333 | Yellow 1334 | 1335 | 1336 | ' 1337 | ,$Actual | Differentiate $RGB | Should -Be 0 1338 | } 1339 | } 1340 | 1341 | Context 'Normal single column mark down table' { 1342 | 1343 | BeforeAll { 1344 | $RGB = @( 1345 | [pscustomobject]@{'Color' = 'Red'}, 1346 | [pscustomobject]@{'Color' = 'Green'}, 1347 | [pscustomobject]@{'Color' = 'Yellow'} 1348 | ) 1349 | } 1350 | 1351 | It 'Left aligned' { 1352 | 1353 | $Actual = ConvertFrom-SourceTable ' 1354 | |--------| 1355 | | Color | 1356 | |--------| 1357 | | Red | 1358 | | Green | 1359 | | Yellow | 1360 | |--------| 1361 | ' 1362 | ,$Actual | Differentiate $RGB | Should -Be 0 1363 | } 1364 | 1365 | It 'Right aligned' { 1366 | 1367 | $Actual = ConvertFrom-SourceTable -Parse ' 1368 | |----------| 1369 | | Color | 1370 | |----------| 1371 | | "Red" | 1372 | | "Green" | 1373 | | "Yellow" | 1374 | |----------| 1375 | ' 1376 | ,$Actual | Differentiate $RGB | Should -Be 0 1377 | } 1378 | } 1379 | 1380 | Context 'Narrow single column mark down table' { 1381 | 1382 | BeforeAll { 1383 | $RGB = @( 1384 | [pscustomobject]@{'Color' = 'Red'}, 1385 | [pscustomobject]@{'Color' = 'Green'}, 1386 | [pscustomobject]@{'Color' = 'Yellow'} 1387 | ) 1388 | } 1389 | 1390 | It 'Left aligned' { 1391 | 1392 | $Actual = ConvertFrom-SourceTable ' 1393 | |------| 1394 | |Color | 1395 | |------| 1396 | |Red | 1397 | |Green | 1398 | |Yellow| 1399 | |------| 1400 | ' 1401 | ,$Actual | Differentiate $RGB | Should -Be 0 1402 | } 1403 | 1404 | It 'Right aligned' { 1405 | 1406 | $Actual = ConvertFrom-SourceTable -Parse ' 1407 | |--------| 1408 | | Color| 1409 | |--------| 1410 | | "Red"| 1411 | | "Green"| 1412 | |"Yellow"| 1413 | |--------| 1414 | ' 1415 | ,$Actual | Differentiate $RGB | Should -Be 0 1416 | } 1417 | } 1418 | 1419 | BeforeAll { 1420 | $EmbeddedSeperators = @( 1421 | [pscustomobject]@{'Department' = 'Sales'; 'Name' = 'Aerts'; 'Country' = 'Belgium'} 1422 | [pscustomobject]@{'Department' = '-'; 'Name' = '|'; 'Country' = 'Germany'} 1423 | [pscustomobject]@{'Department' = 'Sales'; 'Name' = '-'; 'Country' = '|'} 1424 | [pscustomobject]@{'Department' = '||'; 'Name' = 'Duval'; 'Country' = '-'} 1425 | [pscustomobject]@{'Department' = '-'; 'Name' = '-'; 'Country' = '-'} 1426 | [pscustomobject]@{'Department' = '|'; 'Name' = '|'; 'Country' = '|'} 1427 | ) 1428 | } 1429 | 1430 | Context 'Simple table with embedded seperators' { 1431 | 1432 | BeforeAll { 1433 | $Table = ' 1434 | Department Name Country 1435 | ---------- ---- ------- 1436 | Sales Aerts Belgium 1437 | - | Germany 1438 | Sales - | 1439 | || Duval - 1440 | - - - 1441 | | | | 1442 | ' 1443 | $Object = $EmbeddedSeperators 1444 | } 1445 | 1446 | It 'Raw table as argument' { 1447 | $Actual = ConvertFrom-SourceTable $Table 1448 | ,$Actual | Differentiate $Object | Should -Be 0 1449 | } 1450 | 1451 | It 'Raw table from pipeline' { 1452 | $Actual = $Table | ConvertFrom-SourceTable 1453 | ,$Actual | Differentiate $Object | Should -Be 0 1454 | } 1455 | 1456 | It 'Streamed table lines from pipeline' { 1457 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable 1458 | ,$Actual | Differentiate $Object | Should -Be 0 1459 | } 1460 | } 1461 | 1462 | Context 'Markdown table with embedded seperators' { 1463 | 1464 | BeforeAll { 1465 | $Table = ' 1466 | |------------|-------|---------| 1467 | | Department | Name | Country | 1468 | |------------|-------|---------| 1469 | | Sales | Aerts | Belgium | 1470 | | - | | | Germany | 1471 | | Sales | - | | | 1472 | | || | Duval | - | 1473 | | - | - | - | 1474 | | | | | | | | 1475 | |------------|-------|---------| 1476 | ' 1477 | $Object = $EmbeddedSeperators 1478 | } 1479 | 1480 | It 'Raw table as argument' { 1481 | $Actual = ConvertFrom-SourceTable $Table 1482 | ,$Actual | Differentiate $Object | Should -Be 0 1483 | } 1484 | 1485 | It 'Raw table from pipeline' { 1486 | $Actual = $Table | ConvertFrom-SourceTable 1487 | ,$Actual | Differentiate $Object | Should -Be 0 1488 | } 1489 | 1490 | It 'Streamed table lines from pipeline' { 1491 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable 1492 | ,$Actual | Differentiate $Object | Should -Be 0 1493 | } 1494 | } 1495 | 1496 | Context 'SAP Table' { 1497 | 1498 | BeforeAll { 1499 | $Table = ' 1500 | --------------------------------------- 1501 | |Geldig v. |Geldig tot| Inv.waarde| 1502 | --------------------------------------- 1503 | |01.02.2015|31.12.2015|16.303,0000000 | 1504 | |01.02.2014|31.01.2015|76.345,0000000 | 1505 | |01.02.2013|31.01.2014|91.982,0000000 | 1506 | |01.02.2012|31.01.2013|82.809,0000000 | 1507 | |01.02.2011|31.01.2012|72.603,0000000 | 1508 | |01.02.2010|31.01.2011|97.865,0000000 | 1509 | |01.01.2009|31.01.2010|75.061,0000000 | 1510 | --------------------------------------- 1511 | ' 1512 | $Object = @( 1513 | [pscustomobject]@{'Geldig v.' = '01.02.2015'; 'Geldig tot' = '31.12.2015'; 'Inv.waarde' = '16.303,0000000'} 1514 | [pscustomobject]@{'Geldig v.' = '01.02.2014'; 'Geldig tot' = '31.01.2015'; 'Inv.waarde' = '76.345,0000000'} 1515 | [pscustomobject]@{'Geldig v.' = '01.02.2013'; 'Geldig tot' = '31.01.2014'; 'Inv.waarde' = '91.982,0000000'} 1516 | [pscustomobject]@{'Geldig v.' = '01.02.2012'; 'Geldig tot' = '31.01.2013'; 'Inv.waarde' = '82.809,0000000'} 1517 | [pscustomobject]@{'Geldig v.' = '01.02.2011'; 'Geldig tot' = '31.01.2012'; 'Inv.waarde' = '72.603,0000000'} 1518 | [pscustomobject]@{'Geldig v.' = '01.02.2010'; 'Geldig tot' = '31.01.2011'; 'Inv.waarde' = '97.865,0000000'} 1519 | [pscustomobject]@{'Geldig v.' = '01.01.2009'; 'Geldig tot' = '31.01.2010'; 'Inv.waarde' = '75.061,0000000'} 1520 | ) 1521 | } 1522 | 1523 | It 'Raw table as argument' { 1524 | $Actual = ConvertFrom-SourceTable $Table 1525 | ,$Actual | Differentiate $Object | Should -Be 0 1526 | } 1527 | 1528 | It 'Raw table from pipeline' { 1529 | $Actual = $Table | ConvertFrom-SourceTable 1530 | ,$Actual | Differentiate $Object | Should -Be 0 1531 | } 1532 | 1533 | It 'Streamed table lines from pipeline' { 1534 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable 1535 | ,$Actual | Differentiate $Object | Should -Be 0 1536 | } 1537 | } 1538 | 1539 | Context 'Stackoverflow questions' { 1540 | 1541 | It 'Split string on arbitrary-length substrings (Powershell)' { # https://stackoverflow.com/a/55752375/1701026 1542 | 1543 | $Table = @' 1544 | @SUB-SECTOR: sec_C SECTOR: reft 1545 | # 1546 | # Trade routes within the subsector 1547 | # 1548 | #--------1---------2---------3---------4---------5---------6--- 1549 | #PlanetName Loc. UPP Code B Notes Z PBG Al LRX X 1550 | #---------- ---- --------- - --------------- - --- -- --- - 1551 | Lemente 1907 B897563-B Ag Ni 824 Na 1552 | Zamoran 2108 B674675-A Q Ag Ni 904 Dr 1553 | '@ -Split '[\r\n]+' | ForEach-Object {$_ -Replace '^#', ' '} 1554 | 1555 | $Actual = $Table | ConvertFrom-SourceTable -HDash '-' 1556 | 1557 | ,$Actual | Differentiate @( 1558 | [pscustomobject]@{'PlanetName' = 'Lemente'; 'Loc.' = '1907'; 'UPP Code' = 'B897563-B'; 'B' = ''; 'Notes' = 'Ag Ni'; 'Z' = ''; 'PBG' = '824'; 'Al' = 'Na'; 'LRX' = ''; 'X' = ''}, 1559 | [pscustomobject]@{'PlanetName' = 'Zamoran'; 'Loc.' = '2108'; 'UPP Code' = 'B674675-A'; 'B' = 'Q'; 'Notes' = 'Ag Ni'; 'Z' = ''; 'PBG' = '904'; 'Al' = 'Dr'; 'LRX' = ''; 'X' = ''} 1560 | ) | Should -Be 0 1561 | } 1562 | 1563 | It 'Getting an average of each column from CSV' { # https://stackoverflow.com/q/57087760/1701026 1564 | 1565 | $Table = ' 1566 | timestamp streams TRP A B C D 1567 | 6/4/2019 6775 305 56 229 132 764 1568 | 6/4/2019 6910 316 28 356 118 134 1569 | 6/4/2019 6749 316 54 218 206 144 1570 | 6/5/2019 5186 267 84 280 452 258 1571 | 6/5/2019 5187 240 33 436 455 245 1572 | 6/5/2019 5224 291 21 245 192 654 1573 | 6/6/2019 5254 343 42 636 403 789 1574 | 6/6/2019 5180 252 23 169 328 888 1575 | 6/6/2019 5181 290 32 788 129 745 1576 | 6/6/2019 5244 328 44 540 403 989 1577 | ' 1578 | 1579 | $Object = @( 1580 | [pscustomobject]@{'timestamp' = '6/4/2019'; 'streams' = '6775'; 'TRP' = '305'; 'A' = '56'; 'B' = '229'; 'C' = '132'; 'D' = '764'}, 1581 | [pscustomobject]@{'timestamp' = '6/4/2019'; 'streams' = '6910'; 'TRP' = '316'; 'A' = '28'; 'B' = '356'; 'C' = '118'; 'D' = '134'}, 1582 | [pscustomobject]@{'timestamp' = '6/4/2019'; 'streams' = '6749'; 'TRP' = '316'; 'A' = '54'; 'B' = '218'; 'C' = '206'; 'D' = '144'}, 1583 | [pscustomobject]@{'timestamp' = '6/5/2019'; 'streams' = '5186'; 'TRP' = '267'; 'A' = '84'; 'B' = '280'; 'C' = '452'; 'D' = '258'}, 1584 | [pscustomobject]@{'timestamp' = '6/5/2019'; 'streams' = '5187'; 'TRP' = '240'; 'A' = '33'; 'B' = '436'; 'C' = '455'; 'D' = '245'}, 1585 | [pscustomobject]@{'timestamp' = '6/5/2019'; 'streams' = '5224'; 'TRP' = '291'; 'A' = '21'; 'B' = '245'; 'C' = '192'; 'D' = '654'}, 1586 | [pscustomobject]@{'timestamp' = '6/6/2019'; 'streams' = '5254'; 'TRP' = '343'; 'A' = '42'; 'B' = '636'; 'C' = '403'; 'D' = '789'}, 1587 | [pscustomobject]@{'timestamp' = '6/6/2019'; 'streams' = '5180'; 'TRP' = '252'; 'A' = '23'; 'B' = '169'; 'C' = '328'; 'D' = '888'}, 1588 | [pscustomobject]@{'timestamp' = '6/6/2019'; 'streams' = '5181'; 'TRP' = '290'; 'A' = '32'; 'B' = '788'; 'C' = '129'; 'D' = '745'}, 1589 | [pscustomobject]@{'timestamp' = '6/6/2019'; 'streams' = '5244'; 'TRP' = '328'; 'A' = '44'; 'B' = '540'; 'C' = '403'; 'D' = '989'} 1590 | ) 1591 | 1592 | $Actual = ConvertFrom-SourceTable $Table 1593 | ,$Actual | Differentiate $Object | Should -Be 0 1594 | 1595 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable 1596 | ,$Actual | Differentiate $Object | Should -Be 0 1597 | 1598 | $Table = ' 1599 | | date | abc | A | B | C | D | E | F | G | 1600 | | 6/4/2019 | 6775 | 3059 | 4 | 2292 | 1328 | 764 | 0 | 0 | 1601 | | 6/4/2019 | 6910 | 3167 | 28 | 3568 | 1180 | 1348 | 0 | 0 | 1602 | | 6/4/2019 | 6749 | 3161 | 0 | 2180 | 2060 | 1440 | 0 | 28 | 1603 | | 6/5/2019 | 6738 | 3118 | 4 | 2736 | 1396 | 984 | 0 | 0 | 1604 | | 6/5/2019 | 6718 | 3130 | 12 | 3076 | 1008 | 452 | 0 | 4 | 1605 | | 6/5/2019 | 6894 | 3046 | 4 | 2284 | 1556 | 624 | 0 | 0 | 1606 | | 1/1/2021 | 1111 | 2222 | 3 | 4444 | 5555 | 666 | 7 | 8 | 1607 | ' 1608 | 1609 | $Object = @( 1610 | [pscustomobject]@{'date' = '6/4/2019'; 'abc' = 6775; 'A' = 3059; 'B' = 4; 'C' = 2292; 'D' = 1328; 'E' = 764; 'F' = 0; 'G' = 0} 1611 | [pscustomobject]@{'date' = '6/4/2019'; 'abc' = 6910; 'A' = 3167; 'B' = 28; 'C' = 3568; 'D' = 1180; 'E' = 1348; 'F' = 0; 'G' = 0} 1612 | [pscustomobject]@{'date' = '6/4/2019'; 'abc' = 6749; 'A' = 3161; 'B' = 0; 'C' = 2180; 'D' = 2060; 'E' = 1440; 'F' = 0; 'G' = 28} 1613 | [pscustomobject]@{'date' = '6/5/2019'; 'abc' = 6738; 'A' = 3118; 'B' = 4; 'C' = 2736; 'D' = 1396; 'E' = 984; 'F' = 0; 'G' = 0} 1614 | [pscustomobject]@{'date' = '6/5/2019'; 'abc' = 6718; 'A' = 3130; 'B' = 12; 'C' = 3076; 'D' = 1008; 'E' = 452; 'F' = 0; 'G' = 4} 1615 | [pscustomobject]@{'date' = '6/5/2019'; 'abc' = 6894; 'A' = 3046; 'B' = 4; 'C' = 2284; 'D' = 1556; 'E' = 624; 'F' = 0; 'G' = 0} 1616 | [pscustomobject]@{'date' = '1/1/2021'; 'abc' = 1111; 'A' = 2222; 'B' = 3; 'C' = 4444; 'D' = 5555; 'E' = 666; 'F' = 7; 'G' = 8} 1617 | ) 1618 | 1619 | $Actual = ConvertFrom-SourceTable $Table 1620 | ,$Actual | Differentiate $Object | Should -Be 0 1621 | 1622 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable 1623 | ,$Actual | Differentiate $Object | Should -Be 0 1624 | 1625 | $Table = ' 1626 | timestamp|abc | A | B | C | D | E | F | G | 1627 | 6/4/2019 |6775 | 3059 | 4 | 2292 | 1328 | 764 | 0 | 0 | 1628 | 6/4/2019 |6910 | 3167 | 28 | 3568 | 1180 | 1348 | 0 | 0 | 1629 | 6/4/2019 |6749 | 3161 | 0 | 2180 | 2060 | 1440 | 0 | 28 | 1630 | 6/5/2019 |6738 | 3118 | 4 | 2736 | 1396 | 984 | 0 | 0 | 1631 | 6/5/2019 |6718 | 3130 | 12 | 3076 | 1008 | 452 | 0 | 4 | 1632 | 6/5/2019 |6894 | 3046 | 4 | 2284 | 1556 | 624 | 0 | 0 | 1633 | 1/1/2021 |1111 | 2222 | 3 | 4444 | 5555 | 666 | 7 | 8 | 1634 | ' 1635 | 1636 | $Object = @( 1637 | [pscustomobject]@{'timestamp' = '6/4/2019'; 'abc' = '6775'; 'A' = '3059'; 'B' = '4'; 'C' = '2292'; 'D' = '1328'; 'E' = '764'; 'F' = '0'; 'G' = '0'} 1638 | [pscustomobject]@{'timestamp' = '6/4/2019'; 'abc' = '6910'; 'A' = '3167'; 'B' = '28'; 'C' = '3568'; 'D' = '1180'; 'E' = '1348'; 'F' = '0'; 'G' = '0'} 1639 | [pscustomobject]@{'timestamp' = '6/4/2019'; 'abc' = '6749'; 'A' = '3161'; 'B' = '0'; 'C' = '2180'; 'D' = '2060'; 'E' = '1440'; 'F' = '0'; 'G' = '28'} 1640 | [pscustomobject]@{'timestamp' = '6/5/2019'; 'abc' = '6738'; 'A' = '3118'; 'B' = '4'; 'C' = '2736'; 'D' = '1396'; 'E' = '984'; 'F' = '0'; 'G' = '0'} 1641 | [pscustomobject]@{'timestamp' = '6/5/2019'; 'abc' = '6718'; 'A' = '3130'; 'B' = '12'; 'C' = '3076'; 'D' = '1008'; 'E' = '452'; 'F' = '0'; 'G' = '4'} 1642 | [pscustomobject]@{'timestamp' = '6/5/2019'; 'abc' = '6894'; 'A' = '3046'; 'B' = '4'; 'C' = '2284'; 'D' = '1556'; 'E' = '624'; 'F' = '0'; 'G' = '0'} 1643 | [pscustomobject]@{'timestamp' = '1/1/2021'; 'abc' = '1111'; 'A' = '2222'; 'B' = '3'; 'C' = '4444'; 'D' = '5555'; 'E' = '666'; 'F' = '7'; 'G' = '8'} 1644 | ) 1645 | 1646 | $Actual = ConvertFrom-SourceTable $Table 1647 | ,$Actual | Differentiate $Object | Should -Be 0 1648 | 1649 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable 1650 | ,$Actual | Differentiate $Object | Should -Be 0 1651 | 1652 | $Table = ' 1653 | timestamp streams TRP A B C D 1654 | 6/4/2019 6775 305 56 229 132 764 1655 | 6/4/2019 6910 316 28 356 118 134 1656 | 6/4/2019 6749 316 54 218 206 144 1657 | 6/5/2019 5186 267 84 280 452 258 1658 | 6/5/2019 5187 240 33 436 455 245 1659 | 6/5/2019 5224 291 21 245 192 654 1660 | 6/6/2019 5254 343 42 636 403 789 1661 | 6/6/2019 5180 252 23 169 328 888 1662 | 6/6/2019 5181 290 32 788 129 745 1663 | 6/6/2019 5244 328 44 540 403 989 1664 | ' 1665 | 1666 | $Object = @( 1667 | [pscustomobject]@{'timestamp' = '6/4/2019'; 'streams' = 6775; 'TRP' = '305'; 'A' = 56; 'B' = 229; 'C' = 132; 'D' = 764} 1668 | [pscustomobject]@{'timestamp' = '6/4/2019'; 'streams' = 6910; 'TRP' = '316'; 'A' = 28; 'B' = 356; 'C' = 118; 'D' = 134} 1669 | [pscustomobject]@{'timestamp' = '6/4/2019'; 'streams' = 6749; 'TRP' = '316'; 'A' = 54; 'B' = 218; 'C' = 206; 'D' = 144} 1670 | [pscustomobject]@{'timestamp' = '6/5/2019'; 'streams' = 5186; 'TRP' = '267'; 'A' = 84; 'B' = 280; 'C' = 452; 'D' = 258} 1671 | [pscustomobject]@{'timestamp' = '6/5/2019'; 'streams' = 5187; 'TRP' = '240'; 'A' = 33; 'B' = 436; 'C' = 455; 'D' = 245} 1672 | [pscustomobject]@{'timestamp' = '6/5/2019'; 'streams' = 5224; 'TRP' = '291'; 'A' = 21; 'B' = 245; 'C' = 192; 'D' = 654} 1673 | [pscustomobject]@{'timestamp' = '6/6/2019'; 'streams' = 5254; 'TRP' = '343'; 'A' = 42; 'B' = 636; 'C' = 403; 'D' = 789} 1674 | [pscustomobject]@{'timestamp' = '6/6/2019'; 'streams' = 5180; 'TRP' = '252'; 'A' = 23; 'B' = 169; 'C' = 328; 'D' = 888} 1675 | [pscustomobject]@{'timestamp' = '6/6/2019'; 'streams' = 5181; 'TRP' = '290'; 'A' = 32; 'B' = 788; 'C' = 129; 'D' = 745} 1676 | [pscustomobject]@{'timestamp' = '6/6/2019'; 'streams' = 5244; 'TRP' = '328'; 'A' = 44; 'B' = 540; 'C' = 403; 'D' = 989} 1677 | ) 1678 | 1679 | $Actual = ConvertFrom-SourceTable $Table 1680 | ,$Actual | Differentiate $Object | Should -Be 0 1681 | 1682 | $Actual = ($Table -Split '[\r\n]+') | ConvertFrom-SourceTable 1683 | ,$Actual | Differentiate $Object | Should -Be 0 1684 | 1685 | } 1686 | It 'Powershell extract data from text file and convert to csv' { # https://stackoverflow.com/q/59328405/1701026 1687 | 1688 | $Actual = ConvertFrom-SourceTable -Header Count,Address,Mode,Port ' 1689 | 1 010D.0C93.A02C Dynamic Gi1/0/5 1690 | 1 011B.782D.6719 Dynamic Gi1/0/22 1691 | 1 0003.4790.B479 Dynamic Gi1/0/1 1692 | 1 0054.B671.1EB8 Dynamic Gi1/0/2' 1693 | 1694 | ,$Actual | Differentiate @( 1695 | [pscustomobject]@{'Count' = '1'; 'Address' = '010D.0C93.A02C'; 'Mode' = 'Dynamic'; 'Port' = 'Gi1/0/5'}, 1696 | [pscustomobject]@{'Count' = '1'; 'Address' = '011B.782D.6719'; 'Mode' = 'Dynamic'; 'Port' = 'Gi1/0/22'}, 1697 | [pscustomobject]@{'Count' = '1'; 'Address' = '0003.4790.B479'; 'Mode' = 'Dynamic'; 'Port' = 'Gi1/0/1'}, 1698 | [pscustomobject]@{'Count' = '1'; 'Address' = '0054.B671.1EB8'; 'Mode' = 'Dynamic'; 'Port' = 'Gi1/0/2'} 1699 | ) | Should -Be 0 1700 | 1701 | } 1702 | 1703 | It 'Pull specific data from text files in PowerShell with search parameters' { # https://stackoverflow.com/q/79138278/1701026 1704 | 1705 | $Text = @' 1706 | ********************************************************************************************************* 1707 | 9/5/2021 7:42:16 AM Target: AP01 (VC) 1708 | ********************************************************************************************************* 1709 | WIFI AP BSS Table 1710 | ------------------ 1711 | bss ess port ip band/ht-mode/bandwidth ch/EIRP/max-EIRP type cur-cl ap name in-t(s) tot-t flags 1712 | --- --- ---- -- ---------------------- ---------------- ---- ------ ------- ------- ----- ----- 1713 | ab:cd:ef:gh:if:jk trustedwifi ?/? A.b.c.d 2.4GHz/HT/20MHz 1/24.0/25.5 ap 0 AP01 0 43d:14h:48m:14s K 1714 | ab:cd:ef:gh:if:jk secure ?/? A.b.c.d 2.4GHz/HT/20MHz 1/24.0/25.5 ap 0 AP01 0 43d:14h:47m:53s - 1715 | ab:cd:ef:gh:if:jk guest ?/? A.b.c.d 2.4GHz/HT/20MHz 1/24.0/25.5 ap 0 AP01 0 22h:11m:43s - 1716 | ab:cd:ef:gh:if:jk trustedwifi ?/? A.b.c.d 5GHz/VHT/40MHz 100+/18.0/30.0 ap 0 AP01 0 43d:14h:48m:15s K 1717 | ab:cd:ef:gh:if:jk secure ?/? A.b.c.d 5GHz/VHT/40MHz 100+/18.0/30.0 ap 0 AP01 0 43d:14h:47m:54s - 1718 | ab:cd:ef:gh:if:jk guest ?/? A.b.c.d 5GHz/VHT/40MHz 100+/18.0/30.0 ap 0 AP01 0 22h:11m:44s - 1719 | 1720 | Channel followed by "*" indicates channel selected due to unsupported configured channel. 1721 | "Spectrum" followed by "^" indicates Local Spectrum Override in effect. 1722 | 1723 | Num APs:6 1724 | Num Associations:0 1725 | 1726 | Flags: a = Airslice policy; A = Airslice app monitoring; c = MBO Cellular Data Capable BSS; d = Deferred Delete Pending; D = VLAN Discovered; E = Enhanced-open BSS without transition mode; I = Imminent VAP Down; K = 802.11K Enabled; m = Agile Multiband (MBO) BSS; M = WPA3-SAE mixed mode BSS; o = Enhanced-open transition mode open BSS; O = Enhanced-open BSS with transition mode; r = 802.11r Enabled; t = Broadcast TWT Enabled; T = Individual TWT Enabled; W = 802.11W Enabled; x = MBSSID Tx BSS; 3 = WPA3 BSS; 1727 | 1728 | 1729 | ********************************************************************************************************* 1730 | 9/5/2021 7:42:18 AM Target: AP02 1731 | ********************************************************************************************************* 1732 | WIFI AP BSS Table 1733 | ------------------ 1734 | bss ess port ip band/ht-mode/bandwidth ch/EIRP/max-EIRP type cur-cl ap name in-t(s) tot-t flags 1735 | --- --- ---- -- ---------------------- ---------------- ---- ------ ------- ------- ----- ----- 1736 | ab:cd:ef:gh:if:jk trustedwifi ?/? A.b.c.d 2.4GHz/HT/20MHz 11/24.0/25.5 ap 0 AP02 0 43d:14h:47m:24s K 1737 | ab:cd:ef:gh:if:jl secure ?/? A.b.c.d 2.4GHz/HT/20MHz 11/24.0/25.5 ap 0 AP02 0 43d:14h:47m:3s - 1738 | ab:cd:ef:gh:if:jm guest ?/? A.b.c.d 2.4GHz/HT/20MHz 11/24.0/25.5 ap 0 AP02 0 22h:11m:43s - 1739 | ab:cd:ef:gh:if:j0 rustedwifi ?/? A.b.c.d 5GHz/VHT/40MHz 108+/18.0/30.0 ap 0 AP02 0 43d:14h:47m:24s K 1740 | ab:cd:ef:gh:if:jp secure ?/? A.b.c.d 5GHz/VHT/40MHz 108+/18.0/30.0 ap 0 AP02 0 43d:14h:47m:4s - 1741 | ab:cd:ef:gh:if:jq guest ?/? A.b.c.d 5GHz/VHT/40MHz 108+/18.0/30.0 ap 0 AP02 0 22h:11m:43s - 1742 | '@ 1743 | 1744 | $Actual = $Text -Split '\r?\n' | Select-String 'Mhz' | ConvertFrom-SourceTable -Header 'bss', 'ess', '', '', '', '', '', '', 'ap name' 1745 | $Expected = @( 1746 | [pscustomobject]@{ bss = 'ab:cd:ef:gh:if:jk'; ess = 'trustedwifi'; 'ap name' = 'AP01' } 1747 | [pscustomobject]@{ bss = 'ab:cd:ef:gh:if:jk'; ess = 'secure'; 'ap name' = 'AP01' } 1748 | [pscustomobject]@{ bss = 'ab:cd:ef:gh:if:jk'; ess = 'guest'; 'ap name' = 'AP01' } 1749 | [pscustomobject]@{ bss = 'ab:cd:ef:gh:if:jk'; ess = 'trustedwifi'; 'ap name' = 'AP01' } 1750 | [pscustomobject]@{ bss = 'ab:cd:ef:gh:if:jk'; ess = 'secure'; 'ap name' = 'AP01' } 1751 | [pscustomobject]@{ bss = 'ab:cd:ef:gh:if:jk'; ess = 'guest'; 'ap name' = 'AP01' } 1752 | [pscustomobject]@{ bss = 'ab:cd:ef:gh:if:jk'; ess = 'trustedwifi'; 'ap name' = 'AP02' } 1753 | [pscustomobject]@{ bss = 'ab:cd:ef:gh:if:jl'; ess = 'secure'; 'ap name' = 'AP02' } 1754 | [pscustomobject]@{ bss = 'ab:cd:ef:gh:if:jm'; ess = 'guest'; 'ap name' = 'AP02' } 1755 | [pscustomobject]@{ bss = 'ab:cd:ef:gh:if:j0'; ess = 'rustedwifi'; 'ap name' = 'AP02' } 1756 | [pscustomobject]@{ bss = 'ab:cd:ef:gh:if:jp'; ess = 'secure'; 'ap name' = 'AP02' } 1757 | [pscustomobject]@{ bss = 'ab:cd:ef:gh:if:jq'; ess = 'guest'; 'ap name' = 'AP02' } 1758 | ) 1759 | 1760 | ,$Actual | Differentiate $Expected | Should -Be 0 1761 | } 1762 | 1763 | } 1764 | 1765 | Context 'Resolved bugs' { 1766 | 1767 | BeforeAll { 1768 | It 'Single width column at position 0' { 1769 | 1770 | $Actual = ConvertFrom-SourceTable ' 1771 | A B XY ZY 1772 | - - -- -- 1773 | 1 val1 foo1 bar1 1774 | 2 val2 foo2 bar2 1775 | 3 val3 foo3 bar3 1776 | 4 val4 foo4 bar4 1777 | 5 val5 foo5 bar5 1778 | 6 val6 foo6 bar6' 1779 | 1780 | ,$Actual | Differentiate @( 1781 | [pscustomobject]@{'A' = '1'; 'B' = 'val1'; 'XY' = 'foo1'; 'ZY' = 'bar1'}, 1782 | [pscustomobject]@{'A' = '2'; 'B' = 'val2'; 'XY' = 'foo2'; 'ZY' = 'bar2'}, 1783 | [pscustomobject]@{'A' = '3'; 'B' = 'val3'; 'XY' = 'foo3'; 'ZY' = 'bar3'}, 1784 | [pscustomobject]@{'A' = '4'; 'B' = 'val4'; 'XY' = 'foo4'; 'ZY' = 'bar4'}, 1785 | [pscustomobject]@{'A' = '5'; 'B' = 'val5'; 'XY' = 'foo5'; 'ZY' = 'bar5'}, 1786 | [pscustomobject]@{'A' = '6'; 'B' = 'val6'; 'XY' = 'foo6'; 'ZY' = 'bar6'} 1787 | ) | Should -Be 0 1788 | 1789 | } 1790 | 1791 | It 'Here table without ruler with spaces in header' { 1792 | 1793 | $Actual = ConvertFrom-SourceTable ' 1794 | USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME 1795 | a2270725-3 13 Disc 2+00:17 7/2/2019 1:50 PM 1796 | a2232655-3 14 Disc 4+09:54 7/1/2019 2:10 AM 1797 | a2129521-3 30 Disc 2+04:50 7/1/2019 4:52 AM 1798 | a16991754-3 49 Disc 22:51 7/1/2019 5:44 AM 1799 | p.vbr.1 58 Disc 4+20:19 6/25/2019 11:20 AM 1800 | a16990384-3 59 Disc 1:43 6/27/2019 10:20 AM 1801 | a2169135-3 68 Disc 3+00:50 7/2/2019 11:13 AM 1802 | a2289685-3 79 Disc 6:40 7/2/2019 9:04 PM 1803 | >a2310806-3 rdp-tcp#93 85 Active . 7/1/2019 9:05 AM 1804 | a16991667-3 98 Disc 3+00:31 6/26/2019 6:35 AM 1805 | a2064837-3 107 Disc 8:32 7/3/2019 12:47 AM 1806 | a2282463-3 108 Disc 2+01:51 7/3/2019 8:55 AM 1807 | a2292833-3 116 Disc 1+21:30 7/3/2019 2:06 PM 1808 | a18005447-3 126 Disc 8+20:09 6/26/2019 2:48 PM 1809 | a2185113-3 135 Disc 9:19 6/26/2019 9:14 PM 1810 | a2067993-3 139 Disc 1+03:58 7/4/2019 8:08 AM 1811 | a2101008-3 140 Disc 5:10 7/3/2019 10:00 PM 1812 | a2256517-3 141 Disc 1+03:32 7/4/2019 8:32 AM 1813 | a2340150-3 142 Disc 12:35 7/4/2019 9:53 PM 1814 | a2076309-3 143 Disc 3:37 7/5/2019 3:37 AM' 1815 | 1816 | ,$Actual | Differentiate @( 1817 | [pscustomobject]@{'ID' = '13'; 'IDLE TIME' = '2+00:17'; 'LOGON TIME' = '7/2/2019 1:50 PM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a2270725-3'}, 1818 | [pscustomobject]@{'ID' = '14'; 'IDLE TIME' = '4+09:54'; 'LOGON TIME' = '7/1/2019 2:10 AM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a2232655-3'}, 1819 | [pscustomobject]@{'ID' = '30'; 'IDLE TIME' = '2+04:50'; 'LOGON TIME' = '7/1/2019 4:52 AM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a2129521-3'}, 1820 | [pscustomobject]@{'ID' = '49'; 'IDLE TIME' = '22:51'; 'LOGON TIME' = '7/1/2019 5:44 AM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a16991754-3'}, 1821 | [pscustomobject]@{'ID' = '58'; 'IDLE TIME' = '4+20:19'; 'LOGON TIME' = '6/25/2019 11:20 AM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'p.vbr.1'}, 1822 | [pscustomobject]@{'ID' = '59'; 'IDLE TIME' = '1:43'; 'LOGON TIME' = '6/27/2019 10:20 AM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a16990384-3'}, 1823 | [pscustomobject]@{'ID' = '68'; 'IDLE TIME' = '3+00:50'; 'LOGON TIME' = '7/2/2019 11:13 AM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a2169135-3'}, 1824 | [pscustomobject]@{'ID' = '79'; 'IDLE TIME' = '6:40'; 'LOGON TIME' = '7/2/2019 9:04 PM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a2289685-3'}, 1825 | [pscustomobject]@{'ID' = '85'; 'IDLE TIME' = '.'; 'LOGON TIME' = '7/1/2019 9:05 AM'; 'SESSIONNAME' = 'rdp-tcp#93'; 'STATE' = 'Active'; 'USERNAME' = '>a2310806-3'}, 1826 | [pscustomobject]@{'ID' = '98'; 'IDLE TIME' = '3+00:31'; 'LOGON TIME' = '6/26/2019 6:35 AM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a16991667-3'}, 1827 | [pscustomobject]@{'ID' = '107'; 'IDLE TIME' = '8:32'; 'LOGON TIME' = '7/3/2019 12:47 AM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a2064837-3'}, 1828 | [pscustomobject]@{'ID' = '108'; 'IDLE TIME' = '2+01:51'; 'LOGON TIME' = '7/3/2019 8:55 AM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a2282463-3'}, 1829 | [pscustomobject]@{'ID' = '116'; 'IDLE TIME' = '1+21:30'; 'LOGON TIME' = '7/3/2019 2:06 PM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a2292833-3'}, 1830 | [pscustomobject]@{'ID' = '126'; 'IDLE TIME' = '8+20:09'; 'LOGON TIME' = '6/26/2019 2:48 PM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a18005447-3'}, 1831 | [pscustomobject]@{'ID' = '135'; 'IDLE TIME' = '9:19'; 'LOGON TIME' = '6/26/2019 9:14 PM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a2185113-3'}, 1832 | [pscustomobject]@{'ID' = '139'; 'IDLE TIME' = '1+03:58'; 'LOGON TIME' = '7/4/2019 8:08 AM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a2067993-3'}, 1833 | [pscustomobject]@{'ID' = '140'; 'IDLE TIME' = '5:10'; 'LOGON TIME' = '7/3/2019 10:00 PM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a2101008-3'}, 1834 | [pscustomobject]@{'ID' = '141'; 'IDLE TIME' = '1+03:32'; 'LOGON TIME' = '7/4/2019 8:32 AM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a2256517-3'}, 1835 | [pscustomobject]@{'ID' = '142'; 'IDLE TIME' = '12:35'; 'LOGON TIME' = '7/4/2019 9:53 PM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a2340150-3'}, 1836 | [pscustomobject]@{'ID' = '143'; 'IDLE TIME' = '3:37'; 'LOGON TIME' = '7/5/2019 3:37 AM'; 'SESSIONNAME' = ''; 'STATE' = 'Disc'; 'USERNAME' = 'a2076309-3'} 1837 | ) | Should -Be 0 1838 | } 1839 | } 1840 | 1841 | It 'Last line contains white spaces and data is floating' { 1842 | 1843 | $Actual = ConvertFrom-SourceTable ' 1844 | Department Id Name Country Age ReportsTo 1845 | ---------- -- ---- ------- --- --------- 1846 | Engineering {2, 4} {Bauer, Duval} {Germany, France} {31, 21} {4, 5} 1847 | Sales {3, 1} {Cook, Aerts} {England, Belgium} {69, 40} {1, 5} 1848 | Engineering {6, 4} {Fischer, Duval} {Germany, France} {29, 21} {4, 5} 1849 | ' 1850 | 1851 | ,$Actual | Differentiate @( 1852 | [pscustomobject]@{'Age' = '{31, 21}'; 'Country' = '{Germany, France}'; 'Department' = 'Engineering'; 'Id' = '{2, 4}'; 'Name' = '{Bauer, Duval}'; 'ReportsTo' = '{4, 5}'}, 1853 | [pscustomobject]@{'Age' = '{69, 40}'; 'Country' = '{England, Belgium}'; 'Department' = 'Sales'; 'Id' = '{3, 1}'; 'Name' = '{Cook, Aerts}'; 'ReportsTo' = '{1, 5}'}, 1854 | [pscustomobject]@{'Age' = '{29, 21}'; 'Country' = '{Germany, France}'; 'Department' = 'Engineering'; 'Id' = '{6, 4}'; 'Name' = '{Fischer, Duval}'; 'ReportsTo' = '{4, 5}'} 1855 | ) | Should -Be 0 1856 | 1857 | $Actual = ConvertFrom-SourceTable ' 1858 | Department Id Name Country Age ReportsTo 1859 | 1860 | ---------- -- ---- ------- --- --------- 1861 | 1862 | Engineering {2, 4} {Bauer, Duval} {Germany, France} {31, 21} {4, 5} 1863 | 1864 | Sales {3, 1} {Cook, Aerts} {England, Belgium} {69, 40} {1, 5} 1865 | 1866 | Engineering {6, 4} {Fischer, Duval} {Germany, France} {29, 21} {4, 5} 1867 | ' 1868 | 1869 | ,$Actual | Differentiate @( 1870 | [pscustomobject]@{'Age' = '{31, 21}'; 'Country' = '{Germany, France}'; 'Department' = 'Engineering'; 'Id' = '{2, 4}'; 'Name' = '{Bauer, Duval}'; 'ReportsTo' = '{4, 5}'}, 1871 | [pscustomobject]@{'Age' = '{69, 40}'; 'Country' = '{England, Belgium}'; 'Department' = 'Sales'; 'Id' = '{3, 1}'; 'Name' = '{Cook, Aerts}'; 'ReportsTo' = '{1, 5}'}, 1872 | [pscustomobject]@{'Age' = '{29, 21}'; 'Country' = '{Germany, France}'; 'Department' = 'Engineering'; 'Id' = '{6, 4}'; 'Name' = '{Fischer, Duval}'; 'ReportsTo' = '{4, 5}'} 1873 | ) | Should -Be 0 1874 | 1875 | $Actual = ConvertFrom-SourceTable ' 1876 | Department Id Name Country Age ReportsTo 1877 | 1878 | ---------- -- ---- ------- --- --------- 1879 | 1880 | Engineering {2, 4} {Bauer, Duval} {Germany, France} {31, 21} {4, 5} 1881 | 1882 | Sales {3, 1} {Cook, Aerts} {England, Belgium} {69, 40} {1, 5} 1883 | 1884 | Engineering {6, 4} {Fischer, Duval} {Germany, France} {29, 21} {4, 5} 1885 | ' 1886 | 1887 | ,$Actual | Differentiate @( 1888 | [pscustomobject]@{'Age' = '{31, 21}'; 'Country' = '{Germany, France}'; 'Department' = 'Engineering'; 'Id' = '{2, 4}'; 'Name' = '{Bauer, Duval}'; 'ReportsTo' = '{4, 5}'}, 1889 | [pscustomobject]@{'Age' = '{69, 40}'; 'Country' = '{England, Belgium}'; 'Department' = 'Sales'; 'Id' = '{3, 1}'; 'Name' = '{Cook, Aerts}'; 'ReportsTo' = '{1, 5}'}, 1890 | [pscustomobject]@{'Age' = '{29, 21}'; 'Country' = '{Germany, France}'; 'Department' = 'Engineering'; 'Id' = '{6, 4}'; 'Name' = '{Fischer, Duval}'; 'ReportsTo' = '{4, 5}'} 1891 | ) | Should -Be 0 1892 | } 1893 | 1894 | It 'Connect (rulerless) single spaced headers where either column is empty' { # https://stackoverflow.com/q/59541667/1701026 1895 | 1896 | $Actual = ConvertFrom-SourceTable ' 1897 | NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES 1898 | me-pod-name 2/2 Running 0 6s 10.0.0.10 node1 1899 | me-pod-name-2 1/1 Running 0 6d18h 10.0.1.20 node2 1900 | me-pod-name-3 1/1 Running 0 11d 10.0.0.30 node3 ' 1901 | 1902 | 1903 | } 1904 | 1905 | It 'convert list output to table(object)' { # https://stackoverflow.com/a/60882963/1701026 1906 | 1907 | BeforeAll { 1908 | $Table = ' 1909 | [ ID] Interval Transfer Bandwidth 1910 | [ 4] 0.00-1.00 sec 11.4 MBytes 95.4 Mbits/sec 1911 | [ 4] 1.00-2.00 sec 11.2 MBytes 94.2 Mbits/sec 1912 | [ 4] 2.00-3.00 sec 11.2 MBytes 94.3 Mbits/sec 1913 | [ 4] 3.00-4.00 sec 11.2 MBytes 94.5 Mbits/sec 1914 | [ 4] 4.00-5.00 sec 11.2 MBytes 94.2 Mbits/sec 1915 | [ 4] 5.00-6.00 sec 11.2 MBytes 94.4 Mbits/sec 1916 | [ 4] 6.00-7.00 sec 11.2 MBytes 94.4 Mbits/sec 1917 | [ 4] 7.00-8.00 sec 11.2 MBytes 94.3 Mbits/sec 1918 | [ 4] 8.00-9.00 sec 11.2 MBytes 94.2 Mbits/sec 1919 | [ 4] 9.00-10.00 sec 11.2 MBytes 94.5 Mbits/sec' 1920 | 1921 | $Actual = $Table | ConvertFrom-SourceTable -Omit '[]' 1922 | 1923 | ,$Actual | Differentiate @( 1924 | [pscustomobject]@{'ID' = '4'; 'Interval' = '0.00-1.00 sec'; 'Transfer' = '11.4 MBytes'; 'Bandwidth' = '95.4 Mbits/sec'}, 1925 | [pscustomobject]@{'ID' = '4'; 'Interval' = '1.00-2.00 sec'; 'Transfer' = '11.2 MBytes'; 'Bandwidth' = '94.2 Mbits/sec'}, 1926 | [pscustomobject]@{'ID' = '4'; 'Interval' = '2.00-3.00 sec'; 'Transfer' = '11.2 MBytes'; 'Bandwidth' = '94.3 Mbits/sec'}, 1927 | [pscustomobject]@{'ID' = '4'; 'Interval' = '3.00-4.00 sec'; 'Transfer' = '11.2 MBytes'; 'Bandwidth' = '94.5 Mbits/sec'}, 1928 | [pscustomobject]@{'ID' = '4'; 'Interval' = '4.00-5.00 sec'; 'Transfer' = '11.2 MBytes'; 'Bandwidth' = '94.2 Mbits/sec'}, 1929 | [pscustomobject]@{'ID' = '4'; 'Interval' = '5.00-6.00 sec'; 'Transfer' = '11.2 MBytes'; 'Bandwidth' = '94.4 Mbits/sec'}, 1930 | [pscustomobject]@{'ID' = '4'; 'Interval' = '6.00-7.00 sec'; 'Transfer' = '11.2 MBytes'; 'Bandwidth' = '94.4 Mbits/sec'}, 1931 | [pscustomobject]@{'ID' = '4'; 'Interval' = '7.00-8.00 sec'; 'Transfer' = '11.2 MBytes'; 'Bandwidth' = '94.3 Mbits/sec'}, 1932 | [pscustomobject]@{'ID' = '4'; 'Interval' = '8.00-9.00 sec'; 'Transfer' = '11.2 MBytes'; 'Bandwidth' = '94.2 Mbits/sec'}, 1933 | [pscustomobject]@{'ID' = '4'; 'Interval' = '9.00-10.00 sec'; 'Transfer' = '11.2 MBytes'; 'Bandwidth' = '94.5 Mbits/sec'} 1934 | ) | Should -Be 0 1935 | } 1936 | } 1937 | 1938 | It '#2 Losing certain columns' { 1939 | 1940 | $Actual = ConvertFrom-SourceTable ' 1941 | ID Done Have ETA Up Down Ratio Status Name 1942 | 1 100% 17.34 MB Unknown 0.0 0.0 0.0 Idle BitComet Stable (build 1.69.7.8) 1943 | 2 31% 14.54 GB 50 min 1533.0 9600.0 0.0 Up & Down [BeanSub&VCB-Studio] Vinland Saga [Ma10p_1080p] 1944 | 3 100% 75.47 MB Unknown 0.0 0.0 0.0 Idle [Airota][Kakushigoto][01-12][ASS][fonts][CHS].7z 1945 | 4 7% 353.4 MB 1 hrs 0.0 2285.0 0.0 Downloading [Sakurato.sub][Kakushigoto][01-12 END][CHT][1080P]' 1946 | 1947 | ,$Actual | Differentiate @( 1948 | [pscustomobject]@{'ID' = '1'; 'Done' = '100%'; 'Have' = '17.34 MB'; 'ETA' = 'Unknown'; 'Up' = '0.0'; 'Down' = '0.0'; 'Ratio' = '0.0'; 'Status' = 'Idle'; 'Name' = 'BitComet Stable (build 1.69.7.8)'}, 1949 | [pscustomobject]@{'ID' = '2'; 'Done' = '31%'; 'Have' = '14.54 GB'; 'ETA' = '50 min'; 'Up' = '1533.0'; 'Down' = '9600.0'; 'Ratio' = '0.0'; 'Status' = 'Up & Down'; 'Name' = '[BeanSub&VCB-Studio] Vinland Saga [Ma10p_1080p]'}, 1950 | [pscustomobject]@{'ID' = '3'; 'Done' = '100%'; 'Have' = '75.47 MB'; 'ETA' = 'Unknown'; 'Up' = '0.0'; 'Down' = '0.0'; 'Ratio' = '0.0'; 'Status' = 'Idle'; 'Name' = '[Airota][Kakushigoto][01-12][ASS][fonts][CHS].7z'}, 1951 | [pscustomobject]@{'ID' = '4'; 'Done' = '7%'; 'Have' = '353.4 MB'; 'ETA' = '1 hrs'; 'Up' = '0.0'; 'Down' = '2285.0'; 'Ratio' = '0.0'; 'Status' = 'Downloading'; 'Name' = '[Sakurato.sub][Kakushigoto][01-12 END][CHT][1080P]'} 1952 | ) | Should -Be 0 1953 | 1954 | $Actual = ConvertFrom-SourceTable ' 1955 | Done ID Have ETA Up Down Ratio Status Name 1956 | 100% 1 17.34 MB Unknown 0.0 0.0 0.0 Idle BitComet Stable (build 1.69.7.8) 1957 | 31% 2 14.54 GB 50 min 1533.0 9600.0 0.0 Up & Down [BeanSub&VCB-Studio] Vinland Saga [Ma10p_1080p] 1958 | 100% 3 75.47 MB Unknown 0.0 0.0 0.0 Idle [Airota][Kakushigoto][01-12][ASS][fonts][CHS].7z 1959 | 7% 4 353.4 MB 1 hrs 0.0 2285.0 0.0 Downloading [Sakurato.sub][Kakushigoto][01-12 END][CHT][1080P]' 1960 | 1961 | ,$Actual | Differentiate @( 1962 | [pscustomobject]@{'Done' = '100%'; 'ID' = '1'; 'Have' = '17.34 MB'; 'ETA' = 'Unknown'; 'Up' = '0.0'; 'Down' = '0.0'; 'Ratio' = '0.0'; 'Status' = 'Idle'; 'Name' = 'BitComet Stable (build 1.69.7.8)'}, 1963 | [pscustomobject]@{'Done' = '31%'; 'ID' = '2'; 'Have' = '14.54 GB'; 'ETA' = '50 min'; 'Up' = '1533.0'; 'Down' = '9600.0'; 'Ratio' = '0.0'; 'Status' = 'Up & Down'; 'Name' = '[BeanSub&VCB-Studio] Vinland Saga [Ma10p_1080p]'}, 1964 | [pscustomobject]@{'Done' = '100%'; 'ID' = '3'; 'Have' = '75.47 MB'; 'ETA' = 'Unknown'; 'Up' = '0.0'; 'Down' = '0.0'; 'Ratio' = '0.0'; 'Status' = 'Idle'; 'Name' = '[Airota][Kakushigoto][01-12][ASS][fonts][CHS].7z'}, 1965 | [pscustomobject]@{'Done' = '7%'; 'ID' = '4'; 'Have' = '353.4 MB'; 'ETA' = '1 hrs'; 'Up' = '0.0'; 'Down' = '2285.0'; 'Ratio' = '0.0'; 'Status' = 'Downloading'; 'Name' = '[Sakurato.sub][Kakushigoto][01-12 END][CHT][1080P]'} 1966 | ) | Should -Be 0 1967 | } 1968 | 1969 | } 1970 | } 1971 | 1972 | --------------------------------------------------------------------------------