<commit_before>import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { Link } from 'react-router'
import * as applicationActions from '../../actions/application'

class ProductOverview extends Component {

  static propTypes = {
    products: PropTypes.array.isRequired,
    loadProducts: PropTypes.func.isRequired,
    performSearch: PropTypes.func.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      term: ''
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  componentWillMount () {
    this.props.loadProducts()
    console.log("CWM" )
  }

  handleChange (event) {
    this.setState({term: event.target.value});
  }

  handleSubmit(event) {
    console.log('search with ', this.state.term)
    event.preventDefault();
    this.props.performSearch(this.state.term)
  }

  render () {
    const { products } = this.props
    console.log("Products", products)
    const productList = products.map(_ =>

<Link to={`/product/${_.key}`}>{_.title}</Link>

)

    return (

        <form onSubmit={this.handleSubmit}>

            <input type="text" name="term" className="form-control" value={this.state.value}
                   onChange={this.handleChange} placeholder="Search for..."/>

              <button className="btn btn-default" type="submit">Go!</button>

        </form>

        {productList}

    )
  }

}

export default connect(
  ({ application: { products } }) => ({ products }),
  applicationActions
)(ProductOverview)
<commit_msg>type ahead search<commit_after>import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { Link } from 'react-router'
import * as applicationActions from '../../actions/application'

class ProductOverview extends Component {

  static propTypes = {
    products: PropTypes.array.isRequired,
    loadProducts: PropTypes.func.isRequired,
    performSearch: PropTypes.func.isRequired,
  };

  componentWillMount () {
    this.props.loadProducts()
    console.log("CWM" )
  }

  handleChange (event) {
    const term = event.target.value
    if (term.length){
      this.props.performSearch(term)
    } else {
      this.props.loadProducts()
    }
  }

