<commit_before>/* Модуль сжатия js-скриптов, css-стилей, html-файлов и
 * конвертации картинок в css-стилях
 * в base64 и помещения их в файл стилей
 */
var Minify = {};
console.log('minify.js loaded...');

/* функция сжимает js-скрипты
 * и сохраняет их с именем .min.js
 */

var fs = require('fs');
var path=require('path');
var crypto = require('crypto');

try{
    var html = require('./lib/html');
    var js = require('./lib/js');
    var css = require('./lib/css');
    var img = require('./lib/img');

    Minify._uglifyJS = js._uglifyJS;
    Minify._cleanCSS = css._cleanCSS;
    Minify.htmlMinify = html.htmlMinify;
/*********************************/
}
catch(pError){
    console.log(pError);
    console.log('One of the necessary modules is absent\n'  +
        're-install the module\n'                           +
        'npm r minify\n'                                    +
        'npm i minify');
}

/* object contains hashes of files*/
var Hashes;
/* boolean hashes.json changed or not */
var HashesChanged_b;

var MinFolder='min/';
/* function clear MinFolder
 * if we could not create
 * directory and it is
 * not exist
 */
var folderExist = function(pError, pStat){
    "use strict";
    /*file found and it's directory */
    if(!pError && pStat.isDirectory())
        console.log('folder exist: ' + MinFolder);
    else MinFolder='/';
};

/*
 * function says thet folder created
 * if everything is OK, or
 * moves to folderExist function
 */
var makeFolder = function(pError){
    "use strict";
    /*folder created successfully*/
    if(!pError)
        console.log('folder created: min');
    else fs.stat(MinFolder,folderExist);
};

/* Trying to create folder min
 * where woud be minifyed versions
 * of files 511(10)=777(8)
 * rwxrwxrwx
 */
fs.mkdir(MinFolder, 511, makeFolder);

exports.MinFolder = MinFolder;
exports.Cache    = {};

/*
 * Функция ищет в имени файла расширение
 * и если находит возвращает true
 * @pName - получает имя файла
 * @pExt - расширение
 */
Minify._checkExtension=function(pName,pExt)
{
    /* если длина имени больше
     * длинны расширения -
     * имеет смысл продолжать
     */
    if(pName.length>pExt.length){
        var lLength=pName.length;           /* длина имени*/
        var lExtNum=pName.lastIndexOf(pExt);/* последнее вхождение расширения*/
        var lExtSub=lLength-lExtNum;        /* длина расширения*/
        /* если pExt - расширение pName */
        if(lExtSub===pExt.length)
            return true;
        else
            return false;
    }
    else return false;
};

/* function gets file extension
 * @pFileName
 * return Ext
 */
Minify._getExtension = function(pFileName){
    /* checking for js/html/css */

    var lExt;
    var lCheck_f = Minify._checkExtension;

    if (lCheck_f(pFileName, 'css'))
        lExt = '.css';
    else if(lCheck_f(pFileName, 'html'))
        lExt = '.html';
    else if (lCheck_f(pFileName,'js'))
        lExt = '.js';

    return lExt;
};

/* function minificate js,css and html files
 * @pFiles_a                - array of js, css and html file names or string, if name
 *                            single, or object if postProcessing neaded
 *                              {'client.js': function(pFinalCode){} }
 *                            or convertion images to base64 neaded
 *                              {'style.css': true}
 *                            or {'style.css':{minimize: true, func: function(){}}
 *
 * @pOptions                - object contain main options
 *                          if cache true files do not writes on disk, just saves
 *                              in Minify Cache
 * Example:
 * {cache: false, gzip: true, callback: func(){}}
 */
exports.optimize = function(pFiles_a, pOptions){
    'use strict';

     /* if passed string, or object
      * putting it to array
      */
    if (typeof pFiles_a === 'string' ||
        !pFiles_a[0])
            pFiles_a=[pFiles_a];

    var lName;

    var lAllCSS = '';
    var lCSS_o;

    /* varible contains all readed file names */
    var lReadedFilesCount=0;
    /*
     * Processing of files
     * @pFileName       - name of file
     * @pData           - data of file
     * @pForce          - minify file anyway
     */
    var dataReaded_f = function(pFileName, pData, pForce){
        ++lReadedFilesCount;
        var lLastFile_b;
        /* if leng this not equal
         * file not last
         */
        if (lReadedFilesCount === pFiles_a.length)
            lLastFile_b = true;

        /*
         * if postProcessing function exist
         * getting it from pFileName object
         */
        var lMoreProcessing_f;
        if(typeof pFileName === 'object'){
            var lName;
            for(lName in pFileName){
                break;
            }
            lMoreProcessing_f = pFileName[lName];
            pFileName = lName;
        }
        console.log('file ' + pFileName + ' readed');

        var lExt = Minify._getExtension(pFileName);
        var minFileName = pFileName.replace(lExt, '.min' + lExt);

        minFileName = path.basename(minFileName);
        minFileName = MinFolder + minFileName;

           /* functin minimize files */
        var lProcessing_f = function(){
        var final_code;

            /* getting optimized version */
            switch(lExt){
                case '.js':
                    final_code  = Minify._uglifyJS(pData);
                    break;

                case '.html':
                    final_code  = Minify.htmlMinify(pData);
                    break;

                case '.css':
                    final_code  = Minify._cleanCSS(pData);

                    lAllCSS    += final_code;

                    /* in css could be lCSS_o
                     * {img: true, moreProcessing: function(){}}
                     */
                    if(!lCSS_o){
                        lCSS_o = lMoreProcessing_f;
                        if (typeof lCSS_o === 'object'){
                                lMoreProcessing_f = lCSS_o.moreProcessing;
                        }
                    }
                    break;

                default:
                    return console.log('unknow file type '  +
                        lExt + ', only *.js, *.css, *.html');
            }
            /* if it's last file
             * and base64images setted up
             * se should convert it
             */
            if (lLastFile_b && (lCSS_o && lCSS_o.img ||
                lCSS_o === true)){
                    if(isFileChanged('all.min.css', lAllCSS))
                        base64_images(lAllCSS);
            }

            /* if lMoreProcessing_f seeted up
             * and function associated with
             * current file name exists -
             * run it
             */
            if(lMoreProcessing_f                    &&
                typeof lMoreProcessing_f === "function"){
                    final_code = lMoreProcessing_f(final_code);
            }

            /* записываем сжатый js-скрипт
             * в кэш если установлен pCache_b
             * или на диск, если не установлен
             */
            if(pOptions && pOptions.cache){
                exports.Cache[minFileName] = final_code;
                console.log('file ' + minFileName + ' saved to cache...');
            }
            else{
                /* minimized file will be in min file
                 * if it's possible if not -
                 * in root
                 */
                fs.writeFile(minFileName, final_code, fileWrited(minFileName));
            }
        };

        if(isFileChanged(pFileName, pData, lLastFile_b)){
            lProcessing_f();
          /* if file was not changed */
        } else{
            fs.readFile(minFileName, function(pError, pFinalCode){
                /* if could not read file call forse minification */
                if(pError)
                    lProcessing_f();

                /* if need to save in cache - do it */
                else {
                    if(pOptions){
                        if(pOptions.cache){
                            exports.Cache[minFileName] = pFinalCode;
                            console.log('file ' + minFileName + ' saved to cache...');
                        }

                        if(pOptions.callback &&
                            typeof pOptions.callback === 'function')
                                pOptions.callback();
                    }
                }
            });
        }
        /* calling callback function if it exist
         */
        if(pOptions &&
            pOptions.callback &&
            typeof pOptions.callback === 'function')
                pOptions.callback();
    };

    /* moving thru all elements of js files array */
    for(var i=0; pFiles_a[i]; i++){
        /* if postProcessing function exist
         * getting file name and passet next
         */
        var lPostProcessing_o = pFiles_a[i];
        if(typeof lPostProcessing_o === 'object'){
            for(lName in lPostProcessing_o){
            }
        } else lName = pFiles_a[i];
        console.log('reading file ' + lName + '...');

        /* if it's last file send true */
        fs.readFile(lName,
            fileReaded(pFiles_a[i],
                dataReaded_f));
    }
    /* saving the name of last readed file for hash saving function */

    return true;
};

/* функция переводит картинки в base64 и записывает в css-файл*/
function base64_images(pFileContent_s){
    'use strict';
     var b64img;
     try{
        b64img = require('css-b64-images');
    }catch(error){
        console.log('can\'n load clean-css \n'    +
                'npm install -g css-b64-images\n' +
                'https://github.com/Filirom1/css-base64-images');

        fs.writeFile(MinFolder + 'all.min.css',
            pFileContent_s,
            fileWrited(MinFolder + 'all.min.css'));

        return pFileContent_s;
    }
    b64img.fromString(pFileContent_s, '.','', function(err, css){
        console.log('images converted to base64 and saved in css file');
        fs.writeFile(MinFolder + 'all.min.css', css, fileWrited(MinFolder + 'all.min.css'));
    });
}

/* Функция создаёт асинхроную версию
 * для чтения файла
 * @pFileName       - имя считываемого файла
 * @pProcessFunc    - функция обработки файла
 * @pLastFile_b     - последний файл?
 */
function fileReaded(pFileName,pProcessFunc, pLastFile_b){
    "use strict";
    return function(pError,pData){
        /* функция в которую мы попадаем,
         * если данные считались
         *
         * если ошибка - показываем её
         * иначе если переданная функция -
         * функция запускаем её
         */
        if(!pError)
            if (pProcessFunc && typeof pProcessFunc==="function")
                    pProcessFunc(pFileName,pData.toString(), pLastFile_b);
        else console.log(pError);
    };
}

/*
 * Функция вызываеться после записи файла
 * и выводит ошибку или сообщает,
 * что файл успешно записан
 */
function fileWrited(pFileName){
    "use strict";
    return function(error){
        console.log(error?error:('file '+pFileName+' writed...'));
    };
}

/*
 * Function reads hash table of files
 * checks is file changed or not
 * and return result.
 * @pFileName - name of file
 * @pFileData - data of file
 * result: boolean
 */
