<commit_before>#!/usr/bin/env node

'use strict';

const fs = require('fs');
const execSync = require('child_process').execSync;
const promisify = require('es6-promisify');
const wraptile = require('wraptile/legacy');
const currify = require('currify/legacy');
const eof = require('../lib/eof');

const tryExec = promisify(_tryExec);
const update = wraptile(_update);
const ifInstall = wraptile(_ifInstall);
const ifCommit = wraptile(_ifCommit);
const save = currify(_save);

const stdout = process.stdout;
const write = stdout.write.bind(stdout);

const argv = process.argv.slice(2);
const args = require('minimist')(argv, {
    boolean: [
        'version',
        'help',
        'save-exact',
        'install',
        'dev',
        'commit',
        'add',
    ],
    alias: {
        v: 'version',
        h: 'help',
        E: 'save-exact',
        i: 'install',
        D: 'dev',
        a: 'add',
        c: 'commit'
    },
    unknown: (cmd) => {
        const msg = '\'%s\' is not a nupdate option. See \'nupdate --help\'.';

        if (/^--?/.test(cmd))
            exit(msg, cmd);
    }
});

if (!args.length && args.help) {
    help();
} else if (args.version) {
    console.log('v' + require('../package').version);
} else {
    main(args._[0], {
        exact: args['save-exact'],
        install: args.install,
        dev: args.dev,
        commit: args.commit,
        add: args.add,
    });
}

function main(name, options) {
    if (!name)
        return console.error('Module name could not be empty');

    const fullstore = require('fullstore');
    const versionStore = fullstore();
    const pathStore = fullstore();

    const cmd = `npm info ${name} --json`;

    tryExec(cmd)
        .then(JSON.parse)
        .then(getVersion)
        .then(versionStore)
        .then(find)
        .then(pathStore)
        .then(update(name, options, pathStore, versionStore))
        .then(eof)
        .then(save(pathStore))
        .then(ifInstall(options.install, name))
        .then(ifCommit(options.commit, name, pathStore, versionStore))
        .catch(onError);
}

function _ifInstall(is, name) {
    if (!is)
        return;

    return tryExec(`npm i ${name} --no-save`)
        .then(write)
}

function _ifCommit(is, name, path, version) {
    if (!is)
        return;

    const commit = [
        `git add ${path()}`,
        `git commit -m "feature(package) ${name} v${version()}"`,
    ].join('&&');

    const cmd = `${commit} || true`;

    return tryExec(cmd)
        .then(write)
}

function find() {
    const findUp = require('find-up');
    return findUp('package.json');
}

function _update(name, options, path, version) {
    const nupdate = require('..');
    const info = fs.readFileSync(path(), 'utf8');
    const result = nupdate(name, version(), info, options);

    return result;
}

function _tryExec(cmd, fn) {
    const tryCatch = require('try-catch');

    const error = tryCatch(() => {
        const data = execSync(cmd).toString();
        fn(null, data);
    });

    error && fn(error);
}

function onError(error) {
    if (error)
        return console.error(error.message);
}

function getVersion(info) {
    return info.version;
}

function _save(pathStore, data) {
    fs.writeFileSync(pathStore(), data);
}

function exit() {
    console.error.apply(console, arguments);
    process.exit(1);
}

function help() {
    const bin = require('../json/help');
    const usage = 'Usage: nupdate [options]';

    console.log(usage);
    console.log('Options:');

    Object.keys(bin)
        .map((name) => {
            return `  '${name} ${bin[name]}`;
        })
        .forEach((line) => {
            console.log(line);
        });
}

<commit_msg>fix(nupdate) --help: rm quote<commit_after>#!/usr/bin/env node

'use strict';

const fs = require('fs');
const execSync = require('child_process').execSync;
const promisify = require('es6-promisify');
const wraptile = require('wraptile/legacy');
const currify = require('currify/legacy');
const eof = require('../lib/eof');

const tryExec = promisify(_tryExec);
const update = wraptile(_update);
const ifInstall = wraptile(_ifInstall);
const ifCommit = wraptile(_ifCommit);
const save = currify(_save);

const stdout = process.stdout;
const write = stdout.write.bind(stdout);

const argv = process.argv.slice(2);
const args = require('minimist')(argv, {
    boolean: [
        'version',
        'help',
        'save-exact',
        'install',
        'dev',
        'commit',
        'add',
    ],
    alias: {
        v: 'version',
        h: 'help',
        E: 'save-exact',
        i: 'install',
        D: 'dev',
        a: 'add',
        c: 'commit'
    },
    unknown: (cmd) => {
        const msg = '\'%s\' is not a nupdate option. See \'nupdate --help\'.';

        if (/^--?/.test(cmd))
            exit(msg, cmd);
    }
});

if (!args.length && args.help) {
    help();
} else if (args.version) {
    console.log('v' + require('../package').version);
} else {
    main(args._[0], {
        exact: args['save-exact'],
        install: args.install,
        dev: args.dev,
        commit: args.commit,
        add: args.add,
    });
}

function main(name, options) {
    if (!name)
        return console.error('Module name could not be empty');

    const fullstore = require('fullstore');
    const versionStore = fullstore();
    const pathStore = fullstore();

    const cmd = `npm info ${name} --json`;

    tryExec(cmd)
        .then(JSON.parse)
        .then(getVersion)
        .then(versionStore)
        .then(find)
        .then(pathStore)
        .then(update(name, options, pathStore, versionStore))
        .then(eof)
        .then(save(pathStore))
        .then(ifInstall(options.install, name))
        .then(ifCommit(options.commit, name, pathStore, versionStore))
        .catch(onError);
}

function _ifInstall(is, name) {
    if (!is)
        return;

    return tryExec(`npm i ${name} --no-save`)
        .then(write)
}

function _ifCommit(is, name, path, version) {
    if (!is)
        return;

    const commit = [
        `git add ${path()}`,
        `git commit -m "feature(package) ${name} v${version()}"`,
    ].join('&&');

    const cmd = `${commit} || true`;

    return tryExec(cmd)
        .then(write)
}

function find() {
    const findUp = require('find-up');
    return findUp('package.json');
}

function _update(name, options, path, version) {
    const nupdate = require('..');
    const info = fs.readFileSync(path(), 'utf8');
    const result = nupdate(name, version(), info, options);

    return result;
}

function _tryExec(cmd, fn) {
    const tryCatch = require('try-catch');

    const error = tryCatch(() => {
        const data = execSync(cmd).toString();
        fn(null, data);
    });

    error && fn(error);
}

function onError(error) {
    if (error)
        return console.error(error.message);
}

function getVersion(info) {
    return info.version;
}

function _save(pathStore, data) {
    fs.writeFileSync(pathStore(), data);
}

function exit() {
    console.error.apply(console, arguments);
    process.exit(1);
}

function help() {
    const bin = require('../json/help');
    const usage = 'Usage: nupdate [options]';

    console.log(usage);
    console.log('Options:');

    Object.keys(bin)
        .map((name) => {
            return `  ${name} ${bin[name]}`;
        })
        .forEach((line) => {
            console.log(line);
        });
}

<|endoftext|>


<commit_before>var deepstream = require( 'deepstream.io-client-js' );
var conf = require( '../conf' ).client;

var latency = [];

module.exports = function( pid, clientType, deepstreamURL ) {

	function getLatencyStats( latency, result ) {
		var l = latency[ 0 ];
		var total = 0;
		for( var i = 0; i < latency.length; i++ ) {
			l = latency[ i ];
			if( l > result.max ) {
				result.max = l;
			}
			if( l < result.min ) {
				result.min = l;
			}
			total += l;
		}
		result.avg = Math.round( total / latency.length );
		return result;
	}

	function updateRecord( record, data ) {
		setTimeout( function() {
			data.timestamp = Date.now();
			record.set( data );
		}, conf.messageFrequency );
	}

	var ds = deepstream( deepstreamURL );
	var userName = pid + '-' + clientType;

	ds.on( 'error', function( e ) {
		console.log( 'error occured', arguments );
	} );

	ds.login( {
		username: userName
	}, function( success, errorEvent, errorMessage ) {
		//console.log( 'deepstream ' + userName + ' client connected to ' + deepstreamURL );

		var record = ds.record.getRecord( 'perf/' + pid );

		record.subscribe( clientType === 'ping' ? 'pong' : 'ping', function( data ) {
			var lastTimestamp = record.get( 'timestamp' );

			if( record.get( 'ping' ) === conf.messageLimit ) {
				ds.close();
				process.send( {
					pid: pid,
					type: clientType,
					latency: latency
				} );
				process.exit();
			} else if( clientType === 'ping' && !( record.get( 'ping' ) === 1 && record.get( 'pong' ) === 0 ) ) {
				updateRecord( record, {
					'ping': record.get( 'ping' ) + 1,
					'pong': record.get( 'pong' ),
				} );
				conf.calculateLatency && latency.push( Date.now() - lastTimestamp );
			} else if( clientType === 'pong' ) {
				updateRecord( record, {
					'ping': record.get( 'ping' ),
					'pong': record.get( 'pong' ) + 1,
				} );
			}
		} );

		if( clientType === 'ping' ) {
			record.set( {
				ping: 1,
				pong: 0,
				timestamp: Date.now()
			} );
		}

	} );

}
<commit_msg>Removing getLatencyStats from client (dead code)<commit_after>var deepstream = require( 'deepstream.io-client-js' );
var conf = require( '../conf' ).client;

