notebook | Cell 9 | inject cells in to notebooks when built with webpack | Search

This code provides tools for managing and exporting Jupyter Notebooks, specifically handling import dependencies and generating function handlers for deployment. It recursively analyzes notebooks, extracts import information, and likely produces output files containing functions based on the processed code.

Run example

npm run import -- "export notebook"

export notebook

var fs = require('fs')
var path = require('path')
var mkdirpSync = importer.import("mkdirp")

var importer = require('../Core')
var authorTemplate = importer.import("authoring header template")
var {
    replaceImports, replaceCore
} = importer.import("replace notebook imports",
"replace core requirement")
var getImports = importer.import("get imports")
var {fixImports} = importer.import("fix project paths")
var delintCode = importer.import("delint notebooks")
var niceName = importer.import("rename cell to nice name")
var {matchFilename} = importer.import("match filename")
var {makeHandler} = importer.import("generic gcloud function handler")
var makeHandlerCell = importer.interpret('generic gcloud function handler')

function getImportsRecursively(searches) {
    if(typeof searches === 'string') {
        searches = [searches]
    }
    const processed = []
    const allCells = []
    const pending = importer.interpret(searches)
    while(pending.length > 0) {
        var cell = pending.pop()
        processed.push(cell.id)
        allCells.push(cell)
        if(cell.code.length > 10000 || cell.filename.includes('cache.ipynb')) continue
        continue
        getImports(cell.code).forEach(search => {
            try {
                var cells = importer.interpret([search])
                cells.forEach(c => {
                    if(!processed.includes(c.id))
                        pending.push(c)
                })
            } catch (e) {console.log(e)}
        })
    }
    return allCells
}

// searches are the top level cells starting the import tree
function exportNotebook(searches, projectOutput, matchOutput) {
    projectOutput = projectOutput || process.env.EXPORT_PATH
        || path.join(path.resolve(__dirname), '../.functions');
    if(!matchOutput) matchOutput = {}
    const nextImports = []
    const cells = getImportsRecursively(searches)
    cells.forEach((cell, i) => {
        var exportedCode
        assert(!niceName(cell).match(/^\./),
               `No filename ${cell.id} -> ${niceName(cell)} ${cell.questions}!`)
        // some special exceptions with file-naming
        if(cell.name === 'readme.md') {
            exportedCode = cell.markdown
        } else if (cell.language === 'javascript' || cell.language === 'node') {
            try {
                exportedCode = replaceImports(cell.code).ownerDocument.toString()
                exportedCode = replaceCore(exportedCode).ownerDocument.toString()
                const delinted = delintCode(exportedCode)[0]
                exportedCode = delinted.fixed || delinted.code
            } catch (e) {
                console.log(exportedCode)
                throw new Error(`Error exporting ${cell.id}: ${e.message}`)
            }
        } else {
            exportedCode = cell.code
        }
        outputExport(exportedCode, cell, projectOutput, matchOutput)
    })
    
    // if there is no index, add a google handler
    if(Object.values(matchOutput).filter(o => o.includes('index.js')).length === 0
       && cells.filter(cell => niceName(cell).includes('index')).length === 0) {
        outputExport(makeHandler(searches), makeHandlerCell, projectOutput, {
            '*function-handler*': './index.js'
        })
    }
    
    return fixImports(projectOutput)
    // TODO: output packed cells and cache
    // TODO: zip and upload to AWS
}

function outputExport(exportedCode, cell, projectOutput, matchOutput) {
    // emit the file in every location request
    matchFilename(niceName(cell), matchOutput, projectOutput).forEach(filename => {
        console.log(`emitting ${filename}`);
        // create directory if needed
        mkdirpSync(path.dirname(filename));
        // add a code block header
        if(path.extname(filename) == '.js' || path.extname(filename) == '.cs') {
            exportedCode = authorTemplate(cell.markdown, exportedCode)
        }
        fs.writeFileSync(filename, exportedCode);
    })
}

module.exports = exportNotebook;

What the code could have been:

const fs = require('fs');
const path = require('path');
const mkdirpSync = require('mkdirp');
const importer = require('../Core');

