import | | how does node module require work | Search

The code defines a function importNotebook to import a notebook, which interprets the notebook using the interpret function from the Core module, and then makes a module based on the notebook's language.

Run example

npm run import -- "import notebook"

import notebook

var path = require('path');

// TODO: combine with id2 from rpc and nicename from notebook export
// TODO: insert niceName and getExports here?
// must have a unique id for each unique cell so that
// individual cells can serve as modules as well as notebooks
// adding the cell id as a part of the filename
function getCellPath(cell) {
    var question = cell.questions && cell.questions[0]
        ? (' aka ' + cell.questions[0].substr(0, 50))
        : ''
    return path.join(path.dirname(path.resolve(cell.filename)), cell.id) + question
}

const ACCUMULATOR = {
    info: [],
    error: [],
    log: [],
}

const CONSOLE = {
    info: function (...args) {
        ACCUMULATOR.info.push(args.map(a => String(a)).join(' '))
        console.info(...args)
    },
    error: function (...args) {
        ACCUMULATOR.error.push(args.map(a => String(a)).join(' '))
        console.error(...args)
    },
    log: function (...args) {
        ACCUMULATOR.log.push(args.map(a => String(a)).join(' '))
        console.log(...args)
    },
}

// How to test if a notebook has already been imported
function importNotebook("notebook",
"ctx = {}") {
    var {interpret, makeModule, makeESModule, makeDylib, makeDLL, makePythonModule} = require('../Core')
    // accept all arguments as the list of queries
    if(arguments.length > 2) {
        notebook = Array.from(arguments)
        if(typeof arugments[arguments.length - 1] === 'object'
          && !Array.isArray(arugments[arguments.length - 1])) {
            notebook = notebook.slice(0, arguments.length - 1)
            ctx = arugments[arguments.length - 1]
        }
    }

    if (typeof notebook === 'undefined') {
        return Promise.resolve({})
    }
    
    // TODO: move this sort of thing to cache
    var cells = interpret(notebook)
    
    if(typeof cells.code !== 'undefined') {
        if(!cells.filename.includes('Core')) {
            CONSOLE.log(`importing ${notebook} - 1 cell - ${cells.id}`)
        }

        if(cells.language == 'python') {

            return makePythonModule(cells.source.join(''),
                cells.id,
                ctx)

        } else

        if(cells.language == 'csharp') {

            return makeDLL(cells.source.join(''), cells.id)

        } else

        if(cells.language == 'c' || cells.language == 'cpp' || cells.language == 'objective-c') {

            return makeDylib(cells.source.join(''), cells.id)

        } else

        if (!cells.filename.match(/Core\//gi) && !cells.filename.match(/cache/gi) 
            && cells.source.join('').match(/^import\s|^export\s/gmi)
        ) {
            CONSOLE.error('ES module matched')
            return Promise.resolve(makeESModule(cells.source.join(''), cells.filename, ctx))
        } else
        
        return makeModule(cells.source.join(''),
                          getCellPath(cells),
                          ctx)
    }
    
    CONSOLE.log(`importing ${notebook} - ${cells.length} cells - ${cells.map(c => c.id)}`)
    var package = {}

    for(let i = 0; i < cells.length; i++) {

        let result

        if(cells.language == 'python') {

            result = makePythonModule(cells[i].source.join(''),
                cells[i].id,
                ctx)

        } else

        if(cells[i].language == 'csharp') {

            result = makeDLL(cells[i].source.join(''), cells[i].id)

        } else

        if(cells[i].language == 'c' || cells[i].language == 'cpp' || cells[i].language == 'objective-c') {

            result = makeDylib(cells[i].source.join(''), cells[i].id)

        } else
        if (!cells[i].filename.match(/Core\//gi) && !cells[i].filename.match(/cache/gi) 
            && cells[i].source.join('').match(/^import\s|^export\s/gi)
        ) {
            result = Promise.resolve(makeESModule(cells[i].source.join(''), cells[i].filename, ctx))
        } else
            result = makeModule(cells[i].source.join(''),
                                      getCellPath(cells[i]),
                                      ctx)

        if(typeof result === 'object') {
            // TODO: handle promises and merge them if they are objects?
            if(typeof result[Object.keys(result)[0]] === 'function') {
                const func = result[Object.keys(result)[0]]
                package[cells[i].id] = func
                package[func.name] = func
            }
            Object.assign(package, result)
        }
        if (typeof result === 'function') {
            package[cells[i].id] = result
            package[result.name] = result
        }
        package[i] = result
        Object.assign(ctx, package)
    }

    return package
}

module.exports.importNotebook = importNotebook;
module.exports.import = importNotebook;
module.exports.CONSOLE = CONSOLE
module.exports.ACCUMULATOR = ACCUMULATOR

What the code could have been:

const path = require('path');
const { interpret, makeModule, makeESModule, makeDylib, makeDLL, makePythonModule } = require('../Core');

class Accumulator {
    constructor() {
        this.info = [];
        this.error = [];
        this.log = [];
    }

    info(...args) {
        this.info.push(args.map(a => String(a)).join(' '));
        console.info(...args);
    }

    error(...args) {
        this.error.push(args.map(a => String(a)).join(' '));
        console.error(...args);
    }

    log(...args) {
        this.log.push(args.map(a => String(a)).join(' '));
        console.log(...args);
    }
}

const console = new Accumulator();

const getCellPath = (cell) => {
    const { questions, filename, id } = cell;
    const question = questions && questions[0]? ` aka ${questions[0].substr(0, 50)}` : '';
    return path.join(path.dirname(path.resolve(filename)), id) + question;
};

const isESModule = (source) => /^import\s|^export\s/gi.test(source.join(''));

const getModuleMaker = (language, makeDylib, makeDLL, makePythonModule) => {
    switch (language) {
        case 'csharp':
            return makeDLL;
        case 'c':
        case 'cpp':
        case 'objective-c':
            return makeDylib;
        case 'python':
            return makePythonModule;
        default:
            return makeModule;
    }
};

const importNotebook = async (notebook, ctx = {}) => {
    if (arguments.length > 2) {
        notebook = Array.from(arguments);
        if (typeof notebook[notebook.length - 1] === 'object' &&!Array.isArray(notebook[notebook.length - 1])) {
            notebook = notebook.slice(0, notebook.length - 1);
            ctx = notebook[notebook.length - 1];
        }
    }

    if (typeof notebook === 'undefined') {
        return Promise.resolve({});
    }

    const cells = await interpret(notebook);

    const package = {};
    const moduleMakers = {};

    for (const cell of cells) {
        const { source, language, id, filename } = cell;
        const { make } = getModuleMaker(language, makeDylib, makeDLL, makePythonModule);

        if (isESModule(source)) {
            await console.log(`importing ${filename} - ES module`);
            moduleMakers[id] = makeESModule(source.join(''), filename, ctx);
        } else {
            await console.log(`importing ${filename} - ${cells.length} cell - ${id}`);
            moduleMakers[id] = await make(source.join(''), getCellPath(cell), ctx);
        }

        Object.assign(package, moduleMakers[id]);
        Object.assign(ctx, package);
    }

    return package;
};

module.exports = {
    importNotebook,
    console,
    package: {}
};

Code Breakdown

Importing Modules and Functions

The code starts by importing the path module and defining a function getCellPath to construct a file path from a cell object.

var path = require('path');
function getCellPath(cell) {
    //...
}

Defining Accumulator and Console Objects

It then defines two objects: ACCUMULATOR and CONSOLE. ACCUMULATOR is an object that accumulates log messages, and CONSOLE is an object that provides wrapper functions for console.log, console.info, and console.error. These wrapper functions add log messages to the ACCUMULATOR object.

const ACCUMULATOR = {
    info: [],
    error: [],
    log: [],
}

const CONSOLE = {
    info: function (...args) {
        //...
    },
    error: function (...args) {
        //...
    },
    log: function (...args) {
        //...
    },
}

Defining the ImportNotebook Function

The importNotebook function is defined to import a notebook. It takes a notebook parameter, which can be either a single value or an array of values, and an optional ctx parameter.

function importNotebook(notebook, ctx = {}) {
    //...
}

Interpreting and Making Modules

The function uses the interpret function from the Core module to interpret the notebook. It then checks the language of the notebook and makes a module using the corresponding function (e.g., makePythonModule for Python notebooks).

var cells = interpret(notebook)
if (typeof cells.code!== 'undefined') {
    if (!cells.filename.includes('Core')) {
        CONSOLE.log(`importing ${notebook} - 1 cell - ${cells.id}`)
    }

    if (cells.language == 'python') {
        return makePythonModule(cells.code, ctx)
    }
}

Incomplete Documentation

The code snippet appears to be incomplete, as there are several TODO comments and some functions are not fully defined. Additionally, the importNotebook function seems to return a Promise, but it is not clear what the resolution value is.