7.2

var latency = [];

module.exports = function( pid, clientType, deepstreamURL ) {

	function updateRecord( record, data ) {
		setTimeout( function() {
			data.timestamp = Date.now();
			record.set( data );
		}, conf.messageFrequency );
	}

	var ds = deepstream( deepstreamURL );
	var userName = pid + '-' + clientType;

	ds.on( 'error', function( e ) {
		console.log( 'error occured', arguments );
	} );

	ds.login( {
		username: userName
	}, function( success, errorEvent, errorMessage ) {
		//console.log( 'deepstream ' + userName + ' client connected to ' + deepstreamURL );

		var record = ds.record.getRecord( 'perf/' + pid );

		record.subscribe( clientType === 'ping' ? 'pong' : 'ping', function( data ) {
			var lastTimestamp = record.get( 'timestamp' );

			if( record.get( 'ping' ) === conf.messageLimit ) {
				ds.close();
				process.send( {
					pid: pid,
					type: clientType,
					latency: latency
				} );
				process.exit();
			} else if( clientType === 'ping' && !( record.get( 'ping' ) === 1 && record.get( 'pong' ) === 0 ) ) {
				updateRecord( record, {
					'ping': record.get( 'ping' ) + 1,
					'pong': record.get( 'pong' ),
				} );
				conf.calculateLatency && latency.push( Date.now() - lastTimestamp );
			} else if( clientType === 'pong' ) {
				updateRecord( record, {
					'ping': record.get( 'ping' ),
					'pong': record.get( 'pong' ) + 1,
				} );
			}
		} );

		if( clientType === 'ping' ) {
			record.set( {
				ping: 1,
				pong: 0,
				timestamp: Date.now()
			} );
		}

	} );

}
<|endoftext|>


<commit_before>// Copyright 2014 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview CSS base Light weight popup menu that flyout on hovering over
 * the menu button.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.ui.FlyoutMenu');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.style');
goog.require('ydn.crm.ui');

/**
 * Light weight popup menu that flyout on hovering over the menu button.
 * @param {ydn.ui.FlyoutMenu.Option=} opt_option
 * @param {Array.<?ydn.ui.FlyoutMenu.ItemOption>=} opt_menu_items
 * @constructor
 * @struct
 */
ydn.ui.FlyoutMenu = function(opt_option, opt_menu_items) {
  /**
   * @type {Element}
   * @private
   */
  this.el_ = null;
  /**
   * @type {ydn.ui.FlyoutMenu.Option}
   * @private
   */
  this.option_ = opt_option || /** @type {ydn.ui.FlyoutMenu.Option} */ ({});
  /**
   * @type {Array.<?ydn.ui.FlyoutMenu.ItemOption>}
   * @private
   */
  this.items_option_ = ydn.object.clone(opt_menu_items || []);
};

/**
 * @const
 * @type {string}
 */
ydn.ui.FlyoutMenu.CSS_CLASS = 'more-menu';

/**
 * @const
 * @type {string}
 */
ydn.ui.FlyoutMenu.CSS_CLASS_MENU = 'flyout-menu';

/**
 * @const
 * @type {string}
 */
ydn.ui.FlyoutMenu.CSS_CLASS_LEFT = 'left-menu';

/**
 * @param {Element} el
 */
ydn.ui.FlyoutMenu.prototype.render = function(el) {
  goog.asserts.assert(!this.el_, 'Already rendered.');
  this.el_ = document.createElement('div');
  this.el_.className = ydn.ui.FlyoutMenu.CSS_CLASS;
  var icon_name = this.option_.className || 'more-vert';
  var svg = ydn.crm.ui.createSvgIcon(icon_name);
  var button = document.createElement('div');
  button.className = 'svg-button';
  button.appendChild(svg);
  var menu = ydn.ui.FlyoutMenu.renderMenu(this.items_option_);
  menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
  menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_LEFT);
  this.el_.appendChild(button);
  this.el_.appendChild(menu);
  el.appendChild(this.el_);
};

/**
 * @param {Element} el
 * @param {string} names
 * @return {string}
 * @private
 */
ydn.ui.FlyoutMenu.listItemName_ = function(el, names) {
  if (el && el instanceof Element) {
    if (el.classList.contains('goog-menuitem')) {
      var name = el.getAttribute('name');
      if (!names) {
        names = name;
      } else {
        names = name + ',' + names;
      }
      return ydn.ui.FlyoutMenu.listItemName_(el.parentElement, names);
    } else if (el.classList.contains('goog-menu')) {
      return ydn.ui.FlyoutMenu.listItemName_(el.parentElement, names);
    }
  }
  return names;
};

/**
 * Handle click event to root menu.
 * @param {goog.events.BrowserEvent|Event} e
 * @return {?string} if menu item is click, it will be return item name. For
 * hierichal menu, item name are separated by comma from root t leave, i.e.,
 * 'sync,contact'.
 */
ydn.ui.FlyoutMenu.handleClick = function(e) {
  // todo: e.target or e.target.parentElement instead.
  var item = null;

  if (e.target instanceof Element) {
    if (e.target.classList.contains('goog-menuitem')) {
      item = e.target;
    } else if (e.target.parentElement &&
        e.target.parentElement.classList.contains('goog-menuitem')) {
      item = e.target.parentElement;
    }
  }
  if (item) {
    e.preventDefault();
    e.stopPropagation();
    var is_disable = item.classList.contains('goog-menuitem-disabled');
    var el = goog.dom.getAncestorByClass(item, ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
    goog.style.setElementShown(el, false);
    setTimeout(function() {
      // menu show/hide status is determine by hover state
      goog.style.setElementShown(el, true);
    }, 1000);
    return is_disable ? null : ydn.ui.FlyoutMenu.listItemName_(item, '');
  }
  return null;
};

/**
 * Handle click event to root menu.
 * @param {goog.events.BrowserEvent} e
 * @return {?string} if menu item is click, it will be return item name.
 */
ydn.ui.FlyoutMenu.prototype.handleClick = function(e) {
  return ydn.ui.FlyoutMenu.handleClick(e);
};

/**
 * Set menu items.
 * @param {Array.<?ydn.ui.FlyoutMenu.ItemOption>} item
 */
ydn.ui.FlyoutMenu.prototype.setItems = function(item) {
  this.items_option_ = ydn.object.clone(item);
  if (this.el_) {
    var menu = this.el_.querySelector('.' + ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
    this.el_.removeChild(menu);
    menu = ydn.ui.FlyoutMenu.renderMenu(this.items_option_);
    menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
    menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_LEFT);
    this.el_.appendChild(menu);
  }
};

/**
 * @return {Element}
 */
ydn.ui.FlyoutMenu.prototype.getElement = function() {
  return this.el_;
};

/**
 * @typedef {{
 *   iconName: (string|undefined),
 *   className: (string|undefined),
 *   title: (string|undefined),
 *   isRightMenu: (boolean|undefined)
 * }}
 * iconName: svg icon name 'more-vert', 'more-horiz' or 'menu'
 * className: css class name.
 * isRightMenu: menu render to right side. By default, render to left.
 * title: div title for the menu item.
 */
ydn.ui.FlyoutMenu.Option;

/**
 * @typedef {{
 *   label: string,
 *   name: (string),
 *   value: *,
 *   title: (string|undefined),
 *   type: (string|undefined),
 *   disabled: (boolean|undefined),
 *   children: (Array.<ydn.ui.FlyoutMenu.ItemOption>|undefined)
 * }}
 * label: menu text.
 * name: commend in dispatching event.
 * type: SugarCrm.ModuleField#type for input type, default to type='text', for
 * 'bool', it becomes type='checkbox'
 * When type is 'bool', value can be 'true' or 'false'.
 */
ydn.ui.FlyoutMenu.ItemOption;

/**
 * @param {Array.<?ydn.ui.FlyoutMenu.ItemOption>} items_option
 * @return {Element}
 */
ydn.ui.FlyoutMenu.renderMenu = function(items_option) {
  // console.log(options);
  var dom = goog.dom.getDomHelper();
  var items = [];
  for (var i = 0; i < items_option.length; i++) {
    var opt = items_option[i];
    if (!opt) {
      // render as menu separator
      var rep = dom.createDom('div', {
        'class': 'goog-menuseparator',
        'role': 'separator'
      });
      items.push(rep);
      continue;
    }

    var menu_content = [dom.createDom('div', {
      'class': 'goog-menuitem-content'
    }, opt.label)];
    if (opt.type == 'bool') {
      var chk = dom.createDom('div', {
        'class': 'goog-menuitem-checkbox',
        'role': 'menuitem'
      });
      menu_content.unshift(chk);
    }
    if (opt.children) {
      var svg_arrow = ydn.crm.ui.createSvgIcon('arrow-drop-right');
      svg_arrow.classList.add('left-arrow');
      menu_content.unshift(svg_arrow);
    }
    var div_options = {
      'class': 'goog-menuitem',
      'role': opt.type == 'bool' ? 'goog-menuitem-checkbox' : 'menuitem'
    };
    if (opt.title) {
      div_options['title'] = opt.title;
    }
    var menuitem = dom.createDom('div', div_options, menu_content);
    menuitem.setAttribute('name', opt.name);
    if (opt.value) {
      menuitem.classList.add('goog-option-selected');
    }
    if (opt.children) {
      var sub_menu = ydn.ui.FlyoutMenu.renderMenu(opt.children);
      menuitem.appendChild(sub_menu);
    }
    if (opt.disabled) {
      menuitem.classList.add('goog-menuitem-disabled');
    }
    items.push(menuitem);
  }

  var menu = dom.createDom('div', {
    'class': 'goog-menu goog-menu-vertical',
    'role': 'menu'
  },
  items);
  return menu;
};

/**
 *

 *   var el = document.querySelector('#menu');
 *   var menu = ydn.ui.FlyoutMenu.decoratePopupMenu(el, items);
 *   el.onclick = function(e) {
 *     var name = ydn.ui.FlyoutMenu.handleClick(e);
 *     if (name) {
 *       // menu item with the name click.
 *     }
 *   }
 *

 * @param {Element} el
 * @param {Array<?ydn.ui.FlyoutMenu.ItemOption>} items_option
 * @param {ydn.ui.FlyoutMenu.Option=} opt_option
 */
ydn.ui.FlyoutMenu.decoratePopupMenu = function(el, items_option, opt_option) {
  // console.log(options);
  var icon_name = 'more-vert';
  if (opt_option) {
    if (opt_option.iconName) {
      icon_name = opt_option.iconName;
    }
  }
  var button = document.createElement('span');
  button.className = 'flyout-button';
  var svg = ydn.crm.ui.createSvgIcon(icon_name);
  var menu = ydn.ui.FlyoutMenu.renderMenu(items_option);
  menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
  if (!opt_option || !opt_option.isRightMenu) {
    menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_LEFT);
  }
  button.appendChild(svg);
  button.appendChild(menu);
  el.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS);
  el.appendChild(button);
};