function isFileChanged(pFileName, pFileData, pLastFile_b){
        var lReadedHash;

        if(!Hashes)
            try {
                /* try to read file with hashes */
                console.log('trying  to read hashes.json');
                Hashes = require(process.cwd()+'/hashes');
            }catch(pError) {
                console.log('hashes.json not found... \n');
                Hashes = {};
            }

        for(var lFileName in Hashes)
            /* if founded row with
             * file name - save hash
             */
            if (lFileName === pFileName) {
                lReadedHash = Hashes[pFileName];
                break;
        }

        /* create hash of file data */
        var lFileHash = crypto.createHash('sha1');
        lFileHash = crypto.createHash('sha1');
        lFileHash.update(pFileData);
        lFileHash = lFileHash.digest('hex');

        if(Hashes[pFileName] !== lFileHash){
            Hashes[pFileName] = lFileHash;
            HashesChanged_b = true;
        }

        if(pLastFile_b){
            /* if hashes file was changes - write it */
            if(HashesChanged_b)
                fs.writeFile('./hashes.json',
                    JSON.stringify(Hashes),
                    fileWrited('./hashes.json'));
            else console.log('no one file has been changed');
        }
        /* has file changed? */
        return lReadedHash !== lFileHash;
}<commit_msg>added parameters to calback function<commit_after>/* Модуль сжатия js-скриптов, css-стилей, html-файлов и
 * конвертации картинок в css-стилях
 * в base64 и помещения их в файл стилей
 */
var Minify = {};
console.log('minify.js loaded...');

/* функция сжимает js-скрипты
 * и сохраняет их с именем .min.js
 */

var fs = require('fs');
var path=require('path');
var crypto = require('crypto');

try{
    var html = require('./lib/html');
    var js = require('./lib/js');
    var css = require('./lib/css');
    var img = require('./lib/img');

    Minify._uglifyJS = js._uglifyJS;
    Minify._cleanCSS = css._cleanCSS;
    Minify.htmlMinify = html.htmlMinify;
/*********************************/
}
catch(pError){
    console.log(pError);
    console.log('One of the necessary modules is absent\n'  +
        're-install the module\n'                           +
        'npm r minify\n'                                    +
        'npm i minify');
}

/* object contains hashes of files*/
var Hashes;
/* boolean hashes.json changed or not */
var HashesChanged_b;

var MinFolder='min/';
/* function clear MinFolder
 * if we could not create
 * directory and it is
 * not exist
 */
var folderExist = function(pError, pStat){
    "use strict";
    /*file found and it's directory */
    if(!pError && pStat.isDirectory())
        console.log('folder exist: ' + MinFolder);
    else MinFolder='/';
};

/*
 * function says thet folder created
 * if everything is OK, or
 * moves to folderExist function
 */
var makeFolder = function(pError){
    "use strict";
    /*folder created successfully*/
    if(!pError)
        console.log('folder created: min');
    else fs.stat(MinFolder,folderExist);
};

/* Trying to create folder min
 * where woud be minifyed versions
 * of files 511(10)=777(8)
 * rwxrwxrwx
 */
fs.mkdir(MinFolder, 511, makeFolder);

exports.MinFolder = MinFolder;
exports.Cache    = {};

/*
 * Функция ищет в имени файла расширение
 * и если находит возвращает true
 * @pName - получает имя файла
 * @pExt - расширение
 */
Minify._checkExtension=function(pName,pExt)
{
    /* если длина имени больше
     * длинны расширения -
     * имеет смысл продолжать
     */
    if(pName.length>pExt.length){
        var lLength=pName.length;           /* длина имени*/
        var lExtNum=pName.lastIndexOf(pExt);/* последнее вхождение расширения*/
        var lExtSub=lLength-lExtNum;        /* длина расширения*/
        /* если pExt - расширение pName */
        if(lExtSub===pExt.length)
            return true;
        else
            return false;
    }
    else return false;
};

/* function gets file extension
 * @pFileName
 * return Ext
 */
Minify._getExtension = function(pFileName){
    /* checking for js/html/css */

    var lExt;
    var lCheck_f = Minify._checkExtension;

    if (lCheck_f(pFileName, 'css'))
        lExt = '.css';
    else if(lCheck_f(pFileName, 'html'))
        lExt = '.html';
    else if (lCheck_f(pFileName,'js'))
        lExt = '.js';

    return lExt;
};

/* function minificate js,css and html files
 * @pFiles_a                - array of js, css and html file names or string, if name
 *                            single, or object if postProcessing neaded
 *                              {'client.js': function(pFinalCode){} }
 *                            or convertion images to base64 neaded
 *                              {'style.css': true}
 *                            or {'style.css':{minimize: true, func: function(){}}
 *
 * @pOptions                - object contain main options
 *                          if cache true files do not writes on disk, just saves
 *                              in Minify Cache
 * Example:
 * {cache: false, gzip: true, callback: func(){}}
 */
exports.optimize = function(pFiles_a, pOptions){
    'use strict';

     /* if passed string, or object
      * putting it to array
      */
    if (typeof pFiles_a === 'string' ||
        !pFiles_a[0])
            pFiles_a=[pFiles_a];

    var lName;

    var lAllCSS = '';
    var lCSS_o;

    /* varible contains all readed file names */
    var lReadedFilesCount=0;
    /*
     * Processing of files
     * @pFileName       - name of file
     * @pData           - data of file
     * @pForce          - minify file anyway
     */
    var dataReaded_f = function(pFileName, pData, pForce){
        ++lReadedFilesCount;
        var lLastFile_b;
        /* if leng this not equal
         * file not last
         */
        if (lReadedFilesCount === pFiles_a.length)
            lLastFile_b = true;

        /*
         * if postProcessing function exist
         * getting it from pFileName object
         */
        var lMoreProcessing_f;
        if(typeof pFileName === 'object'){
            var lName;
            for(lName in pFileName){
                break;
            }
            lMoreProcessing_f = pFileName[lName];
            pFileName = lName;
        }
        console.log('file ' + pFileName + ' readed');

        var lExt = Minify._getExtension(pFileName);
        var minFileName = pFileName.replace(lExt, '.min' + lExt);

        minFileName = path.basename(minFileName);
        minFileName = MinFolder + minFileName;

           /* functin minimize files */
        var lProcessing_f = function(){
        var final_code;

            /* getting optimized version */
            switch(lExt){
                case '.js':
                    final_code  = Minify._uglifyJS(pData);
                    break;

                case '.html':
                    final_code  = Minify.htmlMinify(pData);
                    break;

                case '.css':
                    final_code  = Minify._cleanCSS(pData);

                    lAllCSS    += final_code;

                    /* in css could be lCSS_o
                     * {img: true, moreProcessing: function(){}}
                     */
                    if(!lCSS_o){
                        lCSS_o = lMoreProcessing_f;
                        if (typeof lCSS_o === 'object'){
                                lMoreProcessing_f = lCSS_o.moreProcessing;
                        }
                    }
                    break;

                default:
                    return console.log('unknow file type '  +
                        lExt + ', only *.js, *.css, *.html');
            }
            /* if it's last file
             * and base64images setted up
             * se should convert it
             */
            if (lLastFile_b && (lCSS_o && lCSS_o.img ||
                lCSS_o === true)){
                    if(isFileChanged('all.min.css', lAllCSS))
                        base64_images(lAllCSS);
            }

            /* if lMoreProcessing_f seeted up
             * and function associated with
             * current file name exists -
             * run it
             */
            if(lMoreProcessing_f                    &&
                typeof lMoreProcessing_f === "function"){
                    final_code = lMoreProcessing_f(final_code);
            }

            /* записываем сжатый js-скрипт
             * в кэш если установлен pCache_b
             * или на диск, если не установлен
             */
            if(pOptions && pOptions.cache){
                exports.Cache[minFileName] = final_code;
                console.log('file ' + minFileName + ' saved to cache...');
            }
            else{
                /* minimized file will be in min file
                 * if it's possible if not -
                 * in root
                 */
                fs.writeFile(minFileName, final_code, fileWrited(minFileName));
            }

            /* calling callback function if it exist */
            if(pOptions &&
                pOptions.callback &&
                typeof pOptions.callback === 'function')
                    pOptions.callback(final_code);
        };

        if(isFileChanged(pFileName, pData, lLastFile_b)){
            lProcessing_f();
          /* if file was not changed */
        } else{
            fs.readFile(minFileName, function(pError, pFinalCode){
                /* if could not read file call forse minification */
                if(pError)
                    lProcessing_f();

                /* if need to save in cache - do it */
                else {
                    if(pOptions){
                        if(pOptions.cache){
                            exports.Cache[minFileName] = pFinalCode;
                            console.log('file ' + minFileName + ' saved to cache...');
                        }

                        if(pOptions.callback &&
                            typeof pOptions.callback === 'function')
                                pOptions.callback(pFinalCode);
                    }
                }
            });
        }
    };

    /* moving thru all elements of js files array */
    for(var i=0; pFiles_a[i]; i++){
        /* if postProcessing function exist
         * getting file name and passet next
         */
        var lPostProcessing_o = pFiles_a[i];
        if(typeof lPostProcessing_o === 'object'){
            for(lName in lPostProcessing_o){
            }
        } else lName = pFiles_a[i];
        console.log('reading file ' + lName + '...');

        /* if it's last file send true */
        fs.readFile(lName,
            fileReaded(pFiles_a[i],
                dataReaded_f));
    }
    /* saving the name of last readed file for hash saving function */

    return true;
};

/* функция переводит картинки в base64 и записывает в css-файл*/
function base64_images(pFileContent_s){
    'use strict';
     var b64img;
     try{
        b64img = require('css-b64-images');
    }catch(error){
        console.log('can\'n load clean-css \n'    +
                'npm install -g css-b64-images\n' +
                'https://github.com/Filirom1/css-base64-images');

        fs.writeFile(MinFolder + 'all.min.css',
            pFileContent_s,
            fileWrited(MinFolder + 'all.min.css'));

        return pFileContent_s;
    }
    b64img.fromString(pFileContent_s, '.','', function(err, css){
        console.log('images converted to base64 and saved in css file');
        fs.writeFile(MinFolder + 'all.min.css', css, fileWrited(MinFolder + 'all.min.css'));
    });
}

/* Функция создаёт асинхроную версию
 * для чтения файла
 * @pFileName       - имя считываемого файла
 * @pProcessFunc    - функция обработки файла
 * @pLastFile_b     - последний файл?
 */
function fileReaded(pFileName,pProcessFunc, pLastFile_b){
    "use strict";
    return function(pError,pData){
        /* функция в которую мы попадаем,
         * если данные считались
         *
         * если ошибка - показываем её
         * иначе если переданная функция -
         * функция запускаем её
         */
        if(!pError)
            if (pProcessFunc && typeof pProcessFunc==="function")
                    pProcessFunc(pFileName,pData.toString(), pLastFile_b);
        else console.log(pError);
    };
}

/*
 * Функция вызываеться после записи файла
 * и выводит ошибку или сообщает,
 * что файл успешно записан
 */
function fileWrited(pFileName){
    "use strict";
    return function(error){
        console.log(error?error:('file '+pFileName+' writed...'));
    };
}

