$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 '"
+ }
+ $label
+ }
+ ""
+ ) -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 '"
+ }
+ $label
+ }
+ ""
+ ) -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,}"
+ }
+ 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,}"
+ }
+ 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$Name>"
+ }
+ }
+}
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$Name>"
+ }
+ }
+
+}
+
+
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) {
+ ""
+ }
+ }
+}
\ 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) {
+ ""
+ }
+ }
+
+}
+
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