/**
 * Decorate as enable or disable to a given named menu item.
 * @param {Element} menu goog-menu element
 * @param {string} name menu item name.
 * @param {boolean} val true to enable, false to disable.
 */
ydn.ui.FlyoutMenu.setEnableMenuItem = function(menu, name, val) {
  var el = menu.querySelector('div.goog-menuitem[name="' + name + '"]');
  if (!el) {
    return;
  }
  if (val) {
    el.classList.remove('goog-menuitem-disabled');
  } else {
    el.classList.add('goog-menuitem-disabled');
  }
};

/**
 * Set menu label.
 * @param {Element} menu goog-menu element
 * @param {string} name menu item name.
 * @param {string} val true to enable, false to disable.
 */
ydn.ui.FlyoutMenu.setMenuItemLabel = function(menu, name, val) {
  var el = menu.querySelector('div.goog-menuitem[name="' + name + '"]');
  if (!el) {
    return;
  }
  var content = el.querySelector('.goog-menuitem-content');
  content.textContent = val;
};

/**
 * Decorate as enable or disable to a given named menu item.
 * @param {string} name menu item name.
 * @param {boolean} val true to enable, false to disable.
 */
ydn.ui.FlyoutMenu.prototype.setEnableMenuItem = function(name, val) {
  var el = this.getElement();
  ydn.ui.FlyoutMenu.setEnableMenuItem(el, name, val);
};

/**
 * Set menu label.
 * @param {string} name menu item name.
 * @param {string} val true to enable, false to disable.
 */
ydn.ui.FlyoutMenu.prototype.setMenuItemLabel = function(name, val) {
  var el = this.getElement();
  ydn.ui.FlyoutMenu.setMenuItemLabel(el, name, val);
};
<commit_msg>add menu<commit_after>// Copyright 2014 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview CSS base Light weight popup menu that flyout on hovering over
 * the menu button.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.ui.FlyoutMenu');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.style');
goog.require('ydn.crm.ui');

/**
 * Light weight popup menu that flyout on hovering over the menu button.
 * @param {ydn.ui.FlyoutMenu.Option=} opt_option
 * @param {Array.<?ydn.ui.FlyoutMenu.ItemOption>=} opt_menu_items
 * @constructor
 * @struct
 */
ydn.ui.FlyoutMenu = function(opt_option, opt_menu_items) {
  /**
   * @type {Element}
   * @private
   */
  this.el_ = null;
  /**
   * @type {ydn.ui.FlyoutMenu.Option}
   * @private
   */
  this.option_ = opt_option || /** @type {ydn.ui.FlyoutMenu.Option} */ ({});
  /**
   * @type {Array.<?ydn.ui.FlyoutMenu.ItemOption>}
   * @private
   */
  this.items_option_ = ydn.object.clone(opt_menu_items || []);
};

/**
 * @const
 * @type {string}
 */
ydn.ui.FlyoutMenu.CSS_CLASS = 'more-menu';

/**
 * @const
 * @type {string}
 */
ydn.ui.FlyoutMenu.CSS_CLASS_MENU = 'flyout-menu';

/**
 * @const
 * @type {string}
 */
ydn.ui.FlyoutMenu.CSS_CLASS_LEFT = 'left-menu';

/**
 * @param {Element} el
 */
ydn.ui.FlyoutMenu.prototype.render = function(el) {
  goog.asserts.assert(!this.el_, 'Already rendered.');
  this.el_ = document.createElement('div');
  this.el_.className = ydn.ui.FlyoutMenu.CSS_CLASS;
  var icon_name = this.option_.iconName || 'more-vert';
  var svg = ydn.crm.ui.createSvgIcon(icon_name);
  var button = document.createElement('div');
  button.className = 'svg-button';
  button.appendChild(svg);
  var menu = ydn.ui.FlyoutMenu.renderMenu(this.items_option_);
  menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
  menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_LEFT);
  this.el_.appendChild(button);
  this.el_.appendChild(menu);
  el.appendChild(this.el_);
};

/**
 * @param {Element} el
 * @param {string} names
 * @return {string}
 * @private
 */
ydn.ui.FlyoutMenu.listItemName_ = function(el, names) {
  if (el && el instanceof Element) {
    if (el.classList.contains('goog-menuitem')) {
      var name = el.getAttribute('name');
      if (!names) {
        names = name;
      } else {
        names = name + ',' + names;
      }
      return ydn.ui.FlyoutMenu.listItemName_(el.parentElement, names);
    } else if (el.classList.contains('goog-menu')) {
      return ydn.ui.FlyoutMenu.listItemName_(el.parentElement, names);
    }
  }
  return names;
};

/**
 * Handle click event to root menu.
 * @param {goog.events.BrowserEvent|Event} e
 * @return {?string} if menu item is click, it will be return item name. For
 * hierichal menu, item name are separated by comma from root t leave, i.e.,
 * 'sync,contact'.
 */