  render () {
    const { products } = this.props
    const productList = products.map(_ =>

<Link to={`/product/${_.key}`}>{_.title}</Link>

)

    return (

          <input type="text" name="term" className="form-control" value={this.state.value}
                 onChange={::this.handleChange} placeholder="Search for..."/>

        {productList}

    )
  }

}

export default connect(
  ({ application: { products } }) => ({ products }),
  applicationActions
)(ProductOverview)
<|endoftext|>


<commit_before>var Util, DOM, CloudFunc;

(function(scope, Util, DOM, CloudFunc) {
    'use strict';

    scope.CloudCmd = new CloudCmdProto(Util, DOM, CloudFunc);

    function CloudCmdProto(Util, DOM, CloudFunc) {
        var Key, Config, Modules, Extensions,
            FileTemplate, PathTemplate, Listeners,
            DIR_HTML                = '/html/',
            DIR_HTML_FS             = DIR_HTML + 'fs/',
            DIR_JSON                = '/json/',

            Images                  = DOM.Images,
            Info                    = DOM.CurrentInfo,
            CloudCmd                = this,
            Storage                 = DOM.Storage;

        this.MousePosition          = {};
        this.LIBDIR                 = '/lib/';
        this.LIBDIRCLIENT           = '/lib/client/';
        this.MIN_ONE_PANEL_WIDTH    = 1155;
        this.OLD_BROWSER            = false;
        this.HOST                   = location.origin ||
                                      location.protocol + '//' + location.host;
        /**
         * Функция привязываеться ко всем ссылкам и
         *  загружает содержимое каталогов
         *
         * @param params - {
         *          paramLink - ссылка
         *          needRefresh - необходимость обязательной загрузки данных с сервера
         *          panel
         *      }
         * @param event
         */
        this.loadDir                = function(params) {
            var link, imgPosition, panelChanged, pathParams,
                isRefresh, panel, nohistory,
                p               = params,
                currentLink     = DOM.getCurrentLink(),
                href            = currentLink.href;

            if (params) {
                pathParams      = p.path;
                isRefresh       = p.isRefresh;
                panel           = p.panel;
                nohistory       = p.nohistory;
            }

            if (pathParams)
                link = pathParams;
            else
                link = Util.rmStr(href, CloudCmd.HOST);

            if (link || currentLink.target !== '_blank') {
                if (panel && panel !== Info.panel) {
                    DOM.changePanel();
                    panelChanged = true;
                }

                if (panelChanged || isRefresh)
                    imgPosition = {
                        top: true
                    };

                Images.showLoad(imgPosition);

                /* загружаем содержимое каталога */
                ajaxLoad(link, {
                    refresh     : isRefresh,
                    nohistory   : nohistory
                }, panel);
            }
        };

        /**
         * функция устанавливает курсор на каталог
         * с которого мы пришли, если мы поднялись
         * в верх по файловой структуре
         * @param pDirName - имя каталога с которого мы пришли
         */
        function currentToParent(dirName) {
            var rootDir;
            /* убираем слэш с имени каталога */
            dirName     = Util.rmStr(dirName, '/');
            rootDir     = DOM.getCurrentFileByName(dirName);

            if (rootDir)
                DOM.setCurrentFile(rootDir);
        }

        /**
         * function load modules
         * @pParams = {name, path, func, dobefore, arg}
         */
        function loadModule(params) {
            var name, path, func, doBefore, slash, afterSlash,
                isContain;

            if (params) {
                name        = params.name,
                path        = params.path,
                func        = params.func,
                doBefore    = params.dobefore;

                if (path && !name) {
                    name = Util.getStrBigFirst(path);
                    name = Util.rmStr(name, '.js');

                    slash = name.indexOf('/');
                    if (slash > 0) {
                        afterSlash = name.substr(slash);
                        name = Util.rmStr(name, afterSlash);
                    }
                }

                isContain = Util.isContainStr(path, '.js');

                if (!isContain)
                    path += '.js';

                if (!CloudCmd[name]) {
                    CloudCmd[name] = function() {
                        var pathFull    = CloudCmd.LIBDIRCLIENT + path,
                            args        = arguments;

                        Util.exec(doBefore);

                        return DOM.jsload(pathFull, func ||
                            function() {
                                var Proto = CloudCmd[name];

                                if (Util.isFunction(Proto))
                                    CloudCmd[name] = Util.applyConstructor(Proto, args);
                            });
                    };

                    CloudCmd[name].show = CloudCmd[name];
                }
            }
        }

        /** Конструктор CloudClient, который
         * выполняет весь функционал по
         * инициализации
         */
        this.init                    = function() {
            var callback = function() {
                    Util.exec.series([
                        initModules,
                        baseInit,
                        Util.exec.with(CloudCmd.route, location.hash)
                    ]);
                },

                func = function(callback) {
                    var src = CloudCmd.LIBDIRCLIENT + 'polyfill.js';

                    CloudCmd.OLD_BROWSER = true;

                    DOM.jqueryLoad(function() {
                        DOM.jsload(src, callback);
                    });
                };

            Util.exec.if(document.body.scrollIntoViewIfNeeded, callback, func);
        };

        this.route                   = function(path) {
            var module, file, current, msg,
                query   = path.split('/');

            if (path) {
                module      = query[0];
                module      = Util.slice(module, 1).join('');
                module      = Util.getStrBigFirst(module);

                file        = query[1];
                current     = DOM.getCurrentFileByName(file);

                if (file && !current) {
                    msg = CloudFunc.formatMsg('set current file', file, 'error');
                    Util.log(msg);
                } else {
                    DOM.setCurrentFile(current);
                    CloudCmd.execFromModule(module, 'show');
                }
            }
        };

        function initModules(callback) {
            Util.exec.if(CloudCmd.Key, function() {
                Key          = new CloudCmd.Key();
                CloudCmd.Key = Key;
                Key.bind();
            }, function(callback) {
                loadModule({
                    /* привязываем клавиши к функциям */
                    path  : 'key.js',
                    func : callback
                 });
             });

            CloudCmd.getModules(function(modules) {
                var storageObj, mod, path,
                    STORAGE     = 'storage',
                    showLoad    = Images.showLoad.bind(Images),

                    doBefore    = {
                        'edit'                  : showLoad,
                        'menu'                  : showLoad,
                        'storage/_filepicker'   : showLoad
                    },

                    load = function(name, path, func) {
                        loadModule({
                            name        : name,
                            path        : path,
                            dobefore    : func
                        });
                    };

                if (!modules)
                    modules = [];

                modules.forEach(function(module) {
                    if (Util.isString(module))
                        load(null, module, doBefore[module]);
                });

                storageObj = Util.findObjByNameInArr(modules, STORAGE),
                mod        = Util.getNamesFromObjArray(storageObj);

                mod.forEach(function(name) {
                    path = STORAGE + '/_' + name.toLowerCase();

                    load(name, path, doBefore[path]);
                });

                Util.exec(callback);
            });
        }

        function baseInit(pCallBack) {
            var files       = DOM.getFiles(),
                LEFT        = CloudFunc.PANEL_LEFT,
                RIGHT       = CloudFunc.PANEL_RIGHT;

            /* выделяем строку с первым файлом                                  */
            if (files)
                DOM.setCurrentFile(files[0]);

            Listeners = CloudCmd.Listeners;
            Listeners.init();
            /* загружаем Google Analytics */
            Listeners.analytics();
            Listeners.changeLinks(LEFT);
            Listeners.changeLinks(RIGHT);

            Listeners.setOnPanel(LEFT);
            Listeners.setOnPanel(RIGHT);

            Listeners.initKeysPanel();

            CloudCmd.getConfig(function(config) {
                var localStorage    = config.localStorage,
                    dirPath         = DOM.getCurrentDirPath();

                /* устанавливаем переменную доступности кэша                    */
                Storage.setAllowed(localStorage);
                /* Устанавливаем кэш корневого каталога                         */

                dirPath     = CloudFunc.rmLastSlash(dirPath) || '/';

                Storage.get(dirPath, function(data) {
                    if (!data) {
                        data    = getJSONfromFileTable();
                        Storage.set(dirPath, data);
                    }
                });
            });

            Util.exec(CloudCmd.Key);
            Util.exec(pCallBack);
        }

        this.getTemplate = function(data, name, callback) {
            var path    = DIR_HTML + name +'.html',
                func    = getSystemFile(data, path);

            func(callback);
        };

        function getSystemFile(pGlobal, pURL) {

            function lGetSysFile(callback) {
                Util.exec.if(pGlobal, callback, function() {
                    DOM.ajax({
                        url     : pURL,
                        success : function(pLocal) {
                            pGlobal = pLocal;
                            Util.exec(callback, pLocal);
                        }
                    });
                });
            }

            return lGetSysFile;
        }

        this.setConfig              = function(config) { Config = config; };
        this.getConfig              = function(callback) {
            Util.exec.if(Config, callback, function() {
                var RESTful = DOM.RESTful;

                RESTful.Config.read(function(config) {
                    Config = config;
                    callback(config);
                });
            });
        };

        this.getModules             = getSystemFile(Modules,        DIR_JSON    + 'modules.json');
        this.getExt                 = getSystemFile(Extensions,     DIR_JSON    + 'ext.json');
        this.getFileTemplate        = getSystemFile(FileTemplate,   DIR_HTML_FS + 'file.html');
        this.getPathTemplate        = getSystemFile(PathTemplate,   DIR_HTML_FS + 'path.html');
        this.getLinkTemplate        = getSystemFile(PathTemplate,   DIR_HTML_FS + 'link.html');

        this.execFromModule         = function(moduleName, funcName, params) {
            var obj     = CloudCmd[moduleName],
                isObj   = Util.isObject(obj);

            Util.exec.if(isObj,
                function() {
                    var obj     = CloudCmd[moduleName],
                        func    = obj[funcName];

                    Util.exec(func, params);
                },

                function(callback) {
                    Util.exec(obj, callback);
                });
        };

        this.refresh                =  function(current, panelParam) {
            var NEEDREFRESH     = true,
                panel           = panelParam || current && current.parentElement,
                path            = DOM.getCurrentDirPath(panel),
                link            = CloudFunc.FS + path,
                notSlashlLink   = CloudFunc.rmLastSlash(link);

            CloudCmd.loadDir({
                path        : notSlashlLink,
                isRefresh   : NEEDREFRESH,
                panel: panel
            });
        };

        /**
         * Функция загружает json-данные о Файловой Системе
         * через ajax-запрос.
         * @param path - каталог для чтения
         * @param pOptions
         * { refresh, nohistory } - необходимость обновить данные о каталоге
         */
        function ajaxLoad(path, options, panel) {
            var SLASH       = '/',
                fsPath      = decodeURI(path),
                cleanPath   = Util.rmStrOnce(fsPath, CloudFunc.FS) || SLASH;

            if (!options)
                options    = {};

            Util.log ('reading dir: "' + cleanPath + '";');

            Storage.get(cleanPath, function(json) {
                var RESTful     = DOM.RESTful,
                    obj         = Util.parseJSON(json),
                    isRefresh   = options.refresh,
                    nohistory   = options.nohistory;

                if (!isRefresh && json)
                    createFileTable(obj, panel, nohistory);
                else
                    RESTful.read(cleanPath, 'json', function(obj) {
                        createFileTable(obj, panel, nohistory);
                        Storage.set(cleanPath, obj);
                    });
            });
        }

        /**
         * Функция строит файловую таблицу
         * @param json  - данные о файлах
         * @param panelParam
         * @param nohistory
         */
        function createFileTable(json, panelParam, nohistory) {
            var files,
                panel           = panelParam || DOM.getPanel(),
                /* getting current element if was refresh */
                path            = DOM.getCurrentDirPath(panel),
                wasRefresh      = path === json.path,
                funcs           = [
                    CloudCmd.getFileTemplate,
                    CloudCmd.getPathTemplate,
                    CloudCmd.getLinkTemplate
                ];

            Util.exec.parallel(funcs, function(templFile, templPath, templLink) {
                var n, found, varCurrent, varName, current,
                    dir     = DOM.getCurrentDirName(),
                    name    = DOM.getCurrentName(),
                    i       = panel.childNodes.length;

                Listeners.unSetOnPanel(panel.id);

                while(i--)
                    panel.removeChild(panel.lastChild);

                panel.innerHTML = CloudFunc.buildFromJSON(json, templFile, templPath, templLink);
                files           = DOM.getFiles(panel);

                /* searching current file */
                if (wasRefresh) {
                    n = files.length;

                    for (i = 0; i < n ; i++) {
                        varCurrent  = files[i],
                        varName     = DOM.getCurrentName(varCurrent);
                        found       = varName === name;

                        if (found) {
                            current    = files[i];
                            break;
                        }
                    }
                }
                if (!found) /* .. */
                    current = files[0];

                DOM.setCurrentFile(current, {
                    nohistory: nohistory
                });

                Listeners.changeLinks(panel.id);
                Listeners.setOnPanel(panel.id);

                if (name === '..' && dir !== '/')
                    currentToParent(dir);
            });
        }

        /**
         * Функция генерирует JSON из html-таблицы файлов и
         * используеться при первом заходе в корень
         */
        function getJSONfromFileTable() {
            var current, name, size, owner, mode, ret,
                path        = DOM.getCurrentDirPath(),
                infoFiles   = Info.files,

                fileTable   = {
                    path    : path,
                    files   : []
                },

                files       = fileTable.files,

                i, n        = infoFiles.length;

            for (i = 0; i < n; i++) {
                current     = infoFiles[i];
                name        = DOM.getCurrentName(current);
                size        = DOM.getCurrentSize(current);
                owner       = DOM.getCurrentOwner(current);
                mode        = DOM.getCurrentMode(current);

                mode        = CloudFunc.getNumericPermissions(mode);

                if (name !== '..')
                    files.push({
                        name    : name,
                        size    : size,
                        mode    : mode,
                        owner   : owner
                    });
            }

            ret = Util.stringifyJSON(fileTable);

            return ret;
        }

        this.goToParentDir          = function() {
            var path        = Info.dirPath,
                parentPath  = Info.parentDirPath;

            if (path !== parentPath) {
                path    = parentPath;
                path    = CloudFunc.FS + CloudFunc.rmLastSlash(path);

                CloudCmd.loadDir({
                    path: path
                });
            }
        };

    }
})(this, Util, DOM, CloudFunc);
<commit_msg>fix(client) createFileTable: rm unSetOnPanel<commit_after>var Util, DOM, CloudFunc;

(function(scope, Util, DOM, CloudFunc) {
    'use strict';

    scope.CloudCmd = new CloudCmdProto(Util, DOM, CloudFunc);

    function CloudCmdProto(Util, DOM, CloudFunc) {
        var Key, Config, Modules, Extensions,
            FileTemplate, PathTemplate, Listeners,
            DIR_HTML                = '/html/',
            DIR_HTML_FS             = DIR_HTML + 'fs/',
            DIR_JSON                = '/json/',

            Images                  = DOM.Images,
            Info                    = DOM.CurrentInfo,
            CloudCmd                = this,
            Storage                 = DOM.Storage;

        this.MousePosition          = {};
        this.LIBDIR                 = '/lib/';
        this.LIBDIRCLIENT           = '/lib/client/';
        this.MIN_ONE_PANEL_WIDTH    = 1155;
        this.OLD_BROWSER            = false;
        this.HOST                   = location.origin ||
                                      location.protocol + '//' + location.host;
        /**
         * Функция привязываеться ко всем ссылкам и
         *  загружает содержимое каталогов
         *
         * @param params - {
         *          paramLink - ссылка
         *          needRefresh - необходимость обязательной загрузки данных с сервера
         *          panel
         *      }
         * @param event
         */
        this.loadDir                = function(params) {
            var link, imgPosition, panelChanged, pathParams,
                isRefresh, panel, nohistory,
                p               = params,
                currentLink     = DOM.getCurrentLink(),
                href            = currentLink.href;

            if (params) {
                pathParams      = p.path;
                isRefresh       = p.isRefresh;
                panel           = p.panel;
                nohistory       = p.nohistory;
            }

            if (pathParams)
                link = pathParams;
            else
                link = Util.rmStr(href, CloudCmd.HOST);

            if (link || currentLink.target !== '_blank') {
                if (panel && panel !== Info.panel) {
                    DOM.changePanel();
                    panelChanged = true;
                }

                if (panelChanged || isRefresh)
                    imgPosition = {
                        top: true
                    };

                Images.showLoad(imgPosition);

                /* загружаем содержимое каталога */
                ajaxLoad(link, {
                    refresh     : isRefresh,
                    nohistory   : nohistory
                }, panel);
            }
        };

        /**
         * функция устанавливает курсор на каталог
         * с которого мы пришли, если мы поднялись
         * в верх по файловой структуре
         * @param pDirName - имя каталога с которого мы пришли
         */
        function currentToParent(dirName) {
            var rootDir;
            /* убираем слэш с имени каталога */
            dirName     = Util.rmStr(dirName, '/');
            rootDir     = DOM.getCurrentFileByName(dirName);

            if (rootDir)
                DOM.setCurrentFile(rootDir);
        }

        /**
         * function load modules
         * @pParams = {name, path, func, dobefore, arg}
         */
        function loadModule(params) {
            var name, path, func, doBefore, slash, afterSlash,
                isContain;

            if (params) {
                name        = params.name,
                path        = params.path,
                func        = params.func,
                doBefore    = params.dobefore;

                if (path && !name) {
                    name = Util.getStrBigFirst(path);
                    name = Util.rmStr(name, '.js');

                    slash = name.indexOf('/');
                    if (slash > 0) {
                        afterSlash = name.substr(slash);
                        name = Util.rmStr(name, afterSlash);
                    }
                }

                isContain = Util.isContainStr(path, '.js');

                if (!isContain)
                    path += '.js';

                if (!CloudCmd[name]) {
                    CloudCmd[name] = function() {
                        var pathFull    = CloudCmd.LIBDIRCLIENT + path,
                            args        = arguments;

                        Util.exec(doBefore);

                        return DOM.jsload(pathFull, func ||
                            function() {
                                var Proto = CloudCmd[name];

                                if (Util.isFunction(Proto))
                                    CloudCmd[name] = Util.applyConstructor(Proto, args);
                            });
                    };

                    CloudCmd[name].show = CloudCmd[name];
                }
            }
        }

        /** Конструктор CloudClient, который
         * выполняет весь функционал по
         * инициализации
         */
        this.init                    = function() {
            var callback = function() {
                    Util.exec.series([
                        initModules,
                        baseInit,
                        Util.exec.with(CloudCmd.route, location.hash)
                    ]);
                },

                func = function(callback) {
                    var src = CloudCmd.LIBDIRCLIENT + 'polyfill.js';

                    CloudCmd.OLD_BROWSER = true;

                    DOM.jqueryLoad(function() {
                        DOM.jsload(src, callback);
                    });
                };

            Util.exec.if(document.body.scrollIntoViewIfNeeded, callback, func);
        };

        this.route                   = function(path) {
            var module, file, current, msg,
                query   = path.split('/');

            if (path) {
                module      = query[0];
                module      = Util.slice(module, 1).join('');
                module      = Util.getStrBigFirst(module);

                file        = query[1];
                current     = DOM.getCurrentFileByName(file);

                if (file && !current) {
                    msg = CloudFunc.formatMsg('set current file', file, 'error');
                    Util.log(msg);
                } else {
                    DOM.setCurrentFile(current);
                    CloudCmd.execFromModule(module, 'show');
                }
            }
        };

        function initModules(callback) {
            Util.exec.if(CloudCmd.Key, function() {
                Key          = new CloudCmd.Key();
                CloudCmd.Key = Key;
                Key.bind();
            }, function(callback) {
                loadModule({
                    /* привязываем клавиши к функциям */
                    path  : 'key.js',
                    func : callback
                 });
             });

            CloudCmd.getModules(function(modules) {
                var storageObj, mod, path,
                    STORAGE     = 'storage',
                    showLoad    = Images.showLoad.bind(Images),

                    doBefore    = {
                        'edit'                  : showLoad,
                        'menu'                  : showLoad,
                        'storage/_filepicker'   : showLoad
                    },

                    load = function(name, path, func) {
                        loadModule({
                            name        : name,
                            path        : path,
                            dobefore    : func
                        });
                    };

                if (!modules)
                    modules = [];

                modules.forEach(function(module) {
                    if (Util.isString(module))
                        load(null, module, doBefore[module]);
                });

                storageObj = Util.findObjByNameInArr(modules, STORAGE),
                mod        = Util.getNamesFromObjArray(storageObj);

                mod.forEach(function(name) {
                    path = STORAGE + '/_' + name.toLowerCase();

                    load(name, path, doBefore[path]);
                });

                Util.exec(callback);
            });
        }

        function baseInit(pCallBack) {
            var files       = DOM.getFiles(),
                LEFT        = CloudFunc.PANEL_LEFT,
                RIGHT       = CloudFunc.PANEL_RIGHT;

            /* выделяем строку с первым файлом                                  */
            if (files)
                DOM.setCurrentFile(files[0]);

            Listeners = CloudCmd.Listeners;
            Listeners.init();
            /* загружаем Google Analytics */
            Listeners.analytics();
            Listeners.changeLinks(LEFT);
            Listeners.changeLinks(RIGHT);

            Listeners.setOnPanel(LEFT);
            Listeners.setOnPanel(RIGHT);

            Listeners.initKeysPanel();

            CloudCmd.getConfig(function(config) {
                var localStorage    = config.localStorage,
                    dirPath         = DOM.getCurrentDirPath();

                /* устанавливаем переменную доступности кэша                    */
                Storage.setAllowed(localStorage);
                /* Устанавливаем кэш корневого каталога                         */

                dirPath     = CloudFunc.rmLastSlash(dirPath) || '/';

                Storage.get(dirPath, function(data) {
                    if (!data) {
                        data    = getJSONfromFileTable();
                        Storage.set(dirPath, data);
                    }
                });
            });

            Util.exec(CloudCmd.Key);
            Util.exec(pCallBack);
        }

        this.getTemplate = function(data, name, callback) {
            var path    = DIR_HTML + name +'.html',
                func    = getSystemFile(data, path);

            func(callback);
        };

        function getSystemFile(pGlobal, pURL) {

            function lGetSysFile(callback) {
                Util.exec.if(pGlobal, callback, function() {
                    DOM.ajax({
                        url     : pURL,
                        success : function(pLocal) {
                            pGlobal = pLocal;
                            Util.exec(callback, pLocal);
                        }
                    });
                });
            }

            return lGetSysFile;
        }

        this.setConfig              = function(config) { Config = config; };
        this.getConfig              = function(callback) {
            Util.exec.if(Config, callback, function() {
                var RESTful = DOM.RESTful;

                RESTful.Config.read(function(config) {
                    Config = config;
                    callback(config);
                });
            });
        };

        this.getModules             = getSystemFile(Modules,        DIR_JSON    + 'modules.json');
        this.getExt                 = getSystemFile(Extensions,     DIR_JSON    + 'ext.json');
        this.getFileTemplate        = getSystemFile(FileTemplate,   DIR_HTML_FS + 'file.html');
        this.getPathTemplate        = getSystemFile(PathTemplate,   DIR_HTML_FS + 'path.html');
        this.getLinkTemplate        = getSystemFile(PathTemplate,   DIR_HTML_FS + 'link.html');

        this.execFromModule         = function(moduleName, funcName, params) {
            var obj     = CloudCmd[moduleName],
                isObj   = Util.isObject(obj);

            Util.exec.if(isObj,
                function() {
                    var obj     = CloudCmd[moduleName],
                        func    = obj[funcName];

                    Util.exec(func, params);
                },

                function(callback) {
                    Util.exec(obj, callback);
                });
        };

        this.refresh                =  function(current, panelParam) {
            var NEEDREFRESH     = true,
                panel           = panelParam || current && current.parentElement,
                path            = DOM.getCurrentDirPath(panel),
                link            = CloudFunc.FS + path,
                notSlashlLink   = CloudFunc.rmLastSlash(link);

            CloudCmd.loadDir({
                path        : notSlashlLink,
                isRefresh   : NEEDREFRESH,
                panel: panel
            });
        };

        /**
         * Функция загружает json-данные о Файловой Системе
         * через ajax-запрос.
         * @param path - каталог для чтения
         * @param pOptions
         * { refresh, nohistory } - необходимость обновить данные о каталоге
         */
        function ajaxLoad(path, options, panel) {
            var SLASH       = '/',
                fsPath      = decodeURI(path),
                cleanPath   = Util.rmStrOnce(fsPath, CloudFunc.FS) || SLASH;

            if (!options)
                options    = {};

            Util.log ('reading dir: "' + cleanPath + '";');

            Storage.get(cleanPath, function(json) {
                var RESTful     = DOM.RESTful,
                    obj         = Util.parseJSON(json),
                    isRefresh   = options.refresh,
                    nohistory   = options.nohistory;

                if (!isRefresh && json)
                    createFileTable(obj, panel, nohistory);
                else
                    RESTful.read(cleanPath, 'json', function(obj) {
                        createFileTable(obj, panel, nohistory);
                        Storage.set(cleanPath, obj);
                    });
            });
        }

        /**
         * Функция строит файловую таблицу
         * @param json  - данные о файлах
         * @param panelParam
         * @param nohistory
         */
        function createFileTable(json, panelParam, nohistory) {
            var files,
                panel           = panelParam || DOM.getPanel(),
                /* getting current element if was refresh */
                path            = DOM.getCurrentDirPath(panel),
                wasRefresh      = path === json.path,
                funcs           = [
                    CloudCmd.getFileTemplate,
                    CloudCmd.getPathTemplate,
                    CloudCmd.getLinkTemplate
                ];

            Util.exec.parallel(funcs, function(templFile, templPath, templLink) {
                var n, found, varCurrent, varName, current,
                    dir     = DOM.getCurrentDirName(),
                    name    = DOM.getCurrentName(),
                    i       = panel.childNodes.length;

                while(i--)
                    panel.removeChild(panel.lastChild);

                panel.innerHTML = CloudFunc.buildFromJSON(json, templFile, templPath, templLink);
                files           = DOM.getFiles(panel);

                /* searching current file */
                if (wasRefresh) {
                    n = files.length;

                    for (i = 0; i < n ; i++) {
                        varCurrent  = files[i],
                        varName     = DOM.getCurrentName(varCurrent);
                        found       = varName === name;

                        if (found) {
                            current    = files[i];
                            break;
                        }
                    }
                }
                if (!found) /* .. */
                    current = files[0];

                DOM.setCurrentFile(current, {
                    nohistory: nohistory
                });

                Listeners.changeLinks(panel.id);
                Listeners.setOnPanel(panel.id);

                if (name === '..' && dir !== '/')
                    currentToParent(dir);
            });
        }

        /**
         * Функция генерирует JSON из html-таблицы файлов и
         * используеться при первом заходе в корень
         */
        function getJSONfromFileTable() {
            var current, name, size, owner, mode, ret,
                path        = DOM.getCurrentDirPath(),
                infoFiles   = Info.files,

                fileTable   = {
                    path    : path,
                    files   : []
                },

                files       = fileTable.files,

                i, n        = infoFiles.length;

            for (i = 0; i < n; i++) {
                current     = infoFiles[i];
                name        = DOM.getCurrentName(current);
                size        = DOM.getCurrentSize(current);
                owner       = DOM.getCurrentOwner(current);
                mode        = DOM.getCurrentMode(current);

                mode        = CloudFunc.getNumericPermissions(mode);

                if (name !== '..')
                    files.push({
                        name    : name,
                        size    : size,
                        mode    : mode,
                        owner   : owner
                    });
            }

            ret = Util.stringifyJSON(fileTable);

            return ret;
        }

        this.goToParentDir          = function() {
            var path        = Info.dirPath,
                parentPath  = Info.parentDirPath;

            if (path !== parentPath) {
                path    = parentPath;
                path    = CloudFunc.FS + CloudFunc.rmLastSlash(path);

                CloudCmd.loadDir({
                    path: path
                });
            }
        };

    }
})(this, Util, DOM, CloudFunc);
<|endoftext|>


<commit_before>/**
 * setup.js
 */
"use strict";
{
  /* api */
  const {ChildProcess, CmdArgs} = require("./modules/child-process");
  const {allowedField, browserData} = require("./modules/browser-data");
  const {escapeChar, getType, isString, logError} = require("./modules/common");
  const {
    createDir, createFile, getAbsPath, isDir, isExecutable, isFile,
  } = require("./modules/file-util");
  const os = require("os");
  const path = require("path");
  const process = require("process");
  const readline = require("readline");

  /* constants */
  const {HOST} = require("./modules/constant");
  const CHAR = "utf8";
  const DIR_CWD = process.cwd();
  const DIR_HOME = os.homedir();
  const IS_MAC = os.platform() === "darwin";
  const IS_WIN = os.platform() === "win32";
  const PERM_DIR = 0o700;
  const PERM_EXEC = 0o700;
  const PERM_FILE = 0o600;

  /* editor config */
  const editorConfig = {
    editorPath: "",
    cmdArgs: [],
    fileAfterCmdArgs: false,
  };

  /* variables */
  const vars = {
    browser: null,
    configDir: [DIR_CWD, "config"],
  };

  /**
   * set browser
   * @param {string} browser - browser config data
   * @returns {void}
   */
  const setBrowser = browser => {
    browser && Object.keys(browser).length && browser.alias &&
      (vars.browser = browser);
  };

  /**
   * set config directory
   * @param {string} dir - directory path
   * @returns {void}
   */
  const setConfigDir = dir => {
    const configPath = getAbsPath(dir);
    if (!configPath) {
      throw new Error(`Failed to normalize ${dir}`);
    }
    if (!configPath.startsWith(DIR_HOME)) {
      throw new Error(`Config path is not sub directory of ${DIR_HOME}.`);
    }
    const homeDir = escapeChar(DIR_HOME, /(\\)/g);
    const reHomeDir = new RegExp(`^(?:${homeDir}|~)`);
    const subDir = (configPath.replace(reHomeDir, "")).split(path.sep)
                     .filter(i => i);
    vars.configDir = subDir.length && [DIR_HOME, ...subDir] || [DIR_HOME];
  };

  /**
   * get browser data
   * @param {string} key - key
   * @returns {Object} - browser data
   */
  const getBrowserData = key => {
    let browser;
    key = isString(key) && key.toLowerCase().trim();
    if (key) {
      const items = Object.keys(browserData);
      for (const item of items) {
        if (item === key) {
          const obj = browserData[item];
          if (IS_WIN && obj.regWin || IS_MAC && obj.hostMac ||
              !IS_WIN && !IS_MAC && obj.hostLinux) {
            browser = browserData[item];
          }
          break;
        }
      }
    }
    return browser || null;
  };

  /**
   * get browser specific config directory
   * @returns {?Array} - config directory array
   */
  const getBrowserConfigDir = () => {
    const {browser, configDir} = vars;
    let dir;
    if (browser) {
      const {alias, aliasLinux, aliasMac, aliasWin} = browser;
      dir = IS_WIN && [...configDir, aliasWin || alias] ||
            IS_MAC && [...configDir, aliasMac || alias] ||
            [...configDir, aliasLinux || alias];
    }
    return dir || null;
  };

  /* file handlers */
  /**
   * create editor config
   * @param {string} configPath - config directory path
   * @returns {string} - editor config path
   */
  const createEditorConfig = async configPath => {
    if (await !isDir(configPath)) {
      throw new Error(`No such directory: ${configPath}.`);
    }
    const editorConfigPath = path.join(configPath, "editorconfig.json");
    await createFile(
      editorConfigPath, JSON.stringify(editorConfig, null, "  "),
      {encoding: CHAR, flag: "w", mode: PERM_FILE}
    );
    if (await !isFile(editorConfigPath)) {
      throw new Error(`Failed to create ${editorConfigPath}.`);
    }
    console.info(`Created: ${editorConfigPath}`);
    return editorConfigPath;
  };

  /**
   * create app manifest
   * @param {string} configPath - config direcory path
   * @param {string} shellPath - shell script path
   * @returns {string} - app manifest path
   */
  const createAppManifest = async (configPath, shellPath) => {
    if (await !isDir(configPath)) {
      throw new Error(`No such directory: ${configPath}.`);
    }
    if (await !isFile(shellPath)) {
      throw new Error(`No such file: ${shellPath}.`);
    }
    const {browser} = vars;
    if (!browser) {
      throw new Error(`Expected Object but got ${getType(browser)}.`);
    }
    const {hostLinux, hostMac, regWin, type} = browser;
    const {key, value} = allowedField[type];
    const manifest = JSON.stringify({
      [key]: [...value],
      description: "Native messaging host for withExEditor",
      name: HOST,
      path: shellPath,
      type: "stdio",
    });
    const fileName = `${HOST}.json`;
    const filePath = path.resolve(
      IS_WIN && path.join(configPath, fileName) ||
      IS_MAC && path.join(...hostMac, fileName) ||
      path.join(...hostLinux, fileName)
    );
    if (IS_WIN) {
      const reg = path.join(process.env.WINDIR, "system32", "reg.exe");
      const regKey = path.join(...regWin);
      const args = ["add", regKey, "/ve", "/d", filePath, "/f"];
      const opt = {
        cwd: null,
        encoding: CHAR,
        env: process.env,
      };
      const proc = await (new ChildProcess(reg, args, opt)).spawn();
      proc.on("error", e => {
        throw e;
      });
      proc.stderr.on("data", data => {
        data && console.error(`stderr: ${reg}: ${data}`);
      });
      proc.on("close", code => {
        if (code === 0) {
          console.info(`Created: ${regKey}`);
        } else {
          console.warn(`${reg} exited with ${code}.`);
        }
      });
    } else {
      const hostDir = IS_MAC && hostMac || hostLinux;
      const hostDirPath = await createDir(hostDir, PERM_DIR);
      if (await !isDir(hostDirPath)) {
        throw new Error(`Failed to create ${path.join(...hostDir)}.`);
      }
    }
    await createFile(
      filePath, manifest,
      {encoding: CHAR, flag: "w", mode: PERM_FILE}
    );
    if (await !isFile(filePath)) {
      throw new Error(`Failed to create ${filePath}.`);
    }
    console.info(`Created: ${filePath}`);
    return filePath;
  };

  /**
   * create shell script
   * @param {string} configPath - config directory path
   * @returns {string} - shell script path
   */
  const createShellScript = async configPath => {
    if (await !isDir(configPath)) {
      throw new Error(`No such directory: ${configPath}.`);
    }
    const shellExt = IS_WIN && "cmd" || "sh";
    const shellPath = path.join(configPath, `${HOST}.${shellExt}`);
    const indexPath = path.resolve(path.join(DIR_CWD, "index.js"));
    if (await isFile(indexPath)) {
      const node = process.argv0;
      const cmd = `${node} ${indexPath}`;
      const file = IS_WIN && `@echo off\n${cmd}\n` ||
                   `#!/usr/bin/env bash\n${cmd}\n`;
      await createFile(
        shellPath, file,
        {encoding: CHAR, flag: "w", mode: PERM_EXEC}
      );
    }
    if (await !isFile(shellPath)) {
      throw new Error(`Failed to create ${shellPath}.`);
    }
    console.info(`Created: ${shellPath}`);
    return shellPath;
  };

  /**
   * create config directory
   * @returns {string} - config directory path
   */
  const createConfig = async () => {
    const dir = await getBrowserConfigDir();
    if (!Array.isArray(dir)) {
      throw new TypeError(`Expected Array but got ${getType(dir)}.`);
    }
    const configPath = await createDir(dir, PERM_DIR);
    if (await !isDir(configPath)) {
      throw new Error(`Failed to create ${path.join(dir)}.`);
    }
    console.info(`Created: ${configPath}`);
    return configPath;
  };

  /**
   * setup
   * @returns {Promise.<Array>} - results of each handler
   */
  const setup = async () => {
    const configPath = await createConfig();
    const shellPath = await createShellScript(configPath);
    return Promise.all([
      createAppManifest(configPath, shellPath),
      createEditorConfig(configPath),
    ]);
  };

  /* readline */
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });

  /* questions */
  const ques = {
    browser: "Enter which browser you would like to set up the host for:\n",
    editorPath: "Enter editor path:\n",
    cmdArgs: "Enter command line options:\n",
    filePos: "Put file path after command arguments? [y/n]\n",
  };

  /**
   * abort setup
   * @param {string} msg - message
   * @returns {void}
   */
  const abortSetup = msg => {
    console.info(`Setup aborted: ${msg}`);
    process.exit(1);
  };

  /**
   * handle editor temporary file position input
   * @param {string} ans - user input
   * @returns {AsyncFuncrtion} - setup
   */
  const handleFilePosInput = ans => {
    if (isString(ans)) {
      ans = ans.trim();
      /^y(?:es)?$/i.test(ans) && (editorConfig.fileAfterCmdArgs = true);
    }
    rl.close();
    return setup().catch(logError);
  };

  /**
   * handle editor cmd args input
   * @param {string} ans - user input
   * @returns {void}
   */
  const handleCmdArgsInput = ans => {
    if (isString(ans)) {
      editorConfig.cmdArgs = (new CmdArgs(ans.trim())).toArray();
    }
    rl.question(ques.filePos, handleFilePosInput);
  };

  /**
   * handle editor path input
   * @param {string} ans - user input
   * @returns {void}
   */
  const handleEditorPathInput = ans => {
    if (isString(ans)) {
      ans = ans.trim();
      if (ans.length) {
        if (isExecutable(ans)) {
          editorConfig.editorPath = ans;
          rl.question(ques.cmdArgs, handleCmdArgsInput);
        } else {
          console.warn(`${ans} is not executable.`);
          rl.question(ques.editorPath, handleEditorPathInput);
        }
      } else {
        rl.question(ques.cmdArgs, handleCmdArgsInput);
      }
    } else {
      rl.question(ques.cmdArgs, handleCmdArgsInput);
    }
  };

  /**
   * handle browser config directory input
   * @param {string} ans - user input
   * @returns {void}
   */
  const handleBrowserConfigDir = ans => {
    const dir = getBrowserConfigDir();
    if (!Array.isArray(dir)) {
      throw new TypeError(`Expected Array but got ${getType(dir)}.`);
    }
    const msg = `${path.join(...dir)} already exists.`;
    if (isString(ans)) {
      ans = ans.trim();
      if (/^y(?:es)?$/i.test(ans)) {
        rl.question(ques.editorPath, handleEditorPathInput);
      } else {
        abortSetup(msg);
      }
    } else {
      abortSetup(msg);
    }
  };

  /**
   * handle browser input
   * @param {string} ans - user input
   * @returns {void}
   */
  const handleBrowserInput = ans => {
    const msg = "Browser not specified.";
    if (isString(ans)) {
      ans = ans.trim();
      if (ans.length) {
        const browser = getBrowserData(ans);
        browser && setBrowser(browser);
        if (browser) {
          const dir = getBrowserConfigDir();
          if (!Array.isArray(dir)) {
            throw new TypeError(`Expected Array but got ${getType(dir)}.`);
          }
          const dirPath = path.join(...dir);
          if (isDir(dirPath)) {
            rl.question(`${dirPath} already exists. Overwrite? [y/n]\n`,
                        handleBrowserConfigDir);
          } else {
            rl.question(ques.editorPath, handleEditorPathInput);
          }
        } else {
          // TODO: Add custom setup
          abortSetup(`${ans} not supported.`);
        }
      } else {
        abortSetup(msg);
      }
    } else {
      abortSetup(msg);
    }
  };

  /**
   * extract argument
   * @param {string} arg - argument in key=value format
   * @param {RegExp} re - RegExp
   * @returns {string} - argument value
   */
  const extractArg = (arg, re) => {
    let value;
    if (isString(arg) && re && re.ignoreCase) {
      arg = re.exec(arg.trim());
      arg && ([, value] = arg);
    }
    return value || null;
  };

  /* start up */
  {
    const [, , ...args] = process.argv;
    let browser;
    if (Array.isArray(args) && args.length) {
      for (const arg of args) {
        let value;
        if (/^--browser=/i.test(arg)) {
          value = extractArg(arg, /^--browser=(.+)$/i);
          value && (browser = getBrowserData(value));
          browser && setBrowser(browser);
        } else if (/^--config-path=/i.test(arg)) {
          value = extractArg(arg, /^--config-path=(.+)$/i);
          value && setConfigDir(value);
        }
      }
    }
    if (browser) {
      const dir = getBrowserConfigDir();
      if (!Array.isArray(dir)) {
        throw new TypeError(`Expected Array but got ${getType(dir)}.`);
      }
      const dirPath = path.join(...dir);
      if (isDir(dirPath)) {
        rl.question(`${dirPath} already exists. Overwrite? [y/n]\n`,
                    handleBrowserConfigDir);
      } else {
        rl.question(ques.editorPath, handleEditorPathInput);
      }
    } else {
      const arr = [];
      const items = Object.keys(browserData);
      for (const item of items) {
        const obj = browserData[item];
        (IS_WIN && obj.regWin || IS_MAC && obj.hostMac ||
         !IS_WIN && !IS_MAC && obj.hostLinux) &&
          arr.push(item);
      }
      rl.question(`${ques.browser}[${arr.join(" ")}]\n`, handleBrowserInput);
    }
  }
}
<commit_msg>Refactor createFile handling<commit_after>/**
 * setup.js
 */