/*
 * Function reads hash table of files
 * checks is file changed or not
 * and return result.
 * @pFileName - name of file
 * @pFileData - data of file
 * result: boolean
 */
function isFileChanged(pFileName, pFileData, pLastFile_b){
        var lReadedHash;

        if(!Hashes)
            try {
                /* try to read file with hashes */
                console.log('trying  to read hashes.json');
                Hashes = require(process.cwd()+'/hashes');
            }catch(pError) {
                console.log('hashes.json not found... \n');
                Hashes = {};
            }

        for(var lFileName in Hashes)
            /* if founded row with
             * file name - save hash
             */
            if (lFileName === pFileName) {
                lReadedHash = Hashes[pFileName];
                break;
        }

        /* create hash of file data */
        var lFileHash = crypto.createHash('sha1');
        lFileHash = crypto.createHash('sha1');
        lFileHash.update(pFileData);
        lFileHash = lFileHash.digest('hex');

        if(Hashes[pFileName] !== lFileHash){
            Hashes[pFileName] = lFileHash;
            HashesChanged_b = true;
        }

        if(pLastFile_b){
            /* if hashes file was changes - write it */
            if(HashesChanged_b)
                fs.writeFile('./hashes.json',
                    JSON.stringify(Hashes),
                    fileWrited('./hashes.json'));
            else console.log('no one file has been changed');
        }
        /* has file changed? */
        return lReadedHash !== lFileHash;
}<|endoftext|>


<commit_before>/* Модуль сжатия js-скриптов, css-стилей, html-файлов и
 * конвертации картинок в css-стилях
 * в base64 и помещения их в файл стилей
 */
(function() {
    'use strict';

    var DIR         = __dirname + '/',
        LIBDIR      = DIR + 'lib/',
        os          = require('os'),
        main        = require(LIBDIR + 'main'),
        img         = main.require(LIBDIR + 'img'),
        mkdirp      = main.require('mkdirp'),
        platform    = process.platform,
        WIN32       = platform === 'win32',
        crypto      = main.crypto,
        fs          = main.fs,
        path        = main.path,
        Util        = main.util,

        MinifyDir, TMPDIR, MinFolder;

    if (os.tmpdir) {
        TMPDIR      = os.tmpdir();
        MinFolder   =
        MinifyDir   = TMPDIR + '/minify/';

        if (!WIN32)
            MinFolder += process.getuid() + '/';
    } else {

        MinifyDir   =
        MinFolder   = DIR + '/min/';
    }

    exports.getName         = getName;
    exports.optimize        = optimize;
    exports.optimizeData    = main.optimize;
    exports.MinFolder       = MinFolder;

    /*
     * Trying to create folder min
     * where woud be minifyed versions
     */
    function makeDir(callback) {
        fs.exists(MinFolder, function(exist) {
            var func = Util.retExec(callback, null);

            Util.ifExec(exist || !mkdirp, func, function() {
                mkdirp(MinFolder, function(error) {
                    /* make dir with 777 mode do not work properly
                     * so change mode after creation
                     */
                    if (!error)
                        fs.chmod(MinifyDir, '777', callback);
                    else
                        callback(error);
                });
            });
        });
    }

    /**
     * function minificate js,css and html files
     *
     * @param files     -   js, css or html file path
     * @param options   -   object contain main options
     */
    function optimize(file, options) {
        makeDir(function(error) {
            var name, basename, msg,
                notLog  = options.notLog,
                isObj   = Util.isObject(file);

            if (error)
                log(error, notLog);

            if (isObj)
                name    = Object.keys(file)[0];
            else
                name    = file;

            basename    = path.basename(name);
            msg         = 'minify: reading file ' + basename + '...';

            log(msg, notLog);

             fs.readFile(name, 'utf8', Util.bind(fileRead, {
                name            : name,
                optimizeParams  : file,
                options         : options,
                callback        : onDataRead
            }));
        });
    }

   /**
    * Processing of files
    * @param fileData {name, data}
    */
    function onDataRead(fileData, error, data) {
        var ext, minFileName,
            readFilesCount  = 0,
            options         = fileData.options,
            notLog          = options.notLog,
            filename        = fileData.name,
            basename        = path.basename(filename);

        log('minify: file ' + basename + ' read', notLog);

        if (error) {
            options.callback(error);
        } else {
            ext         = Util.getExtension(filename);
            minFileName = getName(filename, ext);

            main.optimize({
                ext : ext,
                data: data
            }, function(error, data) {
                if (error)
                    Util.exec(options.callback, error);
                else
                    Util.ifExec(ext !== '.css', function(error, optimizedData) {
                        var ret = Util.isString(optimizedData);

                        if (ret)
                            data    = optimizedData;

                        ++readFilesCount;

                        writeFile(minFileName, data, notLog, function(dataMin) {
                            if (options)
                                if (options.returnName)
                                     Util.exec(options.callback, null, {
                                         name: minFileName
                                     });
                                 else
                                    Util.exec(options.callback, null, dataMin);
                            });
                        }, function(callback) {
                            img.optimize(filename, data, callback);
                        });
                });
        }
    }

    /**
     * function get name of file in min folder
     *
     * @param name
     * @param ext
     */
    function getName(name, ext) {
        var ret, minFileName;

        if (Util.isString(name)) {
            if (!ext)
                ext         = Util.getExtension(name);

            minFileName = crypto.createHash('sha1')
                .update(name)
                .digest('hex') + ext;

            ret = MinFolder + minFileName;
        }

        return ret;
    }

    function fileRead(params, error, data) {
        if (params)
            Util.exec(params.callback, params, error, data);
    }

    /*
     * Функция записывает файла
     * и выводит ошибку или сообщает,
     * что файл успешно записан
     */
    function writeFile(name, data, notLog, callback) {
        fs.writeFile(name, data, function(error) {
            var basename = path.basename(name),
                msg = 'minify: file ' + basename + ' written...';

            log(error || msg, notLog);

            Util.exec(callback, data);
        });
    }

    function log(msg, notLog) {
        if (!notLog)
            Util.log(msg);
    }

})();
<commit_msg>feature(minify) chmod -> process.umask<commit_after>/* Модуль сжатия js-скриптов, css-стилей, html-файлов и
 * конвертации картинок в css-стилях
 * в base64 и помещения их в файл стилей
 */
(function() {
    'use strict';

    var DIR         = __dirname + '/',
        LIBDIR      = DIR + 'lib/',
        os          = require('os'),
        main        = require(LIBDIR + 'main'),
        img         = main.require(LIBDIR + 'img'),
        mkdirp      = main.require('mkdirp'),
        platform    = process.platform,
        WIN32       = platform === 'win32',
        crypto      = main.crypto,
        fs          = main.fs,
        path        = main.path,
        Util        = main.util,

        MinifyDir, TMPDIR, MinFolder;

    if (os.tmpdir) {
        TMPDIR      = os.tmpdir();
        MinFolder   =
        MinifyDir   = TMPDIR + '/minify/';

        if (!WIN32)
            MinFolder += process.getuid() + '/';
    } else {

        MinifyDir   =
        MinFolder   = DIR + '/min/';
    }

    exports.getName         = getName;
    exports.optimize        = optimize;
    exports.optimizeData    = main.optimize;
    exports.MinFolder       = MinFolder;

    /*
     * Trying to create folder min
     * where woud be minifyed versions
     */
    function makeDir(callback) {
        fs.exists(MinFolder, function(exist) {
            var func = Util.retExec(callback, null);
            Util.ifExec(exist || !mkdirp, func, function() {
                var ANY_MASK    = 0,
                    umask       = process.umask(ANY_MASK);
                /*
                 * change creation mask to any
                 * save current mask
                 */
                mkdirp(MinFolder, function(error) {
                    process.umask(umask);
                    callback(error);
                });
            });
        });
    }

    /**
     * function minificate js,css and html files
     *
     * @param files     -   js, css or html file path
     * @param options   -   object contain main options
     */
    function optimize(file, options) {
        makeDir(function(error) {
            var name, basename, msg,
                notLog  = options.notLog,
                isObj   = Util.isObject(file);

            if (error)
                log(error, notLog);

            if (isObj)
                name    = Object.keys(file)[0];
            else
                name    = file;

            basename    = path.basename(name);
            msg         = 'minify: reading file ' + basename + '...';

            log(msg, notLog);

             fs.readFile(name, 'utf8', Util.bind(fileRead, {
                name            : name,
                optimizeParams  : file,
                options         : options,
                callback        : onDataRead
            }));
        });
    }

   /**
    * Processing of files
    * @param fileData {name, data}
    */
    function onDataRead(fileData, error, data) {
        var ext, minFileName,
            readFilesCount  = 0,
            options         = fileData.options,
            notLog          = options.notLog,
            filename        = fileData.name,
            basename        = path.basename(filename);

        log('minify: file ' + basename + ' read', notLog);

        if (error) {
            options.callback(error);
        } else {
            ext         = Util.getExtension(filename);
            minFileName = getName(filename, ext);

            main.optimize({
                ext : ext,
                data: data
            }, function(error, data) {
                if (error)
                    Util.exec(options.callback, error);
                else
                    Util.ifExec(ext !== '.css', function(error, optimizedData) {
                        var ret = Util.isString(optimizedData);

                        if (ret)
                            data    = optimizedData;

                        ++readFilesCount;

                        writeFile(minFileName, data, notLog, function(dataMin) {
                            if (options)
                                if (options.returnName)
                                     Util.exec(options.callback, null, {
                                         name: minFileName
                                     });
                                 else
                                    Util.exec(options.callback, null, dataMin);
                            });
                        }, function(callback) {
                            img.optimize(filename, data, callback);
                        });
                });
        }
    }

    /**
     * function get name of file in min folder
     *
     * @param name
     * @param ext
     */
    function getName(name, ext) {
        var ret, minFileName;

        if (Util.isString(name)) {
            if (!ext)
                ext         = Util.getExtension(name);

            minFileName = crypto.createHash('sha1')
                .update(name)
                .digest('hex') + ext;

            ret = MinFolder + minFileName;
        }

        return ret;
    }

    function fileRead(params, error, data) {
        if (params)
            Util.exec(params.callback, params, error, data);
    }

    /*
     * Функция записывает файла
     * и выводит ошибку или сообщает,
     * что файл успешно записан
     */
    function writeFile(name, data, notLog, callback) {
        fs.writeFile(name, data, function(error) {
            var basename = path.basename(name),
                msg = 'minify: file ' + basename + ' written...';

            log(error || msg, notLog);

            Util.exec(callback, data);
        });
    }

    function log(msg, notLog) {
        if (!notLog)
            Util.log(msg);
    }

})();
<|endoftext|>