ydn.ui.FlyoutMenu.handleClick = function(e) {
  // todo: e.target or e.target.parentElement instead.
  var item = null;

  if (e.target instanceof Element) {
    if (e.target.classList.contains('goog-menuitem')) {
      item = e.target;
    } else if (e.target.parentElement &&
        e.target.parentElement.classList.contains('goog-menuitem')) {
      item = e.target.parentElement;
    }
  }
  if (item) {
    e.preventDefault();
    e.stopPropagation();
    var is_disable = item.classList.contains('goog-menuitem-disabled');
    var el = goog.dom.getAncestorByClass(item, ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
    goog.style.setElementShown(el, false);
    setTimeout(function() {
      // menu show/hide status is determine by hover state
      goog.style.setElementShown(el, true);
    }, 1000);
    return is_disable ? null : ydn.ui.FlyoutMenu.listItemName_(item, '');
  }
  return null;
};

/**
 * Handle click event to root menu.
 * @param {goog.events.BrowserEvent} e
 * @return {?string} if menu item is click, it will be return item name.
 */
ydn.ui.FlyoutMenu.prototype.handleClick = function(e) {
  return ydn.ui.FlyoutMenu.handleClick(e);
};

/**
 * Set menu items.
 * @param {Array.<?ydn.ui.FlyoutMenu.ItemOption>} item
 */
ydn.ui.FlyoutMenu.prototype.setItems = function(item) {
  this.items_option_ = ydn.object.clone(item);
  if (this.el_) {
    var menu = this.el_.querySelector('.' + ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
    this.el_.removeChild(menu);
    menu = ydn.ui.FlyoutMenu.renderMenu(this.items_option_);
    menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
    menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_LEFT);
    this.el_.appendChild(menu);
  }
};

/**
 * @return {Element}
 */
ydn.ui.FlyoutMenu.prototype.getElement = function() {
  return this.el_;
};

/**
 * @typedef {{
 *   iconName: (string|undefined),
 *   className: (string|undefined),
 *   title: (string|undefined),
 *   isRightMenu: (boolean|undefined)
 * }}
 * iconName: svg icon name 'more-vert', 'more-horiz' or 'menu'
 * className: css class name.
 * isRightMenu: menu render to right side. By default, render to left.
 * title: div title for the menu item.
 */
ydn.ui.FlyoutMenu.Option;

/**
 * @typedef {{
 *   label: string,
 *   name: (string),
 *   value: *,
 *   title: (string|undefined),
 *   type: (string|undefined),
 *   disabled: (boolean|undefined),
 *   children: (Array.<ydn.ui.FlyoutMenu.ItemOption>|undefined)
 * }}
 * label: menu text.
 * name: commend in dispatching event.
 * type: SugarCrm.ModuleField#type for input type, default to type='text', for
 * 'bool', it becomes type='checkbox'
 * When type is 'bool', value can be 'true' or 'false'.
 */
ydn.ui.FlyoutMenu.ItemOption;

/**
 * @param {Array.<?ydn.ui.FlyoutMenu.ItemOption>} items_option
 * @return {Element}
 */
ydn.ui.FlyoutMenu.renderMenu = function(items_option) {
  // console.log(options);
  var dom = goog.dom.getDomHelper();
  var items = [];
  for (var i = 0; i < items_option.length; i++) {
    var opt = items_option[i];
    if (!opt) {
      // render as menu separator
      var rep = dom.createDom('div', {
        'class': 'goog-menuseparator',
        'role': 'separator'
      });
      items.push(rep);
      continue;
    }

    var menu_content = [dom.createDom('div', {
      'class': 'goog-menuitem-content'
    }, opt.label)];
    if (opt.type == 'bool') {
      var chk = dom.createDom('div', {
        'class': 'goog-menuitem-checkbox',
        'role': 'menuitem'
      });
      menu_content.unshift(chk);
    }
    if (opt.children) {
      var svg_arrow = ydn.crm.ui.createSvgIcon('arrow-drop-right');
      svg_arrow.classList.add('left-arrow');
      menu_content.unshift(svg_arrow);
    }
    var div_options = {
      'class': 'goog-menuitem',
      'role': opt.type == 'bool' ? 'goog-menuitem-checkbox' : 'menuitem'
    };
    if (opt.title) {
      div_options['title'] = opt.title;
    }
    var menuitem = dom.createDom('div', div_options, menu_content);
    menuitem.setAttribute('name', opt.name);
    if (opt.value) {
      menuitem.classList.add('goog-option-selected');
    }
    if (opt.children) {
      var sub_menu = ydn.ui.FlyoutMenu.renderMenu(opt.children);
      menuitem.appendChild(sub_menu);
    }
    if (opt.disabled) {
      menuitem.classList.add('goog-menuitem-disabled');
    }
    items.push(menuitem);
  }

  var menu = dom.createDom('div', {
    'class': 'goog-menu goog-menu-vertical',
    'role': 'menu'
  },
  items);
  return menu;
};

/**
 *

 *   var el = document.querySelector('#menu');
 *   var menu = ydn.ui.FlyoutMenu.decoratePopupMenu(el, items);
 *   el.onclick = function(e) {
 *     var name = ydn.ui.FlyoutMenu.handleClick(e);
 *     if (name) {
 *       // menu item with the name click.
 *     }
 *   }
 *

 * @param {Element} el
 * @param {Array<?ydn.ui.FlyoutMenu.ItemOption>} items_option
 * @param {ydn.ui.FlyoutMenu.Option=} opt_option
 */
ydn.ui.FlyoutMenu.decoratePopupMenu = function(el, items_option, opt_option) {
  // console.log(options);
  var icon_name = 'more-vert';
  if (opt_option) {
    if (opt_option.iconName) {
      icon_name = opt_option.iconName;
    }
  }
  var button = document.createElement('span');
  button.className = 'flyout-button';
  var svg = ydn.crm.ui.createSvgIcon(icon_name);
  var menu = ydn.ui.FlyoutMenu.renderMenu(items_option);
  menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
  if (!opt_option || !opt_option.isRightMenu) {
    menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_LEFT);
  }
  button.appendChild(svg);
  button.appendChild(menu);
  el.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS);
  el.appendChild(button);
};

/**
 * Decorate as enable or disable to a given named menu item.
 * @param {Element} menu goog-menu element
 * @param {string} name menu item name.
 * @param {boolean} val true to enable, false to disable.
 */
ydn.ui.FlyoutMenu.setEnableMenuItem = function(menu, name, val) {
  var el = menu.querySelector('div.goog-menuitem[name="' + name + '"]');
  if (!el) {
    return;
  }
  if (val) {
    el.classList.remove('goog-menuitem-disabled');
  } else {
    el.classList.add('goog-menuitem-disabled');
  }
};

/**
 * Set menu label.
 * @param {Element} menu goog-menu element
 * @param {string} name menu item name.
 * @param {string} val true to enable, false to disable.
 */
ydn.ui.FlyoutMenu.setMenuItemLabel = function(menu, name, val) {
  var el = menu.querySelector('div.goog-menuitem[name="' + name + '"]');
  if (!el) {
    return;
  }
  var content = el.querySelector('.goog-menuitem-content');
  content.textContent = val;
};

/**
 * Decorate as enable or disable to a given named menu item.
 * @param {string} name menu item name.
 * @param {boolean} val true to enable, false to disable.
 */
ydn.ui.FlyoutMenu.prototype.setEnableMenuItem = function(name, val) {
  var el = this.getElement();
  ydn.ui.FlyoutMenu.setEnableMenuItem(el, name, val);
};

/**
 * Set menu label.
 * @param {string} name menu item name.
 * @param {string} val true to enable, false to disable.
 */
ydn.ui.FlyoutMenu.prototype.setMenuItemLabel = function(name, val) {
  var el = this.getElement();
  ydn.ui.FlyoutMenu.setMenuItemLabel(el, name, val);
};
<|endoftext|>


<commit_before>// var chai = require('chai');
// expect = chai.expect;
var expect = require('chai').expect;

var taskList = []

// I can list my tasks

function listTasks (){
  for (var i = 0; i < taskList.length; i++){
    return taskList[i];
}
}

// I can add a task to my list

function addTaskToList(task, list){
  // What is the task? parameter task
  // Where is the task goin? parameter list
  // What order / priority? lowest, on the bottom
  return list.push(task)
}

function removeTaskToList(task, list){

}

function editTaskToDoList(task, list){

}

function prioritizeTaskToDoList(task, list){

}

function completeTaskToDoList(task, list){

}

expect(taskList.length).to.equal(0);
addTaskToList("Remember the milk", taskList);
expect(taskList[0]).to.equal("Remember the milk");
expect(taskList.length).to.equal(1);
expect(taskList.length).to.equal(1);
addTaskToList("Take out the trash", taskList);
expect(taskList[1]).to.equal("Take out the trash");
expect(taskList.length).to.equal(2);

listTasks();
expect(listTasks()).to.equal("Remember the milk", "Take out the trash");
console.log(listTasks)
<commit_msg>testing<commit_after>// var chai = require('chai');
// expect = chai.expect;
var expect = require('chai').expect;

var taskList = []

// I can list my tasks

function listTasks (){
  for (var i = 0; i < taskList.length; i++){
    return taskList[i];
}
}

// I can add a task to my list

function addTaskToList(task, list){
  // What is the task? parameter task
  // Where is the task goin? parameter list
  // What order / priority? lowest, on the bottom
  return list.push(task)
}

function removeTaskToList(task, list){

}

function editTaskToDoList(task, list){

}

function prioritizeTaskToDoList(task, list){

}

function completeTaskToDoList(task, list){

}

expect(taskList.length).to.equal(0);
addTaskToList("Remember the milk", taskList);
expect(taskList[0]).to.equal("Remember the milk");
expect(taskList.length).to.equal(1);
expect(taskList.length).to.equal(1);
addTaskToList("Take out the trash", taskList);
expect(taskList[1]).to.equal("Take out the trash");
expect(taskList.length).to.equal(2);

listTasks();
expect(listTasks()).to.equal("Remember the milk", "Take out the trash");
console.log(listTasks())
<|endoftext|>


<commit_before>goog.provide('app.Alerts');

goog.require('app');

/**
 * @param {angularGettext.Catalog} gettextCatalog Gettext catalog.
 * @constructor
 * @export
 */
app.Alerts = function(gettextCatalog) {

  /**
   * @type {Array.<appx.AlertMessage>}
   * @private
   */
  this.alerts_ = [];

  /**
   * @type {angularGettext.Catalog}
   * @private
   */
  this.gettextCatalog_ = gettextCatalog;
};

/**
 * Use this function to annotate a string for extraction by the
 * gettext extract tool. The call to this function will be eliminated
 * by the Closure compiler when minifying, so it should not have any
 * performance effect.
 * See https://angular-gettext.rocketeer.be/dev-guide/annotate-js/
 * @param {string} str String to have extracted by gettext tool
 * @return {string}
 */
app.Alerts.prototype.gettext = function(str) {
  return str;
};

/**
 * @param {appx.AlertMessage} data Alert data.
 * @export
 */
app.Alerts.prototype.add = function(data) {
  var timeout = data['timeout'] || 0;
  this.addLoading_(timeout);
  var msg = data['msg'];
  msg = msg instanceof Object ? this.formatErrorMsg_(msg) :
      this.filterStr_(msg);
  this.alerts_.push({
    type: data['type'] || 'warning',
    msg: msg,
    timeout: timeout
  });
};

/**
 * @param {(string|Object)} msg
 * @export
 */
app.Alerts.prototype.addSuccess = function(msg) {
  this.add({
    'type': 'success',
    'msg': msg,
    'timeout': 5000
  });
};

/**
 * @param {(string|Object)} msg
 * @export
 */
