Repository: mwouts/jupytext
Pull request #354 (2019-10-18 22:09:30)

Powershell
Added support for Powershell scripts and notebooks


Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index 6f8ffb8f..a5e63ba9 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -15,6 +15,7 @@ Release History
 - ``jupytext notebook.py --to ipynb`` updates the timestamp of ``notebook.py`` so that the paired notebook still works in Jupyter (#335, #254)
 - The Jupyter Notebook extension for Jupytext is compatible with Jupyter Notebook 6.0 (#346)
 - Added support for Rust/Evxcr, by Jonas Bushart (#351)
+- Added support for PowerShell (#349)
 
 **BugFixes**
 
diff --git a/README.md b/README.md
index bb326418..ef2e7ee5 100755
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ It can also convert these documents **into** Jupyter
 Notebooks, allowing you to synchronize content in both
 directions.
 
-The languages that are currently supported by Jupytext are: Julia, Python, R, Bash, Scheme, Clojure, Matlab, Octave, C++, q/kdb+, IDL, TypeScript, Javascript and Scala. Extending Jupytext to more languages should be easy - read more at [CONTRIBUTING.md](https://github.com/mwouts/jupytext/blob/master/CONTRIBUTING.md). In addition, jupytext users can choose between two formats for notebooks as scripts:
+The languages that are currently supported by Jupytext are: Julia, Python, R, Bash, Scheme, Clojure, Matlab, Octave, C++, q/kdb+, IDL, TypeScript, Javascript, Scala, Rust/Evxcr and PowerShell. Extending Jupytext to more languages should be easy - read more at [CONTRIBUTING.md](https://github.com/mwouts/jupytext/blob/master/CONTRIBUTING.md). In addition, jupytext users can choose between two formats for notebooks as scripts:
 - The `percent` format, compatible with several IDEs, including Spyder, Hydrogen, VScode and PyCharm. In that format, cells are delimited with a commented `%%`.
 - The `light` format, designed for this project. Use that format to open standard scripts as notebooks, or to save notebooks as scripts with few cell markers - none when possible.
 
diff --git a/jupytext/languages.py b/jupytext/languages.py
index 06be0c60..371bdfff 100644
--- a/jupytext/languages.py
+++ b/jupytext/languages.py
@@ -16,6 +16,7 @@ _SCRIPT_EXTENSIONS = {'.py': {'language': 'python', 'comment': '#'},
                       '.clj': {'language': 'clojure', 'comment': ';;'},
                       '.scm': {'language': 'scheme', 'comment': ';;'},
                       '.sh': {'language': 'bash', 'comment': '#'},
+                      '.ps1': {'language': 'powershell', 'comment': '#'},
                       '.q': {'language': 'q', 'comment': '/'},
                       '.m': {'language': 'matlab', 'comment': '%'},
                       '.pro': {'language': 'idl', 'comment': ';'},


Test patch:
diff --git a/tests/notebooks/ipynb_ps1/powershell.ipynb b/tests/notebooks/ipynb_ps1/powershell.ipynb
new file mode 100644
index 00000000..d325faf9
--- /dev/null
+++ b/tests/notebooks/ipynb_ps1/powershell.ipynb
@@ -0,0 +1,44 @@
+{
+ "cells": [  
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "This is an extract from\n",
+    "https://github.com/Jaykul/Jupyter-PowerShell/blob/master/LiterateDevOps.ipynb"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 30,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "$imageUrl = 'https://upload.wikimedia.org/wikipedia/commons/2/2f/PowerShell_5.0_icon.png'\n",
+    "$ImageData = @{ \"png\" = (Invoke-WebRequest $imageUrl -UseBasicParsing).RawContentStream.GetBuffer() }\n",
+    "# $ImageData\n",
+    "\n",
+    "Write-Jupyter -InputObject $ImageData -Metadata @{ \"image/png\" = @{ 'width' = 32 } }\n",
+    "Write-Jupyter -InputObject $ImageData -Metadata @{ \"image/png\" = @{ 'width' = 64 } }"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "PowerShell",
+   "language": "PowerShell",
+   "name": "powershell"
+  },
+  "language_info": {
+   "codemirror_mode": "powershell",
+   "file_extension": ".ps1",
+   "mimetype": "text/powershell",
+   "name": "PowerShell",
+   "nbconvert_exporter": null,
+   "pygments_lexer": "powershell",
+   "version": "5.0"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/tests/notebooks/mirror/ipynb_to_Rmd/powershell.Rmd b/tests/notebooks/mirror/ipynb_to_Rmd/powershell.Rmd
new file mode 100644
index 00000000..22def3ad
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_Rmd/powershell.Rmd
@@ -0,0 +1,19 @@
+---
+jupyter:
+  kernelspec:
+    display_name: PowerShell
+    language: PowerShell
+    name: powershell
+---
+
+This is an extract from
+https://github.com/Jaykul/Jupyter-PowerShell/blob/master/LiterateDevOps.ipynb
+
+```{powershell}
+$imageUrl = 'https://upload.wikimedia.org/wikipedia/commons/2/2f/PowerShell_5.0_icon.png'
+$ImageData = @{ "png" = (Invoke-WebRequest $imageUrl -UseBasicParsing).RawContentStream.GetBuffer() }
+# $ImageData
+
+Write-Jupyter -InputObject $ImageData -Metadata @{ "image/png" = @{ 'width' = 32 } }
+Write-Jupyter -InputObject $ImageData -Metadata @{ "image/png" = @{ 'width' = 64 } }
+```
diff --git a/tests/notebooks/mirror/ipynb_to_md/powershell.md b/tests/notebooks/mirror/ipynb_to_md/powershell.md
new file mode 100644
index 00000000..d6e46d0f
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_md/powershell.md
@@ -0,0 +1,19 @@
+---
+jupyter:
+  kernelspec:
+    display_name: PowerShell
+    language: PowerShell
+    name: powershell
+---
+
+This is an extract from
+https://github.com/Jaykul/Jupyter-PowerShell/blob/master/LiterateDevOps.ipynb
+
+```powershell
+$imageUrl = 'https://upload.wikimedia.org/wikipedia/commons/2/2f/PowerShell_5.0_icon.png'
+$ImageData = @{ "png" = (Invoke-WebRequest $imageUrl -UseBasicParsing).RawContentStream.GetBuffer() }
+# $ImageData
+
+Write-Jupyter -InputObject $ImageData -Metadata @{ "image/png" = @{ 'width' = 32 } }
+Write-Jupyter -InputObject $ImageData -Metadata @{ "image/png" = @{ 'width' = 64 } }
+```
diff --git a/tests/notebooks/mirror/ipynb_to_percent/powershell.ps1 b/tests/notebooks/mirror/ipynb_to_percent/powershell.ps1
new file mode 100644
index 00000000..f5cefe25
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_percent/powershell.ps1
@@ -0,0 +1,19 @@
+# ---
+# jupyter:
+#   kernelspec:
+#     display_name: PowerShell
+#     language: PowerShell
+#     name: powershell
+# ---
+
+# %% [markdown]
+# This is an extract from
+# https://github.com/Jaykul/Jupyter-PowerShell/blob/master/LiterateDevOps.ipynb
+
+# %%
+$imageUrl = 'https://upload.wikimedia.org/wikipedia/commons/2/2f/PowerShell_5.0_icon.png'
+$ImageData = @{ "png" = (Invoke-WebRequest $imageUrl -UseBasicParsing).RawContentStream.GetBuffer() }
+# $ImageData
+
+Write-Jupyter -InputObject $ImageData -Metadata @{ "image/png" = @{ 'width' = 32 } }
+Write-Jupyter -InputObject $ImageData -Metadata @{ "image/png" = @{ 'width' = 64 } }
diff --git a/tests/notebooks/mirror/ipynb_to_script/powershell.ps1 b/tests/notebooks/mirror/ipynb_to_script/powershell.ps1
new file mode 100644
index 00000000..e8ca87cd
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_script/powershell.ps1
@@ -0,0 +1,18 @@
+# ---
+# jupyter:
+#   kernelspec:
+#     display_name: PowerShell
+#     language: PowerShell
+#     name: powershell
+# ---
+
+# This is an extract from
+# https://github.com/Jaykul/Jupyter-PowerShell/blob/master/LiterateDevOps.ipynb
+
+# +
+$imageUrl = 'https://upload.wikimedia.org/wikipedia/commons/2/2f/PowerShell_5.0_icon.png'
+$ImageData = @{ "png" = (Invoke-WebRequest $imageUrl -UseBasicParsing).RawContentStream.GetBuffer() }
+# $ImageData
+
+Write-Jupyter -InputObject $ImageData -Metadata @{ "image/png" = @{ 'width' = 32 } }
+Write-Jupyter -InputObject $ImageData -Metadata @{ "image/png" = @{ 'width' = 64 } }
diff --git a/tests/notebooks/mirror/script_to_ipynb/build.ipynb b/tests/notebooks/mirror/script_to_ipynb/build.ipynb
new file mode 100644
index 00000000..252bae33
--- /dev/null
+++ b/tests/notebooks/mirror/script_to_ipynb/build.ipynb
@@ -0,0 +1,282 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "This file originates from\n",
+    "https://github.com/MicrosoftDocs/PowerShell-Docs/blob/staging/build.ps1"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "param(\n",
+    "    [switch]$SkipCabs,\n",
+    "    [switch]$ShowProgress\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Turning off the progress display, by default\n",
+    "$global:ProgressPreference = 'SilentlyContinue'\n",
+    "if ($ShowProgress) { $ProgressPreference = 'Continue' }"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12\n",
+    "$tempDir = [System.IO.Path]::GetTempPath()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Pandoc source URL\n",
+    "$panDocVersion = \"2.7.3\"\n",
+    "$pandocSourceURL = \"https://github.com/jgm/pandoc/releases/download/$panDocVersion/pandoc-$panDocVersion-windows-x86_64.zip\""
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "$pandocDestinationPath = New-Item (Join-Path $tempDir \"pandoc\") -ItemType Directory -Force\n",
+    "$pandocZipPath = Join-Path $pandocDestinationPath \"pandoc-$panDocVersion-windows-x86_64.zip\"\n",
+    "Invoke-WebRequest -Uri $pandocSourceURL -OutFile $pandocZipPath"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "Expand-Archive -Path $pandocZipPath -DestinationPath $pandocDestinationPath -Force\n",
+    "$pandocExePath = Join-Path (Join-Path $pandocDestinationPath \"pandoc-$panDocVersion-windows-x86_64\") \"pandoc.exe\""
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Install ThreadJob if not available\n",
+    "$threadJob = Get-Module ThreadJob -ListAvailable\n",
+    "if ($null -eq $threadjob) {\n",
+    "    Install-Module ThreadJob -RequiredVersion 1.1.2 -Scope CurrentUser -Force\n",
+    "}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Find the reference folder path w.r.t the script\n",
+    "$ReferenceDocset = Join-Path $PSScriptRoot 'reference'"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Go through all the directories in the reference folder\n",
+    "$jobs = [System.Collections.Generic.List[object]]::new()\n",
+    "$excludeList = 'module', 'media', 'docs-conceptual', 'mapping', 'bread', '7'\n",
+    "Get-ChildItem $ReferenceDocset -Directory -Exclude $excludeList | ForEach-Object -Process {\n",
+    "    $job = Start-ThreadJob -Name $_.Name -ArgumentList @($SkipCabs,$pandocExePath,$PSScriptRoot,$_) -ScriptBlock {\n",
+    "        param($SkipCabs, $pandocExePath, $WorkingDirectory, $DocSet)\n",
+    "\n",
+    "        $tempDir = [System.IO.Path]::GetTempPath()\n",
+    "        $workingDir = Join-Path $tempDir $DocSet.Name\n",
+    "        $workingDir = New-Item -ItemType Directory -Path $workingDir -Force\n",
+    "        Set-Location $WorkingDir\n",
+    "\n",
+    "        function Get-ContentWithoutHeader {\n",
+    "            param(\n",
+    "                $path\n",
+    "            )\n",
+    "\n",
+    "            $doc = Get-Content $path -Encoding UTF8\n",
+    "            $start = $end = -1\n",
+    "\n",
+    "            # search the first 30 lines for the Yaml header\n",
+    "            # no yaml header in our docset will ever be that long\n",
+    "\n",
+    "            for ($x = 0; $x -lt 30; $x++) {\n",
+    "                if ($doc[$x] -eq '---') {\n",
+    "                    if ($start -eq -1) {\n",
+    "                        $start = $x\n",
+    "                    }\n",
+    "                    else {\n",
+    "                        if ($end -eq -1) {\n",
+    "                            $end = $x + 1\n",
+    "                            break\n",
+    "                        }\n",
+    "                    }\n",
+    "                }\n",
+    "            }\n",
+    "            if ($end -gt $start) {\n",
+    "                Write-Output ($doc[$end..$($doc.count)] -join \"`r`n\")\n",
+    "            }\n",
+    "            else {\n",
+    "                Write-Output ($doc -join \"`r`n\")\n",
+    "            }\n",
+    "        }\n",
+    "\n",
+    "        $Version = $DocSet.Name\n",
+    "        Write-Verbose -Verbose \"Version = $Version\"\n",
+    "\n",
+    "        $VersionFolder = $DocSet.FullName\n",
+    "        Write-Verbose -Verbose \"VersionFolder = $VersionFolder\"\n",
+    "\n",
+    "        # For each of the directories, go through each module folder\n",
+    "        Get-ChildItem $VersionFolder -Directory | ForEach-Object -Process {\n",
+    "            $ModuleName = $_.Name\n",
+    "            Write-Verbose -Verbose \"ModuleName = $ModuleName\"\n",
+    "\n",
+    "            $ModulePath = Join-Path $VersionFolder $ModuleName\n",
+    "            Write-Verbose -Verbose \"ModulePath = $ModulePath\"\n",
+    "\n",
+    "            $LandingPage = Join-Path $ModulePath \"$ModuleName.md\"\n",
+    "            Write-Verbose -Verbose \"LandingPage = $LandingPage\"\n",
+    "\n",
+    "            $MamlOutputFolder = Join-Path \"$WorkingDirectory\\maml\" \"$Version\\$ModuleName\"\n",
+    "            Write-Verbose -Verbose \"MamlOutputFolder = $MamlOutputFolder\"\n",
+    "\n",
+    "            $CabOutputFolder = Join-Path \"$WorkingDirectory\\updatablehelp\" \"$Version\\$ModuleName\"\n",
+    "            Write-Verbose -Verbose \"CabOutputFolder = $CabOutputFolder\"\n",
+    "\n",
+    "            if (-not (Test-Path $MamlOutputFolder)) {\n",
+    "                New-Item $MamlOutputFolder -ItemType Directory -Force > $null\n",
+    "            }\n",
+    "\n",
+    "            # Process the about topics if any\n",
+    "            $AboutFolder = Join-Path $ModulePath \"About\"\n",
+    "\n",
+    "            if (Test-Path $AboutFolder) {\n",
+    "                Write-Verbose -Verbose \"AboutFolder = $AboutFolder\"\n",
+    "                Get-ChildItem \"$aboutfolder/about_*.md\" | ForEach-Object {\n",
+    "                    $aboutFileFullName = $_.FullName\n",
+    "                    $aboutFileOutputName = \"$($_.BaseName).help.txt\"\n",
+    "                    $aboutFileOutputFullName = Join-Path $MamlOutputFolder $aboutFileOutputName\n",
+    "\n",
+    "                    $pandocArgs = @(\n",
+    "                        \"--from=gfm\",\n",
+    "                        \"--to=plain+multiline_tables\",\n",
+    "                        \"--columns=75\",\n",
+    "                        \"--output=$aboutFileOutputFullName\",\n",
+    "                        \"--quiet\"\n",
+    "                    )\n",
+    "\n",
+    "                    Get-ContentWithoutHeader $aboutFileFullName | & $pandocExePath $pandocArgs\n",
+    "                }\n",
+    "            }\n",
+    "\n",
+    "            try {\n",
+    "                # For each module, create a single maml help file\n",
+    "                # Adding warningaction=stop to throw errors for all warnings, erroraction=stop to make them terminating errors\n",
+    "                New-ExternalHelp -Path $ModulePath -OutputPath $MamlOutputFolder -Force -WarningAction Stop -ErrorAction Stop\n",
+    "\n",
+    "                # For each module, create update-help help files (cab and helpinfo.xml files)\n",
+    "                if (-not $SkipCabs) {\n",
+    "                    $cabInfo = New-ExternalHelpCab -CabFilesFolder $MamlOutputFolder -LandingPagePath $LandingPage -OutputFolder $CabOutputFolder\n",
+    "\n",
+    "                    # Only output the cab fileinfo object\n",
+    "                    if ($cabInfo.Count -eq 8) { $cabInfo[-1].FullName }\n",
+    "                }\n",
+    "            }\n",
+    "            catch {\n",
+    "                Write-Error -Message \"PlatyPS failure: $ModuleName -- $Version\" -Exception $_\n",
+    "            }\n",
+    "        }\n",
+    "\n",
+    "        Remove-Item $workingDir -Force -ErrorAction SilentlyContinue\n",
+    "    }\n",
+    "    Write-Verbose -Verbose \"Started job for $($_.Name)\"\n",
+    "    $jobs += $job\n",
+    "}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "$null = $jobs | Wait-Job"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Variable to collect any errors in during processing\n",
+    "$allErrors = [System.Collections.Generic.List[string]]::new()\n",
+    "foreach ($job in $jobs) {\n",
+    "    Write-Verbose -Verbose \"$($job.Name) output:\"\n",
+    "    if ($job.Verbose.Count -gt 0) {\n",
+    "        foreach ($verboseMessage in $job.Verbose) {\n",
+    "            Write-Verbose -Verbose $verboseMessage\n",
+    "        }\n",
+    "    }\n",
+    "\n",
+    "    if ($job.State -eq \"Failed\") {\n",
+    "        $allErrors += \"$($job.Name) failed due to unhandled exception\"\n",
+    "    }\n",
+    "\n",
+    "    if ($job.Error.Count -gt 0) {\n",
+    "        $allErrors += \"$($job.Name) failed with errors:\"\n",
+    "        $allErrors += $job.Error.ReadAll()\n",
+    "    }\n",
+    "}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# If the above block, produced any errors, throw and fail the job\n",
+    "if ($allErrors.Count -gt 0) {\n",
+    "    $allErrors\n",
+    "    throw \"There are errors during platyPS run!`nPlease fix your markdown to comply with the schema: https://github.com/PowerShell/platyPS/blob/master/platyPS.schema.md\"\n",
+    "}"
+   ]
+  }
+ ],
+ "metadata": {
+  "jupytext": {
+   "cell_metadata_filter": "-all",
+   "main_language": "powershell",
+   "notebook_metadata_filter": "-all"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/tests/notebooks/ps1/build.ps1 b/tests/notebooks/ps1/build.ps1
new file mode 100644
index 00000000..4389478c
--- /dev/null
+++ b/tests/notebooks/ps1/build.ps1
@@ -0,0 +1,179 @@
+# This file originates from
+# https://github.com/MicrosoftDocs/PowerShell-Docs/blob/staging/build.ps1
+
+param(
+    [switch]$SkipCabs,
+    [switch]$ShowProgress
+)
+
+# Turning off the progress display, by default
+$global:ProgressPreference = 'SilentlyContinue'
+if ($ShowProgress) { $ProgressPreference = 'Continue' }
+
+[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
+$tempDir = [System.IO.Path]::GetTempPath()
+
+# Pandoc source URL
+$panDocVersion = "2.7.3"
+$pandocSourceURL = "https://github.com/jgm/pandoc/releases/download/$panDocVersion/pandoc-$panDocVersion-windows-x86_64.zip"
+
+$pandocDestinationPath = New-Item (Join-Path $tempDir "pandoc") -ItemType Directory -Force
+$pandocZipPath = Join-Path $pandocDestinationPath "pandoc-$panDocVersion-windows-x86_64.zip"
+Invoke-WebRequest -Uri $pandocSourceURL -OutFile $pandocZipPath
+
+Expand-Archive -Path $pandocZipPath -DestinationPath $pandocDestinationPath -Force
+$pandocExePath = Join-Path (Join-Path $pandocDestinationPath "pandoc-$panDocVersion-windows-x86_64") "pandoc.exe"
+
+# Install ThreadJob if not available
+$threadJob = Get-Module ThreadJob -ListAvailable
+if ($null -eq $threadjob) {
+    Install-Module ThreadJob -RequiredVersion 1.1.2 -Scope CurrentUser -Force
+}
+
+# Find the reference folder path w.r.t the script
+$ReferenceDocset = Join-Path $PSScriptRoot 'reference'
+
+# Go through all the directories in the reference folder
+$jobs = [System.Collections.Generic.List[object]]::new()
+$excludeList = 'module', 'media', 'docs-conceptual', 'mapping', 'bread', '7'
+Get-ChildItem $ReferenceDocset -Directory -Exclude $excludeList | ForEach-Object -Process {
+    $job = Start-ThreadJob -Name $_.Name -ArgumentList @($SkipCabs,$pandocExePath,$PSScriptRoot,$_) -ScriptBlock {
+        param($SkipCabs, $pandocExePath, $WorkingDirectory, $DocSet)
+
+        $tempDir = [System.IO.Path]::GetTempPath()
+        $workingDir = Join-Path $tempDir $DocSet.Name
+        $workingDir = New-Item -ItemType Directory -Path $workingDir -Force
+        Set-Location $WorkingDir
+
+        function Get-ContentWithoutHeader {
+            param(
+                $path
+            )
+
+            $doc = Get-Content $path -Encoding UTF8
+            $start = $end = -1
+
+            # search the first 30 lines for the Yaml header
+            # no yaml header in our docset will ever be that long
+
+            for ($x = 0; $x -lt 30; $x++) {
+                if ($doc[$x] -eq '---') {
+                    if ($start -eq -1) {
+                        $start = $x
+                    }
+                    else {
+                        if ($end -eq -1) {
+                            $end = $x + 1
+                            break
+                        }
+                    }
+                }
+            }
+            if ($end -gt $start) {
+                Write-Output ($doc[$end..$($doc.count)] -join "`r`n")
+            }
+            else {
+                Write-Output ($doc -join "`r`n")
+            }
+        }
+
+        $Version = $DocSet.Name
+        Write-Verbose -Verbose "Version = $Version"
+
+        $VersionFolder = $DocSet.FullName
+        Write-Verbose -Verbose "VersionFolder = $VersionFolder"
+
+        # For each of the directories, go through each module folder
+        Get-ChildItem $VersionFolder -Directory | ForEach-Object -Process {
+            $ModuleName = $_.Name
+            Write-Verbose -Verbose "ModuleName = $ModuleName"
+
+            $ModulePath = Join-Path $VersionFolder $ModuleName
+            Write-Verbose -Verbose "ModulePath = $ModulePath"
+
+            $LandingPage = Join-Path $ModulePath "$ModuleName.md"
+            Write-Verbose -Verbose "LandingPage = $LandingPage"
+
+            $MamlOutputFolder = Join-Path "$WorkingDirectory\maml" "$Version\$ModuleName"
+            Write-Verbose -Verbose "MamlOutputFolder = $MamlOutputFolder"
+
+            $CabOutputFolder = Join-Path "$WorkingDirectory\updatablehelp" "$Version\$ModuleName"
+            Write-Verbose -Verbose "CabOutputFolder = $CabOutputFolder"
+
+            if (-not (Test-Path $MamlOutputFolder)) {
+                New-Item $MamlOutputFolder -ItemType Directory -Force > $null
+            }
+
+            # Process the about topics if any
+            $AboutFolder = Join-Path $ModulePath "About"
+
+            if (Test-Path $AboutFolder) {
+                Write-Verbose -Verbose "AboutFolder = $AboutFolder"
+                Get-ChildItem "$aboutfolder/about_*.md" | ForEach-Object {
+                    $aboutFileFullName = $_.FullName
+                    $aboutFileOutputName = "$($_.BaseName).help.txt"
+                    $aboutFileOutputFullName = Join-Path $MamlOutputFolder $aboutFileOutputName
+
+                    $pandocArgs = @(
+                        "--from=gfm",
+                        "--to=plain+multiline_tables",
+                        "--columns=75",
+                        "--output=$aboutFileOutputFullName",
+                        "--quiet"
+                    )
+
+                    Get-ContentWithoutHeader $aboutFileFullName | & $pandocExePath $pandocArgs
+                }
+            }
+
+            try {
+                # For each module, create a single maml help file
+                # Adding warningaction=stop to throw errors for all warnings, erroraction=stop to make them terminating errors
+                New-ExternalHelp -Path $ModulePath -OutputPath $MamlOutputFolder -Force -WarningAction Stop -ErrorAction Stop
+
+                # For each module, create update-help help files (cab and helpinfo.xml files)
+                if (-not $SkipCabs) {
+                    $cabInfo = New-ExternalHelpCab -CabFilesFolder $MamlOutputFolder -LandingPagePath $LandingPage -OutputFolder $CabOutputFolder
+
+                    # Only output the cab fileinfo object
+                    if ($cabInfo.Count -eq 8) { $cabInfo[-1].FullName }
+                }
+            }
+            catch {
+                Write-Error -Message "PlatyPS failure: $ModuleName -- $Version" -Exception $_
+            }
+        }
+
+        Remove-Item $workingDir -Force -ErrorAction SilentlyContinue
+    }
+    Write-Verbose -Verbose "Started job for $($_.Name)"
+    $jobs += $job
+}
+
+$null = $jobs | Wait-Job
+
+# Variable to collect any errors in during processing
+$allErrors = [System.Collections.Generic.List[string]]::new()
+foreach ($job in $jobs) {
+    Write-Verbose -Verbose "$($job.Name) output:"
+    if ($job.Verbose.Count -gt 0) {
+        foreach ($verboseMessage in $job.Verbose) {
+            Write-Verbose -Verbose $verboseMessage
+        }
+    }
+
+    if ($job.State -eq "Failed") {
+        $allErrors += "$($job.Name) failed due to unhandled exception"
+    }
+
+    if ($job.Error.Count -gt 0) {
+        $allErrors += "$($job.Name) failed with errors:"
+        $allErrors += $job.Error.ReadAll()
+    }
+}
+
+# If the above block, produced any errors, throw and fail the job
+if ($allErrors.Count -gt 0) {
+    $allErrors
+    throw "There are errors during platyPS run!`nPlease fix your markdown to comply with the schema: https://github.com/PowerShell/platyPS/blob/master/platyPS.schema.md"
+}
diff --git a/tests/test_mirror.py b/tests/test_mirror.py
index c7e81b25..df387bac 100644
--- a/tests/test_mirror.py
+++ b/tests/test_mirror.py
@@ -82,7 +82,8 @@ def assert_conversion_same_as_mirror(nb_file, fmt, mirror_name, compare_notebook
         compare_notebooks(nb_mirror, notebook, fmt, compare_outputs=True)
 
 
-@pytest.mark.parametrize('nb_file', list_notebooks('julia') + list_notebooks('python') + list_notebooks('R'))
+@pytest.mark.parametrize('nb_file', list_notebooks('julia') + list_notebooks('python') +
+                         list_notebooks('R') + list_notebooks('ps1'))
 def test_script_to_ipynb(nb_file, no_jupytext_version_number):
     assert_conversion_same_as_mirror(nb_file, 'ipynb', 'script_to_ipynb')
 
@@ -142,6 +143,11 @@ def test_ipynb_to_bash(nb_file, no_jupytext_version_number):
     assert_conversion_same_as_mirror(nb_file, 'sh', 'ipynb_to_script')
 
 
+@pytest.mark.parametrize('nb_file', list_notebooks('ipynb_ps1'))
+def test_ipynb_to_powershell(nb_file, no_jupytext_version_number):
+    assert_conversion_same_as_mirror(nb_file, 'ps1', 'ipynb_to_script')
+
+
 @pytest.mark.parametrize('nb_file', list_notebooks('ipynb_cpp'))
 def test_ipynb_to_cpp(nb_file, no_jupytext_version_number):
     assert_conversion_same_as_mirror(nb_file, 'cpp', 'ipynb_to_script')
@@ -217,6 +223,11 @@ def test_ipynb_to_bash_percent(nb_file, no_jupytext_version_number):
     assert_conversion_same_as_mirror(nb_file, 'sh:percent', 'ipynb_to_percent')
 
 
+@pytest.mark.parametrize('nb_file', list_notebooks('ipynb_ps1'))
+def test_ipynb_to_powershell_percent(nb_file, no_jupytext_version_number):
+    assert_conversion_same_as_mirror(nb_file, 'ps1:percent', 'ipynb_to_percent')
+
+
 @pytest.mark.parametrize('nb_file', list_notebooks('ipynb_q'))
 def test_ipynb_to_q_percent(nb_file, no_jupytext_version_number):
     assert_conversion_same_as_mirror(nb_file, 'q:percent', 'ipynb_to_percent')