<commit_before>
var logger = require('./logging').getLogger(__LOGGER__),
	React = require('react/addons'),
	RequestContext = require('./context/RequestContext'),
	ClientCssHelper = require('./util/ClientCssHelper'),
	Q = require('q'),
	config = require('./config'),
	ExpressServerRequest = require("./ExpressServerRequest"),
	PageUtil = require("./util/PageUtil"),
	PromiseUtil = require("./util/PromiseUtil");

// TODO FIXME ??
// It *might* be worthwhile to get rid of all the closure-y things in render()
// https://developers.google.com/speed/articles/optimizing-javascript

var DATA_LOAD_WAIT = 250;

class Renderer {

	constructor (context) {
		this.context = context;
		this.router = context.router;
		this._userDataPromise = context.loadUserData();
	}

	render (handlerResult) {
		logger.debug("Triggering userData load");
		beginRender(req, res, start, context, this._userDataPromise, handlerResult);
	}

}

/**
 * renderMiddleware entrypoint. Called by express for every request.
 */
module.exports = function(routes) {

	return function (req, res, next) {

		var start = new Date();

		logger.debug('request: %s', req.path);

		// TODO? pull this context building into its own middleware
		var context = new RequestContext.Builder()
				.setRoutes(routes)
				.setLoaderOpts({}) // TODO FIXME
				.setDefaultXhrHeadersFromRequest(req)
				.create({
					// TODO: context opts?
				});

		// setup navigation handler (TODO: should we have a 'once' version?)
		context.onNavigate( (err, page) => {

			if (err) {
				logger.error("onNavigate error", err);
				if (err.status && err.status === 404) {
					next();
				} else if (err.status === 301 || err.status === 302) {
					res.redirect(err.status, err.redirectUrl);
				} else {
					next(err);
				}
				return;
			}

			logger.debug('Executing navigate action');

			var userDataPromise = context.loadUserData();
			beginRender(req, res, start, context, userDataPromise, page);

		});

		context.navigate(new ExpressServerRequest(req));

	}
}

function beginRender(req, res, start, context, userDataDfd, page) {

	var routeName = context.navigator.getCurrentRoute().name;

	logger.debug("Route Name: %s", routeName);

	// regardless of what happens, write out the header part
	// TODO: should this include the common.js file? seems like it
	// would give it a chance to download and parse while we're loading
	// data
	writeHeader(req, res, routeName, page);

	// user data should never be the long pole here.
	userDataDfd.done(function () {
		writeBody(req, res, context, start, page).then(() => {
			writeData(req, res, context, start)
			setupLateArrivals(req, res, context, start);
		});
	});

	// TODO: we probably want a "we're not waiting any longer for this"
	// timeout as well, and cancel the waiting deferreds
}

function writeHeader(req, res, routeName, pageObject) {
	logger.debug('Sending header');
	res.type('html');

	var pageHeader = "<!DOCTYPE html>"
		// TODO: we should allow title to be a deferred
		+ renderTitle(pageObject) + "\n"
		+ renderStylesheets(pageObject) + "\n"
		+ renderScripts(pageObject) + "\n"
		+ renderMetaTags(pageObject) + "\n"
		+ "
";
	res.write(pageHeader);
}

function renderTitle (pageObject) {
	if (!pageObject.getTitle) return "";

	return "<title>" + (pageObject.getTitle() || "") + "</title>";
}

function renderMetaTags (pageObject) {
	var metaTags = [ {charset: 'utf-8'} ];

	if (pageObject.getMetaTags) {
		var pageMetaTags = pageObject.getMetaTags();
		if (pageMetaTags.length > 0) {
			metaTags = metaTags.concat(pageMetaTags);
		}
	}

	return metaTags.map( tagData => {
		// TODO: escaping
		var tag = '<meta ';
		tag += Object.keys(tagData).map( metaAttrName => {
			return metaAttrName + '="' + tagData[metaAttrName] + '"';
		}).join(' ');
		tag += '>';
		return tag;
	}).join("\n");
}

function renderScripts(pageObject) {
	if (!pageObject.getHeadScriptFiles) return "";

	// default script
	var scripts = pageObject.getHeadScriptFiles();
	if (scripts && !Array.isArray(scripts)) {
		scripts = [scripts];
	}

	return scripts.map( (scriptPath) => {
		// make sure there's a leading '/'
		return ''
	}).join("\n");

}

function renderStylesheets (pageObject) {
	if (!pageObject.getHeadStylesheet) return "";

	var stylesheet = pageObject.getHeadStylesheet();
	if (!stylesheet) {
		return "";
	}
	return '<link rel="stylesheet" type="text/css" href="' + stylesheet + '" id="'+ ClientCssHelper.PAGE_CSS_NODE_ID + '">'
}

/**
 * Writes out the ReactElements to the response. Returns a promise that fulfills when
 * all the ReactElements have been written out.
 */
function writeBody(req, res, context, start, page) {

	logger.debug("React Rendering");
	// standardize to an array of EarlyPromises of ReactElements
	var elementPromises = PageUtil.standardizeElements(page.getElements());

	// a boolean array to keep track of which elements have already been rendered. this is useful
	// bookkeeping when the timeout happens so we don't double-render anything.
	var rendered = [];

	// render the elements in sequence when they resolve (i.e. tell us they are ready).
	// I learned how to chain an array of promises from http://bahmutov.calepin.co/chaining-promises.html
	var noTimeoutRenderPromise =  elementPromises.reduce((chain, next, index) => {
		return chain.then((element) => {
	 		if (!rendered[index-1]) renderElement(res, element, context, index - 1);
	 		rendered[index - 1] = true;
			return next;
		})
	}).then((element) => {
		// reduce is called length - 1 times. we need to call one final time here to make sure we
		// chain the final promise.
 		if (!rendered[elementPromises.length - 1]) renderElement(res, element, context, elementPromises.length - 1);
 		rendered[elementPromises.length - 1] = true;
	}).catch((err) => {
		logger.debug("Error while rendering", err);
	});

	// set up a maximum wait time for data loading. if this timeout fires, we render with whatever we have,
	// as best as possible. note that once the timeout fires, we render everything that's left
	// synchronously.
	var timeoutRenderPromise = Q.delay(DATA_LOAD_WAIT).then(() => {
		// if we rendered everything up to the last element already, just return.
		if (rendered[elementPromises.length - 1]) return;

		debug("Timeout Exceeeded. Rendering...");
		elementPromises.forEach((promise, index) => {
			if (!rendered[index]) {
				renderElement(res, promise.getValue(), context, index);
				rendered[index] = true;
			}
		});
	});

	// return a promise that resolves when either the async render OR the timeout sync
	// render happens.
	return PromiseUtil.race(noTimeoutRenderPromise, timeoutRenderPromise);
}