app.Alerts.prototype.addError = function(msg) {
  this.add({
    'type': 'danger',
    'msg': msg,
    'timeout': 5000
  });
};

/**
 * @param {string} msg
 * @param {Object} errors
 * @export
 */
app.Alerts.prototype.addErrorWithMsg = function(msg, errors) {
  var content = msg + '
' + this.formatErrorMsg_(errors);
  var timeout = 5000;
  this.addLoading_(timeout);
  this.alerts_.push({
    type: 'danger',
    msg: content,
    timeout: timeout
  });
};

/**
 * @return {Array.<appx.AlertMessage>}
 * @export
 */
app.Alerts.prototype.get = function() {
  return this.alerts_;
};

/**
 * @param {number} timeout
 * @private
 */
app.Alerts.prototype.addLoading_ = function(timeout) {
  $('main, aside, .page-header').addClass('loading');
  setTimeout(function() {
    $('main, aside, .page-header').removeClass('loading');
  }, timeout);
};

/**
 * @param {Object} response Response from the API server.
 * @return {string}
 * @private
 */
app.Alerts.prototype.formatErrorMsg_ = function(response) {
  if (!('data' in response) || !response['data'] ||
      !('errors' in response['data']) ||
      !response['data']['errors']) {
    return this.gettextCatalog_.getString('Unknown error');
  }
  var errors = response['data']['errors'];
  var len = errors.length;
  if (len > 1) {
    var msg = '
';
    for (var i = 0; i < len; i++) {
      msg += '
' + this.filterStr_(errors[i]['description']) + ' : ' + this.filterStr_(errors[i]['name']) + '
';
    }
    return msg + '
';
  }
  return this.filterStr_(errors[0]['description']) + ' : ' + this.filterStr_(errors[0]['name']);
};

/**
 * @param {string} str String to filter.
 * @return {string}
 * @private
 */
app.Alerts.prototype.filterStr_ = function(str) {
  str = goog.string.htmlEscape(str);
  return this.gettextCatalog_.getString(str);
};

/**
 * @param {angularGettext.Catalog} gettextCatalog Gettext catalog.
 * @private
 * @ngInject
 * @return {app.Alerts}
 */
app.AlertsFactory_ = function(gettextCatalog) {
  return new app.Alerts(gettextCatalog);
};
app.module.factory('appAlerts', app.AlertsFactory_);
<commit_msg>Fix missing translation in error message<commit_after>goog.provide('app.Alerts');

goog.require('app');

/**
 * @param {angularGettext.Catalog} gettextCatalog Gettext catalog.
 * @constructor
 * @export
 */
app.Alerts = function(gettextCatalog) {

  /**
   * @type {Array.<appx.AlertMessage>}
   * @private
   */
  this.alerts_ = [];

  /**
   * @type {angularGettext.Catalog}
   * @private
   */
  this.gettextCatalog_ = gettextCatalog;
};

/**
 * Use this function to annotate a string for extraction by the
 * gettext extract tool. The call to this function will be eliminated
 * by the Closure compiler when minifying, so it should not have any
 * performance effect.
 * See https://angular-gettext.rocketeer.be/dev-guide/annotate-js/
 * @param {string} str String to have extracted by gettext tool
 * @return {string}
 */
app.Alerts.prototype.gettext = function(str) {
  return str;
};

/**
 * @param {appx.AlertMessage} data Alert data.
 * @export
 */
app.Alerts.prototype.add = function(data) {
  var timeout = data['timeout'] || 0;
  this.addLoading_(timeout);
  var msg = data['msg'];
  msg = msg instanceof Object ? this.formatErrorMsg_(msg) :
      this.filterStr_(msg);
  this.alerts_.push({
    type: data['type'] || 'warning',
    msg: msg,
    timeout: timeout
  });
};

/**
 * @param {(string|Object)} msg
 * @export
 */
app.Alerts.prototype.addSuccess = function(msg) {
  this.add({
    'type': 'success',
    'msg': msg,
    'timeout': 5000
  });
};

/**
 * @param {(string|Object)} msg
 * @export
 */
app.Alerts.prototype.addError = function(msg) {
  this.add({
    'type': 'danger',
    'msg': msg,
    'timeout': 5000
  });
};

/**
 * @param {string} msg
 * @param {Object} errors
 * @export
 */
app.Alerts.prototype.addErrorWithMsg = function(msg, errors) {
  var content = this.filterStr_(msg) + '
' + this.formatErrorMsg_(errors);
  var timeout = 5000;
  this.addLoading_(timeout);
  this.alerts_.push({
    type: 'danger',
    msg: content,
    timeout: timeout
  });
};

/**
 * @return {Array.<appx.AlertMessage>}
 * @export
 */
app.Alerts.prototype.get = function() {
  return this.alerts_;
};

/**
 * @param {number} timeout
 * @private
 */
app.Alerts.prototype.addLoading_ = function(timeout) {
  $('main, aside, .page-header').addClass('loading');
  setTimeout(function() {
    $('main, aside, .page-header').removeClass('loading');
  }, timeout);
};

/**
 * @param {Object} response Response from the API server.
 * @return {string}
 * @private
 */
app.Alerts.prototype.formatErrorMsg_ = function(response) {
  if (!('data' in response) || !response['data'] ||
      !('errors' in response['data']) ||
      !response['data']['errors']) {
    return this.gettextCatalog_.getString('Unknown error');
  }
  var errors = response['data']['errors'];
  var len = errors.length;
  if (len > 1) {
    var msg = '
';
    for (var i = 0; i < len; i++) {
      msg += '
' + this.filterStr_(errors[i]['description']) + ' : ' + this.filterStr_(errors[i]['name']) + '
';
    }
    return msg + '
';
  }
  return this.filterStr_(errors[0]['description']) + ' : ' + this.filterStr_(errors[0]['name']);
};

/**
 * @param {string} str String to filter.
 * @return {string}
 * @private
 */
app.Alerts.prototype.filterStr_ = function(str) {
  str = goog.string.htmlEscape(str);
  return this.gettextCatalog_.getString(str);
};

/**
 * @param {angularGettext.Catalog} gettextCatalog Gettext catalog.
 * @private
 * @ngInject
 * @return {app.Alerts}
 */
app.AlertsFactory_ = function(gettextCatalog) {
  return new app.Alerts(gettextCatalog);
};
app.module.factory('appAlerts', app.AlertsFactory_);
<|endoftext|>


<commit_before>'use strict';

/* global Filer */

var fs,
    format          = require('format-io'),
    exec            = require('execon'),
    squad           = require('squad'),
    shortdate       = require('shortdate'),
    nicki,
    WIN,
    shortdate,
    funcEmpty       = function() {},

    map             = function(fn, array) {
        return array.map(fn);
    },

    sort            = function(fn, array) {
        return array.sort(fn);
    };

if (typeof window !== 'undefined') {
    fs              = new Filer.FileSystem();
} else {
    fs              = require('fs');
    WIN             = process.platform === 'win32';
    nicki           = !WIN && require('nicki');
}

module.exports  = readify;

var parseStats  = exec.with(map, parseStat);

/* sorting on Win and node v0.8.0 */
var sortFiles   = exec.with(sort, function(a, b) {
    return a.name > b.name ? 1 : -1;
});

function readify(path, callback) {
    check(path, callback);

    fs.readdir(path, function(error, names) {
        if (error)
            callback(error);
        else
            getAllStats(path, names, callback);
    });
}

function check(path, callback) {
    var pathMsg     = 'path should be string!',
        callbackMsg = 'callback should be function!';

    if (typeof path !== 'string')
        throw Error(pathMsg);

    if (typeof callback !== 'function')
        throw Error(callbackMsg);
}

/**
 * @param path
 * @param names
 */
function getAllStats(path, names, callback) {
    var funcs,
        length  = names.length,
        dir     = format.addSlashToEnd(path);

    if (!length)
        return fillJSON(dir, [], callback);

    funcs = names.map(function(name) {
        return exec.with(getStat, name, dir + name);
    });

    exec.parallel(funcs, function() {
        var files = [].slice.call(arguments, 1);
        fillJSON(dir, files, callback);
    });
}

function emptyStat() {
    return {
        mode        : 0,
        size        : 0,
        mtime       : '',
        isDirectory : funcEmpty
   };
}

function getStat(name, path, callback) {
    fs.stat(path, function(error, data) {
        if (!data)
            data = emptyStat();

        data.name = name;

        callback(null, data);
    });
}

function parseStat(stat) {
    var file, isDir, size, mode, modeStr, mtime,
        owner = stat.uid || '';

    /* Переводим права доступа в 8-ричную систему */
    modeStr = Number(stat.mode).toString(8);
    mode    = Number(modeStr) || '';
    isDir   = stat.isDirectory();
    size    = isDir ? 'dir' : stat.size;
    mtime   = stat.mtime;

    file = {
        'name'  : stat.name,
        'size'  : format.size(size),
        'date'  : !mtime ? '' :shortdate(mtime),
        'owner' : owner,
        'mode'  : mode && format.permissions.symbolic(mode)
    };

    return file;
}

/**
 * Function fill JSON by file stats
 *
 * @param params - { files, stats, path }
 */