"use strict";
{
  /* api */
  const {ChildProcess, CmdArgs} = require("./modules/child-process");
  const {allowedField, browserData} = require("./modules/browser-data");
  const {escapeChar, getType, isString, logError} = require("./modules/common");
  const {
    createDir, createFile, getAbsPath, isDir, isExecutable, isFile,
  } = require("./modules/file-util");
  const os = require("os");
  const path = require("path");
  const process = require("process");
  const readline = require("readline");

  /* constants */
  const {HOST} = require("./modules/constant");
  const CHAR = "utf8";
  const DIR_CWD = process.cwd();
  const DIR_HOME = os.homedir();
  const IS_MAC = os.platform() === "darwin";
  const IS_WIN = os.platform() === "win32";
  const PERM_DIR = 0o700;
  const PERM_EXEC = 0o700;
  const PERM_FILE = 0o600;

  /* editor config */
  const editorConfig = {
    editorPath: "",
    cmdArgs: [],
    fileAfterCmdArgs: false,
  };

  /* variables */
  const vars = {
    browser: null,
    configDir: [DIR_CWD, "config"],
  };

  /**
   * set browser
   * @param {string} browser - browser config data
   * @returns {void}
   */
  const setBrowser = browser => {
    browser && Object.keys(browser).length && browser.alias &&
      (vars.browser = browser);
  };

  /**
   * set config directory
   * @param {string} dir - directory path
   * @returns {void}
   */
  const setConfigDir = dir => {
    const configPath = getAbsPath(dir);
    if (!configPath) {
      throw new Error(`Failed to normalize ${dir}`);
    }
    if (!configPath.startsWith(DIR_HOME)) {
      throw new Error(`Config path is not sub directory of ${DIR_HOME}.`);
    }
    const homeDir = escapeChar(DIR_HOME, /(\\)/g);
    const reHomeDir = new RegExp(`^(?:${homeDir}|~)`);
    const subDir = (configPath.replace(reHomeDir, "")).split(path.sep)
                     .filter(i => i);
    vars.configDir = subDir.length && [DIR_HOME, ...subDir] || [DIR_HOME];
  };

  /**
   * get browser data
   * @param {string} key - key
   * @returns {Object} - browser data
   */
  const getBrowserData = key => {
    let browser;
    key = isString(key) && key.toLowerCase().trim();
    if (key) {
      const items = Object.keys(browserData);
      for (const item of items) {
        if (item === key) {
          const obj = browserData[item];
          if (IS_WIN && obj.regWin || IS_MAC && obj.hostMac ||
              !IS_WIN && !IS_MAC && obj.hostLinux) {
            browser = browserData[item];
          }
          break;
        }
      }
    }
    return browser || null;
  };

  /**
   * get browser specific config directory
   * @returns {?Array} - config directory array
   */
  const getBrowserConfigDir = () => {
    const {browser, configDir} = vars;
    let dir;
    if (browser) {
      const {alias, aliasLinux, aliasMac, aliasWin} = browser;
      dir = IS_WIN && [...configDir, aliasWin || alias] ||
            IS_MAC && [...configDir, aliasMac || alias] ||
            [...configDir, aliasLinux || alias];
    }
    return dir || null;
  };

  /* file handlers */
  /**
   * create editor config
   * @param {string} configPath - config directory path
   * @returns {string} - editor config path
   */
  const createEditorConfig = async configPath => {
    if (await !isDir(configPath)) {
      throw new Error(`No such directory: ${configPath}.`);
    }
    const editorConfigPath = path.join(configPath, "editorconfig.json");
    const file = await createFile(
      editorConfigPath, JSON.stringify(editorConfig, null, "  "),
      {encoding: CHAR, flag: "w", mode: PERM_FILE}
    );
    if (!file) {
      throw new Error(`Failed to create ${editorConfigPath}.`);
    }
    console.info(`Created: ${editorConfigPath}`);
    return editorConfigPath;
  };

  /**
   * create app manifest
   * @param {string} configPath - config direcory path
   * @param {string} shellPath - shell script path
   * @returns {string} - app manifest path
   */
  const createAppManifest = async (configPath, shellPath) => {
    if (await !isDir(configPath)) {
      throw new Error(`No such directory: ${configPath}.`);
    }
    if (await !isFile(shellPath)) {
      throw new Error(`No such file: ${shellPath}.`);
    }
    const {browser} = vars;
    if (!browser) {
      throw new Error(`Expected Object but got ${getType(browser)}.`);
    }
    const {hostLinux, hostMac, regWin, type} = browser;
    const {key, value} = allowedField[type];
    const manifest = JSON.stringify({
      [key]: [...value],
      description: "Native messaging host for withExEditor",
      name: HOST,
      path: shellPath,
      type: "stdio",
    });
    const fileName = `${HOST}.json`;
    const filePath = path.resolve(
      IS_WIN && path.join(configPath, fileName) ||
      IS_MAC && path.join(...hostMac, fileName) ||
      path.join(...hostLinux, fileName)
    );
    if (IS_WIN) {
      const reg = path.join(process.env.WINDIR, "system32", "reg.exe");
      const regKey = path.join(...regWin);
      const args = ["add", regKey, "/ve", "/d", filePath, "/f"];
      const opt = {
        cwd: null,
        encoding: CHAR,
        env: process.env,
      };
      const proc = await (new ChildProcess(reg, args, opt)).spawn();
      proc.on("error", e => {
        throw e;
      });
      proc.stderr.on("data", data => {
        data && console.error(`stderr: ${reg}: ${data}`);
      });
      proc.on("close", code => {
        if (code === 0) {
          console.info(`Created: ${regKey}`);
        } else {
          console.warn(`${reg} exited with ${code}.`);
        }
      });
    } else {
      const hostDir = IS_MAC && hostMac || hostLinux;
      const hostDirPath = await createDir(hostDir, PERM_DIR);
      if (await !isDir(hostDirPath)) {
        throw new Error(`Failed to create ${path.join(...hostDir)}.`);
      }
    }
    const file = await createFile(
      filePath, manifest, {encoding: CHAR, flag: "w", mode: PERM_FILE}
    );
    if (!file) {
      throw new Error(`Failed to create ${filePath}.`);
    }
    console.info(`Created: ${filePath}`);
    return filePath;
  };

  /**
   * create shell script
   * @param {string} configPath - config directory path
   * @returns {string} - shell script path
   */
  const createShellScript = async configPath => {
    if (await !isDir(configPath)) {
      throw new Error(`No such directory: ${configPath}.`);
    }
    const shellExt = IS_WIN && "cmd" || "sh";
    const shellPath = path.join(configPath, `${HOST}.${shellExt}`);
    const indexPath = path.resolve(path.join(DIR_CWD, "index.js"));
    if (await isFile(indexPath)) {
      const node = process.argv0;
      const cmd = `${node} ${indexPath}`;
      const content = IS_WIN && `@echo off\n${cmd}\n` ||
                        `#!/usr/bin/env bash\n${cmd}\n`;
      const file = await createFile(
        shellPath, content, {encoding: CHAR, flag: "w", mode: PERM_EXEC}
      );
    }
    if (!file) {
      throw new Error(`Failed to create ${shellPath}.`);
    }
    console.info(`Created: ${shellPath}`);
    return shellPath;
  };

  /**
   * create config directory
   * @returns {string} - config directory path
   */
  const createConfig = async () => {
    const dir = await getBrowserConfigDir();
    if (!Array.isArray(dir)) {
      throw new TypeError(`Expected Array but got ${getType(dir)}.`);
    }
    const configPath = await createDir(dir, PERM_DIR);
    if (await !isDir(configPath)) {
      throw new Error(`Failed to create ${path.join(dir)}.`);
    }
    console.info(`Created: ${configPath}`);
    return configPath;
  };

  /**
   * setup
   * @returns {Promise.<Array>} - results of each handler
   */
  const setup = async () => {
    const configPath = await createConfig();
    const shellPath = await createShellScript(configPath);
    return Promise.all([
      createAppManifest(configPath, shellPath),
      createEditorConfig(configPath),
    ]);
  };

  /* readline */
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });

  /* questions */
  const ques = {
    browser: "Enter which browser you would like to set up the host for:\n",
    editorPath: "Enter editor path:\n",
    cmdArgs: "Enter command line options:\n",
    filePos: "Put file path after command arguments? [y/n]\n",
  };

  /**
   * abort setup
   * @param {string} msg - message
   * @returns {void}
   */
  const abortSetup = msg => {
    console.info(`Setup aborted: ${msg}`);
    process.exit(1);
  };

  /**
   * handle editor temporary file position input
   * @param {string} ans - user input
   * @returns {AsyncFuncrtion} - setup
   */
  const handleFilePosInput = ans => {
    if (isString(ans)) {
      ans = ans.trim();
      /^y(?:es)?$/i.test(ans) && (editorConfig.fileAfterCmdArgs = true);
    }
    rl.close();
    return setup().catch(logError);
  };

  /**
   * handle editor cmd args input
   * @param {string} ans - user input
   * @returns {void}
   */
  const handleCmdArgsInput = ans => {
    if (isString(ans)) {
      editorConfig.cmdArgs = (new CmdArgs(ans.trim())).toArray();
    }
    rl.question(ques.filePos, handleFilePosInput);
  };

  /**
   * handle editor path input
   * @param {string} ans - user input
   * @returns {void}
   */
  const handleEditorPathInput = ans => {
    if (isString(ans)) {
      ans = ans.trim();
      if (ans.length) {
        if (isExecutable(ans)) {
          editorConfig.editorPath = ans;
          rl.question(ques.cmdArgs, handleCmdArgsInput);
        } else {
          console.warn(`${ans} is not executable.`);
          rl.question(ques.editorPath, handleEditorPathInput);
        }
      } else {
        rl.question(ques.cmdArgs, handleCmdArgsInput);
      }
    } else {
      rl.question(ques.cmdArgs, handleCmdArgsInput);
    }
  };

  /**
   * handle browser config directory input
   * @param {string} ans - user input
   * @returns {void}
   */
  const handleBrowserConfigDir = ans => {
    const dir = getBrowserConfigDir();
    if (!Array.isArray(dir)) {
      throw new TypeError(`Expected Array but got ${getType(dir)}.`);
    }
    const msg = `${path.join(...dir)} already exists.`;
    if (isString(ans)) {
      ans = ans.trim();
      if (/^y(?:es)?$/i.test(ans)) {
        rl.question(ques.editorPath, handleEditorPathInput);
      } else {
        abortSetup(msg);
      }
    } else {
      abortSetup(msg);
    }
  };

  /**
   * handle browser input
   * @param {string} ans - user input
   * @returns {void}
   */
  const handleBrowserInput = ans => {
    const msg = "Browser not specified.";
    if (isString(ans)) {
      ans = ans.trim();
      if (ans.length) {
        const browser = getBrowserData(ans);
        browser && setBrowser(browser);
        if (browser) {
          const dir = getBrowserConfigDir();
          if (!Array.isArray(dir)) {
            throw new TypeError(`Expected Array but got ${getType(dir)}.`);
          }
          const dirPath = path.join(...dir);
          if (isDir(dirPath)) {
            rl.question(`${dirPath} already exists. Overwrite? [y/n]\n`,
                        handleBrowserConfigDir);
          } else {
            rl.question(ques.editorPath, handleEditorPathInput);
          }
        } else {
          // TODO: Add custom setup
          abortSetup(`${ans} not supported.`);
        }
      } else {
        abortSetup(msg);
      }
    } else {
      abortSetup(msg);
    }
  };

  /**
   * extract argument
   * @param {string} arg - argument in key=value format
   * @param {RegExp} re - RegExp
   * @returns {string} - argument value
   */
  const extractArg = (arg, re) => {
    let value;
    if (isString(arg) && re && re.ignoreCase) {
      arg = re.exec(arg.trim());
      arg && ([, value] = arg);
    }
    return value || null;
  };

  /* start up */
  {
    const [, , ...args] = process.argv;
    let browser;
    if (Array.isArray(args) && args.length) {
      for (const arg of args) {
        let value;
        if (/^--browser=/i.test(arg)) {
          value = extractArg(arg, /^--browser=(.+)$/i);
          value && (browser = getBrowserData(value));
          browser && setBrowser(browser);
        } else if (/^--config-path=/i.test(arg)) {
          value = extractArg(arg, /^--config-path=(.+)$/i);
          value && setConfigDir(value);
        }
      }
    }
    if (browser) {
      const dir = getBrowserConfigDir();
      if (!Array.isArray(dir)) {
        throw new TypeError(`Expected Array but got ${getType(dir)}.`);
      }
      const dirPath = path.join(...dir);
      if (isDir(dirPath)) {
        rl.question(`${dirPath} already exists. Overwrite? [y/n]\n`,
                    handleBrowserConfigDir);
      } else {
        rl.question(ques.editorPath, handleEditorPathInput);
      }
    } else {
      const arr = [];
      const items = Object.keys(browserData);
      for (const item of items) {
        const obj = browserData[item];
        (IS_WIN && obj.regWin || IS_MAC && obj.hostMac ||
         !IS_WIN && !IS_MAC && obj.hostLinux) &&
          arr.push(item);
      }
      rl.question(`${ques.browser}[${arr.join(" ")}]\n`, handleBrowserInput);
    }
  }
}
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {
  MenuItem,
  NavDropdown,
  NavItem,
  OverlayTrigger,
  Tooltip
} from 'react-bootstrap'