function renderElement(res, element, context, index) {
	res.write(`<div data-triton-root-id=${index}>`);
	if (element !== null) {
		element = React.addons.cloneWithProps(element, { context: context });
		res.write(React.renderToString(element));
	}
	res.write("
");
}

function writeData(req, res, context, start) {
	logger.debug('Exposing context state');
	res.expose(context.dehydrate(), 'InitialContext');
	res.expose(getNonInternalConfigs(), "Config");

	res.write("
"); //

	var pageFooter = ""
		+ "";

	res.write(pageFooter);

	var routeName = context.navigator.getCurrentRoute().name;

	logger.time(`content_written.${routeName}`, new Date - start);
}

function setupLateArrivals(req, res, context, start) {
	var loader = context.loader;
	var notLoaded = loader.getPendingRequests();
	var routeName = context.navigator.getCurrentRoute().name;

	notLoaded.forEach( pendingRequest => {
		pendingRequest.entry.dfd.promise.then( data => {
			logger.debug("Late arrival: %s", pendingRequest.url)
			logger.time(`late_arrival.${routeName}`, new Date - start);
			res.write("");
		})
	});

	// TODO: maximum-wait-time-exceeded-so-cancel-pending-requests code
	var promises = notLoaded.map( result => result.entry.dfd.promise );
	Q.allSettled(promises).then(function () {
		res.end("");
		logger.time(`all_done.${routeName}`, new Date - start);
	});
}

function getNonInternalConfigs() {
	var nonInternal = {};
	var fullConfig = config();
	Object.keys(fullConfig).forEach( configKey => {
		if (configKey !== 'internal') {
			nonInternal[configKey] = fullConfig[configKey];
		}
	});
	return nonInternal;
}
<commit_msg>Add a guage for number of late arrivals<commit_after>
var logger = require('./logging').getLogger(__LOGGER__({guage:{hi:1}})),
	React = require('react/addons'),
	RequestContext = require('./context/RequestContext'),
	ClientCssHelper = require('./util/ClientCssHelper'),
	Q = require('q'),
	config = require('./config'),
	ExpressServerRequest = require("./ExpressServerRequest"),
	PageUtil = require("./util/PageUtil"),
	PromiseUtil = require("./util/PromiseUtil");

// TODO FIXME ??
// It *might* be worthwhile to get rid of all the closure-y things in render()
// https://developers.google.com/speed/articles/optimizing-javascript

var DATA_LOAD_WAIT = 250;

class Renderer {

	constructor (context) {
		this.context = context;
		this.router = context.router;
		this._userDataPromise = context.loadUserData();
	}

	render (handlerResult) {
		logger.debug("Triggering userData load");
		beginRender(req, res, start, context, this._userDataPromise, handlerResult);
	}

}

/**
 * renderMiddleware entrypoint. Called by express for every request.
 */
module.exports = function(routes) {

	return function (req, res, next) {

		var start = new Date();

		logger.debug('request: %s', req.path);

		// TODO? pull this context building into its own middleware
		var context = new RequestContext.Builder()
				.setRoutes(routes)
				.setLoaderOpts({}) // TODO FIXME
				.setDefaultXhrHeadersFromRequest(req)
				.create({
					// TODO: context opts?
				});

		// setup navigation handler (TODO: should we have a 'once' version?)
		context.onNavigate( (err, page) => {

			if (err) {
				logger.error("onNavigate error", err);
				if (err.status && err.status === 404) {
					next();
				} else if (err.status === 301 || err.status === 302) {
					res.redirect(err.status, err.redirectUrl);
				} else {
					next(err);
				}
				return;
			}

			logger.debug('Executing navigate action');

			var userDataPromise = context.loadUserData();
			beginRender(req, res, start, context, userDataPromise, page);

		});

		context.navigate(new ExpressServerRequest(req));

	}
}

function beginRender(req, res, start, context, userDataDfd, page) {

	var routeName = context.navigator.getCurrentRoute().name;

	logger.debug("Route Name: %s", routeName);

	// regardless of what happens, write out the header part
	// TODO: should this include the common.js file? seems like it
	// would give it a chance to download and parse while we're loading
	// data
	writeHeader(req, res, routeName, page);

	// user data should never be the long pole here.
	userDataDfd.done(function () {
		writeBody(req, res, context, start, page).then(() => {
			writeData(req, res, context, start)
			setupLateArrivals(req, res, context, start);
		});
	});

	// TODO: we probably want a "we're not waiting any longer for this"
	// timeout as well, and cancel the waiting deferreds
}

function writeHeader(req, res, routeName, pageObject) {
	logger.debug('Sending header');
	res.type('html');

	var pageHeader = "<!DOCTYPE html>"
		// TODO: we should allow title to be a deferred
		+ renderTitle(pageObject) + "\n"
		+ renderStylesheets(pageObject) + "\n"
		+ renderScripts(pageObject) + "\n"
		+ renderMetaTags(pageObject) + "\n"
		+ "
";
	res.write(pageHeader);
}

function renderTitle (pageObject) {
	if (!pageObject.getTitle) return "";

	return "<title>" + (pageObject.getTitle() || "") + "</title>";
}

function renderMetaTags (pageObject) {
	var metaTags = [ {charset: 'utf-8'} ];

	if (pageObject.getMetaTags) {
		var pageMetaTags = pageObject.getMetaTags();
		if (pageMetaTags.length > 0) {
			metaTags = metaTags.concat(pageMetaTags);
		}
	}

	return metaTags.map( tagData => {
		// TODO: escaping
		var tag = '<meta ';
		tag += Object.keys(tagData).map( metaAttrName => {
			return metaAttrName + '="' + tagData[metaAttrName] + '"';
		}).join(' ');
		tag += '>';
		return tag;
	}).join("\n");
}

function renderScripts(pageObject) {
	if (!pageObject.getHeadScriptFiles) return "";

	// default script
	var scripts = pageObject.getHeadScriptFiles();
	if (scripts && !Array.isArray(scripts)) {
		scripts = [scripts];
	}

	return scripts.map( (scriptPath) => {
		// make sure there's a leading '/'
		return ''
	}).join("\n");

}

function renderStylesheets (pageObject) {
	if (!pageObject.getHeadStylesheet) return "";

	var stylesheet = pageObject.getHeadStylesheet();
	if (!stylesheet) {
		return "";
	}
	return '<link rel="stylesheet" type="text/css" href="' + stylesheet + '" id="'+ ClientCssHelper.PAGE_CSS_NODE_ID + '">'
}

/**
 * Writes out the ReactElements to the response. Returns a promise that fulfills when
 * all the ReactElements have been written out.
 */
function writeBody(req, res, context, start, page) {

	logger.debug("React Rendering");
	// standardize to an array of EarlyPromises of ReactElements
	var elementPromises = PageUtil.standardizeElements(page.getElements());

	// a boolean array to keep track of which elements have already been rendered. this is useful
	// bookkeeping when the timeout happens so we don't double-render anything.
	var rendered = [];

	// render the elements in sequence when they resolve (i.e. tell us they are ready).
	// I learned how to chain an array of promises from http://bahmutov.calepin.co/chaining-promises.html
	var noTimeoutRenderPromise =  elementPromises.reduce((chain, next, index) => {
		return chain.then((element) => {
	 		if (!rendered[index-1]) renderElement(res, element, context, index - 1);
	 		rendered[index - 1] = true;
			return next;
		})
	}).then((element) => {
		// reduce is called length - 1 times. we need to call one final time here to make sure we
		// chain the final promise.
 		if (!rendered[elementPromises.length - 1]) renderElement(res, element, context, elementPromises.length - 1);
 		rendered[elementPromises.length - 1] = true;
	}).catch((err) => {
		logger.debug("Error while rendering", err);
	});

	// set up a maximum wait time for data loading. if this timeout fires, we render with whatever we have,
	// as best as possible. note that once the timeout fires, we render everything that's left
	// synchronously.
	var timeoutRenderPromise = Q.delay(DATA_LOAD_WAIT).then(() => {
		// if we rendered everything up to the last element already, just return.
		if (rendered[elementPromises.length - 1]) return;

		debug("Timeout Exceeeded. Rendering...");
		elementPromises.forEach((promise, index) => {
			if (!rendered[index]) {
				renderElement(res, promise.getValue(), context, index);
				rendered[index] = true;
			}
		});
	});

	// return a promise that resolves when either the async render OR the timeout sync
	// render happens.
	return PromiseUtil.race(noTimeoutRenderPromise, timeoutRenderPromise);
}

function renderElement(res, element, context, index) {
	res.write(`<div data-triton-root-id=${index}>`);
	if (element !== null) {
		element = React.addons.cloneWithProps(element, { context: context });
		res.write(React.renderToString(element));
	}
	res.write("
");
}

function writeData(req, res, context, start) {
	logger.debug('Exposing context state');
	res.expose(context.dehydrate(), 'InitialContext');
	res.expose(getNonInternalConfigs(), "Config");

	res.write("
"); //

	var pageFooter = ""
		+ "";

	res.write(pageFooter);

	var routeName = context.navigator.getCurrentRoute().name;

	logger.time(`content_written.${routeName}`, new Date - start);
}

function setupLateArrivals(req, res, context, start) {
	var loader = context.loader;
	var notLoaded = loader.getPendingRequests();
	var routeName = context.navigator.getCurrentRoute().name;

	notLoaded.forEach( pendingRequest => {
		pendingRequest.entry.dfd.promise.then( data => {
			logger.debug("Late arrival: %s", pendingRequest.url)
			logger.time(`late_arrival.${routeName}`, new Date - start);
			res.write("");
		})
	});

	// TODO: maximum-wait-time-exceeded-so-cancel-pending-requests code
	var promises = notLoaded.map( result => result.entry.dfd.promise );
	Q.allSettled(promises).then(function () {
		res.end("");
		logger.guage(`count_late_arrivals.${routeName}`, notLoaded.length);
		logger.time(`all_done.${routeName}`, new Date - start);
	});
}

function getNonInternalConfigs() {
	var nonInternal = {};
	var fullConfig = config();
	Object.keys(fullConfig).forEach( configKey => {
		if (configKey !== 'internal') {
			nonInternal[configKey] = fullConfig[configKey];
		}
	});
	return nonInternal;
}
<|endoftext|>


<commit_before>
var validateScxmls = require('./../nodejs/validateStateEngines.js'),
	oncomplete = require('./../nodejs/oncomplete'),
	props = require('./../nodejs/loadProperties.js');

//TODO get file-list from command line
var options;// = {throwError: true};

var runner = new oncomplete();

validateScxmls.run(function(err){
	err? console.error('FAILED: '+err) : console.log('SCXML files are VALID.');
	console.log('------------------- finished validating SCXML files! -------------------');
	runner && runner.setCompleted();
}, options);

module.exports.run = runner.oncomplete
<commit_msg>modfied success message<commit_after>
var validateScxmls = require('./../nodejs/validateStateEngines.js'),
	oncomplete = require('./../nodejs/oncomplete'),
	props = require('./../nodejs/loadProperties.js');

//TODO get file-list from command line
var options;// = {throwError: true};

var runner = new oncomplete();

validateScxmls.run(function(err){
	err? console.error('FAILED: '+err) : console.log('SUCCESS: SCXML files are VALID.');
	console.log('------------------- finished validating SCXML files! -------------------');
	runner && runner.setCompleted();
}, options);

module.exports.run = runner.oncomplete
<|endoftext|>


<commit_before>/*
===============================================================
jQuery plugin to expand/collapse a content element when a
expander element is clicked. When expanding/collapsing the plug-in
also toggles a class on the element.
See https://github.com/redhotsly/simple-expand
===============================================================
Copyright (C) 2012 Sylvain Hamel

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
===============================================================
*/
/*globals $:false, window:false*/
(function () {
    "use strict";

    // SimpleExpand
    function SimpleExpand() {

        var that = this;

        that.defaults = {

            // hideMode
            // -----------
            // Specifies method to hide the content element.
            //
            // Default: fadeToggle
            //
            // Values:
            // - fadeToggle: Use jquery.fadeToggle()
            // - basic: Use jquery.toggle()
            // - css: relies on user provided css to show/hide. you can define
            //   classes for "collapsed" and "expanded" classes.
            // - a function : custom toggle function. The function receives 3 arguments
            //                expander: the element that triggered the toggle
            //                targets: the items to toggle
            //                expanded: true if expanding; false if collapsing
            //
            // If un an unknown value is specified, the plug-in reverts to "css".
            'hideMode': 'fadeToggle',

            // searchMode
            // -----------
            // Specifies the defaut value for  data-expander-target-search
            // when none is specified on the expander element.
            //
            // Default: parent
            //
            // Values:
            // - parent: go up the expander's parents hierarchy searching
            //           each parent's childens looking for a target
            //
            // - absolute : finds a target globally in the document (useful when
            //              matching an id)
            //
            // - relative : finds a target nested inside the expander
            //
            // If un an unknown value is specified, no targets will be found.
            'defaultSearchMode': 'parent',

            // defaultTarget
            // -----------
            // Specifies the defaut value for data-expander-target when
            // none is specified on the expander element.
            //
            // Default: .content
            'defaultTarget': '.content',

            // throwOnMissingTarget
            // -----------
            // Specifies whether the plug-in throws an exception if it
            // cannot find a target for the expander
            //
            // Default: true
            'throwOnMissingTarget': true,

            // keepStateInCookie
            // -----------
            // Specifies whether the plug-in keeps the expended/collapsed state
            // in a cookie for the next time.
            //
            // Default: false
            //
            // Notes:
            // - This only works for targets with an Id attribute.
            // - Make sure you load the jQuery cookie plug-in (https://github.com/carhartl/jquery-cookie/)
            //   before simple-expand is loaded.
            //
            'keepStateInCookie': false,
            'cookieName': 'simple-expand'
        };

        that.settings = {};
        $.extend(that.settings, that.defaults);

        // Search in the children of the 'parent' element for an element that matches 'filterSelector'
        // but don't search deeper if a 'stopAtSelector' element is met.
        //     See this question to better understand what this does.
        //     http://stackoverflow.com/questions/10902077/how-to-select-children-elements-but-only-one-level-deep-with-jquery
        that.findLevelOneDeep = function (parent, filterSelector, stopAtSelector) {
            return parent.find(filterSelector).filter(function () {
                return !$(this).parentsUntil(parent, stopAtSelector).length;
            });
        };

        // Hides targets
        that.setInitialState = function (expander, targets) {
            var isExpanded = that.readState(expander);

            if (isExpanded) {
                expander.removeClass("collapsed").addClass("expanded");
                that.show(targets);
            } else {
                expander.removeClass("expanded").addClass("collapsed");
                that.hide(targets);
            }
        };

        that.hide = function (targets) {
            if (that.settings.hideMode === "fadeToggle") {
                targets.hide();
            } else if (that.settings.hideMode === "basic") {
                targets.hide();
            }
        };

        that.show = function (targets) {
            if (that.settings.hideMode === "fadeToggle") {
                targets.show();
            } else if (that.settings.hideMode === "basic") {
                targets.show();
            }
        };

        // assert that $.cookie if 'keepStateInCookie' option is enabled
        that.checkKeepStateInCookiePreconditions = function () {
            if (that.settings.keepStateInCookie && $.cookie === undefined){
                throw new Error("simple-expand: keepStateInCookie option requires $.cookie to be defined.");
            }
        };

        // returns the cookie
        that.readCookie = function () {
            var jsonString = $.cookie(that.settings.cookieName);
            if ( jsonString === null  || jsonString === '' ){
                return {};
            }
            else{
                return JSON.parse(jsonString);
            }
        };

        // gets state for the expander from cookies
        that.readState = function (expander) {
            if (!that.settings.keepStateInCookie){
                return false;
            }

            var id = expander.attr('Id');
            if (id === undefined){
                return;
            }

            var cookie = that.readCookie();
            var isExpanded = cookie[id] === true || false;
            return isExpanded;
        };

        // save states of the item in the cookies
        that.saveState = function (expander, isExpanded) {
            if (!that.settings.keepStateInCookie){
                return;
            }

            var id = expander.attr('Id');
            if (id === undefined){
                return;
            }

            var cookie = that.readCookie();
            cookie[id] = isExpanded;
            $.cookie(that.settings.cookieName, JSON.stringify(cookie), { raw: true, path:window.location.pathname });
        };

        // Toggles the targets and sets the 'collapsed' or 'expanded'
        // class on the expander
        that.toggle = function (expander, targets) {

            var isExpanded = that.toggleCss(expander);

            if (that.settings.hideMode === "fadeToggle") {
                targets.fadeToggle(150);
            } else if (that.settings.hideMode === "basic") {
                targets.toggle();
            } else if ($.isFunction(that.settings.hideMode)) {
                that.settings.hideMode(expander, targets, isExpanded);
            }

            that.saveState(expander, isExpanded);

            // prevent default to stop browser from scrolling to: href="#"
            return false;
        };

        // Toggles using css
        that.toggleCss = function (expander) {
            if (expander.hasClass("expanded")) {
                expander.toggleClass("collapsed expanded");
                return false;
            }
            else {
                expander.toggleClass("expanded collapsed");
                return true;
            }
        };

        // returns the targets for the given expander
        that.findTargets = function (expander, searchMode, targetSelector) {
            // find the targets using the specified searchMode
            var targets = [];
            if (searchMode === "absolute") {
                targets = $(targetSelector);
            }
            else if (searchMode === "relative") {
                targets = that.findLevelOneDeep(expander, targetSelector, targetSelector);
            }
            else if (searchMode === "parent") {

                    // Search the expander's parents recursively until targets are found.
                var parent = expander.parent();
                do {
                    targets = that.findLevelOneDeep(parent, targetSelector, targetSelector);

                    // No targets found, prepare for next iteration...
                    if (targets.length === 0) {
                        parent = parent.parent();
                    }
                } while (targets.length === 0 && parent.length !== 0);
            }
            return targets;
        };

        that.activate = function (jquery, options) {
            $.extend(that.settings, options);

            that.checkKeepStateInCookiePreconditions();

            // Plug-in entry point
            //
            // For each expander:
            //    search targets
            //    hide targets
            //    register to targets' click event to toggle them on click
            jquery.each(function () {
                var expander = $(this);

                var targetSelector = expander.attr("data-expander-target") || that.settings.defaultTarget;
                var searchMode = expander.attr("data-expander-target-search") || that.settings.defaultSearchMode;

                var targets = that.findTargets(expander, searchMode, targetSelector);

                // no elements match the target selector
                // there is nothing we can do
                if (targets.length === 0) {
                    if (that.settings.throwOnMissingTarget) {
                        throw "simple-expand: Targets not found";
                    }
                    return this;
                }

                that.setInitialState(expander, targets);

                // hook the click on the expander
                expander.click(function () {
                    return that.toggle(expander, targets);
                });
            });
        };
    }

    // export SimpleExpand
    window.SimpleExpand = SimpleExpand;

    // expose SimpleExpand as a jQuery plugin
    $.fn.simpleexpand = function (options) {
        var instance = new SimpleExpand();
        instance.activate(this, options);
        return this;
    };
}());
<commit_msg>fix documentation for keepStateInCookie<commit_after>/*
===============================================================
jQuery plugin to expand/collapse a content element when a
expander element is clicked. When expanding/collapsing the plug-in
also toggles a class on the element.
See https://github.com/redhotsly/simple-expand
===============================================================
Copyright (C) 2012 Sylvain Hamel

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
===============================================================
*/
/*globals $:false, window:false*/
(function () {
    "use strict";

    // SimpleExpand
    function SimpleExpand() {

        var that = this;

        that.defaults = {

            // hideMode
            // -----------
            // Specifies method to hide the content element.
            //
            // Default: fadeToggle
            //
            // Values:
            // - fadeToggle: Use jquery.fadeToggle()
            // - basic: Use jquery.toggle()
            // - css: relies on user provided css to show/hide. you can define
            //   classes for "collapsed" and "expanded" classes.
            // - a function : custom toggle function. The function receives 3 arguments
            //                expander: the element that triggered the toggle
            //                targets: the items to toggle
            //                expanded: true if expanding; false if collapsing
            //
            // If un an unknown value is specified, the plug-in reverts to "css".
            'hideMode': 'fadeToggle',

            // searchMode
            // -----------
            // Specifies the defaut value for  data-expander-target-search
            // when none is specified on the expander element.
            //
            // Default: parent
            //
            // Values:
            // - parent: go up the expander's parents hierarchy searching
            //           each parent's childens looking for a target
            //
            // - absolute : finds a target globally in the document (useful when
            //              matching an id)
            //
            // - relative : finds a target nested inside the expander
            //
            // If un an unknown value is specified, no targets will be found.
            'defaultSearchMode': 'parent',

            // defaultTarget
            // -----------
            // Specifies the defaut value for data-expander-target when
            // none is specified on the expander element.
            //
            // Default: .content
            'defaultTarget': '.content',

            // throwOnMissingTarget
            // -----------
            // Specifies whether the plug-in throws an exception if it
            // cannot find a target for the expander
            //
            // Default: true
            'throwOnMissingTarget': true,

            // keepStateInCookie
            // -----------
            // Specifies whether the plug-in keeps the expended/collapsed state
            // in a cookie for the next time.
            //
            // Default: false
            //
            // Notes:
            // - This only works for expanders with an Id attribute.
            // - Make sure you load the jQuery cookie plug-in (https://github.com/carhartl/jquery-cookie/)
            //   before simple-expand is loaded.
            //
            'keepStateInCookie': false,
            'cookieName': 'simple-expand'
        };

        that.settings = {};
        $.extend(that.settings, that.defaults);

        // Search in the children of the 'parent' element for an element that matches 'filterSelector'
        // but don't search deeper if a 'stopAtSelector' element is met.
        //     See this question to better understand what this does.
        //     http://stackoverflow.com/questions/10902077/how-to-select-children-elements-but-only-one-level-deep-with-jquery
        that.findLevelOneDeep = function (parent, filterSelector, stopAtSelector) {
            return parent.find(filterSelector).filter(function () {
                return !$(this).parentsUntil(parent, stopAtSelector).length;
            });
        };

        // Hides targets
        that.setInitialState = function (expander, targets) {
            var isExpanded = that.readState(expander);

            if (isExpanded) {
                expander.removeClass("collapsed").addClass("expanded");
                that.show(targets);
            } else {
                expander.removeClass("expanded").addClass("collapsed");
                that.hide(targets);
            }
        };

        that.hide = function (targets) {
            if (that.settings.hideMode === "fadeToggle") {
                targets.hide();
            } else if (that.settings.hideMode === "basic") {
                targets.hide();
            }
        };

        that.show = function (targets) {
            if (that.settings.hideMode === "fadeToggle") {
                targets.show();
            } else if (that.settings.hideMode === "basic") {
                targets.show();
            }
        };

        // assert that $.cookie if 'keepStateInCookie' option is enabled
        that.checkKeepStateInCookiePreconditions = function () {
            if (that.settings.keepStateInCookie && $.cookie === undefined){
                throw new Error("simple-expand: keepStateInCookie option requires $.cookie to be defined.");
            }
        };

        // returns the cookie
        that.readCookie = function () {
            var jsonString = $.cookie(that.settings.cookieName);
            if ( jsonString === null  || jsonString === '' ){
                return {};
            }
            else{
                return JSON.parse(jsonString);
            }
        };

        // gets state for the expander from cookies
        that.readState = function (expander) {
            if (!that.settings.keepStateInCookie){
                return false;
            }

            var id = expander.attr('Id');
            if (id === undefined){
                return;
            }

            var cookie = that.readCookie();
            var isExpanded = cookie[id] === true || false;
            return isExpanded;
        };

        // save states of the item in the cookies
        that.saveState = function (expander, isExpanded) {
            if (!that.settings.keepStateInCookie){
                return;
            }

            var id = expander.attr('Id');
            if (id === undefined){
                return;
            }

            var cookie = that.readCookie();
            cookie[id] = isExpanded;
            $.cookie(that.settings.cookieName, JSON.stringify(cookie), { raw: true, path:window.location.pathname });
        };

        // Toggles the targets and sets the 'collapsed' or 'expanded'
        // class on the expander
        that.toggle = function (expander, targets) {

            var isExpanded = that.toggleCss(expander);

            if (that.settings.hideMode === "fadeToggle") {
                targets.fadeToggle(150);
            } else if (that.settings.hideMode === "basic") {
                targets.toggle();
            } else if ($.isFunction(that.settings.hideMode)) {
                that.settings.hideMode(expander, targets, isExpanded);
            }

            that.saveState(expander, isExpanded);

            // prevent default to stop browser from scrolling to: href="#"
            return false;
        };

        // Toggles using css
        that.toggleCss = function (expander) {
            if (expander.hasClass("expanded")) {
                expander.toggleClass("collapsed expanded");
                return false;
            }
            else {
                expander.toggleClass("expanded collapsed");
                return true;
            }
        };

        // returns the targets for the given expander
        that.findTargets = function (expander, searchMode, targetSelector) {
            // find the targets using the specified searchMode
            var targets = [];
            if (searchMode === "absolute") {
                targets = $(targetSelector);
            }
            else if (searchMode === "relative") {
                targets = that.findLevelOneDeep(expander, targetSelector, targetSelector);
            }
            else if (searchMode === "parent") {

                    // Search the expander's parents recursively until targets are found.
                var parent = expander.parent();
                do {
                    targets = that.findLevelOneDeep(parent, targetSelector, targetSelector);

                    // No targets found, prepare for next iteration...
                    if (targets.length === 0) {
                        parent = parent.parent();
                    }
                } while (targets.length === 0 && parent.length !== 0);
            }
            return targets;
        };

        that.activate = function (jquery, options) {
            $.extend(that.settings, options);

            that.checkKeepStateInCookiePreconditions();

            // Plug-in entry point
            //
            // For each expander:
            //    search targets
            //    hide targets
            //    register to targets' click event to toggle them on click
            jquery.each(function () {
                var expander = $(this);

                var targetSelector = expander.attr("data-expander-target") || that.settings.defaultTarget;
                var searchMode = expander.attr("data-expander-target-search") || that.settings.defaultSearchMode;

                var targets = that.findTargets(expander, searchMode, targetSelector);

                // no elements match the target selector
                // there is nothing we can do
                if (targets.length === 0) {
                    if (that.settings.throwOnMissingTarget) {
                        throw "simple-expand: Targets not found";
                    }
                    return this;
                }

                that.setInitialState(expander, targets);

                // hook the click on the expander
                expander.click(function () {
                    return that.toggle(expander, targets);
                });
            });
        };
    }

    // export SimpleExpand
    window.SimpleExpand = SimpleExpand;

    // expose SimpleExpand as a jQuery plugin
    $.fn.simpleexpand = function (options) {
        var instance = new SimpleExpand();
        instance.activate(this, options);
        return this;
    };
}());
<|endoftext|>


<commit_before>;(function($$){ 'use strict';

  // search, spanning trees, etc
  $$.fn.eles({

    // std functional ele first callback style
    stdBreadthFirstSearch: function( options ){
      options = $$.util.extend( {}, options, {
        std: true
      } );

      return this.breadthFirstSearch( options );
    },

    // do a breadth first search from the nodes in the collection
    // from pseudocode on wikipedia
    breadthFirstSearch: function( roots, fn, directed ){
      var options;
      var std;
      var thisArg;
      if( $$.is.plainObject(roots) && !$$.is.elementOrCollection(roots) ){
        options = roots;
        roots = options.roots;
        fn = options.visit;
        directed = options.directed;
        std = options.std;
        thisArg = options.thisArg;
      }

      directed = arguments.length === 2 && !$$.is.fn(fn) ? fn : directed;
      fn = $$.is.fn(fn) ? fn : function(){};

      var cy = this._private.cy;
      var v = $$.is.string(roots) ? this.filter(roots) : roots;
      var Q = [];
      var connectedNodes = [];
      var connectedBy = {};
      var id2depth = {};
      var V = {};
      var j = 0;
      var found;
      var nodes = this.nodes();
      var edges = this.edges();

      // enqueue v
      for( var i = 0; i < v.length; i++ ){
        if( v[i].isNode() ){
          Q.unshift( v[i] );
          V[ v[i].id() ] = true;

          connectedNodes.push( v[i] );
          id2depth[ v[i].id() ] = 0;
        }
      }

      while( Q.length !== 0 ){
        var v = Q.shift();
        var depth = id2depth[ v.id() ];
        var prevEdge = connectedBy[ v.id() ];
        var prevNode = prevEdge == null ? undefined : prevEdge.connectedNodes().not( v )[0];
        var ret;

        if( std ){
          ret = fn.call(thisArg, v, prevEdge, prevNode, j++, depth);
        } else {
          ret = fn.call(v, j++, depth, v, prevEdge, prevNode);
        }

        if( ret === true ){
          found = v;
          break;
        }

        if( ret === false ){
          break;
        }

        var vwEdges = v.connectedEdges(directed ? function(){ return this.data('source') === v.id(); } : undefined).intersect( edges );
        for( var i = 0; i < vwEdges.length; i++ ){
          var e = vwEdges[i];
          var w = e.connectedNodes(function(){ return this.id() !== v.id(); }).intersect( nodes );

          if( w.length !== 0 && !V[ w.id() ] ){
            w = w[0];

            Q.push( w );
            V[ w.id() ] = true;

            id2depth[ w.id() ] = id2depth[ v.id() ] + 1;

            connectedNodes.push( w );
            connectedBy[ w.id() ] = e;
          }
        }

      }

      var connectedEles = [];

      for( var i = 0; i < connectedNodes.length; i++ ){
        var node = connectedNodes[i];
        var edge = connectedBy[ node.id() ];

        if( edge ){
          connectedEles.push( edge );
        }

        connectedEles.push( node );
      }

      return {
        path: new $$.Collection( cy, connectedEles, { unique: true } ),
        found: new $$.Collection( cy, found, { unique: true } )
      };
    },

    // std functional ele first callback style
    stdDepthFirstSearch: function( options ){
      options = $$.util.extend( {}, options, {
        std: true
      } );

      return this.depthFirstSearch( options );
    },

    // do a depth first search on the nodes in the collection
    // from pseudocode on wikipedia (iterative impl)
    depthFirstSearch: function( roots, fn, directed ){
      var options;
      var std;
      var thisArg;
      if( $$.is.plainObject(roots) && !$$.is.elementOrCollection(roots) ){
        options = roots;
        roots = options.roots;
        fn = options.visit;
        directed = options.directed;
        std = options.std;
        thisArg = options.thisArg;
      }

      directed = arguments.length === 2 && !$$.is.fn(fn) ? fn : directed;
      fn = $$.is.fn(fn) ? fn : function(){};
      var cy = this._private.cy;
      var v = $$.is.string(roots) ? this.filter(roots) : roots;
      var S = [];
      var connectedNodes = [];
      var connectedBy = {};
      var id2depth = {};
      var discovered = {};
      var j = 0;
      var found;
      var edges = this.edges();
      var nodes = this.nodes();

      // push v
      for( var i = 0; i < v.length; i++ ){
        if( v[i].isNode() ){
          S.push( v[i] );

          connectedNodes.push( v[i] );
          id2depth[ v[i].id() ] = 0;
        }
      }

      while( S.length !== 0 ){
        var v = S.pop();

        if( !discovered[ v.id() ] ){
          discovered[ v.id() ] = true;

          var depth = id2depth[ v.id() ];
          var prevEdge = connectedBy[ v.id() ];
          var prevNode = prevEdge == null ? undefined : prevEdge.connectedNodes().not( v )[0];
          var ret;

          if( std ){
            ret = fn.call(thisArg, v, prevEdge, prevNode, j++, depth);
          } else {
            ret = fn.call(v, j++, depth, v, prevEdge, prevNode);
          }

          if( ret === true ){
            found = v;
            break;
          }

          if( ret === false ){
            break;
          }

          var vwEdges = v.connectedEdges(directed ? function(){ return this.data('source') === v.id(); } : undefined).intersect( edges );

          for( var i = 0; i < vwEdges.length; i++ ){
            var e = vwEdges[i];
            var w = e.connectedNodes(function(){ return this.id() !== v.id(); }).intersect( nodes );

            if( w.length !== 0 && !discovered[ w.id() ] ){
              w = w[0];

              S.push( w );

              id2depth[ w.id() ] = id2depth[ v.id() ] + 1;

              connectedNodes.push( w );
              connectedBy[ w.id() ] = e;
            }
          }
        }
      }

      var connectedEles = [];

      for( var i = 0; i < connectedNodes.length; i++ ){
        var node = connectedNodes[i];
        var edge = connectedBy[ node.id() ];

        if( edge ){
          connectedEles.push( edge );
        }

        connectedEles.push( node );
      }

      return {
        path: new $$.Collection( cy, connectedEles, { unique: true } ),
        found: new $$.Collection( cy, found, { unique: true } )
      };
    },

    // kruskal's algorithm (finds min spanning tree, assuming undirected graph)
    // implemented from pseudocode from wikipedia
    kruskal: function( weightFn ){
      weightFn = $$.is.fn(weightFn) ? weightFn : function(){ return 1; }; // if not specified, assume each edge has equal weight (1)

      function findSet(ele){
        for( var i = 0; i < forest.length; i++ ){
          var eles = forest[i];

          if( eles.anySame(ele) ){
            return {
              eles: eles,
              index: i
            };
          }
        }
      }

      var A = new $$.Collection(this._private.cy, []);
      var forest = [];
      var nodes = this.nodes();

      for( var i = 0; i < nodes.length; i++ ){
        forest.push( nodes[i].collection() );
      }

      var edges = this.edges();
      var S = edges.toArray().sort(function(a, b){
        var weightA = weightFn.call(a, a);
        var weightB = weightFn.call(b, b);

        return weightA - weightB;
      });

      for(var i = 0; i < S.length; i++){
        var edge = S[i];
        var u = edge.source()[0];
        var v = edge.target()[0];
        var setU = findSet(u);
        var setV = findSet(v);

        if( setU.index !== setV.index ){
          A = A.add( edge );

          // combine forests for u and v
          forest[ setU.index ] = setU.eles.add( setV.eles );
          forest.splice( setV.index, 1 );
        }
      }

      return nodes.add( A );

    },

    dijkstra: function( root, weightFn, directed ){
      var options;
      if( $$.is.plainObject(root) && !$$.is.elementOrCollection(root) ){
        options = root;
        root = options.root;
        weightFn = options.weight;
        directed = options.directed;
      }

      var cy = this._private.cy;
      weightFn = $$.is.fn(weightFn) ? weightFn : function(){ return 1; }; // if not specified, assume each edge has equal weight (1)

      var source = $$.is.string(root) ? this.filter(root)[0] : root[0];
      var dist = {};
      var prev = {};
      var knownDist = {};

      var edges = this.edges().filter(function(){ return !this.isLoop(); });
      var nodes = this.nodes();
      var Q = [];

      for( var i = 0; i < nodes.length; i++ ){
        dist[ nodes[i].id() ] = nodes[i].same( source ) ? 0 : Infinity;
        Q.push( nodes[i] );
      }

      var valueFn = function(node) {
        return dist[ node.id() ];
      };

      Q = new $$.Collection(cy, Q);

      var heap = $$.Minheap(cy, Q, valueFn);

      var distBetween = function(u, v){
        var uvs = ( directed ? u.edgesTo(v) : u.edgesWith(v) ).intersect(edges);
        var smallestDistance = Infinity;
        var smallestEdge;

        for( var i = 0; i < uvs.length; i++ ){
          var edge = uvs[i];
          var weight = weightFn.apply( edge, [edge] );

          if( weight < smallestDistance || !smallestEdge ){
            smallestDistance = weight;
            smallestEdge = edge;
          }
        }

        return {
          edge: smallestEdge,
          dist: smallestDistance
        };
      };

      while(heap.size() > 0){
        var smallestEl = heap.pop(),
        smalletsDist = smallestEl.value,
        uid = smallestEl.id,
        u = cy.getElementById(uid);

        knownDist[uid] = smalletsDist;

        if( smalletsDist === Math.Infinite ){
          break;
        }

        var neighbors = u.neighborhood().intersect(nodes);
        for( var i = 0; i < neighbors.length; i++ ){
          var v = neighbors[i];
          var vid = v.id();
          var vDist = distBetween(u, v);

          var alt = smalletsDist + vDist.dist;

          if( alt < heap.getValueById(vid) ){
            heap.edit(vid, alt);
            prev[ vid ] = {
              node: u,
              edge: vDist.edge
            };
          }
        } // for
      } // while

      return {
        distanceTo: function(node){
          var target = $$.is.string(node) ? nodes.filter(node)[0] : node[0];

          return knownDist[ target.id() ];
        },

        pathTo: function(node){
          var target = $$.is.string(node) ? nodes.filter(node)[0] : node[0];
          var S = [];
          var u = target;

          if( target.length > 0 ){
            S.unshift( target );

            while( prev[ u.id() ] ){
              var p = prev[ u.id() ];

              S.unshift( p.edge );
              S.unshift( p.node );

              u = p.node;
            }
          }

          return new $$.Collection( cy, S );
        }
      };
    }
  });

  // nice, short mathemathical alias
  $$.elesfn.bfs = $$.elesfn.breadthFirstSearch;
  $$.elesfn.dfs = $$.elesfn.depthFirstSearch;
  $$.elesfn.stdBfs = $$.elesfn.stdBreadthFirstSearch;
  $$.elesfn.stdDfs = $$.elesfn.stdDepthFirstSearch;

})( cytoscape );
<commit_msg>port d9aabee to 2.4 #1122<commit_after>;(function($$){ 'use strict';

  var is = $$.is;

  var defineSearch = function( params ){
    params = {
      bfs: params.bfs || !params.dfs,
      dfs: params.dfs || !params.bfs
    };

    // from pseudocode on wikipedia
    return function searchFn( roots, fn, directed ){
      var options;
      var std;
      var thisArg;
      if( is.plainObject(roots) && !is.elementOrCollection(roots) ){
        options = roots;
        roots = options.roots;
        fn = options.visit;
        directed = options.directed;
        std = options.std;
        thisArg = options.thisArg;
      }

      directed = arguments.length === 2 && !is.fn(fn) ? fn : directed;
      fn = is.fn(fn) ? fn : function(){};

      var cy = this._private.cy;
      var v = roots = is.string(roots) ? this.filter(roots) : roots;
      var Q = [];
      var connectedNodes = [];
      var connectedBy = {};
      var id2depth = {};
      var V = {};
      var j = 0;
      var found;
      var nodes = this.nodes();
      var edges = this.edges();

      // enqueue v
      for( var i = 0; i < v.length; i++ ){
        if( v[i].isNode() ){
          Q.unshift( v[i] );

          if( params.bfs ){
            V[ v[i].id() ] = true;

            connectedNodes.push( v[i] );
          }

          id2depth[ v[i].id() ] = 0;
        }
      }

      while( Q.length !== 0 ){
        var v = params.bfs ? Q.shift() : Q.pop();

        if( params.dfs ){
          if( V[ v.id() ] ){ continue; }

          V[ v.id() ] = true;

          connectedNodes.push( v );
        }

        var depth = id2depth[ v.id() ];
        var prevEdge = connectedBy[ v.id() ];
        var prevNode = prevEdge == null ? undefined : prevEdge.connectedNodes().not( v )[0];
        var ret;

        if( std ){
          ret = fn.call(thisArg, v, prevEdge, prevNode, j++, depth);
        } else {
          ret = fn.call(v, j++, depth, v, prevEdge, prevNode);
        }

        if( ret === true ){
          found = v;
          break;
        }

        if( ret === false ){
          break;
        }

        var vwEdges = v.connectedEdges(directed ? function(){ return this.data('source') === v.id(); } : undefined).intersect( edges );
        for( var i = 0; i < vwEdges.length; i++ ){
          var e = vwEdges[i];
          var w = e.connectedNodes(function(){ return this.id() !== v.id(); }).intersect( nodes );

          if( w.length !== 0 && !V[ w.id() ] ){
            w = w[0];

            Q.push( w );

            if( params.bfs ){
              V[ w.id() ] = true;

              connectedNodes.push( w );
            }

            connectedBy[ w.id() ] = e;

            id2depth[ w.id() ] = id2depth[ v.id() ] + 1;
          }
        }

      }

      var connectedEles = [];

      for( var i = 0; i < connectedNodes.length; i++ ){
        var node = connectedNodes[i];
        var edge = connectedBy[ node.id() ];

        if( edge ){
          connectedEles.push( edge );
        }

        connectedEles.push( node );
      }

      return {
        path: cy.collection( connectedEles, { unique: true } ),
        found: cy.collection( found )
      };
    };
  };

  // search, spanning trees, etc
  $$.fn.eles({

    breadthFirstSearch: defineSearch({ bfs: true }),
    depthFirstSearch: defineSearch({ dfs: true }),

    // kruskal's algorithm (finds min spanning tree, assuming undirected graph)
    // implemented from pseudocode from wikipedia
    kruskal: function( weightFn ){
      weightFn = $$.is.fn(weightFn) ? weightFn : function(){ return 1; }; // if not specified, assume each edge has equal weight (1)

      function findSet(ele){
        for( var i = 0; i < forest.length; i++ ){
          var eles = forest[i];

          if( eles.anySame(ele) ){
            return {
              eles: eles,
              index: i
            };
          }
        }
      }

      var A = new $$.Collection(this._private.cy, []);
      var forest = [];
      var nodes = this.nodes();

      for( var i = 0; i < nodes.length; i++ ){
        forest.push( nodes[i].collection() );
      }

      var edges = this.edges();
      var S = edges.toArray().sort(function(a, b){
        var weightA = weightFn.call(a, a);
        var weightB = weightFn.call(b, b);

        return weightA - weightB;
      });

      for(var i = 0; i < S.length; i++){
        var edge = S[i];
        var u = edge.source()[0];
        var v = edge.target()[0];
        var setU = findSet(u);
        var setV = findSet(v);

        if( setU.index !== setV.index ){
          A = A.add( edge );

          // combine forests for u and v
          forest[ setU.index ] = setU.eles.add( setV.eles );
          forest.splice( setV.index, 1 );
        }
      }

      return nodes.add( A );

    },

    dijkstra: function( root, weightFn, directed ){
      var options;
      if( $$.is.plainObject(root) && !$$.is.elementOrCollection(root) ){
        options = root;
        root = options.root;
        weightFn = options.weight;
        directed = options.directed;
      }

      var cy = this._private.cy;
      weightFn = $$.is.fn(weightFn) ? weightFn : function(){ return 1; }; // if not specified, assume each edge has equal weight (1)

      var source = $$.is.string(root) ? this.filter(root)[0] : root[0];
      var dist = {};
      var prev = {};
      var knownDist = {};

      var edges = this.edges().filter(function(){ return !this.isLoop(); });
      var nodes = this.nodes();
      var Q = [];

      for( var i = 0; i < nodes.length; i++ ){
        dist[ nodes[i].id() ] = nodes[i].same( source ) ? 0 : Infinity;
        Q.push( nodes[i] );
      }

      var valueFn = function(node) {
        return dist[ node.id() ];
      };

      Q = new $$.Collection(cy, Q);

      var heap = $$.Minheap(cy, Q, valueFn);

      var distBetween = function(u, v){
        var uvs = ( directed ? u.edgesTo(v) : u.edgesWith(v) ).intersect(edges);
        var smallestDistance = Infinity;
        var smallestEdge;

        for( var i = 0; i < uvs.length; i++ ){
          var edge = uvs[i];
          var weight = weightFn.apply( edge, [edge] );

          if( weight < smallestDistance || !smallestEdge ){
            smallestDistance = weight;
            smallestEdge = edge;
          }
        }

        return {
          edge: smallestEdge,
          dist: smallestDistance
        };
      };

      while(heap.size() > 0){
        var smallestEl = heap.pop(),
        smalletsDist = smallestEl.value,
        uid = smallestEl.id,
        u = cy.getElementById(uid);

        knownDist[uid] = smalletsDist;

        if( smalletsDist === Math.Infinite ){
          break;
        }

        var neighbors = u.neighborhood().intersect(nodes);
        for( var i = 0; i < neighbors.length; i++ ){
          var v = neighbors[i];
          var vid = v.id();
          var vDist = distBetween(u, v);

          var alt = smalletsDist + vDist.dist;

          if( alt < heap.getValueById(vid) ){
            heap.edit(vid, alt);
            prev[ vid ] = {
              node: u,
              edge: vDist.edge
            };
          }
        } // for
      } // while

      return {
        distanceTo: function(node){
          var target = $$.is.string(node) ? nodes.filter(node)[0] : node[0];

          return knownDist[ target.id() ];
        },

        pathTo: function(node){
          var target = $$.is.string(node) ? nodes.filter(node)[0] : node[0];
          var S = [];
          var u = target;

          if( target.length > 0 ){
            S.unshift( target );

            while( prev[ u.id() ] ){
              var p = prev[ u.id() ];

              S.unshift( p.edge );
              S.unshift( p.node );

              u = p.node;
            }
          }

          return new $$.Collection( cy, S );
        }
      };
    }
  });

  // nice, short mathemathical alias
  $$.elesfn.bfs = $$.elesfn.breadthFirstSearch;
  $$.elesfn.dfs = $$.elesfn.depthFirstSearch;
  $$.elesfn.stdBfs = $$.elesfn.stdBreadthFirstSearch;
  $$.elesfn.stdDfs = $$.elesfn.stdDepthFirstSearch;

})( cytoscape );
<|endoftext|>