function fillJSON(path, stats, callback) {
    var processFiles    = squad(changeOrder, sortFiles, parseStats),
        json            = {
            path    : '',
            files   : processFiles(stats)
        };

    json.path   = format.addSlashToEnd(path);

    changeUIDToName(json, function(error, files) {
        json.files = files;
        callback(null, json);
    });
}

function changeUIDToName(json, callback) {
    if (!nicki)
        callback(null, json.files);
    else
        nicki(function(error, names) {
            var files = json.files.slice();

            if (!error)
                files = files.map(function(file) {
                    var owner   = file.owner;
                        owner   = names[owner];

                    if (owner)
                        file.owner   = owner;

                    return file;
                });

            callback(error, files);
        });
}

function isDir(file) {
    return file.size === 'dir';
}

function not(fn) {
    return function() {
        return !fn.apply(null, arguments);
    };
}

function changeOrder(json) {
    var isFile  = not(isDir),

        dirs    = json.filter(isDir),
        files   = json.filter(isFile),
        sorted  = dirs.concat(files);

    return sorted;
}

<commit_msg>chore(readify) add " "<commit_after>'use strict';

/* global Filer */

var fs,
    format          = require('format-io'),
    exec            = require('execon'),
    squad           = require('squad'),
    shortdate       = require('shortdate'),
    nicki,
    WIN,
    funcEmpty       = function() {},

    map             = function(fn, array) {
        return array.map(fn);
    },

    sort            = function(fn, array) {
        return array.sort(fn);
    };

if (typeof window !== 'undefined') {
    fs              = new Filer.FileSystem();
} else {
    fs              = require('fs');
    WIN             = process.platform === 'win32';
    nicki           = !WIN && require('nicki');
}

module.exports  = readify;

var parseStats  = exec.with(map, parseStat);

/* sorting on Win and node v0.8.0 */
var sortFiles   = exec.with(sort, function(a, b) {
    return a.name > b.name ? 1 : -1;
});

function readify(path, callback) {
    check(path, callback);

    fs.readdir(path, function(error, names) {
        if (error)
            callback(error);
        else
            getAllStats(path, names, callback);
    });
}

function check(path, callback) {
    var pathMsg     = 'path should be string!',
        callbackMsg = 'callback should be function!';

    if (typeof path !== 'string')
        throw Error(pathMsg);

    if (typeof callback !== 'function')
        throw Error(callbackMsg);
}

/**
 * @param path
 * @param names
 */
function getAllStats(path, names, callback) {
    var funcs,
        length  = names.length,
        dir     = format.addSlashToEnd(path);

    if (!length)
        return fillJSON(dir, [], callback);

    funcs = names.map(function(name) {
        return exec.with(getStat, name, dir + name);
    });

    exec.parallel(funcs, function() {
        var files = [].slice.call(arguments, 1);
        fillJSON(dir, files, callback);
    });
}

function emptyStat() {
    return {
        mode        : 0,
        size        : 0,
        mtime       : '',
        isDirectory : funcEmpty
   };
}

function getStat(name, path, callback) {
    fs.stat(path, function(error, data) {
        if (!data)
            data = emptyStat();

        data.name = name;

        callback(null, data);
    });
}

function parseStat(stat) {
    var file, isDir, size, mode, modeStr, mtime,
        owner = stat.uid || '';

    /* Переводим права доступа в 8-ричную систему */
    modeStr = Number(stat.mode).toString(8);
    mode    = Number(modeStr) || '';
    isDir   = stat.isDirectory();
    size    = isDir ? 'dir' : stat.size;
    mtime   = stat.mtime;

    file = {
        'name'  : stat.name,
        'size'  : format.size(size),
        'date'  : !mtime ? '' : shortdate(mtime),
        'owner' : owner,
        'mode'  : mode && format.permissions.symbolic(mode)
    };

    return file;
}

/**
 * Function fill JSON by file stats
 *
 * @param params - { files, stats, path }
 */
function fillJSON(path, stats, callback) {
    var processFiles    = squad(changeOrder, sortFiles, parseStats),
        json            = {
            path    : '',
            files   : processFiles(stats)
        };

    json.path   = format.addSlashToEnd(path);

    changeUIDToName(json, function(error, files) {
        json.files = files;
        callback(null, json);
    });
}

function changeUIDToName(json, callback) {
    if (!nicki)
        callback(null, json.files);
    else
        nicki(function(error, names) {
            var files = json.files.slice();

            if (!error)
                files = files.map(function(file) {
                    var owner   = file.owner;
                        owner   = names[owner];

                    if (owner)
                        file.owner   = owner;

                    return file;
                });

            callback(error, files);
        });
}

function isDir(file) {
    return file.size === 'dir';
}

function not(fn) {
    return function() {
        return !fn.apply(null, arguments);
    };
}

function changeOrder(json) {
    var isFile  = not(isDir),

        dirs    = json.filter(isDir),
        files   = json.filter(isFile),
        sorted  = dirs.concat(files);

    return sorted;
}

<|endoftext|>


<commit_before>var crypto = require('crypto');

var Request = exports.Request = function(method, path, query){
  if(typeof path != 'string') throw "Expected string";
  if(typeof query != 'object') throw "Expected object";

  var query_hash = {},
      auth_hash = {},
      k;
  for(key in query){
    k = key.toLowerCase();
    k.substring(0,5) == 'auth_' ? auth_hash[k] = query[key] : query_hash[k] = query[key];
  }

  this.method = method.toUpperCase();
  this.path = path;
  this.query_hash = query_hash;
  this.auth_hash = auth_hash;
};

Request.prototype = {
  sign: function(token){
    this.auth_hash = {
      auth_version: "1.0",
      auth_key: token.key,
      auth_timestamp: new Date().getTime()
    }

    this.auth_hash['auth_signature'] = this.signature(token);

    return this.auth_hash
  },
  authenticate_by_token_with_exceptions: function(token, timestamp_grace){
    timestamp_grace = timestamp_grace || 600;
    validate_version();
    validate_timestamp(timestamp_grace);
    validate_signature(token);
    return true;
  },
  authenticate_by_token: function(token, timestamp_grace){
    timestamp_grace = timestamp_grace || 600;
    try {
      authenticate_by_token_with_exceptions(token, timestamp_grace);
      return true;
    } catch(e){
      return false;
    }
  },
  authenticate: function(timestamp_grace, block){
    timestamp_grace = timestamp_grace || 600;
    var key = this.auth_hash['auth_key']
    if(!key) throw "Authentication key required";
    token = block(key);
    if(!token || !token.secret){
      throw "Invalid authentication key";
    }
    authenticate_by_token_with_exceptions(token, timestamp_grace);
    return token;
  },
  get auth_hash(){
    if(!this._auth_hash || !this._auth_hash['auth_signature']) throw "Request not signed";
    return this._auth_hash;
  },
  set auth_hash(val){
    this._auth_hash = val;
  },
  signature: function(token){
    return crypto.createHmac('sha256', token.secret).update(this.string_to_sign()).digest('hex');
  },
  string_to_sign: function(){
    return [this.method, this.path, this.parameter_string()].join("\n");
  },
  validate_version: function(){
    var version = this.auth_hash['auth_version'];
    if(!version) throw "Version required";
    if(version != '1.0') throw "Version not supported";
    return true;
  },
  validate_timestamp: function(grace){
    if(grace == null) return true;

    var timestamp = this.auth_hash["auth_timestamp"],
        server_time = new Date().getTime(),
        error = Math.abs(parseInt(timestamp) - server_time);

    if(!timestamp) throw "Timestamp required";
    if(error >= grace){
      throw "Timestamp expired: Given timestamp "+
        "("+timestamp+") "+
        "not within "+grace+"s of server time "+
        "("+server_time+")";
    }
    return true;
  },
  validate_signature: function(token){
    var _signature = this.signature(token);
    if(this.auth_hash["auth_signature"] != _signature){
      throw "Invalid signature: you should have "+
        "sent "+_signature+
        ", but you sent "+this.auth_hash["auth_signature"];
    }
    return true
  }
};
<commit_msg>Fixing scope<commit_after>var crypto = require('crypto');

var Request = exports.Request = function(method, path, query){
  if(typeof path != 'string') throw "Expected string";
  if(typeof query != 'object') throw "Expected object";

  var query_hash = {},
      auth_hash = {},
      k;
  for(key in query){
    k = key.toLowerCase();
    k.substring(0,5) == 'auth_' ? auth_hash[k] = query[key] : query_hash[k] = query[key];
  }

  this.method = method.toUpperCase();
  this.path = path;
  this.query_hash = query_hash;
  this.auth_hash = auth_hash;
};

