lib/mobo.js

Summary

Maintainability
C
1 day
Test Coverage
/**
 * This is the main module that makes use of the several submodules.
 *
 * It triggers the generation and upload of the model.
 */

//////////////////////////////////////////
// Requirements                         //
//////////////////////////////////////////

var fs                = require('fs-extra');
var path              = require('path');
var semlog            = require('semlog');
var log               = semlog.log;

var readProject       = require('./input/readProject.js');

var compatibilityLayer     = require('./modelToModel/compatibilityLayer.js');
var intermediaryLayer     = require('./modelToModel/intermediaryLayer.js');
var smwLayer               = require('./modelToModel/smwLayer.js');
var generateWikiStructure  = require('./modelToText/generateWikiStructure.js');
var generateGraph     = require('./modelToText/generateGraph.js');

var writeExportFiles  = require('./output/writeExportFiles.js');
var uploadExportFiles = require('./output/uploadExportFiles.js');

var validate          = require('./util/validate.js');
var statistics        = require('./util/statistics.js');
var moboUtil          = require('./util/moboUtil.js');

var packageJSON       = require('./../package');


//////////////////////////////////////////
// Variables                            //
//////////////////////////////////////////

/** Microtimestamp for benchmarking */
exports.start = (new Date()).getTime();

/** Mobo's currently running */
exports.running = false;

/** mobo registry, containing the model in it's various processing stages */
exports.registry = {};


//////////////////////////////////////////
// MOBO BOOTSTRAP FUNCTIONS             //
//////////////////////////////////////////

/**
 * Triggers the complete mobo generation process
 *
 * @param {{}}              settings        Settings Object
 * @param {function|false}  onGenerated     onGenerated callback function
 * @param {function}        callback        callback function when finally done
 */
exports.exec = function(settings, onGenerated, callback) {
    'use strict';

    if (exports.running === true) {
        log('[W] mobo is currently running, skipping execution');
        return;
    }

    exports.start = (new Date()).getTime();
    exports.running = true;
    exports.settings = settings;
    semlog.clearLogHistory();

    // Put current settings into the log object, remove the password before
    var safeSettings = JSON.parse(JSON.stringify(settings)); // deep copy
    safeSettings.mw_password = '**********';
    log(safeSettings, true);

    log(' ');
    log('mobo v' + packageJSON.version + ' [' + semlog.humanDate((new Date())) + ']');
    log('================================================================================');


    //////////////////////////////////////////
    // MOBO MAIN SEQUENCE                   //
    //////////////////////////////////////////

    // 1) READ PROJECT FILES FROM FILE SYSTEM
    var q = readProject.exec(settings.importModelDir);

    q.then(function(registry) {


        exports.registry = registry;
        exports.registry.settings = settings;

        // Mobo related statistics
        registry.statistics.$mobo = {
            version: 'v' + packageJSON.version,
            timestamp: Math.round(exports.start / 1000), // UNIX Timestamp (Microtimestamp / 1000)
            date: semlog.humanDate(exports.now)        // Human readable date
        };

        // Benchmark statistics
        registry.statistics.benchmark = {
            readProject: (new Date()).getTime() - exports.start
        };

        // Compatibility Layer
        if (settings.compatibilityLayer) {
            registry = compatibilityLayer.exec(settings, registry);
        }

        // 2) EXTEND PROJECT TO EXPANDED REGISTRY

        registry = intermediaryLayer.exec(settings, registry);

        registry = smwLayer.exec(settings, registry);


        // 3) PRE-VALIDATE PROJECT / REGISTRY

        registry.statistics.benchmark.expansionLayer = (new Date()).getTime() - exports.start;

        return validate.validateRegistry(exports.registry);

    });


    q.then(function(registry) {

        // 4) POST-VALIDATE EXTENDED REGISTRY

        registry.statistics.benchmark.validateRegistry = (new Date()).getTime() - exports.start;

        return validate.validateExpandedRegistry(exports.registry);
    });

    q.then(function(registry) {

        // 5) OPTIONAL: BUILD GRAPH FROM MODEL

        registry.statistics.benchmark.validateExpandedRegistry = (new Date()).getTime() - exports.start;
        return generateGraph.exec(settings, registry);
    });


    q.then(function(registry) {

        // 6) GENERATE WIKI STRUCTURE

        registry.statistics.benchmark.generateGraph = (new Date()).getTime() - exports.start;
        return generateWikiStructure.exec(settings, registry);
    });


    q.then(function(registry) {

        // 7) OPTIONAL: WRITE EXPORT FILES

        // Calculate registry statistics if enabled
        if (settings.statistics) {
            registry = statistics.registryStatistics(settings, registry);
        }

        // Write registry to file (used by the WebApp)
        fs.outputFile(settings.processedModelDir + '/_registry.json', JSON.stringify(registry, null, 4), function() {
            if (onGenerated) {
                onGenerated(); // Refreshes the webapp if given
            }
        });

        registry.statistics.benchmark.generateWikiStructure = (new Date()).getTime() - exports.start;

        return writeExportFiles.exec(settings, registry);

    });

    q.then(function(registry) {

        // 8) UPLOAD TO WIKI

        registry.statistics.benchmark.writeExportFiles = (new Date()).getTime() - exports.start;

        // This has to be a callback, because the nodemw library made problems with promises
        return uploadExportFiles.exec(settings, registry, function(err) {

            // 9) COMPLETED! WRITING FILES & STATISTICS

            registry.statistics.benchmark.uploadExportFiles = (new Date()).getTime() - exports.start;


            if (registry.statistics && registry.statistics.changes) {
                statistics.printStatistics('DIFF  | ', {
                    'Added': registry.statistics.changes.added,
                    'Changed': registry.statistics.changes.changed,
                    'Removed': registry.statistics.changes.removed
                });
            }

            statistics.printStatistics('TIME  | ', {
                'Input': registry.statistics.benchmark.readProject + 'ms',
                'Processing': registry.statistics.benchmark.writeExportFiles + 'ms',
                'Output / Upload': registry.statistics.benchmark.uploadExportFiles + 'ms'
            });

            if (settings.writeLogFile) {

                moboUtil.writeLogHistory(settings.processedModelDir + '/logfiles/');

            }

            if (settings.statistics) {
                statistics.writeStatistics(registry.statistics, settings.processedModelDir);
            }

            if (settings.verbose) {
                semlog.debug(registry.statistics);
            }

            if (err) {
                log('[E] Error while uploading to the wiki: ' + err.message);
                log(err);
                if (callback) {
                    callback(err, false);
                }
            }

            exports.running = false;

            if (settings.uploadReport) {
                exports.running = true;
                uploadExportFiles.uploadReport(registry, function() {
                    exports.running = false;
                });
            }

            if (callback) {
                callback(false, registry.generated);
            }
        });
    });

    q.catch(function(e) {

        // X) HANDLE ERRORS (FINAL CATCH)

        log('[E] mobo runtime error!');
        exports.running = false;
        log(e);
    });

};