/**
 * This component displays the sign-in status in the nav bar.
 * - When a user is not logged in: display 'Sign In' as a link or button.
 * - When a user is logged in, display an 'avatar' (retrieved from the profile prop)
 *   and a dropdown button so the user can access more options.
 */
export default class NavLoginButton extends Component {
  static propTypes = {
    id: PropTypes.string.isRequired,
    links: PropTypes.arrayOf(PropTypes.shape({
      target: PropTypes.string,
      text: PropTypes.string.isRequired,
      url: PropTypes.string.isRequired
    })),
    onSignInClick: PropTypes.func.isRequired,
    onSignOutClick: PropTypes.func.isRequired,
    profile: PropTypes.shape({
      email: PropTypes.string.isRequired,
      name: PropTypes.string.isRequired,
      nickname: PropTypes.string,
      picture: PropTypes.string
    })
  }

  static defaultProps = {
    links: null,
    profile: null
  }

  render () {
    const {
      className,
      id,
      links,
      onSignInClick,
      onSignOutClick,
      profile,
      style
    } = this.props

    const commonProps = {
      className,
      id,
      pullRight: true,
      style
    }

    const tooltip = (
      <Tooltip id='user-tooltip'>
        {profile.nickname || profile.name} ({profile.email})
      </Tooltip>
    )

    return profile
    ? (
      <OverlayTrigger placement='left' overlay={tooltip}>
        <NavDropdown
          {...commonProps}
          title={}
        >
          <MenuItem header>
            {profile.nickname || profile.name}
          </MenuItem>

          {links && links.map(item => (
            <MenuItem href={item.url} target={item.target}>{item.text}</MenuItem>
          ))}
          <MenuItem divider />
          <MenuItem onSelect={onSignOutClick}>Sign out</MenuItem>
        </NavDropdown>
      </OverlayTrigger>
    )
    : (
      <NavItem
        {...commonProps}
        onClick={onSignInClick}
      >
        Sign in
      </NavItem>
    )
  }
}
<commit_msg>refactor(NavLoginButton): Extract display name for avatar alt text.<commit_after>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {
  MenuItem,
  NavDropdown,
  NavItem,
  OverlayTrigger,
  Tooltip
} from 'react-bootstrap'