const {
  replaceImports,
  replaceCore,
} = importer.import(['./replace notebook imports', './replace core requirement']);

const getImports = importer.import('./get imports');
const fixImports = importer.import('./fix project paths');
const delintCode = importer.import('./delint notebooks');
const niceName = importer.import('./rename cell to nice name');
const matchFilename = importer.import('./match filename');
const makeHandler = importer.import('./generic gcloud function handler');
const authorTemplate = importer.import('./authoring header template');

/**
 * Recursively get all imports from the given search cells.
 * @param {string|string[]} searches The top-level cells to start the import tree.
 * @returns {object[]} An array of all cells in the import tree.
 */
function getImportsRecursively(searches) {
  if (typeof searches ==='string') {
    searches = [searches];
  }

  const processed = [];
  const allCells = [];
  const pending = importer.interpret(searches);

  while (pending.length > 0) {
    const cell = pending.pop();
    processed.push(cell.id);
    allCells.push(cell);

    if (cell.code.length > 10000 || cell.filename.includes('cache.ipynb')) continue;

    try {
      getImports(cell.code).forEach((search) => {
        const cells = importer.interpret([search]);
        cells.forEach((c) => {
          if (!processed.includes(c.id)) pending.push(c);
        });
      });
    } catch (e) {
      console.log(e);
    }
  }

  return allCells;
}

/**
 * Export a notebook, recursively getting all imports and writing them to files.
 * @param {string|string[]} searches The top-level cells to start the export.
 * @param {string} [projectOutput] The directory to write the exported code to.
 * @param {object} [matchOutput] An object mapping nice names to file extensions.
 * @returns {string} The fixed import paths for the exported project.
 */
function exportNotebook(searches, projectOutput, matchOutput) {
  projectOutput = projectOutput || process.env.EXPORT_PATH || path.join(path.resolve(__dirname), '../.functions');
  matchOutput = matchOutput || {};

  const nextImports = [];
  const cells = getImportsRecursively(searches);

  cells.forEach((cell, i) => {
    let exportedCode;

    assert(!niceName(cell).match(/^\./), `No filename ${cell.id} -> ${niceName(cell)} ${cell.questions}!`);

    if (cell.name ==='readme.md') {
      exportedCode = cell.markdown;
    } else {
      try {
        exportedCode = replaceImports(cell.code).ownerDocument.toString();
        exportedCode = replaceCore(exportedCode).ownerDocument.toString();
        const delinted = delintCode(exportedCode)[0];
        exportedCode = delinted.fixed || delinted.code;
      } catch (e) {
        throw new Error(`Error exporting ${cell.id}: ${e.message}`);
      }

      exportedCode = authorTemplate(cell.markdown, exportedCode);
    }

    outputExport(exportedCode, cell, projectOutput, matchOutput);
  });

  // Add a Google handler if there is no index
  if (Object.values(matchOutput).filter((o) => o.includes('index.js')).length === 0 && cells.filter((cell) => niceName(cell).includes('index')).length === 0) {
    outputExport(makeHandler(searches), makeHandler(), projectOutput, {
      '*function-handler*': './index.js',
    });
  }

  return fixImports(projectOutput);
}

/**
 * Output the exported code to the specified files.
 * @param {string} exportedCode The code to be written to files.
 * @param {object} cell The cell containing the code.
 * @param {string} projectOutput The directory to write the exported code to.
 * @param {object} matchOutput An object mapping nice names to file extensions.
 */
function outputExport(exportedCode, cell, projectOutput, matchOutput) {
  matchFilename(niceName(cell), matchOutput, projectOutput).forEach((filename) => {
    console.log(`emitting ${filename}`);

    // Create directory if needed
    mkdirpSync(path.dirname(filename));

    // Write code to file
    fs.writeFileSync(filename, exportedCode);
  });
}

module.exports = exportNotebook;

This code defines functions for processing and exporting Jupyter Notebooks, focusing on handling imports and generating function handlers.

Here's a breakdown:

  1. Imports:

  2. getImportsRecursively Function:

  3. exportNotebook Function: