diff --git a/.github/workflows/TestAndPublish.yml b/.github/workflows/TestAndPublish.yml index d8db114bb..9b3dc7f89 100644 --- a/.github/workflows/TestAndPublish.yml +++ b/.github/workflows/TestAndPublish.yml @@ -5,88 +5,6 @@ on: pull_request: workflow_dispatch: jobs: - PowerShellStaticAnalysis: - runs-on: ubuntu-latest - steps: - - name: InstallScriptCop - id: InstallScriptCop - shell: pwsh - run: | - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - Install-Module -Name ScriptCop -Repository PSGallery -Force -Scope CurrentUser - Import-Module ScriptCop -Force -PassThru - - name: InstallPSScriptAnalyzer - id: InstallPSScriptAnalyzer - shell: pwsh - run: | - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - Install-Module -Name PSScriptAnalyzer -Repository PSGallery -Force -Scope CurrentUser - Import-Module PSScriptAnalyzer -Force -PassThru - - name: InstallPSDevOps - id: InstallPSDevOps - shell: pwsh - run: | - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - Install-Module -Name PSDevOps -Repository PSGallery -Force -Scope CurrentUser - Import-Module PSDevOps -Force -PassThru - - name: Check out repository - uses: actions/checkout@v2 - - name: RunScriptCop - id: RunScriptCop - shell: pwsh - run: | - $Parameters = @{} - $Parameters.ModulePath = ${env:ModulePath} - foreach ($k in @($parameters.Keys)) { - if ([String]::IsNullOrEmpty($parameters[$k])) { - $parameters.Remove($k) - } - } - Write-Host "::debug:: RunScriptCop $(@(foreach ($p in $Parameters.GetEnumerator()) {'-' + $p.Key + ' ' + $p.Value}) -join ' ')" - & {param([string]$ModulePath) - Import-Module ScriptCop, PSDevOps -PassThru | Out-Host - - if (-not $ModulePath) { - $orgName, $moduleName = $env:GITHUB_REPOSITORY -split "/" - $ModulePath = ".\$moduleName.psd1" - } - if ($ModulePath -like '*PSDevOps*') { - Remove-Module PSDeVOps # If running ScriptCop on PSDeVOps, we need to remove the global module first. - } - - - $importedModule =Import-Module $ModulePath -Force -PassThru - - $importedModule | Out-Host - - $importedModule | - Test-Command | - Tee-Object -Variable scriptCopIssues | - Out-Host - - foreach ($issue in $scriptCopIssues) { - Write-GitHubWarning -Message "$($issue.ItemWithProblem): $($issue.Problem)" - } - } @Parameters - - name: RunPSScriptAnalyzer - id: RunPSScriptAnalyzer - shell: pwsh - run: | - Import-Module PSScriptAnalyzer, PSDevOps -PassThru | Out-Host - $invokeScriptAnalyzerSplat = @{Path='.\'} - if ($ENV:PSScriptAnalyzer_Recurse) { - $invokeScriptAnalyzerSplat.Recurse = $true - } - $result = Invoke-ScriptAnalyzer @invokeScriptAnalyzerSplat - - foreach ($r in $result) { - if ('information', 'warning' -contains $r.Severity) { - Write-GitHubWarning -Message "$($r.RuleName) : $($r.Message)" -SourcePath $r.ScriptPath -LineNumber $r.Line -ColumnNumber $r.Column - } - elseif ($r.Severity -eq 'Error') { - Write-GitHubError -Message "$($r.RuleName) : $($r.Message)" -SourcePath $r.ScriptPath -LineNumber $r.Line -ColumnNumber $r.Column - } - } TestPowerShellOnLinux: runs-on: ubuntu-latest steps: @@ -596,4 +514,41 @@ jobs: uses: StartAutomating/EZOut@master - name: UseHelpOut uses: StartAutomating/HelpOut@master - + - name: Log in to ghcr.io + uses: docker/login-action@master + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract Docker Metadata (for branch) + if: ${{github.ref_name != 'main' && github.ref_name != 'master' && github.ref_name != 'latest'}} + id: meta + uses: docker/metadata-action@master + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + - name: Extract Docker Metadata (for main) + if: ${{github.ref_name == 'main' || github.ref_name == 'master' || github.ref_name == 'latest'}} + id: metaMain + uses: docker/metadata-action@master + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: latest=true + - name: Build and push Docker image (from main) + if: ${{github.ref_name == 'main' || github.ref_name == 'master' || github.ref_name == 'latest'}} + uses: docker/build-push-action@master + with: + context: . + push: true + tags: ${{ steps.metaMain.outputs.tags }} + labels: ${{ steps.metaMain.outputs.labels }} + - name: Build and push Docker image (from branch) + if: ${{github.ref_name != 'main' && github.ref_name != 'master' && github.ref_name != 'latest'}} + uses: docker/build-push-action@master + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} diff --git a/Assets/PipeScript-4-chevron-animated.svg b/Assets/PipeScript-4-chevron-animated.svg index 7ce5c2e9e..248fe46a1 100644 --- a/Assets/PipeScript-4-chevron-animated.svg +++ b/Assets/PipeScript-4-chevron-animated.svg @@ -6,13 +6,10 @@ - + - - - P | diff --git a/Assets/PipeScript-4-chevron.svg b/Assets/PipeScript-4-chevron.svg index 610a74967..61fd2c242 100644 --- a/Assets/PipeScript-4-chevron.svg +++ b/Assets/PipeScript-4-chevron.svg @@ -6,13 +6,10 @@ - + - - - P | diff --git a/Assets/PipeScript-animated.svg b/Assets/PipeScript-animated.svg index 91f1b86d4..2b5b84f76 100644 --- a/Assets/PipeScript-animated.svg +++ b/Assets/PipeScript-animated.svg @@ -6,13 +6,10 @@ - + - - - P | diff --git a/Assets/PipeScript-ouroboros-animated.svg b/Assets/PipeScript-ouroboros-animated.svg index 55426f9fb..948480b51 100644 --- a/Assets/PipeScript-ouroboros-animated.svg +++ b/Assets/PipeScript-ouroboros-animated.svg @@ -6,13 +6,10 @@ - + - - - P | @@ -20,40 +17,76 @@ | pt - - - - - - - - - - - - - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Assets/PipeScript-ouroboros.svg b/Assets/PipeScript-ouroboros.svg index a02444acf..c0a7d7ec1 100644 --- a/Assets/PipeScript-ouroboros.svg +++ b/Assets/PipeScript-ouroboros.svg @@ -6,13 +6,10 @@ - + - - - P | @@ -20,10 +17,6 @@ | pt - - - - @@ -32,4 +25,20 @@ + + + + + + + + + + + + + + + + diff --git a/Assets/PipeScript.svg b/Assets/PipeScript.svg index 9f220d509..629c3dc23 100644 --- a/Assets/PipeScript.svg +++ b/Assets/PipeScript.svg @@ -6,13 +6,10 @@ - + - - - P | diff --git a/Build/Github/Jobs/BuildPipeScript.psd1 b/Build/Github/Jobs/BuildPipeScript.psd1 index 96fb18a46..3dd03844a 100644 --- a/Build/Github/Jobs/BuildPipeScript.psd1 +++ b/Build/Github/Jobs/BuildPipeScript.psd1 @@ -30,6 +30,56 @@ id = 'PipeScriptBranch' }, 'RunEZOut', - 'RunHelpOut' + 'RunHelpOut', + @{ + 'name'='Log in to ghcr.io' + 'uses'='docker/login-action@master' + 'with'=@{ + 'registry'='${{ env.REGISTRY }}' + 'username'='${{ github.actor }}' + 'password'='${{ secrets.GITHUB_TOKEN }}' + } + }, + @{ + name = 'Extract Docker Metadata (for branch)' + if = '${{github.ref_name != ''main'' && github.ref_name != ''master'' && github.ref_name != ''latest''}}' + id = 'meta' + uses = 'docker/metadata-action@master' + with = @{ + 'images'='${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}' + } + }, + @{ + name = 'Extract Docker Metadata (for main)' + if = '${{github.ref_name == ''main'' || github.ref_name == ''master'' || github.ref_name == ''latest''}}' + id = 'metaMain' + uses = 'docker/metadata-action@master' + with = @{ + 'images'='${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}' + 'flavor'='latest=true' + } + }, + @{ + name = 'Build and push Docker image (from main)' + if = '${{github.ref_name == ''main'' || github.ref_name == ''master'' || github.ref_name == ''latest''}}' + uses = 'docker/build-push-action@master' + with = @{ + 'context'='.' + 'push'='true' + 'tags'='${{ steps.metaMain.outputs.tags }}' + 'labels'='${{ steps.metaMain.outputs.labels }}' + } + }, + @{ + name = 'Build and push Docker image (from branch)' + if = '${{github.ref_name != ''main'' && github.ref_name != ''master'' && github.ref_name != ''latest''}}' + uses = 'docker/build-push-action@master' + with = @{ + 'context'='.' + 'push'='true' + 'tags'='${{ steps.meta.outputs.tags }}' + 'labels'='${{ steps.meta.outputs.labels }}' + } + } ) } \ No newline at end of file diff --git a/Build/PipeScript.GitHubWorkflow.PSDevOps.ps1 b/Build/PipeScript.GitHubWorkflow.PSDevOps.ps1 index 4f74a31f4..991b05589 100644 --- a/Build/PipeScript.GitHubWorkflow.PSDevOps.ps1 +++ b/Build/PipeScript.GitHubWorkflow.PSDevOps.ps1 @@ -6,10 +6,12 @@ Import-BuildStep -SourcePath ( Push-Location ($PSScriptRoot | Split-Path) New-GitHubWorkflow -Name "Analyze, Test, Tag, and Publish" -On Push, PullRequest, - Demand -Job PowerShellStaticAnalysis, - TestPowerShellOnLinux, + Demand -Job TestPowerShellOnLinux, TagReleaseAndPublish, - BuildPipeScript -OutputPath .\.github\workflows\TestAndPublish.yml + BuildPipeScript -OutputPath .\.github\workflows\TestAndPublish.yml -Environment ([Ordered]@{ + REGISTRY = 'ghcr.io' + IMAGE_NAME = '${{ github.repository }}' + }) New-GitHubWorkflow -On Demand -Job RunGitPub -Name GitPub -OutputPath .\.github\workflows\GitPub.yml diff --git a/Build/PipeScript.PSSVG.ps1 b/Build/PipeScript.PSSVG.ps1 index d1db60f30..e5ad6ff0e 100644 --- a/Build/PipeScript.PSSVG.ps1 +++ b/Build/PipeScript.PSSVG.ps1 @@ -1,9 +1,9 @@ #requires -Module PSSVG -$psChevron = Invoke-restMethod https://pssvg.start-automating.com/Examples/PowerShellChevron.svg $RotateEvery = [Timespan]'00:00:15' $Variants = '', '4-chevron','ouroboros','animated','4-chevron-animated','ouroboros-animated' +$ϕ = (1 + [math]::sqrt(5))/2 foreach ($variant in $variants) { @@ -30,10 +30,10 @@ svg -ViewBox 1920,1080 @( "12.5,100" "55,50" ) -join ' ') -Fill '#4488ff' -Class 'foreground-fill' - ) -MarkerWidth 75 -MarkerHeight 75 -RefX 50 -RefY 50 -Orient 'auto' + ) -MarkerWidth (75/$ϕ) -MarkerHeight (75/$ϕ) -RefX 50 -RefY 50 -Orient 'auto-start-reverse' ) - $psChevron.svg.symbol.OuterXml + # $psChevron.svg.symbol.OuterXml svg.text -FontSize 192 -TextAnchor 'middle' -DominantBaseline 'middle' -X 50% -Y 50% -Content @( @@ -46,14 +46,27 @@ svg -ViewBox 1920,1080 @( - foreach ($circleN in 0..2) { + + if ($variant -match 'ouroboros') { + $numberOfCircles = 1..6 + $RotateEvery = [timespan]'00:01:00' + } else { + $numberOfCircles = 0..2 + $RotateEvery = [timespan]'00:00:15' + } + foreach ($circleN in $numberOfCircles) { $radius = 475 - ($circleN * 5) $circleTop = (1920/2), ((1080/2)-$radius) $circleMid = (1920/2), (1080/2) $circleRight = ((1920/2) + $radius),((1080/2)) $circleBottom = (1920/2), ((1080/2)+$radius) $circleLeft = ((1920/2) - $radius),((1080/2)) - $rotateEach = $RotateEvery * (1 + $circleN) + $rotateEach = + if ($variant -match 'ouroboros') { + $RotateEvery / ($circleN) + } else { + $RotateEvery * (1 + $circleN) + } if ($circleN) { if ($variant -in '', '4-chevron') { diff --git a/Commands/Aspects/DynamicParameter-Aspects.ps.ps1 b/Commands/Aspects/DynamicParameter-Aspects.ps.ps1 index 4b08189dc..17acaaa48 100644 --- a/Commands/Aspects/DynamicParameter-Aspects.ps.ps1 +++ b/Commands/Aspects/DynamicParameter-Aspects.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Dynamic\s{0,}Parameter")] +param() + Aspect function DynamicParameter { <# .SYNOPSIS @@ -90,7 +93,7 @@ Aspect function DynamicParameter { $paramName = "$($BlankParameterName[-1])$pos" } # construct a minimal dynamic parameter - $DynamicParameters.Add($paramName, + $DynamicParameters.Add($paramName, [Management.Automation.RuntimeDefinedParameter]::new( $paramName, [PSObject], diff --git a/Commands/Aspects/Module-Aspects.ps.ps1 b/Commands/Aspects/Module-Aspects.ps.ps1 index 8ee8c63f3..88a3b446c 100644 --- a/Commands/Aspects/Module-Aspects.ps.ps1 +++ b/Commands/Aspects/Module-Aspects.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Module")] +param() + Aspect function ModuleExtensionType { <# .SYNOPSIS @@ -140,7 +143,7 @@ Aspect function ModuleExtensionPattern { # We'll go thru each pattern in order $combinedRegex = @(foreach ($categoryExtensionTypeInfo in @($ModuleExtensionTypes.psobject.properties)) { - $categoryPattern = $categoryExtensionTypeInfo.Value.Pattern + $categoryPattern = @($categoryExtensionTypeInfo.Value.Pattern,$categoryExtensionTypeInfo.Value.FilePattern,$categoryExtensionTypeInfo.Value.CommandPattern -ne $null)[0] # ( and skip anyone that does not have a pattern) continue if -not $categoryPattern @@ -210,7 +213,7 @@ Aspect function ModuleExtensionCommand { $PSTypeName ) - process { + process { if ($Module -is [string]) { $Module = Get-Module $Module } diff --git a/Commands/Aspects/Module-Aspects.ps1 b/Commands/Aspects/Module-Aspects.ps1 index c7c0b404e..0faa03916 100644 --- a/Commands/Aspects/Module-Aspects.ps1 +++ b/Commands/Aspects/Module-Aspects.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Module")] +param() + function Aspect.ModuleExtensionType { @@ -289,7 +292,7 @@ function Aspect.ModuleExtensionPattern { # We'll go thru each pattern in order $combinedRegex = @(foreach ($categoryExtensionTypeInfo in @($ModuleExtensionTypes.psobject.properties)) { - $categoryPattern = $categoryExtensionTypeInfo.Value.Pattern + $categoryPattern = @($categoryExtensionTypeInfo.Value.Pattern,$categoryExtensionTypeInfo.Value.FilePattern,$categoryExtensionTypeInfo.Value.CommandPattern -ne $null)[0] # ( and skip anyone that does not have a pattern) if (-not $categoryPattern) { continue } @@ -379,7 +382,7 @@ function Aspect.ModuleExtensionCommand { $PSTypeName ) - process { + process { if ($Module -is [string]) { $Module = Get-Module $Module } @@ -564,7 +567,7 @@ function Aspect.ModuleExtensionCommand { # We'll go thru each pattern in order $combinedRegex = @(foreach ($categoryExtensionTypeInfo in @($ModuleExtensionTypes.psobject.properties)) { - $categoryPattern = $categoryExtensionTypeInfo.Value.Pattern + $categoryPattern = @($categoryExtensionTypeInfo.Value.Pattern,$categoryExtensionTypeInfo.Value.FilePattern,$categoryExtensionTypeInfo.Value.CommandPattern -ne $null)[0] # ( and skip anyone that does not have a pattern) if (-not $categoryPattern) { continue } diff --git a/Commands/Aspects/Object-Aspects.ps.ps1 b/Commands/Aspects/Object-Aspects.ps.ps1 index 75393c2b7..9aed51bc8 100644 --- a/Commands/Aspects/Object-Aspects.ps.ps1 +++ b/Commands/Aspects/Object-Aspects.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Object")] +param() + Aspect function GroupObjectByTypeName { <# .SYNOPSIS @@ -49,4 +52,4 @@ Aspect function GroupObjectByType { end { $groupedByType } -} +} \ No newline at end of file diff --git a/Commands/Aspects/Object-Aspects.ps1 b/Commands/Aspects/Object-Aspects.ps1 index b5d1ac812..9ba2f4627 100644 --- a/Commands/Aspects/Object-Aspects.ps1 +++ b/Commands/Aspects/Object-Aspects.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Object")] +param() + function Aspect.GroupObjectByTypeName { @@ -58,4 +61,3 @@ function Aspect.GroupObjectByType { } - diff --git a/Commands/AutomaticVariables/Is-AutomaticVariables.ps.ps1 b/Commands/AutomaticVariables/Is-AutomaticVariables.ps.ps1 index 122af7341..e4f09dfd2 100644 --- a/Commands/AutomaticVariables/Is-AutomaticVariables.ps.ps1 +++ b/Commands/AutomaticVariables/Is-AutomaticVariables.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Automatic\s{0,}Variable")] +param() + PipeScript.Automatic.Variable function IsPipedTo { <# .SYNOPSIS diff --git a/Commands/AutomaticVariables/My-AutomaticVariables.ps.ps1 b/Commands/AutomaticVariables/My-AutomaticVariables.ps.ps1 index 83d261a9d..4b446927c 100644 --- a/Commands/AutomaticVariables/My-AutomaticVariables.ps.ps1 +++ b/Commands/AutomaticVariables/My-AutomaticVariables.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Automatic\s{0,}Variable")] +param() + # Declares various 'my' automatic variables PipeScript.Automatic.Variable function MyCallstack { <# diff --git a/Commands/CliXml/ConvertFrom-CliXml.ps.ps1 b/Commands/CliXml/ConvertFrom-CliXml.ps.ps1 index a0606bf19..047050ed9 100644 Binary files a/Commands/CliXml/ConvertFrom-CliXml.ps.ps1 and b/Commands/CliXml/ConvertFrom-CliXml.ps.ps1 differ diff --git a/Commands/Command/Search-Command.ps.ps1 b/Commands/Command/Search-Command.ps.ps1 new file mode 100644 index 000000000..4aa8fc045 --- /dev/null +++ b/Commands/Command/Search-Command.ps.ps1 @@ -0,0 +1,201 @@ +[ValidatePattern('Search-Command')] +param() + +function Search-Command { + <# + .SYNOPSIS + Searches commands + .DESCRIPTION + Searches loaded commands as quickly as possible. + .NOTES + Get-Command is somewhat notoriously slow, and command lookup is one of the more expensive parts of PowerShell. + + This command is designed to speedily search for commands by name, module, verb, noun, or pattern. + + Because Search-Command is built for speed, it will _not_ autodiscover commands. + #> + [OutputType([Management.Automation.CommandInfo])] + param( + # The name of the command + [vbn()] + [Alias('Like')] + [string[]] + $Name = '*', + + # One or more patterns to match. + [vbn()] + [Alias('Match','Matches')] + [PSObject[]] + $Pattern, + + # The module the command should be in. + [vbn()] + [PSObject[]] + $Module, + + # The verb of the command. + # This will be treated as the start of the command name (before any punctuation). + [vbn()] + [string[]] + $Verb, + + # The noun of the command + # This will be treated as the end of the command name (after any punctuation). + [vbn()] + [string[]] + $Noun, + + # A pattern for the parameter name. + # Only commands that have a parameter name or alias matching this will be returned. + [vbn()] + [string[]] + $ParameterName, + + # A pattern for the parameter type. + # Only commands that have a parameter type matching this will be returned. + # If `-ParameterName` is also provided, it will ensure these parameters have a type that match this pattern. + [vbn()] + [psobject[]] + $ParameterType, + + # A pattern for the output type. + # Only commands that have an output type matching this will be returned. + [vbn()] + [PSObject[]] + $OutputType, + + # The type of command to search for. + [vbn()] + [Alias('CommandTypes')] + [Management.Automation.CommandTypes] + $CommandType = 'Alias,Function,Cmdlet' + ) + + begin { + # This command is built for speed. + # It will cache all lookups. + if (-not $script:CommandLookupCache) { + $script:CommandLookupCache = [Ordered]@{} + } + + # It will also declare inner ScriptBlocks instead of inner functions + # (this avoids command lookup, and is faster) + + # To Search the command cache + $SearchCommandCache = { + # If there was a list of patterns + if ($PatternList) { + # check each pattern + foreach ($searchPattern in $PatternList) { + # against the cache command pointer + @($script:CommandLookupCache[$lookupKeys] | . $UnrollValues) -match $searchPattern + } + } else { + # otherwise, get all the commands + $script:CommandLookupCache[$lookupKeys] + } + } + + # To filter values + $FilterValues = { process { + # check each object + $in = $_ + + # against each post condition + foreach ($thisPostCondition in $PostConditions) { + $this = $_ = $in + + $conditionOutput = . $thisPostCondition + # and if it passes, return it + if (-not $conditionOutput) { return } + } + + return $in + } } + + # To unroll values, simply pass them thru. + $UnrollValues = { process { $_ } } + } + + process { + # Get our lookup keys + $lookupKeys = @( + foreach ($cmdName in $name) { + $lookupKey = "[$commandType]/$cmdName" + if (-not $script:CommandLookupCache[$lookupKey]) { + # by caching the pointer to the commands list, we can rapidly filter and easily know when new commands are added. + $script:CommandLookupCache[$lookupKey] = $global:ExecutionContext.InvokeCommand.GetCommands($cmdName, $commandType,$true) + } + $lookupKey + } + ) + + # Determine the list of patterns, given our parameters. + $PatternList = @( + if ($pattern) { $pattern} + if ($noun) { + "\p{P}(?>$($noun -join '|'))" + } + if ($verb) { + "^$($verb -join '|')\p{P}" + } + ) + + # Determine the list of post conditions, given our parameters. + $PostConditions = @( + # First, filter by module. + if ($module) { + + if ($module -match '\*') { + # We'll use `-like` for wildcard matching, + { foreach ($mod in $module) { if ($_.Module.Name -like $mod) { return $true } } } + } else { + # and `-eq` for exact matching. + { foreach ($mod in $module) { if ($_.Module.Name -eq $mod) { return $true } } } + } + } + + # Next, filter by parameter name. + if ($ParameterName) { + { + # Combine all parameter names and aliases into a single regex pattern. + $AnyParameterName = [Regex]::new("^(?>$($ParameterName -join '|'))$",'IgnoreCase,IgnorePatternWhitespace','00:00:00.001') + # Check if any parameter name matches the pattern. + @($_.Parameters.Keys;$_.Parameters.Values.Aliases) -match $AnyParameterName + } + } + if ($parameterType) { + { + # Combine all parameter types into a single regex pattern. + $AnyParameterType = [Regex]::new("(?>$($ParameterType -replace '\.','\.' -join '|'))",'IgnoreCase,IgnorePatternWhitespace','00:00:00.001') + # If we have a parameter name, filter by that first + @(if ($ParameterName) { + $in.Parameters[@( + $AnyParameterName = [Regex]::new("^(?>$($ParameterName -join '|'))$",'IgnoreCase,IgnorePatternWhitespace','00:00:00.001') + @($_.Parameters.Keys;$_.Parameters.Values.Aliases) -match $AnyParameterName + )].ParameterType.Fullname + } else { + $in.Parameters.Values.ParameterType.Fullname + }) -match $AnyParameterType # check if any parameter type matches the pattern. + } + } + if ($OutputType) { + { + # Combine all output types into a single regex pattern. + $anyOutputType = [Regex]::new("(?>$($OutputType -join '|'))",'IgnoreCase,IgnorePatternWhitespace','00:00:00.001') + # Check if any output type matches the pattern. + $_.OutputType.Name -match $anyOutputType + } + } + ) + + if (-not $PostConditions) { + . $SearchCommandCache | + . $UnrollValues + } else { + . $SearchCommandCache | + . $UnrollValues | + . $FilterValues + } + } +} diff --git a/Commands/Command/Search-Command.ps1 b/Commands/Command/Search-Command.ps1 new file mode 100644 index 000000000..e2c9cbb4f --- /dev/null +++ b/Commands/Command/Search-Command.ps1 @@ -0,0 +1,204 @@ +[ValidatePattern('Search-Command')] +param() + +function Search-Command { + + <# + .SYNOPSIS + Searches commands + .DESCRIPTION + Searches loaded commands as quickly as possible. + .NOTES + Get-Command is somewhat notoriously slow, and command lookup is one of the more expensive parts of PowerShell. + + This command is designed to speedily search for commands by name, module, verb, noun, or pattern. + + Because Search-Command is built for speed, it will _not_ autodiscover commands. + #> + [OutputType([Management.Automation.CommandInfo])] + param( + # The name of the command + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Like')] + [string[]] + $Name = '*', + + # One or more patterns to match. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Match','Matches')] + [PSObject[]] + $Pattern, + + # The module the command should be in. + [Parameter(ValueFromPipelineByPropertyName)] + [PSObject[]] + $Module, + + # The verb of the command. + # This will be treated as the start of the command name (before any punctuation). + [Parameter(ValueFromPipelineByPropertyName)] + [string[]] + $Verb, + + # The noun of the command + # This will be treated as the end of the command name (after any punctuation). + [Parameter(ValueFromPipelineByPropertyName)] + [string[]] + $Noun, + + # A pattern for the parameter name. + # Only commands that have a parameter name or alias matching this will be returned. + [Parameter(ValueFromPipelineByPropertyName)] + [string[]] + $ParameterName, + + # A pattern for the parameter type. + # Only commands that have a parameter type matching this will be returned. + # If `-ParameterName` is also provided, it will ensure these parameters have a type that match this pattern. + [Parameter(ValueFromPipelineByPropertyName)] + [psobject[]] + $ParameterType, + + # A pattern for the output type. + # Only commands that have an output type matching this will be returned. + [Parameter(ValueFromPipelineByPropertyName)] + [PSObject[]] + $OutputType, + + # The type of command to search for. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('CommandTypes')] + [Management.Automation.CommandTypes] + $CommandType = 'Alias,Function,Cmdlet' + ) + + begin { + # This command is built for speed. + # It will cache all lookups. + if (-not $script:CommandLookupCache) { + $script:CommandLookupCache = [Ordered]@{} + } + + # It will also declare inner ScriptBlocks instead of inner functions + # (this avoids command lookup, and is faster) + + # To Search the command cache + $SearchCommandCache = { + # If there was a list of patterns + if ($PatternList) { + # check each pattern + foreach ($searchPattern in $PatternList) { + # against the cache command pointer + @($script:CommandLookupCache[$lookupKeys] | . $UnrollValues) -match $searchPattern + } + } else { + # otherwise, get all the commands + $script:CommandLookupCache[$lookupKeys] + } + } + + # To filter values + $FilterValues = { process { + # check each object + $in = $_ + + # against each post condition + foreach ($thisPostCondition in $PostConditions) { + $this = $_ = $in + + $conditionOutput = . $thisPostCondition + # and if it passes, return it + if (-not $conditionOutput) { return } + } + + return $in + } } + + # To unroll values, simply pass them thru. + $UnrollValues = { process { $_ } } + } + + process { + # Get our lookup keys + $lookupKeys = @( + foreach ($cmdName in $name) { + $lookupKey = "[$commandType]/$cmdName" + if (-not $script:CommandLookupCache[$lookupKey]) { + # by caching the pointer to the commands list, we can rapidly filter and easily know when new commands are added. + $script:CommandLookupCache[$lookupKey] = $global:ExecutionContext.InvokeCommand.GetCommands($cmdName, $commandType,$true) + } + $lookupKey + } + ) + + # Determine the list of patterns, given our parameters. + $PatternList = @( + if ($pattern) { $pattern} + if ($noun) { + "\p{P}(?>$($noun -join '|'))" + } + if ($verb) { + "^$($verb -join '|')\p{P}" + } + ) + + # Determine the list of post conditions, given our parameters. + $PostConditions = @( + # First, filter by module. + if ($module) { + + if ($module -match '\*') { + # We'll use `-like` for wildcard matching, + { foreach ($mod in $module) { if ($_.Module.Name -like $mod) { return $true } } } + } else { + # and `-eq` for exact matching. + { foreach ($mod in $module) { if ($_.Module.Name -eq $mod) { return $true } } } + } + } + + # Next, filter by parameter name. + if ($ParameterName) { + { + # Combine all parameter names and aliases into a single regex pattern. + $AnyParameterName = [Regex]::new("^(?>$($ParameterName -join '|'))$",'IgnoreCase,IgnorePatternWhitespace','00:00:00.001') + # Check if any parameter name matches the pattern. + @($_.Parameters.Keys;$_.Parameters.Values.Aliases) -match $AnyParameterName + } + } + if ($parameterType) { + { + # Combine all parameter types into a single regex pattern. + $AnyParameterType = [Regex]::new("(?>$($ParameterType -replace '\.','\.' -join '|'))",'IgnoreCase,IgnorePatternWhitespace','00:00:00.001') + # If we have a parameter name, filter by that first + @(if ($ParameterName) { + $in.Parameters[@( + $AnyParameterName = [Regex]::new("^(?>$($ParameterName -join '|'))$",'IgnoreCase,IgnorePatternWhitespace','00:00:00.001') + @($_.Parameters.Keys;$_.Parameters.Values.Aliases) -match $AnyParameterName + )].ParameterType.Fullname + } else { + $in.Parameters.Values.ParameterType.Fullname + }) -match $AnyParameterType # check if any parameter type matches the pattern. + } + } + if ($OutputType) { + { + # Combine all output types into a single regex pattern. + $anyOutputType = [Regex]::new("(?>$($OutputType -join '|'))",'IgnoreCase,IgnorePatternWhitespace','00:00:00.001') + # Check if any output type matches the pattern. + $_.OutputType.Name -match $anyOutputType + } + } + ) + + if (-not $PostConditions) { + . $SearchCommandCache | + . $UnrollValues + } else { + . $SearchCommandCache | + . $UnrollValues | + . $FilterValues + } + } + +} + diff --git a/Commands/Compilers/Compile-LanguageDefinition.ps.ps1 b/Commands/Compilers/Compile-LanguageDefinition.ps.ps1 index 61d6454a7..1becaf566 100644 --- a/Commands/Compilers/Compile-LanguageDefinition.ps.ps1 +++ b/Commands/Compilers/Compile-LanguageDefinition.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern('Language')] +param() + Compile function LanguageDefinition { <# .SYNOPSIS diff --git a/Commands/HTML/Out-HTML.ps1 b/Commands/HTML/Out-HTML.ps1 index e3dfe52f8..469c1c372 100644 --- a/Commands/HTML/Out-HTML.ps1 +++ b/Commands/HTML/Out-HTML.ps1 @@ -8,6 +8,7 @@ Get-Process -id $pid | Out-HTML #> [OutputType([string])] + [Alias('text.html.out')] [CmdletBinding(DefaultParameterSetName='DefaultFormatter')] param( # The input object @@ -16,14 +17,17 @@ $InputObject, # The desired identifier for the output. + [Parameter(ValueFromPipelineByPropertyName)] [string] $Id, # The CSS class for the output. This will be inferred from the .pstypenames + [Parameter(ValueFromPipelineByPropertyName)] [string] $CssClass, # A CSS Style + [Parameter(ValueFromPipelineByPropertyName)] [Collections.IDictionary] $Style, @@ -32,6 +36,7 @@ [string[]]$ItemType, # If more than one view is available, this view will be used + [Parameter(ValueFromPipelineByPropertyName)] [string]$ViewName) begin { @@ -63,7 +68,8 @@ return "data-$dataAttributeName=$dataAttributeValue" } - } + } + if (-not $script:QuickRandom) { $script:QuickRandom = [Random]::new() @@ -445,6 +451,8 @@ $($inputObject | Out-String) $_ } } | Out-HTML @psboundParameters } + } elseif ($inputObject.ToHTML.Invoke) { + $null = $htmlOut.Append($inputObject.ToHTML()) } else { $matchingTypeName = $null #region Match TypeName to Formatter diff --git a/Commands/Interpreters/Get-Interpreter.ps.ps1 b/Commands/Interpreters/Get-Interpreter.ps.ps1 index b9c4d79b8..7193a950f 100644 --- a/Commands/Interpreters/Get-Interpreter.ps.ps1 +++ b/Commands/Interpreters/Get-Interpreter.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Interpret")] +param() + function Get-Interpreter { <# diff --git a/Commands/Interpreters/Invoke-Interpreter.ps.ps1 b/Commands/Interpreters/Invoke-Interpreter.ps.ps1 index e5022f09e..33d03e4aa 100644 --- a/Commands/Interpreters/Invoke-Interpreter.ps.ps1 +++ b/Commands/Interpreters/Invoke-Interpreter.ps.ps1 @@ -1,3 +1,5 @@ +[ValidatePattern("Interpret")] +param() function Invoke-Interpreter { <# diff --git a/Commands/JSON/Export-Json.ps.ps1 b/Commands/JSON/Export-Json.ps.ps1 index 0f6822344..295bc59b5 100644 --- a/Commands/JSON/Export-Json.ps.ps1 +++ b/Commands/JSON/Export-Json.ps.ps1 @@ -1,3 +1,7 @@ +[ValidatePattern("JSON")] +param() + + function Export-Json { <# .SYNOPSIS diff --git a/Commands/JSON/Export-Json.ps1 b/Commands/JSON/Export-Json.ps1 index d083504e5..d5bcf1a98 100644 --- a/Commands/JSON/Export-Json.ps1 +++ b/Commands/JSON/Export-Json.ps1 @@ -1,3 +1,7 @@ +[ValidatePattern("JSON")] +param() + + function Export-Json { <# diff --git a/Commands/JSON/Import-Json.ps.ps1 b/Commands/JSON/Import-Json.ps.ps1 index 170394980..f977e8985 100644 --- a/Commands/JSON/Import-Json.ps.ps1 +++ b/Commands/JSON/Import-Json.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JSON")] +param() + function Import-Json { <# diff --git a/Commands/JSON/Import-Json.ps1 b/Commands/JSON/Import-Json.ps1 index 7daa467b6..5164e229c 100644 --- a/Commands/JSON/Import-Json.ps1 +++ b/Commands/JSON/Import-Json.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JSON")] +param() + function Import-Json { <# diff --git a/Commands/JSON/Out-JSON.ps.ps1 b/Commands/JSON/Out-JSON.ps.ps1 new file mode 100644 index 000000000..397d19859 --- /dev/null +++ b/Commands/JSON/Out-JSON.ps.ps1 @@ -0,0 +1,49 @@ +[ValidatePattern("JSON")] +param() + +function Out-JSON { + <# + .SYNOPSIS + Outputs objects as JSON + .DESCRIPTION + Outputs objects in JSON. + + If only one object is outputted, it will not be in a list. + If multiple objects are outputted, they will be in a list. + .LINK + @{ + a = 'b' + } | Out-JSON + #> + [CmdletBinding(PositionalBinding=$false)] + [Alias('text.json.out')] + param( + # The input object. This will form the majority of the JSON. + [vfp()] + [PSObject] + $InputObject, + + # Any arguments. These will be appended to the input. + [Parameter(ValueFromRemainingArguments=$true)] + [PSObject[]] + $ArgumentList, + + # The depth of the JSON. By default, this is the FormatEnumerationLimit. + [int] + $Depth = $FormatEnumerationLimit + ) + + $inputAndArguments = @( $input ) + $ArgumentList + if (-not $depth) { + $depth = if ($FormatEnumerationLimit) { + $FormatEnumerationLimit + } else { + 4 + } + } + if ($inputAndArguments.Length -eq 1) { + ConvertTo-Json -InputObject $inputAndArguments[0] -Depth $Depth + } else { + $inputAndArguments | ConvertTo-Json -Depth $Depth + } +} diff --git a/Commands/JSON/Out-JSON.ps1 b/Commands/JSON/Out-JSON.ps1 new file mode 100644 index 000000000..8db3541e1 --- /dev/null +++ b/Commands/JSON/Out-JSON.ps1 @@ -0,0 +1,52 @@ +[ValidatePattern("JSON")] +param() + +function Out-JSON { + + <# + .SYNOPSIS + Outputs objects as JSON + .DESCRIPTION + Outputs objects in JSON. + + If only one object is outputted, it will not be in a list. + If multiple objects are outputted, they will be in a list. + .LINK + @{ + a = 'b' + } | Out-JSON + #> + [CmdletBinding(PositionalBinding=$false)] + [Alias('text.json.out')] + param( + # The input object. This will form the majority of the JSON. + [Parameter(ValueFromPipeline)] + [PSObject] + $InputObject, + + # Any arguments. These will be appended to the input. + [Parameter(ValueFromRemainingArguments=$true)] + [PSObject[]] + $ArgumentList, + + # The depth of the JSON. By default, this is the FormatEnumerationLimit. + [int] + $Depth = $FormatEnumerationLimit + ) + + $inputAndArguments = @( $input ) + $ArgumentList + if (-not $depth) { + $depth = if ($FormatEnumerationLimit) { + $FormatEnumerationLimit + } else { + 4 + } + } + if ($inputAndArguments.Length -eq 1) { + ConvertTo-Json -InputObject $inputAndArguments[0] -Depth $Depth + } else { + $inputAndArguments | ConvertTo-Json -Depth $Depth + } + +} + diff --git a/Commands/Module/Mount-Module.ps.ps1 b/Commands/Module/Mount-Module.ps.ps1 new file mode 100644 index 000000000..8110e7a7b --- /dev/null +++ b/Commands/Module/Mount-Module.ps.ps1 @@ -0,0 +1,58 @@ +[ValidatePattern('Mount[-\.]Module')] +param() +function Mount-Module +{ + <# + .SYNOPSIS + Mounts a module. + .DESCRIPTION + Mounts a module as a PSDrive. + + This can shorten paths considerably, and make it easier to work with a module's contents. + .EXAMPLE + Get-Module PipeScript | Mount-Module + .EXAMPLE + Mount-Module -Name Microsoft.PowerShell.Management + #> + [Alias('Mount.Module')] + param( + # The name of the module + [vfp()] + [Alias('ModuleName')] + [string] + $Name, + + # The root of the module. + [vfp()] + [string] + $Root, + + # The description + [vfp()] + [string] + $Description + ) + + process { + # Get the piped in object + $pipedIn = $_ + # If we have no name, return + return if -not $name + + if ($pipedIn -is [Management.Automation.PSModuleInfo]) { + if (-not $root) { + $root = $pipedIn | Split-Path + } + } + if (-not $root) { + # Get the root of the module + $root = Get-Module $Name | Select-Object -First 1 | Split-Path + # Return if we could not get the root (no module is loaded) + return if (-not $? -or -not $root) + } + # If we have no description, use the name + if (-not $Description) { $Description = $Name } + # Mount the module + New-PSDrive -Name $name -PSProvider FileSystem -Root $root -Description $description -ErrorAction Ignore -Scope Global + } +} \ No newline at end of file diff --git a/Commands/Module/Mount-Module.ps1 b/Commands/Module/Mount-Module.ps1 new file mode 100644 index 000000000..b522cf93b --- /dev/null +++ b/Commands/Module/Mount-Module.ps1 @@ -0,0 +1,59 @@ +[ValidatePattern('Mount[-\.]Module')] +param() +function Mount-Module { + + <# + .SYNOPSIS + Mounts a module. + .DESCRIPTION + Mounts a module as a PSDrive. + + This can shorten paths considerably, and make it easier to work with a module's contents. + .EXAMPLE + Get-Module PipeScript | Mount-Module + .EXAMPLE + Mount-Module -Name Microsoft.PowerShell.Management + #> + [Alias('Mount.Module')] + param( + # The name of the module + [Parameter(ValueFromPipeline)] + [Alias('ModuleName')] + [string] + $Name, + + # The root of the module. + [Parameter(ValueFromPipeline)] + [string] + $Root, + + # The description + [Parameter(ValueFromPipeline)] + [string] + $Description + ) + + process { + # Get the piped in object + $pipedIn = $_ + # If we have no name, return + if (-not $name) { return } + + if ($pipedIn -is [Management.Automation.PSModuleInfo]) { + if (-not $root) { + $root = $pipedIn | Split-Path + } + } + if (-not $root) { + # Get the root of the module + $root = Get-Module $Name | Select-Object -First 1 | Split-Path + # Return if we could not get the root (no module is loaded) + if ((-not $? -or -not $root)) { return } + } + # If we have no description, use the name + if (-not $Description) { $Description = $Name } + # Mount the module + New-PSDrive -Name $name -PSProvider FileSystem -Root $root -Description $description -ErrorAction Ignore -Scope Global + } + +} diff --git a/Commands/Module/Use-Module.ps.ps1 b/Commands/Module/Use-Module.ps.ps1 new file mode 100644 index 000000000..743ec8f15 --- /dev/null +++ b/Commands/Module/Use-Module.ps.ps1 @@ -0,0 +1,76 @@ +[ValidatePattern('Use[-\.]Module')] +param() +function Use-Module +{ + <# + .SYNOPSIS + Uses a module. + .DESCRIPTION + Uses a module's context to run a `[ScriptBlock]` (with dot-sourcing). + .EXAMPLE + Get-Module PipeScript | Use-Module -ScriptBlock { $myInvocation.MyCommand.ScriptBlock.Module } + #> + [Alias('Use.Module','Module.Use')] + param( + # The name of the module + [vfp()] + [Alias('ModuleName')] + [string] + $Name, + + # The script block to use. + [ScriptBlock] + $ScriptBlock = {}, + + # The list of arguments to pass to the script block. + [Parameter(ValueFromRemainingArguments)] + [Alias('Arguments','Args')] + [PSObject[]] + $ArgumentList, + + # Any named parameters to pass to the script block. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Parameters')] + [Collections.IDictionary] + $Parameter + ) + + process { + # Get the piped in object + $pipedIn = $_ + # If we have no name, return + return if -not $name + + # Get the module context + $moduleContext = + # (if it was already piped in, we already have it) + if ($pipedIn -is [Management.Automation.PSModuleInfo]) { + $name = $pipedIn + $pipedIn + } else { + Get-Module $Name | Select-Object -First 1 + } + + + # We're running in the module context, + $runningIn = $moduleContext + # and we want `$toRun` the ScriptBlock. + $ToRun = $ScriptBlock + + # The rest of the code is tedium. + # If there were arguments and parameters, pass them both with splatting. + if ($ArgumentList) { + if ($Parameter) { + . $runningIn $ToRun @ArgumentList @Parameter + } else { + . $runningIn $ToRun @ArgumentList + } + } elseif ($Parameter) { + # If there were only parameters, pass them with splatting. + . $runningIn $ToRun @Parameter + } else { + # If there were no arguments or parameters, just run the script block. + . $runningIn $ToRun + } + } +} \ No newline at end of file diff --git a/Commands/Module/Use-Module.ps1 b/Commands/Module/Use-Module.ps1 new file mode 100644 index 000000000..ca1ffbd07 --- /dev/null +++ b/Commands/Module/Use-Module.ps1 @@ -0,0 +1,77 @@ +[ValidatePattern('Use[-\.]Module')] +param() +function Use-Module { + + <# + .SYNOPSIS + Uses a module. + .DESCRIPTION + Uses a module's context to run a `[ScriptBlock]` (with dot-sourcing). + .EXAMPLE + Get-Module PipeScript | Use-Module -ScriptBlock { $myInvocation.MyCommand.ScriptBlock.Module } + #> + [Alias('Use.Module','Module.Use')] + param( + # The name of the module + [Parameter(ValueFromPipeline)] + [Alias('ModuleName')] + [string] + $Name, + + # The script block to use. + [ScriptBlock] + $ScriptBlock = {}, + + # The list of arguments to pass to the script block. + [Parameter(ValueFromRemainingArguments)] + [Alias('Arguments','Args')] + [PSObject[]] + $ArgumentList, + + # Any named parameters to pass to the script block. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Parameters')] + [Collections.IDictionary] + $Parameter + ) + + process { + # Get the piped in object + $pipedIn = $_ + # If we have no name, return + if (-not $name) { return } + + # Get the module context + $moduleContext = + # (if it was already piped in, we already have it) + if ($pipedIn -is [Management.Automation.PSModuleInfo]) { + $name = $pipedIn + $pipedIn + } else { + Get-Module $Name | Select-Object -First 1 + } + + + # We're running in the module context, + $runningIn = $moduleContext + # and we want `$toRun` the ScriptBlock. + $ToRun = $ScriptBlock + + # The rest of the code is tedium. + # If there were arguments and parameters, pass them both with splatting. + if ($ArgumentList) { + if ($Parameter) { + . $runningIn $ToRun @ArgumentList @Parameter + } else { + . $runningIn $ToRun @ArgumentList + } + } elseif ($Parameter) { + # If there were only parameters, pass them with splatting. + . $runningIn $ToRun @Parameter + } else { + # If there were no arguments or parameters, just run the script block. + . $runningIn $ToRun + } + } + +} diff --git a/Commands/ModuleMember/Import-ModuleMember.ps.ps1 b/Commands/ModuleMember/Import-ModuleMember.ps.ps1 index 0119d88c9..b669766a1 100644 --- a/Commands/ModuleMember/Import-ModuleMember.ps.ps1 +++ b/Commands/ModuleMember/Import-ModuleMember.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Module\s?Member")] +param() + function Import-ModuleMember { <# diff --git a/Commands/Optimization/Optimizer-ConsolidateAspects.ps.ps1 b/Commands/Optimization/Optimizer-ConsolidateAspects.ps.ps1 index f4e65a825..3714521bc 100644 --- a/Commands/Optimization/Optimizer-ConsolidateAspects.ps.ps1 +++ b/Commands/Optimization/Optimizer-ConsolidateAspects.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Optimize")] +param() + PipeScript.Optimizer function ConsolidateAspects { <# .SYNOPSIS diff --git a/Commands/Parsers/Out-Parser.ps.ps1 b/Commands/Parsers/Out-Parser.ps.ps1 index 02e9fa5f7..85071176f 100644 --- a/Commands/Parsers/Out-Parser.ps.ps1 +++ b/Commands/Parsers/Out-Parser.ps.ps1 @@ -1,4 +1,7 @@ -function Out-Parser +[ValidatePattern("Parse")] +param() + +function Out-Parser { <# .Synopsis @@ -57,7 +60,7 @@ } if (-not $script:ParserCommandMappingCache[$ParserCommand]) { # If we don't have a cached Command list - $script:ParserCommandMappingCache[$ParserCommand] = $PSParser.ForCommand($ParserCommand) + $script:ParserCommandMappingCache[$ParserCommand] = $PSParser.ForCommand($ParserCommand) } # If there was already an Command cached, we can skip the previous steps and just reuse the cached Commands. $ParserOutputCommands = $script:ParserCommandMappingCache[$ParserCommand] diff --git a/Commands/Parsers/Parse-CSharp.ps.ps1 b/Commands/Parsers/Parse-CSharp.ps.ps1 index e81990cd2..95046906a 100644 --- a/Commands/Parsers/Parse-CSharp.ps.ps1 +++ b/Commands/Parsers/Parse-CSharp.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>CSharp|C#|Parse)")] +param() + Parse function CSharp { <# .SYNOPSIS diff --git a/Commands/Parsers/Parse-PowerShell.ps.ps1 b/Commands/Parsers/Parse-PowerShell.ps.ps1 index b89969d66..fb494e6e4 100644 --- a/Commands/Parsers/Parse-PowerShell.ps.ps1 +++ b/Commands/Parsers/Parse-PowerShell.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>PowerShell|Parse)")] +param() + Parse function PowerShell { <# .SYNOPSIS diff --git a/Commands/Parsers/Parse-PowerShell.ps1 b/Commands/Parsers/Parse-PowerShell.ps1 index b4118e5d8..8bd5621ab 100644 --- a/Commands/Parsers/Parse-PowerShell.ps1 +++ b/Commands/Parsers/Parse-PowerShell.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>PowerShell|Parse)")] +param() + function Parse.PowerShell { diff --git a/Commands/PipeScript/Export-PipeScript.ps1 b/Commands/PipeScript/Export-PipeScript.ps1 index 91044c04b..5b8207934 100644 --- a/Commands/PipeScript/Export-PipeScript.ps1 +++ b/Commands/PipeScript/Export-PipeScript.ps1 @@ -36,7 +36,11 @@ function Export-Pipescript { # The throttle limit for parallel jobs. [int] - $ThrottleLimit = 7 + $ThrottleLimit = 7, + + # If set, will ignore validation when building. + [switch] + $Force ) begin { @@ -95,6 +99,10 @@ function Export-Pipescript { $ValidateAgainst ) + if ($force) { + return $true + } + #region Build Condition $ValidateAgainstString = if ($ValidateAgainst.ToString -is [psscriptmethod]) { @@ -114,35 +122,22 @@ function Export-Pipescript { } foreach ($commandAttribute in $CommandInfo.ScriptBlock.Attributes) { - if ($commandAttribute.RegexPattern) { - if ($env:GITHUB_STEP_SUMMARY) { - @( - "* $($CommandInfo) has a Build Validation Pattern: (``$($commandAttribute.RegexPattern)``)." - "* $($CommandInfo) Validating Against: ``$ValidateAgainstString``" - ) -join [Environment]::Newline | Out-File -Path $env:GITHUB_STEP_SUMMARY -Append - } + if ($commandAttribute.RegexPattern) { $validationPattern = [Regex]::new($commandAttribute.RegexPattern, $commandAttribute.Options, '00:00:00.1') if (-not $validationPattern.IsMatch($ValidateAgainstString)) { if ($env:GITHUB_STEP_SUMMARY) { - "* 🖐️ Skipping $($CommandInfo) because $ValidateAgainstString did not match ($($commandAttribute.RegexPattern))" | Out-File -Path $env:GITHUB_STEP_SUMMARY -Append + "* ⍻ Skipping $($CommandInfo) because commit did not match ``$($commandAttribute.RegexPattern)``" | Out-File -Path $env:GITHUB_STEP_SUMMARY -Append } Write-Warning "Skipping $($CommandInfo) : Did not match $($validationPattern)" return $false } } elseif ($commandAttribute -is [ValidateScript]) - { - if ($env:GITHUB_STEP_SUMMARY) { - @( - "* $($CommandInfo) has a Build Validation Script" - ) -join [Environment]::Newline | - Out-File -Path $env:GITHUB_STEP_SUMMARY -Append - } + { $validationOutput = . $commandAttribute.ScriptBlock $ValidateAgainst if (-not $validationOutput) { if ($env:GITHUB_STEP_SUMMARY) { - "* 🖐️ Skipping $($CommandInfo) because $($ValidateAgainstString) did not meet the validation criteria:" | Out-File -Path $env:GITHUB_STEP_SUMMARY -Append - + "* ⍻ Skipping $($CommandInfo) because validation script returned false" | Out-File -Path $env:GITHUB_STEP_SUMMARY -Append } Write-Warning "Skipping $($CommandInfo) : The $ValidateAgainstString was not valid" return $false diff --git a/Commands/PipeScript/Get-PipeScript.ps1 b/Commands/PipeScript/Get-PipeScript.ps1 index cd3da6b10..061e0d21a 100644 --- a/Commands/PipeScript/Get-PipeScript.ps1 +++ b/Commands/PipeScript/Get-PipeScript.ps1 @@ -38,7 +38,7 @@ function Get-PipeScript { # One or more PipeScript Command Types. [Parameter(ValueFromPipelineByPropertyName)] - [ValidateSet('Analyzer','Aspect','AutomaticVariable','BuildScript','Compiler','Interface','Language','Optimizer','Parser','Partial','PipeScriptNoun','PostProcessor','PreProcessor','Protocol','Route','Sentence','Template','Transform','Transpiler')] + [ValidateSet('Analyzer','Aspect','AutomaticVariable','BuildScript','Compiler','ContentType','Interface','Language','Optimizer','Parser','Partial','PipeScriptNoun','PostProcessor','PreProcessor','Protocol','Route','Sentence','Service','Technology','Template','Transform','Transpiler')] [string[]] $PipeScriptType, @@ -147,7 +147,7 @@ $ModuleExtensionTypeAspect = { $PSTypeName ) - process { + process { if ($Module -is [string]) { $Module = Get-Module $Module } @@ -332,7 +332,7 @@ $ModuleExtensionTypeAspect = { # We'll go thru each pattern in order $combinedRegex = @(foreach ($categoryExtensionTypeInfo in @($ModuleExtensionTypes.psobject.properties)) { - $categoryPattern = $categoryExtensionTypeInfo.Value.Pattern + $categoryPattern = @($categoryExtensionTypeInfo.Value.Pattern,$categoryExtensionTypeInfo.Value.FilePattern,$categoryExtensionTypeInfo.Value.CommandPattern -ne $null)[0] # ( and skip anyone that does not have a pattern) if (-not $categoryPattern) { continue } @@ -697,7 +697,7 @@ $ModuleExtensionTypeAspect = { # We'll go thru each pattern in order $combinedRegex = @(foreach ($categoryExtensionTypeInfo in @($ModuleExtensionTypes.psobject.properties)) { - $categoryPattern = $categoryExtensionTypeInfo.Value.Pattern + $categoryPattern = @($categoryExtensionTypeInfo.Value.Pattern,$categoryExtensionTypeInfo.Value.FilePattern,$categoryExtensionTypeInfo.Value.CommandPattern -ne $null)[0] # ( and skip anyone that does not have a pattern) if (-not $categoryPattern) { continue } diff --git a/Commands/PipeScript/Invoke-PipeScript.ps1 b/Commands/PipeScript/Invoke-PipeScript.ps1 index c70183990..36400b84b 100644 --- a/Commands/PipeScript/Invoke-PipeScript.ps1 +++ b/Commands/PipeScript/Invoke-PipeScript.ps1 @@ -478,7 +478,32 @@ $AttributeSyntaxTree.TypeName.Name } - # See if we could find a transpiler that fits. + $foundTranspiler = :FoundCommand do { + $foundIt = Get-Transpiler -TranspilerName "$transpilerStepName" + if ($foundIt) { + $foundIt; break FoundCommand + } + foreach ($langName in 'PipeScript','PowerShell') { + if ($PSLanguage.$langName.Templates.$transpilerStepName) { + $PSLanguage.$langName.Templates.$transpilerStepName; break FoundCommand + } + if ($PSLanguage.$langName.Templates.("$langName.$transpilerStepName")) { + $PSLanguage.$langName.Templates.("$langName.$transpilerStepName"); break FoundCommand + } + } + $realCommandExists = $ExecutionContext.SessionState.InvokeCommand.GetCommand(($transpilerStepName -replace '_','-'), 'Function,Cmdlet,Alias') + if ($realCommandExists) { + $realCommandExists; break FoundCommand + } + } while ($false) + + if ($InputObject) { + if ($foundTranspiler -and -not $foundTranspiler.CouldPipe($InputObject)) { + $foundTranspiler = $null + } + } + + <## See if we could find a transpiler that fits. $foundTranspiler = if ($InputObject) { Get-Transpiler -TranspilerName "$transpilerStepName" -CouldPipe $InputObject | @@ -486,7 +511,7 @@ } else { Get-Transpiler -TranspilerName "$transpilerStepName" } - + #> # Collect all of the arguments of the attribute, in the order they were specified. $argsInOrder = @( @($AttributeSyntaxTree.PositionalArguments) + @($AttributeSyntaxTree.NamedArguments) | Sort-Object { $_.Extent.StartOffset}) @@ -562,31 +587,7 @@ } else { & $foundTranspiler @ArgumentList @Parameter } - } - elseif ($( - $realCommandExists = $ExecutionContext.SessionState.InvokeCommand.GetCommand(($transpilerStepName -replace '_','-'), 'All') - $realCommandExists - )) { - if ($positionalArguments) { - $ArgumentList += $positionalArguments - } - if ($inputObject) { - $canPipe = foreach ($param in $realCommandExists.Parameters.Values) { - if ($param.Attributes.ValueFromPipeline -and $inputObject -is $param.ParameterType) { - $true - break - } - } - if ($canPipe) { - $inputObject | - & $realCommandExists @Parameter @ArgumentList - } else { - & $realCommandExists @Parameter @ArgumentList - } - } else { - & $realCommandExists @Parameter @ArgumentList - } - } + } elseif ($script:TypeAcceleratorsList -notcontains $transpilerStepName -and $transpilerStepName -notin 'Ordered') { $psCmdlet.WriteError( [Management.Automation.ErrorRecord]::new( diff --git a/Commands/PostProcessing/PostProcess-InitializeAutomaticVariable.ps.ps1 b/Commands/PostProcessing/PostProcess-InitializeAutomaticVariable.ps.ps1 index dfd671be7..86899be98 100644 --- a/Commands/PostProcessing/PostProcess-InitializeAutomaticVariable.ps.ps1 +++ b/Commands/PostProcessing/PostProcess-InitializeAutomaticVariable.ps.ps1 @@ -1,3 +1,7 @@ +[ValidatePattern("PostProcess")] +param() + + PipeScript.PostProcess function InitializeAutomaticVariables { <# .SYNOPSIS diff --git a/Commands/PostProcessing/PostProcess-PartialFunction.ps.ps1 b/Commands/PostProcessing/PostProcess-PartialFunction.ps.ps1 index f804cf0fe..1c4044cc0 100644 --- a/Commands/PostProcessing/PostProcess-PartialFunction.ps.ps1 +++ b/Commands/PostProcessing/PostProcess-PartialFunction.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("PostProcess")] +param() + PipeScript.PostProcess function PartialFunction { <# .SYNOPSIS diff --git a/Commands/Protocols/HTTP-Protocol.ps.ps1 b/Commands/Protocols/HTTP-Protocol.ps.ps1 index a0fd0b759..802641050 100644 --- a/Commands/Protocols/HTTP-Protocol.ps.ps1 +++ b/Commands/Protocols/HTTP-Protocol.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>HTTP|Protocol)")] +param() + Protocol function HTTP { <# .SYNOPSIS @@ -101,7 +104,15 @@ Protocol function HTTP { # The invocation command. By default, Invoke-RestMethod. # Whatever alternative command provided should have a similar signature to Invoke-RestMethod. [string] - $Invoker = 'Invoke-RestMethod' + $Invoker = 'Invoke-RestMethod', + + [Alias('Expand Property')] + [string] + $ExpandProperty, + + [Alias('Properties')] + [string[]] + $Property ) process { @@ -229,6 +240,15 @@ Protocol function HTTP { $param.Value } } + + + if ($ExpandProperty -or $Property) { + if ($property -and -not $ExpandProperty) { + " | Select-Object -Property $(@( + + ) -join ',')" + } + } ) -join ' ' [scriptblock]::Create($newScript) diff --git a/Commands/Protocols/HTTP-Protocol.ps1 b/Commands/Protocols/HTTP-Protocol.ps1 index 731a6cfde..de1816f80 100644 --- a/Commands/Protocols/HTTP-Protocol.ps1 +++ b/Commands/Protocols/HTTP-Protocol.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>HTTP|Protocol)")] +param() + function Protocol.HTTP { @@ -103,7 +106,15 @@ function Protocol.HTTP { # The invocation command. By default, Invoke-RestMethod. # Whatever alternative command provided should have a similar signature to Invoke-RestMethod. [string] - $Invoker = 'Invoke-RestMethod' + $Invoker = 'Invoke-RestMethod', + + [Alias('Expand Property')] + [string] + $ExpandProperty, + + [Alias('Properties')] + [string[]] + $Property ) process { @@ -231,6 +242,15 @@ function Protocol.HTTP { $param.Value } } + + + if ($ExpandProperty -or $Property) { + if ($property -and -not $ExpandProperty) { + " | Select-Object -Property $(@( + + ) -join ',')" + } + } ) -join ' ' [scriptblock]::Create($newScript) diff --git a/Commands/Protocols/JSONSchema-Protocol.ps.ps1 b/Commands/Protocols/JSONSchema-Protocol.ps.ps1 index 36dcbd66b..3b81fc353 100644 --- a/Commands/Protocols/JSONSchema-Protocol.ps.ps1 +++ b/Commands/Protocols/JSONSchema-Protocol.ps.ps1 @@ -1,3 +1,7 @@ +[ValidatePattern("(?>JSONSchema|Protocol)")] +param() + + Protocol function JSONSchema { <# .SYNOPSIS diff --git a/Commands/Protocols/JSONSchema-Protocol.ps1 b/Commands/Protocols/JSONSchema-Protocol.ps1 index 56145a9d1..984f06bd1 100644 --- a/Commands/Protocols/JSONSchema-Protocol.ps1 +++ b/Commands/Protocols/JSONSchema-Protocol.ps1 @@ -1,3 +1,7 @@ +[ValidatePattern("(?>JSONSchema|Protocol)")] +param() + + function Protocol.JSONSchema { diff --git a/Commands/Protocols/OpenAPI-Protocol.ps.ps1 b/Commands/Protocols/OpenAPI-Protocol.ps.ps1 index b23b8eed7..ad0cdb98c 100644 --- a/Commands/Protocols/OpenAPI-Protocol.ps.ps1 +++ b/Commands/Protocols/OpenAPI-Protocol.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>OpenAPI|Protocol)")] +param() + Protocol function OpenAPI { <# .SYNOPSIS diff --git a/Commands/Protocols/OpenAPI-Protocol.ps1 b/Commands/Protocols/OpenAPI-Protocol.ps1 index b4c31787f..6f566b603 100644 --- a/Commands/Protocols/OpenAPI-Protocol.ps1 +++ b/Commands/Protocols/OpenAPI-Protocol.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>OpenAPI|Protocol)")] +param() + function Protocol.OpenAPI { diff --git a/Commands/Protocols/UDP-Protocol.ps.ps1 b/Commands/Protocols/UDP-Protocol.ps.ps1 index ef4293847..1f62cc1c1 100644 --- a/Commands/Protocols/UDP-Protocol.ps.ps1 +++ b/Commands/Protocols/UDP-Protocol.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>UDP|Protocol)")] +param() + Protocol function UDP { <# .SYNOPSIS diff --git a/Commands/Protocols/UDP-Protocol.ps1 b/Commands/Protocols/UDP-Protocol.ps1 index e3cb5bd28..7d5dca8fe 100644 --- a/Commands/Protocols/UDP-Protocol.ps1 +++ b/Commands/Protocols/UDP-Protocol.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>UDP|Protocol)")] +param() + function Protocol.UDP { diff --git a/Commands/Routes/Route-Uptime.ps.ps1 b/Commands/Routes/Route-Uptime.ps.ps1 index 5e7fcc046..4f7c77e3f 100644 --- a/Commands/Routes/Route-Uptime.ps.ps1 +++ b/Commands/Routes/Route-Uptime.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Route")] +param() + Route function Uptime { <# .SYNOPSIS diff --git a/Commands/Routes/Route-Uptime.ps1 b/Commands/Routes/Route-Uptime.ps1 index aae6ffd68..30371005a 100644 --- a/Commands/Routes/Route-Uptime.ps1 +++ b/Commands/Routes/Route-Uptime.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Route")] +param() + function Route.Uptime { diff --git a/Commands/Routes/Route-VersionInfo.ps.ps1 b/Commands/Routes/Route-VersionInfo.ps.ps1 index 874707649..5c2f2150b 100644 --- a/Commands/Routes/Route-VersionInfo.ps.ps1 +++ b/Commands/Routes/Route-VersionInfo.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Route")] +param() + Route function VersionInfo { <# .SYNOPSIS diff --git a/Commands/Routes/Route-VersionInfo.ps1 b/Commands/Routes/Route-VersionInfo.ps1 index 785f86ce8..338f8649e 100644 --- a/Commands/Routes/Route-VersionInfo.ps1 +++ b/Commands/Routes/Route-VersionInfo.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Route")] +param() + function Route.VersionInfo { diff --git a/Commands/Services/Serve-Asset.ps.ps1 b/Commands/Services/Serve-Asset.ps.ps1 new file mode 100644 index 000000000..2fb176c38 --- /dev/null +++ b/Commands/Services/Serve-Asset.ps.ps1 @@ -0,0 +1,60 @@ +[ValidatePattern('Asset')] +param() + +Serve function Asset { + <# + .SYNOPSIS + Serves asset files. + .DESCRIPTION + Serves asset files. + + These files will not be run, but will be served as static files. + + One or more extensions can be provided. + + Any file that matches these extensions will be served. + .NOTES + This service allows asset files to be served from a module without exposing the file system. + #> + [ValidatePattern( + '(?>$($this.Extension -replace "\.","\." -join "|"))$', Options ='IgnoreCase,IgnorePatternWhitespace' + )] + param( + # The list of extensions to serve as assets + [vfp(Mandatory)] + [PSObject[]] + $Extension, + + # The module containing the assets. + [vbn()] + [PSObject] + $Module, + + # The request object. + # This should generally not be provided, as it will be provided by the server. + # (it can be provided for testing purposes) + [vbn()] + [PSObject] + $Request + ) + + process { + + if (-not $module) { return } + if (-not $request) { return } + + $relativePath = ($Request.Url.Segments -replace '^/' -replace '/$' -ne '') -join [IO.Path]::DirectorySeparatorChar + $absolutePath = $module | Split-Path | Join-Path -ChildPath $relativePath + + if ($absolutePath -and (Test-Path $absolutePath)) { + $fileItem = (Get-Item -LiteralPath $absolutePath) + if ($fileItem.Extension -in $extension) { + if ($psNode) { + $psNode.ServeFile($fileItem, $request, $response) + } else { + $fileItem + } + } + } + } +} diff --git a/Commands/Services/Serve-Asset.ps1 b/Commands/Services/Serve-Asset.ps1 new file mode 100644 index 000000000..b16fca1c5 --- /dev/null +++ b/Commands/Services/Serve-Asset.ps1 @@ -0,0 +1,63 @@ + +function Serve.Asset { + + <# + .SYNOPSIS + Serves asset files. + .DESCRIPTION + Serves asset files. + + These files will not be run, but will be served as static files. + + One or more extensions can be provided. + + Any file that matches these extensions will be served. + .NOTES + This service allows asset files to be served from a module without exposing the file system. + #> + [ValidatePattern( + '(?>$($this.Extension -join "|"))$', Options ='IgnoreCase,IgnorePatternWhitespace' + )] + param( + # The list of extensions to serve as assets + [Parameter(Mandatory,ValueFromPipeline)] + [PSObject[]] + $Extension, + + # The module containing the assets. + [Parameter(ValueFromPipelineByPropertyName)] + [PSObject] + $Module, + + # The request object. + # This should generally not be provided, as it will be provided by the server. + # (it can be provided for testing purposes) + [Parameter(ValueFromPipelineByPropertyName)] + [PSObject] + $Request + ) + + process { + + if (-not $module) { return } + if (-not $request) { return } + + $relativePath = ($Request.Url.Segments -replace '^/' -replace '/$' -ne '') -join [IO.Path]::DirectorySeparatorChar + $absolutePath = $module | Split-Path | Join-Path -ChildPath $relativePath + + if ($absolutePath -and (Test-Path $absolutePath)) { + $fileItem = (Get-Item -LiteralPath $absolutePath) + if ($fileItem.Extension -in $extension) { + if ($psNode) { + #[IO.File]::ReadAllText($fileItem) + $psNode.ServeFile($fileItem, $request, $response) + } else { + $fileItem + } + } + } + } + +} + + diff --git a/Commands/Services/Serve-Command.ps.ps1 b/Commands/Services/Serve-Command.ps.ps1 new file mode 100644 index 000000000..2c99c2e1b --- /dev/null +++ b/Commands/Services/Serve-Command.ps.ps1 @@ -0,0 +1,147 @@ +[ValidatePattern('Command')] +param() + +Serve function Command { + <# + .SYNOPSIS + Serves a command. + .DESCRIPTION + Serves a command or pattern of commands. + #> + [ValidatePattern( + ' + / + (?> + $($this.Command) + | + $($this.Command -split "\p{P}" -join "/") + | + $( + $CmdParts = $this.Command -split "\p{P}" + [Array]::Reverse($cmdParts) + $cmdParts -join "/" + ) + ) + /? + ', Options ='IgnoreCase,IgnorePatternWhitespace' + )] + [CmdletBinding(PositionalBinding=$false)] + param( + # The command or pattern of commands that is being served. + [vbn(Mandatory)] + [Alias('CommandPattern')] + [PSObject] + $Command, + + # The request object. + # This should generally not be provided, as it will be provided by the server. + # (it can be provided for testing purposes) + [vbn()] + [PSObject] + $Request, + + # A collection of parameters to pass to the command. + [vbn()] + [Alias('Parameters')] + [PSObject] + $Parameter, + + # A collection of parameters to pass to the command by default, if no parameter was provided. + [vbn()] + [Alias('DefaultParameters')] + [PSObject] + $DefaultParameter, + + # One or more parameters to remove. + [vbn()] + [Alias('HideParameter','DenyParameter','DenyParameters')] + [string[]] + $RemoveParameter, + + # The displayed name of the command + [vbn()] + [Alias('Title','DisplayName')] + [string] + $Name + ) + + process { + $ServingTheseCommands = + if ($Command -is [string]) { + $ExecutionContext.SessionState.InvokeCommand.GetCommands('*', 'Function,Alias,Cmdlet', $true) -match $Command + } elseif ($Command -is [Management.Automation.CommandInfo]) { + $Command + } elseif ($Command -is [ScriptBlock]) { + $function:InnerServiceFunction = $Command + $ExecutionContext.SessionState.InvokeCommand.GetCommand('InnerServiceFunction','Function') + $Command + } + + return if -not $ServingTheseCommands + + $CombinedParameters = [Ordered]@{} + + if ($RemoveParameter) { + foreach ($prop in $RemoveParameter) { + $CombinedParameters.Remove($prop) + } + } + + if ($DefaultParameter) { + if ($DefaultParameter -is [Collections.IDictionary]) { + $DefaultParameter = [PSCustomObject]$DefaultParameter + } + foreach ($prop in $DefaultParameter.psobject.properties) { + $CombinedParameters[$prop.Name] = $prop.Value + } + } + + if ($Parameter) { + if ($Parameter -is [Collections.IDictionary]) { + $Parameter = [PSCustomObject]$Parameter + } + foreach ($prop in $Parameter.psobject.properties) { + $CombinedParameters[$prop.Name] = $prop.Value + } + } + + + if ($Request) { + $requestParameter = if ($Request.Params) { + $Request.Params + } elseif ($Request.Body) { + $Request.Body + } + if ($RequestParameter -is [Collections.IDictionary]) { + $RequestParameter = [PSCustomObject]$RequestParameter + } + foreach ($prop in $RequestParameter.psobject.properties) { + if ($RemoveParameter -contains $prop.Name) { continue } + $CombinedParameters[$prop.Name] = $prop.Value + } + } + + @(foreach ($NowServingCommand in $ServingTheseCommands) { + $nowServingCommandParameters = $NowServingCommand.CouldRun($CombinedParameters) + if ($nowServingCommandParameters) { + . { + & $NowServingCommand @nowServingCommandParameters + trap { + object @{ + PSTypeName = 'Service.Error' + CommandName = $NowServingCommand.Name + RequestParameters = $RequestParameter + Error = $_ + } + } + } + } else { + object @{ + PSTypeName = 'Service.MissingParameter' + CommandName = $NowServingCommand.Name + RequestParameters = $RequestParameter + } + } + }) + } +} diff --git a/Commands/Services/Serve-Command.ps1 b/Commands/Services/Serve-Command.ps1 new file mode 100644 index 000000000..2d4e6171a --- /dev/null +++ b/Commands/Services/Serve-Command.ps1 @@ -0,0 +1,152 @@ +[ValidatePattern('Command')] +param() + + +function Serve.Command { + + <# + .SYNOPSIS + Serves a command. + .DESCRIPTION + Serves a command or pattern of commands. + #> + [ValidatePattern( + ' + / + (?> + $($this.Command) + | + $($this.Command -split "\p{P}" -join "/") + | + $( + $CmdParts = $this.Command -split "\p{P}" + [Array]::Reverse($cmdParts) + $cmdParts -join "/" + ) + ) + /? + ', Options ='IgnoreCase,IgnorePatternWhitespace' + )] + [CmdletBinding(PositionalBinding=$false)] + param( + # The command or pattern of commands that is being served. + [Parameter(Mandatory,ValueFromPipelineByPropertyName)] + [Alias('CommandPattern')] + [PSObject] + $Command, + + # The request object. + # This should generally not be provided, as it will be provided by the server. + # (it can be provided for testing purposes) + [Parameter(ValueFromPipelineByPropertyName)] + [PSObject] + $Request, + + # A collection of parameters to pass to the command. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Parameters')] + [PSObject] + $Parameter, + + # A collection of parameters to pass to the command by default, if no parameter was provided. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('DefaultParameters')] + [PSObject] + $DefaultParameter, + + # One or more parameters to remove. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('HideParameter','DenyParameter','DenyParameters')] + [string[]] + $RemoveParameter, + + # The displayed name of the command + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Title','DisplayName')] + [string] + $Name + ) + + process { + $ServingTheseCommands = + if ($Command -is [string]) { + $ExecutionContext.SessionState.InvokeCommand.GetCommands('*', 'Function,Alias,Cmdlet', $true) -match $Command + } elseif ($Command -is [Management.Automation.CommandInfo]) { + $Command + } elseif ($Command -is [ScriptBlock]) { + $function:InnerServiceFunction = $Command + $ExecutionContext.SessionState.InvokeCommand.GetCommand('InnerServiceFunction','Function') + $Command + } + + if (-not $ServingTheseCommands) { return } + + $CombinedParameters = [Ordered]@{} + + if ($RemoveParameter) { + foreach ($prop in $RemoveParameter) { + $CombinedParameters.Remove($prop) + } + } + + if ($DefaultParameter) { + if ($DefaultParameter -is [Collections.IDictionary]) { + $DefaultParameter = [PSCustomObject]$DefaultParameter + } + foreach ($prop in $DefaultParameter.psobject.properties) { + $CombinedParameters[$prop.Name] = $prop.Value + } + } + + if ($Parameter) { + if ($Parameter -is [Collections.IDictionary]) { + $Parameter = [PSCustomObject]$Parameter + } + foreach ($prop in $Parameter.psobject.properties) { + $CombinedParameters[$prop.Name] = $prop.Value + } + } + + + if ($Request) { + $requestParameter = if ($Request.Params) { + $Request.Params + } elseif ($Request.Body) { + $Request.Body + } + if ($RequestParameter -is [Collections.IDictionary]) { + $RequestParameter = [PSCustomObject]$RequestParameter + } + foreach ($prop in $RequestParameter.psobject.properties) { + if ($RemoveParameter -contains $prop.Name) { continue } + $CombinedParameters[$prop.Name] = $prop.Value + } + } + + @(foreach ($NowServingCommand in $ServingTheseCommands) { + $nowServingCommandParameters = $NowServingCommand.CouldRun($CombinedParameters) + if ($nowServingCommandParameters) { + . { + & $NowServingCommand @nowServingCommandParameters + trap { + [PSCustomObject][Ordered]@{ + PSTypeName = 'Service.Error' + CommandName = $NowServingCommand.Name + RequestParameters = $RequestParameter + Error = $_ + } + } + } + } else { + [PSCustomObject][Ordered]@{ + PSTypeName = 'Service.MissingParameter' + CommandName = $NowServingCommand.Name + RequestParameters = $RequestParameter + } + } + }) + } + +} + + diff --git a/Commands/Services/Serve-Module.ps.ps1 b/Commands/Services/Serve-Module.ps.ps1 new file mode 100644 index 000000000..5dd1089b0 --- /dev/null +++ b/Commands/Services/Serve-Module.ps.ps1 @@ -0,0 +1,121 @@ +[ValidatePattern('Module')] +param() + +Serve function Module { + <# + .SYNOPSIS + Serves a Module + .DESCRIPTION + Services a request to a module. + + This should first attempt to call any of the module's routes, + Then attempt to find an appropriate topic. + Then return the default module topic. + .NOTES + This serves a module and it's associated services. + + Services are defined in the module's manifest, under .Service(s). + + ### Debugging Serve.Module + + To debug Serve.Module, pass it any loaded module, and a "mocked" request object. + + PipeScript will never strongly bind $Request, so that it can be defined by any web host. + #> + param( + # The module being served + [vfp(Mandatory)] + [psmoduleinfo] + $Module, + + # The request object. + # This should generally not be provided, as it will be provided by the server. + # (it can be provided for testing purposes) + [vbn()] + [PSObject] + $Request + ) + + process { + $nowServingModule = $Module + + $NowServingModuleRoutes = $NowServingModule.Route + $UrlSegments = @($Request.Url.Segments -replace '/$' -ne '') + $MyParameters = [Ordered]@{} + $PSBoundParameters + $AdditionalContext = @() + + $NowServingModuleOutput = . { + $ServedARoute = $false + :ServicingRoutes foreach ($nowServingRoute in $NowServingModuleRoutes) { + $ShouldServeThisUrl = $nowServingRoute.ForUrl($request.Url) + if ($ShouldServeThisUrl) { + $ServedARoute = $true + } + if ($ShouldServeThisUrl -is [ScriptBlock]) { + . $ShouldServeThisUrl + } elseif ($ShouldServeThisUrl -is [Management.Automation.CommandInfo]) { + . $ShouldServeThisUrl + } elseif ($ShouldServeThisUrl.pstypenames -contains 'PipeScript.Module.Service') { + foreach ($serviceParameterSet in $ShouldServeThisUrl.GetServiceParameters()) { + $serviceCommand = $serviceParameterSet.Command + if ($serviceCommand) { + $AdditionalContext += $serviceParameterSet + $serviceParameterCopy = [Ordered]@{} + $serviceParameterSet + foreach ($parameterName in $MyParameters.Keys) { + if ($serviceCommand.Parameters[$parameterName] -and + -not $serviceParameterCopy[$parameterName] + ) { + $serviceParameterCopy[$parameterName] = $MyParameters[$parameterName] + } + } + . $serviceCommand @serviceParameterCopy + } + } + } + } + + return if $ServedARoute + + $potentialTopicName = $Request.Url.Segments -replace '^/' -ne '' -join ' ' + if ($potentialTopicName) { + $module.Topics.Get($potentialTopicName) + } else { + $module.Topics.Get($module.Name) + } + } + + $nowServingSiteInfo = $nowServingModule.Site + + if ($request.ContentType -and $request.ContentType -ne 'text/html') { + return $NowServingModuleOutput + } + + if ($nowServingSiteInfo) { + $nowServingSite = foreach ($siteInfo in $nowServingSiteInfo) { + if ($request.Url -match "^$([Regex]::Escape($siteInfo.Url))") { + $siteInfo + break + } + } + if (-not $nowServingSite) { + $nowServingSite = $nowServingSiteInfo[0] + } + } + + + if (-not $nowServingSite) { + $nowServingSite = object @{ + PSTypeName = 'PipeScript.Module.Website' + url = $request.Url + } + } + + $AdditionalContext += $nowServingSite + $AdditionalContext += @{content=$NowServingModuleOutput | Out-HTML} + + $nowServingSite.UseLayout( + $nowServingSite.GetDefaultLayout(), + (@($NowServingSite) + $AdditionalContext) + ) + } +} \ No newline at end of file diff --git a/Commands/Services/Serve-Module.ps1 b/Commands/Services/Serve-Module.ps1 new file mode 100644 index 000000000..d3169507e --- /dev/null +++ b/Commands/Services/Serve-Module.ps1 @@ -0,0 +1,125 @@ +[ValidatePattern('Module')] +param() + + +function Serve.Module { + + <# + .SYNOPSIS + Serves a Module + .DESCRIPTION + Services a request to a module. + + This should first attempt to call any of the module's routes, + Then attempt to find an appropriate topic. + Then return the default module topic. + .NOTES + This serves a module and it's associated services. + + Services are defined in the module's manifest, under .Service(s). + + ### Debugging Serve.Module + + To debug Serve.Module, pass it any loaded module, and a "mocked" request object. + + PipeScript will never strongly bind $Request, so that it can be defined by any web host. + #> + param( + # The module being served + [Parameter(Mandatory,ValueFromPipeline)] + [psmoduleinfo] + $Module, + + # The request object. + # This should generally not be provided, as it will be provided by the server. + # (it can be provided for testing purposes) + [Parameter(ValueFromPipelineByPropertyName)] + [PSObject] + $Request + ) + + process { + $nowServingModule = $Module + + $NowServingModuleRoutes = $NowServingModule.Route + $UrlSegments = @($Request.Url.Segments -replace '/$' -ne '') + $MyParameters = [Ordered]@{} + $PSBoundParameters + $AdditionalContext = @() + + $NowServingModuleOutput = . { + $ServedARoute = $false + :ServicingRoutes foreach ($nowServingRoute in $NowServingModuleRoutes) { + $ShouldServeThisUrl = $nowServingRoute.ForUrl($request.Url) + if ($ShouldServeThisUrl) { + $ServedARoute = $true + } + if ($ShouldServeThisUrl -is [ScriptBlock]) { + . $ShouldServeThisUrl + } elseif ($ShouldServeThisUrl -is [Management.Automation.CommandInfo]) { + . $ShouldServeThisUrl + } elseif ($ShouldServeThisUrl.pstypenames -contains 'PipeScript.Module.Service') { + foreach ($serviceParameterSet in $ShouldServeThisUrl.GetServiceParameters()) { + $serviceCommand = $serviceParameterSet.Command + if ($serviceCommand) { + $AdditionalContext += $serviceParameterSet + $serviceParameterCopy = [Ordered]@{} + $serviceParameterSet + foreach ($parameterName in $MyParameters.Keys) { + if ($serviceCommand.Parameters[$parameterName] -and + -not $serviceParameterCopy[$parameterName] + ) { + $serviceParameterCopy[$parameterName] = $MyParameters[$parameterName] + } + } + . $serviceCommand @serviceParameterCopy + } + } + } + } + + if ($ServedARoute) { return } + + $potentialTopicName = $Request.Url.Segments -replace '^/' -ne '' -join ' ' + if ($potentialTopicName) { + $module.Topics.Get($potentialTopicName) + } else { + $module.Topics.Get($module.Name) + } + } + + $nowServingSiteInfo = $nowServingModule.Site + + if ($request.ContentType -and $request.ContentType -ne 'text/html') { + return $NowServingModuleOutput + } + + if ($nowServingSiteInfo) { + $nowServingSite = foreach ($siteInfo in $nowServingSiteInfo) { + if ($request.Url -match "^$([Regex]::Escape($siteInfo.Url))") { + $siteInfo + break + } + } + if (-not $nowServingSite) { + $nowServingSite = $nowServingSiteInfo[0] + } + } + + + if (-not $nowServingSite) { + $nowServingSite = [PSCustomObject][Ordered]@{ + PSTypeName = 'PipeScript.Module.Website' + url = $request.Url + } + } + + $AdditionalContext += $nowServingSite + $AdditionalContext += @{content=$NowServingModuleOutput | Out-HTML} + + $nowServingSite.UseLayout( + $nowServingSite.GetDefaultLayout(), + (@($NowServingSite) + $AdditionalContext) + ) + } + +} + diff --git a/Commands/Services/Serve-Variable.ps.ps1 b/Commands/Services/Serve-Variable.ps.ps1 new file mode 100644 index 000000000..439b83848 --- /dev/null +++ b/Commands/Services/Serve-Variable.ps.ps1 @@ -0,0 +1,51 @@ +[ValidatePattern('Variable')] +param() + +Serve function Variable { + <# + .SYNOPSIS + Serves variables. + .DESCRIPTION + Serves variables from a module. + .NOTES + This service allows select variables to be served from a module. + + It does not allow for patterns of variables to be served. + + Additionally, it will not allow the variable to be changed (only viewed) + #> + [ValidatePattern( + '/$($this.Variable -replace "^","\`$?" -join "|")/?', Options ='IgnoreCase,IgnorePatternWhitespace' + )] + param( + # The list of variables to serve + [vfp(Mandatory)] + [PSObject[]] + $Variable, + + # The module containing the variables. + [vbn()] + [PSObject] + $Module, + + # The request object. + # This should generally not be provided, as it will be provided by the server. + # (it can be provided for testing purposes) + [vbn()] + [PSObject] + $Request + ) + + process { + return if -not $Module + return if -not $Request + + $segments = @($Request.Url.Segments -replace '^/' -replace '/$' -ne '') + $variableName = $segments[0] -replace '^\$' + $gotVariable = $module.SessionState.PSVariable.Get($variableName) + + return if -not $gotVariable + + $gotVariable.Value + } +} diff --git a/Commands/Services/Serve-Variable.ps1 b/Commands/Services/Serve-Variable.ps1 new file mode 100644 index 000000000..fa2563f59 --- /dev/null +++ b/Commands/Services/Serve-Variable.ps1 @@ -0,0 +1,56 @@ +[ValidatePattern('Variable')] +param() + + +function Serve.Variable { + + <# + .SYNOPSIS + Serves variables. + .DESCRIPTION + Serves variables from a module. + .NOTES + This service allows select variables to be served from a module. + + It does not allow for patterns of variables to be served. + + Additionally, it will not allow the variable to be changed (only viewed) + #> + [ValidatePattern( + '/$($this.Variable -replace "^","\`$?" -join "|")/?', Options ='IgnoreCase,IgnorePatternWhitespace' + )] + param( + # The list of variables to serve + [Parameter(Mandatory,ValueFromPipeline)] + [PSObject[]] + $Variable, + + # The module containing the variables. + [Parameter(ValueFromPipelineByPropertyName)] + [PSObject] + $Module, + + # The request object. + # This should generally not be provided, as it will be provided by the server. + # (it can be provided for testing purposes) + [Parameter(ValueFromPipelineByPropertyName)] + [PSObject] + $Request + ) + + process { + if (-not $Module) { return } + if (-not $Request) { return } + + $segments = @($Request.Url.Segments -replace '^/' -replace '/$' -ne '') + $variableName = $segments[0] -replace '^\$' + $gotVariable = $module.SessionState.PSVariable.Get($variableName) + + if (-not $gotVariable) { return } + + $gotVariable.Value + } + +} + + diff --git a/Commands/Signals/Null-Signal.ps.ps1 b/Commands/Signals/Null-Signal.ps.ps1 index bacd68508..5d8bf03a7 100644 --- a/Commands/Signals/Null-Signal.ps.ps1 +++ b/Commands/Signals/Null-Signal.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Signal")] +param() + Signal function Nothing { <# .SYNOPSIS diff --git a/Commands/Signals/Out-Signal.ps.ps1 b/Commands/Signals/Out-Signal.ps.ps1 index aa3f1f99f..eb2a54ab3 100644 --- a/Commands/Signals/Out-Signal.ps.ps1 +++ b/Commands/Signals/Out-Signal.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Signal")] +param() + Signal function Out { <# .SYNOPSIS diff --git a/Commands/Start-PSNode.ps.ps1 b/Commands/Start-PSNode.ps.ps1 index 2ce2ad2c8..e3a6b1e5f 100644 --- a/Commands/Start-PSNode.ps.ps1 +++ b/Commands/Start-PSNode.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern('psnode')] +param() + function Start-PSNode { <# .SYNOPSIS @@ -15,78 +18,88 @@ function Start-PSNode { $Command, # The name of the server, or the route that is being served. - [Parameter(ValueFromPipelineByPropertyName)] + [vbn()] [Alias('Route','Host','HostHeader')] [String[]] $Server, # The cross origin resource sharing - [Parameter(ValueFromPipelineByPropertyName)] + [vbn()] [Alias('AccessControlAllowOrigin','Access-Control-Allow-Origin')] [string] $CORS = '*', # The root directory. If this is provided, the PSNode will act as a file server for this location. - [Parameter(ValueFromPipelineByPropertyName)] + [vbn()] [string] $RootPath, + # The lifespan of the job. If this is provided, the job will automatically stop after this time. + [vbn()] + [Timespan] + $Lifespan, + # The buffer size. If PSNode is acting as a file server, this is the size of the buffer that it will use to stream files. - [Parameter(ValueFromPipelineByPropertyName)] + [vbn()] [Uint32] $BufferSize = 512kb, # The number of runspaces in the PSNode's runspace pool. # As the PoolSize increases, the PSNode will be able to handle more concurrent requests and will consume more memory. - [Parameter(ValueFromPipelineByPropertyName)] + [vbn()] [Uint32] $PoolSize = 3, + # The maximum age to cache returned files. By default, 7 days. + [vbn()] + [Uint32] + $MaxAge = 604800, + # The user session timeout. By default, 15 minutes. - [Parameter(ValueFromPipelineByPropertyName)] + [vbn()] [TimeSpan]$SessionTimeout, # The modules that will be loaded in the PSNode. - [Parameter(ValueFromPipelineByPropertyName)] + [vbn()] [string[]] $ImportModule, # The functions that will be loaded in the PSNode. - [Parameter(ValueFromPipelineByPropertyName)] + [vbn()] [Alias('Functions','Function')] [Management.Automation.FunctionInfo[]] $DeclareFunction, # The aliases that will be loaded in the PSNode. - [Parameter(ValueFromPipelineByPropertyName)] + [vbn()] [Alias('Alias', 'Aliases')] [Management.Automation.AliasInfo[]] $DeclareAlias, # Any additional types.ps1xml files to load in the PSNode. - [Parameter(ValueFromPipelineByPropertyName)] + [vbn()] [Alias('ImportTypesFile', 'ImportTypeFiles','ImportTypesFiles')] [string[]] $ImportTypeFile, # Any additional format.ps1xml files to load in the PSNode. - [Parameter(ValueFromPipelineByPropertyName)] + [vbn()] [Alias('ImportFormatsFile', 'ImportFormatFiles','ImportFormatsFiles')] [string[]] $ImportFormatFile, # If set, will allow the directories beneath RootPath to be browsed. - [Parameter(ValueFromPipelineByPropertyName)] + [vbn()] [Switch] $AllowBrowseDirectory, # If set, will execute .ps1 files located beneath the RootPath. If this is not provided, these .PS1 files will be displayed in the browser like any other file (assuming you provided a RootPath) - [Parameter(ValueFromPipelineByPropertyName)] + [vbn()] [Switch] - $AllowScriptExecution, + $AllowScriptExecution, # The authentication type - [Parameter(ValueFromPipelineByPropertyName=$true)] + [vbn()] [Net.AuthenticationSchemes] $AuthenticationType = "Anonymous" ) @@ -243,8 +256,9 @@ Add-Member -InputObject $request -MemberType ScriptProperty -Name Params -Value } $sourceCode = $sourceCode.Replace("<#ScriptPreface#>", $scriptPreface.ToString().Replace('"','""')) + $addedType = if ($PSVersionTable.Platform -eq 'Unix') { - + $linuxRefs = "System.Web",([IO.Path].Assembly),([PSObject].Assembly), ([Net.HttpListener].Assembly), ([IO.FileInfo].Assembly), @@ -254,11 +268,17 @@ Add-Member -InputObject $request -MemberType ScriptProperty -Name Params -Value ([Timers.Timer].Assembly), 'System.ComponentModel.Primitives', ([Collections.Specialized.NameValueCollection].Assembly), ([Regex].Assembly),[Net.WebHeaderCollection].Assembly - $linuxRefs += [PSObject].Assembly.GetReferencedAssemblies() + $linuxRefs += [PSObject].Assembly.GetReferencedAssemblies() + $compilerParams = "-r:$([PSObject].Assembly.Location)", "-r:$([Hashtable].Assembly.Location)" Add-Type -TypeDefinition $sourceCode -ReferencedAssemblies $linuxRefs -IgnoreWarnings -CompilerOptions $compilerParams -PassThru - } else { + } + elseif ($PSVersionTable.PSEdition -eq 'Desktop') { + Add-type -AssemblyName System.Web + Add-Type -TypeDefinition $sourceCode -ReferencedAssemblies System.Web -IgnoreWarnings -PassThru + } + else { Add-Type -TypeDefinition $sourceCode -IgnoreWarnings -PassThru } diff --git a/Commands/Start-PSNode.ps1 b/Commands/Start-PSNode.ps1 index f597b3182..fbd2b7c10 100644 --- a/Commands/Start-PSNode.ps1 +++ b/Commands/Start-PSNode.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern('psnode')] +param() + function Start-PSNode { <# @@ -32,6 +35,11 @@ function Start-PSNode { [string] $RootPath, + # The lifespan of the job. If this is provided, the job will automatically stop after this time. + [Parameter(ValueFromPipelineByPropertyName)] + [Timespan] + $Lifespan, + # The buffer size. If PSNode is acting as a file server, this is the size of the buffer that it will use to stream files. [Parameter(ValueFromPipelineByPropertyName)] [Uint32] @@ -43,6 +51,11 @@ function Start-PSNode { [Uint32] $PoolSize = 3, + # The maximum age to cache returned files. By default, 7 days. + [Parameter(ValueFromPipelineByPropertyName)] + [Uint32] + $MaxAge = 604800, + # The user session timeout. By default, 15 minutes. [Parameter(ValueFromPipelineByPropertyName)] [TimeSpan]$SessionTimeout, @@ -84,10 +97,10 @@ function Start-PSNode { # If set, will execute .ps1 files located beneath the RootPath. If this is not provided, these .PS1 files will be displayed in the browser like any other file (assuming you provided a RootPath) [Parameter(ValueFromPipelineByPropertyName)] [Switch] - $AllowScriptExecution, + $AllowScriptExecution, # The authentication type - [Parameter(ValueFromPipelineByPropertyName=$true)] + [Parameter(ValueFromPipelineByPropertyName)] [Net.AuthenticationSchemes] $AuthenticationType = "Anonymous" ) @@ -244,8 +257,9 @@ Add-Member -InputObject $request -MemberType ScriptProperty -Name Params -Value } $sourceCode = $sourceCode.Replace("<#ScriptPreface#>", $scriptPreface.ToString().Replace('"','""')) + $addedType = if ($PSVersionTable.Platform -eq 'Unix') { - + $linuxRefs = "System.Web",([IO.Path].Assembly),([PSObject].Assembly), ([Net.HttpListener].Assembly), ([IO.FileInfo].Assembly), @@ -255,11 +269,17 @@ Add-Member -InputObject $request -MemberType ScriptProperty -Name Params -Value ([Timers.Timer].Assembly), 'System.ComponentModel.Primitives', ([Collections.Specialized.NameValueCollection].Assembly), ([Regex].Assembly),[Net.WebHeaderCollection].Assembly - $linuxRefs += [PSObject].Assembly.GetReferencedAssemblies() + $linuxRefs += [PSObject].Assembly.GetReferencedAssemblies() + $compilerParams = "-r:$([PSObject].Assembly.Location)", "-r:$([Hashtable].Assembly.Location)" Add-Type -TypeDefinition $sourceCode -ReferencedAssemblies $linuxRefs -IgnoreWarnings -CompilerOptions $compilerParams -PassThru - } else { + } + elseif ($PSVersionTable.PSEdition -eq 'Desktop') { + Add-type -AssemblyName System.Web + Add-Type -TypeDefinition $sourceCode -ReferencedAssemblies System.Web -IgnoreWarnings -PassThru + } + else { Add-Type -TypeDefinition $sourceCode -IgnoreWarnings -PassThru } diff --git a/Dockerfile b/Dockerfile index 0cf65bbc2..eb11a8548 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,17 +5,19 @@ FROM mcr.microsoft.com/powershell ENV PIPESCRIPT_VERSION 0.2.8 -RUN apt-get update && apt-get install -y git curl ca-certificates libc6 libgcc1 +RUN apt-get update && apt-get install -y git curl ca-certificates libc6 libgcc1 liblttng-ust1 libstdc++6 libunwind8 zlib1g build-essential libgdiplus golang python3 nodejs dotnet-sdk-8.0 && apt-get clean -ENV PSModulePath ./Modules +COPY ./ ./usr/local/share/powershell/Modules/PipeScript -RUN opt/microsoft/powershell/7/pwsh --noprofile --nologo -c Install-Module 'Splatter','PSSVG','ugit','Irregular' -Scope CurrentUser -Force +SHELL ["pwsh", "-noprofile", "-nologo", "-command"] -COPY ./ ./Modules/PipeScript +RUN @(Install-Module 'Splatter','PSSVG','ugit','Irregular' -AcceptLicense -Scope CurrentUser -Force && New-Item -ItemType File -Path \$Profile -Force | Add-Content -Value "Import-Module 'Splatter','PSSVG','ugit','Irregular','PipeScript'") -COPY ././Http.Server.Start.ps1 /root/.config/powershell/Microsoft.PowerShell_profile.ps1 +COPY ./Http.Server.Start.ps1 ./Http.Server.Start.ps1 +RUN Add-Content -Path \$Profile -Value ./Http.Server.Start.ps1 +ENTRYPOINT ["pwsh","-nologo"] diff --git a/Http.Server.Start.ps1 b/Http.Server.Start.ps1 index 58a49cc91..5f274d870 100644 --- a/Http.Server.Start.ps1 +++ b/Http.Server.Start.ps1 @@ -10,8 +10,7 @@ #> param() Push-Location $PSScriptRoot -$ImportedModules = Get-ChildItem -Path /Modules -Directory | - ForEach-Object { Import-Module $_.FullName -Global -Force -PassThru } +$ImportedModules = Get-Module # Requests can be routed by piping into PSNode, which will start multiple PSNode jobs. @@ -24,8 +23,6 @@ $ImportedModules = Get-ChildItem -Path /Modules -Directory | Command = { $Url = $request.Url - - # Of course, a job can have modules loaded, and $NowServingModule = $null # all loaded modules can have one or more .Server(s) associated with them. @@ -43,13 +40,12 @@ $ImportedModules = Get-ChildItem -Path /Modules -Directory | } if ($NowServingModule) { - # If a module has a [ScriptBlock] value in it's server list, we can use this to respond - # (manifests cannot declare a [ScriptBlock], it would have to be added during or after module load) - foreach ($moduleServer in $NowServingModule) { - if ($moduleServer -is [ScriptBlock]) { - return . $moduleServer - } + $moduleServed = Serve.Module -Module $NowServingModule -Request $request + if ($moduleServed) { + return $moduleServed } + + # We can also serve up module responses using events. @@ -79,7 +75,10 @@ $ImportedModules = Get-ChildItem -Path /Modules -Directory | # $ServerEvent $responseEvents | Remove-Event -ErrorAction Ignore # Remove the event to reclaim memory and keep things safe. - return $nowServingResponse # return whatever response we got (or nothing) + if ($nowServingResponse) { + return $nowServingResponse + } + # return $nowServingResponse # return whatever response we got (or nothing) } @@ -115,12 +114,14 @@ $ImportedModules = Get-ChildItem -Path /Modules -Directory | if ($mappedRoute) { . $mappedRoute # run that } else { + Serve.Module -Module $PipeScript -Request $request + return # Otherwise, show the PipeScript logo Get-Content ( Get-Module PipeScript | Split-Path | Join-Path -ChildPath Assets | - Join-Path -ChildPath PipeScript-ouroborus-animated.svg + Join-Path -ChildPath PipeScript-ouroboros-animated.svg ) -Raw } } diff --git a/Languages/ADA/ADA-Language.ps.ps1 b/Languages/ADA/ADA-Language.ps.ps1 index 0baa074f3..d36a81d81 100644 --- a/Languages/ADA/ADA-Language.ps.ps1 +++ b/Languages/ADA/ADA-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>ADA|Language)")] +param() + Language function ADA { <# .SYNOPSIS diff --git a/Languages/ADA/ADA-Language.ps1 b/Languages/ADA/ADA-Language.ps1 index 224cee4b7..182aa0004 100644 --- a/Languages/ADA/ADA-Language.ps1 +++ b/Languages/ADA/ADA-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>ADA|Language)")] +param() + function Language.ADA { <# diff --git a/Languages/ATOM/ATOM-Language.ps.ps1 b/Languages/ATOM/ATOM-Language.ps.ps1 index 289bbcec2..21b908e14 100644 --- a/Languages/ATOM/ATOM-Language.ps.ps1 +++ b/Languages/ATOM/ATOM-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>ATOM|Language)")] +param() + Language function ATOM { <# .SYNOPSIS diff --git a/Languages/ATOM/ATOM-Language.ps1 b/Languages/ATOM/ATOM-Language.ps1 index e7b5aab01..65dd85d00 100644 --- a/Languages/ATOM/ATOM-Language.ps1 +++ b/Languages/ATOM/ATOM-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>ATOM|Language)")] +param() + function Language.ATOM { <# diff --git a/Languages/Arduino/Arduino-Language.ps.ps1 b/Languages/Arduino/Arduino-Language.ps.ps1 index a7b7577b6..dbe217ad8 100644 --- a/Languages/Arduino/Arduino-Language.ps.ps1 +++ b/Languages/Arduino/Arduino-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Arduino|Language)")] +param() + Language function Arduino { <# .SYNOPSIS diff --git a/Languages/Arduino/Arduino-Language.ps1 b/Languages/Arduino/Arduino-Language.ps1 index f2407e499..c15d36587 100644 --- a/Languages/Arduino/Arduino-Language.ps1 +++ b/Languages/Arduino/Arduino-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Arduino|Language)")] +param() + function Language.Arduino { <# diff --git a/Languages/BASIC/BASIC-Language.ps.ps1 b/Languages/BASIC/BASIC-Language.ps.ps1 index 830f2cc82..4211f5ef3 100644 --- a/Languages/BASIC/BASIC-Language.ps.ps1 +++ b/Languages/BASIC/BASIC-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Basic|Language)")] +param() + Language function BASIC { <# .SYNOPSIS diff --git a/Languages/BASIC/BASIC-Language.ps1 b/Languages/BASIC/BASIC-Language.ps1 index e5ffc7055..5119cefed 100644 --- a/Languages/BASIC/BASIC-Language.ps1 +++ b/Languages/BASIC/BASIC-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Basic|Language)")] +param() + function Language.BASIC { <# diff --git a/Languages/Bash/Bash-Language.ps.ps1 b/Languages/Bash/Bash-Language.ps.ps1 index 1b11e7e0e..0ee363ddc 100644 --- a/Languages/Bash/Bash-Language.ps.ps1 +++ b/Languages/Bash/Bash-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Bash|Language)")] +param() + Language function Bash { <# .SYNOPSIS @@ -43,6 +46,9 @@ Language function Bash { $EndPattern = "(?${endComment})" + # One or more wrappers can be used to create a wrapper of a PowerShell script. + $Wrapper = 'Template.Bash.Wrapper' + $Interpreter = $ExecutionContext.SessionState.InvokeCommand.GetCommand('bash','Application') } diff --git a/Languages/Bash/Bash-Language.ps1 b/Languages/Bash/Bash-Language.ps1 index 0f07afa60..d889062a5 100644 --- a/Languages/Bash/Bash-Language.ps1 +++ b/Languages/Bash/Bash-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Bash|Language)")] +param() + function Language.Bash { <# @@ -48,6 +51,9 @@ $languageDefinition = New-Module { $EndPattern = "(?${endComment})" + # One or more wrappers can be used to create a wrapper of a PowerShell script. + $Wrapper = 'Template.Bash.Wrapper' + $Interpreter = $ExecutionContext.SessionState.InvokeCommand.GetCommand('bash','Application') $LanguageName = 'Bash' Export-ModuleMember -Variable * -Function * -Alias * diff --git a/Languages/Bash/Templates/Bash-Template-Wrapper.ps.ps1 b/Languages/Bash/Templates/Bash-Template-Wrapper.ps.ps1 new file mode 100644 index 000000000..7a12551dc --- /dev/null +++ b/Languages/Bash/Templates/Bash-Template-Wrapper.ps.ps1 @@ -0,0 +1,42 @@ +[ValidatePattern('Bash')] +param() + +Template function Bash.Wrapper { + <# + .Synopsis + Wraps PowerShell in a Bash Script + .Description + Wraps PowerShell in a Bash Script + #> + [Alias('Bash')] + param( + [Parameter(Mandatory,ValueFromPipeline,ParameterSetName='ScriptInfo')] + [Management.Automation.ExternalScriptInfo] + $ScriptInfo, + + [Parameter(Mandatory,ValueFromPipeline,ParameterSetName='ScriptBlock')] + [ScriptBlock] + $ScriptBlock + ) + + process { + switch ($PSCmdlet.ParameterSetName) + { + ScriptBlock { +@" +#!/usr/bin/env bash +pwsh -noprofile -nologo -command "& {$($ScriptBlock -replace '"', '\"' -replace '\$', '\$')} $@ ; if (\`$error.Count) { exit 1}" +"@ + + } + ScriptInfo { + @" +#!/usr/bin/env bash +pwsh -noprofile -nologo -command "& './$($ScriptInfo.Name)' $@ ; if (\`$error.Count) { exit 1}" +"@ + + } + + } + } +} \ No newline at end of file diff --git a/Languages/Bash/Templates/Bash-Template-Wrapper.ps1 b/Languages/Bash/Templates/Bash-Template-Wrapper.ps1 new file mode 100644 index 000000000..bc2a1e3e2 --- /dev/null +++ b/Languages/Bash/Templates/Bash-Template-Wrapper.ps1 @@ -0,0 +1,46 @@ +[ValidatePattern('Bash')] +param() + + +function Template.Bash.Wrapper { + + <# + .Synopsis + Wraps PowerShell in a Bash Script + .Description + Wraps PowerShell in a Bash Script + #> + [Alias('Bash')] + param( + [Parameter(Mandatory,ValueFromPipeline,ParameterSetName='ScriptInfo')] + [Management.Automation.ExternalScriptInfo] + $ScriptInfo, + + [Parameter(Mandatory,ValueFromPipeline,ParameterSetName='ScriptBlock')] + [ScriptBlock] + $ScriptBlock + ) + + process { + switch ($PSCmdlet.ParameterSetName) + { + ScriptBlock { +@" +#!/usr/bin/env bash +pwsh -noprofile -nologo -command "& {$($ScriptBlock -replace '"', '\"' -replace '\$', '\$')} $@ ; if (\`$error.Count) { exit 1}" +"@ + + } + ScriptInfo { + @" +#!/usr/bin/env bash +pwsh -noprofile -nologo -command "& './$($ScriptInfo.Name)' $@ ; if (\`$error.Count) { exit 1}" +"@ + + } + + } + } + +} + diff --git a/Languages/Batch/Batch-Language.ps.ps1 b/Languages/Batch/Batch-Language.ps.ps1 index 9844a6994..7c3b642af 100644 --- a/Languages/Batch/Batch-Language.ps.ps1 +++ b/Languages/Batch/Batch-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Batch|Language)")] +param() + Language function Batch { <# .SYNOPSIS @@ -54,6 +57,9 @@ $EndPattern = "(?${endComment})" # Using -LinePattern will skip any inline code not starting with :: or rem. $LinePattern = "^\s{0,}(?>\:\:|rem)\s{0,}" +# One or more wrappers can be used to create a wrapper of a PowerShell script. +$Wrapper = 'Template.Batch.Wrapper' + # If we're on windows, we can run cmd as the batch interpreter $interpreter = if ($IsWindows) { @($ExecutionContext.SessionState.InvokeCommand.GetCommand('cmd', 'Application'))[0], "/c" diff --git a/Languages/Batch/Batch-Language.ps1 b/Languages/Batch/Batch-Language.ps1 index 234f1427a..4403557f6 100644 --- a/Languages/Batch/Batch-Language.ps1 +++ b/Languages/Batch/Batch-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Batch|Language)")] +param() + function Language.Batch { <# @@ -59,6 +62,9 @@ $EndPattern = "(?${endComment})" # Using -LinePattern will skip any inline code not starting with :: or rem. $LinePattern = "^\s{0,}(?>\:\:|rem)\s{0,}" +# One or more wrappers can be used to create a wrapper of a PowerShell script. +$Wrapper = 'Template.Batch.Wrapper' + # If we're on windows, we can run cmd as the batch interpreter $interpreter = if ($IsWindows) { @($ExecutionContext.SessionState.InvokeCommand.GetCommand('cmd', 'Application'))[0], "/c" diff --git a/Languages/Batch/Templates/Batch-Template-Wrapper.ps.ps1 b/Languages/Batch/Templates/Batch-Template-Wrapper.ps.ps1 new file mode 100644 index 000000000..a89853c58 --- /dev/null +++ b/Languages/Batch/Templates/Batch-Template-Wrapper.ps.ps1 @@ -0,0 +1,46 @@ +[ValidatePattern("batch")] +param() + +Template function Batch.Wrapper { + <# + .Synopsis + Wraps PowerShell in a Windows Batch Script + .Description + Wraps PowerShell in a Windows Batch Script + #> + [Alias('Batch')] + param( + [Parameter(Mandatory,ValueFromPipeline,ParameterSetName='ScriptInfo')] + [Management.Automation.ExternalScriptInfo] + $ScriptInfo, + + [Parameter(Mandatory,ValueFromPipeline,ParameterSetName='ScriptBlock')] + [ScriptBlock] + $ScriptBlock, + + # If set, will use Windows PowerShell core (powershell.exe). If not, will use PowerShell Core (pwsh.exe) + [switch] + $WindowsPowerShell + ) + + process { + $powerShellExe = if (-not $WindowsPowerShell) { 'pwsh' } else { 'powershell'} + switch ($PSCmdlet.ParameterSetName) + { + ScriptBlock { +@" +@echo off +$powerShellExe -noprofile -nologo -command "& {$($ScriptBlock -replace '"', '\"')} %* ; if (`$error.Count) { exit 1}" +"@ + + } + ScriptInfo { + @" +@echo off +$powerShellExe -noprofile -nologo -command "& '%~dp0$($ScriptInfo.Name)' %*; if (`$error.Count) { exit 1}" +"@ + } + } + } + +} \ No newline at end of file diff --git a/Languages/Batch/Templates/Batch-Template-Wrapper.ps1 b/Languages/Batch/Templates/Batch-Template-Wrapper.ps1 new file mode 100644 index 000000000..39f710e5d --- /dev/null +++ b/Languages/Batch/Templates/Batch-Template-Wrapper.ps1 @@ -0,0 +1,50 @@ +[ValidatePattern("batch")] +param() + + +function Template.Batch.Wrapper { + + <# + .Synopsis + Wraps PowerShell in a Windows Batch Script + .Description + Wraps PowerShell in a Windows Batch Script + #> + [Alias('Batch')] + param( + [Parameter(Mandatory,ValueFromPipeline,ParameterSetName='ScriptInfo')] + [Management.Automation.ExternalScriptInfo] + $ScriptInfo, + + [Parameter(Mandatory,ValueFromPipeline,ParameterSetName='ScriptBlock')] + [ScriptBlock] + $ScriptBlock, + + # If set, will use Windows PowerShell core (powershell.exe). If not, will use PowerShell Core (pwsh.exe) + [switch] + $WindowsPowerShell + ) + + process { + $powerShellExe = if (-not $WindowsPowerShell) { 'pwsh' } else { 'powershell'} + switch ($PSCmdlet.ParameterSetName) + { + ScriptBlock { +@" +@echo off +$powerShellExe -noprofile -nologo -command "& {$($ScriptBlock -replace '"', '\"')} %* ; if (`$error.Count) { exit 1}" +"@ + + } + ScriptInfo { + @" +@echo off +$powerShellExe -noprofile -nologo -command "& '%~dp0$($ScriptInfo.Name)' %*; if (`$error.Count) { exit 1}" +"@ + } + } + } + + +} + diff --git a/Languages/Bicep/Bicep-Language.ps.ps1 b/Languages/Bicep/Bicep-Language.ps.ps1 index af89a7c6a..d0a530915 100644 --- a/Languages/Bicep/Bicep-Language.ps.ps1 +++ b/Languages/Bicep/Bicep-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Bicep|Language)")] +param() + Language function Bicep { <# .SYNOPSIS diff --git a/Languages/Bicep/Bicep-Language.ps1 b/Languages/Bicep/Bicep-Language.ps1 index f5610b73c..123f80731 100644 --- a/Languages/Bicep/Bicep-Language.ps1 +++ b/Languages/Bicep/Bicep-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Bicep|Language)")] +param() + function Language.Bicep { <# diff --git a/Languages/BrightScript/BrightScript-Language.ps.ps1 b/Languages/BrightScript/BrightScript-Language.ps.ps1 index 48cddb419..4a7fe36f4 100644 --- a/Languages/BrightScript/BrightScript-Language.ps.ps1 +++ b/Languages/BrightScript/BrightScript-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>BrightScript|Language)")] +param() + Language function BrightScript { <# .SYNOPSIS diff --git a/Languages/BrightScript/BrightScript-Language.ps1 b/Languages/BrightScript/BrightScript-Language.ps1 index 2f8a78792..958aaacaa 100644 --- a/Languages/BrightScript/BrightScript-Language.ps1 +++ b/Languages/BrightScript/BrightScript-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>BrightScript|Language)")] +param() + function Language.BrightScript { <# diff --git a/Languages/C/C-Language.ps.ps1 b/Languages/C/C-Language.ps.ps1 index 1fc805c7d..066db2968 100644 --- a/Languages/C/C-Language.ps.ps1 +++ b/Languages/C/C-Language.ps.ps1 @@ -1,3 +1,5 @@ +[ValidatePattern("(?>C|Language)\s")] +param() Language function C { <# .SYNOPSIS diff --git a/Languages/C/C-Language.ps1 b/Languages/C/C-Language.ps1 index 5d4a568ce..769e0720e 100644 --- a/Languages/C/C-Language.ps1 +++ b/Languages/C/C-Language.ps1 @@ -1,3 +1,5 @@ +[ValidatePattern("(?>C|Language)\s")] +param() function Language.C { <# diff --git a/Languages/C/Templates/C-Template-Include.ps.ps1 b/Languages/C/Templates/C-Template-Include.ps.ps1 index 3ecaaca86..3b3555252 100644 --- a/Languages/C/Templates/C-Template-Include.ps.ps1 +++ b/Languages/C/Templates/C-Template-Include.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>C|Language)\s")] +param() + Template function Include.c { <# .SYNOPSIS diff --git a/Languages/C/Templates/C-Template-Include.ps1 b/Languages/C/Templates/C-Template-Include.ps1 index 23b60b892..721077b89 100644 --- a/Languages/C/Templates/C-Template-Include.ps1 +++ b/Languages/C/Templates/C-Template-Include.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>C|Language)\s")] +param() + function Template.Include.c { diff --git a/Languages/C3/C3-Language.ps.ps1 b/Languages/C3/C3-Language.ps.ps1 index fbdb265c9..a45755175 100644 --- a/Languages/C3/C3-Language.ps.ps1 +++ b/Languages/C3/C3-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>C3|Language)\s")] +param() + Language function C3 { <# .SYNOPSIS @@ -20,6 +23,8 @@ Language function C3 { ) $FilePattern = '\.c3$' + $Compiler = 'c3c' + $ProjectURL = 'https://github.com/c3lang/c3c' # We start off by declaring a number of regular expressions: $startComment = '/\*' # * Start Comments ```\*``` @@ -31,6 +36,4 @@ Language function C3 { $StartPattern = "(?${IgnoredContext}${startComment}\{$Whitespace)" # * EndRegex ```$whitespace + '}' + $EndComment + $ignoredContext``` $EndPattern = "(?$Whitespace\}${endComment}\s{0,}${IgnoredContext})" - - $Compiler = 'c3c' } \ No newline at end of file diff --git a/Languages/C3/C3-Language.ps1 b/Languages/C3/C3-Language.ps1 index 7dd8e1db8..ba180b343 100644 --- a/Languages/C3/C3-Language.ps1 +++ b/Languages/C3/C3-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>C3|Language)\s")] +param() + function Language.C3 { <# @@ -25,6 +28,8 @@ $languageDefinition = New-Module { ) $FilePattern = '\.c3$' + $Compiler = 'c3c' + $ProjectURL = 'https://github.com/c3lang/c3c' # We start off by declaring a number of regular expressions: $startComment = '/\*' # * Start Comments ```\*``` @@ -36,8 +41,6 @@ $languageDefinition = New-Module { $StartPattern = "(?${IgnoredContext}${startComment}\{$Whitespace)" # * EndRegex ```$whitespace + '}' + $EndComment + $ignoredContext``` $EndPattern = "(?$Whitespace\}${endComment}\s{0,}${IgnoredContext})" - - $Compiler = 'c3c' $LanguageName = 'C3' Export-ModuleMember -Variable * -Function * -Alias * } -AsCustomObject diff --git a/Languages/CPlusPlus/CPlusPlus-Language.ps.ps1 b/Languages/CPlusPlus/CPlusPlus-Language.ps.ps1 index 62536eae8..8b13c23ba 100644 --- a/Languages/CPlusPlus/CPlusPlus-Language.ps.ps1 +++ b/Languages/CPlusPlus/CPlusPlus-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>C\+\+|CPlusPlus|Language)\s")] +param() + Language function CPlusPlus { <# .SYNOPSIS diff --git a/Languages/CPlusPlus/CPlusPlus-Language.ps1 b/Languages/CPlusPlus/CPlusPlus-Language.ps1 index 3a0eb5dc8..ddf18a1d8 100644 --- a/Languages/CPlusPlus/CPlusPlus-Language.ps1 +++ b/Languages/CPlusPlus/CPlusPlus-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>C\+\+|CPlusPlus|Language)\s")] +param() + function Language.CPlusPlus { <# diff --git a/Languages/CPlusPlus/Templates/CPlusPlus-Template-HelloWorld.ps.ps1 b/Languages/CPlusPlus/Templates/CPlusPlus-Template-HelloWorld.ps.ps1 index 9eb785d11..b8d891791 100644 --- a/Languages/CPlusPlus/Templates/CPlusPlus-Template-HelloWorld.ps.ps1 +++ b/Languages/CPlusPlus/Templates/CPlusPlus-Template-HelloWorld.ps.ps1 @@ -1,3 +1,7 @@ +[ValidatePattern("(?>C\+\+|CPlusPlus)\s")] +param() + + Template function HelloWorld.cpp { <# .SYNOPSIS diff --git a/Languages/CPlusPlus/Templates/CPlusPlus-Template-Include.ps.ps1 b/Languages/CPlusPlus/Templates/CPlusPlus-Template-Include.ps.ps1 index b59b583d7..9d0672a9a 100644 --- a/Languages/CPlusPlus/Templates/CPlusPlus-Template-Include.ps.ps1 +++ b/Languages/CPlusPlus/Templates/CPlusPlus-Template-Include.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>C\+\+|CPlusPlus)\s")] +param() + Template function Include.cpp { <# .SYNOPSIS diff --git a/Languages/CSS/CSS-Language.ps.ps1 b/Languages/CSS/CSS-Language.ps.ps1 index c9ef63e57..684135cda 100644 --- a/Languages/CSS/CSS-Language.ps.ps1 +++ b/Languages/CSS/CSS-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>CSS|Language)\s")] +param() + Language function CSS { <# .SYNOPSIS diff --git a/Languages/CSS/CSS-Language.ps1 b/Languages/CSS/CSS-Language.ps1 index 8d0c22c41..c05d7bcaf 100644 --- a/Languages/CSS/CSS-Language.ps1 +++ b/Languages/CSS/CSS-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>CSS|Language)\s")] +param() + function Language.CSS { <# diff --git a/Languages/CSharp/CSharp-Language.ps.ps1 b/Languages/CSharp/CSharp-Language.ps.ps1 index 4e401f17d..93686244c 100644 --- a/Languages/CSharp/CSharp-Language.ps.ps1 +++ b/Languages/CSharp/CSharp-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>CSharp|C\#|Language)\s")] +param() + Language function CSharp { <# .SYNOPSIS diff --git a/Languages/CSharp/CSharp-Language.ps1 b/Languages/CSharp/CSharp-Language.ps1 index 8ed140cd5..7a665a86f 100644 --- a/Languages/CSharp/CSharp-Language.ps1 +++ b/Languages/CSharp/CSharp-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>CSharp|C\#|Language)\s")] +param() + function Language.CSharp { <# diff --git a/Languages/CSharp/CSharp-Template-Class.ps.ps1 b/Languages/CSharp/Templates/CSharp-Template-Class.ps.ps1 similarity index 95% rename from Languages/CSharp/CSharp-Template-Class.ps.ps1 rename to Languages/CSharp/Templates/CSharp-Template-Class.ps.ps1 index bb5c044c8..c8b05781d 100644 --- a/Languages/CSharp/CSharp-Template-Class.ps.ps1 +++ b/Languages/CSharp/Templates/CSharp-Template-Class.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("CSharp")] +param() + Template function Class.cs { <# .SYNOPSIS diff --git a/Languages/CSharp/CSharp-Template-Class.ps1 b/Languages/CSharp/Templates/CSharp-Template-Class.ps1 similarity index 100% rename from Languages/CSharp/CSharp-Template-Class.ps1 rename to Languages/CSharp/Templates/CSharp-Template-Class.ps1 diff --git a/Languages/CSharp/CSharp-Template-HelloWorld.ps.ps1 b/Languages/CSharp/Templates/CSharp-Template-HelloWorld.ps.ps1 similarity index 94% rename from Languages/CSharp/CSharp-Template-HelloWorld.ps.ps1 rename to Languages/CSharp/Templates/CSharp-Template-HelloWorld.ps.ps1 index f8c8a4596..4fb250933 100644 --- a/Languages/CSharp/CSharp-Template-HelloWorld.ps.ps1 +++ b/Languages/CSharp/Templates/CSharp-Template-HelloWorld.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("CSharp")] +param() + Template function HelloWorld.cs { <# .SYNOPSIS diff --git a/Languages/CSharp/CSharp-Template-HelloWorld.ps1 b/Languages/CSharp/Templates/CSharp-Template-HelloWorld.ps1 similarity index 100% rename from Languages/CSharp/CSharp-Template-HelloWorld.ps1 rename to Languages/CSharp/Templates/CSharp-Template-HelloWorld.ps1 diff --git a/Languages/CSharp/CSharp-Template-Method.ps.ps1 b/Languages/CSharp/Templates/CSharp-Template-Method.ps.ps1 similarity index 96% rename from Languages/CSharp/CSharp-Template-Method.ps.ps1 rename to Languages/CSharp/Templates/CSharp-Template-Method.ps.ps1 index f22a3a207..5c8f1147f 100644 --- a/Languages/CSharp/CSharp-Template-Method.ps.ps1 +++ b/Languages/CSharp/Templates/CSharp-Template-Method.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("CSharp")] +param() + Template function Method.cs { <# .SYNOPSIS diff --git a/Languages/CSharp/CSharp-Template-Method.ps1 b/Languages/CSharp/Templates/CSharp-Template-Method.ps1 similarity index 100% rename from Languages/CSharp/CSharp-Template-Method.ps1 rename to Languages/CSharp/Templates/CSharp-Template-Method.ps1 diff --git a/Languages/CSharp/CSharp-Template-Namespace.ps.ps1 b/Languages/CSharp/Templates/CSharp-Template-Namespace.ps.ps1 similarity index 95% rename from Languages/CSharp/CSharp-Template-Namespace.ps.ps1 rename to Languages/CSharp/Templates/CSharp-Template-Namespace.ps.ps1 index fef130eb7..8dfd8218d 100644 --- a/Languages/CSharp/CSharp-Template-Namespace.ps.ps1 +++ b/Languages/CSharp/Templates/CSharp-Template-Namespace.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("CSharp")] +param() + Template function Namespace.cs { <# .SYNOPSIS diff --git a/Languages/CSharp/CSharp-Template-Namespace.ps1 b/Languages/CSharp/Templates/CSharp-Template-Namespace.ps1 similarity index 100% rename from Languages/CSharp/CSharp-Template-Namespace.ps1 rename to Languages/CSharp/Templates/CSharp-Template-Namespace.ps1 diff --git a/Languages/CSharp/CSharp-Template-Property.ps.ps1 b/Languages/CSharp/Templates/CSharp-Template-Property.ps.ps1 similarity index 96% rename from Languages/CSharp/CSharp-Template-Property.ps.ps1 rename to Languages/CSharp/Templates/CSharp-Template-Property.ps.ps1 index 38148d71b..341cdec83 100644 --- a/Languages/CSharp/CSharp-Template-Property.ps.ps1 +++ b/Languages/CSharp/Templates/CSharp-Template-Property.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("CSharp")] +param() + Template function Property.cs { <# .SYNOPSIS diff --git a/Languages/CSharp/CSharp-Template-Property.ps1 b/Languages/CSharp/Templates/CSharp-Template-Property.ps1 similarity index 100% rename from Languages/CSharp/CSharp-Template-Property.ps1 rename to Languages/CSharp/Templates/CSharp-Template-Property.ps1 diff --git a/Languages/CSharp/CSharp-Template-TryCatch.ps.ps1 b/Languages/CSharp/Templates/CSharp-Template-TryCatch.ps.ps1 similarity index 97% rename from Languages/CSharp/CSharp-Template-TryCatch.ps.ps1 rename to Languages/CSharp/Templates/CSharp-Template-TryCatch.ps.ps1 index 7690b33ac..80ef0ceeb 100644 --- a/Languages/CSharp/CSharp-Template-TryCatch.ps.ps1 +++ b/Languages/CSharp/Templates/CSharp-Template-TryCatch.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("CSharp")] +param() + Template function TryCatch.cs { <# .SYNOPSIS diff --git a/Languages/CSharp/CSharp-Template-TryCatch.ps1 b/Languages/CSharp/Templates/CSharp-Template-TryCatch.ps1 similarity index 100% rename from Languages/CSharp/CSharp-Template-TryCatch.ps1 rename to Languages/CSharp/Templates/CSharp-Template-TryCatch.ps1 diff --git a/Languages/CUDA/Cuda-Language.ps.ps1 b/Languages/CUDA/Cuda-Language.ps.ps1 new file mode 100644 index 000000000..8a87ed0b4 --- /dev/null +++ b/Languages/CUDA/Cuda-Language.ps.ps1 @@ -0,0 +1,42 @@ +[ValidatePattern("(?>Cuda|Language)[\s\p{P}]")] +param() + +Language function Cuda { + <# + .SYNOPSIS + Cuda PipeScript Language Definition. + .DESCRIPTION + Allows PipeScript to generate Cuda. + + Multiline comments with /*{}*/ will be treated as blocks of PipeScript. + + Multiline comments can be preceeded or followed by 'empty' syntax, which will be ignored. + + The Cuda Inline PipeScript Transpiler will consider the following syntax to be empty: + + * ```null``` + * ```""``` + * ```''``` + #> + [ValidatePattern('\.cu$')] + param() + + # Cuda files are named `.cu`. + $FilePattern = '\.cu$' + + # Cuda is case sensitive. + $CaseSensitive = $true + + # We start off by declaring a number of regular expressions: + $startComment = '/\*' # * Start Comments ```\*``` + $endComment = '\*/' # * End Comments ```/*``` + $Whitespace = '[\s\n\r]{0,}' + # * IgnoredContext ```String.empty```, ```null```, blank strings and characters + $IgnoredContext = "(?(?>$("null", '""', "''" -join '|'))\s{0,}){0,1}" + + $StartPattern = "(?${IgnoredContext}${startComment}\{$Whitespace)" + $EndPattern = "(?$Whitespace\}${endComment}\s{0,}${IgnoredContext})" + + # Cuda's compiler is "nvcc" (if found) + $Compiler = 'nvcc' +} \ No newline at end of file diff --git a/Languages/CUDA/Cuda-Language.ps1 b/Languages/CUDA/Cuda-Language.ps1 new file mode 100644 index 000000000..cb901f9f2 --- /dev/null +++ b/Languages/CUDA/Cuda-Language.ps1 @@ -0,0 +1,57 @@ +[ValidatePattern("(?>Cuda|Language)[\s\p{P}]")] +param() + + +function Language.Cuda { +<# + .SYNOPSIS + Cuda PipeScript Language Definition. + .DESCRIPTION + Allows PipeScript to generate Cuda. + + Multiline comments with /*{}*/ will be treated as blocks of PipeScript. + + Multiline comments can be preceeded or followed by 'empty' syntax, which will be ignored. + + The Cuda Inline PipeScript Transpiler will consider the following syntax to be empty: + + * ```null``` + * ```""``` + * ```''``` + #> +[ValidatePattern('\.cu$')] +param() +$this = $myInvocation.MyCommand +if (-not $this.Self) { +$languageDefinition = New-Module { + param() + + # Cuda files are named `.cu`. + $FilePattern = '\.cu$' + + # Cuda is case sensitive. + $CaseSensitive = $true + + # We start off by declaring a number of regular expressions: + $startComment = '/\*' # * Start Comments ```\*``` + $endComment = '\*/' # * End Comments ```/*``` + $Whitespace = '[\s\n\r]{0,}' + # * IgnoredContext ```String.empty```, ```null```, blank strings and characters + $IgnoredContext = "(?(?>$("null", '""', "''" -join '|'))\s{0,}){0,1}" + + $StartPattern = "(?${IgnoredContext}${startComment}\{$Whitespace)" + $EndPattern = "(?$Whitespace\}${endComment}\s{0,}${IgnoredContext})" + + # Cuda's compiler is "nvcc" (if found) + $Compiler = 'nvcc' + $LanguageName = 'Cuda' + Export-ModuleMember -Variable * -Function * -Alias * +} -AsCustomObject +$languageDefinition.pstypenames.clear() +$languageDefinition.pstypenames.add("Language") +$languageDefinition.pstypenames.add("Language.Cuda") +$this.psobject.properties.add([PSNoteProperty]::new('Self',$languageDefinition)) +} +$this.Self +} + diff --git a/Languages/Conf/Conf-Language.ps.ps1 b/Languages/Conf/Conf-Language.ps.ps1 index e0b020ed9..859e996ff 100644 --- a/Languages/Conf/Conf-Language.ps.ps1 +++ b/Languages/Conf/Conf-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Conf|Language)\s")] +param() + Language function Conf { <# .SYNOPSIS diff --git a/Languages/Conf/Conf-Language.ps1 b/Languages/Conf/Conf-Language.ps1 index 4aee9ec60..8199a8f01 100644 --- a/Languages/Conf/Conf-Language.ps1 +++ b/Languages/Conf/Conf-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Conf|Language)\s")] +param() + function Language.Conf { <# diff --git a/Languages/Crystal/Crystal-Language.ps.ps1 b/Languages/Crystal/Crystal-Language.ps.ps1 index 8c59e023e..b4da4e6c1 100644 --- a/Languages/Crystal/Crystal-Language.ps.ps1 +++ b/Languages/Crystal/Crystal-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Crystal|Language)\s")] +param() + Language function Crystal { <# .SYNOPSIS diff --git a/Languages/Crystal/Crystal-Language.ps1 b/Languages/Crystal/Crystal-Language.ps1 index bc1e9600b..b71c678bf 100644 --- a/Languages/Crystal/Crystal-Language.ps1 +++ b/Languages/Crystal/Crystal-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Crystal|Language)\s")] +param() + function Language.Crystal { <# diff --git a/Languages/Crystal/Crystal-Template-HelloWorld.ps.ps1 b/Languages/Crystal/Templates/Crystal-Template-HelloWorld.ps.ps1 similarity index 89% rename from Languages/Crystal/Crystal-Template-HelloWorld.ps.ps1 rename to Languages/Crystal/Templates/Crystal-Template-HelloWorld.ps.ps1 index f642bdd54..fbc49e773 100644 --- a/Languages/Crystal/Crystal-Template-HelloWorld.ps.ps1 +++ b/Languages/Crystal/Templates/Crystal-Template-HelloWorld.ps.ps1 @@ -1,3 +1,5 @@ +[ValidatePattern("Crystal")] +param() Template function HelloWorld.cr { <# .SYNOPSIS diff --git a/Languages/Crystal/Crystal-Template-HelloWorld.ps1 b/Languages/Crystal/Templates/Crystal-Template-HelloWorld.ps1 similarity index 100% rename from Languages/Crystal/Crystal-Template-HelloWorld.ps1 rename to Languages/Crystal/Templates/Crystal-Template-HelloWorld.ps1 diff --git a/Languages/Dart/Dart-Language.ps.ps1 b/Languages/Dart/Dart-Language.ps.ps1 index 7a7b1a5de..b1c66dbe6 100644 --- a/Languages/Dart/Dart-Language.ps.ps1 +++ b/Languages/Dart/Dart-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Dart|Language)\s")] +param() + Language function Dart { <# .SYNOPSIS diff --git a/Languages/Dart/Dart-Language.ps1 b/Languages/Dart/Dart-Language.ps1 index 9502cf981..975cffb7f 100644 --- a/Languages/Dart/Dart-Language.ps1 +++ b/Languages/Dart/Dart-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Dart|Language)\s")] +param() + function Language.Dart { <# diff --git a/Languages/Dart/Templates/Dart-Template-HelloWorld.ps.ps1 b/Languages/Dart/Templates/Dart-Template-HelloWorld.ps.ps1 new file mode 100644 index 000000000..53b986436 --- /dev/null +++ b/Languages/Dart/Templates/Dart-Template-HelloWorld.ps.ps1 @@ -0,0 +1,24 @@ +[ValidatePattern("Dart")] +param() + +Template function HelloWorld.dart { + <# + .SYNOPSIS + Hello World in Dart + .DESCRIPTION + A Template for Hello World, in Dart. + #> + param( + # The message to print. By default, "hello world". + [vbn()] + [string] + $Message = "hello world" + ) + process { +@" +void main() { + print('$Message'); +} +"@ + } +} diff --git a/Languages/Dart/Templates/Dart-Template-HelloWorld.ps1 b/Languages/Dart/Templates/Dart-Template-HelloWorld.ps1 new file mode 100644 index 000000000..817256425 --- /dev/null +++ b/Languages/Dart/Templates/Dart-Template-HelloWorld.ps1 @@ -0,0 +1,29 @@ +[ValidatePattern("Dart")] +param() + + +function Template.HelloWorld.dart { + + <# + .SYNOPSIS + Hello World in Dart + .DESCRIPTION + A Template for Hello World, in Dart. + #> + param( + # The message to print. By default, "hello world". + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Message = "hello world" + ) + process { +@" +void main() { + print('$Message'); +} +"@ + } + +} + + diff --git a/Languages/Docker/Docker-Language.ps.ps1 b/Languages/Docker/Docker-Language.ps.ps1 index 32551dc66..ee5381ab2 100644 --- a/Languages/Docker/Docker-Language.ps.ps1 +++ b/Languages/Docker/Docker-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Docker|Language)\s")] +param() + Language function Docker { <# .SYNOPSIS diff --git a/Languages/Docker/Docker-Language.ps1 b/Languages/Docker/Docker-Language.ps1 index 192390c91..2c1834db0 100644 --- a/Languages/Docker/Docker-Language.ps1 +++ b/Languages/Docker/Docker-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Docker|Language)\s")] +param() + function Language.Docker { <# diff --git a/Languages/Docker/Templates/Docker-Template-InstallModule.ps.ps1 b/Languages/Docker/Templates/Docker-Template-InstallModule.ps.ps1 new file mode 100644 index 000000000..6c548c204 --- /dev/null +++ b/Languages/Docker/Templates/Docker-Template-InstallModule.ps.ps1 @@ -0,0 +1,30 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.InstallModule { + <# + .SYNOPSIS + Template for installing a PowerShell module in a Dockerfile. + .DESCRIPTION + A Template for installing a PowerShell module in a Dockerfile. + #> + param( + # The module to install. + [vbn()] + [string] + $ModuleName, + + # The version of the module to install. + [vbn()] + [Version] + $ModuleVersion + ) + + process { + if ($ModuleVersion) { + "RUN pwsh -Command Install-Module -Name '$($ModuleName -replace "'","''")' -RequiredVersion '$ModuleVersion' -Force -SkipPublisherCheck -AcceptLicense" + } else { + "RUN pwsh -Command Install-Module -Name '$($ModuleName -replace "'","''")' -Force -SkipPublisherCheck -AcceptLicense" + } + } +} diff --git a/Languages/Docker/Templates/Docker-Template-InstallModule.ps1 b/Languages/Docker/Templates/Docker-Template-InstallModule.ps1 new file mode 100644 index 000000000..c3265dae5 --- /dev/null +++ b/Languages/Docker/Templates/Docker-Template-InstallModule.ps1 @@ -0,0 +1,35 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.InstallModule { + + <# + .SYNOPSIS + Template for installing a PowerShell module in a Dockerfile. + .DESCRIPTION + A Template for installing a PowerShell module in a Dockerfile. + #> + param( + # The module to install. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $ModuleName, + + # The version of the module to install. + [Parameter(ValueFromPipelineByPropertyName)] + [Version] + $ModuleVersion + ) + + process { + if ($ModuleVersion) { + "RUN pwsh -Command Install-Module -Name '$($ModuleName -replace "'","''")' -RequiredVersion '$ModuleVersion' -Force -SkipPublisherCheck -AcceptLicense" + } else { + "RUN pwsh -Command Install-Module -Name '$($ModuleName -replace "'","''")' -Force -SkipPublisherCheck -AcceptLicense" + } + } + +} + + diff --git a/Languages/Docker/Templates/Docker-Template-InstallPackage.ps.ps1 b/Languages/Docker/Templates/Docker-Template-InstallPackage.ps.ps1 new file mode 100644 index 000000000..4e06c9e53 --- /dev/null +++ b/Languages/Docker/Templates/Docker-Template-InstallPackage.ps.ps1 @@ -0,0 +1,21 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.InstallPackage { + <# + .SYNOPSIS + Template for installing a package in a Dockerfile. + .DESCRIPTION + A Template for installing a package in a Dockerfile. + #> + param( + # The package to install. + [vbn()] + [string[]] + $PackageName + ) + + process { + "RUN apt-get update && apt-get install -y $($PackageName -join ' ')" + } +} \ No newline at end of file diff --git a/Languages/Docker/Templates/Docker-Template-InstallPackage.ps1 b/Languages/Docker/Templates/Docker-Template-InstallPackage.ps1 new file mode 100644 index 000000000..f74e81926 --- /dev/null +++ b/Languages/Docker/Templates/Docker-Template-InstallPackage.ps1 @@ -0,0 +1,25 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.InstallPackage { + + <# + .SYNOPSIS + Template for installing a package in a Dockerfile. + .DESCRIPTION + A Template for installing a package in a Dockerfile. + #> + param( + # The package to install. + [Parameter(ValueFromPipelineByPropertyName)] + [string[]] + $PackageName + ) + + process { + "RUN apt-get update && apt-get install -y $($PackageName -join ' ')" + } + +} + diff --git a/Languages/Docker/Templates/Docker-Template-LabelModule.ps.ps1 b/Languages/Docker/Templates/Docker-Template-LabelModule.ps.ps1 new file mode 100644 index 000000000..cc9323aa9 --- /dev/null +++ b/Languages/Docker/Templates/Docker-Template-LabelModule.ps.ps1 @@ -0,0 +1,49 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.LabelModule { + <# + .SYNOPSIS + Labels a docker image from a module. + .DESCRIPTION + Applies labels to a docker image from module metadata. + #> + [Alias('Template.Docker.ModuleLabel','Template.Docker.ModuleLabels')] + param( + # The module to label. + [vfp()] + [PSModuleInfo] + $Module + ) + + process { + $this = $theModule = $Module + + if ($this.PrivateData.PSData.ProjectURI) { + Template.Docker.Label -Label "ProjectURI" -Value "$($this.PrivateData.PSData.ProjectURI)" + Template.Docker.Label -Label "org.opencontainers.image.source" -Value "$($this.PrivateData.PSData.ProjectURI)" + } + + if ($this.PrivateData.PSData.LicenseUri) { + Template.Docker.Label -Label "LicenseURI" -Value "$($this.PrivateData.PSData.LicenseUri)" + } + + Template.Docker.Label -Label "Version" -Value "$($theModule.Version)" + Template.Docker.Label -Label "Name" -Value "$($theModule.Name)" + + if ($theModule.Description) { + Template.Docker.Label -Label "Description" -Value "$($theModule.Description)" + Template.Docker.Label -Label "org.opencontainers.image.description" -Value "$($theModule.Description)" + } + + if ($theModule.CompanyName) { + Template.Docker.Label -Label "CompanyName" -Value "$($theModule.CompanyName)" + Template.Docker.Label -Label "org.opencontainers.image.vendor" -Value "$($theModule.CompanyName)" + } + + if ($theModule.Author) { + Template.Docker.Label -Label "Author" -Value "$($theModule.Author)" + Template.Docker.Label -Label "org.opencontainers.image.authors" -Value "$($theModule.Author)" + } + } +} \ No newline at end of file diff --git a/Languages/Docker/Templates/Docker-Template-LabelModule.ps1 b/Languages/Docker/Templates/Docker-Template-LabelModule.ps1 new file mode 100644 index 000000000..548ea467f --- /dev/null +++ b/Languages/Docker/Templates/Docker-Template-LabelModule.ps1 @@ -0,0 +1,53 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.LabelModule { + + <# + .SYNOPSIS + Labels a docker image from a module. + .DESCRIPTION + Applies labels to a docker image from module metadata. + #> + [Alias('Template.Docker.ModuleLabel','Template.Docker.ModuleLabels')] + param( + # The module to label. + [Parameter(ValueFromPipeline)] + [PSModuleInfo] + $Module + ) + + process { + $this = $theModule = $Module + + if ($this.PrivateData.PSData.ProjectURI) { + Template.Docker.Label -Label "ProjectURI" -Value "$($this.PrivateData.PSData.ProjectURI)" + Template.Docker.Label -Label "org.opencontainers.image.source" -Value "$($this.PrivateData.PSData.ProjectURI)" + } + + if ($this.PrivateData.PSData.LicenseUri) { + Template.Docker.Label -Label "LicenseURI" -Value "$($this.PrivateData.PSData.LicenseUri)" + } + + Template.Docker.Label -Label "Version" -Value "$($theModule.Version)" + Template.Docker.Label -Label "Name" -Value "$($theModule.Name)" + + if ($theModule.Description) { + Template.Docker.Label -Label "Description" -Value "$($theModule.Description)" + Template.Docker.Label -Label "org.opencontainers.image.description" -Value "$($theModule.Description)" + } + + if ($theModule.CompanyName) { + Template.Docker.Label -Label "CompanyName" -Value "$($theModule.CompanyName)" + Template.Docker.Label -Label "org.opencontainers.image.vendor" -Value "$($theModule.CompanyName)" + } + + if ($theModule.Author) { + Template.Docker.Label -Label "Author" -Value "$($theModule.Author)" + Template.Docker.Label -Label "org.opencontainers.image.authors" -Value "$($theModule.Author)" + } + } + +} + diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-Add.ps.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-Add.ps.ps1 new file mode 100644 index 000000000..07c886f0c --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-Add.ps.ps1 @@ -0,0 +1,56 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.Add { + <# + .SYNOPSIS + Template for adding files to a Docker image. + .DESCRIPTION + A Template for adding files to a Docker image. + .LINK + https://docs.docker.com/engine/reference/builder/#add + #> + [Alias('Template.Docker.NewItem')] + param( + # The source of the file to add. + [vbn()] + [string] + $Source, + + # The destination of the file to add. + [vbn()] + [string] + $Destination, + + # Keep git directory + [vbn()] + [switch] + $KeepGit, + + # Verify the checksum of the file + [vbn()] + [string] + $Checksum, + + # Change the owner permissions + [Alias('Chown')] + [vbn()] + [string] + $ChangeOwner + ) + + process { + $AddOptions = @( + if ($KeepGit) { + "--keep-git-dir=true" + } + if ($Checksum) { + "--checksum=$Checksum" + } + if ($ChangeOwner) { + "--chown=$ChangeOwner" + } + ) -join ' ' + "ADD $addOptions $Source $Destination" -replace '\s{2,}', ' ' + } +} \ No newline at end of file diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-Add.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-Add.ps1 new file mode 100644 index 000000000..d3e1d2d9e --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-Add.ps1 @@ -0,0 +1,60 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.Add { + + <# + .SYNOPSIS + Template for adding files to a Docker image. + .DESCRIPTION + A Template for adding files to a Docker image. + .LINK + https://docs.docker.com/engine/reference/builder/#add + #> + [Alias('Template.Docker.NewItem')] + param( + # The source of the file to add. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Source, + + # The destination of the file to add. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Destination, + + # Keep git directory + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $KeepGit, + + # Verify the checksum of the file + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Checksum, + + # Change the owner permissions + [Alias('Chown')] + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $ChangeOwner + ) + + process { + $AddOptions = @( + if ($KeepGit) { + "--keep-git-dir=true" + } + if ($Checksum) { + "--checksum=$Checksum" + } + if ($ChangeOwner) { + "--chown=$ChangeOwner" + } + ) -join ' ' + "ADD $addOptions $Source $Destination" -replace '\s{2,}', ' ' + } + +} + diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-Argument.ps.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-Argument.ps.ps1 new file mode 100644 index 000000000..4ecbcbf5b --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-Argument.ps.ps1 @@ -0,0 +1,33 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.Argument { + <# + .SYNOPSIS + Template for an argument in a Dockerfile. + .DESCRIPTION + A Template for an argument in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#arg + #> + [Alias('Template.Docker.Arg')] + param( + # The name of the argument. + [vbn()] + [string] + $Name, + + # The default value of the argument. + [vbn()] + [string] + $DefaultValue + ) + + process { + if ($DefaultValue) { + "ARG $Name=$DefaultValue" + } else { + "ARG $Name" + } + } +} \ No newline at end of file diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-Argument.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-Argument.ps1 new file mode 100644 index 000000000..430ee647d --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-Argument.ps1 @@ -0,0 +1,37 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.Argument { + + <# + .SYNOPSIS + Template for an argument in a Dockerfile. + .DESCRIPTION + A Template for an argument in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#arg + #> + [Alias('Template.Docker.Arg')] + param( + # The name of the argument. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Name, + + # The default value of the argument. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $DefaultValue + ) + + process { + if ($DefaultValue) { + "ARG $Name=$DefaultValue" + } else { + "ARG $Name" + } + } + +} + diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-Command.ps.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-Command.ps.ps1 new file mode 100644 index 000000000..4d95feb78 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-Command.ps.ps1 @@ -0,0 +1,25 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.Command { + <# + .SYNOPSIS + Template for running a command in a Dockerfile. + .DESCRIPTION + The CMD instruction sets the command to be executed when running a container from an image. + + There can only be one command. If you list more than one command, only the last one will take effect. + .LINK + https://docs.docker.com/engine/reference/builder/#cmd + #> + param( + # The command to run. + [vbn()] + [string] + $Command + ) + + process { + "CMD $Command" + } +} \ No newline at end of file diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-Command.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-Command.ps1 new file mode 100644 index 000000000..f4dc26115 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-Command.ps1 @@ -0,0 +1,29 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.Command { + + <# + .SYNOPSIS + Template for running a command in a Dockerfile. + .DESCRIPTION + The CMD instruction sets the command to be executed when running a container from an image. + + There can only be one command. If you list more than one command, only the last one will take effect. + .LINK + https://docs.docker.com/engine/reference/builder/#cmd + #> + param( + # The command to run. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Command + ) + + process { + "CMD $Command" + } + +} + diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-CopyItem.ps.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-CopyItem.ps.ps1 new file mode 100644 index 000000000..f1e44d2c7 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-CopyItem.ps.ps1 @@ -0,0 +1,30 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.CopyItem { + <# + .SYNOPSIS + Template for copying an item in a Dockerfile. + .DESCRIPTION + A Template for copying an item in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#copy + #> + [Alias('Template.Docker.Copy')] + param( + # The source item to copy. + [vbn()] + [Alias('SourcePath','SourceItem')] + [string] + $Source, + + # The destination to copy the item to. + [vbn()] + [string] + $Destination + ) + + process { + "COPY $Source $Destination" + } +} \ No newline at end of file diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-CopyItem.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-CopyItem.ps1 new file mode 100644 index 000000000..dd51b7e95 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-CopyItem.ps1 @@ -0,0 +1,34 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.CopyItem { + + <# + .SYNOPSIS + Template for copying an item in a Dockerfile. + .DESCRIPTION + A Template for copying an item in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#copy + #> + [Alias('Template.Docker.Copy')] + param( + # The source item to copy. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('SourcePath','SourceItem')] + [string] + $Source, + + # The destination to copy the item to. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Destination + ) + + process { + "COPY $Source $Destination" + } + +} + diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-EntryPoint.ps.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-EntryPoint.ps.ps1 new file mode 100644 index 000000000..a943f4dd2 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-EntryPoint.ps.ps1 @@ -0,0 +1,34 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.EntryPoint { + <# + .SYNOPSIS + Template for a Dockerfile ENTRYPOINT statement. + .DESCRIPTION + A Template for a Dockerfile ENTRYPOINT statement. + .LINK + https://docs.docker.com/engine/reference/builder/#entrypoint + #> + param( + # The command to run when the container starts. + [vbn()] + [string] + $Command, + + # The arguments to pass to the command. + [vbn()] + [Alias('Arguments','Args','ArgumentList')] + [string[]] + $Argument + ) + + process { + if ($Argument) { + "ENTRYPOINT $Command $Argument" + } else { + "ENTRYPOINT $Command" + } + + } +} \ No newline at end of file diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-EntryPoint.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-EntryPoint.ps1 new file mode 100644 index 000000000..67d65020d --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-EntryPoint.ps1 @@ -0,0 +1,38 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.EntryPoint { + + <# + .SYNOPSIS + Template for a Dockerfile ENTRYPOINT statement. + .DESCRIPTION + A Template for a Dockerfile ENTRYPOINT statement. + .LINK + https://docs.docker.com/engine/reference/builder/#entrypoint + #> + param( + # The command to run when the container starts. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Command, + + # The arguments to pass to the command. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Arguments','Args','ArgumentList')] + [string[]] + $Argument + ) + + process { + if ($Argument) { + "ENTRYPOINT $Command $Argument" + } else { + "ENTRYPOINT $Command" + } + + } + +} + diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-Expose.ps.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-Expose.ps.ps1 new file mode 100644 index 000000000..5c16d604e --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-Expose.ps.ps1 @@ -0,0 +1,28 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.Expose { + <# + .SYNOPSIS + Template for exposing a port in a Dockerfile. + .DESCRIPTION + A Template for exposing a port in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#expose + #> + param( + # The port to expose. By default, 80. + [vbn()] + [int] + $Port = 80, + + # The protocol to expose. By default, tcp. + [ValidateSet('tcp','udp')] + [string] + $Protocol = 'tcp' + ) + + process { + "EXPOSE $Port/$Protocol" + } +} \ No newline at end of file diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-Expose.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-Expose.ps1 new file mode 100644 index 000000000..e9ee82fc2 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-Expose.ps1 @@ -0,0 +1,32 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.Expose { + + <# + .SYNOPSIS + Template for exposing a port in a Dockerfile. + .DESCRIPTION + A Template for exposing a port in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#expose + #> + param( + # The port to expose. By default, 80. + [Parameter(ValueFromPipelineByPropertyName)] + [int] + $Port = 80, + + # The protocol to expose. By default, tcp. + [ValidateSet('tcp','udp')] + [string] + $Protocol = 'tcp' + ) + + process { + "EXPOSE $Port/$Protocol" + } + +} + diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-From.ps.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-From.ps.ps1 new file mode 100644 index 000000000..5964faa49 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-From.ps.ps1 @@ -0,0 +1,23 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.From { + <# + .SYNOPSIS + Template for a Dockerfile FROM statement. + .DESCRIPTION + A Template for a Dockerfile FROM statement. + .LINK + https://docs.docker.com/engine/reference/builder/#from + #> + param( + # The base image to use. By default, mcr.microsoft.com/powershell. + [vbn()] + [string] + $BaseImage = 'mcr.microsoft.com/powershell' + ) + + process { + "FROM $BaseImage" + } +} diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-From.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-From.ps1 new file mode 100644 index 000000000..ee8745512 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-From.ps1 @@ -0,0 +1,28 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.From { + + <# + .SYNOPSIS + Template for a Dockerfile FROM statement. + .DESCRIPTION + A Template for a Dockerfile FROM statement. + .LINK + https://docs.docker.com/engine/reference/builder/#from + #> + param( + # The base image to use. By default, mcr.microsoft.com/powershell. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $BaseImage = 'mcr.microsoft.com/powershell' + ) + + process { + "FROM $BaseImage" + } + +} + + diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-HealthCheck.ps.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-HealthCheck.ps.ps1 new file mode 100644 index 000000000..07ff8b93b --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-HealthCheck.ps.ps1 @@ -0,0 +1,55 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.HealthCheck { + <# + .SYNOPSIS + Template for a health check in a Dockerfile. + .DESCRIPTION + A Template for a health check in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#healthcheck + #> + param( + # The command to run. + [vbn()] + [string] + $Command, + + # The interval between checks. + [vbn()] + [timespan] + $Interval = [Timespan]'00::00:30', + + # The timeout for a check. + [vbn()] + [timespan] + $Timeout = [Timespan]'00::00:30', + + # The start period for the check. + [vbn()] + [timespan] + $StartPeriod = [Timespan]'00::00:00', + + # The start interval for the check. + [vbn()] + [timespan] + $StartInterval = [Timespan]'00::00:05', + + # The number of retries. + [vbn()] + [Alias('Retries','Retry')] + [int] + $RetryCount = 3 + ) + + process { + $HealthCheckParameters = foreach ($myParameterKeyValue in $PSBoundParameters.GetEnumerator()) { + if ($myParameterKeyValue.Key -eq 'Command') { + continue + } + "--$($myParameterKeyValue.Key.ToLower() -replace 'start', 'start-') $($myParameterKeyValue.Value.TotalSeconds)" + } + "HEALTHCHECK $HealthCheckParameters $Command" -replace '\s{2,}', ' ' + } +} diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-HealthCheck.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-HealthCheck.ps1 new file mode 100644 index 000000000..fe5b5d4d0 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-HealthCheck.ps1 @@ -0,0 +1,60 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.HealthCheck { + + <# + .SYNOPSIS + Template for a health check in a Dockerfile. + .DESCRIPTION + A Template for a health check in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#healthcheck + #> + param( + # The command to run. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Command, + + # The interval between checks. + [Parameter(ValueFromPipelineByPropertyName)] + [timespan] + $Interval = [Timespan]'00::00:30', + + # The timeout for a check. + [Parameter(ValueFromPipelineByPropertyName)] + [timespan] + $Timeout = [Timespan]'00::00:30', + + # The start period for the check. + [Parameter(ValueFromPipelineByPropertyName)] + [timespan] + $StartPeriod = [Timespan]'00::00:00', + + # The start interval for the check. + [Parameter(ValueFromPipelineByPropertyName)] + [timespan] + $StartInterval = [Timespan]'00::00:05', + + # The number of retries. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Retries','Retry')] + [int] + $RetryCount = 3 + ) + + process { + $HealthCheckParameters = foreach ($myParameterKeyValue in $PSBoundParameters.GetEnumerator()) { + if ($myParameterKeyValue.Key -eq 'Command') { + continue + } + "--$($myParameterKeyValue.Key.ToLower() -replace 'start', 'start-') $($myParameterKeyValue.Value.TotalSeconds)" + } + "HEALTHCHECK $HealthCheckParameters $Command" -replace '\s{2,}', ' ' + } + +} + + diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-Label.ps.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-Label.ps.ps1 new file mode 100644 index 000000000..aa746fb7b --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-Label.ps.ps1 @@ -0,0 +1,28 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.Label { + <# + .SYNOPSIS + Template for setting a label in a Dockerfile. + .DESCRIPTION + A Template for setting a label in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#label + #> + param( + # The label to set. + [vbn()] + [string] + $Label, + + # The value of the label. + [vbn()] + [string] + $Value + ) + + process { + "LABEL $Label=`"$($Value -replace '"', '\"')`"" + } +} \ No newline at end of file diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-Label.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-Label.ps1 new file mode 100644 index 000000000..05cf5fcba --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-Label.ps1 @@ -0,0 +1,32 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.Label { + + <# + .SYNOPSIS + Template for setting a label in a Dockerfile. + .DESCRIPTION + A Template for setting a label in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#label + #> + param( + # The label to set. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Label, + + # The value of the label. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Value + ) + + process { + "LABEL $Label=`"$($Value -replace '"', '\"')`"" + } + +} + diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-OnBuild.ps.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-OnBuild.ps.ps1 new file mode 100644 index 000000000..d26114e72 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-OnBuild.ps.ps1 @@ -0,0 +1,23 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.OnBuild { + <# + .SYNOPSIS + Template for running a command in the build of a Dockerfile. + .DESCRIPTION + A Template for running a command in the build of this image in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#onbuild + #> + param( + # The command to run. + [vbn()] + [string] + $Command + ) + + process { + "ONBUILD $Command" + } +} \ No newline at end of file diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-OnBuild.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-OnBuild.ps1 new file mode 100644 index 000000000..6c7dc6d36 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-OnBuild.ps1 @@ -0,0 +1,27 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.OnBuild { + + <# + .SYNOPSIS + Template for running a command in the build of a Dockerfile. + .DESCRIPTION + A Template for running a command in the build of this image in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#onbuild + #> + param( + # The command to run. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Command + ) + + process { + "ONBUILD $Command" + } + +} + diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-Run.ps.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-Run.ps.ps1 new file mode 100644 index 000000000..55e217f27 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-Run.ps.ps1 @@ -0,0 +1,23 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.Run { + <# + .SYNOPSIS + Template for running a command in a Dockerfile. + .DESCRIPTION + A Template for running a command in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#run + #> + param( + # The command to run. + [vbn()] + [string] + $Command + ) + + process { + "RUN $Command" + } +} \ No newline at end of file diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-Run.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-Run.ps1 new file mode 100644 index 000000000..a4a2c13dd --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-Run.ps1 @@ -0,0 +1,27 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.Run { + + <# + .SYNOPSIS + Template for running a command in a Dockerfile. + .DESCRIPTION + A Template for running a command in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#run + #> + param( + # The command to run. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Command + ) + + process { + "RUN $Command" + } + +} + diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-SetLocation.ps.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-SetLocation.ps.ps1 new file mode 100644 index 000000000..f235814db --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-SetLocation.ps.ps1 @@ -0,0 +1,24 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.SetLocation { + <# + .SYNOPSIS + Template for setting the working directory in a Dockerfile. + .DESCRIPTION + A Template for setting the working directory in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#workdir + #> + [Alias('Template.Docker.WorkDir','Template.Docker.CD')] + param( + # The path to set as the working directory. + [vbn()] + [string] + $Path + ) + + process { + "WORKDIR $Path" + } +} \ No newline at end of file diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-SetLocation.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-SetLocation.ps1 new file mode 100644 index 000000000..5be071e8c --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-SetLocation.ps1 @@ -0,0 +1,28 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.SetLocation { + + <# + .SYNOPSIS + Template for setting the working directory in a Dockerfile. + .DESCRIPTION + A Template for setting the working directory in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#workdir + #> + [Alias('Template.Docker.WorkDir','Template.Docker.CD')] + param( + # The path to set as the working directory. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Path + ) + + process { + "WORKDIR $Path" + } + +} + diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-SetShell.ps.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-SetShell.ps.ps1 new file mode 100644 index 000000000..2d496fc99 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-SetShell.ps.ps1 @@ -0,0 +1,28 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.SetShell { + <# + .SYNOPSIS + Template for setting the shell in a Dockerfile. + .DESCRIPTION + A Template for setting the shell in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#shell + #> + [Alias('Template.Docker.Shell')] + param( + # The shell to set. + [vbn()] + [string] + $Shell, + + # The arguments to pass to the shell. + [string[]] + $Argument + ) + + process { + "SHELL [`"$(@(@($Shell) + $Argument) -replace '"', '\"') -join '", "'`"]" + } +} \ No newline at end of file diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-SetShell.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-SetShell.ps1 new file mode 100644 index 000000000..ba4093834 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-SetShell.ps1 @@ -0,0 +1,32 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.SetShell { + + <# + .SYNOPSIS + Template for setting the shell in a Dockerfile. + .DESCRIPTION + A Template for setting the shell in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#shell + #> + [Alias('Template.Docker.Shell')] + param( + # The shell to set. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Shell, + + # The arguments to pass to the shell. + [string[]] + $Argument + ) + + process { + "SHELL [`"$(@(@($Shell) + $Argument) -replace '"', '\"') -join '", "'`"]" + } + +} + diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-SetUser.ps.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-SetUser.ps.ps1 new file mode 100644 index 000000000..66be93143 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-SetUser.ps.ps1 @@ -0,0 +1,24 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.SetUser { + <# + .SYNOPSIS + Template for setting the current user in a Dockerfile. + .DESCRIPTION + A Template for setting the current user in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#user + #> + [Alias('Template.Docker.User')] + param( + # The user to set. + [vbn()] + [string] + $User + ) + + process { + "USER $User" + } +} \ No newline at end of file diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-SetUser.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-SetUser.ps1 new file mode 100644 index 000000000..610c4887a --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-SetUser.ps1 @@ -0,0 +1,28 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.SetUser { + + <# + .SYNOPSIS + Template for setting the current user in a Dockerfile. + .DESCRIPTION + A Template for setting the current user in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#user + #> + [Alias('Template.Docker.User')] + param( + # The user to set. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $User + ) + + process { + "USER $User" + } + +} + diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-SetVariable.ps.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-SetVariable.ps.ps1 new file mode 100644 index 000000000..39510a8f3 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-SetVariable.ps.ps1 @@ -0,0 +1,29 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.SetVariable { + <# + .SYNOPSIS + Template for setting a variable in a Dockerfile. + .DESCRIPTION + A Template for setting a variable in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#env + #> + [Alias('Template.Docker.SetEnvironmentVariable','Template.Docker.SetEnvironment','Template.Docker.Env')] + param( + # The name of the variable to set. + [vbn()] + [string] + $Name, + + # The value to set the variable to. + [vbn()] + [string] + $Value + ) + + process { + "ENV $Name $Value" + } +} \ No newline at end of file diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-SetVariable.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-SetVariable.ps1 new file mode 100644 index 000000000..a120ad975 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-SetVariable.ps1 @@ -0,0 +1,33 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.SetVariable { + + <# + .SYNOPSIS + Template for setting a variable in a Dockerfile. + .DESCRIPTION + A Template for setting a variable in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#env + #> + [Alias('Template.Docker.SetEnvironmentVariable','Template.Docker.SetEnvironment','Template.Docker.Env')] + param( + # The name of the variable to set. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Name, + + # The value to set the variable to. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Value + ) + + process { + "ENV $Name $Value" + } + +} + diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-StopSignal.ps.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-StopSignal.ps.ps1 new file mode 100644 index 000000000..47e9c4e45 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-StopSignal.ps.ps1 @@ -0,0 +1,23 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.StopSignal { + <# + .SYNOPSIS + Template for setting the stop signal in a Dockerfile. + .DESCRIPTION + A Template for setting the stop signal in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#stopsignal + #> + param( + # The signal to stop the container. + [vbn()] + [string] + $Signal + ) + + process { + "STOPSIGNAL $Signal" + } +} \ No newline at end of file diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-StopSignal.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-StopSignal.ps1 new file mode 100644 index 000000000..6224361c6 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-StopSignal.ps1 @@ -0,0 +1,27 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.StopSignal { + + <# + .SYNOPSIS + Template for setting the stop signal in a Dockerfile. + .DESCRIPTION + A Template for setting the stop signal in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#stopsignal + #> + param( + # The signal to stop the container. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Signal + ) + + process { + "STOPSIGNAL $Signal" + } + +} + diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-Volume.ps.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-Volume.ps.ps1 new file mode 100644 index 000000000..1271b3c36 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-Volume.ps.ps1 @@ -0,0 +1,24 @@ +[ValidatePattern("docker")] +param() + +Template function Docker.Volume { + <# + .SYNOPSIS + Template for creating a volume in a Dockerfile. + .DESCRIPTION + A Template for creating a volume in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#volume + #> + [Alias('Template.Docker.Vol')] + param( + # The path of the volume. + [vbn()] + [string] + $Path + ) + + process { + "VOLUME $Path" + } +} \ No newline at end of file diff --git a/Languages/Docker/Templates/Syntax/Docker-Template-Volume.ps1 b/Languages/Docker/Templates/Syntax/Docker-Template-Volume.ps1 new file mode 100644 index 000000000..a7edbfcc9 --- /dev/null +++ b/Languages/Docker/Templates/Syntax/Docker-Template-Volume.ps1 @@ -0,0 +1,28 @@ +[ValidatePattern("docker")] +param() + + +function Template.Docker.Volume { + + <# + .SYNOPSIS + Template for creating a volume in a Dockerfile. + .DESCRIPTION + A Template for creating a volume in a Dockerfile. + .LINK + https://docs.docker.com/engine/reference/builder/#volume + #> + [Alias('Template.Docker.Vol')] + param( + # The path of the volume. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Path + ) + + process { + "VOLUME $Path" + } + +} + diff --git a/Languages/Eiffel/Eiffel-Language.ps.ps1 b/Languages/Eiffel/Eiffel-Language.ps.ps1 index 615f2e159..55a8be89c 100644 --- a/Languages/Eiffel/Eiffel-Language.ps.ps1 +++ b/Languages/Eiffel/Eiffel-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Eiffel|Language)[\s\p{P}]")] +param() + Language function Eiffel { <# .SYNOPSIS diff --git a/Languages/Eiffel/Eiffel-Language.ps1 b/Languages/Eiffel/Eiffel-Language.ps1 index 39fa48d0b..80882b0b9 100644 --- a/Languages/Eiffel/Eiffel-Language.ps1 +++ b/Languages/Eiffel/Eiffel-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Eiffel|Language)[\s\p{P}]")] +param() + function Language.Eiffel { <# diff --git a/Languages/FSharp/FSharp-Language.ps.ps1 b/Languages/FSharp/FSharp-Language.ps.ps1 index 4929316f0..2aa657106 100644 --- a/Languages/FSharp/FSharp-Language.ps.ps1 +++ b/Languages/FSharp/FSharp-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>FSharp|F\#|Language)[\s\p{P}]")] +param() + Language function FSharp { <# .SYNOPSIS diff --git a/Languages/FSharp/FSharp-Language.ps1 b/Languages/FSharp/FSharp-Language.ps1 index 2f85d64d8..056e112f6 100644 --- a/Languages/FSharp/FSharp-Language.ps1 +++ b/Languages/FSharp/FSharp-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>FSharp|F\#|Language)[\s\p{P}]")] +param() + function Language.FSharp { <# diff --git a/Languages/GCode/GCode-Language.ps.ps1 b/Languages/GCode/GCode-Language.ps.ps1 index da09ae59c..3c7fe01f4 100644 --- a/Languages/GCode/GCode-Language.ps.ps1 +++ b/Languages/GCode/GCode-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>G-?Code|Language)[\s\p{P}]")] +param() + Language function GCode { <# .SYNOPSIS diff --git a/Languages/GCode/GCode-Language.ps1 b/Languages/GCode/GCode-Language.ps1 index d25d950fd..f7fe5265a 100644 --- a/Languages/GCode/GCode-Language.ps1 +++ b/Languages/GCode/GCode-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>G-?Code|Language)[\s\p{P}]")] +param() + function Language.GCode { <# diff --git a/Languages/GLSL/GLSL-Language.ps.ps1 b/Languages/GLSL/GLSL-Language.ps.ps1 index 5cbe3f891..ad4570e76 100644 --- a/Languages/GLSL/GLSL-Language.ps.ps1 +++ b/Languages/GLSL/GLSL-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>GLSL|Language)[\s\p{P}]")] +param() + Language function GLSL { <# .SYNOPSIS diff --git a/Languages/GLSL/GLSL-Language.ps1 b/Languages/GLSL/GLSL-Language.ps1 index 478cbb025..a8285f684 100644 --- a/Languages/GLSL/GLSL-Language.ps1 +++ b/Languages/GLSL/GLSL-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>GLSL|Language)[\s\p{P}]")] +param() + function Language.GLSL { <# diff --git a/Languages/Go/Go-Language.ps.ps1 b/Languages/Go/Go-Language.ps.ps1 index 5a331ec2a..fcbe149cc 100644 --- a/Languages/Go/Go-Language.ps.ps1 +++ b/Languages/Go/Go-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Go|Language)[\s\p{P}]")] +param() + Language function Go { <# .SYNOPSIS diff --git a/Languages/Go/Go-Language.ps1 b/Languages/Go/Go-Language.ps1 index 85f743fa4..db7f5cf65 100644 --- a/Languages/Go/Go-Language.ps1 +++ b/Languages/Go/Go-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Go|Language)[\s\p{P}]")] +param() + function Language.Go { <# diff --git a/Languages/Go/Go-Template-HelloWorld.ps.ps1 b/Languages/Go/Templates/Go-Template-HelloWorld.ps.ps1 similarity index 90% rename from Languages/Go/Go-Template-HelloWorld.ps.ps1 rename to Languages/Go/Templates/Go-Template-HelloWorld.ps.ps1 index 90f02a2e4..81db9fe75 100644 --- a/Languages/Go/Go-Template-HelloWorld.ps.ps1 +++ b/Languages/Go/Templates/Go-Template-HelloWorld.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Go[\s\p{P}]")] +param() + Template function HelloWorld.go { <# .SYNOPSIS diff --git a/Languages/Go/Go-Template-HelloWorld.ps1 b/Languages/Go/Templates/Go-Template-HelloWorld.ps1 similarity index 90% rename from Languages/Go/Go-Template-HelloWorld.ps1 rename to Languages/Go/Templates/Go-Template-HelloWorld.ps1 index c9b2a7c25..a3971dd18 100644 --- a/Languages/Go/Go-Template-HelloWorld.ps1 +++ b/Languages/Go/Templates/Go-Template-HelloWorld.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("Go[\s\p{P}]")] +param() + function Template.HelloWorld.go { diff --git a/Languages/HCL/HCL-Language.ps.ps1 b/Languages/HCL/HCL-Language.ps.ps1 index 246ec5315..9bd564d63 100644 --- a/Languages/HCL/HCL-Language.ps.ps1 +++ b/Languages/HCL/HCL-Language.ps.ps1 @@ -1,3 +1,7 @@ +[ValidatePattern("(?>HCL|Language)[\s\p{P}]")] +param() + + Language function HCL { <# .SYNOPSIS diff --git a/Languages/HCL/HCL-Language.ps1 b/Languages/HCL/HCL-Language.ps1 index 14fb7b8fa..8b152d3bd 100644 --- a/Languages/HCL/HCL-Language.ps1 +++ b/Languages/HCL/HCL-Language.ps1 @@ -1,3 +1,7 @@ +[ValidatePattern("(?>HCL|Language)[\s\p{P}]")] +param() + + function Language.HCL { <# diff --git a/Languages/HLSL/HLSL-Language.ps.ps1 b/Languages/HLSL/HLSL-Language.ps.ps1 index 2449e7f1a..40403d134 100644 --- a/Languages/HLSL/HLSL-Language.ps.ps1 +++ b/Languages/HLSL/HLSL-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>HLSL|Language)[\s\p{P}]")] +param() + Language function HLSL { <# .SYNOPSIS diff --git a/Languages/HLSL/HLSL-Language.ps1 b/Languages/HLSL/HLSL-Language.ps1 index ed964cf0e..25857d2a9 100644 --- a/Languages/HLSL/HLSL-Language.ps1 +++ b/Languages/HLSL/HLSL-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>HLSL|Language)[\s\p{P}]")] +param() + function Language.HLSL { <# diff --git a/Languages/HTML/HTML-Language.ps.ps1 b/Languages/HTML/HTML-Language.ps.ps1 index a9796b453..cc5ea59e2 100644 --- a/Languages/HTML/HTML-Language.ps.ps1 +++ b/Languages/HTML/HTML-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>HTML|Language)[\s\p{P}]")] +param() + Language function HTML { <# .SYNOPSIS diff --git a/Languages/HTML/HTML-Language.ps1 b/Languages/HTML/HTML-Language.ps1 index 747fd34df..dec73d362 100644 --- a/Languages/HTML/HTML-Language.ps1 +++ b/Languages/HTML/HTML-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>HTML|Language)[\s\p{P}]")] +param() + function Language.HTML { <# diff --git a/Languages/HTML/Templates/Controls/HTML-Template-CustomElement.ps.ps1 b/Languages/HTML/Templates/Controls/HTML-Template-CustomElement.ps.ps1 new file mode 100644 index 000000000..453f5d13d --- /dev/null +++ b/Languages/HTML/Templates/Controls/HTML-Template-CustomElement.ps.ps1 @@ -0,0 +1,296 @@ +[ValidatePattern("(?>HTML|JavaScript)")] +param() + +Template function HTML.CustomElement { + <# + .SYNOPSIS + Template for a custom HTML element. + .DESCRIPTION + A Template for a custom HTML element. + + Creates the JavaScript for a custom HTML element. + .LINK + https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements + .EXAMPLE + Template.HTML.CustomElement -ElementName hello-world -Template "

Hello, World!

" -OnConnected " + console.log('Hello, World!') + " + Template.HTML.Element -Name "hello-world" + #> + [Alias( + 'Template.HTML.Control', + 'Template.Control.html', + 'Template.CustomElement.html', + 'Template.JavaScript.Control', + 'Template.JavaScript.CustomElement', + 'Template.Control.js', + 'Template.CustomElement.js' + )] + param( + # The name of the element. By default, custom-element. + [vbn()] + [Alias('Element','Name')] + [string] + $ElementName = 'custom-element', + + # The class name. + # If not provided, it will be derived from the element name. + [vbn()] + [Alias('Class')] + [string] + $ClassName, + + # The class that the element extends. By default, HTMLElement. + [vbn()] + [Alias('Extends','Extend')] + [string] + $ExtendClass = 'HTMLElement', + + # The base HTML element that is being extended. + # If a specific element is extended, you can create a control in form: + # ~~~html + # + # ~~~ + [vbn()] + [Alias('ExtendElements')] + [string] + $ExtendElement, + + # The parameters to any template. + [vbn()] + [Alias('Parameters')] + [PSObject] + $Parameter, + + # The properties for the element. + # If multiple values are provided, the property will be gettable and settable. + [vbn()] + [Alias('Properties')] + [PSObject[]] + $Property, + + # Any additional members for the element. + [vbn()] + [Alias('Methods')] + [PSObject[]] + $Method, + + # Any additional fields for the element. + [vbn()] + [Alias('Fields')] + [PSObject[]] + $Field, + + # The template content, or the ID of a template. + [vbn()] + [Alias('TemplateID','TemplateContent','Content')] + [string] + $Template, + + # The JavaScript to run when the element is connected. + [vbn()] + [Alias('OnConnection','ConnectedCallback', 'ConnectionCallback')] + [string] + $OnConnected, + + # The JavaScript to run when the element is disconnected. + [vbn()] + [Alias('OnDisconnection','DisconnectedCallback', 'DisconnectionCallback')] + [string] + $OnDisconnected, + + # The JavaScript to run when the element is adopted. + [vbn()] + [Alias('OnAdoption','AdoptedCallback', 'AdoptionCallback')] + [string] + $OnAdopted, + + # The JavaScript to run when attributes are updated. + [vbn()] + [Alias('OnAttributeChanged','AttributeChangeCallback', 'AttributeChangedCallback')] + [string] + $OnAttributeChange, + + # A collection of event handlers. + # Each key or property will be the element ID (followed by a period) and the event name. + # Multiple event names can be separated by commas. + [vbn()] + [Alias('EventHandlers')] + [PSObject] + $EventHandler, + + # The list of observable attributes. + [vbn()] + [Alias('ObservableAttributes','Observable')] + [string[]] + $ObservableAttribute + ) + + process { + if (-not $PSBoundParameters['ClassName']) { + $ClassName = $ElementName -replace '-','_' + } + + $Field += @{"#shadow" = "this.attachShadow({mode: 'open'});"} + if ($ObservableAttribute -and -not $OnAttributeChange) { + foreach ($observerableAttr in $ObservableAttribute) { + $defaultFieldName = "#$($observerableAttr -replace "-","_")" + if (-not @($field.$defaultFieldName)) { + $Field += @{$defaultFieldName = "null"} + } + $defaultPropertyName = "$($observerableAttr -replace "-","_")" + if (-not @($Property.$defaultPropertyName)) { + $Property += @{$defaultPropertyName = @("return this.$defaultFieldName","this.$defaultFieldName = value") } + } + } + } + + + $allMembers = @( + if ($field) { + foreach ($PropertyBag in @($Field)) { + if ($PropertyBag -is [Collections.IDictionary]) { + $PropertyBag = [PSCustomObject]$PropertyBag + } + foreach ($prop in $PropertyBag.PSObject.properties) { + "$($prop.Name) = $($prop.Value)" + } + } + } + + if ($EventHandler) { + if ($EventHandler -is [Collections.IDictionary]) { + $EventHandler = [PSCustomObject]([Ordered]@{} + $EventHandler) + } + $wireEventHandlers = foreach ($prop in $eventHandler.psobject.properties) { + if ($prop.Name -notmatch '\.') { continue } + $propNameSegements = $prop.Name -split '\.' + if ($propNameSegements.Count -lt 2) { continue } + $elementId = $propNameSegements[0..($propNameSegements.Count - 2)] -join '.' + $eventNames = $propNameSegements[-1] -split '\s{0,},\s{0,}' + $eventHandlerScript = $prop.Value + if ($eventHandlerScript -notmatch 'function') { + $eventHandlerScript = "function(event) { $eventHandlerScript }" + if ($eventHandlerScript -notmatch '.bind(this)\s{0,}$') { + $eventHandlerScript = "$eventHandlerScript.bind(this)" + } + } + foreach ($eventName in $eventnames) { + @("this.#shadow.getElementById(`"$elementId`").addEventListener(" + " `"$eventName`"," + " $eventHandlerScript" + ");") -join [Environment]::NewLine + } + } + if ($OnConnected) { + $OnConnected = $wireEventHandlers, $OnConnected -join [Environment]::NewLine + } + } + + if ($OnConnected) { + "connectedCallback() { $OnConnected }" + } + if ($OnDisconnected) { + "disconnectedCallback() { $OnDisconnected }" + } + if ($OnAdopted) { + "adoptedCallback() { $OnAdopted }" + } + if ($ObservableAttribute) { + "static get observedAttributes() { return $(ConvertTo-Json -InputObject $ObservableAttribute -Compress) }" + } + if ($OnAttributeChange) { + "attributeChangedCallback(name, oldValue, newValue) { $OnAttributeChange }" + } elseif ($ObservableAttribute) { + "attributeChangedCallback(name, oldValue, newValue) { + this.setAttribute(name, newValue); + if (this[name.replace('-','_')] && this[name.replace('-','_')] != newValue) { + this[name.replace('-','_')] = newValue; + } + }" + } + + if ($property) { + foreach ($PropertyBag in @($Property)) { + if ($PropertyBag -is [Collections.IDictionary]) { + $PropertyBag = [PSCustomObject]$PropertyBag + } + foreach ($prop in $PropertyBag.PSObject.properties) { + $propName = $prop.Name + $propGet, $propSet = $prop.Value + if ($propGet) { + "get $propName() { $propGet }" + } + if ($propSet) { + "set $propName(value) { $propSet }" + } + } + } + } + + if ($method) { + foreach ($MemberBag in @($method)) { + if ($MemberBag -is [Collections.IDictionary]) { + $MemberBag = [PSCustomObject]$MemberBag + } + foreach ($member in $MemberBag.PSObject.properties) { + $memberName = $member.Name + $memberValue = $member.Value + if ($memberValue -notmatch '\s{0,}\{') { + $memberValue = "{ $memberValue }" + } + "$memberName $memberValue" + } + } + } + ) + + @" +$(if ($MyInvocation.InvocationName -match 'HTML') {' +"@ + } +} diff --git a/Languages/HTML/Templates/Controls/HTML-Template-CustomElement.ps1 b/Languages/HTML/Templates/Controls/HTML-Template-CustomElement.ps1 new file mode 100644 index 000000000..ca61cd3c7 --- /dev/null +++ b/Languages/HTML/Templates/Controls/HTML-Template-CustomElement.ps1 @@ -0,0 +1,301 @@ +[ValidatePattern("(?>HTML|JavaScript)")] +param() + + +function Template.HTML.CustomElement { + + <# + .SYNOPSIS + Template for a custom HTML element. + .DESCRIPTION + A Template for a custom HTML element. + + Creates the JavaScript for a custom HTML element. + .LINK + https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements + .EXAMPLE + Template.HTML.CustomElement -ElementName hello-world -Template "

Hello, World!

" -OnConnected " + console.log('Hello, World!') + " + Template.HTML.Element -Name "hello-world" + #> + [Alias( + 'Template.HTML.Control', + 'Template.Control.html', + 'Template.CustomElement.html', + 'Template.JavaScript.Control', + 'Template.JavaScript.CustomElement', + 'Template.Control.js', + 'Template.CustomElement.js' + )] + param( + # The name of the element. By default, custom-element. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Element','Name')] + [string] + $ElementName = 'custom-element', + + # The class name. + # If not provided, it will be derived from the element name. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Class')] + [string] + $ClassName, + + # The class that the element extends. By default, HTMLElement. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Extends','Extend')] + [string] + $ExtendClass = 'HTMLElement', + + # The base HTML element that is being extended. + # If a specific element is extended, you can create a control in form: + # ~~~html + # + # ~~~ + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('ExtendElements')] + [string] + $ExtendElement, + + # The parameters to any template. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Parameters')] + [PSObject] + $Parameter, + + # The properties for the element. + # If multiple values are provided, the property will be gettable and settable. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Properties')] + [PSObject[]] + $Property, + + # Any additional members for the element. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Methods')] + [PSObject[]] + $Method, + + # Any additional fields for the element. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Fields')] + [PSObject[]] + $Field, + + # The template content, or the ID of a template. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('TemplateID','TemplateContent','Content')] + [string] + $Template, + + # The JavaScript to run when the element is connected. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('OnConnection','ConnectedCallback', 'ConnectionCallback')] + [string] + $OnConnected, + + # The JavaScript to run when the element is disconnected. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('OnDisconnection','DisconnectedCallback', 'DisconnectionCallback')] + [string] + $OnDisconnected, + + # The JavaScript to run when the element is adopted. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('OnAdoption','AdoptedCallback', 'AdoptionCallback')] + [string] + $OnAdopted, + + # The JavaScript to run when attributes are updated. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('OnAttributeChanged','AttributeChangeCallback', 'AttributeChangedCallback')] + [string] + $OnAttributeChange, + + # A collection of event handlers. + # Each key or property will be the element ID (followed by a period) and the event name. + # Multiple event names can be separated by commas. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('EventHandlers')] + [PSObject] + $EventHandler, + + # The list of observable attributes. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('ObservableAttributes','Observable')] + [string[]] + $ObservableAttribute + ) + + process { + if (-not $PSBoundParameters['ClassName']) { + $ClassName = $ElementName -replace '-','_' + } + + $Field += @{"#shadow" = "this.attachShadow({mode: 'open'});"} + if ($ObservableAttribute -and -not $OnAttributeChange) { + foreach ($observerableAttr in $ObservableAttribute) { + $defaultFieldName = "#$($observerableAttr -replace "-","_")" + if (-not @($field.$defaultFieldName)) { + $Field += @{$defaultFieldName = "null"} + } + $defaultPropertyName = "$($observerableAttr -replace "-","_")" + if (-not @($Property.$defaultPropertyName)) { + $Property += @{$defaultPropertyName = @("return this.$defaultFieldName","this.$defaultFieldName = value") } + } + } + } + + + $allMembers = @( + if ($field) { + foreach ($PropertyBag in @($Field)) { + if ($PropertyBag -is [Collections.IDictionary]) { + $PropertyBag = [PSCustomObject]$PropertyBag + } + foreach ($prop in $PropertyBag.PSObject.properties) { + "$($prop.Name) = $($prop.Value)" + } + } + } + + if ($EventHandler) { + if ($EventHandler -is [Collections.IDictionary]) { + $EventHandler = [PSCustomObject]([Ordered]@{} + $EventHandler) + } + $wireEventHandlers = foreach ($prop in $eventHandler.psobject.properties) { + if ($prop.Name -notmatch '\.') { continue } + $propNameSegements = $prop.Name -split '\.' + if ($propNameSegements.Count -lt 2) { continue } + $elementId = $propNameSegements[0..($propNameSegements.Count - 2)] -join '.' + $eventNames = $propNameSegements[-1] -split '\s{0,},\s{0,}' + $eventHandlerScript = $prop.Value + if ($eventHandlerScript -notmatch 'function') { + $eventHandlerScript = "function(event) { $eventHandlerScript }" + if ($eventHandlerScript -notmatch '.bind(this)\s{0,}$') { + $eventHandlerScript = "$eventHandlerScript.bind(this)" + } + } + foreach ($eventName in $eventnames) { + @("this.#shadow.getElementById(`"$elementId`").addEventListener(" + " `"$eventName`"," + " $eventHandlerScript" + ");") -join [Environment]::NewLine + } + } + if ($OnConnected) { + $OnConnected = $wireEventHandlers, $OnConnected -join [Environment]::NewLine + } + } + + if ($OnConnected) { + "connectedCallback() { $OnConnected }" + } + if ($OnDisconnected) { + "disconnectedCallback() { $OnDisconnected }" + } + if ($OnAdopted) { + "adoptedCallback() { $OnAdopted }" + } + if ($ObservableAttribute) { + "static get observedAttributes() { return $(ConvertTo-Json -InputObject $ObservableAttribute -Compress) }" + } + if ($OnAttributeChange) { + "attributeChangedCallback(name, oldValue, newValue) { $OnAttributeChange }" + } elseif ($ObservableAttribute) { + "attributeChangedCallback(name, oldValue, newValue) { + this.setAttribute(name, newValue); + if (this[name.replace('-','_')] && this[name.replace('-','_')] != newValue) { + this[name.replace('-','_')] = newValue; + } + }" + } + + if ($property) { + foreach ($PropertyBag in @($Property)) { + if ($PropertyBag -is [Collections.IDictionary]) { + $PropertyBag = [PSCustomObject]$PropertyBag + } + foreach ($prop in $PropertyBag.PSObject.properties) { + $propName = $prop.Name + $propGet, $propSet = $prop.Value + if ($propGet) { + "get $propName() { $propGet }" + } + if ($propSet) { + "set $propName(value) { $propSet }" + } + } + } + } + + if ($method) { + foreach ($MemberBag in @($method)) { + if ($MemberBag -is [Collections.IDictionary]) { + $MemberBag = [PSCustomObject]$MemberBag + } + foreach ($member in $MemberBag.PSObject.properties) { + $memberName = $member.Name + $memberValue = $member.Value + if ($memberValue -notmatch '\s{0,}\{') { + $memberValue = "{ $memberValue }" + } + "$memberName $memberValue" + } + } + } + ) + + @" +$(if ($MyInvocation.InvocationName -match 'HTML') {' +"@ + } + +} + + diff --git a/Languages/HTML/Templates/HTML-Template-HelloWorld.ps.ps1 b/Languages/HTML/Templates/HTML-Template-HelloWorld.ps.ps1 new file mode 100644 index 000000000..0380e7d86 --- /dev/null +++ b/Languages/HTML/Templates/HTML-Template-HelloWorld.ps.ps1 @@ -0,0 +1,25 @@ +[ValidatePattern("HTML")] +param() + +Template function HelloWorld.html { + <# + .SYNOPSIS + Hello World in HTML + .DESCRIPTION + A Template for Hello World, in HTML. + #> + [Alias('Template.HTML.HelloWorld')] + param( + # The message to print. By default, "hello world". + [vbn()] + [string] + $Message = "hello world" + ) + process { +@" +

+$([Security.SecurityElement]::Escape($message)) +

+"@ + } +} diff --git a/Languages/HTML/Templates/HTML-Template-HelloWorld.ps1 b/Languages/HTML/Templates/HTML-Template-HelloWorld.ps1 new file mode 100644 index 000000000..6a8f3f745 --- /dev/null +++ b/Languages/HTML/Templates/HTML-Template-HelloWorld.ps1 @@ -0,0 +1,30 @@ +[ValidatePattern("HTML")] +param() + + +function Template.HelloWorld.html { + + <# + .SYNOPSIS + Hello World in HTML + .DESCRIPTION + A Template for Hello World, in HTML. + #> + [Alias('Template.HTML.HelloWorld')] + param( + # The message to print. By default, "hello world". + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Message = "hello world" + ) + process { +@" +

+$([Security.SecurityElement]::Escape($message)) +

+"@ + } + +} + + diff --git a/Languages/HTML/Templates/Input/HTML-Template-Command-Input.ps.ps1 b/Languages/HTML/Templates/Input/HTML-Template-Command-Input.ps.ps1 new file mode 100644 index 000000000..657056f97 --- /dev/null +++ b/Languages/HTML/Templates/Input/HTML-Template-Command-Input.ps.ps1 @@ -0,0 +1,265 @@ +[ValidatePattern('HTML')] +param() + + +Template function HTML.Command.Input { + <# + .SYNOPSIS + Generates an HTML command input. + .DESCRIPTION + Generates HTML input for a command. + .EXAMPLE + Get-Command Get-Command | Template.HTML.Command.Input + .EXAMPLE + New-WebPage -Content @( + Get-Command Template.HTML.Command.Input | Template.HTML.Command.Input + ) -Palette "Konsolas" + #> + [CmdletBinding(DefaultParameterSetName='None')] + param( + # The Command Metadata. This can be provided via the pipeline from Get-Command. + [vfp(Mandatory,ParameterSetName='CommandMetadata')] + [Management.Automation.CommandMetadata] + $CommandMetadata, + + # The name of the command. + [vbn()] + [Alias('CommandName')] + [string] + $Name, + + # The element identifier. + [vbn()] + [Alias('ElementID')] + [string] + $Id, + + # The display name of the command. + # If this is not provided, it will be the name, split on camel cased spaced or punctuation. + [vbn()] + [Alias('FriendlyName')] + [string] + $DisplayName, + + # The reset text, by default, nothing. + [vbn()] + [string] + $ResetText = '', + + # If the command supports ShouldProcess (`-WhatIf`, `-Confirm`) + [vbn()] + [switch] + $SupportsShouldProcess, + + # If the command supports paging. + [vbn()] + [switch] + $SupportsPaging, + + # If the command supports positional binding + [vbn()] + [switch] + $PositionalBinding, + + # The help URI for the command. + [vbn()] + [uri] + $HelpUri, + + # The confirm impact of the command. + [vbn()] + [Management.Automation.ConfirmImpact] + $ConfirmImpact, + + # The parameter metadata for the command. + [vbn()] + [Alias('Parameters')] + [PSObject] + $Parameter, + + # The Command Description. + [vbn()] + [Alias('CommandDescription')] + [string] + $Description, + + # The Command Synopsis. + [vbn()] + [Alias('CommandSynopsis')] + [string] + $Synopsis, + + # The Command Example. + [vbn()] + [Alias('CommandExample')] + [string[]] + $Example, + + # The Command Notes. + [vbn()] + [Alias('Note','CommandNotes')] + [string[]] + $Notes, + + # The Command Links. + [vbn()] + [Alias('Links','CommandLinks')] + [string[]] + $Link, + + # The Element Map. + # This maps parameters to the HTML elements that will be used to render them. + [vbn()] + [Management.Automation.Hidden()] + [Alias('ElementNameMap','ElementNames')] + [PSObject] + $ElementMap = [Ordered]@{ + 'Name' = 'h2' + 'Synopsis' = 'p' + 'Description' = 'p' + 'Example' = 'code' + 'Link' = 'p' + 'Notes' = 'p' + }, + + # The Element Attribute Map. + # This maps parameters to the HTML element attributes that will be used to render them. + [vbn()] + [Management.Automation.Hidden()] + [PSObject] + $ElementAttributeMap = [Ordered]@{ + 'Example' = [Ordered]@{ + 'class' = 'language-powershell' + 'language' = 'PowerShell' + } + }, + + # The element separator. This is used to separate elements. + [vbn()] + [psobject] + $ElementSeparator = '
', + + # The Command Attributes. These are used to provide additional information about the command. + # They will be automatically provided if piping a command into this function. + # If an attribute named HTML.Input is provided, it will be returned directly. + [vbn()] + [Alias('Attributes','CommandAttribute','CommandAttributes')] + [PSObject[]] + $Attribute, + + # The Container Element. This is used to hold all of the elements. + [vbn()] + [string] + $ContainerElement, + + # The Container Attributes. + [vbn()] + [PSObject[]] + $ContainerAttribute, + + # The Item Element. This is used to hold each item. + [vbn()] + [string] + $ItemElement, + + # The Item Attributes. + [vbn()] + [PSObject[]] + $ItemAttribute, + + # The Item Separator. This is used to separate items. + # The default is a line break. + [vbn()] + [PSObject] + $ItemSeparator = "
", + + # If set, this will not include a submit button. + [vbn()] + [switch] + $NoSubmit, + + # If set, this will not include a reset button. + [vbn()] + [switch] + $NoReset + ) + + + process { + # First, check our attributes + foreach ($attr in $Attribute) { + # to see if we have an HTML Input attribute. + if ($attr -is [Reflection.AssemblyMetadataAttribute] -and + $attr.Key -match '^HTML\p{P}?Input') { + # If we do, return it. + return $attr.Value + } + } + + + # Then copy the parameters + $myParameterCopy = [Ordered]@{} + $PSBoundParameters + $elementOrder = + @(if ($elementMap -is [Collections.IDictionary]) { + $elementMap.Keys + } else { + $elementMap.PSObject.Properties.Name + }) + + + + $ItemsInContainer = @( + # Many parameters will be turned directly into elements, + # using the -ElementMap and -ElementAttributeMap + foreach ($potentialElement in $elementOrder) { + @(if ($myParameterCopy.($potentialElement)) { + $ElementSplat = [Ordered]@{ + Name = $elementMap.$potentialElement + Content = "$($myParameterCopy.$potentialElement)" + } + if ($ElementAttributeMap.$potentialElement) { + $ElementSplat.Attribute = $ElementAttributeMap.$potentialElement + } + Template.HTML.Element @ElementSplat + }) -join $ElementSeparator + } + + # Any parameters should be turned into input elements. + if ($parameter -is [Collections.IDictionary]) { + $parameter.Values | Template.HTML.Parameter.Input + } elseif ($Parameter) { + $Parameter | Template.HTML.Parameter.Input + } + $CamelCaseSpace = [Regex]::new('(?(?<=[a-z])(?=[A-Z]))') + $CamelCaseOrPunctuation = [Regex]::new("(?>[\s\p{P}]|$CamelCaseSpace)") + if ($ResetText) { + if (-not $NoReset) { + Template.HTML.InputElement -InputType reset -Value $ResetText + } + } + if (-not $NoSubmit -and ($name -or $DisplayName)) { + Template.HTML.InputElement -InputType submit -Value $( + $(if ($DisplayName) { $DisplayName } else { $Name }) -replace $CamelCaseOrPunctuation,' ' + ) + } + ) + + # Now we want to put all of the items into a container. + $ContainerContent = + @(foreach ($itemInContainer in $ItemsInContainer) { + # If we have an item element, wrap it in that element. + if ($ItemElement) { + Template.HTML.Element -Name $ItemElement -Content $itemInContainer -Attribute $ItemAttribute + } else { + $itemInContainer + } + }) -join $ItemSeparator # If we have an item separator, join the items with it. + + # If we have a container element, wrap the all the items in that element. + if ($ContainerElement) { + Template.HTML.Element -Name $ContainerElement -Content $ContainerContent -Attribute $ContainerAttribute -Id $Id + } else { + $ContainerContent # Otherwise, return the items directly. + } + } +} \ No newline at end of file diff --git a/Languages/HTML/Templates/Input/HTML-Template-Command-Input.ps1 b/Languages/HTML/Templates/Input/HTML-Template-Command-Input.ps1 new file mode 100644 index 000000000..039e6d6d7 --- /dev/null +++ b/Languages/HTML/Templates/Input/HTML-Template-Command-Input.ps1 @@ -0,0 +1,269 @@ +[ValidatePattern('HTML')] +param() + + + +function Template.HTML.Command.Input { + + <# + .SYNOPSIS + Generates an HTML command input. + .DESCRIPTION + Generates HTML input for a command. + .EXAMPLE + Get-Command Get-Command | Template.HTML.Command.Input + .EXAMPLE + New-WebPage -Content @( + Get-Command Template.HTML.Command.Input | Template.HTML.Command.Input + ) -Palette "Konsolas" + #> + [CmdletBinding(DefaultParameterSetName='None')] + param( + # The Command Metadata. This can be provided via the pipeline from Get-Command. + [Parameter(Mandatory,ParameterSetName='CommandMetadata',ValueFromPipeline)] + [Management.Automation.CommandMetadata] + $CommandMetadata, + + # The name of the command. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('CommandName')] + [string] + $Name, + + # The element identifier. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('ElementID')] + [string] + $Id, + + # The display name of the command. + # If this is not provided, it will be the name, split on camel cased spaced or punctuation. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('FriendlyName')] + [string] + $DisplayName, + + # The reset text, by default, nothing. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $ResetText = '', + + # If the command supports ShouldProcess (`-WhatIf`, `-Confirm`) + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $SupportsShouldProcess, + + # If the command supports paging. + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $SupportsPaging, + + # If the command supports positional binding + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $PositionalBinding, + + # The help URI for the command. + [Parameter(ValueFromPipelineByPropertyName)] + [uri] + $HelpUri, + + # The confirm impact of the command. + [Parameter(ValueFromPipelineByPropertyName)] + [Management.Automation.ConfirmImpact] + $ConfirmImpact, + + # The parameter metadata for the command. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Parameters')] + [PSObject] + $Parameter, + + # The Command Description. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('CommandDescription')] + [string] + $Description, + + # The Command Synopsis. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('CommandSynopsis')] + [string] + $Synopsis, + + # The Command Example. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('CommandExample')] + [string[]] + $Example, + + # The Command Notes. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Note','CommandNotes')] + [string[]] + $Notes, + + # The Command Links. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Links','CommandLinks')] + [string[]] + $Link, + + # The Element Map. + # This maps parameters to the HTML elements that will be used to render them. + [Parameter(ValueFromPipelineByPropertyName)] + [Management.Automation.Hidden()] + [Alias('ElementNameMap','ElementNames')] + [PSObject] + $ElementMap = [Ordered]@{ + 'Name' = 'h2' + 'Synopsis' = 'p' + 'Description' = 'p' + 'Example' = 'code' + 'Link' = 'p' + 'Notes' = 'p' + }, + + # The Element Attribute Map. + # This maps parameters to the HTML element attributes that will be used to render them. + [Parameter(ValueFromPipelineByPropertyName)] + [Management.Automation.Hidden()] + [PSObject] + $ElementAttributeMap = [Ordered]@{ + 'Example' = [Ordered]@{ + 'class' = 'language-powershell' + 'language' = 'PowerShell' + } + }, + + # The element separator. This is used to separate elements. + [Parameter(ValueFromPipelineByPropertyName)] + [psobject] + $ElementSeparator = '
', + + # The Command Attributes. These are used to provide additional information about the command. + # They will be automatically provided if piping a command into this function. + # If an attribute named HTML.Input is provided, it will be returned directly. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Attributes','CommandAttribute','CommandAttributes')] + [PSObject[]] + $Attribute, + + # The Container Element. This is used to hold all of the elements. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $ContainerElement, + + # The Container Attributes. + [Parameter(ValueFromPipelineByPropertyName)] + [PSObject[]] + $ContainerAttribute, + + # The Item Element. This is used to hold each item. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $ItemElement, + + # The Item Attributes. + [Parameter(ValueFromPipelineByPropertyName)] + [PSObject[]] + $ItemAttribute, + + # The Item Separator. This is used to separate items. + # The default is a line break. + [Parameter(ValueFromPipelineByPropertyName)] + [PSObject] + $ItemSeparator = "
", + + # If set, this will not include a submit button. + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $NoSubmit, + + # If set, this will not include a reset button. + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $NoReset + ) + + + process { + # First, check our attributes + foreach ($attr in $Attribute) { + # to see if we have an HTML Input attribute. + if ($attr -is [Reflection.AssemblyMetadataAttribute] -and + $attr.Key -match '^HTML\p{P}?Input') { + # If we do, return it. + return $attr.Value + } + } + + + # Then copy the parameters + $myParameterCopy = [Ordered]@{} + $PSBoundParameters + $elementOrder = + @(if ($elementMap -is [Collections.IDictionary]) { + $elementMap.Keys + } else { + $elementMap.PSObject.Properties.Name + }) + + + + $ItemsInContainer = @( + # Many parameters will be turned directly into elements, + # using the -ElementMap and -ElementAttributeMap + foreach ($potentialElement in $elementOrder) { + @(if ($myParameterCopy.($potentialElement)) { + $ElementSplat = [Ordered]@{ + Name = $elementMap.$potentialElement + Content = "$($myParameterCopy.$potentialElement)" + } + if ($ElementAttributeMap.$potentialElement) { + $ElementSplat.Attribute = $ElementAttributeMap.$potentialElement + } + Template.HTML.Element @ElementSplat + }) -join $ElementSeparator + } + + # Any parameters should be turned into input elements. + if ($parameter -is [Collections.IDictionary]) { + $parameter.Values | Template.HTML.Parameter.Input + } elseif ($Parameter) { + $Parameter | Template.HTML.Parameter.Input + } + $CamelCaseSpace = [Regex]::new('(?(?<=[a-z])(?=[A-Z]))') + $CamelCaseOrPunctuation = [Regex]::new("(?>[\s\p{P}]|$CamelCaseSpace)") + if ($ResetText) { + if (-not $NoReset) { + Template.HTML.InputElement -InputType reset -Value $ResetText + } + } + if (-not $NoSubmit -and ($name -or $DisplayName)) { + Template.HTML.InputElement -InputType submit -Value $( + $(if ($DisplayName) { $DisplayName } else { $Name }) -replace $CamelCaseOrPunctuation,' ' + ) + } + ) + + # Now we want to put all of the items into a container. + $ContainerContent = + @(foreach ($itemInContainer in $ItemsInContainer) { + # If we have an item element, wrap it in that element. + if ($ItemElement) { + Template.HTML.Element -Name $ItemElement -Content $itemInContainer -Attribute $ItemAttribute + } else { + $itemInContainer + } + }) -join $ItemSeparator # If we have an item separator, join the items with it. + + # If we have a container element, wrap the all the items in that element. + if ($ContainerElement) { + Template.HTML.Element -Name $ContainerElement -Content $ContainerContent -Attribute $ContainerAttribute -Id $Id + } else { + $ContainerContent # Otherwise, return the items directly. + } + } + +} + diff --git a/Languages/HTML/Templates/Input/HTML-Template-InputElement.ps.ps1 b/Languages/HTML/Templates/Input/HTML-Template-InputElement.ps.ps1 new file mode 100644 index 000000000..67ef878c5 --- /dev/null +++ b/Languages/HTML/Templates/Input/HTML-Template-InputElement.ps.ps1 @@ -0,0 +1,230 @@ +[ValidatePattern('HTML')] +param() + +Template function HTML.InputElement { + <# + .SYNOPSIS + Template for an HTML input. + .DESCRIPTION + A Template for an HTML input element. + .LINK + https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input + #> + [CmdletBinding(PositionalBinding=$false)] + param( + # The name of the input. + [vbn()] + [Alias('InputName')] + [string] + $Name, + + # The type of input. + [vbn()] + [alias('type')] + [ValidateSet("button","checkbox","color","date","datetime-local", + "email","file","hidden","image","month","number", + "password","radio","range","reset","search","submit", + "tel","text","time","url","week")] + [string] + $InputType, + + # The ID of the input. + [vbn()] + [string] + $ID, + + # The class of the input. + [vbn()] + [Alias('CssClass')] + [string[]] + $Class, + + # The label of the input. + [vbn()] + [string] + $Label, + + # The value of the input. + [vbn()] + [string] + $Value, + + # The placeholder of the input. + [vbn()] + [string] + $Placeholder, + + # If the input is required. + [vbn()] + [switch] + $Required, + + # If the input is disabled. + [vbn()] + [switch] + $Disabled, + + # If the input is readonly. + [vbn()] + [switch] + $ReadOnly, + + # If the input is checked. + [vbn()] + [switch] + $Checked, + + # The minimum value of the input. + [vbn()] + [string] + $Min, + + # The maximum value of the input. + [vbn()] + [string] + $Max, + + # The step value of the input. + [vbn()] + [string] + $Step, + + # The pattern of the input. + [vbn()] + [string] + $Pattern, + + # The autocomplete of the input. + [vbn()] + [ValidateSet("on","off")] + [string] + $AutoComplete, + + # The form that the input is associated with. + [vbn()] + [string] + $Form, + + # The formaction of the input. + [vbn()] + [string] + $FormAction, + + # The formenctype of the input. + [vbn()] + [Alias('FormEnc','FormEncType')] + [ValidateSet("application/x-www-form-urlencoded","multipart/form-data","text/plain")] + [string] + $FormEncodingType, + + # The formmethod of the input. + [vbn()] + [Alias('FormMeth')] + [ValidateSet("get","post")] + [string] + $FormMethod, + + # The formnovalidate of the input. + [vbn()] + [switch] + $FormNoValidate, + + # The formtarget of the input. + [vbn()] + [Alias('FormTarg')] + [ValidateSet("_blank","_self","_parent","_top")] + [string] + $FormTarget, + + # The height of the input. + [vbn()] + [string] + $Height, + + # The width of the input. + [vbn()] + [string] + $Width, + + # The list of the input. + [vbn()] + [string] + $List, + + # The minimum length of the input. + [vbn()] + [Alias('MinimumLength')] + [int] + $MinLength, + + # If set, an email input can accept multiple emails, or a file input can accept multiple files. + [vbn()] + [switch] + $Multiple, + + # The size of the input. + [vbn()] + [string] + $Size, + + # The accept of the input. + [vbn()] + [string] + $Accept, + + # The accept-charset of the input. + [vbn()] + [string] + $AcceptCharset, + + # The autofocus of the input. + [vbn()] + [switch] + $AutoFocus + ) + + process { + $InputAttributes = @( + if ($ID) { "id='$ID'" } + if ($Class) { "class='$($Class -join ' ')'" } + if ($inputType) { "type='$inputType'" } + if ($name) { "name='$name'" } + if ($value) { "value='$value'" } + if ($placeholder) { "placeholder='$placeholder'" } + if ($required) { "required" } + if ($disabled) { "disabled" } + if ($readOnly) { "readonly" } + if ($checked) { "checked" } + if ($min) { "min='$min'" } + if ($max) { "max='$max'" } + if ($step) { "step='$step'" } + if ($pattern) { "pattern='$pattern'" } + if ($autoComplete) { "autocomplete='$autoComplete'" } + if ($form) { "form='$form'" } + if ($formAction) { "formaction='$formAction'" } + if ($formEncodingType) { "formenctype='$formEncodingType'" } + if ($formMethod) { "formmethod='$formMethod'" } + if ($formNoValidate) { "formnovalidate" } + if ($formTarget) { "formtarget='$formTarget'" } + if ($height) { "height='$height'" } + if ($width) { "width='$width'" } + if ($list) { "list='$list'" } + if ($multiple) { "multiple" } + if ($size) { "size='$size'" } + if ($accept) { "accept='$accept'" } + if ($acceptCharset) { "accept-charset='$acceptCharset'" } + if ($autoFocus) { "autofocus" } + ) -join ' ' + + + @( + if ($Label -and $ID) { + if ($label -notmatch '" + ) -join [Environment]::newLine + } +} \ No newline at end of file diff --git a/Languages/HTML/Templates/Input/HTML-Template-InputElement.ps1 b/Languages/HTML/Templates/Input/HTML-Template-InputElement.ps1 new file mode 100644 index 000000000..909c71f2a --- /dev/null +++ b/Languages/HTML/Templates/Input/HTML-Template-InputElement.ps1 @@ -0,0 +1,234 @@ +[ValidatePattern('HTML')] +param() + + +function Template.HTML.InputElement { + + <# + .SYNOPSIS + Template for an HTML input. + .DESCRIPTION + A Template for an HTML input element. + .LINK + https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input + #> + [CmdletBinding(PositionalBinding=$false)] + param( + # The name of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('InputName')] + [string] + $Name, + + # The type of input. + [Parameter(ValueFromPipelineByPropertyName)] + [alias('type')] + [ValidateSet("button","checkbox","color","date","datetime-local", + "email","file","hidden","image","month","number", + "password","radio","range","reset","search","submit", + "tel","text","time","url","week")] + [string] + $InputType, + + # The ID of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $ID, + + # The class of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('CssClass')] + [string[]] + $Class, + + # The label of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Label, + + # The value of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Value, + + # The placeholder of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Placeholder, + + # If the input is required. + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $Required, + + # If the input is disabled. + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $Disabled, + + # If the input is readonly. + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $ReadOnly, + + # If the input is checked. + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $Checked, + + # The minimum value of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Min, + + # The maximum value of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Max, + + # The step value of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Step, + + # The pattern of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Pattern, + + # The autocomplete of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [ValidateSet("on","off")] + [string] + $AutoComplete, + + # The form that the input is associated with. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Form, + + # The formaction of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $FormAction, + + # The formenctype of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('FormEnc','FormEncType')] + [ValidateSet("application/x-www-form-urlencoded","multipart/form-data","text/plain")] + [string] + $FormEncodingType, + + # The formmethod of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('FormMeth')] + [ValidateSet("get","post")] + [string] + $FormMethod, + + # The formnovalidate of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $FormNoValidate, + + # The formtarget of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('FormTarg')] + [ValidateSet("_blank","_self","_parent","_top")] + [string] + $FormTarget, + + # The height of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Height, + + # The width of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Width, + + # The list of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $List, + + # The minimum length of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('MinimumLength')] + [int] + $MinLength, + + # If set, an email input can accept multiple emails, or a file input can accept multiple files. + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $Multiple, + + # The size of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Size, + + # The accept of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Accept, + + # The accept-charset of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $AcceptCharset, + + # The autofocus of the input. + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $AutoFocus + ) + + process { + $InputAttributes = @( + if ($ID) { "id='$ID'" } + if ($Class) { "class='$($Class -join ' ')'" } + if ($inputType) { "type='$inputType'" } + if ($name) { "name='$name'" } + if ($value) { "value='$value'" } + if ($placeholder) { "placeholder='$placeholder'" } + if ($required) { "required" } + if ($disabled) { "disabled" } + if ($readOnly) { "readonly" } + if ($checked) { "checked" } + if ($min) { "min='$min'" } + if ($max) { "max='$max'" } + if ($step) { "step='$step'" } + if ($pattern) { "pattern='$pattern'" } + if ($autoComplete) { "autocomplete='$autoComplete'" } + if ($form) { "form='$form'" } + if ($formAction) { "formaction='$formAction'" } + if ($formEncodingType) { "formenctype='$formEncodingType'" } + if ($formMethod) { "formmethod='$formMethod'" } + if ($formNoValidate) { "formnovalidate" } + if ($formTarget) { "formtarget='$formTarget'" } + if ($height) { "height='$height'" } + if ($width) { "width='$width'" } + if ($list) { "list='$list'" } + if ($multiple) { "multiple" } + if ($size) { "size='$size'" } + if ($accept) { "accept='$accept'" } + if ($acceptCharset) { "accept-charset='$acceptCharset'" } + if ($autoFocus) { "autofocus" } + ) -join ' ' + + + @( + if ($Label -and $ID) { + if ($label -notmatch '" + ) -join [Environment]::newLine + } + +} + diff --git a/Languages/HTML/Templates/Input/HTML-Template-Parameter-Input.ps.ps1 b/Languages/HTML/Templates/Input/HTML-Template-Parameter-Input.ps.ps1 new file mode 100644 index 000000000..d0b7da8fa --- /dev/null +++ b/Languages/HTML/Templates/Input/HTML-Template-Parameter-Input.ps.ps1 @@ -0,0 +1,165 @@ +Template function HTML.Parameter.Input { + <# + .SYNOPSIS + Generates an HTML parameter input. + .DESCRIPTION + Generates an HTML input element for a parameter. + #> + [CmdletBinding(DefaultParameterSetName='None')] + param( + # The Parameter Metadata. This can be provided via the pipeline from the Parameter.Values of any command. + [vfp(Mandatory,ParameterSetName='ParameterMetadata')] + [Management.Automation.ParameterMetaData] + $ParameterMetadata, + + # The name of the command. + [vbn()] + [string] + $CommandName, + + # The name of the parameter. + [vbn()] + [string] + $ParameterName, + + # The parameter attributes + [vbn()] + [Alias('Attribute','Attributes')] + [PSObject[]] + $ParameterAttribute, + + # The parameter type + [vbn()] + [type] + $ParameterType, + + # The parameter help. + [vbn()] + [string] + $ParameterHelp, + + # If set, the automatic parameters will be included. + # (by default, they will be hidden) + [vbn()] + [Alias('IncludeAutomaticParameters')] + [switch] + $IncludeAutomaticParameter + ) + + process { + + if ($PSCmdlet.ParameterSetName -eq 'ParameterMetadata') { + $ParameterName = $ParameterMetadata.Name + $ParameterType = $ParameterMetadata.ParameterType + $Alias = $ParameterMetadata.Aliases + $ParameterAttribute = $ParameterMetadata.Attributes + } + + if (-not $IncludeAutomaticParameter) { + if ($ParameterName -in 'Verbose','Debug', + 'ErrorAction','ErrorVariable', + 'WarningAction','WarningVariable', + 'InformationAction','InformationVariable', + 'ProgressAction', + 'Confirm','WhatIf', + 'OutBuffer','OutVariable', + 'PipelineVariable' + ) { + return + } + } + + $htmlInputParameters = [Ordered]@{ + Name = $ParameterName + } + + if ($CommandName) { + $htmlInputParameters.id = "$CommandName-$ParameterName" -replace '\p{P}', '-' -replace "\s", '_' + } else { + $htmlInputParameters.id = $ParameterName -replace '\p{P}', '-' -replace "\s", '_' + } + + $validValuesList = @() + + :PickingInputType switch ($ParameterType) { + { $_ -in [int], [double] } { + $htmlInputParameters.type = 'number' + break + } + { $_ -eq [DateTime]} { + $htmlInputParameters.type = 'datetime-local' + break + } + { $_ -eq [bool] -or $_ -eq [switch]} { + $htmlInputParameters.type = 'checkbox' + break + } + { $_.IsSubclassOf([Enum]) } { + $validValuesList += [Enum]::GetNames($ParameterType) + } + { $_ -eq [IO.FileInfo]} { + $htmlInputParameters.type = 'file' + break + } + default { + switch -regex ($ParameterName) { + 'Colou?r' { + $htmlInputParameters.type = 'color' + break PickingInputType + } + 'Email' { + $htmlInputParameters.type = 'email' + break PickingInputType + + } + } + $htmlInputParameters.type = 'text' + } + } + + + + + foreach ($attribute in $ParameterAttribute) { + switch ($attribute) { + [ValidateRange] { + $htmlInputParameters.min = $attribute.Minimum + $htmlInputParameters.max = $attribute.Maximum + } + [ValidatePattern] { + $htmlInputParameters.pattern = $attribute.RegexPattern + } + [ValidateSet] { + $validValuesList += $attribute.ValidValues + } + [Management.Automation.HiddenAttribute] { + # If the parameter is hidden, do not show it. + return + } + [Reflection.AssemblyMetadataAttribute] { + if ($attribute.Key -match 'Html\.?InputType') { + $htmlInputParameters.type = $attribute.Value + } + elseif ($attribute.Key -eq 'HTML.Input' -or 'Input.HTML') { + return $attribute.Value + } + } + } + } + + + if ($validValuesList) { + @( + "" + "" + ) -join [Environment]::newLine + } else { + $htmlInputParameters.Label = $ParameterName + Template.HTML.InputElement @htmlInputParameters + } + } +} diff --git a/Languages/HTML/Templates/Input/HTML-Template-Parameter-Input.ps1 b/Languages/HTML/Templates/Input/HTML-Template-Parameter-Input.ps1 new file mode 100644 index 000000000..d0db5e75d --- /dev/null +++ b/Languages/HTML/Templates/Input/HTML-Template-Parameter-Input.ps1 @@ -0,0 +1,175 @@ + +function Template.HTML.Parameter.Input { + + <# + .SYNOPSIS + Generates an HTML parameter input. + .DESCRIPTION + Generates an HTML input element for a parameter. + #> + [CmdletBinding(DefaultParameterSetName='None')] + param( + # The Parameter Metadata. This can be provided via the pipeline from the Parameter.Values of any command. + [Parameter(Mandatory,ParameterSetName='ParameterMetadata',ValueFromPipeline)] + [Management.Automation.ParameterMetaData] + $ParameterMetadata, + + # The name of the command. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $CommandName, + + # The name of the parameter. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $ParameterName, + + # The parameter attributes + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Attribute','Attributes')] + [PSObject[]] + $ParameterAttribute, + + # The parameter type + [Parameter(ValueFromPipelineByPropertyName)] + [type] + $ParameterType, + + # The parameter help. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $ParameterHelp, + + # If set, the automatic parameters will be included. + # (by default, they will be hidden) + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('IncludeAutomaticParameters')] + [switch] + $IncludeAutomaticParameter + ) + + process { + + if ($PSCmdlet.ParameterSetName -eq 'ParameterMetadata') { + $ParameterName = $ParameterMetadata.Name + $ParameterType = $ParameterMetadata.ParameterType + $Alias = $ParameterMetadata.Aliases + $ParameterAttribute = $ParameterMetadata.Attributes + } + + if (-not $IncludeAutomaticParameter) { + if ($ParameterName -in 'Verbose','Debug', + 'ErrorAction','ErrorVariable', + 'WarningAction','WarningVariable', + 'InformationAction','InformationVariable', + 'ProgressAction', + 'Confirm','WhatIf', + 'OutBuffer','OutVariable', + 'PipelineVariable' + ) { + return + } + } + + $htmlInputParameters = [Ordered]@{ + Name = $ParameterName + } + + if ($CommandName) { + $htmlInputParameters.id = "$CommandName-$ParameterName" -replace '\p{P}', '-' -replace "\s", '_' + } else { + $htmlInputParameters.id = $ParameterName -replace '\p{P}', '-' -replace "\s", '_' + } + + $validValuesList = @() + + :PickingInputType switch ($ParameterType) { + { $_ -in [int], [double] } { + $htmlInputParameters.type = 'number' + break + } + { $_ -eq [DateTime]} { + $htmlInputParameters.type = 'datetime-local' + break + } + { $_ -eq [bool] -or $_ -eq [switch]} { + $htmlInputParameters.type = 'checkbox' + break + } + { $_.IsSubclassOf([Enum]) } { + $validValuesList += [Enum]::GetNames($ParameterType) + } + { $_ -eq [IO.FileInfo]} { + $htmlInputParameters.type = 'file' + break + } + default { + switch -regex ($ParameterName) { + 'Colou?r' { + $htmlInputParameters.type = 'color' + break PickingInputType + } + 'Email' { + $htmlInputParameters.type = 'email' + break PickingInputType + + } + } + $htmlInputParameters.type = 'text' + } + } + + + + + foreach ($attribute in $ParameterAttribute) { + switch ($attribute) { + {$_ -is [ValidateRange]} + { + $htmlInputParameters.min = $attribute.Minimum + $htmlInputParameters.max = $attribute.Maximum + } + {$_ -is [ValidatePattern]} + { + $htmlInputParameters.pattern = $attribute.RegexPattern + } + {$_ -is [ValidateSet]} + { + $validValuesList += $attribute.ValidValues + } + {$_ -is [Management.Automation.HiddenAttribute]} + { + # If the parameter is hidden, do not show it. + return + } + {$_ -is [Reflection.AssemblyMetadataAttribute]} + { + if ($attribute.Key -match 'Html\.?InputType') { + $htmlInputParameters.type = $attribute.Value + } + elseif ($attribute.Key -eq 'HTML.Input' -or 'Input.HTML') { + return $attribute.Value + } + } + } + } + + + if ($validValuesList) { + @( + "" + "" + ) -join [Environment]::newLine + } else { + $htmlInputParameters.Label = $ParameterName + Template.HTML.InputElement @htmlInputParameters + } + } + +} + + diff --git a/Languages/HTML/Templates/Layout/HTML-Default-Layout.ps.ps1 b/Languages/HTML/Templates/Layout/HTML-Default-Layout.ps.ps1 new file mode 100644 index 000000000..8d65572a0 --- /dev/null +++ b/Languages/HTML/Templates/Layout/HTML-Default-Layout.ps.ps1 @@ -0,0 +1,314 @@ +[ValidatePattern('HTML')] +param() + +Template function HTML.Default.Layout { + <# + .SYNOPSIS + The default HTML layout. + .DESCRIPTION + The template for a default HTML layout. + + This generates a single HTML page with the provided content and metadata. + + HTML.Default.Layout -Name 'My Page' -Content 'This is my page' + #> + [CmdletBinding(PositionalBinding=$false)] + [Alias('New-WebPage','Template.default.html')] + param( + # The name of the page. This will set the title tag. + [vbn(Position=0)] + [Alias('Title','DisplayName')] + [string] + $Name, + + # The content of the page. This is the body of the page. + [vbn(Position=1)] + [Alias('PageContent','Body')] + [psobject] + $Content, + + # The separator. This will be placed between multiple content items, if `-Content` is an array. + # By default, this is a line break. + [vbn()] + [psobject] + $Separator = "
", + + # The name of the site. If set, it will include the og:site_name meta tag. + [vbn()] + [Alias('SiteTitle','WebsiteName')] + [string] + $SiteName, + + # The page type. By default 'website'. + # This sets the og:type meta tag. + # Cannonically, this can be set to 'article', 'book', 'profile', 'video', 'music', 'movie', 'restaurant', 'product', 'place', 'game', 'app', 'event', or 'author'. + [vbn()] + [string] + $PageType = 'website', + + # The description of the page. If set, it will include the og:description meta tag. + [vbn()] + [Alias('Desc')] + [string] + $Description, + + # One or more stylesheets. If this ends in .CSS, it will be included as a link tag. Otherwise, it will be included as a style tag. + [vbn()] + [Alias('CSS')] + [psobject[]] + $StyleSheet, + + # One or more RSS feeds to include in the page. If set, it will automatically include the link tag. + [vbn()] + [Alias('Feed','Feeds','RSSFeed','RSSUrl')] + [PSObject] + $RSS, + + # One or more Atom feeds to include in the page. If set, it will automatically include the link tag. + [vbn()] + [Alias('AtomFeed','AtomUrl')] + [PSObject] + $Atom, + + # Any JavaScripts to include in the page. If set, it will include the script tag. + [vbn()] + [Alias('JavaScripts','JS')] + [psobject[]] + $JavaScript, + + # The name of the palette to use. If set, it will include the 4bitcss link tag. + # This is a CSS file that sets the foreground and background colors. + # If palette contains slashes, it will be presumed to be an external palette file. + # This palette file _should_ follow the same conventions as those found in [4bitcss](https://4bitcss.com). + [vbn()] + [Alias('Palette','ColorScheme','ColorPalette')] + [string[]] + $PaletteName, + + # One or more google fonts to include + [vbn()] + [string[]] + $GoogleFont, + + # The absolute path to the page. If set, it will include the og:url meta tag. + [vbn()] + [Alias('FullUrl','AbsoluteUrl','AbsoluteUri','Permalink','PermaLinkUrl','PermalinkUri')] + [uri] + $AbsolutePath, + + # The image to use for the page. If set, it will include the og:image meta tag. + [vbn()] + [uri] + $Image, + + # The language of the page. If set, it will include the lang attribute. + [vbn()] + [cultureinfo] + $Language = [cultureinfo]::CurrentUICulture, + + # The date the page was published. If set, it will include the article:published_time meta tag. + [vbn()] + [Alias('PublishDate','Date')] + [DateTime] + $PublishTime, + + # The analytics ID. If set, it will include the Google Analytics tag. + [vbn()] + [string] + $AnalyticsID, + + # The viewport. By default, it is set to 'width=device-width'. + [vbn()] + [string] + $Viewport = 'width=device-width', + + # The width of the page. By default, it is set to '100%'. + [vbn()] + [string] + $Width = '100%', + + # The height of the page. By default, it is set to '100%'. + [vbn()] + [string] + $Height = '100%', + + # The font size of the page. By default, it is set to '1.5em'. + [vbn()] + [string] + $FontSize = '1.5em', + + # The font family of the page. + # If a -GoogleFont has been provided, this will default to the first value. + [vbn()] + [string] + $FontFamily, + + # The page margin. By default, nothing. + [vbn()] + [string] + $Margin, + + # The page padding. By default, nothing. + [vbn()] + [string] + $Padding, + + # Any additional header information for the page + [vbn()] + [Alias('PageHeader')] + [string] + $Head, + + # One or more CSS classes to apply to the body. + [vbn()] + [Alias('CssClass')] + [string[]] + $Class + ) + + begin { + filter Escape { [Security.SecurityElement]::Escape($_) } + filter EscapeAttribute { [Web.HttpUtility]::HtmlAttributeEncode($_) } + + filter ToFeed { + param([string]$FeedType) + if ($_ -is [string] -or $_ -is [uri]) { + "" + } else { + if ($_ -is [Collections.IDictionary]) { + $_ = [PSCustomObject]$_ + } + $_ | . { + param( + [vbn()][Alias('Title','DisplayName','FeedName','Name')][string]$FeedTitle, + [vbn()][Alias('Url','Link','Feed','FeedUri')][uri]$FeedUrl + ) + if (-not $FeedTitle) { $FeedTitle = $(@($FeedType -split '[\p{P}\+]')[1]) } + "" + } + } + } + } + + + process { + $safeTitle = $Name | Escape + + $headerTags = @( + "$SafeTitle" + "" + if ($SiteName) { + "" + } + if ($SafeTitle) { "" } + if ($PageType) { "" } + if ($Description) { + "" + "" + "" + } + if ($PublishTime) { "" } + if ($AbsolutePath) { + "" + "" + if ($AbsolutePath.DnsSafeHost) { + "" + } + } + if ($Image) { + "" + "" + "" + } + + if ($AnalyticsID) { + "" + '' + "" + } + if ($GoogleFont) { + "" + if (-not $PSBoundParameters['FontFamily']) { $FontFamily = $GoogleFont[0] } + } + if ($PaletteName) { + foreach ($NameOfPalette in $PaletteName) { + if ($PaletteName -match '[\\/]') { + "" + } else { + $MachineFriendlyName = $NameOfPalette -replace '\s','-' -replace '\p{P}','-' -replace '-+','-' -replace '-$' + "" + } + + } + $Class += "foreground", "background" + } + + if ($RSS) { + $RSS | ToFeed -FeedType 'application/rss+xml' + } + + if ($Atom) { + $Atom | ToFeed -FeedType 'application/atom+xml' + } + + foreach ($css in $StyleSheet) { + if ($css -match '\.css$') { + "" + } else { + "" + } + } + + $defaultBodyStyle = @( + if ($Width) { "width: $Width" } + if ($Height) { "height: $Height" } + if ($FontSize) { "font-size: $FontSize" } + if ($FontFamily) { "font-family: $FontFamily" } + if ($Margin) { "margin: $Margin" } + if ($Padding) { "padding: $Padding" } + ) -join ';' + + if ($defaultBodyStyle) { + "" + } + + foreach ($js in $JavaScript) { + if ($js -notmatch '\n' -and $js -match '\.js$') { + "" + } + elseif ($js -notmatch '^\s{0,}$js" + } + else { + $js + } + } + + if ($Head) { + $Head + } + ) -join ([Environment]::newLine + (' ' * 4)) + + +@" + + + + $headerTags + + +$( + if ($content) { + if ($content -is [Collections.IList]) { + $content -join $Separator + } else { + $content + } + } +) + + +"@ + } +} + diff --git a/Languages/HTML/Templates/Layout/HTML-Default-Layout.ps1 b/Languages/HTML/Templates/Layout/HTML-Default-Layout.ps1 new file mode 100644 index 000000000..6de852d9a --- /dev/null +++ b/Languages/HTML/Templates/Layout/HTML-Default-Layout.ps1 @@ -0,0 +1,325 @@ +[ValidatePattern('HTML')] +param() + + +function Template.HTML.Default.Layout { + + <# + .SYNOPSIS + The default HTML layout. + .DESCRIPTION + The template for a default HTML layout. + + This generates a single HTML page with the provided content and metadata. + + HTML.Default.Layout -Name 'My Page' -Content 'This is my page' + #> + [CmdletBinding(PositionalBinding=$false)] + [Alias('New-WebPage','Template.default.html')] + param( + # The name of the page. This will set the title tag. + [Parameter(ValueFromPipelineByPropertyName,Position=0)] + [Alias('Title','DisplayName')] + [string] + $Name, + + # The content of the page. This is the body of the page. + [Parameter(ValueFromPipelineByPropertyName,Position=1)] + [Alias('PageContent','Body')] + [psobject] + $Content, + + # The separator. This will be placed between multiple content items, if `-Content` is an array. + # By default, this is a line break. + [Parameter(ValueFromPipelineByPropertyName)] + [psobject] + $Separator = "
", + + # The name of the site. If set, it will include the og:site_name meta tag. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('SiteTitle','WebsiteName')] + [string] + $SiteName, + + # The page type. By default 'website'. + # This sets the og:type meta tag. + # Cannonically, this can be set to 'article', 'book', 'profile', 'video', 'music', 'movie', 'restaurant', 'product', 'place', 'game', 'app', 'event', or 'author'. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $PageType = 'website', + + # The description of the page. If set, it will include the og:description meta tag. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Desc')] + [string] + $Description, + + # One or more stylesheets. If this ends in .CSS, it will be included as a link tag. Otherwise, it will be included as a style tag. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('CSS')] + [psobject[]] + $StyleSheet, + + # One or more RSS feeds to include in the page. If set, it will automatically include the link tag. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Feed','Feeds','RSSFeed','RSSUrl')] + [PSObject] + $RSS, + + # One or more Atom feeds to include in the page. If set, it will automatically include the link tag. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('AtomFeed','AtomUrl')] + [PSObject] + $Atom, + + # Any JavaScripts to include in the page. If set, it will include the script tag. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('JavaScripts','JS')] + [psobject[]] + $JavaScript, + + # The name of the palette to use. If set, it will include the 4bitcss link tag. + # This is a CSS file that sets the foreground and background colors. + # If palette contains slashes, it will be presumed to be an external palette file. + # This palette file _should_ follow the same conventions as those found in [4bitcss](https://4bitcss.com). + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Palette','ColorScheme','ColorPalette')] + [string[]] + $PaletteName, + + # One or more google fonts to include + [Parameter(ValueFromPipelineByPropertyName)] + [string[]] + $GoogleFont, + + # The absolute path to the page. If set, it will include the og:url meta tag. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('FullUrl','AbsoluteUrl','AbsoluteUri','Permalink','PermaLinkUrl','PermalinkUri')] + [uri] + $AbsolutePath, + + # The image to use for the page. If set, it will include the og:image meta tag. + [Parameter(ValueFromPipelineByPropertyName)] + [uri] + $Image, + + # The language of the page. If set, it will include the lang attribute. + [Parameter(ValueFromPipelineByPropertyName)] + [cultureinfo] + $Language = [cultureinfo]::CurrentUICulture, + + # The date the page was published. If set, it will include the article:published_time meta tag. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('PublishDate','Date')] + [DateTime] + $PublishTime, + + # The analytics ID. If set, it will include the Google Analytics tag. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $AnalyticsID, + + # The viewport. By default, it is set to 'width=device-width'. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Viewport = 'width=device-width', + + # The width of the page. By default, it is set to '100%'. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Width = '100%', + + # The height of the page. By default, it is set to '100%'. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Height = '100%', + + # The font size of the page. By default, it is set to '1.5em'. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $FontSize = '1.5em', + + # The font family of the page. + # If a -GoogleFont has been provided, this will default to the first value. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $FontFamily, + + # The page margin. By default, nothing. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Margin, + + # The page padding. By default, nothing. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Padding, + + # Any additional header information for the page + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('PageHeader')] + [string] + $Head, + + # One or more CSS classes to apply to the body. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('CssClass')] + [string[]] + $Class + ) + + begin { + filter Escape { + [Security.SecurityElement]::Escape($_) + } + filter EscapeAttribute { + [Web.HttpUtility]::HtmlAttributeEncode($_) + } + + filter ToFeed { + + param([string]$FeedType) + if ($_ -is [string] -or $_ -is [uri]) { + "" + } else { + if ($_ -is [Collections.IDictionary]) { + $_ = [PSCustomObject]$_ + } + $_ | . { + param( + [Parameter(ValueFromPipelineByPropertyName)][Alias('Title','DisplayName','FeedName','Name')][string]$FeedTitle, + [Parameter(ValueFromPipelineByPropertyName)][Alias('Url','Link','Feed','FeedUri')][uri]$FeedUrl + ) + if (-not $FeedTitle) { $FeedTitle = $(@($FeedType -split '[\p{P}\+]')[1]) } + "" + } + } + + } + } + + + process { + $safeTitle = $Name | Escape + + $headerTags = @( + "$SafeTitle" + "" + if ($SiteName) { + "" + } + if ($SafeTitle) { "" } + if ($PageType) { "" } + if ($Description) { + "" + "" + "" + } + if ($PublishTime) { "" } + if ($AbsolutePath) { + "" + "" + if ($AbsolutePath.DnsSafeHost) { + "" + } + } + if ($Image) { + "" + "" + "" + } + + if ($AnalyticsID) { + "" + '' + "" + } + if ($GoogleFont) { + "" + if (-not $PSBoundParameters['FontFamily']) { $FontFamily = $GoogleFont[0] } + } + if ($PaletteName) { + foreach ($NameOfPalette in $PaletteName) { + if ($PaletteName -match '[\\/]') { + "" + } else { + $MachineFriendlyName = $NameOfPalette -replace '\s','-' -replace '\p{P}','-' -replace '-+','-' -replace '-$' + "" + } + + } + $Class += "foreground", "background" + } + + if ($RSS) { + $RSS | ToFeed -FeedType 'application/rss+xml' + } + + if ($Atom) { + $Atom | ToFeed -FeedType 'application/atom+xml' + } + + foreach ($css in $StyleSheet) { + if ($css -match '\.css$') { + "" + } else { + "" + } + } + + $defaultBodyStyle = @( + if ($Width) { "width: $Width" } + if ($Height) { "height: $Height" } + if ($FontSize) { "font-size: $FontSize" } + if ($FontFamily) { "font-family: $FontFamily" } + if ($Margin) { "margin: $Margin" } + if ($Padding) { "padding: $Padding" } + ) -join ';' + + if ($defaultBodyStyle) { + "" + } + + foreach ($js in $JavaScript) { + if ($js -notmatch '\n' -and $js -match '\.js$') { + "" + } + elseif ($js -notmatch '^\s{0,}$js" + } + else { + $js + } + } + + if ($Head) { + $Head + } + ) -join ([Environment]::newLine + (' ' * 4)) + + +@" + + + + $headerTags + + +$( + if ($content) { + if ($content -is [Collections.IList]) { + $content -join $Separator + } else { + $content + } + } +) + + +"@ + } + +} + + + diff --git a/Languages/HTML/Templates/Syntax/HTML-Template-Element.ps.ps1 b/Languages/HTML/Templates/Syntax/HTML-Template-Element.ps.ps1 new file mode 100644 index 000000000..9ece92af6 --- /dev/null +++ b/Languages/HTML/Templates/Syntax/HTML-Template-Element.ps.ps1 @@ -0,0 +1,116 @@ +[ValidatePattern("HTML")] +param() + +Template function HTML.Element { + <# + .SYNOPSIS + Template for an HTML element. + .DESCRIPTION + A Template for an HTML element. + .LINK + https://developer.mozilla.org/en-US/docs/Web/HTML/Element + #> + [Alias('Template.Element.html')] + [CmdletBinding(PositionalBinding=$false)] + param( + # The name of the element. + [vbn()] + [Alias('ElementName')] + [string] + $Name, + + # The element identifier. + [vbn()] + [Alias('ElementId')] + [string] + $Id, + + # One or more CSS classes. + [vbn()] + [Alias('ElementClass','CssClass','CssClasses')] + [string[]] + $Class, + + # The attributes of the element. + [vbn()] + [Alias('ElementAttribute','ElementAttributes','Attributes')] + [PSObject] + $Attribute, + + # Any data attributes of the element. + [vbn()] + [Alias('DataAttribute','DataAttributes')] + [PSObject] + $Data, + + # The content of the element. + [vbn()] + [psobject] + $Content, + + # The separator. + # This will be placed between multiple content items, if `-Content` is an array. + [vbn()] + [psobject] + $Separator + ) + + process { + $attributesString = @( + if ($Id) { + " id='$Id'" + } + if ($Class) { + " class='$($Class -join ' ')" + } + if ($Data) { + if ($Data -is [Collections.IDictionary]) { + $Data = [PSCustomObject]$Data + } + $CamelCaseSpace = [Regex]::new('(?(?<=[a-z])(?=[A-Z]))') + @(foreach ($property in $Data.PSObject.Properties) { + $propertyName = $property.Name -replace $CamelCaseSpace,'-' -replace '\s','_' + $propertyValue = $property.Value + " data-$propertyName='$propertyValue'" + }) -join '' + } + if ($Attribute) { + if ($attribute -is [Collections.IDictionary]) { + $attribute = [PSCustomObject]$attribute + } + @(foreach ($property in $attribute.PSObject.Properties) { + $propertyName = $property.Name -replace '\p{P}','-' -replace '\s','_' + $propertyValue = $property.Value + if ($propertyValue -is [switch]) { + $propertyValue = $propertyValue -as [bool] + " $propertyName=$($propertyValue.ToString().ToLower())" + } + elseif ($propertyValue -is [int] -or $propertyValue -is [double]) { + " $propertyName='$propertyValue'" + } else { + " $propertyName='$propertyValue'" + } + }) -join '' + } + ) -join '' + + + if (-not $content) { + "<${Name}${AttributeString} />" -replace "\s{1,}\/\>$", "/>" + } else { + $innerContent = if ($content -is [Collections.IList]) { + if ($content.OuterXML) { + $content.OuterXML -join $Separator + } else { + $content -join $Separator + } + } elseif ($content.OuterXML) { + $content.OuterXML + } else { + $content + } + + "<${Name}${attributesString}>$innerContent" + } + } +} diff --git a/Languages/HTML/Templates/Syntax/HTML-Template-Element.ps1 b/Languages/HTML/Templates/Syntax/HTML-Template-Element.ps1 new file mode 100644 index 000000000..921e5dcfd --- /dev/null +++ b/Languages/HTML/Templates/Syntax/HTML-Template-Element.ps1 @@ -0,0 +1,121 @@ +[ValidatePattern("HTML")] +param() + + +function Template.HTML.Element { + + <# + .SYNOPSIS + Template for an HTML element. + .DESCRIPTION + A Template for an HTML element. + .LINK + https://developer.mozilla.org/en-US/docs/Web/HTML/Element + #> + [Alias('Template.Element.html')] + [CmdletBinding(PositionalBinding=$false)] + param( + # The name of the element. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('ElementName')] + [string] + $Name, + + # The element identifier. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('ElementId')] + [string] + $Id, + + # One or more CSS classes. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('ElementClass','CssClass','CssClasses')] + [string[]] + $Class, + + # The attributes of the element. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('ElementAttribute','ElementAttributes','Attributes')] + [PSObject] + $Attribute, + + # Any data attributes of the element. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('DataAttribute','DataAttributes')] + [PSObject] + $Data, + + # The content of the element. + [Parameter(ValueFromPipelineByPropertyName)] + [psobject] + $Content, + + # The separator. + # This will be placed between multiple content items, if `-Content` is an array. + [Parameter(ValueFromPipelineByPropertyName)] + [psobject] + $Separator + ) + + process { + $attributesString = @( + if ($Id) { + " id='$Id'" + } + if ($Class) { + " class='$($Class -join ' ')" + } + if ($Data) { + if ($Data -is [Collections.IDictionary]) { + $Data = [PSCustomObject]$Data + } + $CamelCaseSpace = [Regex]::new('(?(?<=[a-z])(?=[A-Z]))') + @(foreach ($property in $Data.PSObject.Properties) { + $propertyName = $property.Name -replace $CamelCaseSpace,'-' -replace '\s','_' + $propertyValue = $property.Value + " data-$propertyName='$propertyValue'" + }) -join '' + } + if ($Attribute) { + if ($attribute -is [Collections.IDictionary]) { + $attribute = [PSCustomObject]$attribute + } + @(foreach ($property in $attribute.PSObject.Properties) { + $propertyName = $property.Name -replace '\p{P}','-' -replace '\s','_' + $propertyValue = $property.Value + if ($propertyValue -is [switch]) { + $propertyValue = $propertyValue -as [bool] + " $propertyName=$($propertyValue.ToString().ToLower())" + } + elseif ($propertyValue -is [int] -or $propertyValue -is [double]) { + " $propertyName='$propertyValue'" + } else { + " $propertyName='$propertyValue'" + } + }) -join '' + } + ) -join '' + + + if (-not $content) { + "<${Name}${AttributeString} />" -replace "\s{1,}\/\>$", "/>" + } else { + $innerContent = if ($content -is [Collections.IList]) { + if ($content.OuterXML) { + $content.OuterXML -join $Separator + } else { + $content -join $Separator + } + } elseif ($content.OuterXML) { + $content.OuterXML + } else { + $content + } + + "<${Name}${attributesString}>$innerContent" + } + } + +} + + diff --git a/Languages/HTML/Templates/Syntax/HTML-Template-Script.ps.ps1 b/Languages/HTML/Templates/Syntax/HTML-Template-Script.ps.ps1 new file mode 100644 index 000000000..48305d03e --- /dev/null +++ b/Languages/HTML/Templates/Syntax/HTML-Template-Script.ps.ps1 @@ -0,0 +1,106 @@ +[ValidatePattern("HTML")] +param() + +Template function HTML.Script { + <# + .SYNOPSIS + Template for a Script tag + .DESCRIPTION + A Template for the script tag. + #> + [Alias('Template.Script.html')] + param( + # The URL of the script. + [vbn()] + [Alias('Uri','Link')] + [uri] + $Url, + + # The inline JavaScript. + [vbn()] + [Alias('Script')] + [string] + $JavaScript, + + # If the script should be loaded asynchronously. + [vbn()] + [switch] + $Async, + + # If the script should not allow EMCA modules + [vbn()] + [switch] + $NoModule, + + # If the script should be deferred. + [vbn()] + [switch] + $Defer, + + # If the script should be blocking. + [vbn()] + [switch] + $Blocking, + + # The fetch priority of the script. + [vbn()] + [ValidateSet("high","low","auto")] + [string] + $FetchPriority, + + # The cross-origin policy of the script. + [vbn()] + [Alias('CORS')] + [string] + $CrossOrigin, + + # The integrity value of the script. + [vbn()] + [string] + $Integrity, + + # The nonce value of the script. + [vbn()] + [string] + $Nonce, + + # The referrer policy of the script. + [vbn()] + [ValidateSet("no-referrer","no-referrer-when-downgrade", + "origin","origin-when-cross-origin", + "same-origin","strict-origin", + "strict-origin-when-cross-origin","unsafe-url")] + [string] + $ReferrerPolicy, + + # The script type + [vbn()] + [string] + $ScriptType + ) + + process { + $ScriptAttributes = @( + if ($async) {"async"} + if ($defer) {"defer"} + if ($Blocking) {"blocking"} + if ($ScriptType) {"type='$ScriptType'"} + if ($FetchPriority) {"fetchpriority='$($fetchPriority.ToLower())'"} + if ($ReferrerPolicy) {"referrerpolicy='$($ReferrerPolicy.ToLower())'"} + if ($CrossOrigin) {"crossorigin='$crossOrigin'"} + if ($Nonce) {"nonce='$nonce'"} + if ($Integrity) {"integrity='$integrity'"} + ) -join ' ' + + if ($url) { + "" + } + elseif ($JavaScript) { + "$JavaScript" + } + } +} \ No newline at end of file diff --git a/Languages/HTML/Templates/Syntax/HTML-Template-Script.ps1 b/Languages/HTML/Templates/Syntax/HTML-Template-Script.ps1 new file mode 100644 index 000000000..02ba1edb1 --- /dev/null +++ b/Languages/HTML/Templates/Syntax/HTML-Template-Script.ps1 @@ -0,0 +1,110 @@ +[ValidatePattern("HTML")] +param() + + +function Template.HTML.Script { + + <# + .SYNOPSIS + Template for a Script tag + .DESCRIPTION + A Template for the script tag. + #> + [Alias('Template.Script.html')] + param( + # The URL of the script. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Uri','Link')] + [uri] + $Url, + + # The inline JavaScript. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Script')] + [string] + $JavaScript, + + # If the script should be loaded asynchronously. + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $Async, + + # If the script should not allow EMCA modules + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $NoModule, + + # If the script should be deferred. + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $Defer, + + # If the script should be blocking. + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $Blocking, + + # The fetch priority of the script. + [Parameter(ValueFromPipelineByPropertyName)] + [ValidateSet("high","low","auto")] + [string] + $FetchPriority, + + # The cross-origin policy of the script. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('CORS')] + [string] + $CrossOrigin, + + # The integrity value of the script. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Integrity, + + # The nonce value of the script. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Nonce, + + # The referrer policy of the script. + [Parameter(ValueFromPipelineByPropertyName)] + [ValidateSet("no-referrer","no-referrer-when-downgrade", + "origin","origin-when-cross-origin", + "same-origin","strict-origin", + "strict-origin-when-cross-origin","unsafe-url")] + [string] + $ReferrerPolicy, + + # The script type + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $ScriptType + ) + + process { + $ScriptAttributes = @( + if ($async) {"async"} + if ($defer) {"defer"} + if ($Blocking) {"blocking"} + if ($ScriptType) {"type='$ScriptType'"} + if ($FetchPriority) {"fetchpriority='$($fetchPriority.ToLower())'"} + if ($ReferrerPolicy) {"referrerpolicy='$($ReferrerPolicy.ToLower())'"} + if ($CrossOrigin) {"crossorigin='$crossOrigin'"} + if ($Nonce) {"nonce='$nonce'"} + if ($Integrity) {"integrity='$integrity'"} + ) -join ' ' + + if ($url) { + "" + } + elseif ($JavaScript) { + "$JavaScript" + } + } + +} + diff --git a/Languages/HTML/Templates/Syntax/HTML-Template-StyleSheet.ps.ps1 b/Languages/HTML/Templates/Syntax/HTML-Template-StyleSheet.ps.ps1 new file mode 100644 index 000000000..b1d993add --- /dev/null +++ b/Languages/HTML/Templates/Syntax/HTML-Template-StyleSheet.ps.ps1 @@ -0,0 +1,25 @@ +[ValidatePattern("HTML")] +param() + +Template function HTML.StyleSheet { + <# + .SYNOPSIS + Template for a StyleSheet link + .DESCRIPTION + A Template for the link to a StyleSheet. + #> + [Alias('Template.Stylesheet.html')] + param( + # The URL of the stylesheet. + [vbn()] + [Alias('Uri','Link')] + [uri] + $Url + ) + + process { + if ($url) { + "" + } + } +} diff --git a/Languages/HTML/Templates/Syntax/HTML-Template-StyleSheet.ps1 b/Languages/HTML/Templates/Syntax/HTML-Template-StyleSheet.ps1 new file mode 100644 index 000000000..db9fa0a9b --- /dev/null +++ b/Languages/HTML/Templates/Syntax/HTML-Template-StyleSheet.ps1 @@ -0,0 +1,30 @@ +[ValidatePattern("HTML")] +param() + + +function Template.HTML.StyleSheet { + + <# + .SYNOPSIS + Template for a StyleSheet link + .DESCRIPTION + A Template for the link to a StyleSheet. + #> + [Alias('Template.Stylesheet.html')] + param( + # The URL of the stylesheet. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Uri','Link')] + [uri] + $Url + ) + + process { + if ($url) { + "" + } + } + +} + + diff --git a/Languages/Haxe/Haxe-Language.ps.ps1 b/Languages/Haxe/Haxe-Language.ps.ps1 index 3a457755f..58ab5beed 100644 --- a/Languages/Haxe/Haxe-Language.ps.ps1 +++ b/Languages/Haxe/Haxe-Language.ps.ps1 @@ -1,3 +1,7 @@ +[ValidatePattern("(?>Haxe|Language)[\s\p{P}]")] +param() + + Language function Haxe { <# .SYNOPSIS diff --git a/Languages/Haxe/Haxe-Language.ps1 b/Languages/Haxe/Haxe-Language.ps1 index e880865d5..250a114a8 100644 --- a/Languages/Haxe/Haxe-Language.ps1 +++ b/Languages/Haxe/Haxe-Language.ps1 @@ -1,3 +1,7 @@ +[ValidatePattern("(?>Haxe|Language)[\s\p{P}]")] +param() + + function Language.Haxe { <# diff --git a/Languages/JSON/JSON-Language.ps.ps1 b/Languages/JSON/JSON-Language.ps.ps1 index 9ef4c63b7..437d5ba55 100644 --- a/Languages/JSON/JSON-Language.ps.ps1 +++ b/Languages/JSON/JSON-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>JSON|Language)[\s\p{P}]")] +param() + Language function JSON { <# .SYNOPSIS diff --git a/Languages/JSON/JSON-Language.ps1 b/Languages/JSON/JSON-Language.ps1 index e660216c0..c3103df39 100644 --- a/Languages/JSON/JSON-Language.ps1 +++ b/Languages/JSON/JSON-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>JSON|Language)[\s\p{P}]")] +param() + function Language.JSON { <# diff --git a/Languages/Java/Java-Language.ps.ps1 b/Languages/Java/Java-Language.ps.ps1 index e20c35736..d698a2557 100644 --- a/Languages/Java/Java-Language.ps.ps1 +++ b/Languages/Java/Java-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Java|Language)[\s\p{P}]")] +param() + Language function Java { <# .SYNOPSIS diff --git a/Languages/Java/Java-Language.ps1 b/Languages/Java/Java-Language.ps1 index 6b8483d07..93eadbe94 100644 --- a/Languages/Java/Java-Language.ps1 +++ b/Languages/Java/Java-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Java|Language)[\s\p{P}]")] +param() + function Language.Java { <# diff --git a/Languages/JavaScript/JavaScript-Language.ps.ps1 b/Languages/JavaScript/JavaScript-Language.ps.ps1 index 5e9b89014..d6e5e4318 100644 --- a/Languages/JavaScript/JavaScript-Language.ps.ps1 +++ b/Languages/JavaScript/JavaScript-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>JavaScript|Language)[\s\p{P}]")] +param() + Language function JavaScript { <# .SYNOPSIS diff --git a/Languages/JavaScript/JavaScript-Language.ps1 b/Languages/JavaScript/JavaScript-Language.ps1 index d3d3d13b9..ba18f34d4 100644 --- a/Languages/JavaScript/JavaScript-Language.ps1 +++ b/Languages/JavaScript/JavaScript-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>JavaScript|Language)[\s\p{P}]")] +param() + function Language.JavaScript { <# diff --git a/Languages/JavaScript/Templates/JavaScript-Template-HelloWorld.ps.ps1 b/Languages/JavaScript/Templates/JavaScript-Template-HelloWorld.ps.ps1 index adaeab9a5..a2259c753 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-HelloWorld.ps.ps1 +++ b/Languages/JavaScript/Templates/JavaScript-Template-HelloWorld.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + Template function HelloWorld.js { <# .SYNOPSIS diff --git a/Languages/JavaScript/Templates/JavaScript-Template-HelloWorld.ps1 b/Languages/JavaScript/Templates/JavaScript-Template-HelloWorld.ps1 index 8c9d80ba1..171a22e54 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-HelloWorld.ps1 +++ b/Languages/JavaScript/Templates/JavaScript-Template-HelloWorld.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + function Template.HelloWorld.js { diff --git a/Languages/JavaScript/Templates/JavaScript-Template-Assignment.ps.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-Assignment.ps.ps1 similarity index 97% rename from Languages/JavaScript/Templates/JavaScript-Template-Assignment.ps.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-Assignment.ps.ps1 index 6f8fdf041..201c26a5c 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-Assignment.ps.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-Assignment.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + Template function Assignment.js { <# .SYNOPSIS diff --git a/Languages/JavaScript/Templates/JavaScript-Template-Assignment.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-Assignment.ps1 similarity index 97% rename from Languages/JavaScript/Templates/JavaScript-Template-Assignment.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-Assignment.ps1 index a7cf36464..56dd47e8f 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-Assignment.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-Assignment.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + function Template.Assignment.js { diff --git a/Languages/JavaScript/Templates/JavaScript-Template-Class.ps.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-Class.ps.ps1 similarity index 95% rename from Languages/JavaScript/Templates/JavaScript-Template-Class.ps.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-Class.ps.ps1 index 573fd47f6..9780e1970 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-Class.ps.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-Class.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + Template function Class.js { <# .SYNOPSIS diff --git a/Languages/JavaScript/Templates/JavaScript-Template-Class.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-Class.ps1 similarity index 95% rename from Languages/JavaScript/Templates/JavaScript-Template-Class.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-Class.ps1 index d68c2bfc5..e6359fa3b 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-Class.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-Class.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + function Template.Class.js { diff --git a/Languages/JavaScript/Templates/JavaScript-Template-DoLoop.ps.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-DoLoop.ps.ps1 similarity index 95% rename from Languages/JavaScript/Templates/JavaScript-Template-DoLoop.ps.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-DoLoop.ps.ps1 index aa21e3a02..a7997e6f9 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-DoLoop.ps.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-DoLoop.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + Template function DoLoop.js { <# .SYNOPSIS diff --git a/Languages/JavaScript/Templates/JavaScript-Template-DoLoop.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-DoLoop.ps1 similarity index 95% rename from Languages/JavaScript/Templates/JavaScript-Template-DoLoop.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-DoLoop.ps1 index f2c5c8dd2..49cad12a0 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-DoLoop.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-DoLoop.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + function Template.DoLoop.js { diff --git a/Languages/JavaScript/Templates/JavaScript-Template-ForLoop.ps.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForLoop.ps.ps1 similarity index 97% rename from Languages/JavaScript/Templates/JavaScript-Template-ForLoop.ps.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForLoop.ps.ps1 index 022794791..17fb4cea2 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-ForLoop.ps.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForLoop.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + Template function ForLoop.js { <# .SYNOPSIS diff --git a/Languages/JavaScript/Templates/JavaScript-Template-ForLoop.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForLoop.ps1 similarity index 97% rename from Languages/JavaScript/Templates/JavaScript-Template-ForLoop.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForLoop.ps1 index 169632d60..17929a3d9 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-ForLoop.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForLoop.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + function Template.ForLoop.js { diff --git a/Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForeachArgument.ps.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForeachArgument.ps.ps1 new file mode 100644 index 000000000..3fd0c8d17 --- /dev/null +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForeachArgument.ps.ps1 @@ -0,0 +1,70 @@ +[ValidatePattern("JavaScript")] +param() + +Template function ForeachArgument.js { + <# + .SYNOPSIS + JavaScript Foreach Argument Template + .DESCRIPTION + A Template for a script that walks over each argument, in JavaScript. + .EXAMPLE + Template.ForeachArgument.js | Set-Content .\Args.js + Invoke-PipeScript -Path .\Args.js -Arguments "a",@{"b"='c'} + #> + [Alias('Template.JavaScript.ForeachArgument')] + param( + # One or more statements + # By default this is `print(sys.argv[i])` + [vbn()] + [Alias('Body','Code','Block','Script','Scripts')] + [string[]] + $Statement = "console.log(args[arg])", + + # One or more statements to run before the loop. + [vbn()] + [string[]] + $Before = 'let args = []', + + # The statement to run after the loop. + [vbn()] + [string[]] + $After, + + # The source of the arguments. + [vbn()] + [string] + $ArgumentSource = 'process.argv.slice(2).forEach(arg => args.push(arg))', + + # The current argument variable. + # This initializes the loop. + [vbn()] + [Alias('CurrentArgument','ArgumentVariable')] + [string] + $Variable = 'arg', + + # The argument collection. + # This is what is looped thru. + [vbn()] + [string] + $Condition = 'args', + + # The number of characters to indent. + [vbn()] + [ValidateRange(1,10)] + [int] + $Indent = 2 + ) + process { +@" +$($Before -join [Environment]::newLine) +$ArgumentSource +$( + Template.ForEachLoop.js -Variable $Variable -Condition $Condition -Body ($Statement -join [Environment]::newLine) +)$( +if ($after) { + [Environment]::NewLine + ($After -join [Environment]::newLine) +} +) +"@ + } +} diff --git a/Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForeachArgument.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForeachArgument.ps1 new file mode 100644 index 000000000..d150e6b22 --- /dev/null +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForeachArgument.ps1 @@ -0,0 +1,72 @@ + +function Template.ForeachArgument.js { + + <# + .SYNOPSIS + JavaScript Foreach Argument Template + .DESCRIPTION + A Template for a script that walks over each argument, in JavaScript. + .EXAMPLE + Template.ForeachArgument.js | Set-Content .\Args.js + Invoke-PipeScript -Path .\Args.js -Arguments "a",@{"b"='c'} + #> + [Alias('Template.JavaScript.ForeachArgument')] + param( + # One or more statements + # By default this is `print(sys.argv[i])` + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Body','Code','Block','Script','Scripts')] + [string[]] + $Statement = "console.log(args[arg])", + + # One or more statements to run before the loop. + [Parameter(ValueFromPipelineByPropertyName)] + [string[]] + $Before = 'let args = []', + + # The statement to run after the loop. + [Parameter(ValueFromPipelineByPropertyName)] + [string[]] + $After, + + # The source of the arguments. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $ArgumentSource = 'process.argv.slice(2).forEach(arg => args.push(arg))', + + # The current argument variable. + # This initializes the loop. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('CurrentArgument','ArgumentVariable')] + [string] + $Variable = 'arg', + + # The argument collection. + # This is what is looped thru. + [Parameter(ValueFromPipelineByPropertyName)] + [string] + $Condition = 'args', + + # The number of characters to indent. + [Parameter(ValueFromPipelineByPropertyName)] + [ValidateRange(1,10)] + [int] + $Indent = 2 + ) + process { +@" +$($Before -join [Environment]::newLine) +$ArgumentSource +$( + Template.ForEachLoop.js -Variable $Variable -Condition $Condition -Body ($Statement -join [Environment]::newLine) +)$( +if ($after) { + [Environment]::NewLine + ($After -join [Environment]::newLine) +} +) +"@ + } + +} + + diff --git a/Languages/JavaScript/Templates/JavaScript-Template-ForeachLoop.ps.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForeachLoop.ps.ps1 similarity index 96% rename from Languages/JavaScript/Templates/JavaScript-Template-ForeachLoop.ps.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForeachLoop.ps.ps1 index 8f23b89dd..5388d79df 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-ForeachLoop.ps.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForeachLoop.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + Template function ForEachLoop.js { <# .SYNOPSIS diff --git a/Languages/JavaScript/Templates/JavaScript-Template-ForeachLoop.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForeachLoop.ps1 similarity index 96% rename from Languages/JavaScript/Templates/JavaScript-Template-ForeachLoop.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForeachLoop.ps1 index 7c792fbde..2c950e5e3 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-ForeachLoop.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-ForeachLoop.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + function Template.ForEachLoop.js { diff --git a/Languages/JavaScript/Templates/JavaScript-Template-Function.ps.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-Function.ps.ps1 similarity index 97% rename from Languages/JavaScript/Templates/JavaScript-Template-Function.ps.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-Function.ps.ps1 index e92b1f090..3bef5eb63 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-Function.ps.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-Function.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + Template function Function.js { <# .SYNOPSIS @@ -5,7 +8,7 @@ Template function Function.js { .DESCRIPTION Template for a `function` in JavaScript. .EXAMPLE - Template.Function.js -Name "Hello" -Body "return 'hello'" + Template.Function.js -Name "Hello" -Body "return 'hello'" #> [Alias('Template.Method.js','Template.Generator.js')] param( diff --git a/Languages/JavaScript/Templates/JavaScript-Template-Function.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-Function.ps1 similarity index 97% rename from Languages/JavaScript/Templates/JavaScript-Template-Function.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-Function.ps1 index 111e7d718..adfe5da3f 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-Function.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-Function.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + function Template.Function.js { @@ -7,7 +10,7 @@ function Template.Function.js { .DESCRIPTION Template for a `function` in JavaScript. .EXAMPLE - Template.Function.js -Name "Hello" -Body "return 'hello'" + Template.Function.js -Name "Hello" -Body "return 'hello'" #> [Alias('Template.Method.js','Template.Generator.js')] param( diff --git a/Languages/JavaScript/Templates/JavaScript-Template-InvokeMethod.ps.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-InvokeMethod.ps.ps1 similarity index 96% rename from Languages/JavaScript/Templates/JavaScript-Template-InvokeMethod.ps.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-InvokeMethod.ps.ps1 index 412ffc63a..fb0dfc5c0 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-InvokeMethod.ps.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-InvokeMethod.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + Template function InvokeMethod.js { <# .SYNOPSIS diff --git a/Languages/JavaScript/Templates/JavaScript-Template-InvokeMethod.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-InvokeMethod.ps1 similarity index 96% rename from Languages/JavaScript/Templates/JavaScript-Template-InvokeMethod.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-InvokeMethod.ps1 index e4151bf29..2737020e4 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-InvokeMethod.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-InvokeMethod.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + function Template.InvokeMethod.js { diff --git a/Languages/JavaScript/Templates/JavaScript-Template-RegexLiteral.ps.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-RegexLiteral.ps.ps1 similarity index 96% rename from Languages/JavaScript/Templates/JavaScript-Template-RegexLiteral.ps.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-RegexLiteral.ps.ps1 index 597363abe..d5878eca7 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-RegexLiteral.ps.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-RegexLiteral.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + Template function RegexLiteral.js { <# .SYNOPSIS diff --git a/Languages/JavaScript/Templates/JavaScript-Template-RegexLiteral.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-RegexLiteral.ps1 similarity index 96% rename from Languages/JavaScript/Templates/JavaScript-Template-RegexLiteral.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-RegexLiteral.ps1 index ae8271581..825f015d1 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-RegexLiteral.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-RegexLiteral.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + function Template.RegexLiteral.js { diff --git a/Languages/JavaScript/Templates/JavaScript-Template-TryCatch.ps.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-TryCatch.ps.ps1 similarity index 97% rename from Languages/JavaScript/Templates/JavaScript-Template-TryCatch.ps.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-TryCatch.ps.ps1 index 08f4398f7..008532f97 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-TryCatch.ps.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-TryCatch.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + Template function TryCatch.js { <# .SYNOPSIS diff --git a/Languages/JavaScript/Templates/JavaScript-Template-TryCatch.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-TryCatch.ps1 similarity index 97% rename from Languages/JavaScript/Templates/JavaScript-Template-TryCatch.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-TryCatch.ps1 index 2880a0da5..25c04071c 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-TryCatch.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-TryCatch.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + function Template.TryCatch.js { diff --git a/Languages/JavaScript/Templates/JavaScript-Template-WhileLoop.ps.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-WhileLoop.ps.ps1 similarity index 95% rename from Languages/JavaScript/Templates/JavaScript-Template-WhileLoop.ps.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-WhileLoop.ps.ps1 index c3c7c5b51..bc75e4a26 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-WhileLoop.ps.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-WhileLoop.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + Template function WhileLoop.js { <# .SYNOPSIS diff --git a/Languages/JavaScript/Templates/JavaScript-Template-WhileLoop.ps1 b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-WhileLoop.ps1 similarity index 95% rename from Languages/JavaScript/Templates/JavaScript-Template-WhileLoop.ps1 rename to Languages/JavaScript/Templates/Syntax/JavaScript-Template-WhileLoop.ps1 index 3bfb661ca..7ab430675 100644 --- a/Languages/JavaScript/Templates/JavaScript-Template-WhileLoop.ps1 +++ b/Languages/JavaScript/Templates/Syntax/JavaScript-Template-WhileLoop.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("JavaScript")] +param() + function Template.WhileLoop.js { diff --git a/Languages/Kotlin/Kotlin-Language.ps.ps1 b/Languages/Kotlin/Kotlin-Language.ps.ps1 index 71f64c92b..99bd2248d 100644 --- a/Languages/Kotlin/Kotlin-Language.ps.ps1 +++ b/Languages/Kotlin/Kotlin-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Kotlin|Language)[\s\p{P}]")] +param() + Language function Kotlin { <# .SYNOPSIS diff --git a/Languages/Kotlin/Kotlin-Language.ps1 b/Languages/Kotlin/Kotlin-Language.ps1 index e080b7937..e18e98ad2 100644 --- a/Languages/Kotlin/Kotlin-Language.ps1 +++ b/Languages/Kotlin/Kotlin-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Kotlin|Language)[\s\p{P}]")] +param() + function Language.Kotlin { <# diff --git a/Languages/Kusto/Kusto-Language.ps.ps1 b/Languages/Kusto/Kusto-Language.ps.ps1 index b9bf63b5c..82e067e3a 100644 --- a/Languages/Kusto/Kusto-Language.ps.ps1 +++ b/Languages/Kusto/Kusto-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Kusto|Language)[\s\p{P}]")] +param() + Language function Kusto { <# .SYNOPSIS diff --git a/Languages/Kusto/Kusto-Language.ps1 b/Languages/Kusto/Kusto-Language.ps1 index 97f795ab5..d1d48f30c 100644 --- a/Languages/Kusto/Kusto-Language.ps1 +++ b/Languages/Kusto/Kusto-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Kusto|Language)[\s\p{P}]")] +param() + function Language.Kusto { <# diff --git a/Languages/LaTeX/LaTeX-Language.ps.ps1 b/Languages/LaTeX/LaTeX-Language.ps.ps1 index c63680816..155abb874 100644 --- a/Languages/LaTeX/LaTeX-Language.ps.ps1 +++ b/Languages/LaTeX/LaTeX-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Latex|Language)[\s\p{P}]")] +param() + Language function LaTeX { <# .SYNOPSIS diff --git a/Languages/LaTeX/LaTeX-Language.ps1 b/Languages/LaTeX/LaTeX-Language.ps1 index e936e1c26..aa8abeec4 100644 --- a/Languages/LaTeX/LaTeX-Language.ps1 +++ b/Languages/LaTeX/LaTeX-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Latex|Language)[\s\p{P}]")] +param() + function Language.LaTeX { <# diff --git a/Languages/Liquid/Liquid-Language.ps.ps1 b/Languages/Liquid/Liquid-Language.ps.ps1 index cffcf758d..e5fc24017 100644 --- a/Languages/Liquid/Liquid-Language.ps.ps1 +++ b/Languages/Liquid/Liquid-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Liquid|Language)[\s\p{P}]")] +param() + Language function Liquid { <# .SYNOPSIS diff --git a/Languages/Liquid/Liquid-Language.ps1 b/Languages/Liquid/Liquid-Language.ps1 index a48d7941f..d10c6e5a3 100644 --- a/Languages/Liquid/Liquid-Language.ps1 +++ b/Languages/Liquid/Liquid-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Liquid|Language)[\s\p{P}]")] +param() + function Language.Liquid { <# diff --git a/Languages/Lua/Lua-Language.ps.ps1 b/Languages/Lua/Lua-Language.ps.ps1 index dcfcd9753..595fd5003 100644 --- a/Languages/Lua/Lua-Language.ps.ps1 +++ b/Languages/Lua/Lua-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>LUA|Language)[\s\p{P}]")] +param() + Language function Lua { <# .SYNOPSIS diff --git a/Languages/Lua/Lua-Language.ps1 b/Languages/Lua/Lua-Language.ps1 index c40a4d3d1..12b91bb99 100644 --- a/Languages/Lua/Lua-Language.ps1 +++ b/Languages/Lua/Lua-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>LUA|Language)[\s\p{P}]")] +param() + function Language.Lua { <# diff --git a/Languages/Markdown/Markdown-Language.ps.ps1 b/Languages/Markdown/Markdown-Language.ps.ps1 index 20d49c069..c4d6107c3 100644 --- a/Languages/Markdown/Markdown-Language.ps.ps1 +++ b/Languages/Markdown/Markdown-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Markdown|Language)[\s\p{P}]")] +param() + Language function Markdown { <# .SYNOPSIS diff --git a/Languages/Markdown/Markdown-Language.ps1 b/Languages/Markdown/Markdown-Language.ps1 index 3d63ba5cf..c74bf6522 100644 --- a/Languages/Markdown/Markdown-Language.ps1 +++ b/Languages/Markdown/Markdown-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>Markdown|Language)[\s\p{P}]")] +param() + function Language.Markdown { <# diff --git a/Languages/ObjectiveC/ObjectiveC-Language.ps.ps1 b/Languages/ObjectiveC/ObjectiveC-Language.ps.ps1 index 9228f070c..2b1629649 100644 --- a/Languages/ObjectiveC/ObjectiveC-Language.ps.ps1 +++ b/Languages/ObjectiveC/ObjectiveC-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>ObjectiveC|Language)[\s\p{P}]")] +param() + Language function ObjectiveC { <# .SYNOPSIS diff --git a/Languages/ObjectiveC/ObjectiveC-Language.ps1 b/Languages/ObjectiveC/ObjectiveC-Language.ps1 index ea6f7f563..8683d51e6 100644 --- a/Languages/ObjectiveC/ObjectiveC-Language.ps1 +++ b/Languages/ObjectiveC/ObjectiveC-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>ObjectiveC|Language)[\s\p{P}]")] +param() + function Language.ObjectiveC { <# diff --git a/Languages/OpenSCAD/OpenSCAD-Language.ps.ps1 b/Languages/OpenSCAD/OpenSCAD-Language.ps.ps1 index b44eab6ab..d52f896f3 100644 --- a/Languages/OpenSCAD/OpenSCAD-Language.ps.ps1 +++ b/Languages/OpenSCAD/OpenSCAD-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>OpenSCAD|Language)[\s\p{P}]")] +param() + Language function OpenSCAD { <# .SYNOPSIS diff --git a/Languages/OpenSCAD/OpenSCAD-Language.ps1 b/Languages/OpenSCAD/OpenSCAD-Language.ps1 index 271d29ba3..502ce8d04 100644 --- a/Languages/OpenSCAD/OpenSCAD-Language.ps1 +++ b/Languages/OpenSCAD/OpenSCAD-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>OpenSCAD|Language)[\s\p{P}]")] +param() + function Language.OpenSCAD { <# diff --git a/Languages/PHP/PHP-Language.ps.ps1 b/Languages/PHP/PHP-Language.ps.ps1 index 1ea167b7c..b4623d5b3 100644 --- a/Languages/PHP/PHP-Language.ps.ps1 +++ b/Languages/PHP/PHP-Language.ps.ps1 @@ -1,3 +1,7 @@ +[ValidatePattern("(?>PHP|Language)[\s\p{P}]")] +param() + + Language function PHP { <# .SYNOPSIS diff --git a/Languages/PHP/PHP-Language.ps1 b/Languages/PHP/PHP-Language.ps1 index 597d567fb..2a1872aab 100644 --- a/Languages/PHP/PHP-Language.ps1 +++ b/Languages/PHP/PHP-Language.ps1 @@ -1,3 +1,7 @@ +[ValidatePattern("(?>PHP|Language)[\s\p{P}]")] +param() + + function Language.PHP { <# diff --git a/Languages/Perl/Perl-Language.ps.ps1 b/Languages/Perl/Perl-Language.ps.ps1 index 111649bb1..7f2f0189f 100644 --- a/Languages/Perl/Perl-Language.ps.ps1 +++ b/Languages/Perl/Perl-Language.ps.ps1 @@ -1,3 +1,7 @@ +[ValidatePattern("(?>Perl|Language)[\s\p{P}]")] +param() + + Language function Perl { <# .SYNOPSIS diff --git a/Languages/Perl/Perl-Language.ps1 b/Languages/Perl/Perl-Language.ps1 index 4b147e572..d45239a1d 100644 --- a/Languages/Perl/Perl-Language.ps1 +++ b/Languages/Perl/Perl-Language.ps1 @@ -1,3 +1,7 @@ +[ValidatePattern("(?>Perl|Language)[\s\p{P}]")] +param() + + function Language.Perl { <# diff --git a/Languages/PipeScript/Templates/PipeScript-Template-ExplicitOutput.ps.ps1 b/Languages/PipeScript/Templates/PipeScript-Template-ExplicitOutput.ps.ps1 new file mode 100644 index 000000000..4f004f5f8 --- /dev/null +++ b/Languages/PipeScript/Templates/PipeScript-Template-ExplicitOutput.ps.ps1 @@ -0,0 +1,92 @@ +[ValidatePattern("Pipescript")] +param() + +Template function PipeScript.ExplicitOutput { + <# + .Synopsis + Makes Output from a PowerShell function Explicit. + .Description + Makes a PowerShell function explicitly output. + + All statements will be assigned to $null, unless they explicitly use Write-Output or echo. + + If Write-Output or echo is used, the command will be replaced for more effecient output. + .EXAMPLE + Invoke-PipeScript { + [explicit()] + param() + "This Will Not Output" + Write-Output "This Will Output" + } + .EXAMPLE + { + [explicit]{ + 1,2,3,4 + echo "Output" + } + } | .>PipeScript + #> + [OutputType([ScriptBlock])] + [Alias('Explicit','ExplicitOutput')] + param( + # The ScriptBlock that will be transpiled. + [Parameter(Mandatory,ValueFromPipeline)] + [ScriptBlock] + $ScriptBlock + ) + + process { + # Search the ScriptBlock for + $pipelines = $ScriptBlock.Ast.FindAll({ + param($ast) + # all pipelines + if ($ast -isnot [System.Management.Automation.Language.PipelineAst]) { + return $false + } + # (as long as they are not the child or grandchild of Command, Assignment, or Return statements) + $ignoreTypes = 'CommandAst', 'AssignmentStatementAst', 'ReturnStatementAst' + $pipelineParent = $ast.Parent + $pipelineGrandParent = $ast.Parent.Parent + if ($pipelineParent -and $pipelineParent.GetType().Name -in $ignoreTypes) { + return $false + } + if ($pipelineGrandParent -and $pipelineGrandParent.GetType().Name -in $ignoreTypes) { + return $false + } + return $true + }, $false) + + $astReplacements = [Ordered]@{} + foreach ($pipeline in $pipelines) { + $nullify = $pipeline.PipelineElements[-1] -isnot [Management.Automation.Language.CommandAst] + if (-not $nullify) { + $cmdName = $pipeline.PipelineElements[-1].CommandElements[0].Value + $isOutputCmdName = '^(?>Out|Write|Show|Format)' + $resolvedAlias = $ExecutionContext.SessionState.InvokeCommand.GetCommand($cmdName, 'Alias') + $nullify = + $cmdName -notmatch $isOutputCmdName -and + $resolvedAlias.Definition -notmatch $isOutputCmdName + + $isWriteOutput = $cmdName -in 'Write-Output', 'echo', 'write' + if ($isWriteOutput) { + if ($pipeline.PipelineElements.Count -gt 1) { + $strPipeline = "$pipeline" + $restOfPipeline = $strPipeline.Substring(0, + $strPipeline.Length - $pipeline.PipelineElements[-1].ToString().Length) + $astReplacements[$pipeline] = [ScriptBlock]::Create($restOfPipeline.TrimEnd().TrimEnd('|')) + } else { + $ce = $pipeline.PipelineElements[0].CommandElements + $astReplacements[$pipeline] = [ScriptBlock]::Create($ce[1..($ce.Count - 1)] -join ' ') + } + continue + } + } + + if ($nullify) { + $astReplacements[$pipeline] = [ScriptBlock]::Create("`$null = $pipeline") + } + } + + Update-PipeScript -ScriptBlock $ScriptBlock -AstReplacement $astReplacements + } +} \ No newline at end of file diff --git a/Languages/PipeScript/Templates/PipeScript-Template-ExplicitOutput.ps1 b/Languages/PipeScript/Templates/PipeScript-Template-ExplicitOutput.ps1 new file mode 100644 index 000000000..84ca60da4 --- /dev/null +++ b/Languages/PipeScript/Templates/PipeScript-Template-ExplicitOutput.ps1 @@ -0,0 +1,96 @@ +[ValidatePattern("Pipescript")] +param() + + +function Template.PipeScript.ExplicitOutput { + + <# + .Synopsis + Makes Output from a PowerShell function Explicit. + .Description + Makes a PowerShell function explicitly output. + + All statements will be assigned to $null, unless they explicitly use Write-Output or echo. + + If Write-Output or echo is used, the command will be replaced for more effecient output. + .EXAMPLE + Invoke-PipeScript { + [explicit()] + param() + "This Will Not Output" + Write-Output "This Will Output" + } + .EXAMPLE + { + [explicit]{ + 1,2,3,4 + echo "Output" + } + } | .>PipeScript + #> + [OutputType([ScriptBlock])] + [Alias('Explicit','ExplicitOutput')] + param( + # The ScriptBlock that will be transpiled. + [Parameter(Mandatory,ValueFromPipeline)] + [ScriptBlock] + $ScriptBlock + ) + + process { + # Search the ScriptBlock for + $pipelines = $ScriptBlock.Ast.FindAll({ + param($ast) + # all pipelines + if ($ast -isnot [System.Management.Automation.Language.PipelineAst]) { + return $false + } + # (as long as they are not the child or grandchild of Command, Assignment, or Return statements) + $ignoreTypes = 'CommandAst', 'AssignmentStatementAst', 'ReturnStatementAst' + $pipelineParent = $ast.Parent + $pipelineGrandParent = $ast.Parent.Parent + if ($pipelineParent -and $pipelineParent.GetType().Name -in $ignoreTypes) { + return $false + } + if ($pipelineGrandParent -and $pipelineGrandParent.GetType().Name -in $ignoreTypes) { + return $false + } + return $true + }, $false) + + $astReplacements = [Ordered]@{} + foreach ($pipeline in $pipelines) { + $nullify = $pipeline.PipelineElements[-1] -isnot [Management.Automation.Language.CommandAst] + if (-not $nullify) { + $cmdName = $pipeline.PipelineElements[-1].CommandElements[0].Value + $isOutputCmdName = '^(?>Out|Write|Show|Format)' + $resolvedAlias = $ExecutionContext.SessionState.InvokeCommand.GetCommand($cmdName, 'Alias') + $nullify = + $cmdName -notmatch $isOutputCmdName -and + $resolvedAlias.Definition -notmatch $isOutputCmdName + + $isWriteOutput = $cmdName -in 'Write-Output', 'echo', 'write' + if ($isWriteOutput) { + if ($pipeline.PipelineElements.Count -gt 1) { + $strPipeline = "$pipeline" + $restOfPipeline = $strPipeline.Substring(0, + $strPipeline.Length - $pipeline.PipelineElements[-1].ToString().Length) + $astReplacements[$pipeline] = [ScriptBlock]::Create($restOfPipeline.TrimEnd().TrimEnd('|')) + } else { + $ce = $pipeline.PipelineElements[0].CommandElements + $astReplacements[$pipeline] = [ScriptBlock]::Create($ce[1..($ce.Count - 1)] -join ' ') + } + continue + } + } + + if ($nullify) { + $astReplacements[$pipeline] = [ScriptBlock]::Create("`$null = $pipeline") + } + } + + Update-PipeScript -ScriptBlock $ScriptBlock -AstReplacement $astReplacements + } + +} + diff --git a/Transpilers/Inherit.psx.ps1 b/Languages/PipeScript/Templates/PipeScript-Template-Inherit.ps.ps1 similarity index 59% rename from Transpilers/Inherit.psx.ps1 rename to Languages/PipeScript/Templates/PipeScript-Template-Inherit.ps.ps1 index adb917a01..b6fb34189 100644 --- a/Transpilers/Inherit.psx.ps1 +++ b/Languages/PipeScript/Templates/PipeScript-Template-Inherit.ps.ps1 @@ -1,149 +1,154 @@ -<# -.SYNOPSIS - Inherits a Command -.DESCRIPTION - Inherits a given command. - - This acts similarily to inheritance in Object Oriented programming. +[ValidatePattern('(?>PipeScript|Inherit)')] +param() + +Template function PipeScript.Inherit { + <# + .SYNOPSIS + Inherits a Command + .DESCRIPTION + Inherits a given command. + + This acts similarily to inheritance in Object Oriented programming. - By default, inheriting a function will join the contents of that function with the -ScriptBlock. + By default, inheriting a function will join the contents of that function with the -ScriptBlock. - Your ScriptBlock will come first, so you can override any of the behavior of the underlying command. + Your ScriptBlock will come first, so you can override any of the behavior of the underlying command. - You can "abstractly" inherit a command, that is, inherit only the command's parameters. - - Inheritance can also be -Abstract. - - When you use Abstract inheritance, you get only the function definition and header from the inherited command. - - You can also -Override the command you are inheriting. - - This will add an [Alias()] attribute containing the original command name. - - One interesting example is overriding an application - - -.EXAMPLE - Invoke-PipeScript { - [inherit("Get-Command")] - param() - } -.EXAMPLE - { - [inherit("gh",Overload)] - param() - begin { "ABOUT TO CALL GH"} - end { "JUST CALLED GH" } - }.Transpile() -.EXAMPLE - # Inherit Get-Transpiler abstractly and make it output the parameters passed in. - { - [inherit("Get-Transpiler", Abstract)] - param() process { $psBoundParameters } - }.Transpile() -.EXAMPLE - { - [inherit("Get-Transpiler", Dynamic, Abstract)] - param() - } | .>PipeScript -#> -param( -# The command that will be inherited. -[Parameter(Mandatory,Position=0)] -[Alias('CommandName')] -[string] -$Command, - -# If provided, will abstractly inherit a function. -# This include the function's parameters, but not it's content -# It will also define a variable within a dynamicParam {} block that contains the base command. -[switch] -$Abstract, - -# If provided, will set an alias on the function to replace the original command. -[Alias('Overload')] -[switch] -$Override, - -# If set, will dynamic overload commands. -# This will use dynamic parameters instead of static parameters, and will use a proxy command to invoke the inherited command. -[switch] -$Dynamic, - -# If set, will not generate a dynamic parameter block. This is primarily present so Abstract inheritance has a small change footprint. -[switch] -$NoDynamic, - -# If set, will always inherit commands as proxy commands. -# This is implied by -Dynamic. -[switch] -$Proxy, - -# The Command Type. This can allow you to specify the type of command you are overloading. -# If the -CommandType includes aliases, and another command is also found, that command will be used. -# (this ensures you can continue to overload commands) -[Alias('CommandTypes')] -[string[]] -$CommandType = 'All', - -# A list of block types to be excluded during a merge of script blocks. -# By default, no blocks will be excluded. -[ValidateSet('using', 'requires', 'help','header','param','dynamicParam','begin','process','end')] -[Alias('SkipBlockType','SkipBlockTypes','ExcludeBlockTypes')] -[string[]] -$ExcludeBlockType, - -# A list of block types to include during a merge of script blocks. -[ValidateSet('using', 'requires', 'help','header','param','dynamicParam','begin','process','end')] -[Alias('BlockType','BlockTypes','IncludeBlockTypes')] -[string[]] -$IncludeBlockType = @('using', 'requires', 'help','header','param','dynamicParam','begin','process','end'), - -# A list of parameters to include. Can contain wildcards. -# If -IncludeParameter is provided without -ExcludeParameter, all other parameters will be excluded. -[string[]] -$IncludeParameter, - -# A list of parameters to exclude. Can contain wildcards. -# Excluded parameters with default values will declare the default value at the beginnning of the command. -[string[]] -$ExcludeParameter, - -# The ArgumentList parameter name -# When inheriting an application, a parameter is created to accept any remaining arguments. -# This is the name of that parameter (by default, 'ArgumentList') -# This parameter is ignored when inheriting from anything other than an application. -[Alias('ArgumentListParameter')] -[string] -$ArgumentListParameterName = 'ArgumentList', - -# The ArgumentList parameter aliases -# When inheriting an application, a parameter is created to accept any remaining arguments. -# These are the aliases for that parameter (by default, 'Arguments' and 'Args') -# This parameter is ignored when inheriting from anything other than an application. -[Alias('ArgumentListParameters','ArgumentListParameterNames')] -[string[]] -$ArgumentListParameterAlias = @('Arguments', 'Args'), - -# The ArgumentList parameter type -# When inheriting an application, a parameter is created to accept any remaining arguments. -# This is the type of that parameter (by default, '[string[]]') -# This parameter is ignored when inheriting from anything other than an application. -[type] -$ArgumentListParameterType = [string[]], - -# The help for the argument list parameter. -# When inheriting an application, a parameter is created to accept any remaining arguments. -# This is the help of that parameter (by default, 'Arguments to $($InhertedApplication.Name)') -# This parameter is ignored when inheriting from anything other than an application. -[Alias('ArgumentListParameterDescription')] -[string] -$ArgumentListParameterHelp = 'Arguments to $($InhertedApplication.Name)', - -[Parameter(ValueFromPipeline,ParameterSetName='ScriptBlock')] -[scriptblock] -$ScriptBlock = {} -) + You can "abstractly" inherit a command, that is, inherit only the command's parameters. + + Inheritance can also be -Abstract. + + When you use Abstract inheritance, you get only the function definition and header from the inherited command. + + You can also -Override the command you are inheriting. + + This will add an [Alias()] attribute containing the original command name. + + One interesting example is overriding an application + + + .EXAMPLE + Invoke-PipeScript { + [inherit("Get-Command")] + param() + } + .EXAMPLE + { + [inherit("gh",Overload)] + param() + begin { "ABOUT TO CALL GH"} + end { "JUST CALLED GH" } + }.Transpile() + .EXAMPLE + # Inherit Get-Transpiler abstractly and make it output the parameters passed in. + { + [inherit("Get-Transpiler", Abstract)] + param() process { $psBoundParameters } + }.Transpile() + .EXAMPLE + { + [inherit("Get-Transpiler", Dynamic, Abstract)] + param() + } | .>PipeScript + #> + [Alias('Inherit')] + param( + # The command that will be inherited. + [Parameter(Mandatory,Position=0)] + [Alias('CommandName')] + [string] + $Command, + + # If provided, will abstractly inherit a function. + # This include the function's parameters, but not it's content + # It will also define a variable within a dynamicParam {} block that contains the base command. + [switch] + $Abstract, + + # If provided, will set an alias on the function to replace the original command. + [Alias('Overload')] + [switch] + $Override, + + # If set, will dynamic overload commands. + # This will use dynamic parameters instead of static parameters, and will use a proxy command to invoke the inherited command. + [switch] + $Dynamic, + + # If set, will not generate a dynamic parameter block. This is primarily present so Abstract inheritance has a small change footprint. + [switch] + $NoDynamic, + + # If set, will always inherit commands as proxy commands. + # This is implied by -Dynamic. + [switch] + $Proxy, + + # The Command Type. This can allow you to specify the type of command you are overloading. + # If the -CommandType includes aliases, and another command is also found, that command will be used. + # (this ensures you can continue to overload commands) + [Alias('CommandTypes')] + [string[]] + $CommandType = 'All', + + # A list of block types to be excluded during a merge of script blocks. + # By default, no blocks will be excluded. + [ValidateSet('using', 'requires', 'help','header','param','dynamicParam','begin','process','end')] + [Alias('SkipBlockType','SkipBlockTypes','ExcludeBlockTypes')] + [string[]] + $ExcludeBlockType, + + # A list of block types to include during a merge of script blocks. + [ValidateSet('using', 'requires', 'help','header','param','dynamicParam','begin','process','end')] + [Alias('BlockType','BlockTypes','IncludeBlockTypes')] + [string[]] + $IncludeBlockType = @('using', 'requires', 'help','header','param','dynamicParam','begin','process','end'), + + # A list of parameters to include. Can contain wildcards. + # If -IncludeParameter is provided without -ExcludeParameter, all other parameters will be excluded. + [string[]] + $IncludeParameter, + + # A list of parameters to exclude. Can contain wildcards. + # Excluded parameters with default values will declare the default value at the beginnning of the command. + [string[]] + $ExcludeParameter, + + # The ArgumentList parameter name + # When inheriting an application, a parameter is created to accept any remaining arguments. + # This is the name of that parameter (by default, 'ArgumentList') + # This parameter is ignored when inheriting from anything other than an application. + [Alias('ArgumentListParameter')] + [string] + $ArgumentListParameterName = 'ArgumentList', + + # The ArgumentList parameter aliases + # When inheriting an application, a parameter is created to accept any remaining arguments. + # These are the aliases for that parameter (by default, 'Arguments' and 'Args') + # This parameter is ignored when inheriting from anything other than an application. + [Alias('ArgumentListParameters','ArgumentListParameterNames')] + [string[]] + $ArgumentListParameterAlias = @('Arguments', 'Args'), + + # The ArgumentList parameter type + # When inheriting an application, a parameter is created to accept any remaining arguments. + # This is the type of that parameter (by default, '[string[]]') + # This parameter is ignored when inheriting from anything other than an application. + [type] + $ArgumentListParameterType = [string[]], + + # The help for the argument list parameter. + # When inheriting an application, a parameter is created to accept any remaining arguments. + # This is the help of that parameter (by default, 'Arguments to $($InhertedApplication.Name)') + # This parameter is ignored when inheriting from anything other than an application. + [Alias('ArgumentListParameterDescription')] + [string] + $ArgumentListParameterHelp = 'Arguments to $($InhertedApplication.Name)', + + [Parameter(ValueFromPipeline,ParameterSetName='ScriptBlock')] + [scriptblock] + $ScriptBlock = {} + ) process { # To start off with, let's resolve the command we're inheriting. @@ -245,7 +250,7 @@ process { $applicationWrapper } elseif ($resolvedCommand -is [Management.Automation.CmdletInfo] -or $Proxy) { # If we're inheriting a Cmdlet or -Proxy was passed, inherit from a proxy command. - .>ProxyCommand -CommandName $Command + Template.PipeScript.ProxyCommand -CommandName $Command } elseif ( # If it's a function or script @@ -275,6 +280,11 @@ process { Write-Error "Could not resolve [ScriptBlock] to inheirt from Command: '$Command'" return } + + if ($Abstract -and -not $Dynamic) { + $NotDynamic = $true + } + # Now we're passing a series of script blocks to Join-PipeScript $( @@ -388,9 +398,6 @@ dynamicParam { } ) | Join-PipeScript @joinSplat # join the scripts together and return one [ScriptBlock] - - } - - +} \ No newline at end of file diff --git a/Languages/PipeScript/Templates/PipeScript-Template-Inherit.ps1 b/Languages/PipeScript/Templates/PipeScript-Template-Inherit.ps1 new file mode 100644 index 000000000..b393cc0ea --- /dev/null +++ b/Languages/PipeScript/Templates/PipeScript-Template-Inherit.ps1 @@ -0,0 +1,407 @@ +[ValidatePattern('(?>PipeScript|Inherit)')] +param() + + +function Template.PipeScript.Inherit { + + <# + .SYNOPSIS + Inherits a Command + .DESCRIPTION + Inherits a given command. + + This acts similarily to inheritance in Object Oriented programming. + + By default, inheriting a function will join the contents of that function with the -ScriptBlock. + + Your ScriptBlock will come first, so you can override any of the behavior of the underlying command. + + You can "abstractly" inherit a command, that is, inherit only the command's parameters. + + Inheritance can also be -Abstract. + + When you use Abstract inheritance, you get only the function definition and header from the inherited command. + + You can also -Override the command you are inheriting. + + This will add an [Alias()] attribute containing the original command name. + + One interesting example is overriding an application + + + .EXAMPLE + Invoke-PipeScript { + [inherit("Get-Command")] + param() + } + .EXAMPLE + { + [inherit("gh",Overload)] + param() + begin { "ABOUT TO CALL GH"} + end { "JUST CALLED GH" } + }.Transpile() + .EXAMPLE + # Inherit Get-Transpiler abstractly and make it output the parameters passed in. + { + [inherit("Get-Transpiler", Abstract)] + param() process { $psBoundParameters } + }.Transpile() + .EXAMPLE + { + [inherit("Get-Transpiler", Dynamic, Abstract)] + param() + } | .>PipeScript + #> + [Alias('Inherit')] + param( + # The command that will be inherited. + [Parameter(Mandatory,Position=0)] + [Alias('CommandName')] + [string] + $Command, + + # If provided, will abstractly inherit a function. + # This include the function's parameters, but not it's content + # It will also define a variable within a dynamicParam {} block that contains the base command. + [switch] + $Abstract, + + # If provided, will set an alias on the function to replace the original command. + [Alias('Overload')] + [switch] + $Override, + + # If set, will dynamic overload commands. + # This will use dynamic parameters instead of static parameters, and will use a proxy command to invoke the inherited command. + [switch] + $Dynamic, + + # If set, will not generate a dynamic parameter block. This is primarily present so Abstract inheritance has a small change footprint. + [switch] + $NoDynamic, + + # If set, will always inherit commands as proxy commands. + # This is implied by -Dynamic. + [switch] + $Proxy, + + # The Command Type. This can allow you to specify the type of command you are overloading. + # If the -CommandType includes aliases, and another command is also found, that command will be used. + # (this ensures you can continue to overload commands) + [Alias('CommandTypes')] + [string[]] + $CommandType = 'All', + + # A list of block types to be excluded during a merge of script blocks. + # By default, no blocks will be excluded. + [ValidateSet('using', 'requires', 'help','header','param','dynamicParam','begin','process','end')] + [Alias('SkipBlockType','SkipBlockTypes','ExcludeBlockTypes')] + [string[]] + $ExcludeBlockType, + + # A list of block types to include during a merge of script blocks. + [ValidateSet('using', 'requires', 'help','header','param','dynamicParam','begin','process','end')] + [Alias('BlockType','BlockTypes','IncludeBlockTypes')] + [string[]] + $IncludeBlockType = @('using', 'requires', 'help','header','param','dynamicParam','begin','process','end'), + + # A list of parameters to include. Can contain wildcards. + # If -IncludeParameter is provided without -ExcludeParameter, all other parameters will be excluded. + [string[]] + $IncludeParameter, + + # A list of parameters to exclude. Can contain wildcards. + # Excluded parameters with default values will declare the default value at the beginnning of the command. + [string[]] + $ExcludeParameter, + + # The ArgumentList parameter name + # When inheriting an application, a parameter is created to accept any remaining arguments. + # This is the name of that parameter (by default, 'ArgumentList') + # This parameter is ignored when inheriting from anything other than an application. + [Alias('ArgumentListParameter')] + [string] + $ArgumentListParameterName = 'ArgumentList', + + # The ArgumentList parameter aliases + # When inheriting an application, a parameter is created to accept any remaining arguments. + # These are the aliases for that parameter (by default, 'Arguments' and 'Args') + # This parameter is ignored when inheriting from anything other than an application. + [Alias('ArgumentListParameters','ArgumentListParameterNames')] + [string[]] + $ArgumentListParameterAlias = @('Arguments', 'Args'), + + # The ArgumentList parameter type + # When inheriting an application, a parameter is created to accept any remaining arguments. + # This is the type of that parameter (by default, '[string[]]') + # This parameter is ignored when inheriting from anything other than an application. + [type] + $ArgumentListParameterType = [string[]], + + # The help for the argument list parameter. + # When inheriting an application, a parameter is created to accept any remaining arguments. + # This is the help of that parameter (by default, 'Arguments to $($InhertedApplication.Name)') + # This parameter is ignored when inheriting from anything other than an application. + [Alias('ArgumentListParameterDescription')] + [string] + $ArgumentListParameterHelp = 'Arguments to $($InhertedApplication.Name)', + + [Parameter(ValueFromPipeline,ParameterSetName='ScriptBlock')] + [scriptblock] + $ScriptBlock = {} + ) + +process { + # To start off with, let's resolve the command we're inheriting. + $commandTypes = [Management.Automation.CommandTypes]$($CommandType -join ',') + $resolvedCommand = $ExecutionContext.SessionState.InvokeCommand.GetCommand($Command, $commandTypes) + # If it is an alias + if ($resolvedCommand -is [Management.Automation.AliasInfo]) { + # check for other commands by this name + $otherCommandExists = $ExecutionContext.SessionState.InvokeCommand.GetCommand($Command, + $commandTypes -bxor ([Management.Automation.CommandTypes]'Alias')) + if ($otherCommandExists) { + # and use those instead (otherwise, a command can only be overloaded once). + Write-Verbose "Using $otherCommandExists instead of alias" + $resolvedCommand = $otherCommandExists + } + } + + # If we could not resolve the command + if (-not $resolvedCommand) { + Write-Error "Could not resolve -Command '$Command'" # error out. + return + } + + $InheritTemplate = + if ($resolvedCommand.Inherit) { + $resolvedCommand.Inherit + } elseif ($resolvedCommand.Module.Inherit) { + $resolvedCommand.Module.Inherit + } + + if ($InheritTemplate -is [Management.Automation.PSMethodInfo]) { + return $InheritTemplate.Invoke(@([Ordered]@{} + $PSBoundParameters)) + } + elseif ($InheritTemplate -is [scriptblock]) { + return (& $InheritTemplate @([Ordered]@{} + $PSBoundParameters) ) + } + + # Prepare parameters for Join-ScriptBlock + $joinSplat = @{} + foreach ($key in 'IncludeBlockType', 'ExcludeBlockType') { + if ($PSBoundParameters[$key]) { + $joinSplat[$key] = $PSBoundParameters[$key] + } + } + + $joinInheritedSplat = @{} + foreach ($key in 'IncludeParameter', 'ExcludeParameter') { + if ($PSBoundParameters[$key]) { + $joinInheritedSplat[$key] = $PSBoundParameters[$key] + } + } + + # and determine the name of the command as a variable + $commandVariable = $Command -replace '\W' + + # If -Dynamic was passed + if ($Dynamic) { + $Proxy = $true # it implies -Proxy. + } + + $InhertedApplication = + if ($resolvedCommand -is [Management.Automation.ApplicationInfo]) { + $resolvedCommand + } + elseif ( + $resolvedCommand -is [Management.Automation.AliasInfo] -and + $resolvedCommand.ResolvedCommand -is [Management.Automation.ApplicationInfo] + ) { + $resolvedCommand.ResolvedCommand + } + + if ($InhertedApplication) { + # In a couple of cases, we're inheriting an application. + # In this scenario, we want to use the same wrapper [ScriptBlock] + $paramHelp = + foreach ($helpLine in $ExecutionContext.SessionState.InvokeCommand.ExpandString($ArgumentListParameterHelp) -split '(?>\r\n|\n)') { + "# $HelpLine" + } + + $applicationWrapper = New-PipeScript -Parameter @{ + $ArgumentListParameterName = @( + $paramHelp + "[Parameter(ValueFromRemainingArguments)]" + "[Alias('$($ArgumentListParameterAlias -join "','")')]" + "[$ArgumentListParameterType]" + "`$$ArgumentListParameterName" + ) + } -Process { + & $baseCommand @ArgumentList + } + } + + + # Now we get the script block that we're going to inherit. + $resolvedScriptBlock = + # If we're inheriting an application + if ($resolvedCommand -is [Management.Automation.ApplicationInfo]) { + # use our light wrapper. + $applicationWrapper + } elseif ($resolvedCommand -is [Management.Automation.CmdletInfo] -or $Proxy) { + # If we're inheriting a Cmdlet or -Proxy was passed, inherit from a proxy command. + Template.PipeScript.ProxyCommand -CommandName $Command + } + elseif ( + # If it's a function or script + $resolvedCommand -is [Management.Automation.FunctionInfo] -or + $resolvedCommand -is [management.Automation.ExternalScriptInfo] + ) { + # inherit from the ScriptBlock definition. + $resolvedCommand.ScriptBlock + } + elseif ( + # If we're inheriting an alias to something with a scriptblock + $resolvedCommand -is [Management.Automation.AliasInfo] -and + $resolvedCommand.ResolvedCommand.ScriptBlock) { + + # inherit from that ScriptBlock. + $resolvedCommand.ResolvedCommand.ScriptBlock + } + elseif ( + # And if we're inheriting from an alias that points to an application + $resolvedCommand -is [Management.Automation.AliasInfo] -and + $resolvedCommand.ResolvedCommand -is [Management.Automation.ApplicationInfo]) { + # use our lite wrapper once more. + $applicationWrapper + } + + if (-not $resolvedCommand) { + Write-Error "Could not resolve [ScriptBlock] to inheirt from Command: '$Command'" + return + } + + if ($Abstract -and -not $Dynamic) { + $NotDynamic = $true + } + + # Now we're passing a series of script blocks to Join-PipeScript + +$( + # If we do not have a resolved command, + if (-not $resolvedCommand) { + {} # the first script block is empty. + } + else { + # If we have a resolvedCommand, fully qualify it. + $fullyQualifiedCommand = + if ($resolvedCommand.Module) { + "$($resolvedCommand.Module)\$command" + } else { + "$command" + } + + # Then create a dynamicParam block that will set `$baseCommand, + # as well as a `$script: scoped variable for the command name. + if ($NotDynamic) # unless, of course -NotDynamic is passed + { + {} + } else + { + [scriptblock]::create(@" +dynamicParam { + `$baseCommand = + if (-not `$script:$commandVariable) { + `$script:$commandVariable = + `$executionContext.SessionState.InvokeCommand.GetCommand('$($command -replace "'", "''")','$($resolvedCommand.CommandType)') + `$script:$commandVariable + } else { + `$script:$commandVariable + } + $( + # Embed -IncludeParameter + if ($IncludeParameter) { + "`$IncludeParameter = '$($IncludeParameter -join "','")'" + } else { + "`$IncludeParameter = @()" + }) + $( + # Embed -ExcludeParameter + if ($ExcludeParameter) { + "`$ExcludeParameter = '$($ExcludeParameter -join "','")'" + } + else { + "`$ExcludeParameter = @()" + }) +} +"@) + } + } +), + # Next is our [ScriptBlock]. This will come before almost everything else. + $scriptBlock, + $( + # If we are -Overriding, create an [Alias] + if ($Override) { + [ScriptBlock]::create("[Alias('$($command)')]param()") + } else { + {} + } +),$( + # Now we include the resolved script + if ($Abstract -or $Dynamic) { + # If we're using -Abstract or -Dynamic, we will strip out a few blocks first. + $excludeFromInherited = 'begin','process', 'end', 'header','help' + if ($Dynamic) { + if (-not $Abstract) { + $excludeFromInherited = 'param' + } else { + $excludeFromInherited += 'param' + } + } + $resolvedScriptBlock | Join-PipeScript -ExcludeBlockType $excludeFromInherited @joinInheritedSplat + } else { + + } +), $( + if ($Dynamic) { + # If -Dynamic was passed, generate dynamic parameters. +{ + +dynamicParam { + $DynamicParameters = [Management.Automation.RuntimeDefinedParameterDictionary]::new() + :nextInputParameter foreach ($paramName in ([Management.Automation.CommandMetaData]$baseCommand).Parameters.Keys) { + if ($ExcludeParameter) { + foreach ($exclude in $ExcludeParameter) { + if ($paramName -like $exclude) { continue nextInputParameter} + } + } + if ($IncludeParameter) { + $shouldInclude = + foreach ($include in $IncludeParameter) { + if ($paramName -like $include) { $true;break} + } + if (-not $shouldInclude) { continue nextInputParameter } + } + + $DynamicParameters.Add($paramName, [Management.Automation.RuntimeDefinedParameter]::new( + $baseCommand.Parameters[$paramName].Name, + $baseCommand.Parameters[$paramName].ParameterType, + $baseCommand.Parameters[$paramName].Attributes + )) + } + $DynamicParameters +} +} + } else { + {} + } +) | + Join-PipeScript @joinSplat # join the scripts together and return one [ScriptBlock] +} + + +} + diff --git a/Languages/PipeScript/Templates/PipeScript-Template-OutputFile.ps.ps1 b/Languages/PipeScript/Templates/PipeScript-Template-OutputFile.ps.ps1 new file mode 100644 index 000000000..0f3de16ed --- /dev/null +++ b/Languages/PipeScript/Templates/PipeScript-Template-OutputFile.ps.ps1 @@ -0,0 +1,123 @@ +[ValidatePattern("PipeScript")] +param() + +Template function PipeScript.OutputFile { + <# + .SYNOPSIS + Outputs to a File + .DESCRIPTION + Outputs the result of a script into a file. + .EXAMPLE + Invoke-PipeScript { + [OutputFile("hello.txt")] + param() + + 'hello world' + } + .Example + Invoke-PipeScript { + param() + + $Message = 'hello world' + [Save(".\Hello.txt")]$Message + } + #> + [Alias('Save','OutputFile')] + param( + # The Output Path + [Parameter(Mandatory)] + [string] + $OutputPath, + + # The Script Block that will be run. + [Parameter(ValueFromPipeline)] + [scriptblock] + $ScriptBlock, + + [Parameter(ValueFromPipeline)] + [Management.Automation.Language.VariableExpressionast] + $VariableAst, + + # The encoding parameter. + [string] + $Encoding, + + # If set, will force output, overwriting existing files. + [switch] + $Force, + + # The export script + [scriptblock] + $ExportScript, + + # The serialization depth. Currently only used when saving to JSON files. + [int] + $Depth = 100 + ) + + begin { + function SaveJson { + # Determine if the content appears to already be JSON + if ($jsonToSave -match '^[\s\r\n]{0,}[\[\{\`"/]') { + $jsonToSave | Set-Content -Path '$safeOutputPath' $OtherParams + } else { + ConvertTo-JSON -InputObject `$jsonToSave -Depth $depth | + Set-Content -Path '$safeOutputPath' $otherParams + } + } + } + + process { + $safeOutputPath = $OutputPath.Replace("'","''") + $otherParams = @( + if ($Encoding) { + "-Encoding '$($encoding.Replace("'", "''"))'" + } + if ($Force) { + "-Force" + } + ) -join ' ' + + $inputObjectScript = + if ($VariableAst) { + $VariableAst.Extent.ToString() + } elseif ($ScriptBlock.Ast.ParamBlock.Parameters.Count) + { + "`$ParameterCopy = [Ordered]@{} + `$psBoundParameters; & { $ScriptBlock } @ParameterCopy" + } else { + "& { $ScriptBlock }" + } + + if ($OutputPath -match '\.json') { + [ScriptBlock]::Create(" +`$jsonToSave = $inputObjectScript +# Determine if the content appears to already be JSON +if (`$jsonToSave -match '^[\s\r\n]{0,}[\[\{\`"/]') { + `$jsonToSave | Set-Content -Path '$safeOutputPath' $OtherParams +} else { + ConvertTo-JSON -InputObject `$jsonToSave -Depth $depth | Set-Content -Path '$safeOutputPath' $otherParams +} + + ") + } + elseif ($OutputPath -match '\.[c|t]sv$') { + [ScriptBlock]::Create("$inputObjectScript | Export-Csv -Path '$safeOutputPath' $otherParams") + } + elseif ($OutputPath -match '\.(?>clixml|clix|xml)$') { + [ScriptBlock]::Create(" +`$toSave = $inputObjectScript +if (`$toSave -as [xml]) { + `$strWrite = [IO.StringWriter]::new() + `$configurationXml.Save(`$strWrite) + (`"$strWrite`" -replace '^\<\?xml version=`"1.0`" encoding=`"utf-16`"\?\>') | Set-Content -Path '$safeOutputPath' $otherParams +} else { + `$toSave | Export-Clixml -Path '$safeOutputPath' $otherParams +} +") + } + else { + [ScriptBlock]::Create("$inputObjectScript | Set-Content -Path '$safeOutputPath' $otherParams") + } + } + +} \ No newline at end of file diff --git a/Languages/PipeScript/Templates/PipeScript-Template-OutputFile.ps1 b/Languages/PipeScript/Templates/PipeScript-Template-OutputFile.ps1 new file mode 100644 index 000000000..7bb17e8eb --- /dev/null +++ b/Languages/PipeScript/Templates/PipeScript-Template-OutputFile.ps1 @@ -0,0 +1,127 @@ +[ValidatePattern("PipeScript")] +param() + + +function Template.PipeScript.OutputFile { + + <# + .SYNOPSIS + Outputs to a File + .DESCRIPTION + Outputs the result of a script into a file. + .EXAMPLE + Invoke-PipeScript { + [OutputFile("hello.txt")] + param() + + 'hello world' + } + .Example + Invoke-PipeScript { + param() + + $Message = 'hello world' + [Save(".\Hello.txt")]$Message + } + #> + [Alias('Save','OutputFile')] + param( + # The Output Path + [Parameter(Mandatory)] + [string] + $OutputPath, + + # The Script Block that will be run. + [Parameter(ValueFromPipeline)] + [scriptblock] + $ScriptBlock, + + [Parameter(ValueFromPipeline)] + [Management.Automation.Language.VariableExpressionast] + $VariableAst, + + # The encoding parameter. + [string] + $Encoding, + + # If set, will force output, overwriting existing files. + [switch] + $Force, + + # The export script + [scriptblock] + $ExportScript, + + # The serialization depth. Currently only used when saving to JSON files. + [int] + $Depth = 100 + ) + + begin { + function SaveJson { + # Determine if the content appears to already be JSON + if ($jsonToSave -match '^[\s\r\n]{0,}[\[\{\`"/]') { + $jsonToSave | Set-Content -Path '$safeOutputPath' $OtherParams + } else { + ConvertTo-JSON -InputObject `$jsonToSave -Depth $depth | + Set-Content -Path '$safeOutputPath' $otherParams + } + } + } + + process { + $safeOutputPath = $OutputPath.Replace("'","''") + $otherParams = @( + if ($Encoding) { + "-Encoding '$($encoding.Replace("'", "''"))'" + } + if ($Force) { + "-Force" + } + ) -join ' ' + + $inputObjectScript = + if ($VariableAst) { + $VariableAst.Extent.ToString() + } elseif ($ScriptBlock.Ast.ParamBlock.Parameters.Count) + { + "`$ParameterCopy = [Ordered]@{} + `$psBoundParameters; & { $ScriptBlock } @ParameterCopy" + } else { + "& { $ScriptBlock }" + } + + if ($OutputPath -match '\.json') { + [ScriptBlock]::Create(" +`$jsonToSave = $inputObjectScript +# Determine if the content appears to already be JSON +if (`$jsonToSave -match '^[\s\r\n]{0,}[\[\{\`"/]') { + `$jsonToSave | Set-Content -Path '$safeOutputPath' $OtherParams +} else { + ConvertTo-JSON -InputObject `$jsonToSave -Depth $depth | Set-Content -Path '$safeOutputPath' $otherParams +} + + ") + } + elseif ($OutputPath -match '\.[c|t]sv$') { + [ScriptBlock]::Create("$inputObjectScript | Export-Csv -Path '$safeOutputPath' $otherParams") + } + elseif ($OutputPath -match '\.(?>clixml|clix|xml)$') { + [ScriptBlock]::Create(" +`$toSave = $inputObjectScript +if (`$toSave -as [xml]) { + `$strWrite = [IO.StringWriter]::new() + `$configurationXml.Save(`$strWrite) + (`"$strWrite`" -replace '^\<\?xml version=`"1.0`" encoding=`"utf-16`"\?\>') | Set-Content -Path '$safeOutputPath' $otherParams +} else { + `$toSave | Export-Clixml -Path '$safeOutputPath' $otherParams +} +") + } + else { + [ScriptBlock]::Create("$inputObjectScript | Set-Content -Path '$safeOutputPath' $otherParams") + } + } + + +} + diff --git a/Languages/PipeScript/Templates/PipeScript-Template-ProxyCommand.ps.ps1 b/Languages/PipeScript/Templates/PipeScript-Template-ProxyCommand.ps.ps1 new file mode 100644 index 000000000..079831946 --- /dev/null +++ b/Languages/PipeScript/Templates/PipeScript-Template-ProxyCommand.ps.ps1 @@ -0,0 +1,108 @@ +[ValidatePattern('(?>PipeScript|ProxyCommand)')] +param() + +Template function PipeScript.ProxyCommand { + <# + .SYNOPSIS + Creates Proxy Commands + .DESCRIPTION + Generates a Proxy Command for an underlying PowerShell or PipeScript command. + .EXAMPLE + ProxyCommand -CommandName Get-Process -RemoveParameter * + .EXAMPLE + Invoke-PipeScript -ScriptBlock {[ProxyCommand('Get-Process')]param()} + .EXAMPLE + Invoke-PipeScript -ScriptBlock { + [ProxyCommand('Get-Process', + RemoveParameter='*', + DefaultParameter={ + @{id='$pid'} + })] + param() + } + .EXAMPLE + { + function Get-MyProcess { + [ProxyCommand('Get-Process', + RemoveParameter='*', + DefaultParameter={ + @{id='$pid'} + })] + param() + } + } | .>PipeScript + #> + [Alias('ProxyCommand')] + param( + # The ScriptBlock that will become a proxy command. This should be empty, since it is ignored. + [Parameter(ValueFromPipeline)] + [scriptblock] + $ScriptBlock, + + # The name of the command being proxied. + [Parameter(Mandatory,Position=0)] + [string] + $CommandName, + + # If provided, will remove any number of parameters from the proxy command. + [string[]] + $RemoveParameter, + + # Any default parameters for the ProxyCommand. + [Collections.IDictionary] + $DefaultParameter + ) + + process { + + $resolvedCommand = $ExecutionContext.SessionState.InvokeCommand.GetCommand($CommandName, 'Alias,Function,Cmdlet') + if (-not $resolvedCommand) { + Write-Error "Could not resolve -CommandName '$CommandName'" + return + } + + $commandMetadata = [Management.Automation.CommandMetadata]$resolvedCommand + + if ($RemoveParameter) { + $toRemove = @( + foreach ($paramName in $commandMetadata.Parameters.Keys) { + if ($RemoveParameter -contains $paramName) { + $paramName + } else { + foreach ($rp in $RemoveParameter) { + if ($paramName -like $rp) { + $paramName + break + } + } + } + } + ) + + $null = foreach ($tr in $toRemove) { + $commandMetadata.Parameters.Remove($tr) + } + } + + $proxyCommandText = [Management.Automation.ProxyCommand]::Create($commandMetadata) + + if ($DefaultParameter) { + $toSplat = "@' +$(ConvertTo-Json $DefaultParameter -Depth 100) +'@" + $insertPoint = $proxyCommandText.IndexOf('$scriptCmd = {& $wrappedCmd @PSBoundParameters }') + $proxyCommandText = $proxyCommandText.Insert($insertPoint,@" + `$_DefaultParameters = ConvertFrom-Json $toSplat + foreach (`$property in `$_DefaultParameters.psobject.properties) { + `$psBoundParameters[`$property.Name] = `$property.Value + if (`$property.Value -is [string] -and `$property.Value.StartsWith('`$')) { + `$psBoundParameters[`$property.Name] = `$executionContext.SessionState.PSVariable.Get(`$property.Value.Substring(1)).Value + } + } +"@) + + } + + [ScriptBlock]::Create($proxyCommandText) + } +} \ No newline at end of file diff --git a/Languages/PipeScript/Templates/PipeScript-Template-ProxyCommand.ps1 b/Languages/PipeScript/Templates/PipeScript-Template-ProxyCommand.ps1 new file mode 100644 index 000000000..91b506240 --- /dev/null +++ b/Languages/PipeScript/Templates/PipeScript-Template-ProxyCommand.ps1 @@ -0,0 +1,112 @@ +[ValidatePattern('(?>PipeScript|ProxyCommand)')] +param() + + +function Template.PipeScript.ProxyCommand { + + <# + .SYNOPSIS + Creates Proxy Commands + .DESCRIPTION + Generates a Proxy Command for an underlying PowerShell or PipeScript command. + .EXAMPLE + ProxyCommand -CommandName Get-Process -RemoveParameter * + .EXAMPLE + Invoke-PipeScript -ScriptBlock {[ProxyCommand('Get-Process')]param()} + .EXAMPLE + Invoke-PipeScript -ScriptBlock { + [ProxyCommand('Get-Process', + RemoveParameter='*', + DefaultParameter={ + @{id='$pid'} + })] + param() + } + .EXAMPLE + { + function Get-MyProcess { + [ProxyCommand('Get-Process', + RemoveParameter='*', + DefaultParameter={ + @{id='$pid'} + })] + param() + } + } | .>PipeScript + #> + [Alias('ProxyCommand')] + param( + # The ScriptBlock that will become a proxy command. This should be empty, since it is ignored. + [Parameter(ValueFromPipeline)] + [scriptblock] + $ScriptBlock, + + # The name of the command being proxied. + [Parameter(Mandatory,Position=0)] + [string] + $CommandName, + + # If provided, will remove any number of parameters from the proxy command. + [string[]] + $RemoveParameter, + + # Any default parameters for the ProxyCommand. + [Collections.IDictionary] + $DefaultParameter + ) + + process { + + $resolvedCommand = $ExecutionContext.SessionState.InvokeCommand.GetCommand($CommandName, 'Alias,Function,Cmdlet') + if (-not $resolvedCommand) { + Write-Error "Could not resolve -CommandName '$CommandName'" + return + } + + $commandMetadata = [Management.Automation.CommandMetadata]$resolvedCommand + + if ($RemoveParameter) { + $toRemove = @( + foreach ($paramName in $commandMetadata.Parameters.Keys) { + if ($RemoveParameter -contains $paramName) { + $paramName + } else { + foreach ($rp in $RemoveParameter) { + if ($paramName -like $rp) { + $paramName + break + } + } + } + } + ) + + $null = foreach ($tr in $toRemove) { + $commandMetadata.Parameters.Remove($tr) + } + } + + $proxyCommandText = [Management.Automation.ProxyCommand]::Create($commandMetadata) + + if ($DefaultParameter) { + $toSplat = "@' +$(ConvertTo-Json $DefaultParameter -Depth 100) +'@" + $insertPoint = $proxyCommandText.IndexOf('$scriptCmd = {& $wrappedCmd @PSBoundParameters }') + $proxyCommandText = $proxyCommandText.Insert($insertPoint,@" + `$_DefaultParameters = ConvertFrom-Json $toSplat + foreach (`$property in `$_DefaultParameters.psobject.properties) { + `$psBoundParameters[`$property.Name] = `$property.Value + if (`$property.Value -is [string] -and `$property.Value.StartsWith('`$')) { + `$psBoundParameters[`$property.Name] = `$executionContext.SessionState.PSVariable.Get(`$property.Value.Substring(1)).Value + } + } +"@) + + } + + [ScriptBlock]::Create($proxyCommandText) + } + +} + diff --git a/Transpilers/Rest.psx.ps1 b/Languages/PipeScript/Templates/PipeScript-Template-Rest.ps.ps1 similarity index 77% rename from Transpilers/Rest.psx.ps1 rename to Languages/PipeScript/Templates/PipeScript-Template-Rest.ps.ps1 index 9dd1a2517..9958d12f5 100644 --- a/Transpilers/Rest.psx.ps1 +++ b/Languages/PipeScript/Templates/PipeScript-Template-Rest.ps.ps1 @@ -1,11 +1,28 @@ -<# -.SYNOPSIS - Generates PowerShell to talk to a REST api. -.DESCRIPTION - Generates PowerShell that communicates with a REST api. -.EXAMPLE - { - function Get-Sentiment { +[ValidatePattern('(?>PipeScript|REST)')] +param() + +Template function PipeScript.Rest { + <# + .SYNOPSIS + Template for simple REST in PipeScript + .DESCRIPTION + Template for a Restful function in PipeScript. + .EXAMPLE + { + function Get-Sentiment { + [Rest("http://text-processing.com/api/sentiment/", + ContentType="application/x-www-form-urlencoded", + Method = "POST", + BodyParameter="Text", + ForeachOutput = { + $_ | Select-Object -ExpandProperty Probability -Property Label + } + )] + param() + } + } | .>PipeScript | Set-Content .\Get-Sentiment.ps1 + .EXAMPLE + Invoke-PipeScript { [Rest("http://text-processing.com/api/sentiment/", ContentType="application/x-www-form-urlencoded", Method = "POST", @@ -15,137 +32,125 @@ } )] param() - } - } | .>PipeScript | Set-Content .\Get-Sentiment.ps1 -.EXAMPLE - Invoke-PipeScript { - [Rest("http://text-processing.com/api/sentiment/", - ContentType="application/x-www-form-urlencoded", - Method = "POST", - BodyParameter="Text", - ForeachOutput = { - $_ | Select-Object -ExpandProperty Probability -Property Label - } - )] - param() - } -Parameter @{Text='wow!'} -.EXAMPLE - { - [Rest("https://api.github.com/users/{username}/repos", - QueryParameter={"type", "sort", "direction", "page", "per_page"} - )] - param() - } | .>PipeScript -.EXAMPLE - Invoke-PipeScript { - [Rest("https://api.github.com/users/{username}/repos", - QueryParameter={"type", "sort", "direction", "page", "per_page"} - )] - param() - } -UserName StartAutomating -.EXAMPLE - { - [Rest("http://text-processing.com/api/sentiment/", - ContentType="application/x-www-form-urlencoded", - Method = "POST", - BodyParameter={@{ - Text = ' - [Parameter(Mandatory,ValueFromPipelineByPropertyName)] - [string] - $Text - ' - }})] - param() - } | .>PipeScript -#> -param( -# The ScriptBlock. -# If not empty, the contents of this ScriptBlock will preceed the REST api call. -[Parameter(ValueFromPipeline)] -[scriptblock] -$ScriptBlock = {}, + } -Parameter @{Text='wow!'} + .EXAMPLE + { + [Rest("https://api.github.com/users/{username}/repos", + QueryParameter={"type", "sort", "direction", "page", "per_page"} + )] + param() + } | .>PipeScript + .EXAMPLE + Invoke-PipeScript { + [Rest("https://api.github.com/users/{username}/repos", + QueryParameter={"type", "sort", "direction", "page", "per_page"} + )] + param() + } -Parameter @{UserName='StartAutomating'} + .EXAMPLE + { + [Rest("http://text-processing.com/api/sentiment/", + ContentType="application/x-www-form-urlencoded", + Method = "POST", + BodyParameter={@{ + Text = ' + [Parameter(Mandatory,ValueFromPipelineByPropertyName)] + [string] + $Text + ' + }})] + param() + } | .>PipeScript + #> + [Alias('REST')] + param( + # The ScriptBlock. + # If not empty, the contents of this ScriptBlock will preceed the REST api call. + [Parameter(ValueFromPipeline)] + [scriptblock] + $ScriptBlock = {}, -# One or more REST endpoints. This endpoint will be parsed for REST variables. -[Parameter(Mandatory,Position=0)] -[string[]] -$RESTEndpoint, + # One or more REST endpoints. This endpoint will be parsed for REST variables. + [Parameter(Mandatory,Position=0)] + [string[]] + $RESTEndpoint, -# The content type. If provided, this parameter will be passed to the -InvokeCommand. -[string] -$ContentType, + # The content type. If provided, this parameter will be passed to the -InvokeCommand. + [string] + $ContentType, -# The method. If provided, this parameter will be passed to the -InvokeCommand. -[string] -$Method, + # The method. If provided, this parameter will be passed to the -InvokeCommand. + [string] + $Method, -# The invoke command. This command _must_ have a parameter -URI. -[Alias('Invoker')] -[string] -$InvokeCommand = 'Invoke-RestMethod', + # The invoke command. This command _must_ have a parameter -URI. + [Alias('Invoker')] + [string] + $InvokeCommand = 'Invoke-RestMethod', -# The name of a variable containing additional invoke parameters. -# By default, this is 'InvokeParams' -[Alias('InvokerParameters','InvokerParameter')] -[string] -$InvokeParameterVariable = 'InvokeParams', + # The name of a variable containing additional invoke parameters. + # By default, this is 'InvokeParams' + [Alias('InvokerParameters','InvokerParameter')] + [string] + $InvokeParameterVariable = 'InvokeParams', -# A dictionary of help for uri parameters. -[Alias('UrlParameterHelp')] -[Collections.IDictionary] -$UriParameterHelp, + # A dictionary of help for uri parameters. + [Alias('UrlParameterHelp')] + [Collections.IDictionary] + $UriParameterHelp, -# A dictionary of URI parameter types. -[Alias('UrlParameterType')] -[Collections.IDictionary] -$UriParameterType, + # A dictionary of URI parameter types. + [Alias('UrlParameterType')] + [Collections.IDictionary] + $UriParameterType, -<# -A dictionary or list of parameters for the body. + <# + A dictionary or list of parameters for the body. -If a parameter has a ```[ComponentModel.DefaultBindingProperty]``` attribute, -it will be used to rename the body parameter. + If a parameter has a ```[ComponentModel.DefaultBindingProperty]``` attribute, + it will be used to rename the body parameter. -If a parameter has a ```[ComponentModel.AmbientValue]``` attribute with a ```[ScriptBlock]``` value, -it will be used to redefine the value. + If a parameter has a ```[ComponentModel.AmbientValue]``` attribute with a ```[ScriptBlock]``` value, + it will be used to redefine the value. -If a parameter value is a [DateTime], it will be turned into a [string] using the standard format. + If a parameter value is a [DateTime], it will be turned into a [string] using the standard format. -If a parameter is a [switch], it will be turned into a [bool]. -#> -[PSObject] -$BodyParameter, + If a parameter is a [switch], it will be turned into a [bool]. + #> + [PSObject] + $BodyParameter, -<# -A dictionary or list of query parameters. + <# + A dictionary or list of query parameters. -If a parameter has a ```[ComponentModel.DefaultBindingProperty]``` attribute, -it will be used to rename the body parameter. + If a parameter has a ```[ComponentModel.DefaultBindingProperty]``` attribute, + it will be used to rename the body parameter. -If a parameter has a ```[ComponentModel.AmbientValue]``` attribute with a ```[ScriptBlock]``` value, -it will be used to redefine the value. + If a parameter has a ```[ComponentModel.AmbientValue]``` attribute with a ```[ScriptBlock]``` value, + it will be used to redefine the value. -If a parameter value is a [DateTime], it will be turned into a [string] using the standard format. + If a parameter value is a [DateTime], it will be turned into a [string] using the standard format. -If a parameter is a [switch], it will be turned into a [bool]. -#> -[PSObject] -$QueryParameter, + If a parameter is a [switch], it will be turned into a [bool]. + #> + [PSObject] + $QueryParameter, -# If provided, will join multiple values of a query by this string. -# If the string is '&', each value will be provided as a key-value pair. -[string] -$JoinQueryValue, + # If provided, will join multiple values of a query by this string. + # If the string is '&', each value will be provided as a key-value pair. + [string] + $JoinQueryValue, -# A script block to be run on each output. -[ScriptBlock] -$ForEachOutput -) + # A script block to be run on each output. + [ScriptBlock] + $ForEachOutput + ) begin { # Declare a Regular Expression to match URL variables. @@ -261,7 +266,11 @@ process { # If we used any URI parameters if ($uriParamBlock.Ast.ParamBlock.Parameters) { # Carry on the begin block from this command (this is a neat trick) - [scriptblock]::Create($MyInvocation.MyCommand.ScriptBlock.Ast.BeginBlock.Extent.ToString()) + if ($MyInvocation.MyCommand.ScriptBlock.Ast.BeginBlock) { + [scriptblock]::Create($MyInvocation.MyCommand.ScriptBlock.Ast.BeginBlock.Extent.ToString()) + } elseif ($MyInvocation.MyCommand.ScriptBlock.Ast.Body.BeginBlock) { + [scriptblock]::Create($MyInvocation.MyCommand.ScriptBlock.Ast.Body.BeginBlock.Extent.ToString()) + } } else { { } } $foundAttributesOfInterest = @@ -578,3 +587,5 @@ process { $RestScript | Join-PipeScript } + +} \ No newline at end of file diff --git a/Languages/PipeScript/Templates/PipeScript-Template-Rest.ps1 b/Languages/PipeScript/Templates/PipeScript-Template-Rest.ps1 new file mode 100644 index 000000000..6bf832aef --- /dev/null +++ b/Languages/PipeScript/Templates/PipeScript-Template-Rest.ps1 @@ -0,0 +1,595 @@ +[ValidatePattern('(?>PipeScript|REST)')] +param() + + +function Template.PipeScript.Rest { + + <# + .SYNOPSIS + Template for simple REST in PipeScript + .DESCRIPTION + Template for a Restful function in PipeScript. + .EXAMPLE + { + function Get-Sentiment { + [Rest("http://text-processing.com/api/sentiment/", + ContentType="application/x-www-form-urlencoded", + Method = "POST", + BodyParameter="Text", + ForeachOutput = { + $_ | Select-Object -ExpandProperty Probability -Property Label + } + )] + param() + } + } | .>PipeScript | Set-Content .\Get-Sentiment.ps1 + .EXAMPLE + Invoke-PipeScript { + [Rest("http://text-processing.com/api/sentiment/", + ContentType="application/x-www-form-urlencoded", + Method = "POST", + BodyParameter="Text", + ForeachOutput = { + $_ | Select-Object -ExpandProperty Probability -Property Label + } + )] + param() + } -Parameter @{Text='wow!'} + .EXAMPLE + { + [Rest("https://api.github.com/users/{username}/repos", + QueryParameter={"type", "sort", "direction", "page", "per_page"} + )] + param() + } | .>PipeScript + .EXAMPLE + Invoke-PipeScript { + [Rest("https://api.github.com/users/{username}/repos", + QueryParameter={"type", "sort", "direction", "page", "per_page"} + )] + param() + } -Parameter @{UserName='StartAutomating'} + .EXAMPLE + { + [Rest("http://text-processing.com/api/sentiment/", + ContentType="application/x-www-form-urlencoded", + Method = "POST", + BodyParameter={@{ + Text = ' + [Parameter(Mandatory,ValueFromPipelineByPropertyName)] + [string] + $Text + ' + }})] + param() + } | .>PipeScript + #> + [Alias('REST')] + param( + # The ScriptBlock. + # If not empty, the contents of this ScriptBlock will preceed the REST api call. + [Parameter(ValueFromPipeline)] + [scriptblock] + $ScriptBlock = {}, + + # One or more REST endpoints. This endpoint will be parsed for REST variables. + [Parameter(Mandatory,Position=0)] + [string[]] + $RESTEndpoint, + + # The content type. If provided, this parameter will be passed to the -InvokeCommand. + [string] + $ContentType, + + # The method. If provided, this parameter will be passed to the -InvokeCommand. + [string] + $Method, + + # The invoke command. This command _must_ have a parameter -URI. + [Alias('Invoker')] + [string] + $InvokeCommand = 'Invoke-RestMethod', + + # The name of a variable containing additional invoke parameters. + # By default, this is 'InvokeParams' + [Alias('InvokerParameters','InvokerParameter')] + [string] + $InvokeParameterVariable = 'InvokeParams', + + # A dictionary of help for uri parameters. + [Alias('UrlParameterHelp')] + [Collections.IDictionary] + $UriParameterHelp, + + # A dictionary of URI parameter types. + [Alias('UrlParameterType')] + [Collections.IDictionary] + $UriParameterType, + + <# + A dictionary or list of parameters for the body. + + + If a parameter has a ```[ComponentModel.DefaultBindingProperty]``` attribute, + it will be used to rename the body parameter. + + + If a parameter has a ```[ComponentModel.AmbientValue]``` attribute with a ```[ScriptBlock]``` value, + it will be used to redefine the value. + + + If a parameter value is a [DateTime], it will be turned into a [string] using the standard format. + + If a parameter is a [switch], it will be turned into a [bool]. + #> + [PSObject] + $BodyParameter, + + <# + A dictionary or list of query parameters. + + + If a parameter has a ```[ComponentModel.DefaultBindingProperty]``` attribute, + it will be used to rename the body parameter. + + + If a parameter has a ```[ComponentModel.AmbientValue]``` attribute with a ```[ScriptBlock]``` value, + it will be used to redefine the value. + + + If a parameter value is a [DateTime], it will be turned into a [string] using the standard format. + + If a parameter is a [switch], it will be turned into a [bool]. + #> + [PSObject] + $QueryParameter, + + # If provided, will join multiple values of a query by this string. + # If the string is '&', each value will be provided as a key-value pair. + [string] + $JoinQueryValue, + + # A script block to be run on each output. + [ScriptBlock] + $ForEachOutput + ) + +begin { + # Declare a Regular Expression to match URL variables. + $RestVariable = [Regex]::new(@' +# Matches URL segments and query strings containing variables. +# Variables can be enclosed in brackets or curly braces, or preceeded by a $ or : +(?> # A variable can be in a URL segment or subdomain + (?[/\.]) # Match the ing slash|dot ... + (?\?)? # ... an optional ? (to indicate optional) ... + (?: + \{(?\w+)\}| # ... A name in {} OR + \[(?\w+)\]| # A name in [] OR + \<(?\w+)\>| # A name in <> OR + \:(?\w+) # A : followed by a + ) +| + (? # If it's optional it can also be + [{\[](?/) # a bracket or brace, followed by a slash + ) + (?\w+)[}\]] # then a name followed by } or ] +| # OR it can be in a query parameter: + (?[?&]) # Match The ing ? or & ... + (?[\w\-]+) # ... the parameter name ... + = # ... an equals ... + (?\?)? # ... an optional ? (to indicate optional) ... + (?: + \{(?\w+)\}| # ... A name in {} OR + \[(?\w+)\]| # A name in [] OR + \<(?\w+)\>| # A name in <> OR + \:(?\w+) # A : followed by a + ) +) +'@, 'IgnoreCase,IgnorePatternWhitespace') + + + # Next declare a script block that will replace the rest variable. + $ReplaceRestVariable = { + param($match) + + if ($uriParameter -and $uriParameter[$match.Groups["Variable"].Value]) { + return $match.Groups["Start"].Value + $( + if ($match.Groups["Query"].Success) { $match.Groups["Query"].Value + '=' } + ) + + ([Web.HttpUtility]::UrlEncode( + $uriParameter[$match.Groups["Variable"].Value] + )) + } else { + return '' + } + } +} + +process { + # First, create a collection of URI parameters. + $uriParameters = [Ordered]@{} + # Then, walk over each potential endpoint + foreach ($endpoint in $RESTEndpoint) { + # and each match of a $RestVariable + foreach ($match in $RestVariable.Matches($endpoint)) { + # The name of the parameter will be in the named capture ${Variable}. + $parameterName = $match.Groups["Variable"].Value + # The parameter type will be a string + $parameterType = if ($UriParameterType.$parameterName) { + if ($UriParameterType.$parameterName -as [type]) { + $UriParameterType.$parameterName + } + } else { + '[string]' + } + # and we'll need to put it in the proper parameter set. + $parameterAttribute = "[Parameter($( + if (-not $match.Groups["IsOptional"].Value) {'Mandatory'} + ),ValueFromPipelineByPropertyName,ParameterSetName='$endpoint')]" + # Combine these three pieces to create the parameter attribute. + $uriParameters[$parameterName] = @( + if ($UriParameterHelp -and $UriParameterHelp.$parameterName) { + if ($UriParameterHelp.$parameterName -notmatch '^\<{0,1}\#' ) { + if ($UriParameterHelp.$parameterName -match '[\r\n]') { + "<# " + $UriParameterHelp.$parameterName + "#>" + } else { + "# " + $UriParameterHelp.$parameterName + } + } else { + $UriParameterHelp.$parameterName + } + } + $parameterAttribute + $parameterType + '$' + $parameterName + ) -join [Environment]::Newline + } + } + + # Create a parameter block out of the uri parameters. + $uriParamBlock = + New-PipeScript -Parameter $uriParameters + + # Next, create a parameter block out of any of the body parameters. + $bodyParamBlock = + if ($BodyParameter) { + New-PipeScript -Parameter $BodyParameter + } else { {} } + + # And one for each of the query parameters. + $QueryParamblock = + if ($QueryParameter) { + New-PipeScript -Parameter $QueryParameter + } else { {} } + + + + $myBeginBlocks = @( + # If we used any URI parameters + if ($uriParamBlock.Ast.ParamBlock.Parameters) { + # Carry on the begin block from this command (this is a neat trick) + if ($MyInvocation.MyCommand.ScriptBlock.Ast.BeginBlock) { + [scriptblock]::Create($MyInvocation.MyCommand.ScriptBlock.Ast.BeginBlock.Extent.ToString()) + } elseif ($MyInvocation.MyCommand.ScriptBlock.Ast.Body.BeginBlock) { + [scriptblock]::Create($MyInvocation.MyCommand.ScriptBlock.Ast.Body.BeginBlock.Extent.ToString()) + } + } else { { } } + + $foundAttributesOfInterest = + $QueryParamblock, $bodyParamBlock | + Search-PipeScript -AstCondition { + param($ast) + if ($ast -isnot [Management.Automation.Language.AttributeAST]) { return } + $reflectedType = $ast.TypeName.GetReflectionType() + if ($reflectedType -eq [ComponentModel.AmbientValueAttribute]) { return $true } + if ($reflectedType -eq [ComponentModel.DefaultBindingPropertyAttribute]) { return $true } + } + + if ($foundAttributesOfInterest) { +{ + begin { + $myCmd = $MyInvocation.MyCommand + function ConvertRestInput { + param([Collections.IDictionary]$RestInput = @{}, [switch]$ToQueryString) + foreach ($ri in @($RestInput.GetEnumerator())) { + $RestParameterAttributes = @($myCmd.Parameters[$ri.Key].Attributes) + $restParameterName = $ri.Key + $restParameterValue = $ri.Value + foreach ($attr in $RestParameterAttributes) { + if ($attr -is [ComponentModel.AmbientValueAttribute] -and + $attr.Value -is [ScriptBlock]) { + $_ = $this = $ri.Value + $restParameterValue = & $attr.Value + } + if ($attr -is [ComponentModel.DefaultBindingPropertyAttribute]) { + $restParameterName = $attr.Name + } + } + $restParameterValue = + if ($restParameterValue -is [DateTime]) { + $restParameterValue.Tostring('o') + } + elseif ($restParameterValue -is [switch]) { + $restParameterValue -as [bool] + } + else { + if ($ToQueryString -and + $restParameterValue -is [Array] -and + $JoinQueryValue) { + $restParameterValue -join $JoinQueryValue + } else { + $restParameterValue + } + + } + + if ($restParameterValue -is [Collections.IDictionary]) { + $RestInput.Remove($ri.Key) + foreach ($kv in $restParameterValue.GetEnumerator()) { + $RestInput[$kv.Key] = $kv.Value + } + } elseif ($restParameterName -ne $ri.Key) { + $RestInput.Remove($ri.Key) + $RestInput[$restParameterName] = $restParameterValue + } else { + $RestInput[$ri.Key] = $restParameterValue + } + } + $RestInput + } + } +} + } else { +{ + begin { + function ConvertRestInput { + param([Collections.IDictionary]$RestInput = @{}, [switch]$ToQueryString) + foreach ($ri in @($RestInput.GetEnumerator())) { + + $restParameterValue = $ri.Value + $restParameterValue = + if ($restParameterValue -is [DateTime]) { + $restParameterValue.Tostring('o') + } + elseif ($restParameterValue -is [switch]) { + $restParameterValue -as [bool] + } + else { + if ($ToQueryString -and + $restParameterValue -is [Array] -and + $JoinQueryValue) { + $restParameterValue -join $JoinQueryValue + } else { + $restParameterValue + } + } + + $RestInput[$ri.Key] = $restParameterValue + } + $RestInput + } + } +} + } + ) + + # Next, collect the names of bodyParameters, queryParameters, and uriParameters. + $bodyParameterNames = + foreach ($param in $bodyParamBlock.Ast.ParamBlock.Parameters) { $param.Name -replace '^\$' } + $queryParameterNames = + foreach ($param in $QueryParamblock.Ast.ParamBlock.Parameters) { $param.Name -replace '^\$' } + $uriParameterNames = + foreach ($param in $uriParamBlock.Ast.ParamBlock.Parameters) { $param.Name -replace '^\$' } + + + # Collect all of the parts of the script + $RestScript = @( + # Start with the underlying script block + $ScriptBlock + + # Then include the begin block from this command (or declare myCmd) + foreach ($beginBlock in $myBeginBlocks) { + $beginBlock + } + + # Then declare the initial variables. + [scriptblock]::Create((@" +process { + `$InvokeCommand = '$InvokeCommand' + `$invokerCommandinfo = + `$ExecutionContext.SessionState.InvokeCommand.GetCommand('$InvokeCommand', 'All') + `$method = '$Method' + `$contentType = '$contentType' + `$bodyParameterNames = @('$($bodyParameterNames -join "','")') + `$queryParameterNames = @('$($queryParameterNames -join "','")') + `$joinQueryValue = '$joinQueryValue' + `$uriParameterNames = @('$($uriParameterNames -join "','")') + `$endpoints = @("$($endpoint -join "','")") + `$ForEachOutput = { + $(if ($foreachOutput) { $ForEachOutput | .>Pipescript }) + } + if (`$ForEachOutput -match '^\s{0,}$') { + `$ForEachOutput = `$null + } +} +"@)) + # Next, add some boilerplate code for error handling and setting defaults +{ +process { + if (-not $invokerCommandinfo) { + Write-Error "Unable to find invoker '$InvokeCommand'" + return + } + if (-not $psParameterSet) { $psParameterSet = $psCmdlet.ParameterSetName} + if ($psParameterSet -eq '__AllParameterSets') { $psParameterSet = $endpoints[0]} +} +} + # If we had any uri parameters + if ($uriParameters.Count) { + # Add the uri parameter block + $uriParamBlock + # Then add a bit to process {} to extract out the URL +{ +process { + $originalUri = "$psParameterSet" + if (-not $PSBoundParameters.ContainsKey('UriParameter')) { + $uriParameter = [Ordered]@{} + } + foreach ($uriParameterName in $uriParameterNames) { + if ($psBoundParameters.ContainsKey($uriParameterName)) { + $uriParameter[$uriParameterName] = $psBoundParameters[$uriParameterName] + } + } + + $uri = $RestVariable.Replace($originalUri, $ReplaceRestVariable) +} +} + } else { + # If uri parameters were not supplied, default to the first endpoint. +{ + process { + $uri = $endpoints[0] + } +} + } + # Now create the invoke splat and populate it. +{ +process { + $invokeSplat = @{} + $invokeSplat.Uri = $uri + if ($method) { + $invokeSplat.Method = $method + } + if ($ContentType -and $invokerCommandInfo.Parameters.ContentType) { + $invokeSplat.ContentType = $ContentType + } +} +} + + # If we have an InvokeParameterVariable + if ($InvokeParameterVariable) { + # Create the code that looks for it and joins it with the splat. + $InvokeParameterVariable = $InvokeParameterVariable -replace '^\$' +[scriptblock]::Create(" +process { + if (`$$InvokeParameterVariable -and `$$InvokeParameterVariable -is [Collections.IDictionary]) { + `$invokeSplat += `$$InvokeParameterVariable + } +} +") + + } + + # If QueryParameter Names were provided + if ($queryParameterNames) { + # Include the query parameter block + $QueryParamblock + # And a section of process to handle query parameters. +{ +process { + $QueryParams = [Ordered]@{} + foreach ($QueryParameterName in $QueryParameterNames) { + if ($PSBoundParameters.ContainsKey($QueryParameterName)) { + $QueryParams[$QueryParameterName] = $PSBoundParameters[$QueryParameterName] + } else { + $queryDefault = $ExecutionContext.SessionState.PSVariable.Get($QueryParameterName).Value + if ($null -ne $queryDefault) { + $QueryParams[$QueryParameterName] = $queryDefault + } + } + } +} +} +{ +process { + $queryParams = ConvertRestInput $queryParams -ToQueryString + + if ($invokerCommandinfo.Parameters['QueryParameter'] -and + $invokerCommandinfo.Parameters['QueryParameter'].ParameterType -eq [Collections.IDictionary]) { + $invokeSplat.QueryParameter = $QueryParams + } else { + $queryParamStr = + @(foreach ($qp in $QueryParams.GetEnumerator()) { + $qpValue = $qp.value + if ($JoinQueryValue -eq '&') { + foreach ($qVal in $qpValue -split '&') { + "$($qp.Key)=$([Web.HttpUtility]::UrlEncode($qValue).Replace('+', '%20'))" + } + } else { + "$($qp.Key)=$([Web.HttpUtility]::UrlEncode($qpValue).Replace('+', '%20'))" + } + }) -join '&' + if ($invokeSplat.Uri.Contains('?')) { + $invokeSplat.Uri = "$($invokeSplat.Uri)" + '&' + $queryParamStr + } else { + $invokeSplat.Uri = "$($invokeSplat.Uri)" + '?' + $queryParamStr + } + } +} +} + } + + # If any body parameters exist + if ($bodyParameterNames) { + # Include the body parameter block + $bodyParamBlock + # and a process section to handle the body +{ +process { + $completeBody = [Ordered]@{} + foreach ($bodyParameterName in $bodyParameterNames) { + if ($bodyParameterName) { + if ($PSBoundParameters.ContainsKey($bodyParameterName)) { + $completeBody[$bodyParameterName] = $PSBoundParameters[$bodyParameterName] + } else { + $bodyDefault = $ExecutionContext.SessionState.PSVariable.Get($bodyParameterName).Value + if ($null -ne $bodyDefault) { + $completeBody[$bodyParameterName] = $bodyDefault + } + } + } + } + + $completeBody = ConvertRestInput $completeBody + + $bodyContent = + if ($ContentType -match 'x-www-form-urlencoded') { + @(foreach ($bodyPart in $completeBody.GetEnumerator()) { + "$($bodyPart.Key.ToString().ToLower())=$([Web.HttpUtility]::UrlEncode($bodyPart.Value))" + }) -join '&' + } elseif ($ContentType -match 'json' -or -not $ContentType) { + ConvertTo-Json $completeBody + } + + if ($bodyContent -and $method -ne 'get') { + $invokeSplat.Body = $bodyContent + } +} +} + } + + # Last but not least, include the part of process that calls the REST api. + { +process { + Write-Verbose "$($invokeSplat.Uri)" + if ($ForEachOutput) { + if ($ForEachOutput.Ast.ProcessBlock) { + & $invokerCommandinfo @invokeSplat | & $ForEachOutput + } else { + & $invokerCommandinfo @invokeSplat | ForEach-Object -Process $ForEachOutput + } + } else { + & $invokerCommandinfo @invokeSplat + } +} + } + ) + + # Join all of the parts together and you've got yourself a RESTful function. + $RestScript | + Join-PipeScript +} + + +} + diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-Dot.ps.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-Dot.ps.ps1 new file mode 100644 index 000000000..ce52cb4bd --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-Dot.ps.ps1 @@ -0,0 +1,316 @@ +[ValidatePattern("PipeScript")] +param() + +Template function PipeScript.Dot { + <# + .SYNOPSIS + Dot Notation + .DESCRIPTION + Dot Notation simplifies multiple operations on one or more objects. + + Any command named . (followed by a letter) will be treated as the name of a method or property. + + If it is followed by parenthesis, these will be treated as method arguments. + + If it is followed by a ScriptBlock, a dynamic property will be created that will return the result of that script block. + + If any other arguments are found before the next .Name, they will be considered arguments to a method. + .EXAMPLE + .> { + [DateTime]::now | .Month .Day .Year + } + .EXAMPLE + .> { + "abc", "123", "abc123" | .Length + } + .EXAMPLE + .> { 1.99 | .ToString 'C' [CultureInfo]'gb-gb' } + .EXAMPLE + .> { 1.99 | .ToString('C') } + .EXAMPLE + .> { 1..5 | .Number { $_ } .Even { -not ($_ % 2) } .Odd { ($_ % 2) -as [bool]} } + .EXAMPLE + .> { .ID { Get-Random } .Count { 0 } .Total { 10 }} + .EXAMPLE + .> { + # Declare a new object + .Property = "ConstantValue" .Numbers = 1..100 .Double = { + param($n) + $n * 2 + } .EvenNumbers = { + $this.Numbers | Where-Object { -not ($_ % 2)} + } .OddNumbers = { + $this.Numbers | Where-Object { $_ % 2} + } + } + #> + [ValidateScript({ + $commandAst = $_ + $DotChainPattern = '^\.\p{L}' + if (-not $commandAst.CommandElements) { return $false } + $commandAst.CommandElements[0].Value -match $DotChainPattern + })] + param( + [Parameter(Mandatory,ParameterSetName='Command',ValueFromPipeline)] + [Management.Automation.Language.CommandAst] + $CommandAst + ) + + begin { + $DotProperty = { +if ($in.PSObject.Methods[$PropertyName].OverloadDefinitions -match '\(\)$') { + $in.$PropertyName.Invoke() +} elseif ($in.PSObject.Properties[$PropertyName]) { + $in.$PropertyName +} + } + + $DotMethod = { $in.$MethodName($MethodArguments) } + } + + process { + + # Create a collection for the entire chain of operations and their arguments. + $DotChain = @() + $DotArgsAst = @() + $DotChainPart = '' + $DotChainPattern = '^\.\p{L}' + + $targetCommandAst = $CommandAst + $commandSequence = @() + $lastCommandAst = $null + # Then, walk over each element of the commands + $CommandElements = @(do { + $lastCommandAst = $targetCommandAst + $commandSequence += $targetCommandAst + $targetCommandAst.CommandElements + # If the grandparent didn't have a list of statements, this is the only dot sequence in the chain. + if (-not $CommandAst.Parent.Parent.Statements) { break } + $nextStatementIndex = $commandAst.Parent.Parent.Statements.IndexOf($targetCommandAst.Parent) + 1 + $nextStatement = $CommandAst.Parent.Parent.Statements[$nextStatementIndex] + if ($nextStatement -isnot [Management.Automation.Language.PipelineAst]) { + break + } + if ($nextStatement.PipelineElements[0] -isnot + [Management.Automation.Language.CommandAst]) { + break + } + if ($nextStatement.PipelineElements[0].CommandElements[0].Value -notmatch + $DotChainPattern) { + break + } + $targetCommandAst = $CommandAst.Parent.Parent.Statements[$nextStatementIndex].PipelineElements[0] + } while ($targetCommandAst)) + + for ( $elementIndex =0 ; $elementIndex -lt $CommandElements.Count; $elementIndex++) { + # If we are on the first element, or the command element starts with the DotChainPattern + if ($elementIndex -eq 0 -or $CommandElements[$elementIndex].Value -match $DotChainPattern) { + if ($DotChainPart) { + $DotChain += [PSCustomObject]@{ + PSTypeName = 'PipeScript.Dot.Chain' + Name = $DotChainPart + Arguments = $DotArgsAst + } + } + + $DotArgsAst = @() + + # A given step started with dots can have more than one step in the chain specified. + $elementDotChain = $CommandElements[$elementIndex].Value.Split('.') + [Array]::Reverse($elementDotChain) + $LastElement, $otherElements = $elementDotChain + if ($otherElements) { + foreach ($element in $otherElements) { + $DotChain += [PSCustomObject]@{ + PSTypeName = 'PipeScript.Dot.Chain' + Name = $element + Arguments = @() + } + } + } + + $DotChainPart = $LastElement + } + # If we are not on the first index or the element does not start with a dot, it is an argument. + else { + $DotArgsAst += $CommandElements[$elementIndex] + } + } + + if ($DotChainPart) { + $DotChain += [PSCustomObject]@{ + PSTypeName = 'PipeScript.Dot.Chain' + Name = $DotChainPart + Arguments = $DotArgsAst + } + } + + + $NewScript = @() + $indent = 0 + $WasPipedTo = $CommandAst.IsPipedTo + + # By default, we are not creating a property bag. + # This default will change if: + # * More than one property is defined + # * A property is explicitly assigned + $isPropertyBag = $false + + # If we were piped to, adjust indent (for now) + if ($WasPipedTo) { + $indent += 4 + } + + # Declare the start of the chain (even if we don't use it) + $propertyBagStart = (' ' * $indent) + '[PSCustomObject][Ordered]@{' + # and keep track of all items we must post process. + $PostProcess = @() + + # If more than one item was in the chain + if ($DotChain.Length -ge 0) { + $indent += 4 # adjust indentation + } + + # Walk thru all items in the chain of properties. + foreach ($Dot in $DotChain) { + $firstDotArg, $secondDotArg, $restDotArgs = $dot.Arguments + # Determine what will be the segment of the dot chain. + $thisSegement = + # If the dot chain has no arguments, treat it as a property + if (-not $dot.Arguments) { + $DotProperty -replace '\$PropertyName', "'$($dot.Name)'" + } + # If the dot chain's first argument is an assignment + elseif ($firstDotArg -is [Management.Automation.Language.StringConstantExpressionAst] -and + $firstDotArg.Value -eq '=') { + $isPropertyBag = $true + # and the second is a script block + if ($secondDotArg -is [Management.Automation.Language.ScriptBlockExpressionAst]) { + # it will become either a [ScriptMethod] or [ScriptProperty] + $secondScriptBlock = [ScriptBlock]::Create( + $secondDotArg.Extent.ToString() -replace '^\{' -replace '\}$' + ) + + # If the script block had parameters (even if they were empty parameters) + # It should become a ScriptMethod + if ($secondScriptBlock.Ast.ParamBlock) { + "[PSScriptMethod]::New('$($dot.Name)', $secondDotArg)" + } else { + # Otherwise, it will become a ScriptProperty + "[PSScriptProperty]::New('$($dot.Name)', $secondDotArg)" + } + $PostProcess += $dot.Name + } + # If we had an array of arguments, and both elements were ScriptBlocks + elseif ($secondDotArg -is [Management.Automation.Language.ArrayLiteralAst] -and + $secondDotArg.Elements.Count -eq 2 -and + $secondDotArg.Elements[0] -is [Management.Automation.Language.ScriptBlockExpressionAst] -and + $secondDotArg.Elements[1] -is [Management.Automation.Language.ScriptBlockExpressionAst] + ) { + # Then we will treat this as a settable script block + $PostProcess += $dot.Name + "[PSScriptProperty]::New('$($dot.Name)', $($secondDotArg.Elements[0]), $($secondDotArg.Elements[1]))" + } + elseif (-not $restDotArgs) { + # Otherwise, if we only have one argument, use the expression directly + $secondDotArg.Extent.ToString() + } elseif ($restDotArgs) { + # Otherwise, if we had multiple values, create a list. + @( + $secondDotArg.Extent.ToString() + foreach ($otherDotArg in $restDotArgs) { + $otherDotArg.Extent.Tostring() + } + ) -join ',' + } + } + # If the dot chain's first argument is a ScriptBlock + elseif ($firstDotArg -is [Management.Automation.Language.ScriptBlockExpressionAst]) + { + # Run that script block + "& $($firstDotArg.Extent.ToString())" + } + elseif ($firstDotArg -is [Management.Automation.Language.ParenExpressionAst]) { + # If the first argument is a parenthesis, assume the contents to be method arguments + $DotMethod -replace '\$MethodName', $dot.Name -replace '\(\$MethodArguments\)',$firstDotArg.ToString() + } + else { + # If the first argument is anything else, assume all remaining arguments to be method parameters. + $DotMethod -replace '\$MethodName', $dot.Name -replace '\(\$MethodArguments\)',( + '(' + ($dot.Arguments -join ',') + ')' + ) + } + + # Now we add the segment to the total script + $NewScript += + if (-not $isPropertyBag -and $DotChain.Length -eq 1 -and $thisSegement -notmatch '^\[PS') { + # If the dot chain is a single item, and not part of a property bag, include it directly + "$(' ' * ($indent - 4))$thisSegement" + } else { + + $isPropertyBag = $true + # Otherwise include this segment as a hashtable assignment with the correct indentation. + $thisSegement = @($thisSegement -split '[\r\n]+' -ne '' -replace '$', (' ' * 8)) -join [Environment]::NewLine + @" +$(' ' * $indent)'$($dot.Name.Replace("'","''"))' = +$(' ' * ($indent + 4))$thisSegement +"@ + } + } + + + # If we were generating a property bag + if ($isPropertyBag) { + if ($WasPipedTo) { # and it was piped to + # Add the start of the pipeline and the property bag start to the top of the script. + $NewScript = @('& { process {') + ((' ' * $indent) + '$in = $this = $_') + $propertyBagStart + $NewScript + } else { + # If it was not piped to, just add the start of the property bag + $newScript = @($propertyBagStart) + $NewScript + } + } elseif ($WasPipedTo) { + # If we were piped to (but were not a property bag) + $indent -= 4 + # add the start of the pipeline to the top of the script. + $newScript = @('& { process {') + ((' ' * $indent) + '$in = $this = $_') + $NewScript + } + + # If we were a property bag + if ($isPropertyBag) { + # close out the script + $NewScript += ($(' ' * $indent) + '}') + $indent -= 4 + } + + # If there was post processing + if ($PostProcess) { + # Change the property bag start to assign it to a variable + $NewScript = $newScript -replace ($propertyBagStart -replace '\W', '\$0'), "`$Out = $propertyBagStart" + foreach ($post in $PostProcess) { + # and change any [PSScriptProperty] or [PSScriptMethod] into a method on that object. + $newScript += "`$Out.PSObject.Members.Add(`$out.$Post)" + } + # Then output. + $NewScript += '$Out' + } + + # If we were piped to + if ($WasPipedTo) { + # close off the script. + $NewScript += '} }' + } else { + # otherwise, make it a subexpression + $NewScript = '$(' + ($NewScript -join [Environment]::NewLine) + ')' + } + + $NewScript = $NewScript -join [Environment]::Newline + + # Return the created script. + $newScriptBlock = [scriptblock]::Create($NewScript) + + if ($targetCommandAst -ne $CommandAst) { + $newScriptBlock | Add-Member NoteProperty SkipUntil $commandSequence + } + $newScriptBlock + } +} \ No newline at end of file diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-Dot.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-Dot.ps1 new file mode 100644 index 000000000..2fc3de3f9 --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-Dot.ps1 @@ -0,0 +1,320 @@ +[ValidatePattern("PipeScript")] +param() + + +function Template.PipeScript.Dot { + + <# + .SYNOPSIS + Dot Notation + .DESCRIPTION + Dot Notation simplifies multiple operations on one or more objects. + + Any command named . (followed by a letter) will be treated as the name of a method or property. + + If it is followed by parenthesis, these will be treated as method arguments. + + If it is followed by a ScriptBlock, a dynamic property will be created that will return the result of that script block. + + If any other arguments are found before the next .Name, they will be considered arguments to a method. + .EXAMPLE + .> { + [DateTime]::now | .Month .Day .Year + } + .EXAMPLE + .> { + "abc", "123", "abc123" | .Length + } + .EXAMPLE + .> { 1.99 | .ToString 'C' [CultureInfo]'gb-gb' } + .EXAMPLE + .> { 1.99 | .ToString('C') } + .EXAMPLE + .> { 1..5 | .Number { $_ } .Even { -not ($_ % 2) } .Odd { ($_ % 2) -as [bool]} } + .EXAMPLE + .> { .ID { Get-Random } .Count { 0 } .Total { 10 }} + .EXAMPLE + .> { + # Declare a new object + .Property = "ConstantValue" .Numbers = 1..100 .Double = { + param($n) + $n * 2 + } .EvenNumbers = { + $this.Numbers | Where-Object { -not ($_ % 2)} + } .OddNumbers = { + $this.Numbers | Where-Object { $_ % 2} + } + } + #> + [ValidateScript({ + $commandAst = $_ + $DotChainPattern = '^\.\p{L}' + if (-not $commandAst.CommandElements) { return $false } + $commandAst.CommandElements[0].Value -match $DotChainPattern + })] + param( + [Parameter(Mandatory,ParameterSetName='Command',ValueFromPipeline)] + [Management.Automation.Language.CommandAst] + $CommandAst + ) + + begin { + $DotProperty = { +if ($in.PSObject.Methods[$PropertyName].OverloadDefinitions -match '\(\)$') { + $in.$PropertyName.Invoke() +} elseif ($in.PSObject.Properties[$PropertyName]) { + $in.$PropertyName +} + } + + $DotMethod = { $in.$MethodName($MethodArguments) } + } + + process { + + # Create a collection for the entire chain of operations and their arguments. + $DotChain = @() + $DotArgsAst = @() + $DotChainPart = '' + $DotChainPattern = '^\.\p{L}' + + $targetCommandAst = $CommandAst + $commandSequence = @() + $lastCommandAst = $null + # Then, walk over each element of the commands + $CommandElements = @(do { + $lastCommandAst = $targetCommandAst + $commandSequence += $targetCommandAst + $targetCommandAst.CommandElements + # If the grandparent didn't have a list of statements, this is the only dot sequence in the chain. + if (-not $CommandAst.Parent.Parent.Statements) { break } + $nextStatementIndex = $commandAst.Parent.Parent.Statements.IndexOf($targetCommandAst.Parent) + 1 + $nextStatement = $CommandAst.Parent.Parent.Statements[$nextStatementIndex] + if ($nextStatement -isnot [Management.Automation.Language.PipelineAst]) { + break + } + if ($nextStatement.PipelineElements[0] -isnot + [Management.Automation.Language.CommandAst]) { + break + } + if ($nextStatement.PipelineElements[0].CommandElements[0].Value -notmatch + $DotChainPattern) { + break + } + $targetCommandAst = $CommandAst.Parent.Parent.Statements[$nextStatementIndex].PipelineElements[0] + } while ($targetCommandAst)) + + for ( $elementIndex =0 ; $elementIndex -lt $CommandElements.Count; $elementIndex++) { + # If we are on the first element, or the command element starts with the DotChainPattern + if ($elementIndex -eq 0 -or $CommandElements[$elementIndex].Value -match $DotChainPattern) { + if ($DotChainPart) { + $DotChain += [PSCustomObject]@{ + PSTypeName = 'PipeScript.Dot.Chain' + Name = $DotChainPart + Arguments = $DotArgsAst + } + } + + $DotArgsAst = @() + + # A given step started with dots can have more than one step in the chain specified. + $elementDotChain = $CommandElements[$elementIndex].Value.Split('.') + [Array]::Reverse($elementDotChain) + $LastElement, $otherElements = $elementDotChain + if ($otherElements) { + foreach ($element in $otherElements) { + $DotChain += [PSCustomObject]@{ + PSTypeName = 'PipeScript.Dot.Chain' + Name = $element + Arguments = @() + } + } + } + + $DotChainPart = $LastElement + } + # If we are not on the first index or the element does not start with a dot, it is an argument. + else { + $DotArgsAst += $CommandElements[$elementIndex] + } + } + + if ($DotChainPart) { + $DotChain += [PSCustomObject]@{ + PSTypeName = 'PipeScript.Dot.Chain' + Name = $DotChainPart + Arguments = $DotArgsAst + } + } + + + $NewScript = @() + $indent = 0 + $WasPipedTo = $CommandAst.IsPipedTo + + # By default, we are not creating a property bag. + # This default will change if: + # * More than one property is defined + # * A property is explicitly assigned + $isPropertyBag = $false + + # If we were piped to, adjust indent (for now) + if ($WasPipedTo) { + $indent += 4 + } + + # Declare the start of the chain (even if we don't use it) + $propertyBagStart = (' ' * $indent) + '[PSCustomObject][Ordered]@{' + # and keep track of all items we must post process. + $PostProcess = @() + + # If more than one item was in the chain + if ($DotChain.Length -ge 0) { + $indent += 4 # adjust indentation + } + + # Walk thru all items in the chain of properties. + foreach ($Dot in $DotChain) { + $firstDotArg, $secondDotArg, $restDotArgs = $dot.Arguments + # Determine what will be the segment of the dot chain. + $thisSegement = + # If the dot chain has no arguments, treat it as a property + if (-not $dot.Arguments) { + $DotProperty -replace '\$PropertyName', "'$($dot.Name)'" + } + # If the dot chain's first argument is an assignment + elseif ($firstDotArg -is [Management.Automation.Language.StringConstantExpressionAst] -and + $firstDotArg.Value -eq '=') { + $isPropertyBag = $true + # and the second is a script block + if ($secondDotArg -is [Management.Automation.Language.ScriptBlockExpressionAst]) { + # it will become either a [ScriptMethod] or [ScriptProperty] + $secondScriptBlock = [ScriptBlock]::Create( + $secondDotArg.Extent.ToString() -replace '^\{' -replace '\}$' + ) + + # If the script block had parameters (even if they were empty parameters) + # It should become a ScriptMethod + if ($secondScriptBlock.Ast.ParamBlock) { + "[PSScriptMethod]::New('$($dot.Name)', $secondDotArg)" + } else { + # Otherwise, it will become a ScriptProperty + "[PSScriptProperty]::New('$($dot.Name)', $secondDotArg)" + } + $PostProcess += $dot.Name + } + # If we had an array of arguments, and both elements were ScriptBlocks + elseif ($secondDotArg -is [Management.Automation.Language.ArrayLiteralAst] -and + $secondDotArg.Elements.Count -eq 2 -and + $secondDotArg.Elements[0] -is [Management.Automation.Language.ScriptBlockExpressionAst] -and + $secondDotArg.Elements[1] -is [Management.Automation.Language.ScriptBlockExpressionAst] + ) { + # Then we will treat this as a settable script block + $PostProcess += $dot.Name + "[PSScriptProperty]::New('$($dot.Name)', $($secondDotArg.Elements[0]), $($secondDotArg.Elements[1]))" + } + elseif (-not $restDotArgs) { + # Otherwise, if we only have one argument, use the expression directly + $secondDotArg.Extent.ToString() + } elseif ($restDotArgs) { + # Otherwise, if we had multiple values, create a list. + @( + $secondDotArg.Extent.ToString() + foreach ($otherDotArg in $restDotArgs) { + $otherDotArg.Extent.Tostring() + } + ) -join ',' + } + } + # If the dot chain's first argument is a ScriptBlock + elseif ($firstDotArg -is [Management.Automation.Language.ScriptBlockExpressionAst]) + { + # Run that script block + "& $($firstDotArg.Extent.ToString())" + } + elseif ($firstDotArg -is [Management.Automation.Language.ParenExpressionAst]) { + # If the first argument is a parenthesis, assume the contents to be method arguments + $DotMethod -replace '\$MethodName', $dot.Name -replace '\(\$MethodArguments\)',$firstDotArg.ToString() + } + else { + # If the first argument is anything else, assume all remaining arguments to be method parameters. + $DotMethod -replace '\$MethodName', $dot.Name -replace '\(\$MethodArguments\)',( + '(' + ($dot.Arguments -join ',') + ')' + ) + } + + # Now we add the segment to the total script + $NewScript += + if (-not $isPropertyBag -and $DotChain.Length -eq 1 -and $thisSegement -notmatch '^\[PS') { + # If the dot chain is a single item, and not part of a property bag, include it directly + "$(' ' * ($indent - 4))$thisSegement" + } else { + + $isPropertyBag = $true + # Otherwise include this segment as a hashtable assignment with the correct indentation. + $thisSegement = @($thisSegement -split '[\r\n]+' -ne '' -replace '$', (' ' * 8)) -join [Environment]::NewLine + @" +$(' ' * $indent)'$($dot.Name.Replace("'","''"))' = +$(' ' * ($indent + 4))$thisSegement +"@ + } + } + + + # If we were generating a property bag + if ($isPropertyBag) { + if ($WasPipedTo) { # and it was piped to + # Add the start of the pipeline and the property bag start to the top of the script. + $NewScript = @('& { process {') + ((' ' * $indent) + '$in = $this = $_') + $propertyBagStart + $NewScript + } else { + # If it was not piped to, just add the start of the property bag + $newScript = @($propertyBagStart) + $NewScript + } + } elseif ($WasPipedTo) { + # If we were piped to (but were not a property bag) + $indent -= 4 + # add the start of the pipeline to the top of the script. + $newScript = @('& { process {') + ((' ' * $indent) + '$in = $this = $_') + $NewScript + } + + # If we were a property bag + if ($isPropertyBag) { + # close out the script + $NewScript += ($(' ' * $indent) + '}') + $indent -= 4 + } + + # If there was post processing + if ($PostProcess) { + # Change the property bag start to assign it to a variable + $NewScript = $newScript -replace ($propertyBagStart -replace '\W', '\$0'), "`$Out = $propertyBagStart" + foreach ($post in $PostProcess) { + # and change any [PSScriptProperty] or [PSScriptMethod] into a method on that object. + $newScript += "`$Out.PSObject.Members.Add(`$out.$Post)" + } + # Then output. + $NewScript += '$Out' + } + + # If we were piped to + if ($WasPipedTo) { + # close off the script. + $NewScript += '} }' + } else { + # otherwise, make it a subexpression + $NewScript = '$(' + ($NewScript -join [Environment]::NewLine) + ')' + } + + $NewScript = $NewScript -join [Environment]::Newline + + # Return the created script. + $newScriptBlock = [scriptblock]::Create($NewScript) + + if ($targetCommandAst -ne $CommandAst) { + $newScriptBlock | Add-Member NoteProperty SkipUntil $commandSequence + } + $newScriptBlock + } + +} + diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-DoubleDot.ps.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-DoubleDot.ps.ps1 new file mode 100644 index 000000000..2fb68c070 --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-DoubleDot.ps.ps1 @@ -0,0 +1,48 @@ +[ValidatePattern('PipeScript')] +param() + +Template function PipeScript.DoubleDot { + <# + .SYNOPSIS + Supports "Double Dotted" location changes + .DESCRIPTION + This little compiler is here to help small syntax flubs and relative file traversal. + + Any pair of dots will be read as "Push-Location up N directories" + + `^` + Any pair of dots will be read as "Pop-Location n times" + .EXAMPLE + Invoke-PipeScript { .. } + .EXAMPLE + Invoke-PipeScript { ^.. } + #> + [ValidateScript({ + $commandAst = $_ + $IsOnlyDoubleDot = '^\^{0,1}(?:\.\.){1,}$' + if (-not $commandAst.CommandElements) { return $false } + if ($commandAst.CommandElements.Count -gt 1 ) { return $false } + $commandAst.CommandElements[0].Value -match $IsOnlyDoubleDot + })] + param( + # The command ast + [Parameter(Mandatory,ParameterSetName='Command',ValueFromPipeline)] + [Management.Automation.Language.CommandAst] + $CommandAst + ) + + process { + [scriptblock]::Create("`$($( + if ($CommandAst -notmatch "^\^") { # Going up! + "Push-Location (" + {$(if ($PSScriptRoot) { $PSScriptRoot} else { $pwd})} + [Regex]::New("\.\.").Replace("$CommandAst", " | Split-Path") + ")" + } else { + [Regex]::New("\.\.").Replace( # Going down + $CommandAst -replace "^\^", + "Pop-Location;" + ) + } + )`)") + } +} \ No newline at end of file diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-DoubleDot.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-DoubleDot.ps1 new file mode 100644 index 000000000..3bbc7c323 --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-DoubleDot.ps1 @@ -0,0 +1,52 @@ +[ValidatePattern('PipeScript')] +param() + + +function Template.PipeScript.DoubleDot { + + <# + .SYNOPSIS + Supports "Double Dotted" location changes + .DESCRIPTION + This little compiler is here to help small syntax flubs and relative file traversal. + + Any pair of dots will be read as "Push-Location up N directories" + + `^` + Any pair of dots will be read as "Pop-Location n times" + .EXAMPLE + Invoke-PipeScript { .. } + .EXAMPLE + Invoke-PipeScript { ^.. } + #> + [ValidateScript({ + $commandAst = $_ + $IsOnlyDoubleDot = '^\^{0,1}(?:\.\.){1,}$' + if (-not $commandAst.CommandElements) { return $false } + if ($commandAst.CommandElements.Count -gt 1 ) { return $false } + $commandAst.CommandElements[0].Value -match $IsOnlyDoubleDot + })] + param( + # The command ast + [Parameter(Mandatory,ParameterSetName='Command',ValueFromPipeline)] + [Management.Automation.Language.CommandAst] + $CommandAst + ) + + process { + [scriptblock]::Create("`$($( + if ($CommandAst -notmatch "^\^") { # Going up! + "Push-Location (" + {$(if ($PSScriptRoot) { $PSScriptRoot} else { $pwd})} + [Regex]::New("\.\.").Replace("$CommandAst", " | Split-Path") + ")" + } else { + [Regex]::New("\.\.").Replace( # Going down + $CommandAst -replace "^\^", + "Pop-Location;" + ) + } + )`)") + } + +} + diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-DoubleEqualCompare.ps.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-DoubleEqualCompare.ps.ps1 new file mode 100644 index 000000000..52f5eddd6 --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-DoubleEqualCompare.ps.ps1 @@ -0,0 +1,66 @@ +[ValidatePattern('PipeScript')] +param() + +Template function PipeScript.DoubleEqualCompare { + <# + .SYNOPSIS + Allows equality comparison. + .DESCRIPTION + Allows most equality comparison using double equals (==). + + Many languages support this syntax. PowerShell does not. + + This transpiler enables equality comparison with ==. + .NOTES + This will not work if there is a constant on both sides of the expression + + + if ($null == $null) { "this will work"} + if ('' == '') { "this will not"} + + .EXAMPLE + Invoke-PipeScript -ScriptBlock { + $a = 1 + if ($a == 1 ) { + "A is $a" + } + } + .EXAMPLE + { + $a == "b" + } | .>PipeScript + #> + [ValidateScript({ + # This is valid if the assignment statement's + $AssignmentStatementAST = $_ + # The operator must be an equals + $AssignmentStatementAST.Operator -eq 'Equals' -and + # The right must start with = + $AssignmentStatementAST.Right -match '^=' -and + $AssignmentStatementAST.Parent.ToString() -match "$( + [Regex]::Escape($AssignmentStatementAST.Left.ToString()) + )\s{0,}==[^=]" + })] + param( + # The original assignment statement. + [Parameter(Mandatory,ValueFromPipeline)] + [Management.Automation.Language.AssignmentStatementAst] + $AssignmentStatementAST + ) + + process { + # This is a very trivial transpiler. + + # We create a new script by: + $newScript = + # taking the left side as-is. + $AssignmentStatementAST.Left.Extent.ToString() + + # replacing the = with ' -eq ' + ' -eq ' + + # And replacing any the = and any trailing whitespace. + ($AssignmentStatementAST.Right.Extent.ToString() -replace '^=\s{1,}') + + [scriptblock]::Create($newScript) + } +} + diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-DoubleEqualCompare.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-DoubleEqualCompare.ps1 new file mode 100644 index 000000000..9a3e4fa7c --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-DoubleEqualCompare.ps1 @@ -0,0 +1,71 @@ +[ValidatePattern('PipeScript')] +param() + + +function Template.PipeScript.DoubleEqualCompare { + + <# + .SYNOPSIS + Allows equality comparison. + .DESCRIPTION + Allows most equality comparison using double equals (==). + + Many languages support this syntax. PowerShell does not. + + This transpiler enables equality comparison with ==. + .NOTES + This will not work if there is a constant on both sides of the expression + + + if ($null == $null) { "this will work"} + if ('' == '') { "this will not"} + + .EXAMPLE + Invoke-PipeScript -ScriptBlock { + $a = 1 + if ($a == 1 ) { + "A is $a" + } + } + .EXAMPLE + { + $a == "b" + } | .>PipeScript + #> + [ValidateScript({ + # This is valid if the assignment statement's + $AssignmentStatementAST = $_ + # The operator must be an equals + $AssignmentStatementAST.Operator -eq 'Equals' -and + # The right must start with = + $AssignmentStatementAST.Right -match '^=' -and + $AssignmentStatementAST.Parent.ToString() -match "$( + [Regex]::Escape($AssignmentStatementAST.Left.ToString()) + )\s{0,}==[^=]" + })] + param( + # The original assignment statement. + [Parameter(Mandatory,ValueFromPipeline)] + [Management.Automation.Language.AssignmentStatementAst] + $AssignmentStatementAST + ) + + process { + # This is a very trivial transpiler. + + # We create a new script by: + $newScript = + # taking the left side as-is. + $AssignmentStatementAST.Left.Extent.ToString() + + # replacing the = with ' -eq ' + ' -eq ' + + # And replacing any the = and any trailing whitespace. + ($AssignmentStatementAST.Right.Extent.ToString() -replace '^=\s{1,}') + + [scriptblock]::Create($newScript) + } + +} + + + diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-Keyword.ps.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-Keyword.ps.ps1 new file mode 100644 index 000000000..754eabe63 --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-Keyword.ps.ps1 @@ -0,0 +1,104 @@ +[ValidatePattern('(?>PipeScript|Keyword)')] +param() + +Template function PipeScript.Keyword { + <# + .SYNOPSIS + Template a PipeScript language keyword. + .DESCRIPTION + A template for a PipeScript language keyword. + + This declares a keyword in the PipeScript language, which can be used to create new language constructs. + #> + + [Reflection.AssemblyMetaData('Order', -10)] + [ValidateScript({ + # This only applies to a command AST + $cmdAst = $_ -as [Management.Automation.Language.CommandAst] + if (-not $cmdAst) { return $false } + # It must have 3-4 elements. + if ($cmdAst.CommandElements.Count -lt 3 -or $cmdAst.CommandElements.Count -gt 4) { + return $false + } + # The first element must `keyword`. + if ($cmdAst.CommandElements[0].Value -notin 'keyword') { + return $false + } + # The second element must be a bareword + if ($cmdAst.CommandElements[1].StringConstantType -ne 'Bareword') { + return $false + } + + # It must have a script block + $scriptBlockFound = $false + foreach ($cmdElement in $cmdAst.CommandElements) { + if ($cmdElement -is [Management.Automation.Language.ScriptBlockExpressionAst]) { + $scriptBlockFound = $true + break + } + } + + if (-not $scriptBlockFound) { return $false } + return $true + })] + + [Alias('Keyword')] + [CmdletBinding(DefaultParameterSetName='None')] + param( + # The name of the keyword + [vbn(Position=0)] + [Alias('FunctionName')] + [string] + $KeywordName, + + # The definition of the keyword + [vbn(Position=1)] + [Alias('Definition','ScriptBlock')] + [ScriptBlock] + $KeywordDefinition, + + # The CommandAst. This is provided when compiling, and is used to extract the keyword name and definition. + [vfp(Mandatory,ParameterSetName='CommandAst')] + [Management.Automation.Language.CommandAst] + $CommandAst + ) + + process { + # Namespaced functions are really simple: + + if ($PSCmdlet.ParameterSetName -eq 'CommandAst') { + $KeywordName = $CommandAst.CommandElements[1].Value + foreach ($element in $CommandAst.CommandElements) { + if ($element -is [Management.Automation.Language.ScriptBlockExpressionAst]) { + $KeywordDefinition = $element.ScriptBlock.GetScriptBlock() + break + } + } + } + + $aliasNamesFound = @() + + foreach ($keywordAttribute in $KeywordDefinition.Attributes) { + if ($keywordAttribute -is [Alias]) { + $aliasNamesFound += $keywordAttribute.AliasNames + } + } + + # For now, we'll just ensure that all keywords have the appropriate alias. + # This will be expanded in the future. + if ($aliasNamesFound -notcontains "PipeScript.Template.Keyword.$($KeywordName)") { + $KeywordDefinition = [scriptblock]::create("[Alias('PipeScript.Template.Keyword.$($KeywordName)')]param()"), $KeywordDefinition | + Join-PipeScript + } + + # Redefine the function + $redefined = [ScriptBlock]::Create(" +function Keyword.$KeywordName { +$KeywordDefinition +} +") + # Return the compiled redefinition. + $redefined | Use-PipeScript + } +} + diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-Keyword.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-Keyword.ps1 new file mode 100644 index 000000000..0643a31e3 --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-Keyword.ps1 @@ -0,0 +1,109 @@ +[ValidatePattern('(?>PipeScript|Keyword)')] +param() + + +function Template.PipeScript.Keyword { + + <# + .SYNOPSIS + Template a PipeScript language keyword. + .DESCRIPTION + A template for a PipeScript language keyword. + + This declares a keyword in the PipeScript language, which can be used to create new language constructs. + #> + + [Reflection.AssemblyMetaData('Order', -10)] + [ValidateScript({ + # This only applies to a command AST + $cmdAst = $_ -as [Management.Automation.Language.CommandAst] + if (-not $cmdAst) { return $false } + # It must have 3-4 elements. + if ($cmdAst.CommandElements.Count -lt 3 -or $cmdAst.CommandElements.Count -gt 4) { + return $false + } + # The first element must `keyword`. + if ($cmdAst.CommandElements[0].Value -notin 'keyword') { + return $false + } + # The second element must be a bareword + if ($cmdAst.CommandElements[1].StringConstantType -ne 'Bareword') { + return $false + } + + # It must have a script block + $scriptBlockFound = $false + foreach ($cmdElement in $cmdAst.CommandElements) { + if ($cmdElement -is [Management.Automation.Language.ScriptBlockExpressionAst]) { + $scriptBlockFound = $true + break + } + } + + if (-not $scriptBlockFound) { return $false } + return $true + })] + + [Alias('Keyword')] + [CmdletBinding(DefaultParameterSetName='None')] + param( + # The name of the keyword + [Parameter(ValueFromPipelineByPropertyName,Position=0)] + [Alias('FunctionName')] + [string] + $KeywordName, + + # The definition of the keyword + [Parameter(ValueFromPipelineByPropertyName,Position=1)] + [Alias('Definition','ScriptBlock')] + [ScriptBlock] + $KeywordDefinition, + + # The CommandAst. This is provided when compiling, and is used to extract the keyword name and definition. + [Parameter(Mandatory,ParameterSetName='CommandAst',ValueFromPipeline)] + [Management.Automation.Language.CommandAst] + $CommandAst + ) + + process { + # Namespaced functions are really simple: + + if ($PSCmdlet.ParameterSetName -eq 'CommandAst') { + $KeywordName = $CommandAst.CommandElements[1].Value + foreach ($element in $CommandAst.CommandElements) { + if ($element -is [Management.Automation.Language.ScriptBlockExpressionAst]) { + $KeywordDefinition = $element.ScriptBlock.GetScriptBlock() + break + } + } + } + + $aliasNamesFound = @() + + foreach ($keywordAttribute in $KeywordDefinition.Attributes) { + if ($keywordAttribute -is [Alias]) { + $aliasNamesFound += $keywordAttribute.AliasNames + } + } + + # For now, we'll just ensure that all keywords have the appropriate alias. + # This will be expanded in the future. + if ($aliasNamesFound -notcontains "PipeScript.Template.Keyword.$($KeywordName)") { + $KeywordDefinition = [scriptblock]::create("[Alias('PipeScript.Template.Keyword.$($KeywordName)')]param()"), $KeywordDefinition | + Join-PipeScript + } + + # Redefine the function + $redefined = [ScriptBlock]::Create(" +function Keyword.$KeywordName { +$KeywordDefinition +} +") + # Return the compiled redefinition. + $redefined | Use-PipeScript + } + +} + + + diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-NamespacedAlias.ps.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-NamespacedAlias.ps.ps1 new file mode 100644 index 000000000..2fbc83514 --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-NamespacedAlias.ps.ps1 @@ -0,0 +1,102 @@ +[ValidatePattern('PipeScript')] +param() + +Template function PipeScript.NamespacedAlias { + <# + .SYNOPSIS + Declares a namespaced alias + .DESCRIPTION + Declares an alias in a namespace. + + Namespaces are used to logically group functionality in a way that can be efficiently queried. + .EXAMPLE + . { + PipeScript.Template alias .\Transpilers\Templates\*.psx.ps1 + }.Transpile() + #> + [Reflection.AssemblyMetaData('Order', -10)] + [ValidateScript({ + # This only applies to a command AST + $cmdAst = $_ -as [Management.Automation.Language.CommandAst] + if (-not $cmdAst) { return $false } + # It must have at least 3 elements. + if ($cmdAst.CommandElements.Count -lt 3) { + return $false + } + # The second element must be 'alias'. + if ($cmdAst.CommandElements[1].Value -ne 'alias') { + return $false + } + + # Attempt to resolve the command + $potentialCmdName = $cmdAst.CommandElements[0] + # Attempt to resolve the command + if (-not $global:AllCommands) { + $global:AllCommands = $executionContext.SessionState.InvokeCommand.GetCommands('*','Alias,Function,Cmdlet', $true) + } + $potentialCmdName = "$($cmdAst.CommandElements[0])" + return -not ($global:AllCommands.Name -eq $potentialCmdName) + })] + param( + # The CommandAST that will be transformed. + [Parameter(Mandatory,ValueFromPipeline)] + [Management.Automation.Language.CommandAst] + $CommandAst + ) + + process { + $namespace, $null, $locations = $CommandAst.CommandElements + + $namespaceSeparatorPattern = [Regex]::new('[\p{P}]{1,}','RightToLeft') + + $namespaceSeparator = $namespaceSeparatorPattern.Match($namespace).Value + # If there was no punctuation, the namespace separator will be a '.' + if (-not $namespaceSeparator) {$namespaceSeparator = '.'} + # If the pattern was empty brackets `[]`, make the separator `[`. + elseif ($namespaceSeparator -eq '[]') { $namespaceSeparator = '[' } + # If the pattern was `<>`, make the separator `<`. + elseif ($namespaceSeparator -eq '<>') { $namespaceSeparator = '<' } + + $namespace = $namespace -replace "$namespaceSeparatorPattern$" + + $locationsEmbed = '"' + $($locations -replace '"','`"' -join '","') + '"' + + $scriptBlockToCreate = + " +`$aliasNamespace = '$($namespace -replace "'","''")' +`$aliasNamespaceSeparator = '$namespaceSeparator' +`$aliasesToCreate = [Ordered]@{} +foreach (`$aliasNamespacePattern in $locationsEmbed) { +" + { + $commandsToAlias = $ExecutionContext.SessionState.InvokeCommand.GetCommands($aliasNamespacePattern, 'All', $true) + if ($commandsToAlias) { + foreach ($commandToAlias in $commandsToAlias) { + $aliasName = $aliasNamespace, $commandToAlias.Name -join $aliasNamespaceSeparator + $aliasesToCreate[$aliasName] = $commandsToAlias + } + } + elseif (Test-Path $aliasNamespacePattern) { + foreach ($fileToAlias in (Get-ChildItem -Path $aliasNamespacePattern)) { + $aliasName = $aliasNamespace, $fileToAlias.Name -join $aliasNamespaceSeparator + $aliasesToCreate[$aliasName] = $fileToAlias.FullName + } + } + else { + $aliasNamespace += $aliasNamespaceSeparator + $aliasNamespacePattern + $aliasNamespaceSeparator + } +} + " +} +" + { +foreach ($toCreateAlias in $aliasesToCreate.GetEnumerator()) { + $aliasName, $aliasedTo = $toCreateAlias.Key, $toCreateAlias.Value + if ($aliasNamespaceSeparator -match '(?>\[|\<)$') { + if ($matches.0 -eq '[') { $aliasName += ']' } + elseif ($matches.0 -eq '<') { $aliasName += '>' } + } + Set-Alias $aliasName $commandToAlias +} +} + + [ScriptBlock]::Create($scriptBlockToCreate) + } +} \ No newline at end of file diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-NamespacedAlias.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-NamespacedAlias.ps1 new file mode 100644 index 000000000..50c99bcf5 --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-NamespacedAlias.ps1 @@ -0,0 +1,106 @@ +[ValidatePattern('PipeScript')] +param() + + +function Template.PipeScript.NamespacedAlias { + + <# + .SYNOPSIS + Declares a namespaced alias + .DESCRIPTION + Declares an alias in a namespace. + + Namespaces are used to logically group functionality in a way that can be efficiently queried. + .EXAMPLE + . { + PipeScript.Template alias .\Transpilers\Templates\*.psx.ps1 + }.Transpile() + #> + [Reflection.AssemblyMetaData('Order', -10)] + [ValidateScript({ + # This only applies to a command AST + $cmdAst = $_ -as [Management.Automation.Language.CommandAst] + if (-not $cmdAst) { return $false } + # It must have at least 3 elements. + if ($cmdAst.CommandElements.Count -lt 3) { + return $false + } + # The second element must be 'alias'. + if ($cmdAst.CommandElements[1].Value -ne 'alias') { + return $false + } + + # Attempt to resolve the command + $potentialCmdName = $cmdAst.CommandElements[0] + # Attempt to resolve the command + if (-not $global:AllCommands) { + $global:AllCommands = $executionContext.SessionState.InvokeCommand.GetCommands('*','Alias,Function,Cmdlet', $true) + } + $potentialCmdName = "$($cmdAst.CommandElements[0])" + return -not ($global:AllCommands.Name -eq $potentialCmdName) + })] + param( + # The CommandAST that will be transformed. + [Parameter(Mandatory,ValueFromPipeline)] + [Management.Automation.Language.CommandAst] + $CommandAst + ) + + process { + $namespace, $null, $locations = $CommandAst.CommandElements + + $namespaceSeparatorPattern = [Regex]::new('[\p{P}]{1,}','RightToLeft') + + $namespaceSeparator = $namespaceSeparatorPattern.Match($namespace).Value + # If there was no punctuation, the namespace separator will be a '.' + if (-not $namespaceSeparator) {$namespaceSeparator = '.'} + # If the pattern was empty brackets `[]`, make the separator `[`. + elseif ($namespaceSeparator -eq '[]') { $namespaceSeparator = '[' } + # If the pattern was `<>`, make the separator `<`. + elseif ($namespaceSeparator -eq '<>') { $namespaceSeparator = '<' } + + $namespace = $namespace -replace "$namespaceSeparatorPattern$" + + $locationsEmbed = '"' + $($locations -replace '"','`"' -join '","') + '"' + + $scriptBlockToCreate = + " +`$aliasNamespace = '$($namespace -replace "'","''")' +`$aliasNamespaceSeparator = '$namespaceSeparator' +`$aliasesToCreate = [Ordered]@{} +foreach (`$aliasNamespacePattern in $locationsEmbed) { +" + { + $commandsToAlias = $ExecutionContext.SessionState.InvokeCommand.GetCommands($aliasNamespacePattern, 'All', $true) + if ($commandsToAlias) { + foreach ($commandToAlias in $commandsToAlias) { + $aliasName = $aliasNamespace, $commandToAlias.Name -join $aliasNamespaceSeparator + $aliasesToCreate[$aliasName] = $commandsToAlias + } + } + elseif (Test-Path $aliasNamespacePattern) { + foreach ($fileToAlias in (Get-ChildItem -Path $aliasNamespacePattern)) { + $aliasName = $aliasNamespace, $fileToAlias.Name -join $aliasNamespaceSeparator + $aliasesToCreate[$aliasName] = $fileToAlias.FullName + } + } + else { + $aliasNamespace += $aliasNamespaceSeparator + $aliasNamespacePattern + $aliasNamespaceSeparator + } +} + " +} +" + { +foreach ($toCreateAlias in $aliasesToCreate.GetEnumerator()) { + $aliasName, $aliasedTo = $toCreateAlias.Key, $toCreateAlias.Value + if ($aliasNamespaceSeparator -match '(?>\[|\<)$') { + if ($matches.0 -eq '[') { $aliasName += ']' } + elseif ($matches.0 -eq '<') { $aliasName += '>' } + } + Set-Alias $aliasName $commandToAlias +} +} + + [ScriptBlock]::Create($scriptBlockToCreate) + } + +} + diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-NamespacedObject.ps.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-NamespacedObject.ps.ps1 new file mode 100644 index 000000000..789252386 --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-NamespacedObject.ps.ps1 @@ -0,0 +1,159 @@ +[ValidatePattern('PipeScript')] +param() + +Template function PipeScript.NamespacedObject { + <# + .SYNOPSIS + Namespaced functions + .DESCRIPTION + Allows the declaration of a object or singleton in a namespace. + + Namespaces are used to logically group functionality and imply standardized behavior. + .EXAMPLE + Invoke-PipeScript { + My Object Precious { $IsARing = $true; $BindsThemAll = $true } + My.Precious + } + #> + [Reflection.AssemblyMetaData('Order', -10)] + [ValidateScript({ + # This only applies to a command AST + $cmdAst = $_ -as [Management.Automation.Language.CommandAst] + if (-not $cmdAst) { return $false } + # It must have at 4-5 elements. + if ($cmdAst.CommandElements.Count -lt 4 -or $cmdAst.CommandElements.Count -gt 5) { + return $false + } + + # The second element must be a bareword + if ($cmdAst.CommandElements[1].StringConstantType -ne 'Bareword') { + return $false + } + + # The second element must the name of the object type. + if ($cmdAst.CommandElements[1].Value -notin + 'object', 'instance','factory','an','a','f','o','i', + 'singleton','single','constant','const','the','c','s','t' + ) { + return $false + } + + # The last element must be a ScriptBlock or HashtableAst + if ( + $cmdAst.CommandElements[-1] -isnot [Management.Automation.Language.ScriptBlockExpressionAst] -and + $cmdAst.CommandElements[-1] -isnot [Management.Automation.Language.HashtableAst] + ) { + return $false + } + + # Attempt to resolve the command + if (-not $global:AllCommands) { + $global:AllCommands = $executionContext.SessionState.InvokeCommand.GetCommands('*','Alias,Function,Cmdlet', $true) + } + $potentialCmdName = "$($cmdAst.CommandElements[0])" + return -not ($global:AllCommands.Name -eq $potentialCmdName) + })] + param( + # The CommandAST that will be transformed. + [Parameter(Mandatory,ValueFromPipeline)] + [Management.Automation.Language.CommandAst] + $CommandAst + ) + + process { + # Namespaced functions are really simple: + + # We use multiple assignment to pick out the parts of the function + $namespace, $objectType, $functionName, $objectDefinition = $CommandAst.CommandElements + + # Then, we determine the last punctuation. + $namespaceSeparatorPattern = [Regex]::new('[\p{P}<>]{1,}','RightToLeft') + $namespaceSeparator = $namespaceSeparatorPattern.Match($namespace).Value + # If there was no punctuation, the namespace separator will be a '.' + if (-not $namespaceSeparator) {$namespaceSeparator = '.'} + # If the pattern was empty brackets `[]`, make the separator `[`. + elseif ($namespaceSeparator -eq '[]') { $namespaceSeparator = '[' } + # If the pattern was `<>`, make the separator `<`. + elseif ($namespaceSeparator -eq '<>') { $namespaceSeparator = '<' } + + # Replace any trailing separators from the namespace. + $namespace = $namespace -replace "$namespaceSeparatorPattern$" + + $blockComments = '' + + $SingletonForms = 'singleton','single','constant','const','the','c','s','t' + $singletonPattern = "(?>$($SingletonForms -join '|'))" + + + + $defineInstance = + if ($objectDefinition -is [Management.Automation.Language.HashtableAst]) { + "[PSCustomObject][Ordered]$($objectDefinition)" + } + elseif ($objectDefinition -is [Management.Automation.Language.ScriptBlockExpressionAst]) { + $findBlockComments = [Regex]::New(" + \<\# # The opening tag + (? + (?:.|\s)+?(?=\z|\#>) # anything until the closing tag + ) + \#\> # the closing tag + ", 'IgnoreCase,IgnorePatternWhitespace', '00:00:01') + $foundBlockComments = $objectDefinition -match $findBlockComments + $objectDefinition = "{ +$($objectDefinition -replace '^\{' -replace '\}$') +Export-ModuleMember -Function * -Alias * -Cmdlet * -Variable * +}" + + if ($foundBlockComments -and $matches.Block) { + $blockComments = $null,"<#",$($matches.Block),"#>",$null -join [Environment]::Newline + } + "New-Module -ArgumentList @(@(`$input) + @(`$args)) -AsCustomObject $objectDefinition" + } + + + # Join the parts back together to get the new function name. + $NewFunctionName = $namespace,$namespaceSeparator,$functionName,$( + # If the namespace separator ends with `[` or `<`, try to close it + if ($namespaceSeparator -match '[\[\<]$') { + if ($matches.0 -eq '[') { ']' } + elseif ($matches.0 -eq '<') { '>' } + } + ) -ne '' -join '' + + $objectDefinition = + if ($objectType -match $singletonPattern) { + "{$(if ($blockComments) {$blockComments}) +$( + @('$this = $myInvocation.MyCommand' + 'if (-not $this.Instance) {' + "`$singletonInstance = $defineInstance" + '$singletonInstance.pstypenames.clear()' + "`$singletonInstance.pstypenames.add('$($NewFunctionName -replace "'","''")')" + "`$singletonInstance.pstypenames.add('$($namespace -replace $namespaceSeparatorPattern -replace "'","''")')" + 'Add-Member -InputObject $this -MemberType NoteProperty -Name Instance -Value $singletonInstance -Force' + '}' + '$this.Instance' + ) -join [Environment]::newLine +) + +}" + } else { + "{ +$(if ($blockComments) {$blockComments}) +`$Instance = $defineInstance +`$Instance.pstypenames.clear() +`$Instance.pstypenames.add('$($NewFunctionName -replace "'","''")') +`$Instance.pstypenames.add('$($namespace -replace $namespaceSeparatorPattern -replace "'","''")') +`$Instance +}" + } + + + # Redefine the function + $redefined = [ScriptBlock]::Create(" +function $NewFunctionName $objectDefinition +") + # Return the transpiled redefinition. + $redefined | .>Pipescript + } +} \ No newline at end of file diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-NamespacedObject.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-NamespacedObject.ps1 new file mode 100644 index 000000000..01c0d710c --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-NamespacedObject.ps1 @@ -0,0 +1,163 @@ +[ValidatePattern('PipeScript')] +param() + + +function Template.PipeScript.NamespacedObject { + + <# + .SYNOPSIS + Namespaced functions + .DESCRIPTION + Allows the declaration of a object or singleton in a namespace. + + Namespaces are used to logically group functionality and imply standardized behavior. + .EXAMPLE + Invoke-PipeScript { + My Object Precious { $IsARing = $true; $BindsThemAll = $true } + My.Precious + } + #> + [Reflection.AssemblyMetaData('Order', -10)] + [ValidateScript({ + # This only applies to a command AST + $cmdAst = $_ -as [Management.Automation.Language.CommandAst] + if (-not $cmdAst) { return $false } + # It must have at 4-5 elements. + if ($cmdAst.CommandElements.Count -lt 4 -or $cmdAst.CommandElements.Count -gt 5) { + return $false + } + + # The second element must be a bareword + if ($cmdAst.CommandElements[1].StringConstantType -ne 'Bareword') { + return $false + } + + # The second element must the name of the object type. + if ($cmdAst.CommandElements[1].Value -notin + 'object', 'instance','factory','an','a','f','o','i', + 'singleton','single','constant','const','the','c','s','t' + ) { + return $false + } + + # The last element must be a ScriptBlock or HashtableAst + if ( + $cmdAst.CommandElements[-1] -isnot [Management.Automation.Language.ScriptBlockExpressionAst] -and + $cmdAst.CommandElements[-1] -isnot [Management.Automation.Language.HashtableAst] + ) { + return $false + } + + # Attempt to resolve the command + if (-not $global:AllCommands) { + $global:AllCommands = $executionContext.SessionState.InvokeCommand.GetCommands('*','Alias,Function,Cmdlet', $true) + } + $potentialCmdName = "$($cmdAst.CommandElements[0])" + return -not ($global:AllCommands.Name -eq $potentialCmdName) + })] + param( + # The CommandAST that will be transformed. + [Parameter(Mandatory,ValueFromPipeline)] + [Management.Automation.Language.CommandAst] + $CommandAst + ) + + process { + # Namespaced functions are really simple: + + # We use multiple assignment to pick out the parts of the function + $namespace, $objectType, $functionName, $objectDefinition = $CommandAst.CommandElements + + # Then, we determine the last punctuation. + $namespaceSeparatorPattern = [Regex]::new('[\p{P}<>]{1,}','RightToLeft') + $namespaceSeparator = $namespaceSeparatorPattern.Match($namespace).Value + # If there was no punctuation, the namespace separator will be a '.' + if (-not $namespaceSeparator) {$namespaceSeparator = '.'} + # If the pattern was empty brackets `[]`, make the separator `[`. + elseif ($namespaceSeparator -eq '[]') { $namespaceSeparator = '[' } + # If the pattern was `<>`, make the separator `<`. + elseif ($namespaceSeparator -eq '<>') { $namespaceSeparator = '<' } + + # Replace any trailing separators from the namespace. + $namespace = $namespace -replace "$namespaceSeparatorPattern$" + + $blockComments = '' + + $SingletonForms = 'singleton','single','constant','const','the','c','s','t' + $singletonPattern = "(?>$($SingletonForms -join '|'))" + + + + $defineInstance = + if ($objectDefinition -is [Management.Automation.Language.HashtableAst]) { + "[PSCustomObject][Ordered]$($objectDefinition)" + } + elseif ($objectDefinition -is [Management.Automation.Language.ScriptBlockExpressionAst]) { + $findBlockComments = [Regex]::New(" + \<\# # The opening tag + (? + (?:.|\s)+?(?=\z|\#>) # anything until the closing tag + ) + \#\> # the closing tag + ", 'IgnoreCase,IgnorePatternWhitespace', '00:00:01') + $foundBlockComments = $objectDefinition -match $findBlockComments + $objectDefinition = "{ +$($objectDefinition -replace '^\{' -replace '\}$') +Export-ModuleMember -Function * -Alias * -Cmdlet * -Variable * +}" + + if ($foundBlockComments -and $matches.Block) { + $blockComments = $null,"<#",$($matches.Block),"#>",$null -join [Environment]::Newline + } + "New-Module -ArgumentList @(@(`$input) + @(`$args)) -AsCustomObject $objectDefinition" + } + + + # Join the parts back together to get the new function name. + $NewFunctionName = $namespace,$namespaceSeparator,$functionName,$( + # If the namespace separator ends with `[` or `<`, try to close it + if ($namespaceSeparator -match '[\[\<]$') { + if ($matches.0 -eq '[') { ']' } + elseif ($matches.0 -eq '<') { '>' } + } + ) -ne '' -join '' + + $objectDefinition = + if ($objectType -match $singletonPattern) { + "{$(if ($blockComments) {$blockComments}) +$( + @('$this = $myInvocation.MyCommand' + 'if (-not $this.Instance) {' + "`$singletonInstance = $defineInstance" + '$singletonInstance.pstypenames.clear()' + "`$singletonInstance.pstypenames.add('$($NewFunctionName -replace "'","''")')" + "`$singletonInstance.pstypenames.add('$($namespace -replace $namespaceSeparatorPattern -replace "'","''")')" + 'Add-Member -InputObject $this -MemberType NoteProperty -Name Instance -Value $singletonInstance -Force' + '}' + '$this.Instance' + ) -join [Environment]::newLine +) + +}" + } else { + "{ +$(if ($blockComments) {$blockComments}) +`$Instance = $defineInstance +`$Instance.pstypenames.clear() +`$Instance.pstypenames.add('$($NewFunctionName -replace "'","''")') +`$Instance.pstypenames.add('$($namespace -replace $namespaceSeparatorPattern -replace "'","''")') +`$Instance +}" + } + + + # Redefine the function + $redefined = [ScriptBlock]::Create(" +function $NewFunctionName $objectDefinition +") + # Return the transpiled redefinition. + $redefined | .>Pipescript + } + +} + diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-PipedAssignment.ps.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-PipedAssignment.ps.ps1 new file mode 100644 index 000000000..c334276ef --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-PipedAssignment.ps.ps1 @@ -0,0 +1,63 @@ +[ValidatePattern('PipeScript')] +param() + +Template function PipeScript.PipedAssignment { + <# + .SYNOPSIS + Piped Assignment Transpiler + .DESCRIPTION + Enables Piped Assignment (```|=|```). + + Piped Assignment allows for an expression to be reassigned based off of the pipeline input. + .EXAMPLE + { + $Collection |=| Where-Object Name -match $Pattern + } | .>PipeScript + + # This will become: + + $Collection = $Collection | Where-Object Name -match $pattern + .EXAMPLE + { + $Collection |=| Where-Object Name -match $pattern | Select-Object -ExpandProperty Name + } | .>PipeScript + + # This will become + + $Collection = $Collection | + Where-Object Name -match $pattern | + Select-Object -ExpandProperty Name + #> + [ValidateScript({ + $ast = $_ + if ($ast.PipelineElements.Count -ge 3 -and + $ast.PipelineElements[1].CommandElements -and + $ast.PipelineElements[1].CommandElements[0].Value -eq '=') { + return $true + } + return $false + })] + param( + [Parameter(Mandatory,ValueFromPipeline)] + [Management.Automation.Language.PipelineAst] + $PipelineAst + ) + + process { + $null = $PipelineAst.PipelineElements[0] + [ScriptBlock]::Create(@" + $($PipelineAst.PipelineElements[0]) = $($PipelineAst.PipelineElements[0]) | $( + $( + $(if ($PipelineAst.PipelineElements.Count -gt 3) { + [Environment]::NewLine + (' ' * 4) + } else { + '' + }) + + (@($PipelineAst.PipelineElements[2..$PipelineAst.PipelineElements.Count]) -join ( + ' |' + [Environment]::NewLine + (' ' * 4) + )) + ) + ) +"@) + } +} \ No newline at end of file diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-PipedAssignment.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-PipedAssignment.ps1 new file mode 100644 index 000000000..71fd0c901 --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-PipedAssignment.ps1 @@ -0,0 +1,67 @@ +[ValidatePattern('PipeScript')] +param() + + +function Template.PipeScript.PipedAssignment { + + <# + .SYNOPSIS + Piped Assignment Transpiler + .DESCRIPTION + Enables Piped Assignment (```|=|```). + + Piped Assignment allows for an expression to be reassigned based off of the pipeline input. + .EXAMPLE + { + $Collection |=| Where-Object Name -match $Pattern + } | .>PipeScript + + # This will become: + + $Collection = $Collection | Where-Object Name -match $pattern + .EXAMPLE + { + $Collection |=| Where-Object Name -match $pattern | Select-Object -ExpandProperty Name + } | .>PipeScript + + # This will become + + $Collection = $Collection | + Where-Object Name -match $pattern | + Select-Object -ExpandProperty Name + #> + [ValidateScript({ + $ast = $_ + if ($ast.PipelineElements.Count -ge 3 -and + $ast.PipelineElements[1].CommandElements -and + $ast.PipelineElements[1].CommandElements[0].Value -eq '=') { + return $true + } + return $false + })] + param( + [Parameter(Mandatory,ValueFromPipeline)] + [Management.Automation.Language.PipelineAst] + $PipelineAst + ) + + process { + $null = $PipelineAst.PipelineElements[0] + [ScriptBlock]::Create(@" + $($PipelineAst.PipelineElements[0]) = $($PipelineAst.PipelineElements[0]) | $( + $( + $(if ($PipelineAst.PipelineElements.Count -gt 3) { + [Environment]::NewLine + (' ' * 4) + } else { + '' + }) + + (@($PipelineAst.PipelineElements[2..$PipelineAst.PipelineElements.Count]) -join ( + ' |' + [Environment]::NewLine + (' ' * 4) + )) + ) + ) +"@) + } + +} + diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-SwitchAsIs.ps.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-SwitchAsIs.ps.ps1 new file mode 100644 index 000000000..db18a55d0 --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-SwitchAsIs.ps.ps1 @@ -0,0 +1,77 @@ +[ValidatePattern("PipeScript")] +param() + +Template function PipeScript.SwitchAsIs { + <# + .SYNOPSIS + Switches based off of type, using as or is + .DESCRIPTION + Slightly rewrites a switch statement that is written with full typenames. + + Normally, PowerShell would try to match the typename as a literal string, which is highly unlikely to work. + + SwitchAsIs will take a switch statement in the form of: + + ~~~PowerShell + switch ($t) { + [typeName] { + + } + } + ~~~ + + And rewrite it to use the casting operators + + If the label matches As or Is, it will use the corresponding operators. + .EXAMPLE + 1..10 | Invoke-PipeScript { + switch ($_) { + [int] { $_ } + [double] { $_ } + } + } + #> + [ValidateScript({ + $validating = $_ + if ($validating -isnot [Management.Automation.Language.SwitchStatementAst]) { return $false } + $hasTypeKeys = $validating.Clauses.Item1 -match '^\s{0,}\[' -and $validating.Clauses.Item1 -match '\]\s{0,}$' + + if ($hasTypeKeys -and $validating.Flags -notmatch 'Regex') { + return $true + } + if ($validating.Label -match '^[ai]s') { + return $true + } + return $false + + })] + param( + # The switch statement + [Parameter(ValueFromPipeline)] + [Management.Automation.Language.SwitchStatementAst] + $SwitchStatementAst + ) + + process { + [scriptblock]::Create( + @("switch ($($SwitchStatementAst.Condition)) {" + foreach ($clause in $SwitchStatementAst.Clauses) { + if ($clause.Item1 -match '^\s{0,}\[' -and $clause.Item1 -match '\]\s{0,}$') { + if ($SwitchStatementAst.Label -match 'as') { + "{`$_ -as $($clause.Item1)}" + } else { + "{`$_ -is $($clause.Item1)}" + } + $clause.Item2.ToString() + } else { + $clause.Item1 + $clause.Item2.ToString() + } + } + if ($switchStatementAst.Default) { + "default $($switchStatementAst.Default.ToString())" + } + "}") -join [Environment]::newLine + ) + } +} \ No newline at end of file diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-SwitchAsIs.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-SwitchAsIs.ps1 new file mode 100644 index 000000000..a404befbe --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-SwitchAsIs.ps1 @@ -0,0 +1,81 @@ +[ValidatePattern("PipeScript")] +param() + + +function Template.PipeScript.SwitchAsIs { + + <# + .SYNOPSIS + Switches based off of type, using as or is + .DESCRIPTION + Slightly rewrites a switch statement that is written with full typenames. + + Normally, PowerShell would try to match the typename as a literal string, which is highly unlikely to work. + + SwitchAsIs will take a switch statement in the form of: + + ~~~PowerShell + switch ($t) { + [typeName] { + + } + } + ~~~ + + And rewrite it to use the casting operators + + If the label matches As or Is, it will use the corresponding operators. + .EXAMPLE + 1..10 | Invoke-PipeScript { + switch ($_) { + [int] { $_ } + [double] { $_ } + } + } + #> + [ValidateScript({ + $validating = $_ + if ($validating -isnot [Management.Automation.Language.SwitchStatementAst]) { return $false } + $hasTypeKeys = $validating.Clauses.Item1 -match '^\s{0,}\[' -and $validating.Clauses.Item1 -match '\]\s{0,}$' + + if ($hasTypeKeys -and $validating.Flags -notmatch 'Regex') { + return $true + } + if ($validating.Label -match '^[ai]s') { + return $true + } + return $false + + })] + param( + # The switch statement + [Parameter(ValueFromPipeline)] + [Management.Automation.Language.SwitchStatementAst] + $SwitchStatementAst + ) + + process { + [scriptblock]::Create( + @("switch ($($SwitchStatementAst.Condition)) {" + foreach ($clause in $SwitchStatementAst.Clauses) { + if ($clause.Item1 -match '^\s{0,}\[' -and $clause.Item1 -match '\]\s{0,}$') { + if ($SwitchStatementAst.Label -match 'as') { + "{`$_ -as $($clause.Item1)}" + } else { + "{`$_ -is $($clause.Item1)}" + } + $clause.Item2.ToString() + } else { + $clause.Item1 + $clause.Item2.ToString() + } + } + if ($switchStatementAst.Default) { + "default $($switchStatementAst.Default.ToString())" + } + "}") -join [Environment]::newLine + ) + } + +} + diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-TripleEqualCompare.ps.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-TripleEqualCompare.ps.ps1 new file mode 100644 index 000000000..6c8cce237 --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-TripleEqualCompare.ps.ps1 @@ -0,0 +1,115 @@ +[ValidatePattern('PipeScript')] +param() + +Template function PipeScript.TripleEqualCompare { + <# + .SYNOPSIS + Allows equality type comparison. + .DESCRIPTION + Allows most equality comparison using triple equals (===). + + Many languages support this syntax. PowerShell does not. + + This transpiler enables equality and type comparison with ===. + .NOTES + This will not work if there is a constant on both sides of the expression + + + if ($one === $one) { "this will work"} + if ('' === '') { "this will not"} + + .EXAMPLE + Invoke-PipeScript -ScriptBlock { + $a = 1 + $number = 1 + if ($a === $number ) { + "A is $a" + } + } + .EXAMPLE + Invoke-PipeScript -ScriptBlock { + $One = 1 + $OneIsNotANumber = "1" + if ($one == $OneIsNotANumber) { + 'With ==, a number can be compared to a string, so $a == "1"' + } + if (-not ($One === $OneIsNotANumber)) { + "With ===, a number isn't the same type as a string, so this will be false." + } + } + .EXAMPLE + Invoke-PipeScript -ScriptBlock { + if ($null === $null) { + '$Null really is $null' + } + } + .EXAMPLE + Invoke-PipeScript -ScriptBlock { + $zero = 0 + if (-not ($zero === $null)) { + '$zero is not $null' + } + } + .EXAMPLE + { + $a = "b" + $a === "b" + } | .>PipeScript + #> + [ValidateScript({ + # This is valid if the assignment statement's + $AssignmentStatementAST = $_ + # The operator must be an equals + $AssignmentStatementAST.Operator -eq 'Equals' -and + # The right must start with = + $AssignmentStatementAST.Right -match '^==' -and + # There must not be space between it and the left + $AssignmentStatementAST.Parent.ToString() -match "$( + [Regex]::Escape($AssignmentStatementAST.Left.ToString()) + )\s{0,}===[^=]" + })] + param( + # The original assignment statement. + [Parameter(Mandatory,ValueFromPipeline)] + [Management.Automation.Language.AssignmentStatementAst] + $AssignmentStatementAST + ) + + process { + if ($AssignmentStatementAST.Left -isnot [Management.Automation.Language.VariableExpressionAST]) { + Write-Error "Left side of EqualityTypeComparison must be a variable" + return + } + $leftSide = $AssignmentStatementAST.Left + + foreach ($side in 'Left', 'Right') { + $ExecutionContext.SessionState.PSVariable.Set("${side}Side", $AssignmentStatementAST.$side) + } + + $rightSide = $rightSide -replace '^==' + + # We create a new script by: + $newScript = + # Checking for the existence of GetType on both objects (and the same result from each) + "( + (-not $leftSide.GetType -and -not $rightSide.GetType) -or + ( + ( + ($leftSide.GetType -and $rightSide.GetType) -and + ($leftSide.GetType() -eq $rightSide.GetType() + ) -and (" + + # then, in a new subexpression + [Environment]::Newline + + # taking the left side as-is. + $AssignmentStatementAST.Left.Extent.ToString() + + # replacing the = with ' -eq ' + ' -eq ' + + # And replacing any the = and any trailing whitespace. + ($AssignmentStatementAST.Right.Extent.ToString() -replace '^==\s{1,}') + ") + ) + ) +)" + + [scriptblock]::Create($newScript) + } +} \ No newline at end of file diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-TripleEqualCompare.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-TripleEqualCompare.ps1 new file mode 100644 index 000000000..b63856e87 --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-TripleEqualCompare.ps1 @@ -0,0 +1,119 @@ +[ValidatePattern('PipeScript')] +param() + + +function Template.PipeScript.TripleEqualCompare { + + <# + .SYNOPSIS + Allows equality type comparison. + .DESCRIPTION + Allows most equality comparison using triple equals (===). + + Many languages support this syntax. PowerShell does not. + + This transpiler enables equality and type comparison with ===. + .NOTES + This will not work if there is a constant on both sides of the expression + + + if ($one === $one) { "this will work"} + if ('' === '') { "this will not"} + + .EXAMPLE + Invoke-PipeScript -ScriptBlock { + $a = 1 + $number = 1 + if ($a === $number ) { + "A is $a" + } + } + .EXAMPLE + Invoke-PipeScript -ScriptBlock { + $One = 1 + $OneIsNotANumber = "1" + if ($one == $OneIsNotANumber) { + 'With ==, a number can be compared to a string, so $a == "1"' + } + if (-not ($One === $OneIsNotANumber)) { + "With ===, a number isn't the same type as a string, so this will be false." + } + } + .EXAMPLE + Invoke-PipeScript -ScriptBlock { + if ($null === $null) { + '$Null really is $null' + } + } + .EXAMPLE + Invoke-PipeScript -ScriptBlock { + $zero = 0 + if (-not ($zero === $null)) { + '$zero is not $null' + } + } + .EXAMPLE + { + $a = "b" + $a === "b" + } | .>PipeScript + #> + [ValidateScript({ + # This is valid if the assignment statement's + $AssignmentStatementAST = $_ + # The operator must be an equals + $AssignmentStatementAST.Operator -eq 'Equals' -and + # The right must start with = + $AssignmentStatementAST.Right -match '^==' -and + # There must not be space between it and the left + $AssignmentStatementAST.Parent.ToString() -match "$( + [Regex]::Escape($AssignmentStatementAST.Left.ToString()) + )\s{0,}===[^=]" + })] + param( + # The original assignment statement. + [Parameter(Mandatory,ValueFromPipeline)] + [Management.Automation.Language.AssignmentStatementAst] + $AssignmentStatementAST + ) + + process { + if ($AssignmentStatementAST.Left -isnot [Management.Automation.Language.VariableExpressionAST]) { + Write-Error "Left side of EqualityTypeComparison must be a variable" + return + } + $leftSide = $AssignmentStatementAST.Left + + foreach ($side in 'Left', 'Right') { + $ExecutionContext.SessionState.PSVariable.Set("${side}Side", $AssignmentStatementAST.$side) + } + + $rightSide = $rightSide -replace '^==' + + # We create a new script by: + $newScript = + # Checking for the existence of GetType on both objects (and the same result from each) + "( + (-not $leftSide.GetType -and -not $rightSide.GetType) -or + ( + ( + ($leftSide.GetType -and $rightSide.GetType) -and + ($leftSide.GetType() -eq $rightSide.GetType() + ) -and (" + + # then, in a new subexpression + [Environment]::Newline + + # taking the left side as-is. + $AssignmentStatementAST.Left.Extent.ToString() + + # replacing the = with ' -eq ' + ' -eq ' + + # And replacing any the = and any trailing whitespace. + ($AssignmentStatementAST.Right.Extent.ToString() -replace '^==\s{1,}') + ") + ) + ) +)" + + [scriptblock]::Create($newScript) + } + +} + diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-WhereMethod.ps.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-WhereMethod.ps.ps1 new file mode 100644 index 000000000..77bf730df --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-WhereMethod.ps.ps1 @@ -0,0 +1,49 @@ +[ValidatePattern('PipeScript')] +param() + +Template function PipeScript.WhereMethod { + <# + .SYNOPSIS + Where Method + .DESCRIPTION + Where-Object cannot simply run a method with parameters on each object. + + However, we can easily rewrite a Where-Object statement to do exactly that. + .EXAMPLE + { Get-PipeScript | ? CouldPipeType([ScriptBlock]) } | Use-PipeScript + #> + [ValidateScript({ + $validating = $_ + if ($validating -isnot [Management.Automation.Language.CommandAst]) { + return $false + } + if ($validating.CommandElements[0] -notin '?', 'Where', 'Where-Object') { + return $false + } + if ($validating.CommandElements.Count -ne 3) { + return $false + } + if ($validating.CommandElements[2] -is [Management.Automation.Language.ParenExpressionAst]) { + return $true + } + return $false + + })] + param( + # The Where-Object Command AST. + [Parameter(Mandatory,ValueFromPipeline)] + [Management.Automation.Language.CommandAst] + $WhereCommandAst + ) + + process { + # Use multiple assignment to split the command up + $whereCommand, $whereMethodName, $whereMethod = $whereCommandAst.CommandElements + # Fix psuedo-empty parenthesis to be properly empty. + $whereMethod = $whereMethod -replace '\(`$(?>_|null)\)', '()' + # Create the updated code (don't use Where-Object though, because this will be faster). + $UpdatedWhereCommand = "& { process { if (`$_.${WhereMethodName}${WhereMethod}) { `$_ } } }" + # Output the updated script block (and we're done). + [scriptblock]::Create($UpdatedWhereCommand) + } +} \ No newline at end of file diff --git a/Languages/PipeScript/Templates/Syntax/PipeScript-Template-WhereMethod.ps1 b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-WhereMethod.ps1 new file mode 100644 index 000000000..61ba43c5b --- /dev/null +++ b/Languages/PipeScript/Templates/Syntax/PipeScript-Template-WhereMethod.ps1 @@ -0,0 +1,53 @@ +[ValidatePattern('PipeScript')] +param() + + +function Template.PipeScript.WhereMethod { + + <# + .SYNOPSIS + Where Method + .DESCRIPTION + Where-Object cannot simply run a method with parameters on each object. + + However, we can easily rewrite a Where-Object statement to do exactly that. + .EXAMPLE + { Get-PipeScript | ? CouldPipeType([ScriptBlock]) } | Use-PipeScript + #> + [ValidateScript({ + $validating = $_ + if ($validating -isnot [Management.Automation.Language.CommandAst]) { + return $false + } + if ($validating.CommandElements[0] -notin '?', 'Where', 'Where-Object') { + return $false + } + if ($validating.CommandElements.Count -ne 3) { + return $false + } + if ($validating.CommandElements[2] -is [Management.Automation.Language.ParenExpressionAst]) { + return $true + } + return $false + + })] + param( + # The Where-Object Command AST. + [Parameter(Mandatory,ValueFromPipeline)] + [Management.Automation.Language.CommandAst] + $WhereCommandAst + ) + + process { + # Use multiple assignment to split the command up + $whereCommand, $whereMethodName, $whereMethod = $whereCommandAst.CommandElements + # Fix psuedo-empty parenthesis to be properly empty. + $whereMethod = $whereMethod -replace '\(`$(?>_|null)\)', '()' + # Create the updated code (don't use Where-Object though, because this will be faster). + $UpdatedWhereCommand = "& { process { if (`$_.${WhereMethodName}${WhereMethod}) { `$_ } } }" + # Output the updated script block (and we're done). + [scriptblock]::Create($UpdatedWhereCommand) + } + +} + diff --git a/Languages/PowerShell/PowerShell-Language.ps.ps1 b/Languages/PowerShell/PowerShell-Language.ps.ps1 index e15ea674d..6d8c242fb 100644 --- a/Languages/PowerShell/PowerShell-Language.ps.ps1 +++ b/Languages/PowerShell/PowerShell-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>PowerShell|Language)")] +param() + Language function PowerShell { <# .SYNOPSIS diff --git a/Languages/PowerShell/PowerShell-Language.ps1 b/Languages/PowerShell/PowerShell-Language.ps1 index 759d1e271..f949ff88a 100644 --- a/Languages/PowerShell/PowerShell-Language.ps1 +++ b/Languages/PowerShell/PowerShell-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>PowerShell|Language)")] +param() + function Language.PowerShell { <# diff --git a/Languages/PowerShell/PowerShellData-Language.ps.ps1 b/Languages/PowerShell/PowerShellData-Language.ps.ps1 index ba0713630..3206df4ef 100644 --- a/Languages/PowerShell/PowerShellData-Language.ps.ps1 +++ b/Languages/PowerShell/PowerShellData-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>PowerShell|Language)")] +param() + Language function PowerShellData { <# .SYNOPSIS diff --git a/Languages/PowerShell/PowerShellData-Language.ps1 b/Languages/PowerShell/PowerShellData-Language.ps1 index ffb915938..37204adec 100644 --- a/Languages/PowerShell/PowerShellData-Language.ps1 +++ b/Languages/PowerShell/PowerShellData-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>PowerShell|Language)")] +param() + function Language.PowerShellData { <# diff --git a/Languages/PowerShell/PowerShellXML-Language.ps.ps1 b/Languages/PowerShell/PowerShellXML-Language.ps.ps1 index 47130ed11..3baadf683 100644 --- a/Languages/PowerShell/PowerShellXML-Language.ps.ps1 +++ b/Languages/PowerShell/PowerShellXML-Language.ps.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>PowerShell|Language)")] +param() + Language function PowerShellXML { <# .SYNOPSIS diff --git a/Languages/PowerShell/PowerShellXML-Language.ps1 b/Languages/PowerShell/PowerShellXML-Language.ps1 index 2671ce9fb..68ae6f405 100644 --- a/Languages/PowerShell/PowerShellXML-Language.ps1 +++ b/Languages/PowerShell/PowerShellXML-Language.ps1 @@ -1,3 +1,6 @@ +[ValidatePattern("(?>PowerShell|Language)")] +param() + function Language.PowerShellXML { <# diff --git a/Languages/PowerShell/Templates/PowerShell-Template-RemoveParameter.ps.ps1 b/Languages/PowerShell/Templates/PowerShell-Template-RemoveParameter.ps.ps1 new file mode 100644 index 000000000..85e20dc28 --- /dev/null +++ b/Languages/PowerShell/Templates/PowerShell-Template-RemoveParameter.ps.ps1 @@ -0,0 +1,34 @@ +[ValidatePattern('PowerShell')] +param() + +Template function PowerShell.RemoveParameter { + <# + .SYNOPSIS + Removes Parameters from a ScriptBlock + .DESCRIPTION + Removes Parameters from a ScriptBlock + .EXAMPLE + { + [RemoveParameter("x")] + param($x, $y) + } | .>PipeScript + .LINK + Update-PipeScript + #> + param( + # The name of one or more parameters to remove + [Parameter(Mandatory,Position=0)] + [string[]] + $ParameterName, + + # The ScriptBlock that declares the parameters. + [Parameter(Mandatory,ValueFromPipeline)] + [scriptblock] + $ScriptBlock + ) + + process { + Update-PipeScript -ScriptBlock $ScriptBlock -RemoveParameter $ParameterName + } + +} \ No newline at end of file diff --git a/Languages/PowerShell/Templates/PowerShell-Template-RemoveParameter.ps1 b/Languages/PowerShell/Templates/PowerShell-Template-RemoveParameter.ps1 new file mode 100644 index 000000000..3cc77c0e3 --- /dev/null +++ b/Languages/PowerShell/Templates/PowerShell-Template-RemoveParameter.ps1 @@ -0,0 +1,38 @@ +[ValidatePattern('PowerShell')] +param() + + +function Template.PowerShell.RemoveParameter { + + <# + .SYNOPSIS + Removes Parameters from a ScriptBlock + .DESCRIPTION + Removes Parameters from a ScriptBlock + .EXAMPLE + { + [RemoveParameter("x")] + param($x, $y) + } | .>PipeScript + .LINK + Update-PipeScript + #> + param( + # The name of one or more parameters to remove + [Parameter(Mandatory,Position=0)] + [string[]] + $ParameterName, + + # The ScriptBlock that declares the parameters. + [Parameter(Mandatory,ValueFromPipeline)] + [scriptblock] + $ScriptBlock + ) + + process { + Update-PipeScript -ScriptBlock $ScriptBlock -RemoveParameter $ParameterName + } + + +} + diff --git a/Languages/PowerShell/Templates/PowerShell-Template-RenameVariable.ps.ps1 b/Languages/PowerShell/Templates/PowerShell-Template-RenameVariable.ps.ps1 new file mode 100644 index 000000000..e2147f733 --- /dev/null +++ b/Languages/PowerShell/Templates/PowerShell-Template-RenameVariable.ps.ps1 @@ -0,0 +1,36 @@ +Template function PowerShell.RenameVariable { + <# + .SYNOPSIS + Renames variables + .DESCRIPTION + Renames variables in a ScriptBlock + .EXAMPLE + { + [RenameVariable(VariableRename={ + @{ + x='x1' + y='y1' + } + })] + param($x, $y) + } | .>PipeScript + .LINK + Update-PipeScript + #> + param( + # The name of one or more parameters to remove + [Parameter(Mandatory,Position=0)] + [Alias('Variables','RenameVariables', 'RenameVariable','VariableRenames')] + [Collections.IDictionary] + $VariableRename, + + # The ScriptBlock that declares the parameters. + [Parameter(Mandatory,ValueFromPipeline)] + [scriptblock] + $ScriptBlock + ) + + process { + Update-PipeScript -ScriptBlock $ScriptBlock -RenameVariable $VariableRename + } +} \ No newline at end of file diff --git a/Languages/PowerShell/Templates/PowerShell-Template-RenameVariable.ps1 b/Languages/PowerShell/Templates/PowerShell-Template-RenameVariable.ps1 new file mode 100644 index 000000000..1d9bf325d --- /dev/null +++ b/Languages/PowerShell/Templates/PowerShell-Template-RenameVariable.ps1 @@ -0,0 +1,40 @@ + +function Template.PowerShell.RenameVariable { + + <# + .SYNOPSIS + Renames variables + .DESCRIPTION + Renames variables in a ScriptBlock + .EXAMPLE + { + [RenameVariable(VariableRename={ + @{ + x='x1' + y='y1' + } + })] + param($x, $y) + } | .>PipeScript + .LINK + Update-PipeScript + #> + param( + # The name of one or more parameters to remove + [Parameter(Mandatory,Position=0)] + [Alias('Variables','RenameVariables', 'RenameVariable','VariableRenames')] + [Collections.IDictionary] + $VariableRename, + + # The ScriptBlock that declares the parameters. + [Parameter(Mandatory,ValueFromPipeline)] + [scriptblock] + $ScriptBlock + ) + + process { + Update-PipeScript -ScriptBlock $ScriptBlock -RenameVariable $VariableRename + } + +} + diff --git a/Languages/PowerShell/Templates/Syntax/PowerShell-Template-Attribute.ps.ps1 b/Languages/PowerShell/Templates/Syntax/PowerShell-Template-Attribute.ps.ps1 new file mode 100644 index 000000000..2b7f3c53d --- /dev/null +++ b/Languages/PowerShell/Templates/Syntax/PowerShell-Template-Attribute.ps.ps1 @@ -0,0 +1,128 @@ +[ValidatePattern("(?>PowerShell|PipeScript)")] +param() + +Template function PowerShell.Attribute { + <# + .SYNOPSIS + Template for a PowerShell Attribute + .DESCRIPTION + Writes a value as a PowerShell attribute. + .NOTES + This does not check that the type actually exists yet, because it may not. + .EXAMPLE + [ValidateSet('a','b','c')] | Template.PowerShell.Attribute + .EXAMPLE + Template.PowerShell.Attribute -Type "MyCustomAttribute" -Argument 1,2,3 -Parameter @{Name='Value';Value='Data'} + #> + [Alias('Template.PipeScript.Attribute')] + param( + # The type of the attribute. This does not have to exist (yet). + [vbn()] + [Alias('TypeID','AttributeType')] + [PSObject] + $Type, + + + # Any arguments to the attribute. These will be passed as constructors. + [vbn(ValueFromRemainingArguments)] + [Alias('Args','Argument','Arguments','Constructor','Constructors', + 'AttributeArgs','AttributeArguments','AttributeConstructor','AttributeConstructors')] + [psobject[]] + $ArgumentList, + + # Any parameters to the attribute. These will be passed as properties. + [vbn()] + [Alias('AttributeData','AttributeParameter','AttributeParameters','Parameters','Data')] + [PSObject[]] + $Parameter + ) + + + begin { + filter ToAttributeValue { + if ($_ -is [bool]) { + "`$$($_)" + } + elseif ($_ -is [int] -or $_ -is [double]) { + "$_" + } + elseif ($_ -is [scriptblock]) { + "{$($_)}" + } + elseif ($_ -is [type] -or + $_ -as [type[]]) { + @(foreach ($t in ($_ -as [type[]])) { + if ($accelerators::get.ContainsValue($t)) { + foreach ($acc in $accelerators::get.GetEnumerator()) { + if ($acc.Value -eq $t) { + "[$($acc.Key)]" + } + } + } else { + "[$($t.FullName -replace '^System\.' -replace 'Attribute$')]" + } + }) -join ',' + } + else { + "'$($_ -replace "'","''" -join "','")'" + } + } + } + process { + if (-not $PSBoundParameters["Parameter"] -and $_ -is [Attribute]) { + $parameter = $_ + } + + $visibleProperties = + @( + foreach ($param in $parameter) { + if ($param -is [Collections.IDictionary]) { + $param = [PSCustomObject]$param + } + foreach ($property in $param.psobject.properties) { + if ($null -ne $property.Value) { + if ($property.Name -eq 'TypeID') { continue } + $property + } + } + } + ) + + $accelerators = [psobject].assembly.gettype("System.Management.Automation.TypeAccelerators") + + $typeName = + if ( + ($type -as [Type]) -and + ($accelerators::get.ContainsValue(($type -as [Type]))) + ) { + foreach ($acc in $accelerators::get.GetEnumerator()) { + if ($acc.Value -eq ($type -as [Type])) { + $acc.Key + } + } + } elseif ($type.FullName) { + $Type.FullName -replace '^System\.' -replace 'Attribute$' + } else { + $Type -replace "[\p{P}-[\.]]", '_' + } + + @( + '[' + $typeName + '(' + if ($ArgumentList) { + @($ArgumentList | ToAttributeValue) -join ', ' + ',' + } + @(foreach ($visibleProperty in $visibleProperties) { + $visibleValue = @($visibleProperty.Value | ToAttributeValue) -join ',' + $visibleProperty.Name + + ' = ' + + $visibleValue + }) -join ', ' + ')' + ']' + ) -join '' + } +} + diff --git a/Languages/PowerShell/Templates/Syntax/PowerShell-Template-Attribute.ps1 b/Languages/PowerShell/Templates/Syntax/PowerShell-Template-Attribute.ps1 new file mode 100644 index 000000000..495740311 --- /dev/null +++ b/Languages/PowerShell/Templates/Syntax/PowerShell-Template-Attribute.ps1 @@ -0,0 +1,135 @@ +[ValidatePattern("(?>PowerShell|PipeScript)")] +param() + + +function Template.PowerShell.Attribute { + + <# + .SYNOPSIS + Template for a PowerShell Attribute + .DESCRIPTION + Writes a value as a PowerShell attribute. + .NOTES + This does not check that the type actually exists yet, because it may not. + .EXAMPLE + [ValidateSet('a','b','c')] | Template.PowerShell.Attribute + .EXAMPLE + Template.PowerShell.Attribute -Type "MyCustomAttribute" -Argument 1,2,3 -Parameter @{Name='Value';Value='Data'} + #> + [Alias('Template.PipeScript.Attribute')] + param( + # The type of the attribute. This does not have to exist (yet). + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('TypeID','AttributeType')] + [PSObject] + $Type, + + + # Any arguments to the attribute. These will be passed as constructors. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Args','Argument','Arguments','Constructor','Constructors', + 'AttributeArgs','AttributeArguments','AttributeConstructor','AttributeConstructors')] + [psobject[]] + $ArgumentList, + + # Any parameters to the attribute. These will be passed as properties. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('AttributeData','AttributeParameter','AttributeParameters','Parameters','Data')] + [PSObject[]] + $Parameter + ) + + + begin { + filter ToAttributeValue { + + if ($_ -is [bool]) { + "`$$($_)" + } + elseif ($_ -is [int] -or $_ -is [double]) { + "$_" + } + elseif ($_ -is [scriptblock]) { + "{$($_)}" + } + elseif ($_ -is [type] -or + $_ -as [type[]]) { + @(foreach ($t in ($_ -as [type[]])) { + if ($accelerators::get.ContainsValue($t)) { + foreach ($acc in $accelerators::get.GetEnumerator()) { + if ($acc.Value -eq $t) { + "[$($acc.Key)]" + } + } + } else { + "[$($t.FullName -replace '^System\.' -replace 'Attribute$')]" + } + }) -join ',' + } + else { + "'$($_ -replace "'","''" -join "','")'" + } + + } + } + process { + if (-not $PSBoundParameters["Parameter"] -and $_ -is [Attribute]) { + $parameter = $_ + } + + $visibleProperties = + @( + foreach ($param in $parameter) { + if ($param -is [Collections.IDictionary]) { + $param = [PSCustomObject]$param + } + foreach ($property in $param.psobject.properties) { + if ($null -ne $property.Value) { + if ($property.Name -eq 'TypeID') { continue } + $property + } + } + } + ) + + $accelerators = [psobject].assembly.gettype("System.Management.Automation.TypeAccelerators") + + $typeName = + if ( + ($type -as [Type]) -and + ($accelerators::get.ContainsValue(($type -as [Type]))) + ) { + foreach ($acc in $accelerators::get.GetEnumerator()) { + if ($acc.Value -eq ($type -as [Type])) { + $acc.Key + } + } + } elseif ($type.FullName) { + $Type.FullName -replace '^System\.' -replace 'Attribute$' + } else { + $Type -replace "[\p{P}-[\.]]", '_' + } + + @( + '[' + $typeName + '(' + if ($ArgumentList) { + @($ArgumentList | ToAttributeValue) -join ', ' + ',' + } + @(foreach ($visibleProperty in $visibleProperties) { + $visibleValue = @($visibleProperty.Value | ToAttributeValue) -join ',' + $visibleProperty.Name + + ' = ' + + $visibleValue + }) -join ', ' + ')' + ']' + ) -join '' + } + +} + + + diff --git a/Transpilers/Help.psx.ps1 b/Languages/PowerShell/Templates/Syntax/PowerShell-Template-Help.ps.ps1 similarity index 98% rename from Transpilers/Help.psx.ps1 rename to Languages/PowerShell/Templates/Syntax/PowerShell-Template-Help.ps.ps1 index 7959cd53a..7503e5283 100644 --- a/Transpilers/Help.psx.ps1 +++ b/Languages/PowerShell/Templates/Syntax/PowerShell-Template-Help.ps.ps1 @@ -1,3 +1,4 @@ +Template function PowerShell.Help { <# .SYNOPSIS Help Transpiler @@ -87,4 +88,6 @@ process { $helpScriptBlock, $scriptBlock | Join-PipeScript } +} + } \ No newline at end of file diff --git a/Languages/PowerShell/Templates/Syntax/PowerShell-Template-Help.ps1 b/Languages/PowerShell/Templates/Syntax/PowerShell-Template-Help.ps1 new file mode 100644 index 000000000..43a91c3d9 --- /dev/null +++ b/Languages/PowerShell/Templates/Syntax/PowerShell-Template-Help.ps1 @@ -0,0 +1,97 @@ + +function Template.PowerShell.Help { + +<# +.SYNOPSIS + Help Transpiler +.DESCRIPTION + The Help Transpiler allows you to write inline help without directly writing comments. +.EXAMPLE + { + [Help(Synopsis="The Synopsis", Description="A Description")] + param() + + + "This Script Has Help, Without Directly Writing Comments" + + } | .>PipeScript +.Example + { + param( + [Help(Synopsis="X Value")] + $x + ) + } | .>PipeScript +.EXAMPLE + { + param( + [Help("X Value")] + $x + ) + } | .>PipeScript +#> +[CmdletBinding(DefaultParameterSetName='Parameter')] +param( +# The synopsis of the help topic +[Parameter(Mandatory,Position=0)] +[string] +$Synopsis, + +# The description of the help topic +[string] +$Description, + +# One or more examples +[string[]] +$Example, + +# One or more links +[string[]] +$Link, + +# A ScriptBlock. If this is provided, the help will be added to this scriptblock. +[Parameter(ValueFromPipeline,ParameterSetName='ScriptBlock')] +[scriptblock] +$ScriptBlock +) + +process { + if ($PSCmdlet.ParameterSetName -eq 'Parameter') { + [ScriptBlock]::Create('' + $( + if ($Synopsis -match '[\r\n]') { + if ($Synopsis -notmatch '#\>') { + "<#" + [Environment]::newLine + $Synopsis + [Environment]::newLine + "#>" + } else { + $Synopsis -split "[\r\n]{1,}" -replace '^', '# ' + } + } else { + "# $Synopsis" + } + ) + 'param()') + } elseif ($psCmdlet.ParameterSetName -eq 'ScriptBlock') { + $helpScriptBlock = [ScriptBlock]::Create('<#' + [Environment]::NewLine + $(@( + '.Synopsis' + $Synopsis -split "[\r\n]{1,}" -replace '^', ' ' + if ($Description) { + '.Description' + $Description -split "[\r\n]{1,}" -replace '^', ' ' + } + foreach ($ex in $Example) { + '.Example' + $ex -split "[\r\n]{1,}" -replace '^', ' ' + } + foreach ($lnk in $Link) { + '.Link' + $lnk -split "[\r\n]{1,}" -replace '^', ' ' + } + ) -join [Environment]::newLine) + [Environment]::newLine + "#> + param() + ") + + $helpScriptBlock, $scriptBlock | Join-PipeScript + } +} + + +} + diff --git a/Languages/PowerShell/Templates/Syntax/PowerShell-Template-Parameter.ps.ps1 b/Languages/PowerShell/Templates/Syntax/PowerShell-Template-Parameter.ps.ps1 new file mode 100644 index 000000000..2e326e694 --- /dev/null +++ b/Languages/PowerShell/Templates/Syntax/PowerShell-Template-Parameter.ps.ps1 @@ -0,0 +1,366 @@ +[ValidatePattern("(?>PowerShell|PipeScript)")] +param() + +Template function PowerShell.Parameter { + <# + .SYNOPSIS + PowerShell Parameter Template + .DESCRIPTION + Generates a parameter declaration for a PowerShell function. + .EXAMPLE + Template.PowerShell.Parameter -Name "MyParameter" -Type "string" -DefaultValue "MyDefaultValue" -Description "My Description" + #> + [Alias('Template.PipeScript.Parameter')] + param( + # The name of the parameter. + [vbn()] + [Alias('ParameterName')] + [psobject] + $Name, + + # Any parameter attributes. + # These will appear first. + [vbn()] + [Alias('ParameterAttributes')] + [psobject[]] + $ParameterAttribute, + + # Any parameter aliases + # These will appear beneath parameter attributes. + [vbn()] + [Alias('Aliases','AliasName','AliasNames')] + [psobject[]] + $Alias, + + # Any other attributes. These will appear directly above the type and name. + [vbn()] + [Alias('Attributes')] + [psobject[]] + $Attribute, + + # One or more parameter types. + [vbn()] + [Alias('ParameterType','ParameterTypes')] + [psobject[]] + $Type, + + # One or more default values (if more than one default value is provided, it will be assumed to be an array) + [vbn()] + [Alias('Default','ParameterDefault','ParameterDefaultValue')] + [psobject] + $DefaultValue, + + # A valid set of values + [vbn()] + [Alias('ValidValue','ValidValues')] + [psobject[]] + $ValidateSet, + + # The Parameter description (any help for the parameter) + [vbn()] + [Alias('Help','ParameterHelp','Synopsis','Summary')] + [psobject[]] + $Description, + + # Any simple bindings for the parameter. + # These are often used to describe the name of a parameter in an underlying system. + # For example, a parameter might be named "Name" in PowerShell, but "itemName" in JSON. + [vbn()] + [Alias('Bindings','DefaultBinding','DefaultBindingProperty')] + [string[]] + $Binding, + + # The ambient value + # This script block can be used to coerce a value into the desired real value. + [vbn()] + [Alias('CoerceValue')] + [scriptblock] + $AmbientValue, + + # Any additional parameter metadata. + # Any dictionaries passed to this parameter will be converted to [Reflection.AssemblyMetadata] attributes. + [vbn()] + [Alias('ReflectionMetadata','ParameterMetadata')] + [psobject[]] + $Metadata, + + # The validation pattern for the parameter. + [vbn()] + [Alias('Pattern','Regex')] + [psobject[]] + $ValidatePattern, + + # If set, will make the parameter more weakly typed. + [vbn()] + [switch] + $WeaklyTyped, + + # If set, will attempt to avoid creating mandatory parameters. + [vbn()] + [switch] + $NoMandatory + ) + + begin { + + $generatedParameters = [Collections.Queue]::new() + filter embedParameterHelp { + if ($_ -notmatch '^\s\<\#' -and $_ -notmatch '^\s\#') { + $commentLines = @($_ -split '(?>\r\n|\n)') + if ($commentLines.Count -gt 1) { + '<#' + [Environment]::NewLine + "$_".Trim() + [Environment]::newLine + '#>' + } else { + "# $_" + } + } else { + $_ + } + } + + filter PseudoTypeToRealType { + switch ($_) { + string { [string] } + integer { [int] } + number { [double]} + boolean { [switch] } + array { [PSObject[]] } + object { [PSObject] } + default { + if ($_ -as [type]) { + $_ -as [type] + } + } + } + } + } + + process { + # Presort the attributes + $attribute = @(foreach ($attr in $Attribute) { + switch ($attr) { + [ValidateSet] { + $ValidateSet += $attr.ValidValues + continue + } + [Management.Automation.ParameterAttribute] { + $ParameterAttribute += $attr + continue + } + [Management.Automation.AliasAttribute] { + $Alias += $attr + continue + } + default { + $attr + } + } + }) + + $parameterAttribute = foreach ($paramAttr in $ParameterAttribute) { + switch ($paramAttr) { + [Management.Automation.AliasAttribute] { + $Alias += $paramAttr + } + [Management.Automation.ParameterAttribute] { + $paramAttr + } + [Attribute] { + $Attribute += $paramAttr + } + default { + $paramAttr + } + } + } + + + $parameterName = $name + + $attrs = @($ParameterAttribute;$attribute) + + $parameterAliases = @( + foreach ($aka in $alias) { + if ($aka -is [Management.Automation.AliasAttribute]) { + $aka.AliasNames + } elseif ($aka -is [string]) { + $aka + } + } + ) + + $parameterAttributeParts = @() + $ParameterOtherAttributes = @() + # Aliases can be in .Alias/.Aliases + + [string[]]$aliases = $parameterAliases + + # Help can be in .Help/.Description/.Synopsis/.Summary + [string]$parameterHelpText = $Description -join ([Environment]::NewLine) + + # Metadata can be in .Metadata/.ReflectionMetadata/.ParameterMetadata + $parameterMetadataProperties = $Metadata + + # Valid Values can be found in .ValidValue/.ValidValues/.ValidateSet + $parameterValidValues = $ValidateSet + + $parameterValidPattern = $ValidatePattern + + # Default values can be found in .DefaultValue/.Default + $parameterDefaultValue = $DefaultValue + + $aliasAttribute = @(foreach ($aka in $aliases) { + $aka -replace "'","''" + }) -join "','" + if ($aliasAttribute) { + $aliasAttribute = "[Alias('$aliasAttribute')]" + } + + if ($parameterValidValues) { + $attrs += "[ValidateSet('$($parameterValidValues -replace "'","''" -join "','")')]" + } + + if ($parameterValidPattern) { + $attrs += "[ValidatePattern('$($parameterValidPattern -replace "'","''")')]" + } + + if ($Binding) { + foreach ($bindingProperty in $Binding) { + $attrs += "[ComponentModel.DefaultBindingProperty('$bindingProperty')]" + } + } + + if ($parameterMetadataProperties) { + foreach ($pmdProp in $parameterMetadataProperties) { + if ($pmdProp -is [Collections.IDictionary]) { + foreach ($mdKv in $pmdProp.GetEnumerator()) { + $attrs += "[Reflection.AssemblyMetadata('$($mdKv.Key)', '$($mdKv.Value -replace "',''")')]" + } + } + } + } + + if ($AmbientValue) { + foreach ($ambient in $AmbientValue) { + $attrs += "[ComponentModel.AmbientValue({$ambient})]" + } + } + + $alreadyIncludedAttributes = @() + foreach ($attr in $attrs) { + if ($attr -is [Attribute]) { + $attrType = $attr.GetType() + if ($alreadyIncludedAttributes -contains $attr) { continue } + if ($attr -is [Parameter]) { + $ParameterOtherAttributes += "[Parameter($(@( + if ($attr.Mandatory) { 'Mandatory' } + if ($attr.Postition -ge 0) { "Position=$($attr.Position)"} + if ($attr.ParameterSetName -and $attr.ParameterSetName -ne '__AllParameterSets') { + "ParameterSetName='$($attr.ParameterSetName -replace "'","''")'" + } + if ($attr.ValueFromPipeline) { 'ValueFromPipeline' } + if ($attr.ValueFromPipelineByPropertyName) { 'ValueFromPipelineByPropertyName' } + if ($attr.ValueFromRemainingArguments) { 'ValueFromRemainingArguments' } + if ($attr.DontShow) { 'DontShow' } + ) -join ','))]" + } else { + $attr | Template.PowerShell.Attribute + } + $alreadyIncludedAttributes += $attr + continue + } + if ($attr -notmatch '^\[') { + $parameterAttributeParts += $attr + } else { + $ParameterOtherAttributes += $attr + } + } + + if ( + ($parameterMetadata.Mandatory -or $parameterMetadata.required) -and + ($parameterAttributeParts -notmatch 'Mandatory') -and + -not $NoMandatory) { + $parameterAttributeParts = @('Mandatory') + $parameterAttributeParts + } + + $parameterType = $Type + + $parameterDeclaration = @( + if ($ParameterHelpText) { + $ParameterHelpText | embedParameterHelp + } + if ($parameterAttributeParts) { + "[Parameter($($parameterAttributeParts -join ','))]" + } + if ($aliasAttribute) { + $aliasAttribute + } + if ($ParameterOtherAttributes) { + $ParameterOtherAttributes + } + $PseudoType = $parameterType | PseudoTypeToRealType + if ($PseudoType) { + $parameterType = $PseudoType + if ($parameterType -eq [bool]) { + "[switch]" + } + elseif ($parameterType -eq [array]) { + "[PSObject[]]" + } + else { + if ($WeaklyTyped) { + if ($parameterType.GetInterface -and + $parameterType.GetInterface([Collections.IDictionary])) { + "[Collections.IDictionary]" + } + elseif ($parameterType.GetInterface -and + $parameterType.GetInterface([Collections.IList])) { + "[PSObject[]]" + } + else { + "[PSObject]" + } + } else { + if ($parameterType.IsGenericType -and + $parameterType.GetInterface -and + $parameterType.GetInterface(([Collections.IList])) -and + $parameterType.GenericTypeArguments.Count -eq 1 + ) { + "[$($parameterType.GenericTypeArguments[0].Fullname -replace '^System\.')[]]" + } else { + "[$($parameterType.FullName -replace '^System\.')]" + } + + } + } + } + elseif ($parameterType) { + "[PSTypeName('$($parameterType -replace '^System\.')')]" + } + + $DefaultValueSection = if ($parameterDefaultValue) { + if ($parameterDefaultValue -is [scriptblock]) { + if ($parameterType -eq [scriptblock]) { + "= {$ParameterDefaultValue}" + } else { + "= `$($ParameterDefaultValue)" + } + } elseif ($parameterDefaultValue -is [string]) { + "= `$('$($parameterDefaultValue -replace "'","''")')" + } elseif ($parameterDefaultValue -is [bool] -or $parameterDefaultValue -is [switch]) { + "= `$$($parameterDefaultValue -as [bool])" + } + } else { '' } + + '$' + ($parameterName -replace '^$') + $DefaultValueSection + ) -join [Environment]::newLine + + + + $generatedParameters.Enqueue($parameterDeclaration) + } + + end { + $generatedParameters.ToArray() -join (',' + [Environment]::newLine + [Environment]::newLine) + } +} + diff --git a/Languages/PowerShell/Templates/Syntax/PowerShell-Template-Parameter.ps1 b/Languages/PowerShell/Templates/Syntax/PowerShell-Template-Parameter.ps1 new file mode 100644 index 000000000..cd1df9805 --- /dev/null +++ b/Languages/PowerShell/Templates/Syntax/PowerShell-Template-Parameter.ps1 @@ -0,0 +1,381 @@ +[ValidatePattern("(?>PowerShell|PipeScript)")] +param() + + +function Template.PowerShell.Parameter { + + <# + .SYNOPSIS + PowerShell Parameter Template + .DESCRIPTION + Generates a parameter declaration for a PowerShell function. + .EXAMPLE + Template.PowerShell.Parameter -Name "MyParameter" -Type "string" -DefaultValue "MyDefaultValue" -Description "My Description" + #> + [Alias('Template.PipeScript.Parameter')] + param( + # The name of the parameter. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('ParameterName')] + [psobject] + $Name, + + # Any parameter attributes. + # These will appear first. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('ParameterAttributes')] + [psobject[]] + $ParameterAttribute, + + # Any parameter aliases + # These will appear beneath parameter attributes. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Aliases','AliasName','AliasNames')] + [psobject[]] + $Alias, + + # Any other attributes. These will appear directly above the type and name. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Attributes')] + [psobject[]] + $Attribute, + + # One or more parameter types. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('ParameterType','ParameterTypes')] + [psobject[]] + $Type, + + # One or more default values (if more than one default value is provided, it will be assumed to be an array) + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Default','ParameterDefault','ParameterDefaultValue')] + [psobject] + $DefaultValue, + + # A valid set of values + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('ValidValue','ValidValues')] + [psobject[]] + $ValidateSet, + + # The Parameter description (any help for the parameter) + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Help','ParameterHelp','Synopsis','Summary')] + [psobject[]] + $Description, + + # Any simple bindings for the parameter. + # These are often used to describe the name of a parameter in an underlying system. + # For example, a parameter might be named "Name" in PowerShell, but "itemName" in JSON. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Bindings','DefaultBinding','DefaultBindingProperty')] + [string[]] + $Binding, + + # The ambient value + # This script block can be used to coerce a value into the desired real value. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('CoerceValue')] + [scriptblock] + $AmbientValue, + + # Any additional parameter metadata. + # Any dictionaries passed to this parameter will be converted to [Reflection.AssemblyMetadata] attributes. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('ReflectionMetadata','ParameterMetadata')] + [psobject[]] + $Metadata, + + # The validation pattern for the parameter. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Pattern','Regex')] + [psobject[]] + $ValidatePattern, + + # If set, will make the parameter more weakly typed. + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $WeaklyTyped, + + # If set, will attempt to avoid creating mandatory parameters. + [Parameter(ValueFromPipelineByPropertyName)] + [switch] + $NoMandatory + ) + + begin { + + $generatedParameters = [Collections.Queue]::new() + filter embedParameterHelp { + + if ($_ -notmatch '^\s\<\#' -and $_ -notmatch '^\s\#') { + $commentLines = @($_ -split '(?>\r\n|\n)') + if ($commentLines.Count -gt 1) { + '<#' + [Environment]::NewLine + "$_".Trim() + [Environment]::newLine + '#>' + } else { + "# $_" + } + } else { + $_ + } + + } + + filter PseudoTypeToRealType { + + switch ($_) { + string { [string] } + integer { [int] } + number { [double]} + boolean { [switch] } + array { [PSObject[]] } + object { [PSObject] } + default { + if ($_ -as [type]) { + $_ -as [type] + } + } + } + + } + } + + process { + # Presort the attributes + $attribute = @(foreach ($attr in $Attribute) { + switch ($attr) { + {$_ -is [ValidateSet]} + { + $ValidateSet += $attr.ValidValues + continue + } + {$_ -is [Management.Automation.ParameterAttribute]} + { + $ParameterAttribute += $attr + continue + } + {$_ -is [Management.Automation.AliasAttribute]} + { + $Alias += $attr + continue + } + default { + $attr + } + } + }) + + $parameterAttribute = foreach ($paramAttr in $ParameterAttribute) { + switch ($paramAttr) { + {$_ -is [Management.Automation.AliasAttribute]} + { + $Alias += $paramAttr + } + {$_ -is [Management.Automation.ParameterAttribute]} + { + $paramAttr + } + {$_ -is [Attribute]} + { + $Attribute += $paramAttr + } + default { + $paramAttr + } + } + } + + + $parameterName = $name + + $attrs = @($ParameterAttribute;$attribute) + + $parameterAliases = @( + foreach ($aka in $alias) { + if ($aka -is [Management.Automation.AliasAttribute]) { + $aka.AliasNames + } elseif ($aka -is [string]) { + $aka + } + } + ) + + $parameterAttributeParts = @() + $ParameterOtherAttributes = @() + # Aliases can be in .Alias/.Aliases + + [string[]]$aliases = $parameterAliases + + # Help can be in .Help/.Description/.Synopsis/.Summary + [string]$parameterHelpText = $Description -join ([Environment]::NewLine) + + # Metadata can be in .Metadata/.ReflectionMetadata/.ParameterMetadata + $parameterMetadataProperties = $Metadata + + # Valid Values can be found in .ValidValue/.ValidValues/.ValidateSet + $parameterValidValues = $ValidateSet + + $parameterValidPattern = $ValidatePattern + + # Default values can be found in .DefaultValue/.Default + $parameterDefaultValue = $DefaultValue + + $aliasAttribute = @(foreach ($aka in $aliases) { + $aka -replace "'","''" + }) -join "','" + if ($aliasAttribute) { + $aliasAttribute = "[Alias('$aliasAttribute')]" + } + + if ($parameterValidValues) { + $attrs += "[ValidateSet('$($parameterValidValues -replace "'","''" -join "','")')]" + } + + if ($parameterValidPattern) { + $attrs += "[ValidatePattern('$($parameterValidPattern -replace "'","''")')]" + } + + if ($Binding) { + foreach ($bindingProperty in $Binding) { + $attrs += "[ComponentModel.DefaultBindingProperty('$bindingProperty')]" + } + } + + if ($parameterMetadataProperties) { + foreach ($pmdProp in $parameterMetadataProperties) { + if ($pmdProp -is [Collections.IDictionary]) { + foreach ($mdKv in $pmdProp.GetEnumerator()) { + $attrs += "[Reflection.AssemblyMetadata('$($mdKv.Key)', '$($mdKv.Value -replace "',''")')]" + } + } + } + } + + if ($AmbientValue) { + foreach ($ambient in $AmbientValue) { + $attrs += "[ComponentModel.AmbientValue({$ambient})]" + } + } + + $alreadyIncludedAttributes = @() + foreach ($attr in $attrs) { + if ($attr -is [Attribute]) { + $attrType = $attr.GetType() + if ($alreadyIncludedAttributes -contains $attr) { continue } + if ($attr -is [Parameter]) { + $ParameterOtherAttributes += "[Parameter($(@( + if ($attr.Mandatory) { 'Mandatory' } + if ($attr.Postition -ge 0) { "Position=$($attr.Position)"} + if ($attr.ParameterSetName -and $attr.ParameterSetName -ne '__AllParameterSets') { + "ParameterSetName='$($attr.ParameterSetName -replace "'","''")'" + } + if ($attr.ValueFromPipeline) { 'ValueFromPipeline' } + if ($attr.ValueFromPipelineByPropertyName) { 'ValueFromPipelineByPropertyName' } + if ($attr.ValueFromRemainingArguments) { 'ValueFromRemainingArguments' } + if ($attr.DontShow) { 'DontShow' } + ) -join ','))]" + } else { + $attr | Template.PowerShell.Attribute + } + $alreadyIncludedAttributes += $attr + continue + } + if ($attr -notmatch '^\[') { + $parameterAttributeParts += $attr + } else { + $ParameterOtherAttributes += $attr + } + } + + if ( + ($parameterMetadata.Mandatory -or $parameterMetadata.required) -and + ($parameterAttributeParts -notmatch 'Mandatory') -and + -not $NoMandatory) { + $parameterAttributeParts = @('Mandatory') + $parameterAttributeParts + } + + $parameterType = $Type + + $parameterDeclaration = @( + if ($ParameterHelpText) { + $ParameterHelpText | embedParameterHelp + } + if ($parameterAttributeParts) { + "[Parameter($($parameterAttributeParts -join ','))]" + } + if ($aliasAttribute) { + $aliasAttribute + } + if ($ParameterOtherAttributes) { + $ParameterOtherAttributes + } + $PseudoType = $parameterType | PseudoTypeToRealType + if ($PseudoType) { + $parameterType = $PseudoType + if ($parameterType -eq [bool]) { + "[switch]" + } + elseif ($parameterType -eq [array]) { + "[PSObject[]]" + } + else { + if ($WeaklyTyped) { + if ($parameterType.GetInterface -and + $parameterType.GetInterface([Collections.IDictionary])) { + "[Collections.IDictionary]" + } + elseif ($parameterType.GetInterface -and + $parameterType.GetInterface([Collections.IList])) { + "[PSObject[]]" + } + else { + "[PSObject]" + } + } else { + if ($parameterType.IsGenericType -and + $parameterType.GetInterface -and + $parameterType.GetInterface(([Collections.IList])) -and + $parameterType.GenericTypeArguments.Count -eq 1 + ) { + "[$($parameterType.GenericTypeArguments[0].Fullname -replace '^System\.')[]]" + } else { + "[$($parameterType.FullName -replace '^System\.')]" + } + + } + } + } + elseif ($parameterType) { + "[PSTypeName('$($parameterType -replace '^System\.')')]" + } + + $DefaultValueSection = if ($parameterDefaultValue) { + if ($parameterDefaultValue -is [scriptblock]) { + if ($parameterType -eq [scriptblock]) { + "= {$ParameterDefaultValue}" + } else { + "= `$($ParameterDefaultValue)" + } + } elseif ($parameterDefaultValue -is [string]) { + "= `$('$($parameterDefaultValue -replace "'","''")')" + } elseif ($parameterDefaultValue -is [bool] -or $parameterDefaultValue -is [switch]) { + "= `$$($parameterDefaultValue -as [bool])" + } + } else { '' } + + '$' + ($parameterName -replace '^$') + $DefaultValueSection + ) -join [Environment]::newLine + + + + $generatedParameters.Enqueue($parameterDeclaration) + } + + end { + $generatedParameters.ToArray() -join (',' + [Environment]::newLine + [Environment]::newLine) + } + +} + + + diff --git a/Languages/Pug/Pug-Language.ps.ps1 b/Languages/Pug/Pug-Language.ps.ps1 new file mode 100644 index 000000000..3432943d9 --- /dev/null +++ b/Languages/Pug/Pug-Language.ps.ps1 @@ -0,0 +1,27 @@ +[ValidatePattern("(?>Pug|Language)[\s\p{P}]")] +param() + + +Language function Pug { + <# + .SYNOPSIS + Pug Language Definition + .DESCRIPTION + Allows PipeScript to work with Pug. + + Pug is a high-performance template engine heavily influenced by Haml and implemented with JavaScript for Node.js and browsers. + #> + [ValidatePattern('\.pug$')] + param() + $LanguageName = 'Pug' + $FilePattern = '\.pug$' + $IsTemplateLanguage = $true + $WorksWith = 'Express','React' + $Install = { npm install pug @args } + $Website = 'https://pugjs.org/' + $startComment = '\ + @@ -1843,6 +1843,69 @@ $BackgroundColor + + Default + + System.Net.HttpListenerRequest + + + + + + + + + + + + + + + IPAddress + + + Method + + + Url + + + + + + + + Body + + System.Net.HttpListenerRequest + + + + + + + + + + + + + + + + Method + + + Url + + + Body + + + + + + Language @@ -2034,6 +2097,28 @@ $BackgroundColor + + Namespace + + Namespace + + + + + + + + + + + + Pattern + + + + + + PipeScript @@ -2124,9 +2209,9 @@ $BackgroundColor - PipeScript.Interpreters + PipeScript.Module.Services - PipeScript.Interpreters + PipeScript.Module.Services @@ -2134,35 +2219,107 @@ $BackgroundColor - if ($ExecutionContext.SessionState.InvokeCommand.GetCommand('Show-Markdown', 'Cmdlet')) { - Show-Markdown -InputObject $_.'README' - } else { - $_.'README' - } - + Show-Markdown -InputObject $_.'README.md' + + + + + $_.Commands + + $moduleName = 'PipeScript' + + do { + $lm = Get-Module -Name $moduleName -ErrorAction Ignore + if (-not $lm) { continue } + if ($lm.FormatPartsLoaded) { break } + $wholeScript = @(foreach ($formatFilePath in $lm.exportedFormatFiles) { + foreach ($partNodeName in Select-Xml -LiteralPath $formatFilePath -XPath "/Configuration/Controls/Control/Name[starts-with(., '$')]") { + $ParentNode = $partNodeName.Node.ParentNode + "$($ParentNode.Name)={ + $($ParentNode.CustomControl.CustomEntries.CustomEntry.CustomItem.ExpressionBinding.ScriptBlock)}" + } + }) -join [Environment]::NewLine + New-Module -Name "${ModuleName}.format.ps1xml" -ScriptBlock ([ScriptBlock]::Create(($wholeScript + ';Export-ModuleMember -Variable *'))) | + Import-Module -Global + $onRemove = [ScriptBlock]::Create("Remove-Module '${ModuleName}.format.ps1xml'") + + if (-not $lm.OnRemove) { + $lm.OnRemove = $onRemove + } else { + $lm.OnRemove = [ScriptBlock]::Create($onRemove.ToString() + '' + [Environment]::NewLine + $lm.OnRemove) + } + $lm | Add-Member NoteProperty FormatPartsLoaded $true -Force + + } while ($false) + + +@(& ${PipeScript_Format-RichText} -ForegroundColor 'Cyan' -NoClear) -join '' - - - - - - - PipeScript.Languages - - PipeScript.Languages - - - - - + + $_.Commands + - if ($ExecutionContext.SessionState.InvokeCommand.GetCommand('Show-Markdown', 'Cmdlet')) { - Show-Markdown -InputObject $_.'README' - } else { - $_.'README' - } - + "Commands:" + + [Environment]::NewLine + + ("* ") + ( + $_.Commands -join ([Environment]::NewLine + ("* ")) + ) + ([Environment]::NewLine * 2) + + + + + $_.Commands + + @(& ${PipeScript_Format-RichText} -ForegroundColor 'Cyan' ) -join '' + + + + $_.Commands + + @(& ${PipeScript_Format-RichText} -ForegroundColor 'Green' -NoClear) -join '' + + + + $_.Commands + + + "Types:" + + [Environment]::NewLine + + ("* ") + ( + $_.Type -join ([Environment]::NewLine + ("* ")) + ) + ([Environment]::NewLine * 2) + + + + + $_.Commands + + @(& ${PipeScript_Format-RichText} -ForegroundColor 'Green' ) -join '' + + + + $_.Variable + + @(& ${PipeScript_Format-RichText} -ForegroundColor 'Blue' -NoClear) -join '' + + + + $_.Variable + + + "Variable:" + + [Environment]::NewLine + + ("* ") + ( + $_.Variable -join ([Environment]::NewLine + ("* ")) + ) + ([Environment]::NewLine * 2) + + + + + $_.Variable + + @(& ${PipeScript_Format-RichText} -ForegroundColor 'Blue' ) -join '' @@ -2296,6 +2453,75 @@ $BackgroundColor + + PipeScript.Interpreters + + PipeScript.Interpreters + + + + + + + + if ($ExecutionContext.SessionState.InvokeCommand.GetCommand('Show-Markdown', 'Cmdlet')) { + Show-Markdown -InputObject $_.'README' + } else { + $_.'README' + } + + + + + + + + + PipeScript.Languages + + PipeScript.Languages + + + + + + + + if ($ExecutionContext.SessionState.InvokeCommand.GetCommand('Show-Markdown', 'Cmdlet')) { + Show-Markdown -InputObject $_.'README' + } else { + $_.'README' + } + + + + + + + + + PipeScript.Languages + + PipeScript.Languages + + + + + + + + if ($ExecutionContext.SessionState.InvokeCommand.GetCommand('Show-Markdown', 'Cmdlet')) { + Show-Markdown -InputObject $_.'README' + } else { + $_.'README' + } + + + + + + + PipeScript.Template diff --git a/PipeScript.ps.dockerfile b/PipeScript.ps.dockerfile index b14731b4e..8cfcf236e 100644 --- a/PipeScript.ps.dockerfile +++ b/PipeScript.ps.dockerfile @@ -20,25 +20,30 @@ #} #{ - # param($DockerInstallPackages = @("git","curl","ca-certificates","libc6","libgcc1") ) - # if ($DockerInstallPackages) {"RUN apt-get update && apt-get install -y $($dockerInstallPackages -join ' ')"} + # param($DockerInstallPackages = @("git","curl","ca-certificates","libc6","libgcc1","liblttng-ust1","libstdc++6","libunwind8","zlib1g","build-essential", "libgdiplus", "golang","python3", "nodejs","dotnet-sdk-8.0") ) + # if ($DockerInstallPackages) {"RUN apt-get update && apt-get install -y $($dockerInstallPackages -join ' ') && apt-get clean"} #} -ENV PSModulePath ./Modules +#{ + # $LoadedModuleInPath = (Get-Module | Split-Path) -match ([Regex]::Escape($pwd)) | Select -first 1 + # if ($LoadedModuleInPath) { "COPY ./ ./usr/local/share/powershell/Modules/$($LoadedModuleInPath | Split-Path -Leaf)" } +#} + +SHELL ["pwsh", "-noprofile", "-nologo", "-command"] #{ # param($DockerInstallModules = @("Splatter", "PSSVG", "ugit", "Irregular") ) - # $PowerShellPath = "opt/microsoft/powershell/7/pwsh" - # if ($DockerInstallModules) { "RUN $PowerShellPath --noprofile --nologo -c Install-Module '$($DockerInstallModules -join "','")' -Scope CurrentUser -Force"} -#} - -#{ - # $LoadedModuleInPath = (Get-Module | Split-Path) -match ([Regex]::Escape($pwd)) | Select -first 1 - # if ($LoadedModuleInPath) { "COPY ./ ./Modules/$($LoadedModuleInPath | Split-Path -Leaf)" } + # $PowerShellPath = "/bin/pwsh" + # $InstallModules = "Install-Module '$($DockerInstallModules -join "','")' -AcceptLicense -Scope CurrentUser -Force" + # $NewProfile = "New-Item -ItemType File -Path `$Profile -Force" + # $AddInstalled = "Add-Content -Value `"Import-Module '$(@($DockerInstallModules + $($LoadedModuleInPath | Split-Path -Leaf)) -join "','")'`"" + # if ($DockerInstallModules) { "RUN @($InstallModules && $NewProfile | $AddInstalled)" -replace '\$', '\$' } #} #{ - # param(<# A Script to Run When Docker Starts #>$DockerProfileScript = "./Http.Server.Start.ps1") - # if ($DockerProfileScript) { "COPY ./$DockerProfileScript /root/.config/powershell/Microsoft.PowerShell_profile.ps1"} + # param(<# A Script to Run When Docker Starts #>$Microservice = "./Http.Server.Start.ps1") + # if ($Microservice) { "COPY $Microservice $Microservice"} + # if ($Microservice) { "RUN Add-Content -Path \`$Profile -Value $Microservice" } #} +ENTRYPOINT ["pwsh","-nologo"] diff --git a/PipeScript.ps.psd1 b/PipeScript.ps.psd1 index 9a8bc63d6..b12e88c86 100644 --- a/PipeScript.ps.psd1 +++ b/PipeScript.ps.psd1 @@ -8,7 +8,7 @@ TypesToProcess = 'PipeScript.types.ps1xml' Guid = 'fc054786-b1ce-4ed8-a90f-7cc9c27edb06' CompanyName = 'Start-Automating' - Copyright = '2022-2023 Start-Automating' + Copyright = '2022-2024 Start-Automating' Author = 'James Brundage' FunctionsToExport = '*' <#{ $exportNames = Get-ChildItem -Recurse -Filter '*-*.ps1' | @@ -68,6 +68,7 @@ ' ExcludeCommandType = '(?>Application|Script|Cmdlet)' } + 'Analyzer' = @{ Description = 'Analyzation Commands' Pattern = ' @@ -87,6 +88,26 @@ CommandType = '(?>Function|Alias)' } + 'ContentType' = @{ + Description = 'A content type command.' + Pattern = ' + (?<=(?>^|\p{P})) + (?> + application| + audio| + video| + image| + message| + multipart| + text| + model| + example| + font + )(?=(?>\p{P})) + ' + ExcludeCommandType = '(?>Application|Script|Cmdlet)' + } + 'Language' = @{ Description = 'Language Commands describe languages' Pattern = ' @@ -204,6 +225,42 @@ ' ExcludeCommandType = '(?>Application|Cmdlet)' } + + 'Service' = @{ + Pattern = ' + (?> + (?>^|[\p{P}-[\-]]) # After non-dash punctuation or the start of a string + Se?rv[ie]?c?e?s? # Various forms of the word service + (?> # Followed by either + \.\w$ # any extension and end of string + | # or + \p{P} # any other punctuation. + ) + ) + ' + } + + 'Technology' = @{ + Pattern = ' + (?> + (?>^|[\p{P}-[\-]]) # After non-dash punctuation or the start of a string + (?> + Tech(?>s|nology)? # Various forms of the word technology + | # or + Engines? # Engine or Engines + | + Frameworks? # Framework or Frameworks + | + Platforms? # Platform or Platforms + ) + (?> # Followed by either + \.\w$ # any extension and end of string + | # or + \p{P} # any other punctuation. + ) + ) + ' + } 'Template' = @{ @@ -214,6 +271,30 @@ Server = 'pipescript.dev', 'pipescript.info', 'pipescript.io' Servers = 'pipescript.startautomating.com','pipescript.start-automating.com' + Services = @{ + Name = 'Markdown Service' + Command = 'ConvertFrom-Markdown' + }, @{ + Name = 'Math Service' + Type = 'Math' + }, @{ + Name = 'Pid Service' + Variable = 'pid' + }, @{ + Name = 'Asset Service' + Extension = '.svg','.png','.js','.css' + } + + Site = 'https://pipescript.startautomating.com', @{ + Tech = 'Jekyll' + Root = '/docs' + Palette = 'Konsolas' + }, @{ + Url ='https://api.pipescript.startautomating.com' + Mirror = 'https://pipescript.dev/','https://pipescript.io/', 'https://pipescript.info/' + Tech = 'AzureKubernetesService' + Palette = 'Kolorit' + } Videos = @{ "Run Anything with PipeScript (from RTPSUG)" = "https://www.youtube.com/watch?v=-PuiNAcvalw" diff --git a/PipeScript.ps1.psm1 b/PipeScript.ps1.psm1 index 303654741..5a1c5fe25 100644 --- a/PipeScript.ps1.psm1 +++ b/PipeScript.ps1.psm1 @@ -78,55 +78,65 @@ foreach ($typesXmlNoteProperty in $typesXmlNoteProperties){ } } -# A few extension types we want to publish as variables -$PipeScript.Extensions | - . { - begin { - # Languages will populate `$psLanguage(s)` - $LanguagesByName = [Ordered]@{} +# Languages will populate `$psLanguage(s)` +$LanguagesByName = [Ordered]@{} - # Interpreters will populate `$psInterpreter(s)` - $InterpretersByName = [Ordered]@{} +# Interpreters will populate `$psInterpreter(s)` +$InterpretersByName = [Ordered]@{} - # Parsers will populate `$psParsers` - $ParsersByName = [Ordered]@{} - } +# Technologies will populate `$psTech(s)` +$TechsByName = [Ordered]@{} + +# Parsers will populate `$psParser(s)` +$ParsersByName = [Ordered]@{} + +# A few extension types we want to publish as variables +$PipeScript.Extensions | + . { process { - if ($_.Name -notlike 'Language*') { + if ($_.Name -notlike 'Language*') { + if ($_.pstypenames -contains 'Technology.Command') { + $TechsByName[$_.Name] = $_ + } if ($_.pstypenames -contains 'Parser.Command') { $ParsersByName[$_.Name] = $_ } return } - $languageObject = & $_ + $languageCommand = $_ + $languageObject = & $languageCommand if (-not $languageObject.LanguageName) { return } + $TechsByName[$languageCommand.Name] = $languageObject $LanguagesByName[$languageObject.LanguageName] = $languageObject if ($languageObject.Interpreter) { $InterpretersByName[$languageObject.LanguageName] = $languageObject } - } + } + } - end { - $PSLanguage = $PSLanguages = [PSCustomObject]$LanguagesByName - $PSLanguage.pstypenames.clear() - $PSLanguage.pstypenames.insert(0,'PipeScript.Languages') - - $PSInterpreter = $PSInterpreters = [PSCustomObject]$InterpretersByName - $PSInterpreter.pstypenames.clear() - $PSInterpreter.pstypenames.insert(0,'PipeScript.Interpreters') +$PSLanguage = $PSLanguages = [PSCustomObject]$LanguagesByName +$PSLanguage.pstypenames.clear() +$PSLanguage.pstypenames.insert(0,'PipeScript.Languages') - $PSParser = $PSParsers = [PSCustomObject]$ParsersByName - $PSParser.pstypenames.clear() - $PSParser.pstypenames.insert(0,'PipeScript.Parsers') - } - } +$PSInterpreter = $PSInterpreters = [PSCustomObject]$InterpretersByName +$PSInterpreter.pstypenames.clear() +$PSInterpreter.pstypenames.insert(0,'PipeScript.Interpreters') + +$PSTech = $PSTechs = $PSTechnology = $PSTechnologies = [PSCustomObject]$TechsByName +$PSTech.pstypenames.clear() +$PSTech.pstypenames.insert(0,'PipeScript.Techs') + +$PSParser = $PSParsers = [PSCustomObject]$ParsersByName +$PSParser.pstypenames.clear() +$PSParser.pstypenames.insert(0,'PipeScript.Parsers') Export-ModuleMember -Function * -Alias * -Variable $MyInvocation.MyCommand.ScriptBlock.Module.Name, 'PSLanguage', 'PSLanguages', 'PSInterpreter', 'PSInterpreters', - 'PSParser','PSParsers' + 'PSParser','PSParsers', + 'PSTech', 'PSTechs', 'PSTechnology', 'PSTechnologies' $PreCommandAction = { param($LookupArgs) diff --git a/PipeScript.psd1 b/PipeScript.psd1 index 357836070..09b48fcd6 100644 --- a/PipeScript.psd1 +++ b/PipeScript.psd1 @@ -8,9 +8,9 @@ TypesToProcess = 'PipeScript.types.ps1xml' Guid = 'fc054786-b1ce-4ed8-a90f-7cc9c27edb06' CompanyName = 'Start-Automating' - Copyright = '2022-2023 Start-Automating' + Copyright = '2022-2024 Start-Automating' Author = 'James Brundage' - FunctionsToExport = 'Get-Transpiler','Start-PSNode','PipeScript.Optimizer.ConsolidateAspects','Route.Uptime','Route.VersionInfo','Export-Pipescript','Get-PipeScript','Import-PipeScript','Invoke-PipeScript','Join-PipeScript','New-PipeScript','Search-PipeScript','Update-PipeScript','Use-PipeScript','Import-ModuleMember','Export-Json','Import-Json','ConvertFrom-CliXml','ConvertTo-CliXml','Get-Interpreter','Invoke-Interpreter','Out-Parser','Parse.CSharp','Parse.PowerShell','Protocol.HTTP','Protocol.JSONSchema','Protocol.OpenAPI','Protocol.UDP','Aspect.DynamicParameter','Aspect.ModuleExtensionType','Aspect.ModuleExtensionPattern','Aspect.ModuleExtensionCommand','Aspect.GroupObjectByTypeName','Aspect.GroupObjectByType','Signal.Nothing','Signal.Out','PipeScript.PostProcess.InitializeAutomaticVariables','PipeScript.PostProcess.PartialFunction','Out-HTML','PipeScript.Automatic.Variable.IsPipedTo','PipeScript.Automatic.Variable.IsPipedFrom','PipeScript.Automatic.Variable.MyCallstack','PipeScript.Automatic.Variable.MySelf','PipeScript.Automatic.Variable.MyParameters','PipeScript.Automatic.Variable.MyCaller','PipeScript.Automatic.Variable.MyCommandAst','Compile.LanguageDefinition','Language.JavaScript','Template.Assignment.js','Template.Class.js','Template.DoLoop.js','Template.ForEachLoop.js','Template.ForLoop.js','Template.Function.js','Template.HelloWorld.js','Template.InvokeMethod.js','Template.RegexLiteral.js','Template.TryCatch.js','Template.WhileLoop.js','Language.Rust','Language.Lua','Language.FSharp','Language.GCode','Language.XSD','Language.Ruby','Template.HelloWorld.rb','Language.Haxe','Language.Scala','Language.Racket','Language.SQL','Language.Arduino','Language.PipeScript','Language.Java','Language.Kotlin','Language.Bicep','Language.Markdown','Language.Go','Template.HelloWorld.go','Language.C3','Language.XSL','Language.GLSL','Language.JSON','Language.Batch','Language.TypeScript','Template.HelloWorld.ts','Language.BASIC','Language.XML','Language.CSS','Language.Vue','Language.YAML','Language.TCL','Language.SVG','Language.Docker','Language.CSharp','Template.Class.cs','Template.HelloWorld.cs','Template.Method.cs','Template.Namespace.cs','Template.Property.cs','Template.TryCatch.cs','Language.PowerShell','Language.PowerShellData','Language.PowerShellXML','Language.Conf','Language.Bash','Language.HCL','Language.Razor','Language.OpenSCAD','Language.RSS','Language.ADA','Language.XAML','Language.R','Language.Dart','Language.Crystal','Template.HelloWorld.cr','Language.Perl','Language.BrightScript','Language.Wren','Template.HelloWorld.wren','Language.CPlusPlus','Template.HelloWorld.cpp','Template.Include.cpp','Language.Liquid','Language.Eiffel','Language.Python','Template.Assignment.py','Template.DoLoop.py','Template.HelloWorld.py','Template.Import.py','Template.UntilLoop.py','Template.WhileLoop.py','Language.WebAssembly','Language.HLSL','Language.PHP','Language.Kusto','Language.C','Template.Include.c','Language.TOML','Language.HTML','Language.LaTeX','Language.ATOM','Language.ObjectiveC' + FunctionsToExport = 'Get-Transpiler','Start-PSNode','ConvertFrom-CliXml','ConvertTo-CliXml','Import-ModuleMember','Mount-Module','Use-Module','Out-HTML','Export-Json','Import-Json','Out-JSON','Signal.Nothing','Signal.Out','Compile.LanguageDefinition','Export-Pipescript','Get-PipeScript','Import-PipeScript','Invoke-PipeScript','Join-PipeScript','New-PipeScript','Search-PipeScript','Update-PipeScript','Use-PipeScript','PipeScript.Automatic.Variable.IsPipedTo','PipeScript.Automatic.Variable.IsPipedFrom','PipeScript.Automatic.Variable.MyCallstack','PipeScript.Automatic.Variable.MySelf','PipeScript.Automatic.Variable.MyParameters','PipeScript.Automatic.Variable.MyCaller','PipeScript.Automatic.Variable.MyCommandAst','Serve.Asset','Serve.Command','Serve.Module','Serve.Variable','Get-Interpreter','Invoke-Interpreter','PipeScript.PostProcess.InitializeAutomaticVariables','PipeScript.PostProcess.PartialFunction','Search-Command','Aspect.DynamicParameter','Aspect.ModuleExtensionType','Aspect.ModuleExtensionPattern','Aspect.ModuleExtensionCommand','Aspect.GroupObjectByTypeName','Aspect.GroupObjectByType','Route.Uptime','Route.VersionInfo','Protocol.HTTP','Protocol.JSONSchema','Protocol.OpenAPI','Protocol.UDP','Out-Parser','Parse.CSharp','Parse.PowerShell','PipeScript.Optimizer.ConsolidateAspects','Language.C','Template.Include.c','Language.XSL','Language.C3','Language.Racket','Language.SVG','Language.Pug','Language.Java','Language.Dart','Template.HelloWorld.dart','Language.Crystal','Template.HelloWorld.cr','Language.HTML','Template.HelloWorld.html','Template.HTML.Element','Template.HTML.Script','Template.HTML.StyleSheet','Template.HTML.CustomElement','Template.HTML.Command.Input','Template.HTML.InputElement','Template.HTML.Parameter.Input','Template.HTML.Default.Layout','Language.RSS','Language.LaTeX','Language.SQL','Language.R','Language.Kotlin','Language.JSON','Language.CSharp','Template.Class.cs','Template.HelloWorld.cs','Template.Method.cs','Template.Namespace.cs','Template.Property.cs','Template.TryCatch.cs','Language.Markdown','Language.YAML','Language.Lua','Language.ObjectiveC','Language.OpenSCAD','Language.Arduino','Language.Bash','Template.Bash.Wrapper','Language.Vue','Language.XML','Language.Razor','Language.PipeScript','Template.PipeScript.ExplicitOutput','Template.PipeScript.Inherit','Template.PipeScript.OutputFile','Template.PipeScript.ProxyCommand','Template.PipeScript.Rest','Template.PipeScript.Dot','Template.PipeScript.DoubleDot','Template.PipeScript.DoubleEqualCompare','Template.PipeScript.Keyword','Keyword.$KeywordName','Template.PipeScript.NamespacedAlias','Template.PipeScript.NamespacedObject','Template.PipeScript.PipedAssignment','Template.PipeScript.SwitchAsIs','Template.PipeScript.TripleEqualCompare','Template.PipeScript.WhereMethod','Language.ATOM','Language.Scala','Language.HLSL','Language.XSD','Language.CPlusPlus','Template.HelloWorld.cpp','Template.Include.cpp','Language.Kusto','Language.XAML','Language.TCL','Language.Haxe','Language.Eiffel','Language.CSS','Language.GCode','Language.TOML','Language.JavaScript','Template.HelloWorld.js','Template.Assignment.js','Template.Class.js','Template.DoLoop.js','Template.ForeachArgument.js','Template.ForEachLoop.js','Template.ForLoop.js','Template.Function.js','Template.InvokeMethod.js','Template.RegexLiteral.js','Template.TryCatch.js','Template.WhileLoop.js','Language.Perl','Language.Docker','Template.Docker.InstallModule','Template.Docker.InstallPackage','Template.Docker.LabelModule','Template.Docker.Add','Template.Docker.Argument','Template.Docker.Command','Template.Docker.CopyItem','Template.Docker.EntryPoint','Template.Docker.Expose','Template.Docker.From','Template.Docker.HealthCheck','Template.Docker.Label','Template.Docker.OnBuild','Template.Docker.Run','Template.Docker.SetLocation','Template.Docker.SetShell','Template.Docker.SetUser','Template.Docker.SetVariable','Template.Docker.StopSignal','Template.Docker.Volume','Language.PHP','Language.FSharp','Language.HCL','Language.Ruby','Template.HelloWorld.rb','Language.ADA','Language.PowerShell','Language.PowerShellData','Language.PowerShellXML','Template.PowerShell.RemoveParameter','Template.PowerShell.RenameVariable','Template.PowerShell.Attribute','Template.PowerShell.Help','Template.PowerShell.Parameter','Language.Bicep','Language.Batch','Template.Batch.Wrapper','Language.Python','Template.HelloWorld.py','Template.Import.py','Template.Assignment.py','Template.DoLoop.py','Template.ForeachArgument.py','Template.UntilLoop.py','Template.WhileLoop.py','Language.Cuda','Language.Liquid','Language.Rust','Language.GLSL','Language.WebAssembly','Language.Go','Template.HelloWorld.go','Language.Conf','Language.BASIC','Language.BrightScript','Language.TypeScript','Template.HelloWorld.ts','Language.Wren','Template.HelloWorld.wren','Tech.Jekyll','Tech.Hugo' PrivateData = @{ FunctionTypes = @{ 'Partial' = @{ @@ -55,6 +55,7 @@ ' ExcludeCommandType = '(?>Application|Script|Cmdlet)' } + 'Analyzer' = @{ Description = 'Analyzation Commands' Pattern = ' @@ -74,6 +75,26 @@ CommandType = '(?>Function|Alias)' } + 'ContentType' = @{ + Description = 'A content type command.' + Pattern = ' + (?<=(?>^|\p{P})) + (?> + application| + audio| + video| + image| + message| + multipart| + text| + model| + example| + font + )(?=(?>\p{P})) + ' + ExcludeCommandType = '(?>Application|Script|Cmdlet)' + } + 'Language' = @{ Description = 'Language Commands describe languages' Pattern = ' @@ -191,6 +212,42 @@ ' ExcludeCommandType = '(?>Application|Cmdlet)' } + + 'Service' = @{ + Pattern = ' + (?> + (?>^|[\p{P}-[\-]]) # After non-dash punctuation or the start of a string + Se?rv[ie]?c?e?s? # Various forms of the word service + (?> # Followed by either + \.\w$ # any extension and end of string + | # or + \p{P} # any other punctuation. + ) + ) + ' + } + + 'Technology' = @{ + Pattern = ' + (?> + (?>^|[\p{P}-[\-]]) # After non-dash punctuation or the start of a string + (?> + Tech(?>s|nology)? # Various forms of the word technology + | # or + Engines? # Engine or Engines + | + Frameworks? # Framework or Frameworks + | + Platforms? # Platform or Platforms + ) + (?> # Followed by either + \.\w$ # any extension and end of string + | # or + \p{P} # any other punctuation. + ) + ) + ' + } 'Template' = @{ @@ -201,6 +258,30 @@ Server = 'pipescript.dev', 'pipescript.info', 'pipescript.io' Servers = 'pipescript.startautomating.com','pipescript.start-automating.com' + Services = @{ + Name = 'Markdown Service' + Command = 'ConvertFrom-Markdown' + }, @{ + Name = 'Math Service' + Type = 'Math' + }, @{ + Name = 'Pid Service' + Variable = 'pid' + }, @{ + Name = 'Asset Service' + Extension = '.svg','.png','.js','.css' + } + + Site = 'https://pipescript.startautomating.com', @{ + Tech = 'Jekyll' + Root = '/docs' + Palette = 'Konsolas' + }, @{ + Url ='https://api.pipescript.startautomating.com' + Mirror = 'https://pipescript.dev/','https://pipescript.io/', 'https://pipescript.info/' + Tech = 'AzureKubernetesService' + Palette = 'Kolorit' + } Videos = @{ "Run Anything with PipeScript (from RTPSUG)" = "https://www.youtube.com/watch?v=-PuiNAcvalw" diff --git a/PipeScript.psm1 b/PipeScript.psm1 index c632ea8be..8e09bfa61 100644 --- a/PipeScript.psm1 +++ b/PipeScript.psm1 @@ -122,55 +122,65 @@ foreach ($typesXmlNoteProperty in $typesXmlNoteProperties){ } } -# A few extension types we want to publish as variables -$PipeScript.Extensions | - . { - begin { - # Languages will populate `$psLanguage(s)` - $LanguagesByName = [Ordered]@{} +# Languages will populate `$psLanguage(s)` +$LanguagesByName = [Ordered]@{} - # Interpreters will populate `$psInterpreter(s)` - $InterpretersByName = [Ordered]@{} +# Interpreters will populate `$psInterpreter(s)` +$InterpretersByName = [Ordered]@{} - # Parsers will populate `$psParsers` - $ParsersByName = [Ordered]@{} - } +# Technologies will populate `$psTech(s)` +$TechsByName = [Ordered]@{} + +# Parsers will populate `$psParser(s)` +$ParsersByName = [Ordered]@{} + +# A few extension types we want to publish as variables +$PipeScript.Extensions | + . { process { - if ($_.Name -notlike 'Language*') { + if ($_.Name -notlike 'Language*') { + if ($_.pstypenames -contains 'Technology.Command') { + $TechsByName[$_.Name] = $_ + } if ($_.pstypenames -contains 'Parser.Command') { $ParsersByName[$_.Name] = $_ } return } - $languageObject = & $_ + $languageCommand = $_ + $languageObject = & $languageCommand if (-not $languageObject.LanguageName) { return } + $TechsByName[$languageCommand.Name] = $languageObject $LanguagesByName[$languageObject.LanguageName] = $languageObject if ($languageObject.Interpreter) { $InterpretersByName[$languageObject.LanguageName] = $languageObject } - } + } + } - end { - $PSLanguage = $PSLanguages = [PSCustomObject]$LanguagesByName - $PSLanguage.pstypenames.clear() - $PSLanguage.pstypenames.insert(0,'PipeScript.Languages') - - $PSInterpreter = $PSInterpreters = [PSCustomObject]$InterpretersByName - $PSInterpreter.pstypenames.clear() - $PSInterpreter.pstypenames.insert(0,'PipeScript.Interpreters') +$PSLanguage = $PSLanguages = [PSCustomObject]$LanguagesByName +$PSLanguage.pstypenames.clear() +$PSLanguage.pstypenames.insert(0,'PipeScript.Languages') - $PSParser = $PSParsers = [PSCustomObject]$ParsersByName - $PSParser.pstypenames.clear() - $PSParser.pstypenames.insert(0,'PipeScript.Parsers') - } - } +$PSInterpreter = $PSInterpreters = [PSCustomObject]$InterpretersByName +$PSInterpreter.pstypenames.clear() +$PSInterpreter.pstypenames.insert(0,'PipeScript.Interpreters') + +$PSTech = $PSTechs = $PSTechnology = $PSTechnologies = [PSCustomObject]$TechsByName +$PSTech.pstypenames.clear() +$PSTech.pstypenames.insert(0,'PipeScript.Techs') + +$PSParser = $PSParsers = [PSCustomObject]$ParsersByName +$PSParser.pstypenames.clear() +$PSParser.pstypenames.insert(0,'PipeScript.Parsers') Export-ModuleMember -Function * -Alias * -Variable $MyInvocation.MyCommand.ScriptBlock.Module.Name, 'PSLanguage', 'PSLanguages', 'PSInterpreter', 'PSInterpreters', - 'PSParser','PSParsers' + 'PSParser','PSParsers', + 'PSTech', 'PSTechs', 'PSTechnology', 'PSTechnologies' $PreCommandAction = { param($LookupArgs) diff --git a/PipeScript.types.ps1xml b/PipeScript.types.ps1xml index 6d3e3586e..993922ba3 100644 --- a/PipeScript.types.ps1xml +++ b/PipeScript.types.ps1xml @@ -1,4 +1,4 @@ - + System.Management.Automation.AliasInfo @@ -57,6 +57,10 @@ $this.'.Root' System.Management.Automation.Language.Ast + + GetCollectionAndIndex + GetRelativeIndex + ConvertFromAST + + GetRelativeIndex + + IsEquivalentTo @@ -1937,6 +2043,48 @@ $this.'.Root' + + System.Net.IPAddress + + + IsLocal + + <# +.Synopsis + Tests if an IP is a local Intranet connection +.Description + Determines if the remote connection is coming from within a local Intranet environment or not. +#> +param() + +$IP = $this + +if (-not $IP) { return $false} + +$addressBytes = $IP.GetAddressBytes() +if ($addressBytes.Count -eq 16) { + return $IP.IsIPv6LinkLocal -or $IP.IsIPv6SiteLocal -or "$IP" -eq "::1" +} else { + if ($addressBytes.Count -eq 4) { + if ($addressBytes[0] -eq 10) { + return $true + } elseif ($addressBytes[0] -eq 172 -and $addressBytes[1] -ge 16 -and $addressBytes[1] -le 31) { + return $true + } elseif ($addressBytes[0] -eq 192 -and $addressBytes[1] -eq 168) { + return $true + } elseif ("$IP" -eq "127.0.0.1") { + return $true + } + } +} + +return $false + + + + + + Language @@ -1944,6 +2092,10 @@ $this.'.Root' Aliases Alias + + Filters + Filter + Functions Function @@ -1957,23 +2109,60 @@ $this.'.Root' <# .SYNOPSIS - Gets Language Functions + Gets Language Aliases .DESCRIPTION - Gets Functions related to a language. + Gets Aliases related to a language. These are functions that either match a language's `.FilePattern` or start with a language name, followed by punctuation. #> if (-not $global:AllFunctionsAndAliases) { - $global:AllFunctionsAndAliases = $global:ExecutionContext.SessionState.InvokeCommand.GetCommand('*','Alias,Function',$true) + $global:AllFunctionsAndAliases = $global:ExecutionContext.SessionState.InvokeCommand.GetCommand('*','Alias',$true) +} +$FunctionsForThisLanguage = [Ordered]@{PSTypeName='Language.Functions'} +if ($this.FilePattern) { + foreach ($FunctionForLanguage in $global:AllFunctionsAndAliases -match $this.FilePattern) { + $FunctionsForThisLanguage["$FunctionForLanguage"] = $FunctionForLanguage + } +} +if ($this.LanguageName) { + foreach ($FunctionForLanguage in $global:AllFunctionsAndAliases -match "(?<=(?>^|[\p{P}-[\\]]))$([Regex]::Escape($this.LanguageName))[\p{P}-[\\]]") { + $FunctionsForThisLanguage["$FunctionForLanguage"] = $FunctionForLanguage + } +} + +$FunctionsForThisLanguage = [PSCustomObject]$FunctionsForThisLanguage +$FunctionsForThisLanguage.pstypenames.insert(0,"$($this.LanguageName).Functions") +$FunctionsForThisLanguage + + + + + + Filter + + <# +.SYNOPSIS + Gets Language Filters +.DESCRIPTION + Gets Filters related to a language. + + Filters are a special type of function that are used to filter or transform data. + + These are filters that either match a language's `.FilePattern` or start with a language name, followed by punctuation. +#> +if (-not $global:AllFunctionsAndAliases) { + $global:AllFunctionsAndAliases = $global:ExecutionContext.SessionState.InvokeCommand.GetCommand('*','Function',$true) } $FunctionsForThisLanguage = [Ordered]@{PSTypeName='Language.Functions'} if ($this.FilePattern) { foreach ($FunctionForLanguage in $global:AllFunctionsAndAliases -match $this.FilePattern) { + if ($FunctionForLanguage -isnot [Management.Automation.FilterInfo]) {continue } $FunctionsForThisLanguage["$FunctionForLanguage"] = $FunctionForLanguage } } if ($this.LanguageName) { foreach ($FunctionForLanguage in $global:AllFunctionsAndAliases -match "(?<=(?>^|[\p{P}-[\\]]))$([Regex]::Escape($this.LanguageName))[\p{P}-[\\]]") { + if ($FunctionForLanguage -isnot [Management.Automation.FilterInfo]) {continue } $FunctionsForThisLanguage["$FunctionForLanguage"] = $FunctionForLanguage } } @@ -2223,10 +2412,26 @@ $distinctCommands = @{} Language.Templates + + Controls + Control + Distinct Unique + + Inputs + Input + + + Layouts + Layout + + + Outputs + Output + All @@ -2277,6 +2482,19 @@ foreach ($uniqueCommand in $uniqueList) { $byInputType + + Control + + <# +.SYNOPSIS + Gets all control templates +.DESCRIPTION + Gets all templates that are controls. +#> +param() +$this.All -match 'Control' + + Count @@ -2293,6 +2511,45 @@ $byInputType }).Length + + Input + + <# +.SYNOPSIS + Gets all input templates +.DESCRIPTION + Gets all templates that are inputs. +#> +param() +$this.All -match 'Input' + + + + Layout + + <# +.SYNOPSIS + Gets all layout templates +.DESCRIPTION + Gets all templates that are layouts. +#> +param() +$this.All -match 'Layout' + + + + Output + + <# +.SYNOPSIS + Gets all output templates +.DESCRIPTION + Gets all templates that are output. +#> +param() +$this.All -match 'Output' + + Unique @@ -2330,75 +2587,400 @@ foreach ($psProperty in $theseProperties) { - System.Management.Automation.Language.ParamBlockAst + Namespace + + New + + - Header + Alias - # and extract the difference between the parent and the start of the block -$offsetDifference = $this.Extent.StartOffset - $this.Parent.Extent.StartOffset -# (this is where the header and help reside) -# try to strip off leading braces and whitespace -$this.Parent.Extent.ToString().Substring(0, $offsetDifference) -replace '^[\r\n\s]{0,}\{' + <# +.SYNOPSIS + Gets the aliases in the namespace. +.DESCRIPTION + Gets all the aliases in the namespace. +#> +if (-not $this.Pattern) { + return +} +if (-not $global:AllFunctionsOrFilters) { + $global:AllFunctionsOrFilters = $global:ExecutionContext.SessionState.InvokeCommand.GetCommands('*','Function,Filter',$true) +} +foreach ($cmdPattern in $this.Pattern) { + foreach ($matchingCommand in $global:AllFunctionsOrFilters -match $cmdPattern) { + if ($matchingCommand.CommandType -eq 'Filter') { + $matchingCommand + } + } +} + - ParameterNames + Cmdlet - @(foreach ($parameter in $this.Parameters) { - $parameter.ParameterNames -}) + <# +.SYNOPSIS + Gets the cmdlets in the namespace. +.DESCRIPTION + Gets all the cmdlets in the namespace. +#> +if (-not $this.Pattern) { return } +if (-not $global:AllCmdlets) { + $global:AllCmdlets = $global:ExecutionContext.SessionState.InvokeCommand.GetCommands('*','Cmdlet',$true) +} +$global:AllCmdlets -match $this.Pattern + - - - - System.Management.Automation.Language.ParameterAST - - - Aliases - ParameterNames - - - FriendlyName - DisplayName - - - ValueByName - ByPropertyName - - - ValueFromPipeline - FromPipeline - - - ValueFromPipelineByPropertyName - ByPropertyName - - - ValueFromRemaining - FromUnbound - - - ValueFromRemainingArguments - FromUnbound - - - VBN - ByPropertyName - - - VFP - FromPipeline - - - VFPBN - ByPropertyName - - Binding + Command - $isBindable = $false + <# +.SYNOPSIS + Gets the commands in the namespace. +.DESCRIPTION + Gets all the commands in the namespace. + + That is, all aliases, cmdlets, and functions. +#> +if (-not $this.Pattern) { return } + +@( + $this.Alias + $this.Cmdlet + $this.Function +) -ne $null + + + + Environment + + <# +.SYNOPSIS + Gets the environment variables in the namespace. +.DESCRIPTION + Gets all the environment variables in the namespace. + + These are all the environment variables where the name matches the pattern of the namespace. +#> +param() +foreach ($variable in Get-ChildItem env:) { + if ($variable.Name -match $this.Pattern) { + $variable + } +} + + + + Filter + + <# +.SYNOPSIS + Gets the filters in the namespace. +.DESCRIPTION + Gets all the filters in the namespace. +#> +param() +if (-not $this.Pattern) { + return +} +if (-not $global:AllFunctionsOrFilters) { + $global:AllFunctionsOrFilters = $global:ExecutionContext.SessionState.InvokeCommand.GetCommands('*','Function,Filter',$true) +} +foreach ($cmdPattern in $this.Pattern) { + foreach ($matchingCommand in $global:AllFunctionsOrFilters -match $cmdPattern) { + if ($matchingCommand.CommandType -eq 'Filter') { + $matchingCommand + } + } +} + + + + + Function + + if (-not $this.Pattern) { return } +if (-not $global:AllFunctionsOrFilters) { + $global:AllFunctionsOrFilters = $global:ExecutionContext.SessionState.InvokeCommand.GetCommands('*','Function,Filter',$true) +} +foreach ($cmdPattern in $this.Pattern) { + $global:AllFunctionsOrFilters -match $cmdPattern +} + + + + Pattern + + <# +.SYNOPSIS + Gets the namespace pattern. +.DESCRIPTION + Gets the pattern of a namespace object. +#> +# If we already have a cached .Pattern, return it. +if ($this.'.Pattern') { + return $this.'.Pattern' +} + +# Several types of objects can be used directly as patterns. +# These patterns can still be overridden by setting the .Pattern property. +# (in fact, that's also how they are set) + +# If we are actually a regex, set the .Pattern property and return. +if ($this -is [regex]) { + $this.Pattern = "$this" +} + +# If we are a URI, set the .Pattern property to the URI pattern. +if ($this -is [uri]) { + $this.Pattern = [regex]::Escape("$($this.DnsSafeHost)") +} + +if ($this.Name) { + $this.Pattern = "^$([Regex]::Escape($this.Name))" +} +else { + $this.Pattern = [regex]::Escape("$this") +} + +return $this.'.Pattern' + + + + <# +.SYNOPSIS + Sets the namespace pattern. +.DESCRIPTION + Sets the pattern of a namespace object. +#> +param($value) + +if (-not $Value) { return} + +if ($value -isnot [regex]) { + $value = [regex]::new($value,'IgnoreCase,IgnorePatternWhitespace','00:00:00.01') +} + +$this.psobject.properties.add([psnoteproperty]::new('.Pattern',$value), $true) + + + + PSType + + <# +.SYNOPSIS + Gets the pstypes in the namespace. +.DESCRIPTION + Gets all the pstypes in the namespace. + + This is, gets all extended type information in the namespace. +#> +if (-not $this.Pattern) { return } + +foreach ($typeData in Get-TypeData) { + if ($typeData.TypeName -match $this.Pattern) { + $typeData + } +} + + + + + Type + + <# +.SYNOPSIS + Gets the types in the namespace. +.DESCRIPTION + Gets all the types in the namespace. + + This is, gets all the compiled .NET types in the namespace. +#> +foreach ($assembly in [AppDomain]::CurrentDomain.GetAssemblies()) { + foreach ($typeInAssembly in + @(try { $assembly.GetTypes() } catch { continue }) + ) { + if ($typeInAssembly.FullName -match $this.Pattern) { + $typeInAssembly + } + } +} + + + + Variable + + <# +.SYNOPSIS + Gets the variables in the namespace. +.DESCRIPTION + Gets all the variables in the namespace. + + These are all the variables where the name matches the pattern of the namespace, + or the `.Value.pstypenames` match the pattern of the namespace. +#> + +foreach ($variable in Get-ChildItem variable:) { + if ($variable.Name -in 'this','_') { return } + if ($variable.Name -match $this.Pattern) { + $variable + } + elseif ($variable.value.pstypenames -match $this.Pattern) { + $variable + } +} + + + + + + System.Management.Automation.Language.ParamBlockAst + + + Header + + # and extract the difference between the parent and the start of the block +$offsetDifference = $this.Extent.StartOffset - $this.Parent.Extent.StartOffset +# (this is where the header and help reside) +# try to strip off leading braces and whitespace +$this.Parent.Extent.ToString().Substring(0, $offsetDifference) -replace '^[\r\n\s]{0,}\{' + + + + ParameterNames + + @(foreach ($parameter in $this.Parameters) { + $parameter.ParameterNames +}) + + + + + + System.Management.Automation.Language.ParameterAST + + + Aliases + ParameterNames + + + FriendlyName + DisplayName + + + ValueByName + ByPropertyName + + + ValueFromPipeline + FromPipeline + + + ValueFromPipelineByPropertyName + ByPropertyName + + + ValueFromRemaining + FromUnbound + + + ValueFromRemainingArguments + FromUnbound + + + VBN + ByPropertyName + + + VFP + FromPipeline + + + VFPBN + ByPropertyName + + + Binding + + $isBindable = $false foreach ($attr in $this.Attributes) { $reflectedType = $attr.TypeName.GetReflectionType() if ((-not $reflectedType) -or @@ -2827,13 +3409,23 @@ foreach ($prop in $this.psobject.properties) { <# .SYNOPSIS - Gets all Languages + Gets all items in the collection .DESCRIPTION - Gets all currently loaded language definitions in PipeScript. + Gets all items in the object. + + This would be all Technologies, Languages, or Interpreters. +.NOTES + Any noteproperties that are instance properties will be returned. +.EXAMPLE + $PsLanguages.All +.EXAMPLE + $PSInterpreters.All +.EXAMPLE + $PSTechs.All #> ,@(foreach ($psProperty in $this.PSObject.properties) { if ($psProperty -isnot [psnoteproperty]) { continue } - if ($psProperty.IsInstance -and $psProperty.Value.LanguageName) { + if ($psProperty.IsInstance) { $psProperty.Value } }) @@ -2841,13 +3433,23 @@ foreach ($prop in $this.psobject.properties) { <# .SYNOPSIS - Gets all Languages + Gets all items in the collection .DESCRIPTION - Gets all currently loaded language definitions in PipeScript. + Gets all items in the object. + + This would be all Technologies, Languages, or Interpreters. +.NOTES + Any noteproperties that are instance properties will be returned. +.EXAMPLE + $PsLanguages.All +.EXAMPLE + $PSInterpreters.All +.EXAMPLE + $PSTechs.All #> ,@(foreach ($psProperty in $this.PSObject.properties) { if ($psProperty -isnot [psnoteproperty]) { continue } - if ($psProperty.IsInstance -and $psProperty.Value.LanguageName) { + if ($psProperty.IsInstance) { $psProperty.Value } }) @@ -3151,17 +3753,51 @@ foreach ($prop in $this.psobject.properties) { <# .SYNOPSIS - Gets all Languages + Gets all items in the collection .DESCRIPTION - Gets all currently loaded language definitions in PipeScript. + Gets all items in the object. + + This would be all Technologies, Languages, or Interpreters. +.NOTES + Any noteproperties that are instance properties will be returned. +.EXAMPLE + $PsLanguages.All +.EXAMPLE + $PSInterpreters.All +.EXAMPLE + $PSTechs.All #> ,@(foreach ($psProperty in $this.PSObject.properties) { if ($psProperty -isnot [psnoteproperty]) { continue } - if ($psProperty.IsInstance -and $psProperty.Value.LanguageName) { + if ($psProperty.IsInstance) { $psProperty.Value } }) + + <# +.SYNOPSIS + Gets all items in the collection +.DESCRIPTION + Gets all items in the object. + + This would be all Technologies, Languages, or Interpreters. +.NOTES + Any noteproperties that are instance properties will be returned. +.EXAMPLE + $PsLanguages.All +.EXAMPLE + $PSInterpreters.All +.EXAMPLE + $PSTechs.All +#> +,@(foreach ($psProperty in $this.PSObject.properties) { + if ($psProperty -isnot [psnoteproperty]) { continue } + if ($psProperty.IsInstance) { + $psProperty.Value + } +}) + Count @@ -3183,6 +3819,24 @@ foreach ($prop in $this.psobject.properties) { } return $count + + <# +.SYNOPSIS + Gets the number of loaded languages. +.DESCRIPTION + Gets the number of language definitions loaded by PipeScript. +.EXAMPLE + $PSLanguage.Count +#> +$count= 0 +foreach ($prop in $this.psobject.properties) { + if ($prop -is [psscriptproperty]) { continue } + if ($prop.IsInstance -and $prop.Value.LanguageName) { + $count++ + } +} +return $count + Exclude @@ -3206,31 +3860,19 @@ return @( <# .SYNOPSIS - Sets language exclusions + Gets Languages Exclusions .DESCRIPTION Gets any excluded patterns and paths for languages in PipeScript. -.NOTES - If you provide a `[regex]`, it will set `.ExcludePattern`. - Otherwise, this will set `.ExcludePath`. + + If a command matches any of these patterns, it should not be interpreted. #> -$unrolledArgs = $args | . { process { $_ } } -$patterns = @() -$paths = @( -foreach ($arg in $unrolledArgs) { - if ($arg -is [Regex]) { - $patterns += $arg - } else { - "$arg" - } -}) +param() -if ($patterns) { - $this.ExcludePattern = $patterns -} -if ($paths) { - $this.ExcludePath = $paths -} +return @( + $this.ExcludePattern + $this.ExcludePath +) @@ -3255,20 +3897,19 @@ return $this.'.ExcludePath' <# .SYNOPSIS - Changes the Exclusion Paths + Gets Excluded Paths for all languages. .DESCRIPTION - Sets any excluded paths for interpreted languages in PipeScript. + Gets any excluded paths for interpreted languages in PipeScript. - If a command matches any of these patterns, it should not be interpreted. -.NOTES - Excluded paths will be processed as wildcards. + If a command is like any of these paths, it should not be interpreted. #> -$paths = @(foreach ($arg in $args | . { process { $_ }}) { - "$arg" -}) +param() -Add-Member -InputObject $this -Force -MemberType NoteProperty -Name '.ExcludePath' -Value $paths +if ($null -eq $this.'.ExcludePath'.Length) { + Add-Member -InputObject $this -Force -MemberType NoteProperty -Name '.ExcludePath' -Value @() +} +return $this.'.ExcludePath' @@ -3296,22 +3937,22 @@ return $this.'.ExcludePattern' <# .SYNOPSIS - Changes the Exclusion Patterns + Gets Language Exclusion Patterns .DESCRIPTION - Sets any excluded patterns for interpreted languages in PipeScript. + `$psLanguages.ExcludePattern` and `$psInterpreters.ExcludePattern` contain the patterns excluded from interpretation. - If a command matches any of these patterns, it should not be interpreted. -.NOTES - Under most circumstances, this should not be set. - - Setting this may cause Templates and Protocols to stop working (for interpretable languages) + If a command matches any of these patterns, it should not be interpreted. #> -$patterns = @(foreach ($arg in $args | . { process { $_ }}) { - [Regex]::new("$arg","IgnoreCase,IgnorePatternWhitespace","00:00:00.1") -}) +param() -Add-Member -InputObject $this -Force -MemberType NoteProperty -Name '.ExcludePattern' -Value $patterns +if (-not $this.'.ExcludePattern') { + Add-Member -InputObject $this -Force -MemberType NoteProperty -Name '.ExcludePattern' @( + [Regex]::new('\.ps1?\.','IgnoreCase') + [Regex]::new('://.') + ) +} +return $this.'.ExcludePattern' @@ -3333,6 +3974,23 @@ Add-Member -InputObject $this -Force -MemberType NoteProperty -Name '.ExcludePat } }) + + <# +.SYNOPSIS + Gets the loaded language names. +.DESCRIPTION + Gets the names of language definitions loaded by PipeScript. +.EXAMPLE + $PSLanguage.LanguageName +#> + +,@(foreach ($prop in $this.psobject.properties) { + if ($prop -is [psscriptproperty]) { continue } + if ($prop.IsInstance -and $prop.Value.LanguageName) { + $prop.Value.LanguageName + } +}) + README @@ -3340,7 +3998,7 @@ Add-Member -InputObject $this -Force -MemberType NoteProperty -Name '.ExcludePat A Language is defined a function named Language.NameOfLanguage. -PipeScript presently ships with 68 languages: +PipeScript presently ships with 70 languages: * ADA * Arduino @@ -3357,6 +4015,7 @@ PipeScript presently ships with 68 languages: * Crystal * CSharp * CSS +* Cuda * Dart * Docker * Eiffel @@ -3385,6 +4044,7 @@ PipeScript presently ships with 68 languages: * PowerShell * PowerShellData * PowerShellXML +* Pug * Python * R * Racket @@ -3412,200 +4072,1151 @@ PipeScript presently ships with 68 languages: - PipeScript.net + PipeScript.Module.Container - - PSNodeJob.cs - namespace PipeScript.Net -{ - using System; - using System.ComponentModel; - using System.Collections; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.IO; - using System.Text; - using System.Text.RegularExpressions; - using System.Timers; - using System.Threading; - using System.Management.Automation; - using System.Management.Automation.Runspaces; - using System.Net; - #if Windows - using Microsoft.Win32; - using System.Security.Principal; - #endif - using System.Web; - - - public class PSNodeJob : Job - { - static RunspacePool runspacePool; - PowerShell powerShellCommand; - int bufferSize = 262144; - uint poolSize = 3; - TimeSpan sessionTimeout = TimeSpan.FromMinutes(15); - Dictionary<string, string> MimeTypes = new Dictionary<string, string>(); - RunspacePool _PSNodePool; - ScriptBlock _PSNodeAction; - ScriptBlock _FullPSNodeAction; - PSNodeJob parentJob = null; - AuthenticationSchemes authenticationType = AuthenticationSchemes.Anonymous; - private string PSNodeScriptPreface = @" -<#ScriptPreface#> -"; - - static PSNodeJob() - { - InitialSessionState iss = InitialSessionState.CreateDefault(); - //#StartWindowsOnly - iss.ThreadOptions = PSThreadOptions.UseNewThread; - iss.ApartmentState = ApartmentState.STA; - //#EndWindowsOnly - runspacePool = RunspaceFactory.CreateRunspacePool(iss); - runspacePool.Open(); - AppDomain.CurrentDomain.ProcessExit += PooledJob_Exiting; - } - - RunspacePool PSNodePool - { - get - { - if (_PSNodePool == null || _PSNodePool.RunspacePoolStateInfo.State != RunspacePoolState.Opened) - { - InitialSessionState iss = InitialSessionState.CreateDefault(); - if (this.ImportModule != null) { - iss.ImportPSModule(this.ImportModule); - } - if (this.DeclareFunction != null) { - foreach (FunctionInfo df in this.DeclareFunction) { - iss.Commands.Add(new SessionStateFunctionEntry(df.Name, df.Definition)); - } - } - if (this.DeclareAlias != null) { - foreach (AliasInfo af in this.DeclareAlias) { - iss.Commands.Add(new SessionStateAliasEntry(af.Name, af.Definition)); - } - } - - if (this.ImportTypeFile != null) { - foreach (string typeFile in this.ImportTypeFile) { - iss.Types.Add(new SessionStateTypeEntry(typeFile)); - } - } - - if (this.ImportFormatFile != null) { - foreach (string formatFile in this.ImportFormatFile) { - iss.Formats.Add(new SessionStateFormatEntry(formatFile)); - } - } - - _PSNodePool = RunspaceFactory.CreateRunspacePool(iss); - //#StartWindowsOnly - _PSNodePool.ThreadOptions = PSThreadOptions.UseNewThread; - _PSNodePool.ApartmentState = System.Threading.ApartmentState.STA; - //#EndWindowsOnly - _PSNodePool.SetMaxRunspaces((int)PoolSize); - _PSNodePool.Open(); - } - return _PSNodePool; - } - } + + Build + BuildContainer + + + BuildContainer + + + + DockerFileContent + + <# +.SYNOPSIS + Gets the content of a module's Dockerfile +.DESCRIPTION + A module may define a .Container(s) section in its manifest. + + This section may be docker file content. + + It can also be a hashtable containing a .DockerFile property + (this can be the module-relative path to the .Dockerfile, or it's contents) - public PSNodeJob(string name, string command, ScriptBlock scriptBlock) - : base(command, name) - {} + If neither of these is present, a default docker file will be generated for this module. +#> +if ($this -is [string]) { + return $this +} - private PSNodeJob(ScriptBlock scriptBlock) - {} - - - public PSNodeJob(string name, string command, ScriptBlock scriptBlock, Hashtable parameters) - : base(command, name) - {} +if ($this.Dockerfile) { + if (@($this.Dockerfile -split '[\r\n]+' -ne '').Length -gt 1) { + return $this.Dockerfile + } - public PSNodeJob(string name, string command, ScriptBlock scriptBlock, Hashtable parameters, PSObject[] argumentList) - : base(command, name) - {} + $dockerFilePath = (Join-Path ($this.Module | Split-Path) $this.Dockerfile) + if (Test-Path $dockerFilePath) { + return (Get-Content -Raw $dockerFilePath) + } +} - private PSNodeJob(string name, string command, ScriptBlock scriptBlock, Hashtable parameters, PSObject[] argumentList, bool isChildJob) - : base(command, name) - { - if (! isChildJob) { - PSNodeJob childJob = new PSNodeJob(name, command, scriptBlock, parameters, argumentList, true); - childJob.StateChanged += new EventHandler<JobStateEventArgs>(childJob_StateChanged); - this.ChildJobs.Add(childJob); +if ($this.Module) { + $theModule = $this.Module + $theModuleDefaultDockerPath = Join-Path ($theModule | Split-Path) 'Dockerfile' + if (Test-Path $theModuleDefaultDockerPath) { + return (Get-Content -Raw $theModuleDefaultDockerPath) + } + return @( + Template.Docker.From -BaseImage "mcr.microsoft.com/powershell:latest" + Template.Docker.SetVariable -Name PSModulePath -Value ./Modules + Template.Docker.CopyItem -Source "." -Destination "/Modules/$($theModule.Name)" + if ($theModule.RequiredModules) { + foreach ($requiredModule in $theModule.RequiredModules) { + Template.Docker.InstallModule -ModuleName $requiredModule.Name -ModuleVersion $requiredModule.Version } } + + Template.Docker.LabelModule -Module $theModule + Template.Docker.Label -label "org.opencontainers.image.created" -Value "$([DateTime]::Now.ToString('o'))" - void childJob_StateChanged(object sender, JobStateEventArgs e) - { - this.SetJobState(e.JobStateInfo.State); + if ($this.EntryPoint) { + Template.Docker.SetEntryPoint -EntryPoint $this.EntryPoint + } else { + $importSequence = @( + "PipeScript" + if ($theModule.RequiredModules) { + foreach ($requiredModule in $theModule.RequiredModules) { + "'$($requiredModule.Name)'" + } + } + "'$($theModule.Name)'" + ) -join ', ' + $CommandLine = @( + "Import-Module $importSequence -Force -PassThru | Out-Host" + ) -join ';' + Template.Docker.SetEntryPoint -EntryPoint "pwsh -noexit -Command ;" } - /// <summary> - /// Synchronizes Job State with Background Runspace - /// </summary> - /// <param name="sender"></param> + ) -join [Environment]::NewLine +} + + + + + + + PipeScript.Module.Route + + + ForUri + ForURL + + + ForURL + + + + + + PipeScript.Module.Service + + + GetRoute + GetServiceRoute + + + GetRoutes + GetServiceRoute + + + GetServiceCommands + GetServiceCommand + + + GetServiceParameters + GetServiceParameter + + + GetServiceRoutes + GetServiceRoute + + + GetServiceCommand + + + + GetServiceParameter + + + + GetServiceRoute + + + + + + PipeScript.Module.Services + + + Commands + Command + + + HasServices + HasService + + + Routes + Route + + + Types + Type + + + Variables + Variable + + + Command + + <# +.SYNOPSIS + Gets the commands a module serves. +.DESCRIPTION + Gets the commands served by the module. +#> +param() +$cmdPatternList = @(foreach ($serviceInfo in $this.List) { + if ($serviceInfo.Command) { + [Regex]::Escape($serviceInfo.Command) + } elseif ($serviceInfo.CommandPattern) { + $serviceInfo.CommandPattern + } +}) +$CmdPatternRegex = [Regex]::new("(?>$($cmdPatternList -join '|'))", 'IgnoreCase,IgnorePatternWhitespace','00:00:00.1') + +$ExecutionContext.SessionState.InvokeCommand.GetCommands('*','Function,Cmdlet,Alias', $true) -match $CmdPatternRegex + + + + HasService + + $this.List.Length -as [bool] + + + + + Route + + <# +.SYNOPSIS + Gets all routes a module serves +.DESCRIPTION + Gets all routes served by any of the module services. + + (additional routes can also be declared in the module manifest) +#> +param() + +$serviceRoutes = [Ordered]@{PSTypeName='PipeScript.Module.Route'} + +foreach ($serviceInfo in $this.List) { + if ($serviceInfo.GetServiceRoute) { + $serviceRouteInfo = $serviceInfo.GetServiceRoute() + if ($serviceRouteInfo) { + $serviceRoutes[$serviceRouteInfo] = $serviceInfo + } + } +} + +return [PSCustomObject]$serviceRoutes + + + + Type + + <# +.SYNOPSIS + Gets the types a module serves +.DESCRIPTION + Gets the types served by the module. +#> + +@(foreach ($serviceInfo in $this.List) { + if ($serviceInfo.Type) { + $serviceInfo.Type -as [type] + } +}) + + + + + + Variable + + $variablePatternList = @(foreach ($serviceInfo in $this.List) { + if ($serviceInfo.Variable) { + [Regex]::Escape($serviceInfo.Variable) + } elseif ($serviceInfo.VariablePattern) { + $serviceInfo.VariablePattern + } +}) +$variablePatternRegex = [Regex]::new("(?>$($variablePatternList -join '|'))", 'IgnoreCase,IgnorePatternWhitespace','00:00:00.1') + +foreach ($var in Get-Variable) { + if ($var.Name -match $variablePatternRegex) { + $var + } +} + + + + README + ## Services Serve. + +Any module can have any number of services. + +These serve requests to the module. + +Services can be defined in the `.PrivateData` or `.PrivateData.PSData` sections of a module. + + + + + + + PipeScript.Module.Topic + + + FindCodeBlocks + + + + FindHyperlinks + + + + FindReferences + + + + ToHTML + + + + Aliases + + @( + ($this.Name -replace '[_-]', ' ' -replace '\.md$') + if ($this.Metadata.Alias) { + $this.Metadata.Alias -replace '[_-]', ' ' + } + if ($this.Metadata.Aliases) { + $this.Metadata.Aliases -replace '[_-]', ' ' + } +) + + + + Content + + <# +.SYNOPSIS + Gets a topic's content. +.DESCRIPTION + Gets a topic's content. + + The content is cached for performance reasons. +.EXAMPLE + $PipeScript.Topics.AllTopics.Content # This will be a bunch of markdown content +#> +param() +if (-not $this.'.CachedContent') { + $this | Add-Member NoteProperty '.CachedContent' ([IO.File]::ReadAllText($this.Fullname)) -Force +} +$this.'.CachedContent' + + + + + Metadata + + if ($this.psobject.properties['_CachedMetadata']) { + return $this._CachedMetadata +} + + +$topicData = :FindingMetadata foreach ($potentialExtension in '.psd1','.json','.csv') { + $potentialFileName = $this.Name + $potentialExtension + $potentialFullName = $this.Fullname + $potentialExtension + if (Test-Path $potentialFullName) { + switch ($potentialExtension) { + '.psd1' { + try { + $localizedData = Import-LocalizedData -BaseDirectory $this.Directory.Fullname -FileName $psd1FileName + $importedData = [Ordered]@{} + if ($localizedData) { + $localizedData.GetEnumerator() | + Sort-Object { $_.Key.ToLower() }| + & { process { + $importedData[$_.Key.ToLower()] = $_.Value + } } + } + $importedData + break FindingMetadata + } catch { + Write-Warning "$_" + @{} + } + break + } + '.json' { + Get-Content $potentialFullName -Raw | ConvertFrom-Json + break FindingMetadata + } + '.csv' { + Import-Csv $potentialFullName + break FindingMetadata + } + } + } +} + +if (-not $topicData) { $topicData = [Ordered]@{} } +if ($topicData -isnot [Collections.IDictionary]) { + $topicDataDictionary = [Ordered]@{} + foreach ($property in $topicData.psobject.properties) { + $topicDataDictionary[$property.Name] = $property.Value + } + $topicData = $topicDataDictionary +} + +$this | Add-Member NoteProperty _CachedMetadata $topicData -Force +return $topicData + + + + TopicName + + if ($this.Name -match '^(?>README|INDEX|HOME|DEFAULT)') { + $this.Directory.Name -replace '[_-]', ' ' +} else { + $this.Name -replace '[_-]', ' ' -replace '\.md$' +} + + + + + + + PipeScript.Module.Topics + + + Get + + + + TopicCount + + $this.AllTopics.Length + + + + + + + PipeScript.Module.Website + + + GetDefaultLayout + + + + UseLayout + + + + + + PipeScript.net + + + PSNodeJob.cs + namespace PipeScript.Net +{ + using System; + using System.ComponentModel; + using System.Collections; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.IO; + using System.Text; + using System.Text.RegularExpressions; + using System.Timers; + using System.Threading; + using System.Management.Automation; + using System.Management.Automation.Runspaces; + using System.Net; + #if Windows + using Microsoft.Win32; + using System.Security.Principal; + #endif + using System.Web; + + + public class PSNodeJob : Job + { + + /// <summary> + /// The runspace pool used by all PSNodeJobs + /// </summary> + static RunspacePool runspacePool; + /// <summary> + /// The PowerShell command that is being run by this job + /// </summary> + PowerShell powerShellCommand; + /// <summary> + /// The buffer size used by the job. This is the maximum size of a the buffer for requests. + /// </summary> + int bufferSize = 262144; + /// <summary> + /// The pool size of the runspace pool. This is the maximum number of runspaces that can be run at once. + /// </summary> + uint poolSize = 3; + + + /// <summary> + /// The maximum age of cached file results. This is the maximum amount of time a file should be cached by the client. + /// </summary> + uint maxAge = 604800; + + + /// <summary> + /// The lifespan. This is the amount of time the job will be kept alive. + /// </summary> + TimeSpan lifeSpan = TimeSpan.MinValue; + + /// <summary> + /// The session timeout for the job. This is the amount of time a session will be kept alive. + /// </summary> + TimeSpan sessionTimeout = TimeSpan.FromMinutes(15); + /// <summary> + /// The MIME types that are used by the job. This is a dictionary of file extensions to MIME types. + /// </summary> + Dictionary<string, string> MimeTypes = new Dictionary<string, string>(); + /// <summary> + /// The inner runspace pool that is actively serving results from a job. + /// </summary> + RunspacePool _PSNodePool; + /// <summary> + /// The action that should be run on every request to the node. + /// </summary> + ScriptBlock _PSNodeAction; + /// <summary> + /// The full action that should be run on every request to the node. This includes the preface. + /// </summary> + ScriptBlock _FullPSNodeAction; + /// <summary> + /// The parent job of this job. This is used to determine if the job is a child job (and is required for jobs to work properly). + /// </summary> + PSNodeJob parentJob = null; + /// <summary> + /// The authentication type to use for the job. + /// </summary> + AuthenticationSchemes authenticationType = AuthenticationSchemes.Anonymous; + /// <summary> + /// The preface that should be run before this action. + /// This will be replaced by the actual preface when the type is created. + /// </summary> + private string PSNodeScriptPreface = @" +<#ScriptPreface#> +"; + + + /// <summary> + /// When any PSNodeJob is created, the runspace pool is created. + /// </summary> + static PSNodeJob() + { + InitialSessionState iss = InitialSessionState.CreateDefault(); + //#StartWindowsOnly + iss.ThreadOptions = PSThreadOptions.UseNewThread; + iss.ApartmentState = ApartmentState.STA; + //#EndWindowsOnly + runspacePool = RunspaceFactory.CreateRunspacePool(iss); + runspacePool.Open(); + AppDomain.CurrentDomain.ProcessExit += PooledJob_Exiting; + } + + /// <summary> + /// The runspace pool for this job. This is a property so that it can be created when it is needed. + /// </summary> + RunspacePool PSNodePool + { + get + { + if (_PSNodePool == null || _PSNodePool.RunspacePoolStateInfo.State != RunspacePoolState.Opened) + { + InitialSessionState iss = InitialSessionState.CreateDefault(); + if (this.ImportModule != null) { + iss.ImportPSModule(this.ImportModule); + } + if (this.DeclareFunction != null) { + foreach (FunctionInfo df in this.DeclareFunction) { + iss.Commands.Add(new SessionStateFunctionEntry(df.Name, df.Definition)); + } + } + if (this.DeclareAlias != null) { + foreach (AliasInfo af in this.DeclareAlias) { + iss.Commands.Add(new SessionStateAliasEntry(af.Name, af.Definition)); + } + } + + if (this.ImportTypeFile != null) { + foreach (string typeFile in this.ImportTypeFile) { + iss.Types.Add(new SessionStateTypeEntry(typeFile)); + } + } + + if (this.ImportFormatFile != null) { + foreach (string formatFile in this.ImportFormatFile) { + iss.Formats.Add(new SessionStateFormatEntry(formatFile)); + } + } + + _PSNodePool = RunspaceFactory.CreateRunspacePool(iss); + //#StartWindowsOnly + _PSNodePool.ThreadOptions = PSThreadOptions.UseNewThread; + _PSNodePool.ApartmentState = System.Threading.ApartmentState.STA; + //#EndWindowsOnly + _PSNodePool.SetMaxRunspaces((int)PoolSize); + _PSNodePool.Open(); + } + return _PSNodePool; + } + } + + /// <summary> + /// The http listener that is used by this job. + /// </summary> + public HttpListener Listener { get; set; } + /// <summary> + /// If the directory should be browsable. This can divulge information about the server. + /// It will only be used if the root path is set. + /// </summary> + public bool AllowBrowseDirectory { get; set; } + /// <summary> + /// Allow execution of scripts within the root path. + /// This provides an easy way to turn scripts into services, but can be a security risk if any scripts within the directory are not secure. + /// </summary> + public bool AllowScriptExecution { get; set; } + /// <summary> + /// The CORS header to use for the job. + /// </summary> + public string CORS { get; set;} + + /// <summary> + /// The authentication type to use for the job. + /// </summary> + public AuthenticationSchemes AuthenticationType { + get { return authenticationType; } + set { authenticationType = value; } + } + + /// <summary> + /// The functions that should be declared in the runspace pool. + /// </summary> + public FunctionInfo[] DeclareFunction { get; set; } + /// <summary> + /// The aliases that should be declared in the runspace pool. + /// </summary> + public AliasInfo[] DeclareAlias { get; set; } + /// <summary> + /// The type files that should be imported into the runspace pool. + /// </summary> + public string[] ImportTypeFile { get; set; } + /// <summary> + /// The format files that should be imported into the runspace pool. + /// </summary> + public string[] ImportFormatFile { get; set; } + /// <summary> + /// The blacklist of files that should not be served by the job. + /// </summary> + public string[] FileBlacklist { get; set; } + /// <summary> + /// The buffer size that should be used by the job. + /// </summary> + public int BufferSize { + get { return bufferSize; } + set { bufferSize = value; } + } + + public uint MaxAge { + get { return maxAge; } + set { maxAge = value; } + } + + public TimeSpan LifeSpan { + get { return lifeSpan; } + set { + if (this.JobStateInfo.State == JobState.NotStarted) { + lifeSpan = value; + } + } + } + /// <summary> + /// The modules that should be imported into the runspace pool. + /// </summary> + public string[] ImportModule { get; set; } + /// <summary> + /// The location that the listener should listen on. + /// </summary> + public string[] ListenerLocation { get; set; } + + /// <summary> + /// The pool size that should be used by the job. + /// </summary> + public uint PoolSize { + get { + return poolSize; + } set { + poolSize = value; + } + } + + /// <summary> + /// The root path that should be used by the job. + /// By providing a root path, the job will serve files or scripts from that path. + /// </summary> + public string RootPath { get; set; } + + /// <summary> + /// The session timeout that should be used by the job. + /// </summary> + public TimeSpan SessionTimeout { get { return sessionTimeout; } set { sessionTimeout = value; } } + + + /// <summary> + /// The action that should be run on every request to the node. + /// </summary> + public ScriptBlock PSNodeAction { + get {return _PSNodeAction;} + + set { + _PSNodeAction = value; + _FullPSNodeAction = ScriptBlock.Create(this.PSNodeScriptPreface + _PSNodeAction.ToString()); + } + } + + /// <summary> + /// The Async Callback for the job. + /// </summary> + + public AsyncCallback Callback { + get {return new AsyncCallback(this.ListenerCallback);} + } + + /// <summary> + /// When the job is exiting, the runspace pool is closed. + /// </summary> + /// <param name="sender"></param> + /// <param name="e"></param> + static void PooledJob_Exiting(object sender, EventArgs e) { + runspacePool.Close(); + runspacePool.Dispose(); + runspacePool = null; + } + + public PSNodeJob(string name, string command, ScriptBlock scriptBlock) + : base(command, name) {} + + private PSNodeJob(ScriptBlock scriptBlock) {} + + + public PSNodeJob(string name, string command, ScriptBlock scriptBlock, Hashtable parameters) + : base(command, name) {} + + public PSNodeJob(string name, string command, ScriptBlock scriptBlock, Hashtable parameters, PSObject[] argumentList) + : base(command, name) {} + + private PSNodeJob(string name, string command, ScriptBlock scriptBlock, Hashtable parameters, PSObject[] argumentList, bool isChildJob) + : base(command, name) { + if (! isChildJob) { + PSNodeJob childJob = new PSNodeJob(name, command, scriptBlock, parameters, argumentList, true); + childJob.StateChanged += new EventHandler<JobStateEventArgs>(childJob_StateChanged); + this.ChildJobs.Add(childJob); + } + } + + + void childJob_StateChanged(object sender, JobStateEventArgs e) + { + this.SetJobState(e.JobStateInfo.State); + } + + /// <summary> + /// Synchronizes Job State with Background Runspace + /// </summary> + /// <param name="sender"></param> /// <param name="e"></param> void powerShellCommand_InvocationStateChanged(object sender, PSInvocationStateChangedEventArgs e) { @@ -3624,7 +5235,20 @@ PipeScript presently ships with 68 languages: } } - public void ServeFile(string fullPath, HttpListenerRequest request, HttpListenerResponse response) { + /// <summary> + /// Serves a file from the file system. + /// This is a helper method that is used by the job to serve files. + /// If Root has been set, this will serve files from the root path. + /// </summary> + /// <param name="fullPath">The absolute path to the file</param> + /// <param name="request">The request</param> + /// <param name="response">The response</param> + + public void ServeFile( + string fullPath, + HttpListenerRequest request, + HttpListenerResponse response + ) { if (File.Exists(fullPath)) { FileInfo fileInfo = new FileInfo(fullPath); if (FileBlacklist != null ){ @@ -3645,6 +5269,9 @@ PipeScript presently ships with 68 languages: return; } response.Headers["Accept-Ranges"] = "bytes"; + if (this.maxAge > 0) { + response.Headers["Cache-Control"] = "max-age=" + this.maxAge.ToString(); + } long start = 0; long end = fileInfo.Length; if (!String.IsNullOrEmpty(request.Headers["Range"])) { @@ -3869,8 +5496,10 @@ PipeScript presently ships with 68 languages: this.ServeScript(_FullPSNodeAction.ToString(), context); } catch (Exception e) - { - this.Error.Add(new ErrorRecord(e, e.Message, ErrorCategory.NotSpecified, e)); + { + if (e.HResult != -2146232798) { + this.Error.Add(new ErrorRecord(e, e.Message, ErrorCategory.NotSpecified, e)); + } } } @@ -3910,13 +5539,47 @@ PipeScript presently ships with 68 languages: MimeTypes[extension.ToString().ToLower()]= ctName; } } - #endif - if (! MimeTypes.ContainsKey(".js")) { - MimeTypes[".css"] = "text/javascript"; - } - if (! MimeTypes.ContainsKey(".css")) { - MimeTypes[".css"] = "text/css"; - } + #endif + } + + if (this.LifeSpan.TotalMilliseconds > 0) { + System.Timers.Timer lifeSpanTimer = new System.Timers.Timer(); + lifeSpanTimer.Interval = this.LifeSpan.TotalMilliseconds; + lifeSpanTimer.Elapsed += (sender, e) => { + this.StopJob(); + }; + lifeSpanTimer.AutoReset = false; + lifeSpanTimer.Start(); + } + + // Defaulting several MIME types: + // See https://developer.mozilla.org/en-US/docs/Learn/Server-side/Configuring_server_MIME_types + if (! MimeTypes.ContainsKey(".md")) { + MimeTypes[".md"] = "text/markdown"; + } + if (! MimeTypes.ContainsKey(".html")) { + MimeTypes[".html"] = "text/html"; + } + if (! MimeTypes.ContainsKey(".htm")) { + MimeTypes[".htm"] = "text/html"; + } + if (! MimeTypes.ContainsKey(".js")) { + MimeTypes[".js"] = "text/javascript"; + } + if (! MimeTypes.ContainsKey(".css")) { + MimeTypes[".css"] = "text/css"; + } + if (! MimeTypes.ContainsKey(".svg")) { + MimeTypes[".svg"] = "image/svg+xml"; + } + if (! MimeTypes.ContainsKey(".png")) { + MimeTypes[".png"] = "image/png"; + } + if (! MimeTypes.ContainsKey(".jpg")) { + MimeTypes[".jpg"] = "image/jpeg"; + } + if (! MimeTypes.ContainsKey(".jpeg")) { + MimeTypes[".jpg"] = "image/jpeg"; } Listener = new HttpListener(); @@ -3929,7 +5592,7 @@ PipeScript presently ships with 68 languages: int max = runspacePool.GetMaxRunspaces(); runspacePool.SetMaxRunspaces(max + 1); powerShellCommand = PowerShell.Create(); - powerShellCommand.RunspacePool = runspacePool; + powerShellCommand.RunspacePool = PSNodePool; powerShellCommand.Streams.Error = this.Error; powerShellCommand.Streams.Warning = this.Warning; @@ -4144,228 +5807,578 @@ param($PSNodeJob, $listener) set; } - /// <summary> - /// Determines if the script uses a new scope to execute. - /// </summary> - public bool UseNewScope { - get; - set; - } + /// <summary> + /// Determines if the script uses a new scope to execute. + /// </summary> + public bool UseNewScope { + get; + set; + } + + /// <summary> + /// Transforms arguments. + /// If the attribute is disabled, no transformation will take place. + /// If the attribute has a .TransformScript, this argument will be transformed by invoking the script. + /// </summary> + /// <param name="engineIntrinsics"></param> + /// <param name="inputData"></param> + /// <returns></returns> + public override object Transform(EngineIntrinsics engineIntrinsics, object inputData) { + // If disabled, do nothing + if (this.Disabled) { return inputData; } + // If there is no transform script, return the input data. + if (this.TransformScript == null) { return inputData; } + + // By getting the value of InvocationInfo now, we know what command is trying to perform the transform. + InvocationInfo myInvocation = engineIntrinsics.SessionState.PSVariable.Get("MyInvocation").Value as InvocationInfo; + + engineIntrinsics.SessionState.PSVariable.Set("thisTransform", this); + engineIntrinsics.SessionState.PSVariable.Set("_", inputData); + + // The transform script will be passed the following arguments: + // The input data, invocation info, command, and this attribute. + object[] arguments = new object[] { inputData, myInvocation, myInvocation.MyCommand, this }; + object[] transformInput = new object[] inputData; + + // Invoke it in place. + // ($_ will be the current transformed value) + Collection<PSObject> invokeResults = engineIntrinsics.SessionState.InvokeCommand.InvokeScript(this.UseNewScope, this.TransformScript, transformInput, arguments); + + if (invokeResults != null && invokeResults.Count == 1) { + return invokeResults[0]; + } else if (invokeResults != null) { + return invokeResults; + } else { + return inputData; + } + } + } +} + + + + + PipeScript.Parsers + + + ForCommand + + + + All + + <# +.SYNOPSIS + Gets all Parsers +.DESCRIPTION + Gets all parsers loaded in PipeScript. +#> +,@(foreach ($psProperty in $this.PSObject.properties) { + if ($psProperty -isnot [psnoteproperty]) { continue } + if ($psProperty.Value -isnot [Management.Automation.CommandInfo]) { continue } + $psProperty.Value +}) + + + + Count + + <# +.SYNOPSIS + Gets the number of loaded parsers. +.DESCRIPTION + Gets the number of parsers loaded by PipeScript. +.EXAMPLE + $PSParser.Count +#> +$count= 0 +foreach ($prop in $this.psobject.properties) { + if ($prop -is [psscriptproperty]) { continue } + if ($prop.Value -is [Management.Automation.CommandInfo]) { + $count++ + } +} +return $count + + + + + + PipeScript.Sentence + + + Argument + Arguments + + + ArgumentList + Arguments + + + Parameter + Parameters + + + GetParameterAlias + + + + Run + + - PipeScript.Parsers + PipeScript.Techs + + ExcludePaths + ExcludePaths + + + ExcludePatterns + ExcludePatterns + + + Excludes + Exclude + + + LanguageNames + LanguageName + - ForCommand + ForFile + + + All + + <# +.SYNOPSIS + Gets all items in the collection +.DESCRIPTION + Gets all items in the object. + + This would be all Technologies, Languages, or Interpreters. +.NOTES + Any noteproperties that are instance properties will be returned. +.EXAMPLE + $PsLanguages.All +.EXAMPLE + $PSInterpreters.All +.EXAMPLE + $PSTechs.All +#> +,@(foreach ($psProperty in $this.PSObject.properties) { + if ($psProperty -isnot [psnoteproperty]) { continue } + if ($psProperty.IsInstance) { + $psProperty.Value + } +}) + + + + Count + + <# +.SYNOPSIS + Gets the number of loaded languages. +.DESCRIPTION + Gets the number of language definitions loaded by PipeScript. +.EXAMPLE + $PSLanguage.Count +#> +$count= 0 +foreach ($prop in $this.psobject.properties) { + if ($prop -is [psscriptproperty]) { continue } + if ($prop.IsInstance -and $prop.Value.LanguageName) { + $count++ + } +} +return $count + + + + Exclude + + <# +.SYNOPSIS + Gets Languages Exclusions +.DESCRIPTION + Gets any excluded patterns and paths for languages in PipeScript. + + If a command matches any of these patterns, it should not be interpreted. +#> +param() + + +return @( + $this.ExcludePattern + $this.ExcludePath +) + + + <# +.SYNOPSIS + Sets language exclusions +.DESCRIPTION + Gets any excluded patterns and paths for languages in PipeScript. +.NOTES + If you provide a `[regex]`, it will set `.ExcludePattern`. + Otherwise, this will set `.ExcludePath`. +#> +$unrolledArgs = $args | . { process { $_ } } +$patterns = @() +$paths = @( +foreach ($arg in $unrolledArgs) { + if ($arg -is [Regex]) { + $patterns += $arg + } else { + "$arg" } +}) + +if ($patterns) { + $this.ExcludePattern = $patterns } - - +if ($paths) { + $this.ExcludePath = $paths +} + + + - All + ExcludePath <# .SYNOPSIS - Gets all Parsers + Gets Excluded Paths for all languages. .DESCRIPTION - Gets all parsers loaded in PipeScript. + Gets any excluded paths for interpreted languages in PipeScript. + + If a command is like any of these paths, it should not be interpreted. #> -,@(foreach ($psProperty in $this.PSObject.properties) { - if ($psProperty -isnot [psnoteproperty]) { continue } - if ($psProperty.Value -isnot [Management.Automation.CommandInfo]) { continue } - $psProperty.Value -}) +param() + +if ($null -eq $this.'.ExcludePath'.Length) { + Add-Member -InputObject $this -Force -MemberType NoteProperty -Name '.ExcludePath' -Value @() +} + +return $this.'.ExcludePath' + + <# +.SYNOPSIS + Changes the Exclusion Paths +.DESCRIPTION + Sets any excluded paths for interpreted languages in PipeScript. + + If a command matches any of these patterns, it should not be interpreted. +.NOTES + Excluded paths will be processed as wildcards. +#> +$paths = @(foreach ($arg in $args | . { process { $_ }}) { + "$arg" +}) + +Add-Member -InputObject $this -Force -MemberType NoteProperty -Name '.ExcludePath' -Value $paths + + - Count + ExcludePattern <# .SYNOPSIS - Gets the number of loaded parsers. + Gets Language Exclusion Patterns .DESCRIPTION - Gets the number of parsers loaded by PipeScript. -.EXAMPLE - $PSParser.Count + `$psLanguages.ExcludePattern` and `$psInterpreters.ExcludePattern` contain the patterns excluded from interpretation. + + If a command matches any of these patterns, it should not be interpreted. #> -$count= 0 -foreach ($prop in $this.psobject.properties) { - if ($prop -is [psscriptproperty]) { continue } - if ($prop.Value -is [Management.Automation.CommandInfo]) { - $count++ - } +param() + +if (-not $this.'.ExcludePattern') { + Add-Member -InputObject $this -Force -MemberType NoteProperty -Name '.ExcludePattern' @( + [Regex]::new('\.ps1?\.','IgnoreCase') + [Regex]::new('://.') + ) } -return $count + +return $this.'.ExcludePattern' - - - - - PipeScript.Sentence - - - Argument - Arguments - - - ArgumentList - Arguments - - - Parameter - Parameters - - - GetParameterAlias - - - - Run - - + + @@ -4548,6 +6561,10 @@ else { Assets Asset + + Containers + Container + Directories Folders @@ -4564,6 +6581,10 @@ else { Domains Server + + EntryPoints + EntryPoint + Exports Export @@ -4596,6 +6617,42 @@ else { Servers Server + + Services + Service + + + Sites + Site + + + Topics + Topic + + + WebCommand + Service + + + WebCommands + Service + + + WebService + Service + + + WebServices + Service + + + Website + Site + + + Websites + Site + ExtensionsForName + + FindMetadata + + Folder