/**
 * This component displays the sign-in status in the nav bar.
 * - When a user is not logged in: display 'Sign In' as a link or button.
 * - When a user is logged in, display an 'avatar' (retrieved from the profile prop)
 *   and a dropdown button so the user can access more options.
 */
export default class NavLoginButton extends Component {
  static propTypes = {
    id: PropTypes.string.isRequired,
    links: PropTypes.arrayOf(PropTypes.shape({
      target: PropTypes.string,
      text: PropTypes.string.isRequired,
      url: PropTypes.string.isRequired
    })),
    onSignInClick: PropTypes.func.isRequired,
    onSignOutClick: PropTypes.func.isRequired,
    profile: PropTypes.shape({
      email: PropTypes.string.isRequired,
      name: PropTypes.string.isRequired,
      nickname: PropTypes.string,
      picture: PropTypes.string
    })
  }

  static defaultProps = {
    links: null,
    profile: null
  }

  render () {
    const {
      className,
      id,
      links,
      onSignInClick,
      onSignOutClick,
      profile,
      style
    } = this.props

    const commonProps = {
      className,
      id,
      pullRight: true,
      style
    }

    const displayedName = profile.nickname || profile.name

    const tooltip = (
      <Tooltip id='user-tooltip'>
        {displayedName} ({profile.email})
      </Tooltip>
    )

    return profile
    ? (
      <OverlayTrigger placement='left' overlay={tooltip}>
        <NavDropdown
          {...commonProps}
          title={}
        >
          <MenuItem header>{displayedName}</MenuItem>

          {links && links.map(item => (
            <MenuItem href={item.url} target={item.target}>{item.text}</MenuItem>
          ))}
          <MenuItem divider />
          <MenuItem onSelect={onSignOutClick}>Sign out</MenuItem>
        </NavDropdown>
      </OverlayTrigger>
    )
    : (
      <NavItem {...commonProps} onClick={onSignInClick}>
        Sign in
      </NavItem>
    )
  }
}
<|endoftext|>