Request.prototype = {
  sign: function(token){
    this.auth_hash = {
      auth_version: "1.0",
      auth_key: token.key,
      auth_timestamp: new Date().getTime()
    }

    this.auth_hash['auth_signature'] = this.signature(token);

    return this.auth_hash
  },
  authenticate_by_token_with_exceptions: function(token, timestamp_grace){
    timestamp_grace = timestamp_grace || 600;
    this.validate_version();
    this.validate_timestamp(timestamp_grace);
    this.validate_signature(token);
    return true;
  },
  authenticate_by_token: function(token, timestamp_grace){
    timestamp_grace = timestamp_grace || 600;
    try {
      this.authenticate_by_token_with_exceptions(token, timestamp_grace);
      return true;
    } catch(e){
      return false;
    }
  },
  authenticate: function(block, timestamp_grace){
    timestamp_grace = timestamp_grace || 600;
    var key = this.auth_hash['auth_key'];
    if(!key) throw "Authentication key required";

    var token = block(key);
    if(!token || !token.secret){
      throw "Invalid authentication key";
    }
    this.authenticate_by_token_with_exceptions(token, timestamp_grace);
    return token;
  },
  get auth_hash(){
    if(!this._auth_hash || !this._auth_hash['auth_signature']) throw "Request not signed";
    return this._auth_hash;
  },
  set auth_hash(val){
    this._auth_hash = val;
  },
  signature: function(token){
    return crypto.createHmac('sha256', token.secret).update(this.string_to_sign()).digest('hex');
  },
  string_to_sign: function(){
    return [this.method, this.path, this.parameter_string()].join("\n");
  },
  validate_version: function(){
    var version = this.auth_hash['auth_version'];
    if(!version) throw "Version required";
    if(version != '1.0') throw "Version not supported";
    return true;
  },
  validate_timestamp: function(grace){
    if(grace == null) return true;

    var timestamp = this.auth_hash["auth_timestamp"],
        server_time = new Date().getTime(),
        error = Math.abs(parseInt(timestamp) - server_time);

    if(!timestamp) throw "Timestamp required";
    if(error >= grace){
      throw "Timestamp expired: Given timestamp "+
        "("+timestamp+") "+
        "not within "+grace+"s of server time "+
        "("+server_time+")";
    }
    return true;
  },
  validate_signature: function(token){
    var _signature = this.signature(token);
    if(this.auth_hash["auth_signature"] != _signature){
      throw "Invalid signature: you should have "+
        "sent "+_signature+
        ", but you sent "+this.auth_hash["auth_signature"];
    }
    return true
  }
};
<|endoftext|>


<commit_before>'use strict';

//module imports
const extend = require('extend');

//local imports
const utils = require('./utils');
const FormeError = require('./errors').FormeError;

class FormeRequest {
    constructor(form) {
        this._form = form;

        this._token = null;
        this._tokenCreated = false;
        this._identifier = null;
        this._page = null;
        this._current = null;//current page name
        this._future = null;//future page name
        this._destination = null;

        this._clean = true;//has the form submitted at least once?
        this._started = false;
        this._loading = false;
        this._view = false;
        this._submit = false;
        this._saved = false;
        this._reset = false;
        this._prev = false;
        this._next = false;
        this._halt = false;
        this._error = false;//has there been an error this request?
        this._finished = false;
        this._formFirst = true;
        this._pageFirst = true;
        this._valid = true;
        this._reload = false;
        this._reloadOverride = null;
        this._building = false;
        this._special = false;

        this._processSpecialActions = true;

        this._raw = {};
        this._values = {};
        this._errors = [];
        this._lastErrors = [];
        this._actions = [];

        this._store = {
            identifiers: [],//values per "page"
            visited: {},//pages visited
            completed: {},//pages completed
        };
    };

    //private methods
    _load() {
        if (this._form._driver) {
            //timeout old sessions
            return this._form._driver.timeout()

            //validate or create token
            .then(() => this._getTokenInfo())
            .then(info => {
                this._token = info.token;
                this._tokenCreated = info.created;

                //load the data
                if (this._tokenCreated) {
                    //dont bother loading as this is a newly created token
                    return null;
                } else {
                    //attempt to load data
                    return this._form._driver.load(this._token);
                }
            })

            //process loaded data
            .then(data => {
                //copy data from session into the request
                if (data !== null) {
                    //make sure to create new copies of objects/arrays, because we dont want anything to delete/alter them outside of our control!
                    this._lastErrors = data.errors.slice();
                    this._formFirst = data.first;
                    this._store = Object.assign({}, data.store);
                    this._raw = Object.assign({}, data.raw);
                    this._values = Object.assign({}, data.values);

                    //set the page to the one stored in the session
                    this._setPage(this._form._findPage(data.page));
                }
            })

            //prune excess sessions
            .then(() => this._form._driver.prune(this._form._sessionPrune))//prevent spamming of form sessions

            //clear the saved after loading, as its now plumbed into the request (as we handle all errors, we can save the data at the end)
            .then(() => this._form._driver.clear(this._token))

            //clean promise chain
            .then(() => true)
            .catch(err => Promise.reject(err));//dont need to catch here like this
        } else {
            //we don't have a session handler so do the best we can
            return Promise.resolve(false);
        }
    }

    _save() {
        if (this._form._driver) {
            if (this._reset) {
                //reset
                this._token = null;
                this._identifier = null;
                this._page = null;
                this._current = null;
                this._future = null;
                this._saved = false;
                this._pageFirst = true;
                this._raw = {};
                this._values = {};
                this._errors = [];
                this._lastErrors = [];
                this._actions = [];
                this._store = null;

                return Promise.resolve(false);
            } else {
                //save
                this._saved = true;

                //let the driver save
                return this._form._driver.save(this._token, {
                    raw: this._form._fetchValues(true, false, true, false, true, false, null, null),//secure values will be ignored
                    values: this._form._fetchValues(true, false, false, false, true, false, null, null),//secure values will be ignored
                    errors: this._errors,
                    clean: this._clean,
                    first: this._formFirst,
                    page: this._future,
                    store: this._store,
                    timeout: Date.now()+(this._form._sessionTimeout),//save timeout, its upto the driver to manage this
            })
                .then(() => true);
            }
        } else {
            //no session
            return Promise.resolve(false);
        }
    }

    _getTokenInfo() {
        return new Promise((resolve, reject) => {
            let token = this._form._driver.request(this._form._tokenField, undefined);

            //make sure its valid
            if (token !== undefined) {
                if (token.length !== this._form._sessionTokenSize || !/^[a-z0-9]+$/i.test(token)) {
                    reject(new FormeError('invalid form token'));
                } else {
                    //found old token (and its valid)
                    resolve({
                        created: false,
                        token: token,
                    });
                }
            } else {
                //no token, or it was invalid so generate new
                return utils.string.token(this._form._sessionTokenSize)
                .then(token => resolve({
                    created: true,
                    token: token,
                }))
                .catch(err => reject(err));
            }
        });
    }

    _addError(name, error) {
        //eat teh error
        this._errors.push({
            name: name,
            error: error,
        });

        //flag that this request has had an error
        this._error = true;
    }

    _createStore(identifier) {
        let store = this._store.identifiers.find(store => store.identifier === identifier);

        //do we need to create the store cache?
        if (store === undefined) {
            store = {
                identifier: identifier,
                names: {},
                groups: {},
            };

            //add form group structure to the store
            this._form._makeGroupStructure(store.groups);

            //add to stored identifiers
            this._store.identifiers.push(store);
        }

        //return cached
        return store;
    }

    _clearCurrentStore() {
        if (this._identifier !== null && this._store !== null) {
            this._store.identifiers = this._store.identifiers.filter(identifier => identifier.identifier !== this._identifier);
        }
    }

    _currentStore() {
        if (this._identifier !== null && this._store !== null) {
            const store = this._store.identifiers.find(store => store.identifier === this._identifier);
            return store?store:null;
        }
    }

    _fetchErrors() {
        if (this._view) {
            //include current errors as well as last errors
            //this is because we might have generated an error while building/loading/etc a form when in view mode
            return this._lastErrors.slice().concat(this._errors);
        } else {
            return this._errors.slice();
        }
    }

    _clearErrors() {
        //never clear "last errors"
        this._error = false;
        this._errors = [];
    }

    _hasError() {
        return this._errors.length > 0;
    }

    _addInputToStore(name, group, alias, value) {
        if (value !== undefined) {
            //alias is produced by input._outputName()
            const store = this._createStore(this._identifier);

            //add to store in different places
            utils.group.addGroup(store.groups, alias, group, value);
            store.names[name] = value;
        }
    }

    _fetchStoreValues(values) {
        //create complete set of values from store
        values = values || {};
        values.names = {};
        values.groups = {};

        //merge all together!
        if (this._store) {
            //todo: can we improve the performance of this?
            extend(true, values, ...this._store.identifiers.filter(identifier => ({
                names: identifier.names,
                groups: identifier.groups,
            })));
        }

        //nothing, soz
        return values;
    }

    //page api
    _setPage(page, ignoreNull) {
        if (!ignoreNull || page !== null) {
            this._page = page;
            this._current = page ? page._name : null;
        }
    }

    _pageName(page) {
        if (typeof page === 'string') {
            return page;
        } else if (page && typeof page === 'object') {
            return page._name;
        } else {
            return null;
        }
    }

    _visitPage(page) {
        const pageName = this._pageName(page);

        if (this._submit) {
            this._formFirst = false;

            //flag page (if there is one) as visited
            if (pageName && this._started && this._valid && this._page !== null) {
                this._store.visited[pageName] = true;
            }
        }
    }

    _completePage(page) {
        const pageName = this._pageName(page);

        if (pageName && this._submit && this._started && this._valid && this._page !== null) {
            this._store.visited[this._current] = true;
            this._store.completed[this._current] = true;
        }
    }

