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.
npm run import -- "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;
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:
Imports:
fs
: For file system operations.path
: For working with file paths.mkdirpSync
: For creating directories.importer
: A custom module for importing other modules.importer
for tasks like:
getImportsRecursively
Function:
getImports
to extract import statements from code cells.exportNotebook
Function:
getImportsRecursively
to get all relevant cells.makeHandlerCell
.