<commit_before>//prompts for configuration setup

var prompt = require('prompt'),
  colors = require('colors'),
  async = require('async'),
  fs = require('fs');

var l = console.log,
  dirname = __dirname.replace(/\\/g, '/').toLowerCase(),
  filename = dirname + '/config.json',
  defaultdir = dirname + '/';

var cfg = {
  'dirs': {
    'batches': dirname + '/local/batches/',
    'creds': dirname + '/local/creds/',
    'destinations': dirname + '/local/destinations/',
    'input': dirname + '/local/input/',
    'logs': dirname + '/local/logs/',
    'output': dirname + '/local/output/',
    'sources': dirname + '/local/sources/'
  },
  'logto': 'console',
  'defaultBindVars': {},
  'schedule': dirname + '/local/schedule.json'
};

var pm2cfg = {
  'apps': [{
    'name': 'databridge',
    'script': 'bin/schedule.js',
    'watch': ['bin/schedule.js', 'pm2.json'],
    'env': {
      'NODE_ENV': 'development',
    },
    'env_production': {
      'NODE_ENV': 'production'
    },
    'error_file': dirname + '/local/logs/schedule.log',
    'out_file': dirname + '/local/logs/schedule.log',
    'log_date_format': 'YYYY-MM-DD HH:mm:ss Z'
  }]
};

function addTrailingSlash(s) {
  s.replace(/\\/g, '/');
  if (s.substring(s.length - 1, s.length) == '/') return s;
  return s + '/';
}

l(colors.green('Welcome to setup for'), colors.blue('databridge'), colors.green('.'));
l(colors.white('Setting up local config file.'));
l(colors.white('Type values and press enter or press enter to accept defaults.'));

async.waterfall([
  function(cb) {
    //test for existing config file first
    try {
      if (!fs.lstatSync(filename).isFile()) return cb(null);
    } catch (e) {
      return cb(null);
    }
    //make sure we want to overwrite
    console.log(colors.red('WARN:'), 'config.json already exists at ' + filename);
    prompt.message = '  Overwrite?';
    var p = {
      properties: {
        overwrite: {
          default: 'n',
          description: 'yes/no or y/n'
        }
      }
    };
    prompt.start();
    prompt.get(p, function(err, results) {
      if (err) return cb(err);
      if (results.overwrite !== 'yes' && results.overwrite !== 'y') return cb('Config file exists. Not overwriting.');
      cb(null);
    });
  },
  function(cb) {
    //prompt for default dir
    console.log('Enter the main directory for local files.');
    prompt.message = colors.green('  Directory');
    var p = {
      properties: {
        'defaultdir': {
          default: defaultdir,
          description: colors.green('Main local directory')
        }
      }
    };
    prompt.start();
    prompt.get(p, function(err, results) {
      if (err) return cb(err);
      defaultdir = addTrailingSlash(results.defaultdir);
      cb(null);
    });
  },
  function(cb) {
    // prompt for directories
    prompt.message = colors.green('  Local directory for');
    var dirPrompts = {
      properties: {

      }
    };

    cfg.dirs.forEach(function(d) {
      dirPrompts.properties[d] = {
        default: addTrailingSlash(defaultdir + d),
        description: colors.green(d)
      };
    });
    prompt.start();
    prompt.get(dirPrompts, function(err, results) {
      if (err) return cb(err);
      Object.keys(results).forEach(function(v) {
        cfg.dirs[v] = addTrailingSlash(results[v]);
      });
      cb(null);
    });
  },
  function(cb) {
    // prompt for log
    prompt.message = colors.green('  Log style');
    console.log('Choose a log style.');
    var logPrompt = {
      properties: {
        logto: {
          default: 'console',
          description: colors.green('console or file')
        }
      }
    };
    prompt.start();
    prompt.get(logPrompt, function(err, results) {
      if (err) return cb(err);
      cfg.logto = results.logto;
      cb(null);
    });
  },
  function(cb) {
    //prompt for scheduler setup
    prompt.message = colors.green('  Scheduler');
    console.log('Would you to setup configuration for the scheduler pm2/service?');
    var schPrompt = {
      properties: {
        sch: {
          default: 'yes',
          description: colors.green('yes or no')
        }
      }
    };
    prompt.start();
    prompt.get(schPrompt, function(err, results) {
      if (err) return cb(err);
      cb(null, results.sch);
    });
  },
  function(sch, cb) {
    if (sch !== 'yes' && sch !== 'y') {
      delete cfg.schedule;
      delete cfg.service;
      return cb(null);
    }
    //prompt for scheduler settings
    console.log('Enter the following options.');
    var schPrompt = {
      properties: {
        schedule: {
          default: defaultdir + 'schedule.json',
          description: colors.green('Location of schedule job file')
        },
        log: {
          default: cfg.dirs.logs + 'schedule.log',
          description: colors.green('Service log file')
        }
      }
    };
    prompt.start();
    prompt.get(schPrompt, function(err, results) {
      if (err) return cb(err);
      cfg.schedule = results.schedule;
      pm2cfg.apps[0].out_file = results.log;
      pm2cfg.apps[0].error_file = results.log;
      cb(null, sch);
    });
  },
  function(sch, cb) {
    //write log file
    fs.writeFile(filename, JSON.stringify(cfg, null, 2), function(err) {
      if (err) return cb(err);
      cb(null, sch);
    });
  },
  function(sch, cb) {
    //write pm2.json if setting up scheduler
    if (sch !== 'yes' && sch !== 'y') return cb(null);
    fs.writeFile('pm2.json', JSON.stringify(pm2cfg, null, 2), function(err) {
      if (err) return cb(err);
      cb(null);
    });
  }
], function(err) {
  if (err) return console.error(colors.red(err));
  console.log(colors.green('Success!'), 'Created config file at ' + filename);
  console.log('Recommend testing config file with:\n\n  mocha spec\\config');
});
<commit_msg>hotfix setup<commit_after>//prompts for configuration setup

