Transpilers/Keywords/Until.psx.ps1
<# .SYNOPSIS until keyword .DESCRIPTION The until keyword simplifies event loops. until will always run at least once, and will run until a condition is true. .NOTES until will become a ```do {} while ()``` statement in PowerShell. .EXAMPLE { $x = 0 until ($x == 10) { $x $x++ } } |.>PipeScript .EXAMPLE Invoke-PipeScript { until "00:00:05" { [DateTime]::Now Start-Sleep -Milliseconds 500 } } .EXAMPLE Invoke-PipeScript { until "12:17 pm" { [DateTime]::Now Start-Sleep -Milliseconds 500 } } .EXAMPLE { $eventCounter = 0 until "MyEvent" { $eventCounter++ $eventCounter until "00:00:03" { "sleeping a few seconds" Start-Sleep -Milliseconds 500 } if (-not ($eventCounter % 5)) { $null = New-Event -SourceIdentifier MyEvent } } } | .>PipeScript .EXAMPLE Invoke-PipeScript { $tries = 3 until (-not $tries) { "$tries tries left" $tries-- } } #> [ValidateScript({ $commandAst = $_ return ($commandAst -and $CommandAst.CommandElements[0].Value -eq 'until' -or ( $commandAst.CommandElements.Count -ge 2 -and $commandAst.CommandElements[0].Value -like ':*' -and $commandAst.CommandElements[1].Value -eq 'until' ) ) })] param( [Parameter(Mandatory,ValueFromPipeline,ParameterSetName='CommandAst')] [Management.Automation.Language.CommandAst] $CommandAst ) begin { $myCmdName = $MyInvocation.MyCommand.Name } process { $CommandName, $CommandArgs = $commandAst.CommandElements if ($commandName -like ':*') { $null, $firstArg, $secondArg = $CommandAst.ArgumentList } else { $firstArg, $secondArg = $CommandAst.ArgumentList } # If the first arg is a command expression, it becomes do {} while ($firstArg) if (-not $firstArg -or $firstArg.GetType().Name -notin 'ParenExpressionAst', 'ScriptBlockExpressionAst', 'VariableExpressionAst','MemberExpressionAst', 'string', 'ExpandableStringExpressionAst') { Write-Error "Until must be followed by a Variable, Member, ExpandableString, or Parenthesis Expression" return } if ($secondArg -isnot [Scriptblock]) { Write-Error "Until must be followed by a condition and a ScriptBlock" return } $condition = $firstArg if ($firstArg.GetType().Name -eq 'ParenExpressionAst' -and $firstArg.Pipeline.PipelineElements.Count -eq 1 -and $firstArg.Pipeline.PipelineElements[0].Expression -and $firstArg.Pipeline.PipelineElements[0].Expression.GetType().Name -in 'VariableExpressionAst','MemberExpressionAst','ExpandableStringExpressionAst', 'StringConstantExpressionAst') { $condition = $firstArg.Pipeline.PipelineElements[0].Expression } elseif ($firstArg.GetType().Name -eq 'ScriptBlockExpressionAst') { $condition = $firstArg -replace '^\{' -replace '\}$' } $BeforeLoop = '' $callstack = Get-PSCallStack $callCount = @($callstack | Where-Object { $_.InvocationInfo.MyCommand.Name -eq $myCmdName}).count - 1 $untilVar = '$' + ('_' * $callCount) + 'untilStartTime' if ($condition -is [string]) { if ($condition -as [Timespan]) { $beforeLoop = "$untilVar = [DateTime]::Now" $condition = "(([DateTime]::Now - $untilVar) -ge ([Timespan]'$Condition'))" } elseif ($condition -as [DateTime]) { $condition = "[DateTime]::Now -ge ([DateTime]'$Condition')" } else { $beforeLoop = "$untilVar = [DateTime]::Now" $condition = 'Get-Event -SourceIdentifier ' + "'$condition'" + " -ErrorAction Ignore | Where-Object TimeGenerated -ge $untilVar" } } $conditionScript = [ScriptBlock]::Create($condition) $LoopScript = $secondArg $secondArgScriptBlock = [ScriptBlock]::Create($LoopScript) $conditionScript = $conditionScript | .>Pipescript $untilTranspiled = $secondArgScriptBlock | .>Pipescript $conditionScript = if ($conditionScript -match '^\(') { "(-not $conditionScript)" } else { "(-not ($conditionScript))" } $newScript = @" $(if ($BeforeLoop) { $BeforeLoop + [Environment]::NewLine}) $(if ($CommandName -like ':*') { "$CommandName "})do { $untilTranspiled } while $conditionScript "@ [ScriptBlock]::Create($newScript) } |