//////////////////////////////////////////
// CLI COMMANDS                         //
//////////////////////////////////////////

/**
 * Reads the /cli.md file and returns it as a string
 * @returns {string}
 */
exports.getHelp = function() {
    'use strict';
    return fs.readFileSync(__dirname + '/cli.md').toString();
};

/**
 * Returns the current version of mobo.
 * The version is defined in the package.json file in the root directory
 *
 * @returns {exports.version|*}
 */
exports.getVersion = function() {
    'use strict';
    return packageJSON.version;
};

/**
 * Creates a new model example in the current working directory
 *
 * @param {String}  name            Name of the example. Must match the folder name in /examples/
 * @param {String}  customDirectory Custom directory to install the sample project in
 *
 * @return {boolean} success
 */
exports.install = function(name, customDirectory) {
    'use strict';

    var fs  = require('fs-extra');
    var cwd = customDirectory || process.cwd();

    var currentDir = fs.readdirSync(cwd);

    var installExample = function(name) {

        try {
            fs.copySync(__dirname + '/../examples/' + name, cwd);
            log('[S] Created example "' + name + '" project structure!');
            return true;
        } catch (err) {
            console.error(err);
            return false;
        }

    };

    try {
        fs.readdirSync(__dirname + '/../examples/' + name);
    } catch (e) {
        log('[E] Example "' + name + '" does not exist.');
        var examples = fs.readdirSync(__dirname + '/../examples/');
        log('[i] Available examples: ' + examples.join(', '));
        return false;
    }

    // Create a new init project structure if current directory is empty
    if (currentDir.length > 0) {
        if (currentDir.indexOf('settings.yaml') > -1) {
            log('[i] Skipping init: Current directory has already a project structure!');

            // If an example is to be installed, copy those files on top on the init structure
            if (name !== 'init') {
                installExample(name);
            }

        } else {
            log('[E] Aborting init process: Current directory is not empty!');
            return false;
        }

    } else {

        try {
            fs.copySync(__dirname + '/../examples/init/', cwd);

            log('[S] Created initial project structure');
            log('[i] Please adjust your settings.yaml now');

            // If an example is to be installed, copy those files on top on the init structure
            if (name !== 'init') {
                installExample(name);
            }

            return true;

        } catch (err) {
            console.error(err);
            return false;
        }

    }

    return true; // Success

};

/**
 * This updates the templates of the current directory with the current default templates from mobo
 * Sometimes an update is necessary if new features are introduced.
 * Creates a backup of the current project templates first.
 */
exports.update = function(settings) {
    'use strict';

    var cwd = settings.cwd;
    var templatesDir = settings.templateDir;

    var backupDir = path.join(cwd, '/mobo_template_backup/' + semlog.roboDate((new Date())));
    var templatesSourceDir = path.join(__dirname, '/../examples/init/mobo_template/');

    log(' ');
    log('[i] This command will update the project templates with the current mobo templates.');
    log('[i] Your old templates will be backed up at /mobo_template_backup/*');
    log(' ');

    try {
        fs.mkdirsSync(cwd + '/mobo_template_backup/');
        fs.mkdirsSync(backupDir);

        fs.copySync(templatesDir, backupDir);
        fs.copySync(templatesSourceDir, templatesDir);

        log('[S] Backup of project template files to \n  "' + backupDir + '"');

        return true;

    } catch (err) {
        console.error(err);
        throw err;
    }

};