var prompt = require('prompt'),
  colors = require('colors'),
  async = require('async'),
  fs = require('fs');

var l = console.log,
  dirname = __dirname.replace(/\\/g, '/').toLowerCase(),
  filename = dirname + '/config.json',
  defaultdir = dirname + '/';

var cfg = {
  'dirs': {
    'batches': dirname + '/local/batches/',
    'creds': dirname + '/local/creds/',
    'destinations': dirname + '/local/destinations/',
    'input': dirname + '/local/input/',
    'logs': dirname + '/local/logs/',
    'output': dirname + '/local/output/',
    'sources': dirname + '/local/sources/'
  },
  'logto': 'console',
  'defaultBindVars': {},
  'schedule': dirname + '/local/schedule.json'
};

var pm2cfg = {
  'apps': [{
    'name': 'databridge',
    'script': 'bin/schedule.js',
    'watch': ['bin/schedule.js', 'pm2.json'],
    'env': {
      'NODE_ENV': 'development',
    },
    'env_production': {
      'NODE_ENV': 'production'
    },
    'error_file': dirname + '/local/logs/schedule.log',
    'out_file': dirname + '/local/logs/schedule.log',
    'log_date_format': 'YYYY-MM-DD HH:mm:ss Z'
  }]
};

function addTrailingSlash(s) {
  s.replace(/\\/g, '/');
  if (s.substring(s.length - 1, s.length) == '/') return s;
  return s + '/';
}

l(colors.green('Welcome to setup for'), colors.blue('databridge'), colors.green('.'));
l(colors.white('Setting up local config file.'));
l(colors.white('Type values and press enter or press enter to accept defaults.'));

async.waterfall([
  function(cb) {
    //test for existing config file first
    try {
      if (!fs.lstatSync(filename).isFile()) return cb(null);
    } catch (e) {
      return cb(null);
    }
    //make sure we want to overwrite
    console.log(colors.red('WARN:'), 'config.json already exists at ' + filename);
    prompt.message = '  Overwrite?';
    var p = {
      properties: {
        overwrite: {
          default: 'n',
          description: 'yes/no or y/n'
        }
      }
    };
    prompt.start();
    prompt.get(p, function(err, results) {
      if (err) return cb(err);
      if (results.overwrite !== 'yes' && results.overwrite !== 'y') return cb('Config file exists. Not overwriting.');
      cb(null);
    });
  },
  function(cb) {
    //prompt for default dir
    console.log('Enter the main directory for local files.');
    prompt.message = colors.green('  Directory');
    var p = {
      properties: {
        'defaultdir': {
          default: defaultdir,
          description: colors.green('Main local directory')
        }
      }
    };
    prompt.start();
    prompt.get(p, function(err, results) {
      if (err) return cb(err);
      defaultdir = addTrailingSlash(results.defaultdir);
      cb(null);
    });
  },
  function(cb) {
    // prompt for directories
    prompt.message = colors.green('  Local directory for');
    var dirPrompts = {
      properties: {

      }
    };

    Object.keys(cfg.dirs).forEach(function(d) {
      dirPrompts.properties[d] = {
        default: addTrailingSlash(defaultdir + d),
        description: colors.green(d)
      };
    });
    prompt.start();
    prompt.get(dirPrompts, function(err, results) {
      if (err) return cb(err);
      Object.keys(results).forEach(function(v) {
        cfg.dirs[v] = addTrailingSlash(results[v]);
      });
      cb(null);
    });
  },
  function(cb) {
    // prompt for log
    prompt.message = colors.green('  Log style');
    console.log('Choose a log style.');
    var logPrompt = {
      properties: {
        logto: {
          default: 'console',
          description: colors.green('console or file')
        }
      }
    };
    prompt.start();
    prompt.get(logPrompt, function(err, results) {
      if (err) return cb(err);
      cfg.logto = results.logto;
      cb(null);
    });
  },
  function(cb) {
    //prompt for scheduler setup
    prompt.message = colors.green('  Scheduler');
    console.log('Would you to setup configuration for the scheduler pm2/service?');
    var schPrompt = {
      properties: {
        sch: {
          default: 'yes',
          description: colors.green('yes or no')
        }
      }
    };
    prompt.start();
    prompt.get(schPrompt, function(err, results) {
      if (err) return cb(err);
      cb(null, results.sch);
    });
  },
  function(sch, cb) {
    if (sch !== 'yes' && sch !== 'y') {
      delete cfg.schedule;
      delete cfg.service;
      return cb(null);
    }
    //prompt for scheduler settings
    console.log('Enter the following options.');
    var schPrompt = {
      properties: {
        schedule: {
          default: defaultdir + 'schedule.json',
          description: colors.green('Location of schedule job file')
        },
        log: {
          default: cfg.dirs.logs + 'schedule.log',
          description: colors.green('Service log file')
        }
      }
    };
    prompt.start();
    prompt.get(schPrompt, function(err, results) {
      if (err) return cb(err);
      cfg.schedule = results.schedule;
      pm2cfg.apps[0].out_file = results.log;
      pm2cfg.apps[0].error_file = results.log;
      cb(null, sch);
    });
  },
  function(sch, cb) {
    //write log file
    fs.writeFile(filename, JSON.stringify(cfg, null, 2), function(err) {
      if (err) return cb(err);
      cb(null, sch);
    });
  },
  function(sch, cb) {
    //write pm2.json if setting up scheduler
    if (sch !== 'yes' && sch !== 'y') return cb(null);
    fs.writeFile('pm2.json', JSON.stringify(pm2cfg, null, 2), function(err) {
      if (err) return cb(err);
      cb(null);
    });
  }
], function(err) {
  if (err) return console.error(colors.red(err));
  console.log(colors.green('Success!'), 'Created config file at ' + filename);
  console.log('Recommend testing config file with:\n\n  mocha spec\\config');
});
<|endoftext|>


<commit_before>'use strict';

const BigNumber = require('bignumber.js');
BigNumber.config({ ERRORS: false });

const meteringConfig = require('abacus-metering-config');
const fs = require('fs');
const util = require('util');

const colorize = (t) => util.inspect(t, {
  showHidden: false,
  depth: null,
  colors: true,
  breakLength: process.stdout.columns
});

const maxValue = process.env.MAX_VALUE ? parseInt(process.env.MAX_VALUE) : 15;
const retries = process.env.RETRIES ? parseInt(process.env.RETRIES) : 5;

const randomInt = (max) => Math.floor(Math.random() * Math.floor(max + 1));

const extractPlan = (content) => {
  if (!content.plan_id && !content.plans[0].plan)
    throw new Error('Invalid content');

  return content.plan_id ? content : content.plans[0].plan;
};

