cmdlets/Jupyter.ps1

#!/usr/bin/env powershell
##
# Jupyter.ps1: Cmdlets for working with Jupyter notebooks.
##
# © 2017 Christopher Granade (cgranade@cgranade.com)
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of PoShTeX nor the names
# of its contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
##

## COMMANDS ##

function Invoke-Python {
    [CmdletBinding()]
    param(
        [string] $Source,
        [hashtable] $Variables = @{},
        [hashtable] $Import = @{}
    );

    $headerLines = @(
        # Set the encoding to match what we'll specify when we
        # call Out-File below.
        "# -*- coding: utf-8 -*-",
        # Future proof to make this cmdlet more 2/3 agnostic.
        "from __future__ import division, print_function"
    )

    # Possibly handle additional variables.
    if ($Variables.Count) {
        # We'll pass variables as JSON.
        $headerLines += "from json import loads"

        foreach ($item in $Variables.GetEnumerator()) {
            $jsonPayload = ($item | ConvertTo-Json);
            $headerLines += "$($item.Name) = loads(r`'`'`'$jsonPayload`'`'`')['Value']"
        }
        
        # Remove the side effect of having imported json.loads.
        $headerLines += "del loads"
    }
    
    # Possibly import additional modules.
    if ($Import.Count) {
        foreach ($item in $Import.GetEnumerator()) {
            if ($item.Value -eq $null) {
                $headerLines += "import $($item.Name)"
            } else {
                $headerLines += "import $($item.Name) as $($item.Value)"
            }
        }
    }

    if ($DebugPreference) {
        Write-Host ([System.String]::Join("`n", $headerLines));
    }

    $tempFile = [System.IO.Path]::GetTempFileName();
    ($headerLines + $Source) | Out-File -FilePath $tempFile -Encoding utf8;

    python $tempFile | Write-Output;

    Remove-Item $tempFile | Out-Null;

}

function Update-JupyterNotebook {
    [CmdletBinding()]
    param(
        [Parameter(
            Position=0,
            Mandatory=$true,
            ValueFromPipeline=$true
        )]
        [string[]]
        $Path,

        $NotebookKernel = $null
    )

    $script = @"
from nbconvert.preprocessors import ExecutePreprocessor
 
try:
    import nb_conda_kernels
 
    # Now monkey patch the conda KernelSpecManager into
    # jupyter_client into jupyter_client.kernelspec, as used by
    # jupyter_client.manager, as used in turn by nbconvert.
    import jupyter_client.kernelspec
    jupyter_client.kernelspec.KernelSpecManager = nb_conda_kernels.CondaKernelSpecManager
    print("Successfully loaded Anaconda extensions.")
except ImportError as ex:
    print(ex)
 
if notebook_kernel:
    ep = ExecutePreprocessor(kernel_name=notebook_kernel)
else:
    ep = ExecutePreprocessor()
 
with open(notebook_path, 'r') as notebook_file:
    nb = nbformat.read(notebook_file, as_version=4)
 
ep.preprocess(nb, {'metadata': {
    'path': os.path.split(notebook_path)[0]
}})
 
with open(notebook_path, 'w') as notebook_file:
    nbformat.write(nb, notebook_file)
"@

    
    process {
        foreach ($notebookPath in $Path) {
            Invoke-Python -Import @{
                "sys" = $null; "os" = $null;
                "nbformat" = $null;
            } -Variables @{
                notebook_kernel = $NotebookKernel;
                notebook_path = $notebookPath;
            } -Source $script
        }
    }
}