    _invalidateAfterPage(page) {
        const pageName = this._pageName(page);

        if (pageName && this._submit && this._started && this._valid && this._page !== null) {
            let found = false;
            for (let page of this._form._pages) {
                if (!found) {
                    if (page._name === pageName) {
                        //found, so now after this point we unvisit!
                        found = true;
                    }
                } else {
                    //invalidate!
                    delete this._store.visited[page._name];
                    delete this._store.completed[page._name];
                }
            }
        }
    }

    _hasCompletedPage(page) {
        const pageName = this._pageName(page);
        return pageName && this._page !== null && this._store.completed[pageName];
    }

    _hasVisitedPage(page) {
        const pageName = this._pageName(page);
        return (this._page === null && !this._formFirst) || (pageName && this._page && this._store.visited[pageName]);
    }

    //public methods
    template() {
        return this._form._makeTemplate();
    }

    errors() {
        return this._form.errors(...arguments);
    }

    values() {
        return this._form.values();
    }

    value(name) {
        return this._form.value(name);
    }
}

//expose
module.exports = FormeRequest;<commit_msg>fixed bug in journey<commit_after>'use strict';

//module imports
const extend = require('extend');

//local imports
const utils = require('./utils');
const FormeError = require('./errors').FormeError;

class FormeRequest {
    constructor(form) {
        this._form = form;

        this._token = null;
        this._tokenCreated = false;
        this._identifier = null;
        this._page = null;
        this._current = null;//current page name
        this._future = null;//future page name
        this._destination = null;

        this._clean = true;//has the form submitted at least once?
        this._started = false;
        this._loading = false;
        this._view = false;
        this._submit = false;
        this._saved = false;
        this._reset = false;
        this._prev = false;
        this._next = false;
        this._halt = false;
        this._error = false;//has there been an error this request?
        this._finished = false;
        this._formFirst = true;
        this._pageFirst = true;
        this._valid = true;
        this._reload = false;
        this._reloadOverride = null;
        this._building = false;
        this._special = false;

        this._processSpecialActions = true;

        this._raw = {};
        this._values = {};
        this._errors = [];
        this._lastErrors = [];
        this._actions = [];

        this._store = {
            identifiers: [],//values per "page"
            visited: {},//pages visited
            completed: {},//pages completed
        };
    };

    //private methods
    _load() {
        if (this._form._driver) {
            //timeout old sessions
            return this._form._driver.timeout()

            //validate or create token
            .then(() => this._getTokenInfo())
            .then(info => {
                this._token = info.token;
                this._tokenCreated = info.created;

                //load the data
                if (this._tokenCreated) {
                    //dont bother loading as this is a newly created token
                    return null;
                } else {
                    //attempt to load data
                    return this._form._driver.load(this._token);
                }
            })

            //process loaded data
            .then(data => {
                //copy data from session into the request
                if (data !== null) {
                    //make sure to create new copies of objects/arrays, because we dont want anything to delete/alter them outside of our control!
                    this._lastErrors = data.errors.slice();
                    this._formFirst = data.first;
                    this._store = Object.assign({}, data.store);
                    this._raw = Object.assign({}, data.raw);
                    this._values = Object.assign({}, data.values);

                    //set the page to the one stored in the session
                    this._setPage(this._form._findPage(data.page));
                }
            })

            //prune excess sessions
            .then(() => this._form._driver.prune(this._form._sessionPrune))//prevent spamming of form sessions

            //clear the saved after loading, as its now plumbed into the request (as we handle all errors, we can save the data at the end)
            .then(() => this._form._driver.clear(this._token))

            //clean promise chain
            .then(() => true)
            .catch(err => Promise.reject(err));//dont need to catch here like this
        } else {
            //we don't have a session handler so do the best we can
            return Promise.resolve(false);
        }
    }

    _save() {
        if (this._form._driver) {
            if (this._reset) {
                //reset
                this._token = null;
                this._identifier = null;
                this._page = null;
                this._current = null;
                this._future = null;
                this._saved = false;
                this._pageFirst = true;
                this._raw = {};
                this._values = {};
                this._errors = [];
                this._lastErrors = [];
                this._actions = [];
                this._store = null;

                return Promise.resolve(false);
            } else {
                //save
                this._saved = true;

                //let the driver save
                return this._form._driver.save(this._token, {
                    raw: this._form._fetchValues(true, false, true, false, true, false, null, null),//secure values will be ignored
                    values: this._form._fetchValues(true, false, false, false, true, false, null, null),//secure values will be ignored
                    errors: this._errors,
                    clean: this._clean,
                    first: this._formFirst,
                    page: this._future,
                    store: this._store,
                    timeout: Date.now()+(this._form._sessionTimeout),//save timeout, its upto the driver to manage this
            })
                .then(() => true);
            }
        } else {
            //no session
            return Promise.resolve(false);
        }
    }

    _getTokenInfo() {
        return new Promise((resolve, reject) => {
            let token = this._form._driver.request(this._form._tokenField, undefined);

            //make sure its valid
            if (token !== undefined) {
                if (token.length !== this._form._sessionTokenSize || !/^[a-z0-9]+$/i.test(token)) {
                    reject(new FormeError('invalid form token'));
                } else {
                    //found old token (and its valid)
                    resolve({
                        created: false,
                        token: token,
                    });
                }
            } else {
                //no token, or it was invalid so generate new
                return utils.string.token(this._form._sessionTokenSize)
                .then(token => resolve({
                    created: true,
                    token: token,
                }))
                .catch(err => reject(err));
            }
        });
    }

    _addError(name, error) {
        //eat teh error
        this._errors.push({
            name: name,
            error: error,
        });

        //flag that this request has had an error
        this._error = true;
    }

    _createStore(identifier) {
        let store = this._store.identifiers.find(store => store.identifier === identifier);

        //do we need to create the store cache?
        if (store === undefined) {
            store = {
                identifier: identifier,
                names: {},
                groups: {},
            };

            //add form group structure to the store
            this._form._makeGroupStructure(store.groups);

            //add to stored identifiers
            this._store.identifiers.push(store);
        }

        //return cached
        return store;
    }

    _clearCurrentStore() {
        if (this._identifier !== null && this._store !== null) {
            this._store.identifiers = this._store.identifiers.filter(identifier => identifier.identifier !== this._identifier);
        }
    }

    _currentStore() {
        if (this._identifier !== null && this._store !== null) {
            const store = this._store.identifiers.find(store => store.identifier === this._identifier);
            return store?store:null;
        }
    }

    _fetchErrors() {
        if (this._view) {
            //include current errors as well as last errors
            //this is because we might have generated an error while building/loading/etc a form when in view mode
            return this._lastErrors.slice().concat(this._errors);
        } else {
            return this._errors.slice();
        }
    }

    _clearErrors() {
        //never clear "last errors"
        this._error = false;
        this._errors = [];
    }

    _hasError() {
        return this._errors.length > 0;
    }

    _addInputToStore(name, group, alias, value) {
        if (value !== undefined) {
            //alias is produced by input._outputName()
            const store = this._createStore(this._identifier);

            //add to store in different places
            utils.group.addGroup(store.groups, alias, group, value);
            store.names[name] = value;
        }
    }

    _fetchStoreValues(values) {
        //create complete set of values from store
        values = values || {};
        values.names = {};
        values.groups = {};

        //merge all together!
        if (this._store) {
            //todo: can we improve the performance of this?
            extend(true, values, ...this._store.identifiers.filter(identifier => ({
                names: identifier.names,
                groups: identifier.groups,
            })));
        }

        //nothing, soz
        return values;
    }

    //page api
    _setPage(page, ignoreNull) {
        if (!ignoreNull || page !== null) {
            this._page = page;
            this._current = page ? page._name : null;
        }
    }

    _pageName(page) {
        if (typeof page === 'string') {
            return page;
        } else if (page && typeof page === 'object') {
            return page._name;
        } else {
            return null;
        }
    }

    _visitPage(page) {
        const pageName = this._pageName(page);

        if (this._submit) {
            this._formFirst = false;
        }

        //flag page (if there is one) as visited
        if (pageName && this._started && this._valid && this._page !== null) {
            this._store.visited[pageName] = true;
        }
    }

    _completePage(page) {
        const pageName = this._pageName(page);

        if (pageName && this._submit && this._started && this._valid && this._page !== null) {
            this._store.visited[this._current] = true;
            this._store.completed[this._current] = true;
        }
    }

    _invalidateAfterPage(page) {
        const pageName = this._pageName(page);

        if (pageName && this._submit && this._started && this._valid && this._page !== null) {
            let found = false;
            for (let page of this._form._pages) {
                if (!found) {
                    if (page._name === pageName) {
                        //found, so now after this point we unvisit!
                        found = true;
                    }
                } else {
                    //invalidate!
                    delete this._store.visited[page._name];
                    delete this._store.completed[page._name];
                }
            }
        }
    }

    _hasCompletedPage(page) {
        const pageName = this._pageName(page);
        return pageName && this._page !== null && this._store.completed[pageName];
    }

    _hasVisitedPage(page) {
        const pageName = this._pageName(page);
        return (this._page === null && !this._formFirst) || (pageName && this._page && this._store.visited[pageName]);
    }

    //public methods
    template() {
        return this._form._makeTemplate();
    }

    errors() {
        return this._form.errors(...arguments);
    }

    values() {
        return this._form.values();
    }

    value(name) {
        return this._form.value(name);
    }
}

//expose
module.exports = FormeRequest;<|endoftext|>