describe('Plan tester', () => {

  let metrics;
  let measures = [];

  before(() => {
    const file = process.env.TEST_PLAN || `${__dirname}/test-metering-plan.json`;

    console.log('   Loading plan %s ...', file);
    const fileContent = JSON.parse(fs.readFileSync(file, 'utf8'));
    const plan = extractPlan(fileContent);

    console.log('   Compiling plan ...');
    const compiledPlan = meteringConfig.compile(plan);
    delete compiledPlan.source;

    console.log('   Using max value %d and %d retries', maxValue, retries);

    console.log('   Loaded plan %s\n', colorize(compiledPlan));

    metrics = compiledPlan.metrics;

    compiledPlan.measures.forEach((measure) => measures.push(measure.name));
    console.log('      Detected %j measures', measures);
  });

  const containsMeasure = (metric, measureName) => {
    if (!metric.meter && metric === measureName) {
      console.log('      Found measure "%s" in metric "%s" (default function)', measureName, metric.name);
      return true;
    }

    if (metric.meter.indexOf(measureName) !== -1) {
      console.log('      Found measure "%s" in metric "%s"', measureName, metric.name);
      return true;
    }

    return false;
  };

  it('uses all measures', () => {
    const measuresFound = new Set();

    measures.forEach((measure) => {
      metrics.forEach((metric) => {
        if (containsMeasure(metric, measure))
          measuresFound.add(measure);
      });
    });

    expect([...measuresFound]).to.deep.equal(measures);
  });

  const checkPlanFlow = (measure, metric, previousValue, opts = {}) => {
    if (!opts.noLogs) console.log('        Testing metric "%s" ...', metric.name);

    const meterOutput = metric.meterfn(measure);
    if (!opts.noLogs)
      console.log('           meter(%s) = %s',
        colorize(measure),
        colorize(meterOutput)
      );
    // eslint-disable-next-line no-unused-expressions
    expect(new BigNumber(meterOutput).toNumber()).to.exist;

    const nextValue = {};

    const accumulateOutput = metric.accumulatefn(
      previousValue.accumulated.accumulator,
      meterOutput
    );
    if (!opts.noLogs)
      console.log('           accumulate(%s, %s) = %s',
        colorize(previousValue.accumulated.accumulator),
        colorize(meterOutput),
        colorize(accumulateOutput)
      );
    nextValue.accumulated = {
      accumulator: accumulateOutput
    };

    const aggregateOutput = metric.aggregatefn(
      previousValue.aggregated.aggregator,
      previousValue.aggregated.previous,
      accumulateOutput
    );
    if (!opts.noLogs)
      console.log('           aggregate(%s, %s, %s) = %s',
        colorize(previousValue.aggregated.aggregator),
        colorize(previousValue.aggregated.previous),
        colorize(accumulateOutput),
        colorize(aggregateOutput)
      );
    nextValue.aggregated = {
      aggregator: aggregateOutput,
      previous: previousValue.aggregated.aggregator
    };

    const summarizeOutput = metric.summarizefn(
      previousValue.summarized.total,
      aggregateOutput
    );
    if (!opts.noLogs)
      console.log('           summarize(%s, %s) = %s',
        colorize(previousValue.summarized.total),
        colorize(aggregateOutput),
        colorize(summarizeOutput)
      );
    // eslint-disable-next-line no-unused-expressions
    expect(new BigNumber(summarizeOutput).toNumber()).to.exist;
    nextValue.summarized = {
      total: summarizeOutput
    };

    return nextValue;
  };

  context('on initial accumulation/aggregation', () => {
    const measureFake = {};

    before(() => {
      measures.forEach((measure) => {
        measureFake[measure] = randomInt(maxValue);
      });
    });

    it('works correctly', () => {
      metrics.forEach((metric) =>
        checkPlanFlow(measureFake, metric, {
          accumulated: {
            accumulator: 0
          },
          aggregated: {
            aggregator: undefined,
            previous: undefined
          },
          summarized: {
            total: undefined
          }
        })
      );
    });
  });

  context('with already accumulated/aggregated values', () => {
    const measureFake = {};
    const initial = {};

    before(() => {
      measures.forEach((measure) => {
        measureFake[measure] = randomInt(maxValue);
      });

      metrics.forEach((metric) => {
        const metricName = metric.name;
        console.log('        Setting up metric "%s" ...', metricName);
        initial[metricName] = {};

        initial[metricName].meter = metric.meterfn(measureFake);
        console.log('           meter(%s) = %s',
          colorize(measureFake),
          colorize(initial[metricName].meter)
        );
        // eslint-disable-next-line no-unused-expressions
        expect(new BigNumber(initial[metricName].meter).toNumber()).to.exist;

        initial[metricName].accumulate = metric.accumulatefn(0, initial[metricName].meter);
        console.log('           accumulate(0, %s) = %s',
          colorize(initial[metricName].meter),
          colorize(initial[metricName].accumulate)
        );

        initial[metricName].aggregate = metric.aggregatefn(undefined, undefined, initial[metricName].accumulate);
        console.log('           aggregate(%s) = %s',
          colorize(initial[metricName].accumulate),
          colorize(initial[metricName].aggregate)
        );

        initial[metricName].summarize = metric.summarizefn(undefined, initial[metricName].aggregate);
        console.log('           summarize(%s) = %s',
          colorize(initial[metricName].aggregate),
          colorize(initial[metricName].summarize)
        );
        // eslint-disable-next-line no-unused-expressions
        expect(new BigNumber(initial[metricName].summarize).toNumber()).to.exist;
      });
    });

    it('works when there are accumulated/aggregated values' , () => {
      metrics.forEach((metric) => {
        const metricName = metric.name;
        checkPlanFlow(measureFake, metric, {
          accumulated: {
            accumulator: initial[metricName].accumulate
          },
          aggregated: {
            aggregator: initial[metricName].aggregate,
            previous: initial[metricName].aggregate
          },
          summarized: {
            total: initial[metricName].summarize
          }
        });
      });
    });
  });

  context('on continuous accumulation/aggregation', () => {
    const measureFake = {};
    const step = {};

    const generateMeasure = () => {
      measures.forEach((measure) => {
        measureFake[measure] = randomInt(maxValue);
      });
    };

    before(() => {
      metrics.forEach((metric) => {
        const metricName = metric.name;
        step[metricName] = {
          accumulated: {
            accumulator: 0
          },
          aggregated: {
            aggregator: undefined,
            previous: undefined
          },
          summarized: {
            total: undefined
          }
        };
      });
    });

    it('works correctly', () => {
      for(let i = 0; i <= retries; i++) {
        generateMeasure();
        metrics.forEach((metric) => {
          const metricName = metric.name;
          step[metricName] = checkPlanFlow(measureFake, metric, step[metricName]);
        });
      }
    });
  });

});
<commit_msg>Plan-tester correctly iterates steps<commit_after>'use strict';

const BigNumber = require('bignumber.js');
BigNumber.config({ ERRORS: false });

const meteringConfig = require('abacus-metering-config');
const fs = require('fs');
const util = require('util');

const colorize = (t) => util.inspect(t, {
  showHidden: false,
  depth: null,
  colors: true,
  breakLength: process.stdout.columns
});

const maxValue = process.env.MAX_VALUE ? parseInt(process.env.MAX_VALUE) : 15;
const retries = process.env.RETRIES ? parseInt(process.env.RETRIES) : 5;

const randomInt = (max) => Math.floor(Math.random() * Math.floor(max + 1));

const extractPlan = (content) => {
  if (!content.plan_id && !content.plans[0].plan)
    throw new Error('Invalid content');

  return content.plan_id ? content : content.plans[0].plan;
};

describe('Plan tester', () => {

  let metrics;
  let measures = [];

  before(() => {
    const file = process.env.TEST_PLAN || `${__dirname}/test-metering-plan.json`;

    console.log('   Loading plan %s ...', file);
    const fileContent = JSON.parse(fs.readFileSync(file, 'utf8'));
    const plan = extractPlan(fileContent);

    console.log('   Compiling plan ...');
    const compiledPlan = meteringConfig.compile(plan);
    delete compiledPlan.source;

    console.log('   Using max value %d and %d retries', maxValue, retries);

    console.log('   Loaded plan %s\n', colorize(compiledPlan));

    metrics = compiledPlan.metrics;

    compiledPlan.measures.forEach((measure) => measures.push(measure.name));
    console.log('      Detected %j measures', measures);
  });

  const containsMeasure = (metric, measureName) => {
    if (!metric.meter && metric === measureName) {
      console.log('      Found measure "%s" in metric "%s" (default function)', measureName, metric.name);
      return true;
    }

    if (metric.meter.indexOf(measureName) !== -1) {
      console.log('      Found measure "%s" in metric "%s"', measureName, metric.name);
      return true;
    }

    return false;
  };

  it('uses all measures', () => {
    const measuresFound = new Set();

    measures.forEach((measure) => {
      metrics.forEach((metric) => {
        if (containsMeasure(metric, measure))
          measuresFound.add(measure);
      });
    });

    expect([...measuresFound]).to.deep.equal(measures);
  });

  const checkPlanFlow = (measure, metric, previousValue, opts = {}) => {
    if (!opts.noLogs) console.log('       Testing metric "%s" ...', metric.name);

    const meterOutput = metric.meterfn(measure);
    if (!opts.noLogs)
      console.log('          meter(%s) = %s',
        colorize(measure),
        colorize(meterOutput)
      );
    // eslint-disable-next-line no-unused-expressions
    expect(new BigNumber(meterOutput).toNumber()).to.exist;

    const nextValue = {};

    const accumulateOutput = metric.accumulatefn(
      previousValue.accumulated.accumulator,
      meterOutput
    );
    if (!opts.noLogs)
      console.log('          accumulate(%s, %s) = %s',
        colorize(previousValue.accumulated.accumulator),
        colorize(meterOutput),
        colorize(accumulateOutput)
      );
    nextValue.accumulated = {
      accumulator: accumulateOutput
    };

    const aggregateOutput = metric.aggregatefn(
      previousValue.aggregated.aggregator,
      previousValue.aggregated.previous,
      accumulateOutput
    );
    if (!opts.noLogs)
      console.log('          aggregate(%s, %s, %s) = %s',
        colorize(previousValue.aggregated.aggregator),
        colorize(previousValue.aggregated.previous),
        colorize(accumulateOutput),
        colorize(aggregateOutput)
      );
    nextValue.aggregated = {
      aggregator: aggregateOutput,
      previous: previousValue.aggregated.aggregator
    };

    const summarizeOutput = metric.summarizefn(
      previousValue.summarized.total,
      aggregateOutput
    );
    if (!opts.noLogs)
      console.log('          summarize(%s, %s) = %s',
        colorize(previousValue.summarized.total),
        colorize(aggregateOutput),
        colorize(summarizeOutput)
      );
    // eslint-disable-next-line no-unused-expressions
    expect(new BigNumber(summarizeOutput).toNumber()).to.exist;
    nextValue.summarized = {
      total: summarizeOutput
    };

    return nextValue;
  };

  context('on initial accumulation/aggregation', () => {
    const measureFake = {};

    before(() => {
      measures.forEach((measure) => {
        measureFake[measure] = randomInt(maxValue);
      });
    });

    it('works correctly', () => {
      metrics.forEach((metric) =>
        checkPlanFlow(measureFake, metric, {
          accumulated: {
            accumulator: 0
          },
          aggregated: {
            aggregator: undefined,
            previous: undefined
          },
          summarized: {
            total: undefined
          }
        })
      );
    });
  });

  context('with already accumulated/aggregated values', () => {
    const measureFake = {};
    const initial = {};

    before(() => {
      measures.forEach((measure) => {
        measureFake[measure] = randomInt(maxValue);
      });

      metrics.forEach((metric) => {
        const metricName = metric.name;
        console.log('       Setting up metric "%s" ...', metricName);
        initial[metricName] = {};

        initial[metricName].meter = metric.meterfn(measureFake);
        console.log('          meter(%s) = %s',
          colorize(measureFake),
          colorize(initial[metricName].meter)
        );
        // eslint-disable-next-line no-unused-expressions
        expect(new BigNumber(initial[metricName].meter).toNumber()).to.exist;

        initial[metricName].accumulate = metric.accumulatefn(0, initial[metricName].meter);
        console.log('          accumulate(0, %s) = %s',
          colorize(initial[metricName].meter),
          colorize(initial[metricName].accumulate)
        );

        initial[metricName].aggregate = metric.aggregatefn(undefined, undefined, initial[metricName].accumulate);
        console.log('          aggregate(%s) = %s',
          colorize(initial[metricName].accumulate),
          colorize(initial[metricName].aggregate)
        );

        initial[metricName].summarize = metric.summarizefn(undefined, initial[metricName].aggregate);
        console.log('          summarize(%s) = %s',
          colorize(initial[metricName].aggregate),
          colorize(initial[metricName].summarize)
        );
        // eslint-disable-next-line no-unused-expressions
        expect(new BigNumber(initial[metricName].summarize).toNumber()).to.exist;
      });
    });

    it('works when there are accumulated/aggregated values' , () => {
      metrics.forEach((metric) => {
        const metricName = metric.name;
        checkPlanFlow(measureFake, metric, {
          accumulated: {
            accumulator: initial[metricName].accumulate
          },
          aggregated: {
            aggregator: initial[metricName].aggregate,
            previous: initial[metricName].aggregate
          },
          summarized: {
            total: initial[metricName].summarize
          }
        });
      });
    });
  });

  context('on continuous accumulation/aggregation', () => {
    const measureFake = {};
    const step = {};

    const generateMeasure = () => {
      measures.forEach((measure) => {
        measureFake[measure] = randomInt(maxValue);
      });
    };

    before(() => {
      metrics.forEach((metric) => {
        const metricName = metric.name;
        step[metricName] = {
          accumulated: {
            accumulator: 0
          },
          aggregated: {
            aggregator: undefined,
            previous: undefined
          },
          summarized: {
            total: undefined
          }
        };
      });
    });

    it('works correctly', () => {
      for(let i = 1; i <= retries; i++) {
        console.log('\n       Step %d/%d ...', i, retries);
        generateMeasure();
        metrics.forEach((metric) => {
          const metricName = metric.name;
          step[metricName] = checkPlanFlow(measureFake, metric, step[metricName]);
        });
      }
    });
  });

});
<|endoftext|>