<commit_before>import { ElementAssembler } from 'dommodule/lib/element'

const { assign, getPrototypeOf } = Object
const { HTMLDocument, HTMLElement, document } = window

const CLASS_PREFIX_RE = /^HTML/
const EMPTY_STRING = ''
const HTML_NAMESPACE_URI = 'http://www.w3.org/1999/xhtml'
const HTML_PREFIX = 'html'
const TABINDEX_ATTRIBUTE_NAME = 'tabindex'

/**
 * @see https://www.w3.org/TR/html/dom.html#htmlelement
 * @abstract
 */
export class HTMLElementAssembler extends ElementAssembler {
    /**
     * Click the element
     */
    click() {
        this.node.click()
    }

    /**
     * Focus the element
     */
    focus() {
        this.node.focus()
    }

    /**
     * Blur the element
     */
    blur() {
        this.node.blur()
    }

    /**
     * @param {String} accessKey
     */
    set accessKey(accessKey) {
        this.node.accessKey = accessKey
    }

    /**
     * @returns {String}
     */
    get accessKey() {
        return this.node.accessKey
    }

    /**
     * @param {{}} dataset
     */
    set dataset(dataset) {
        const _dataset = this.dataset
        for(let name in dataset) {
            if(dataset.hasOwnProperty(name)) {
                if(name === null) {
                    delete _dataset[name]
                }
                else _dataset[name] = dataset[name]
            }
        }
    }

    /**
     * @returns {{}} DOMStringMap
     */
    get dataset() {
        return this.node.dataset
    }

    /**
     * @param {String} dir
     */
    set dir(dir) {
        this.node.dir = dir
    }

    /**
     * @returns {String}
     */
    get dir() {
        return this.node.dir
    }

    /**
     * @param {String} innerHTML
     */
    set innerHTML(innerHTML) {
        this.node.innerHTML = innerHTML
    }

    /**
     * @returns {String}
     */
    get innerHTML() {
        return this.node.innerHTML
    }

    /**
     * @param {String} innerText
     */
    set innerText(innerText) {
        this.node.innerText = innerText
    }

    /**
     * @returns {String}
     */
    get innerText() {
        return this.node.innerText
    }

    /**
     * @param {String} lang
     */
    set lang(lang) {
        this.node.lang = lang
    }

    /**
     * @returns {String}
     */
    get lang() {
        return this.node.lang
    }

    /**
     * @param {{}} style
     */
    set style(style) {
        assign(this.node.style, style)
    }

    /**
     * @returns {{}} CSSStyleDeclaration
     */
    get style() {
        return this.node.style
    }

    /**
     * @param {Number} tabIndex
     */
    set tabIndex(tabIndex) {
        if(isNaN(tabIndex)) {
            this.node.removeAttribute(TABINDEX_ATTRIBUTE_NAME)
        }
        else this.node.tabIndex = tabIndex
    }

    /**
     * @returns {Number}
     */
    get tabIndex() {
        return this.node.hasAttribute(TABINDEX_ATTRIBUTE_NAME)?
            this.node.tabIndex :
            NaN
    }

    /**
     * @param {String} title
     */
    set title(title) {
        this.node.title = title
    }

    /**
     * @returns {String}
     */
    get title() {
        return this.node.title
    }

    /**
     * @param {Boolean} translate
     */
    set translate(translate) {
        this.node.translate = translate
    }

    /**
     * @returns {Boolean}
     */
    get translate() {
        return this.node.translate
    }

    /**
     * @param {Boolean} hidden
     */
    set hidden(hidden) {
        this.node.hidden = hidden
    }

    /**
     * @returns {Boolean}
     */
    get hidden() {
        return this.node.hidden
    }

    /**
     * @param {*} [init]
     * @returns {Element|*} created element
     */
    static create(init) {
        if(this === this.superAssembler) {
            throw TypeError(`Could not create an abstract ${ this.name } instance`)
        }
        else if(document instanceof HTMLDocument) {
            const { localName = this.localName } = init || this
            return document.createElement(localName)
        }
        else return super.create(init)
    }

    /**
     * @returns {Function} HTMLDocumentAssembler|DocumentAssembler
     */
    static get documentAssembler() {
        return HTMLElementAssembler.HTMLDocumentAssembler || super.documentAssembler
    }

    /**
     * @returns {window.HTMLElement}
     */
    static get interface() {
        return HTMLElement
    }

    /**
     * @returns {String}
     */
    static get localName() {
        const { superAssembler } = this
        let object = this
        let proto
        while(proto = getPrototypeOf(object)) {
            if(proto === superAssembler) break
            else object = proto
        }
        return object.name.replace(CLASS_PREFIX_RE, EMPTY_STRING).toLowerCase()
    }

    /**
     * @returns {String}
     */
    static get namespace() {
        return HTML_NAMESPACE_URI
    }

    /**
     * @returns {String}
     */
    static get prefix() {
        return document instanceof HTMLDocument? EMPTY_STRING : HTML_PREFIX
    }

    /**
     * @returns {Function} HTMLElementAssembler
     */
    static get superAssembler() {
        return HTMLElementAssembler
    }
}
<commit_msg>HTMLElementAssembler.tabIndex: fix return value for elements with implicit tabIndex attribute<commit_after>import { ElementAssembler } from 'dommodule/lib/element'

const { assign, getPrototypeOf } = Object
const { HTMLDocument, HTMLElement, document } = window

const CLASS_PREFIX_RE = /^HTML/
const EMPTY_STRING = ''
const HTML_NAMESPACE_URI = 'http://www.w3.org/1999/xhtml'
const HTML_PREFIX = 'html'
const TABINDEX_ATTRIBUTE_NAME = 'tabindex'
const TABINDEX_DEFAULT_VALUE = -1

/**
 * @see https://www.w3.org/TR/html/dom.html#htmlelement
 * @abstract
 */
export class HTMLElementAssembler extends ElementAssembler {
    /**
     * Click the element
     */
    click() {
        this.node.click()
    }

    /**
     * Focus the element
     */
    focus() {
        this.node.focus()
    }

    /**
     * Blur the element
     */
    blur() {
        this.node.blur()
    }

    /**
     * @param {String} accessKey
     */
    set accessKey(accessKey) {
        this.node.accessKey = accessKey
    }

    /**
     * @returns {String}
     */
    get accessKey() {
        return this.node.accessKey
    }

    /**
     * @param {{}} dataset
     */
    set dataset(dataset) {
        const _dataset = this.dataset
        for(let name in dataset) {
            if(dataset.hasOwnProperty(name)) {
                if(name === null) {
                    delete _dataset[name]
                }
                else _dataset[name] = dataset[name]
            }
        }
    }

    /**
     * @returns {{}} DOMStringMap
     */
    get dataset() {
        return this.node.dataset
    }

    /**
     * @param {String} dir
     */
    set dir(dir) {
        this.node.dir = dir
    }

    /**
     * @returns {String}
     */
    get dir() {
        return this.node.dir
    }

    /**
     * @param {String} innerHTML
     */
    set innerHTML(innerHTML) {
        this.node.innerHTML = innerHTML
    }

    /**
     * @returns {String}
     */
    get innerHTML() {
        return this.node.innerHTML
    }

    /**
     * @param {String} innerText
     */
    set innerText(innerText) {
        this.node.innerText = innerText
    }

    /**
     * @returns {String}
     */
    get innerText() {
        return this.node.innerText
    }

    /**
     * @param {String} lang
     */
    set lang(lang) {
        this.node.lang = lang
    }

    /**
     * @returns {String}
     */
    get lang() {
        return this.node.lang
    }

    /**
     * @param {{}} style
     */
    set style(style) {
        assign(this.node.style, style)
    }

    /**
     * @returns {{}} CSSStyleDeclaration
     */
    get style() {
        return this.node.style
    }

    /**
     * @param {Number} tabIndex
     */
    set tabIndex(tabIndex) {
        if(isNaN(tabIndex)) {
            this.node.removeAttribute(TABINDEX_ATTRIBUTE_NAME)
        }
        else this.node.tabIndex = tabIndex
    }

    /**
     * @returns {Number}
     */
    get tabIndex() {
        const tabIndex = this.node.tabIndex
        return this.node.hasAttribute(TABINDEX_ATTRIBUTE_NAME) || tabIndex > TABINDEX_DEFAULT_VALUE?
            tabIndex :
            NaN
    }

    /**
     * @param {String} title
     */
    set title(title) {
        this.node.title = title
    }

    /**
     * @returns {String}
     */
    get title() {
        return this.node.title
    }

    /**
     * @param {Boolean} translate
     */
    set translate(translate) {
        this.node.translate = translate
    }

    /**
     * @returns {Boolean}
     */
    get translate() {
        return this.node.translate
    }

    /**
     * @param {Boolean} hidden
     */
    set hidden(hidden) {
        this.node.hidden = hidden
    }

    /**
     * @returns {Boolean}
     */
    get hidden() {
        return this.node.hidden
    }

    /**
     * @param {*} [init]
     * @returns {Element|*} created element
     */
    static create(init) {
        if(this === this.superAssembler) {
            throw TypeError(`Could not create an abstract ${ this.name } instance`)
        }
        else if(document instanceof HTMLDocument) {
            const { localName = this.localName } = init || this
            return document.createElement(localName)
        }
        else return super.create(init)
    }

    /**
     * @returns {Function} HTMLDocumentAssembler|DocumentAssembler
     */
    static get documentAssembler() {
        return HTMLElementAssembler.HTMLDocumentAssembler || super.documentAssembler
    }

    /**
     * @returns {window.HTMLElement}
     */
    static get interface() {
        return HTMLElement
    }

    /**
     * @returns {String}
     */
    static get localName() {
        const { superAssembler } = this
        let object = this
        let proto
        while(proto = getPrototypeOf(object)) {
            if(proto === superAssembler) break
            else object = proto
        }
        return object.name.replace(CLASS_PREFIX_RE, EMPTY_STRING).toLowerCase()
    }

    /**
     * @returns {String}
     */
    static get namespace() {
        return HTML_NAMESPACE_URI
    }

    /**
     * @returns {String}
     */
    static get prefix() {
        return document instanceof HTMLDocument?
            EMPTY_STRING :
            HTML_PREFIX
    }

    /**
     * @returns {Function} HTMLElementAssembler
     */
    static get superAssembler() {
        return HTMLElementAssembler
    }
}
<|endoftext|>


<commit_before>/* eslint-env mocha */
////////////////////////////////////////////////////////////////////////////////
// REQUIRES : BEGIN
////////////////////////////////////////////////////////////////////////////////

import { assert } from 'chai';

import { validate } from '../src/index';
import { Presence } from '../src/enums';

////////////////////////////////////////////////////////////////////////////////
// REQUIRES : END
////////////////////////////////////////////////////////////////////////////////

const MY_CUSTOM_TYPE = 'MY_CUSTOM_TYPE';

describe('Custom casts:', function () {

  const scheme = {
    type : MY_CUSTOM_TYPE,
    $presence : Presence.REQUIRED
  };

  it('should throw if custom cast is not registered', function () {
    assert.throws(() => validate(5, scheme), 'Unknown type MY_CUSTOM_TYPE');
  });

  it('should use custom cast if it\'s provided', function () {

    const options = {
      casts : {
        [MY_CUSTOM_TYPE] (v) {
          return v + 1;
        }
      }
    };

    const result = validate(5, scheme, options);
    assert.equal(result, 6);
  });

  it('should use custom strict cast if it\'s provided', function () {

    const options = {
      strict : true,
      castsStrict : {
        [MY_CUSTOM_TYPE] (v) {
          return v + 1;
        }
      }
    };

    const result = validate(5, scheme, options);
    assert.equal(result, 6);
  });

});
<commit_msg>change test<commit_after>/* eslint-env mocha */
////////////////////////////////////////////////////////////////////////////////
// REQUIRES : BEGIN
////////////////////////////////////////////////////////////////////////////////

import isNaN from 'lodash-compat/lang/isNaN';
import isNumber from 'lodash-compat/lang/isNumber';
import { assert } from 'chai';

import { validate } from '../src/index';
import { CAST_ERROR, Presence } from '../src/enums';

////////////////////////////////////////////////////////////////////////////////
// REQUIRES : END
////////////////////////////////////////////////////////////////////////////////

const INTEGER = 'INTEGER';

describe('Custom casts:', function () {

  const scheme = {
    type : INTEGER,
    $presence : Presence.REQUIRED
  };

  it('should throw if custom cast is not registered', function () {
    assert.throws(() => validate(5, scheme), 'Unknown type INTEGER');
  });

  it('should use custom cast if it\'s provided', function () {

    const options = {
      casts : {
        [INTEGER] (v) {
          v = v - 0;
          if (isNumber(v) && !isNaN(v) && v === Math.round(v))
            return v;
          return CAST_ERROR;
        }
      }
    };

    assert.equal(validate(5, scheme, options), 5);
    assert.equal(validate('5', scheme, options), 5);

    assert.throws(() => validate(5.5, scheme, options),
      'сouldn\'t cast value to type INTEGER');
    assert.throws(() => validate('5.5', scheme, options),
      'сouldn\'t cast value to type INTEGER');
  });

  it('should use custom strict cast if it\'s provided', function () {

    const options = {
      strict : true,
      castsStrict : {
        [INTEGER] (v) {
          if (isNumber(v) && v === Math.round(v))
            return v;
          return CAST_ERROR;
        }
      }
    };

    assert.equal(validate(5, scheme, options), 5);

    assert.throws(() => validate(5.5, scheme, options),
      'сouldn\'t cast value to type INTEGER');
    assert.throws(() => validate('5', scheme, options),
      'сouldn\'t cast value to type INTEGER');
  });

});
<|endoftext|>


<commit_before>/**
 * Command line implementation for CSSComb
 *
 * Usage example:
 * ./node_modules/.bin/csscomb [options] file1 [dir1 [fileN [dirN]]]
 */
var fs = require('fs');
var path = require('path');
var program = require('commander');
var vow = require('vow');
var Comb = require('./csscomb');

program
    .version(require('../package.json').version)
    .usage('[options] <file ...>')
    .option('-v, --verbose', 'verbose mode')
    .option('-c, --config [path]', 'configuration file path')
    .option('-d, --detect', 'detect mode (would return detected options)')
    .option('-l, --lint', 'in case some fixes needed returns an error')
    .parse(process.argv);

if (!program.args.length) {
    console.log('No input paths specified');
    program.help();
}

/**
 * Look for a config file: recursively from current (process) directory
 * up to $HOME dir
 * @param {String} configPath
 * @returns {String}
 */
function getConfigPath(configPath) {
    var HOME = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
    // Since `process.cwd()` can be absolutely anything, build default path
    // relative to current directory:
    var defaultConfigPath = __dirname + '/../config/csscomb.json';

    configPath = configPath || process.cwd() + '/.csscomb.json';

    // If we've finally found a config, return its path:
    if (fs.existsSync(configPath)) return configPath;

    // If we are in HOME dir already and yet no config file, return a default
    // one from our package:
    if (path.dirname(configPath) === HOME) return defaultConfigPath;

    // If there is no config in this directory, go one level up and look for
    // a config there:
    configPath = path.dirname(path.dirname(configPath)) + '/.csscomb.json';
    return getConfigPath(configPath);
}

var configPath = program.config || getConfigPath();
var comb = new Comb();

if (program.detect) {
    console.log(JSON.stringify(comb.detectInFile(program.args[0]), false, 4));
    process.exit(0);
}

if (fs.existsSync(configPath)) {
    var config;
    if (configPath.match(/\.css$/)) {
        config = comb.detectInFile(configPath);
    } else {
        config = require(configPath);
    }

    if (config.template) {
        if (fs.existsSync(config.template)) {
            var templateConfig = comb.detectInFile(config.template);
            for (var attrname in templateConfig) {
                if (!config[attrname]) {
                    config[attrname] = templateConfig[attrname];
                }
            }
        } else {
            console.log('Template configuration file ' + config.template + ' was not found.');
            process.exit(1);
        }
    }

    console.time('spent');

    config.verbose = program.verbose === true || config.verbose;
    config.lint = program.lint;

    comb.configure(config);

    vow.all(program.args.map(comb.processPath.bind(comb)))
    .then(function() {
        if (config.verbose) {
            console.log('');
            console.log(comb.processed + ' file' + (comb.processed === 1 ? '' : 's') + ' processed');
            console.log(comb.changed + ' file' + (comb.changed === 1 ? '' : 's') + ' fixed');
            console.timeEnd('spent');
        }
        if (config.lint && comb.tbchanged) {
            process.exit(1);
        }
    })
    .fail(function(e) {
        console.log('stack: ', e.stack);
        process.exit(1);
    });
} else {
    console.log('Configuration file ' + configPath + ' was not found.');
    process.exit(1);
}
<commit_msg>Stop searching config if we reach root directory<commit_after>/**
 * Command line implementation for CSSComb
 *
 * Usage example:
 * ./node_modules/.bin/csscomb [options] file1 [dir1 [fileN [dirN]]]
 */
var fs = require('fs');
var path = require('path');
var program = require('commander');
var vow = require('vow');
var Comb = require('./csscomb');

program
    .version(require('../package.json').version)
    .usage('[options] <file ...>')
    .option('-v, --verbose', 'verbose mode')
    .option('-c, --config [path]', 'configuration file path')
    .option('-d, --detect', 'detect mode (would return detected options)')
    .option('-l, --lint', 'in case some fixes needed returns an error')
    .parse(process.argv);

if (!program.args.length) {
    console.log('No input paths specified');
    program.help();
}

/**
 * Look for a config file: recursively from current (process) directory
 * up to $HOME dir
 * @param {String} configPath
 * @returns {String}
 */
function getConfigPath(configPath) {
    var HOME = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
    // Since `process.cwd()` can be absolutely anything, build default path
    // relative to current directory:
    var defaultConfigPath = __dirname + '/../config/csscomb.json';

    configPath = configPath || process.cwd() + '/.csscomb.json';

    // If we've finally found a config, return its path:
    if (fs.existsSync(configPath)) return configPath;

    // If we are in HOME dir already and yet no config file, return a default
    // one from our package.
    // If project is located not under HOME, compare to root instead.
    // Since there appears to be no good way to get root path in
    // Windows, assume that if current dir has no parent dir, we're in
    // root.
    var dirname = path.dirname(configPath);
    var parentDirname = path.dirname(dirname);
    if (dirname === HOME || dirname === parentDirname) return defaultConfigPath;

    // If there is no config in this directory, go one level up and look for
    // a config there:
    configPath = parentDirname + '/.csscomb.json';
    return getConfigPath(configPath);
}

var configPath = program.config || getConfigPath();
var comb = new Comb();

if (program.detect) {
    console.log(JSON.stringify(comb.detectInFile(program.args[0]), false, 4));
    process.exit(0);
}

if (fs.existsSync(configPath)) {
    var config;
    if (configPath.match(/\.css$/)) {
        config = comb.detectInFile(configPath);
    } else {
        config = require(configPath);
    }

    if (config.template) {
        if (fs.existsSync(config.template)) {
            var templateConfig = comb.detectInFile(config.template);
            for (var attrname in templateConfig) {
                if (!config[attrname]) {
                    config[attrname] = templateConfig[attrname];
                }
            }
        } else {
            console.log('Template configuration file ' + config.template + ' was not found.');
            process.exit(1);
        }
    }

    console.time('spent');

    config.verbose = program.verbose === true || config.verbose;
    config.lint = program.lint;

    comb.configure(config);

    vow.all(program.args.map(comb.processPath.bind(comb)))
    .then(function() {
        if (config.verbose) {
            console.log('');
            console.log(comb.processed + ' file' + (comb.processed === 1 ? '' : 's') + ' processed');
            console.log(comb.changed + ' file' + (comb.changed === 1 ? '' : 's') + ' fixed');
            console.timeEnd('spent');
        }
        if (config.lint && comb.tbchanged) {
            process.exit(1);
        }
    })
    .fail(function(e) {
        console.log('stack: ', e.stack);
        process.exit(1);
    });
} else {
    console.log('Configuration file ' + configPath + ' was not found.');
    process.exit(1);
}
<|endoftext|>


<commit_before>(function ($) {

    var utils = {

        DEBUGG: true,

        decorate_item: function (element) {
            return '
' + element + '
';
        },

        debug: function(message) {
            if( utils.DEBUGG )
                console.log(message);

            return utils;
        }
    };

    var view = {

        templates_path: '/dist/html/templates.htm',

        panels: {},

        templates: {},

        popovers: {},

        set_path: function (path) {
            this.templates_path = '/' + path
        },

        path: function (is_element) {
            return this.templates_path + (is_element ? 'elements/' : 'properties/');
        },

        load_html: function (callback) {
            $.get(view.templates_path).done(function (result) {
                $(result).find('div.panel').each(function () {
                    view.panels[$(this).data('id')] = $(this)[0].outerHTML;
                });

                $(result).find('div.template').each(function () {
                    view.templates[$(this).data('id')] = $(this).html();
                });

                $(result).find('div.popover').each(function () {
                    view.popovers[$(this).data('id')] = $(this).html();
                });

                utils.debug('Loaded html');

                _.isFunction(callback) ? callback.call(view) : false;
            });

            return this;
        },

        get_panel: function (panel_name, attributes) {
            var template = _.get(this.panels, panel_name, '');

            if( attributes ) {
                var tpl = _.template(_.unescape(template));

                attributes = _.merge(
                    $(template).data(),
                    attributes
                );

                utils.debug('Loaded Attributes ->');
                utils.debug(attributes);

                template = tpl(attributes);
            }

            return template;
        },

        get_template: function (template_name, attributes) {
            var template = _.get(this.templates, template_name, '');

            if( attributes ) {
                //todo temporary ..
                template = $('
'+template+'
');

                _.each(attributes, function(v, k) {
                    template.children().attr(k, v)
                });

                template = template.html();
            }

            return template;
        },

        get_popover: function (popover, attributes) {
            var template = _.get(this.popovers, popover, '');

            if( attributes ) {
                var tpl = _.template(_.unescape(template));

                attributes = _.merge(attributes, $(template).data());

                utils.debug('Loaded Attributes ->');
                utils.debug(attributes);

                template = tpl(attributes);
            }

            return template;
        }

    };

    var parser = {
        text: function(el) {
            var attributes = $(el).getAttributes();

            return attributes;
        },

        headline: function(el) {
            var attributes = $(el).getAttributes();

            attributes['text'] = $(el).text();

            return attributes;
        },

        paragraph: function(el) {
            return this.headline(el);
        },

        button: function(el) {
           return this.headline(el);
        },

        checkbox: function(el) {
            var attributes = $(el).getAttributes();

            attributes['text'] = $(el).find('label').text();

            attributes['name'] = $(el).find('input[type=checkbox]').attr('name');

            var options = [];
            $(el).find('input[type=checkbox]').each(function(k, v) {
                options[k] = $(v).getAttributes()
            });

            attributes['options'] = options;

            return attributes;
        },

        select: function(el) {
            var attributes = this.checkbox(el);

            attributes['name'] = $(el).find('select').attr('name');

            var options = [];
            $(el).find('select option').each(function(k, v) {
                options[k] = $(v).getAttributes()
            });

            attributes['options'] = options;

            return attributes;
        },
        radio: function(el) {
            var attributes = this.checkbox(el);

            attributes['name'] = $(el).find('input[type=radio]').attr('name');

            var options = [];
            $(el).find('input[type=radio]').each(function(k, v) {
                options[k] = $(v).getAttributes()
            });

            attributes['options'] = options;

            return attributes;
        }
    };

    var Panel = function (avatar, options) {

        var self = this;

        self.unique = new Date();

        self.avatar = avatar;

        self.options = options;

        self.events = {};

        self.panel_class = _.get(self.options, 'class', 'properties');

        var layout = '
%template%

cancel

save

';

        if ( _.has(self.options, 'properties.layout'))
            layout = _.get(self.options, 'properties.layout');

        self.set_layout = function (layout) {
            self.layout = layout;

            return self;
        };

        self.get_layout = function () {
            return self.layout;
        };

        self.set_layout(layout);

        self.open = function (parent) {
            if (self.is_opened())
                self.close();

            var template = self.get_template(false),
                parent = parent ? parent : (_.get(self.options, 'properties.parent', $('body')));

            var full_template = self.get_layout().replace('%template%', template);

            $(parent).append(full_template);

            var active_panel = $('.' + self.panel_class);

            active_panel.find('.cancel').on('click', function () {
                if (self.is_opened())
                    self.close();

                self.trigger('cancel', [self.avatar['element']]);
            });

            active_panel.find('.save').on('click', function () {
                var attributes = active_panel
                    .find(":input")
                    .serializeObject();

                self.trigger('save', [self.avatar['element'], attributes]);

                self.close();
            });
        };

        self.close = function () {
            $(document).find('.properties').remove();

            return self;
        };

        self.is_opened = function () {
            return $(document).find('.properties').length;
        };

        self.on = function (event, callback) {
            self.events[event] = callback;

            return self;
        };

        self.trigger = function (event, attributes) {
            var callback = _.get(self.events, event, function () {
            });

            callback.apply(self, attributes);

            return self;
        };

        self.get_avatar = function () {
            return self.avatar;
        };

        self.get_template = function (clean) {
            var avatar = self.get_avatar();
            var data = avatar.data;
            var attributes = avatar.getAttributes();

            return view.get_panel(
                _.get(data, 'panel', 'general'), (! clean) ? attributes : {}
            );
        };
    };

    var Avatar = function (element, data, options) {

        var self = this;

        self.unique = new Date();

        self.element = element;
        self.options = options;

        self.children = $(element).children();

        self.data       = data;

        if( parser.hasOwnProperty(data['type']) && ( typeof parser[data['type']] == 'function' ) ) {
            var function_name = data['type'];

            self.attributes = parser[function_name](self.children)
        } else {
            self.attributes = self.children.getAttributes();
        }

        self.panel = false;

        self.init = function() {
            var tooltip = _.get(self.options, 'tooltip');

            if( self.get_popover() ) {
                tooltip.content = $(self.get_popover());

                tooltip.content.find('.root').data({
                    avatar: self,
                    panel : self.get_panel()
                });
            }

            $(self.element).popover(tooltip)
                .on('show.bs.popover', function () {
                    $('[data-toggle=popover]').not( $(self.element) ).popover('hide');
                });
        };

        self.fillAttributes = function (attributes) {
            _.each(attributes, function (v, k) {
                self.attributes[k] = v;
            });

            $(self.element).data('avatar', self);

            return self;
        };

        self.getAttributes = function() {
            return self.attributes;
        };

        self.get_template = function () {
            var attributes = self.getAttributes();

            return view.get_template(
                _.get(self.data, 'type', 'text'), attributes
            );
        };

        self.get_popover = function() {
            var popover = view.get_popover(
                _.get(self.data, 'popover', 'general')
            );

            return popover;
        };

        self.get_panel = function () {
            if (!self.panel) {
                self.panel = new Panel(self, self.options)
            }

            return self.panel;
        };

        self.remove = function() {
            $(self.element).remove();

            return self;
        };

        self.render = function() {
            $(self.element).html(
                self.get_template()
            );

            return self;
        }
    };

    var Builder = function (elements, arguments) {

        var self = this;

        self.elements = elements;

        self.avatars = [];

        self.sortable_class = 'sortable';

        self.options = _.defaultsDeep(arguments, {
            elements: {},
            tooltip: {
                enabled: true,
                trigger: 'click',
                content: 'edit',
                html: true,
                placement: 'top',
                container: 'body'
            }
        });

        self.init = function () {
            var box = {};

            $(self.elements).each(function() {
                var selector = $(this).data('container');

                if( _.has(box, selector) ) {
                    var container = _.get(box, selector);
                    container['elements'].push($(this))
                } else {
                    box[selector] = {
                        container: $('.' + selector),
                        elements: [$(this)]
                    }
                }
            });

            _.each(box, function(v, k) {
                var elements = $('[data-container='+k+']'),
                    container = v.container;

                //make it draggable
                elements.draggable({
                    revert: "invalid",
                    containment: "document",
                    helper: "clone",
                    cursor: "move"
                });

                container.find('.row').sortable({
                    revert: 10
                }).disableSelection();

                self.init_from_html(container);

                container.droppable({
                    accept: elements,
                    drop: function (event, ui) {
                        var html = view.get_template(
                            $(ui.draggable).data('type')
                        );

                        html = $(utils.decorate_item(html));

                        if (! container.find('.row').length) {
                            container.append('

');

                            container.find('.row').sortable({
                                revert: true
                            }).disableSelection();
                        }

                        var row = container.find('div.row');

                        row.append(html);

                        var avatar = self.add_avatar(
                            html,
                            $(ui.draggable).data()
                        );

                        html.data('avatar', avatar);

                        avatar.get_panel().close();

                        avatar.init();

                        html.trigger('click')
                    }
                });
            });
        };

        self.init_from_html = function(container) {
           $(container).find('.avatar').each(function () {
                var avatar = self.add_avatar(
                    this,
                    $(this).data()
                );

                $(this).data('avatar', avatar);

                avatar.init()
            });
        };

        self.add_avatars = function (avs) {
            _.each(avs, function (avatar) {
                self.add_avatar(avatar);
            });

            return self;
        };

        self.add_avatar = function (avatar, data) {
            var av = new Avatar(avatar, data, self.options);

            self.avatars.push(av);

            return av;
        };

        self.get_container = function() {
            return $(self.container);
        };

        self.save = function () {
        };
        self.load = function () {
        };
    };

    var methods = {
        init: function (options) {
            if(_.has(options, 'template_path') )
                view.set_path(
                    _.get(options, 'template_path')
                );

            var self = this;

            view.load_html(function() {
                (new Builder($(self), options))
                    .init()
            });

            return self;
        }
    };

    $.fn.builder = function (arguments) {
        var method = _.get(arguments, 'method', 'init');

        if (methods[method]) {
            return methods[method].call(this, arguments);
        } else if (typeof method === 'object' || !method) {
            return methods.init.apply(object, arguments);
        } else {
            $.error('Invalid method ' + method + ' for jQuery.builder');
        }
    };

    $.fn.getAttributes = function() {
        var attributes = {};

        if( this.length ) {
            $.each( this[0].attributes, function( index, attr ) {
                attributes[ attr.name ] = attr.value;
            } );
        }

        return attributes;
    };

})(jQuery);<commit_msg>adding defaults options to editors.<commit_after>(function ($) {

    var utils = {

        DEBUGG: true,

        decorate_item: function (element) {
            return '
' + element + '
';
        },

        debug: function(message) {
            if( utils.DEBUGG )
                console.log(message);

            return utils;
        }
    };

    var view = {

        templates_path: '/dist/html/templates.htm',

        panels: {},

        templates: {},

        popovers: {},

        set_path: function (path) {
            this.templates_path = '/' + path
        },

        path: function (is_element) {
            return this.templates_path + (is_element ? 'elements/' : 'properties/');
        },

        load_html: function (callback) {
            $.get(view.templates_path).done(function (result) {
                $(result).find('div.panel').each(function () {
                    view.panels[$(this).data('id')] = $(this)[0].outerHTML;
                });

                $(result).find('div.template').each(function () {
                    view.templates[$(this).data('id')] = $(this).html();
                });

                $(result).find('div.popover').each(function () {
                    view.popovers[$(this).data('id')] = $(this).html();
                });

                utils.debug('Loaded html');

                _.isFunction(callback) ? callback.call(view) : false;
            });

            return this;
        },

        get_panel: function (panel_name, attributes) {
            var template = _.get(this.panels, panel_name, '');

            if( attributes ) {
                var tpl = _.template(_.unescape(template));

                attributes = _.merge(
                    $(template).data(),
                    attributes
                );

                utils.debug('Loaded Attributes ->');
                utils.debug(attributes);

                template = tpl(attributes);
            }

            return template;
        },

        get_template: function (template_name, attributes) {
            var template = _.get(this.templates, template_name, '');

            if( attributes ) {
                //todo temporary ..
                template = $('
'+template+'
');

                _.each(attributes, function(v, k) {
                    template.children().attr(k, v)
                });

                template = template.html();
            }

            return template;
        },

        get_popover: function (popover, attributes) {
            var template = _.get(this.popovers, popover, '');

            if( attributes ) {
                var tpl = _.template(_.unescape(template));

                attributes = _.merge(attributes, $(template).data());

                utils.debug('Loaded Attributes ->');
                utils.debug(attributes);

                template = tpl(attributes);
            }

            return template;
        }

    };

    var parser = {
        text: function(el) {
            var attributes = $(el).getAttributes();

            return attributes;
        },

        headline: function(el) {
            var attributes = $(el).getAttributes();

            attributes['text'] = $(el).text();

            return attributes;
        },

        paragraph: function(el) {
            return this.headline(el);
        },

        button: function(el) {
           return this.headline(el);
        },

        checkbox: function(el) {
            var attributes = $(el).getAttributes();

            attributes['text'] = $(el).find('label').text();

            attributes['name'] = $(el).find('input[type=checkbox]').attr('name');

            var options = [];
            $(el).find('input[type=checkbox]').each(function(k, v) {
                options[k] = $(v).getAttributes()
            });

            attributes['options'] = options;

            return attributes;
        },

        select: function(el) {
            var attributes = this.checkbox(el);

            attributes['name'] = $(el).find('select').attr('name');

            var options = [];
            $(el).find('select option').each(function(k, v) {
                options[k] = $(v).getAttributes()
            });

            attributes['options'] = options;

            return attributes;
        },

        radio: function(el) {
            var attributes = this.checkbox(el);

            attributes['name'] = $(el).find('input[type=radio]').attr('name');

            var options = [];
            $(el).find('input[type=radio]').each(function(k, v) {
                options[k] = $(v).getAttributes()
            });

            attributes['options'] = options;

            return attributes;
        }
    };

    var Panel = function (avatar, options) {

        var self = this;

        self.unique = new Date();

        self.avatar = avatar;

        self.options = options;

        self.events = {};

        self.panel_class = _.get(self.options, 'class', 'properties');

        var layout = '
%template%

cancel

save

';

        if ( _.has(self.options, 'properties.layout'))
            layout = _.get(self.options, 'properties.layout');

        self.set_layout = function (layout) {
            self.layout = layout;

            return self;
        };

        self.get_layout = function () {
            return self.layout;
        };

        self.set_layout(layout);

        self.open = function (parent) {
            if (self.is_opened())
                self.close();

            var template = self.get_template(false),
                parent = parent ? parent : (_.get(self.options, 'properties.parent', $('body')));

            var full_template = self.get_layout().replace('%template%', template);

            $(parent).append(full_template);

            var active_panel = $('.' + self.panel_class);

            active_panel.find('.cancel').on('click', function () {
                if (self.is_opened())
                    self.close();

                self.trigger('cancel', [self.avatar['element']]);
            });

            active_panel.find('.save').on('click', function () {
                var attributes = active_panel
                    .find(":input")
                    .serializeObject();

                self.trigger('save', [self.avatar['element'], attributes]);

                self.close();
            });
        };

        self.close = function () {
            $(document).find('.properties').remove();

            return self;
        };

        self.is_opened = function () {
            return $(document).find('.properties').length;
        };

        self.on = function (event, callback) {
            self.events[event] = callback;

            return self;
        };

        self.trigger = function (event, attributes) {
            var callback = _.get(self.events, event, function () {
            });

            callback.apply(self, attributes);

            return self;
        };

        self.get_avatar = function () {
            return self.avatar;
        };

        self.get_template = function (clean) {
            var avatar = self.get_avatar();
            var data = avatar.data;
            var attributes = avatar.getAttributes();

            return view.get_panel(
                _.get(data, 'panel', 'general'), (! clean) ? attributes : {}
            );
        };
    };

    var Avatar = function (element, data, options) {

        var self = this;

        self.unique = new Date();

        self.element = element;
        self.options = options;

        self.children = $(element).children();

        self.data       = data;

        if( parser.hasOwnProperty(data['type']) && ( typeof parser[data['type']] == 'function' ) ) {
            var function_name = data['type'];

            self.attributes = parser[function_name](self.children)
        } else {
            self.attributes = self.children.getAttributes();
        }

        self.panel = false;

        self.init = function() {
            var editor = _.get(
                    self.options['editors'],
                    _.get(self.data, 'editor', 'tooltip')
                ),
                popover = self.get_popover();

            editor.init(self, popover, editor);
        };

        self.fillAttributes = function (attributes) {
            _.each(attributes, function (v, k) {
                self.attributes[k] = v;
            });

            $(self.element).data('avatar', self);

            return self;
        };

        self.getAttributes = function() {
            return self.attributes;
        };

        self.get_template = function () {
            var attributes = self.getAttributes();

            return view.get_template(
                _.get(self.data, 'type', 'text'), attributes
            );
        };

        self.get_popover = function() {
            var popover = view.get_popover(
                _.get(self.data, 'popover', 'general')
            );

            return popover;
        };

        self.get_panel = function () {
            if (!self.panel) {
                self.panel = new Panel(self, self.options)
            }

            return self.panel;
        };

        self.remove = function() {
            $(self.element).remove();

            return self;
        };

        self.render = function() {
            $(self.element).html(
                self.get_template()
            );

            return self;
        }
    };

    var Builder = function (elements, arguments) {

        var self = this;

        self.elements = elements;

        self.avatars = [];

        self.sortable_class = 'sortable';

        self.options = _.defaultsDeep(arguments, {
            elements: {},
            editors: {
                tooltip: {
                    enabled: true,
                    trigger: 'click',
                    content: 'edit',
                    html: true,
                    placement: 'top',
                    container: 'body'
                },
                medium: {
                    disableEditing: false,
                    toolbar: {
                        buttons: ['bold', 'italic', 'underline', 'h2', 'h3'],
                    }
                }
            }
        });

        self.init = function () {
            var box = {};

            $(self.elements).each(function() {
                var selector = $(this).data('container');

                if( _.has(box, selector) ) {
                    var container = _.get(box, selector);
                    container['elements'].push($(this))
                } else {
                    box[selector] = {
                        container: $('.' + selector),
                        elements: [$(this)]
                    }
                }
            });

            _.each(box, function(v, k) {
                var elements = $('[data-container='+k+']'),
                    container = v.container;

                //make it draggable
                elements.draggable({
                    revert: "invalid",
                    containment: "document",
                    helper: "clone",
                    cursor: "move"
                });

                container.find('.row').sortable({
                    revert: 10
                }).disableSelection();

                self.init_from_html(container);

                container.droppable({
                    accept: elements,
                    drop: function (event, ui) {
                        var html = view.get_template(
                            $(ui.draggable).data('type')
                        );

                        html = $(utils.decorate_item(html));

                        if (! container.find('.row').length) {
                            container.append('

');

                            container.find('.row').sortable({
                                revert: true
                            }).disableSelection();
                        }

                        var row = container.find('div.row');

                        row.append(html);

                        var avatar = self.add_avatar(
                            html,
                            $(ui.draggable).data()
                        );

                        html.data('avatar', avatar);

                        avatar.get_panel().close();

                        avatar.init();

                        html.trigger('click')
                    }
                });
            });
        };

        self.init_from_html = function(container) {
           $(container).find('.avatar').each(function () {
                var avatar = self.add_avatar(
                    this,
                    $(this).data()
                );

                $(this).data('avatar', avatar);

                avatar.init()
            });
        };

        self.add_avatars = function (avs) {
            _.each(avs, function (avatar) {
                self.add_avatar(avatar);
            });

            return self;
        };

        self.add_avatar = function (avatar, data) {
            var av = new Avatar(avatar, data, self.options);

            self.avatars.push(av);

            return av;
        };

        self.get_container = function() {
            return $(self.container);
        };

        self.save = function () {
        };
        self.load = function () {
        };
    };

    var methods = {
        init: function (options) {
            if(_.has(options, 'template_path') )
                view.set_path(
                    _.get(options, 'template_path')
                );

            var self = this;

            view.load_html(function() {
                (new Builder($(self), options))
                    .init()
            });

            return self;
        }
    };

    $.fn.builder = function (arguments) {
        var method = _.get(arguments, 'method', 'init');

        if (methods[method]) {
            return methods[method].call(this, arguments);
        } else if (typeof method === 'object' || !method) {
            return methods.init.apply(object, arguments);
        } else {
            $.error('Invalid method ' + method + ' for jQuery.builder');
        }
    };

    $.fn.getAttributes = function() {
        var attributes = {};

        if( this.length ) {
            $.each( this[0].attributes, function( index, attr ) {
                attributes[ attr.name ] = attr.value;
            } );
        }

        return attributes;
    };

})(jQuery);<|endoftext|>


<commit_before>'use babel'

/* @flow */

import { CompositeDisposable } from 'atom'
import type { Disposable } from 'atom'

const ICON_BUSY = '\uf087'
const ICON_IDLE = '\uf06d'
const MESSAGE_IDLE = 'Idle'

export class SignalElement extends HTMLElement {
  tooltip: Disposable;
  subscriptions: CompositeDisposable;

  createdCallback() {
    this.subscriptions = new CompositeDisposable()
    this.setBusy(false)
    this.setTooltip(MESSAGE_IDLE)
    this.classList.add('inline-block')
  }
  setBusy(busy: boolean) {
    if (busy) {
      this.textContent = ICON_BUSY
      this.classList.add('busy')
      this.classList.remove('idle')
    } else {
      this.textContent = ICON_IDLE
      this.classList.add('idle')
      this.classList.remove('busy')
    }
  }
  setTooltip(title: string) {
    if (this.tooltip) {
      this.tooltip.dispose()
    }
    this.tooltip = atom.tooltips.add(this, {
      title
    })
  }
  dispose() {
    this.subscriptions.dispose()
    this.tooltip.dispose()
  }
}

const element = document.registerElement('busy-signal', {
  prototype: SignalElement.prototype
})

export default element
<commit_msg>:fire: Cleanup element<commit_after>'use babel'

/* @flow */

import type { Disposable } from 'atom'

const ICON = '\uf087'
const MESSAGE_IDLE = 'Idle'

export class SignalElement extends HTMLElement {
  tooltip: Disposable;

  createdCallback() {
    this.setBusy(false)
    this.setTooltip(MESSAGE_IDLE)
    this.textContent = ICON
    this.classList.add('inline-block')
  }
  setBusy(busy: boolean) {
    if (busy) {
      this.classList.add('busy')
      this.classList.remove('idle')
    } else {
      this.classList.add('idle')
      this.classList.remove('busy')
    }
  }
  setTooltip(title: string) {
    if (this.tooltip) {
      this.tooltip.dispose()
    }
    this.tooltip = atom.tooltips.add(this, {
      title
    })
  }
  dispose() {
    this.tooltip.dispose()
  }
}

const element = document.registerElement('busy-signal', {
  prototype: SignalElement.prototype
})

export default element
<|endoftext|>


<commit_before>let t = require('../test-lib/test.js');
let assert = require('assert');
let request = require('request-promise');
let apos;

describe('custom-pages', function() {

  this.timeout(t.timeout);

  after(async function() {
    return t.destroy(apos);
  });

  /// ///
  // EXISTENCE
  /// ///

  it('should initialize', async function() {
    apos = await require('../index.js')({
      root: module,
      shortName: 'test',
      argv: {
        _: []
      },
      modules: {
        'apostrophe-express': {
          secret: 'xxx'
        },
        'nifty-pages': {
          extend: 'apostrophe-custom-pages'
        }
      }
    });
  });

  it('should fire a dispatch route for its homepage', async function() {
    const niftyPages = apos.modules['nifty-pages'];
    niftyPages.dispatch('/', async function(req) {
      req.handlerInvoked = true;
      req.template = function(req, args) {
        return 'niftyPages-index-template-rendered-this';
      };
    });
    // Simulate a page request
    const req = apos.tasks.getAnonReq({
      data: {
        bestPage: {
          type: 'nifty-page'
        }
      },
      remainder: '/'
    });
    await apos.pages.emit('serve', req);
    assert(req.handlerInvoked);
  });

  it('should fire a dispatch route matching a second, longer URL', async function() {
    const niftyPages = apos.modules['nifty-pages'];
    niftyPages.dispatch('/foo', async function(req) {
      req.handlerInvoked = true;
      req.template = function(req, args) {
        return 'niftyPages-foo-template-rendered-this';
      };
    });
    // Simulate a page request
    const req = apos.tasks.getAnonReq({
      data: {
        bestPage: {
          type: 'nifty-page'
        }
      },
      remainder: '/foo'
    });
    await apos.pages.emit('serve', req);
    assert(req.handlerInvoked);
  });

  it('should fire a dispatch route with parameters', async function() {
    const niftyPages = apos.modules['nifty-pages'];
    niftyPages.dispatch('/bar/:bizzle/:kapow/*', async function(req) {
      req.barInvoked = true;
      req.template = function(req, args) {
        return 'niftyPages-bar-template-rendered-this';
      };
    });
    // Simulate a page request
    const req = apos.tasks.getAnonReq({
      data: {
        bestPage: {
          type: 'nifty-page'
        }
      },
      remainder: '/bar/wacky/wonky/wibble/skip'
    });
    await apos.pages.emit('serve', req);
    assert(req.barInvoked);
    assert(req.params.bizzle === 'wacky');
    assert(req.params.kapow === 'wonky');
    assert(req.params[0] === 'wibble/skip');
  });

  it('should allow a later call to dispatch to override an earlier dispatch route', async function() {
    let niftyPages = apos.modules['nifty-pages'];
    await niftyPages.dispatch('/foo', function(req) {
      req.foo2Invoked = true;
      req.template = function(req, args) {
        return 'niftyPages-foo2-template-rendered-this';
      };
    });
    // Simulate a page request
    const req = apos.tasks.getAnonReq({
      data: {
        bestPage: {
          type: 'nifty-page'
        }
      },
      remainder: '/foo'
    });
    await apos.pages.emit('serve', req);
    assert(req.foo2Invoked);
    assert(!req.fooInvoked);
  });

  it('should not match when page type is wrong', async function() {
    let niftyPages = apos.modules['nifty-pages'];
    // Simulate a page request
    const req = apos.tasks.getAnonReq({
      data: {
        bestPage: {
          type: 'wibble-page'
        }
      },
      remainder: '/foo'
    });
    await apos.pages.emit('serve', req);
    assert(!req.foo2Invoked);
  });

  it('should not match when there is no bestPage', async function() {
    let niftyPages = apos.modules['nifty-pages'];
    // Simulate a page request
    const req = apos.tasks.getAnonReq({
      data: {
        bestPage: null
      },
      remainder: '/foo'
    });
    await apos.pages.emit('serve', req);
    assert(!req.foo2Invoked);
  });

  it('should be able to insert a test page manually into the db', async function() {
    let testItem =
      { _id: 'niftyPages1',
        type: 'nifty-page',
        slug: '/niftyPages',
        published: true,
        path: '/niftyPages',
        level: 1,
        rank: 5
      };
    await apos.docs.db.insert(testItem);
  });

  it('should match a dispatch route on a real live page request', async function() {
    const body = await request('http://localhost:3000/niftyPages');
    // Did we get the index output?
    assert(body.match(/niftyPages-index-template-rendered-this/));
  });

  it('runs foo route with /foo remainder', async function() {
    const body = await request('http://localhost:3000/niftyPages/foo');
    // Did we get the foo output?
    assert(body.match(/niftyPages-foo2-template-rendered-this/));
  });

  it('yields 404 with bad remainder (not matching any dispatch routes)', async function() {
    try {
      await request('http://localhost:3000/niftyPages/tututu');
      assert(false);
    } catch (e) {
      assert(e);
    }
  });

});
<commit_msg>Deletes unused vars; lets to consts<commit_after>const t = require('../test-lib/test.js');
const assert = require('assert');
const request = require('request-promise');
let apos;

describe('custom-pages', function() {

  this.timeout(t.timeout);

  after(async function() {
    return t.destroy(apos);
  });

  /// ///
  // EXISTENCE
  /// ///

  it('should initialize', async function() {
    apos = await require('../index.js')({
      root: module,
      shortName: 'test',
      argv: {
        _: []
      },
      modules: {
        'apostrophe-express': {
          secret: 'xxx'
        },
        'nifty-pages': {
          extend: 'apostrophe-custom-pages'
        }
      }
    });
  });

  it('should fire a dispatch route for its homepage', async function() {
    const niftyPages = apos.modules['nifty-pages'];
    niftyPages.dispatch('/', async function(req) {
      req.handlerInvoked = true;
      req.template = function(req, args) {
        return 'niftyPages-index-template-rendered-this';
      };
    });
    // Simulate a page request
    const req = apos.tasks.getAnonReq({
      data: {
        bestPage: {
          type: 'nifty-page'
        }
      },
      remainder: '/'
    });
    await apos.pages.emit('serve', req);
    assert(req.handlerInvoked);
  });

  it('should fire a dispatch route matching a second, longer URL', async function() {
    const niftyPages = apos.modules['nifty-pages'];
    niftyPages.dispatch('/foo', async function(req) {
      req.handlerInvoked = true;
      req.template = function(req, args) {
        return 'niftyPages-foo-template-rendered-this';
      };
    });
    // Simulate a page request
    const req = apos.tasks.getAnonReq({
      data: {
        bestPage: {
          type: 'nifty-page'
        }
      },
      remainder: '/foo'
    });
    await apos.pages.emit('serve', req);
    assert(req.handlerInvoked);
  });

  it('should fire a dispatch route with parameters', async function() {
    const niftyPages = apos.modules['nifty-pages'];
    niftyPages.dispatch('/bar/:bizzle/:kapow/*', async function(req) {
      req.barInvoked = true;
      req.template = function(req, args) {
        return 'niftyPages-bar-template-rendered-this';
      };
    });
    // Simulate a page request
    const req = apos.tasks.getAnonReq({
      data: {
        bestPage: {
          type: 'nifty-page'
        }
      },
      remainder: '/bar/wacky/wonky/wibble/skip'
    });
    await apos.pages.emit('serve', req);
    assert(req.barInvoked);
    assert(req.params.bizzle === 'wacky');
    assert(req.params.kapow === 'wonky');
    assert(req.params[0] === 'wibble/skip');
  });

  it('should allow a later call to dispatch to override an earlier dispatch route', async function() {
    const niftyPages = apos.modules['nifty-pages'];
    await niftyPages.dispatch('/foo', function(req) {
      req.foo2Invoked = true;
      req.template = function(req, args) {
        return 'niftyPages-foo2-template-rendered-this';
      };
    });
    // Simulate a page request
    const req = apos.tasks.getAnonReq({
      data: {
        bestPage: {
          type: 'nifty-page'
        }
      },
      remainder: '/foo'
    });
    await apos.pages.emit('serve', req);
    assert(req.foo2Invoked);
    assert(!req.fooInvoked);
  });

  it('should not match when page type is wrong', async function() {
    // Simulate a page request
    const req = apos.tasks.getAnonReq({
      data: {
        bestPage: {
          type: 'wibble-page'
        }
      },
      remainder: '/foo'
    });
    await apos.pages.emit('serve', req);
    assert(!req.foo2Invoked);
  });

  it('should not match when there is no bestPage', async function() {
    // Simulate a page request
    const req = apos.tasks.getAnonReq({
      data: {
        bestPage: null
      },
      remainder: '/foo'
    });
    await apos.pages.emit('serve', req);
    assert(!req.foo2Invoked);
  });

  it('should be able to insert a test page manually into the db', async function() {
    const testItem =
      { _id: 'niftyPages1',
        type: 'nifty-page',
        slug: '/niftyPages',
        published: true,
        path: '/niftyPages',
        level: 1,
        rank: 5
      };
    await apos.docs.db.insert(testItem);
  });

  it('should match a dispatch route on a real live page request', async function() {
    const body = await request('http://localhost:3000/niftyPages');
    // Did we get the index output?
    assert(body.match(/niftyPages-index-template-rendered-this/));
  });

  it('runs foo route with /foo remainder', async function() {
    const body = await request('http://localhost:3000/niftyPages/foo');
    // Did we get the foo output?
    assert(body.match(/niftyPages-foo2-template-rendered-this/));
  });

  it('yields 404 with bad remainder (not matching any dispatch routes)', async function() {
    try {
      await request('http://localhost:3000/niftyPages/tututu');
      assert(false);
    } catch (e) {
      assert(e);
    }
  });

});
<|endoftext|>


<commit_before>"use strict";
var blc            = require("./");
var defaultOptions = require("./internal/defaultOptions");
var pkg            = require("../package.json");

var chalk = require("chalk");
var humanizeDuration = require("humanize-duration");
var nopter = require("nopter");
var spinner = require("char-spinner");

process.on("unhandledRejection", function(reason, p) {
	log( nopter.error.fatal("Unhandled Rejection", reason, "Error") );
	process.exit(1);
});

function cli()
{
	var filterLevel = "The types of tags and attributes that are considered links.\n";
	filterLevel += "  0: clickable links\n";
	filterLevel += "  1: 0 + media, iframes, meta refreshes\n";
	filterLevel += "  2: 1 + stylesheets, scripts, forms\n";
	filterLevel += "  3: 2 + metadata\n";
	filterLevel += "  Default: "+defaultOptions.filterLevel;

	this.nopter = new nopter();

	this.nopter.config(
	{
		title: "Broken Link Checker",
		description: pkg.description,
		version: pkg.version,
		name: "blc",
		options:
		{
			"exclude":
			{
				rename: "excludedKeywords",
				info: "A keyword/glob to match links against. Can be used multiple times.",
				type: [String, Array],
				default: defaultOptions.excludedKeywords
			},
			"exclude-external":
			{
				rename: "excludeExternalLinks",
				short: "e",
				info: "Will not check external links.",
				type: Boolean
			},
			"exclude-internal":
			{
				rename: "excludeInternalLinks",
				short: "i",
				info: "Will not check internal links.",
				type: Boolean
			},
			"filter-level":
			{
				info: filterLevel,
				type: Number,
				default: defaultOptions.filterLevel
			},
			"follow":
			{
				rename: "followRobotExclusions",
				short: "f",
				info: "Force-follow robot exclusions.",
				type: Boolean
			},
			"get":
			{
				short: "g",
				info: "Change request method to GET.",
				type: Boolean
			},
			"help":
			{
				short: ["h","?"],
				info: "Display this help text.",
				type: Boolean
			},
			"input":
			{
				info: "URL to an HTML document.",
				type: require("url")
			},
			"host-requests":
			{
				rename: "maxSocketsPerHost",
				info: "The maximum number of requests per host/port at any given time.",
				type: Number,
				default: defaultOptions.maxSocketsPerHost
			},
			"ordered":
			{
				rename: "maintainLinkOrder",
				short: "o",
				info: "Maintain the order of links as they appear in their HTML document.",
				type: Boolean
			},
			"recursive":
			{
				short: "r",
				info: "Recursively scan (\"crawl\") the HTML document(s).",
				type: Boolean
			},
			"user-agent":
			{
				info: "The user agent to use for link checks.",
				type: String,
				default: defaultOptions.userAgent
			},
			"verbose":
			{
				short: "v",
				info: "Display excluded links.",
				type: Boolean
			},
			"version":
			{
				short: "V",
				info: "Display the app version.",
				type: Boolean
			}
		},
		aliases: ["input"]
	});
}

cli.prototype.input = function(args, showArgs)
{
	//var testing = args !== undefined;
	args = this.nopter.input(args);

	//if (testing===true && showArgs===true) return args;

	if (args.help === true)
	{
		log( this.nopter.help() );
	}
	else if (args.version === true)
	{
		log(pkg.version);
	}
	else if (args.input != null)
	{
		// TODO :: remove this when nopter's default values for Arrays are fixed
		if (args.excludedKeywords === undefined)
		{
			args.excludedKeywords = defaultOptions.excludedKeywords;
		}

		run(args.input,
		{
			excludedKeywords:       args.excludedKeywords,
			excludeExternalLinks:   args.excludeExternalLinks===true,
			excludeInternalLinks:   args.excludeInternalLinks===true,
			excludeLinksToSamePage: args.verbose!==true,
			filterLevel:            args.filterLevel,
			honorRobotExclusions:   args.followRobotExclusions!==true,
			maxSocketsPerHost:      args.maxSocketsPerHost,
			requestMethod:          args.get!==true ? "head" : "get",
			userAgent:              args.userAgent
		},
		{
			excludeCachedLinks:   args.verbose!==true,
			excludeFilteredLinks: args.verbose!==true,
			maintainLinkOrder:    args.maintainLinkOrder,
			recursive:            args.recursive
		});
	}
	else
	{
		log( nopter.error.fatal("Input URL required", "Use --help for more options", "Error") );
		process.exit(1);
	}
};

function log()
{
	// Avoid spinner chars getting stuck in the log
	spinner.clear();

	console.log.apply(null, arguments);
}

function logMetrics(brokenLinks, excludedLinks, totalLinks, duration, preBreak, exit)
{
	var output = preBreak===true ? "\n" : "";

	output += chalk.gray("Finished! "+totalLinks+" links found.");

	if (excludedLinks > 0)
	{
		output += chalk.gray(" "+excludedLinks+" excluded.");
	}

	if (totalLinks > 0)
	{
		output += chalk.gray(" ");
		output += chalk[ brokenLinks>0 ? "red" : "green" ](brokenLinks+" broken");
		output += chalk.gray(".");
	}

	if (duration != null)
	{
		output += chalk.gray("\nElapsed time: ");
		output += chalk.gray( humanizeDuration(duration, {round:true, largest:2}) );
	}

	log(output);

	if (exit === true)
	{
		process.exit(brokenLinks===0 ? 0 : 1);
	}
}

/*
	Ensure that `logMetrics()` is called after `logResults_delayed()`.
*/
function logMetrics_delayed(brokenLinks, excludedLinks, totalLinks, duration, preBreak, exit)
{
	setImmediate( function()
	{
		logMetrics(brokenLinks, excludedLinks, totalLinks, duration, preBreak, exit);
	});
}

function logPage(data, pageUrl)
{
	var output = "";

	if (++data.total.pages > 1) output += "\n";

	output += chalk.white("Getting links from: ") + chalk.yellow(pageUrl);

	log(output);
}

function logResult(result, finalResult)
{
	var output = "";

	if (result.__cli_excluded !== true)
	{
		// TODO :: if later results are skipped, the last RENDERED result will not be "└─"
		output = chalk.gray( finalResult!==true ? "├─" : "└─" );

		if (result.broken === true)
		{
			output += chalk.red("BROKEN");
			output += chalk.gray("─ ");
		}
		else if (result.excluded === true)
		{
			output += chalk.gray("─SKIP── ");
		}
		else
		{
			output += chalk.gray("──");
			output += chalk.green("OK");
			output += chalk.gray("─── ");
		}

		if (result.url.resolved != null)
		{
			output += chalk.yellow( result.url.resolved );
		}
		else
		{
			// Excluded scheme
			output += chalk.yellow( result.url.original );
		}

		if (result.broken === true)
		{
			output += chalk.gray(" ("+ result.brokenReason +")");
		}
		else if (result.excluded === true)
		{
			output += chalk.gray(" ("+ result.excludedReason +")");
		}
		// Don't display cached message if broken/excluded message is displayed
		else if (result.http.cached === true)
		{
			output += chalk.gray(" (CACHED)");
		}
	}

	return output;
}

/*
	Logs links in the order that they are found in their containing HTML
	document, even if later links receive an earlier response.
*/
function logResults(data)
{
	var done,output,result;
	var nextIsReady = true;

	while (nextIsReady)
	{
		result = data.page.results[data.page.currentIndex];

		if (result !== undefined)
		{
			done = data.page.done===true && data.page.currentIndex>=data.page.results.length-1;

			output = logResult(result, done);

			if (output !== "") log(output);
			if (done === true) return;

			data.page.currentIndex++;
		}
		else
		{
			nextIsReady = false;
		}
	}
}

/*
	Ensure that `logResults()` is called after `data.page.done=true`.
*/
function logResults_delayed(data)
{
	// Avoid more than one delay via multiple synchronous iterations
	if (data.delay === null)
	{
		data.delay = setImmediate( function()
		{
			logResults(data);
			data.delay = null;
		});
	}
}

function pushResult(data, result, options)
{
	if (options.maintainLinkOrder === true)
	{
		data.page.results[result.html.index] = result;
	}
	else
	{
		data.page.results.push(result);
	}
}

function resetPageData(data)
{
	data.page.brokenLinks = 0;
	data.page.currentIndex = 0;
	data.page.done = false;
	data.page.excludedLinks = 0;
	data.page.results = [];
	//data.page.startTime = Date.now();
	data.page.totalLinks = 0;
}

function run(url, checkerOptions, logOptions)
{
	var handlers,instance;
	var data =
	{
		delay: null,
		page: {},
		total:
		{
			brokenLinks: 0,
			excludedLinks: 0,
			links: 0,
			pages: 0,
			startTime: Date.now()
		}
	};

	// In case first page doesn't call "html" handler
	resetPageData(data);

	handlers =
	{
		html: function(tree, robots, response, pageUrl)
		{
			resetPageData(data);

			logPage(data, pageUrl);
		},
		junk: function(result)
		{
			if (logOptions.excludeFilteredLinks === true)
			{
				result.__cli_excluded = true;

				data.page.excludedLinks++;
				data.total.excludedLinks++;
			}

			data.page.totalLinks++;
			data.total.links++;

			pushResult(data, result, logOptions);

			logResults_delayed(data);
		},
		link: function(result)
		{
			// Exclude cached links only if not broken
			if (result.broken===false && result.http.cached===true && logOptions.excludeCachedLinks===true)
			{
				result.__cli_excluded = true;

				data.page.excludedLinks++;
				data.total.excludedLinks++;
			}
			else if (result.broken === true)
			{
				data.page.brokenLinks++;
				data.total.brokenLinks++;
			}

			data.page.totalLinks++;
			data.total.links++;

			pushResult(data, result, logOptions);

			logResults_delayed(data);
		},
		page: function(error, pageUrl)
		{
			if (error != null)
			{
				// "html" handler will not have been called
				logPage(data, pageUrl);

				log( chalk[ error.code!==200 ? "red" : "gray" ](error.name+": "+error.message) );
			}
			else
			{
				data.page.done = true;

				logMetrics_delayed(data.page.brokenLinks, data.page.excludedLinks, data.page.totalLinks);
			}
		},
		end: function()
		{
			if (data.total.pages <= 0)
			{
				process.exit(1);
			}
			else if (data.total.pages === 1)
			{
				process.exit(data.page.done===true && data.total.brokenLinks===0 ? 0 : 1);
			}
			else if (data.total.pages > 1)
			{
				logMetrics_delayed(data.total.brokenLinks, data.total.excludedLinks, data.total.links, Date.now()-data.total.startTime, true, true);
			}
		}
	};

	if (logOptions.recursive !== true)
	{
		instance = new blc.HtmlUrlChecker(checkerOptions, handlers);
	}
	else
	{
		instance = new blc.SiteChecker(checkerOptions, handlers);
	}

	spinner();

	instance.enqueue(url);
}

module.exports = cli;
<commit_msg>Added maxSockets CLI option<commit_after>"use strict";
var blc            = require("./");
var defaultOptions = require("./internal/defaultOptions");
var pkg            = require("../package.json");

var chalk = require("chalk");
var humanizeDuration = require("humanize-duration");
var nopter = require("nopter");
var spinner = require("char-spinner");

process.on("unhandledRejection", function(reason, p) {
	log( nopter.error.fatal("Unhandled Rejection", reason, "Error") );
	process.exit(1);
});

function cli()
{
	var filterLevel = "The types of tags and attributes that are considered links.\n";
	filterLevel += "  0: clickable links\n";
	filterLevel += "  1: 0 + media, iframes, meta refreshes\n";
	filterLevel += "  2: 1 + stylesheets, scripts, forms\n";
	filterLevel += "  3: 2 + metadata\n";
	filterLevel += "  Default: "+defaultOptions.filterLevel;

	this.nopter = new nopter();

	this.nopter.config(
	{
		title: "Broken Link Checker",
		description: pkg.description,
		version: pkg.version,
		name: "blc",
		options:
		{
			"exclude":
			{
				rename: "excludedKeywords",
				info: "A keyword/glob to match links against. Can be used multiple times.",
				type: [String, Array],
				default: defaultOptions.excludedKeywords
			},
			"exclude-external":
			{
				rename: "excludeExternalLinks",
				short: "e",
				info: "Will not check external links.",
				type: Boolean
			},
			"exclude-internal":
			{
				rename: "excludeInternalLinks",
				short: "i",
				info: "Will not check internal links.",
				type: Boolean
			},
			"filter-level":
			{
				info: filterLevel,
				type: Number,
				default: defaultOptions.filterLevel
			},
			"follow":
			{
				rename: "followRobotExclusions",
				short: "f",
				info: "Force-follow robot exclusions.",
				type: Boolean
			},
			"get":
			{
				short: "g",
				info: "Change request method to GET.",
				type: Boolean
			},
			"help":
			{
				short: ["h","?"],
				info: "Display this help text.",
				type: Boolean
			},
			"input":
			{
				info: "URL to an HTML document.",
				type: require("url")
			},
			"host-requests":
			{
				rename: "maxSocketsPerHost",
				info: "Concurrent requests limit per host.",
				type: Number,
				default: defaultOptions.maxSocketsPerHost
			},
			"ordered":
			{
				rename: "maintainLinkOrder",
				short: "o",
				info: "Maintain the order of links as they appear in their HTML document.",
				type: Boolean
			},
			"recursive":
			{
				short: "r",
				info: "Recursively scan (\"crawl\") the HTML document(s).",
				type: Boolean
			},
			"requests":
			{
				rename: "maxSockets",
				info: "Concurrent requests limit.",
				type: Number,
				default: defaultOptions.maxSockets
			},
			"user-agent":
			{
				info: "The user agent to use for link checks.",
				type: String,
				default: defaultOptions.userAgent
			},
			"verbose":
			{
				short: "v",
				info: "Display excluded links.",
				type: Boolean
			},
			"version":
			{
				short: "V",
				info: "Display the app version.",
				type: Boolean
			}
		},
		aliases: ["input"]
	});
}

cli.prototype.input = function(args, showArgs)
{
	//var testing = args !== undefined;
	args = this.nopter.input(args);

	//if (testing===true && showArgs===true) return args;

	if (args.help === true)
	{
		log( this.nopter.help() );
	}
	else if (args.version === true)
	{
		log(pkg.version);
	}
	else if (args.input != null)
	{
		// TODO :: remove this when nopter's default values for Arrays are fixed
		if (args.excludedKeywords === undefined)
		{
			args.excludedKeywords = defaultOptions.excludedKeywords;
		}

		run(args.input,
		{
			excludedKeywords:       args.excludedKeywords,
			excludeExternalLinks:   args.excludeExternalLinks===true,
			excludeInternalLinks:   args.excludeInternalLinks===true,
			excludeLinksToSamePage: args.verbose!==true,
			filterLevel:            args.filterLevel,
			honorRobotExclusions:   args.followRobotExclusions!==true,
			maxSockets:             args.maxSockets,
			maxSocketsPerHost:      args.maxSocketsPerHost,
			requestMethod:          args.get!==true ? "head" : "get",
			userAgent:              args.userAgent
		},
		{
			excludeCachedLinks:   args.verbose!==true,
			excludeFilteredLinks: args.verbose!==true,
			maintainLinkOrder:    args.maintainLinkOrder,
			recursive:            args.recursive
		});
	}
	else
	{
		log( nopter.error.fatal("Input URL required", "Use --help for more options", "Error") );
		process.exit(1);
	}
};

function log()
{
	// Avoid spinner chars getting stuck in the log
	spinner.clear();

	console.log.apply(null, arguments);
}

function logMetrics(brokenLinks, excludedLinks, totalLinks, duration, preBreak, exit)
{
	var output = preBreak===true ? "\n" : "";

	output += chalk.gray("Finished! "+totalLinks+" links found.");

	if (excludedLinks > 0)
	{
		output += chalk.gray(" "+excludedLinks+" excluded.");
	}

	if (totalLinks > 0)
	{
		output += chalk.gray(" ");
		output += chalk[ brokenLinks>0 ? "red" : "green" ](brokenLinks+" broken");
		output += chalk.gray(".");
	}

	if (duration != null)
	{
		output += chalk.gray("\nElapsed time: ");
		output += chalk.gray( humanizeDuration(duration, {round:true, largest:2}) );
	}

	log(output);

	if (exit === true)
	{
		process.exit(brokenLinks===0 ? 0 : 1);
	}
}

/*
	Ensure that `logMetrics()` is called after `logResults_delayed()`.
*/
function logMetrics_delayed(brokenLinks, excludedLinks, totalLinks, duration, preBreak, exit)
{
	setImmediate( function()
	{
		logMetrics(brokenLinks, excludedLinks, totalLinks, duration, preBreak, exit);
	});
}

function logPage(data, pageUrl)
{
	var output = "";

	if (++data.total.pages > 1) output += "\n";

	output += chalk.white("Getting links from: ") + chalk.yellow(pageUrl);

	log(output);
}

function logResult(result, finalResult)
{
	var output = "";

	if (result.__cli_excluded !== true)
	{
		// TODO :: if later results are skipped, the last RENDERED result will not be "└─"
		output = chalk.gray( finalResult!==true ? "├─" : "└─" );

		if (result.broken === true)
		{
			output += chalk.red("BROKEN");
			output += chalk.gray("─ ");
		}
		else if (result.excluded === true)
		{
			output += chalk.gray("─SKIP── ");
		}
		else
		{
			output += chalk.gray("──");
			output += chalk.green("OK");
			output += chalk.gray("─── ");
		}

		if (result.url.resolved != null)
		{
			output += chalk.yellow( result.url.resolved );
		}
		else
		{
			// Excluded scheme
			output += chalk.yellow( result.url.original );
		}

		if (result.broken === true)
		{
			output += chalk.gray(" ("+ result.brokenReason +")");
		}
		else if (result.excluded === true)
		{
			output += chalk.gray(" ("+ result.excludedReason +")");
		}
		// Don't display cached message if broken/excluded message is displayed
		else if (result.http.cached === true)
		{
			output += chalk.gray(" (CACHED)");
		}
	}

	return output;
}

/*
	Logs links in the order that they are found in their containing HTML
	document, even if later links receive an earlier response.
*/
function logResults(data)
{
	var done,output,result;
	var nextIsReady = true;

	while (nextIsReady)
	{
		result = data.page.results[data.page.currentIndex];

		if (result !== undefined)
		{
			done = data.page.done===true && data.page.currentIndex>=data.page.results.length-1;

			output = logResult(result, done);

			if (output !== "") log(output);
			if (done === true) return;

			data.page.currentIndex++;
		}
		else
		{
			nextIsReady = false;
		}
	}
}

/*
	Ensure that `logResults()` is called after `data.page.done=true`.
*/
function logResults_delayed(data)
{
	// Avoid more than one delay via multiple synchronous iterations
	if (data.delay === null)
	{
		data.delay = setImmediate( function()
		{
			logResults(data);
			data.delay = null;
		});
	}
}

function pushResult(data, result, options)
{
	if (options.maintainLinkOrder === true)
	{
		data.page.results[result.html.index] = result;
	}
	else
	{
		data.page.results.push(result);
	}
}

function resetPageData(data)
{
	data.page.brokenLinks = 0;
	data.page.currentIndex = 0;
	data.page.done = false;
	data.page.excludedLinks = 0;
	data.page.results = [];
	//data.page.startTime = Date.now();
	data.page.totalLinks = 0;
}

function run(url, checkerOptions, logOptions)
{
	var handlers,instance;
	var data =
	{
		delay: null,
		page: {},
		total:
		{
			brokenLinks: 0,
			excludedLinks: 0,
			links: 0,
			pages: 0,
			startTime: Date.now()
		}
	};

	// In case first page doesn't call "html" handler
	resetPageData(data);

	handlers =
	{
		html: function(tree, robots, response, pageUrl)
		{
			resetPageData(data);

			logPage(data, pageUrl);
		},
		junk: function(result)
		{
			if (logOptions.excludeFilteredLinks === true)
			{
				result.__cli_excluded = true;

				data.page.excludedLinks++;
				data.total.excludedLinks++;
			}

			data.page.totalLinks++;
			data.total.links++;

			pushResult(data, result, logOptions);

			logResults_delayed(data);
		},
		link: function(result)
		{
			// Exclude cached links only if not broken
			if (result.broken===false && result.http.cached===true && logOptions.excludeCachedLinks===true)
			{
				result.__cli_excluded = true;

				data.page.excludedLinks++;
				data.total.excludedLinks++;
			}
			else if (result.broken === true)
			{
				data.page.brokenLinks++;
				data.total.brokenLinks++;
			}

			data.page.totalLinks++;
			data.total.links++;

			pushResult(data, result, logOptions);

			logResults_delayed(data);
		},
		page: function(error, pageUrl)
		{
			if (error != null)
			{
				// "html" handler will not have been called
				logPage(data, pageUrl);

				log( chalk[ error.code!==200 ? "red" : "gray" ](error.name+": "+error.message) );
			}
			else
			{
				data.page.done = true;

				logMetrics_delayed(data.page.brokenLinks, data.page.excludedLinks, data.page.totalLinks);
			}
		},
		end: function()
		{
			if (data.total.pages <= 0)
			{
				process.exit(1);
			}
			else if (data.total.pages === 1)
			{
				process.exit(data.page.done===true && data.total.brokenLinks===0 ? 0 : 1);
			}
			else if (data.total.pages > 1)
			{
				logMetrics_delayed(data.total.brokenLinks, data.total.excludedLinks, data.total.links, Date.now()-data.total.startTime, true, true);
			}
		}
	};

	if (logOptions.recursive !== true)
	{
		instance = new blc.HtmlUrlChecker(checkerOptions, handlers);
	}
	else
	{
		instance = new blc.SiteChecker(checkerOptions, handlers);
	}

	spinner();

	instance.enqueue(url);
}

module.exports = cli;
<|endoftext|>


<commit_before>var _ = require('lodash');

function Encoder2() {
    var COMPONENT_SEPARATOR, REPEAT_SEPARATOR, DEFAULT_SEPARATORS, FIELD_SEPARATOR, NEW_LINE;

    FIELD_SEPARATOR = '|';
    COMPONENT_SEPARATOR = '^';
    REPEAT_SEPARATOR = '~';
    NEW_LINE = '\r';
    DEFAULT_SEPARATORS = "" + COMPONENT_SEPARATOR + REPEAT_SEPARATOR + "\\&";

    function Encoder2(options) {
        if (options != null ? options.NEW_LINE : void 0) {
            NEW_LINE = options.NEW_LINE;
        }
    }

    var encode = function(message) {
        var encodedMessage, segments;
        segments = encodeAllObjectPropertiesAsSegments(message);
        encodedMessage = segments.join(NEW_LINE);
        return encodedMessage;
    };

    var encodeAllObjectPropertiesAsSegments = function(object) {
        var segmentName, value, _results;
        _results = [];
        for (segmentName in object) {
            if (segmentName === '$'){
                continue;
            }
            if (!_.isArray(object[segmentName])){
                object[segmentName] = [object[segmentName]];
            }
            _.each(object[segmentName], function(value){
                _results.push(encodeSegment(segmentName, value));
            });
        }
        return _results;
    };

    var encodeSegment = function(name, segment) {
        if (name === 'MSH') {
            if (!segment['MSH.1']) {
                segment['MSH.1'] = DEFAULT_SEPARATORS;
            }
        }
        segment = name + joinAllFieldsInSparseArrayIntoASingleLine(segment, name);
        return segment;
    };

    var joinAllFieldsInSparseArrayIntoASingleLine = function(fields, name) {
        if (_.isString(fields)){
            return fields;
        }

        var len = _(fields).keys().max(function (k){ return parseInt(k.split('.').pop(), 10);});
        len = parseInt((len+"").split('.').pop(),10);
        var result = '';
        if (!len || len < 2) {
            return result;
        }

        for (var i = 1; i <= len; i++){
            result += FIELD_SEPARATOR;
            result += encodeField(fields[name + '.' + i], name + '.' + i);
        }
        return result;
    };

    var encodeField = function(value, name) {
        var result = [];
        if (!value) {
            return '';
        }
        if (_.isString(value)) {
            return value;
        }else{
            _.each(value, function (val){
                var len = _(val).keys().max(function (k){ return parseInt(k.split('.').pop(), 10);});
                len = parseInt((len+"").split('.').pop(),10);
                for (var i = 1; i <= len; i++) {
                    if (_.isString(val[name + '.' + i]) || !val[name + '.' + i]){
                        result.push(val[name + '.' + i] || "");
                    }else{
                        result.push(_.map(val[name + '.' + i], function (subvalue){
                            return subvalue;
                        }).join(REPEAT_SEPARATOR));
                    }
                }
            });
            return result.join(COMPONENT_SEPARATOR);
        }
    };

    return {
        encode: encode,
        encodeSegment: encodeSegment,
        encodeField: encodeField
    };

};

module.exports = Encoder2;
<commit_msg>adding subseperator support<commit_after>var _ = require('lodash');

function Encoder2() {
    var COMPONENT_SEPARATOR, SUBCOMPONENT_SEPARATOR, REPEAT_SEPARATOR, DEFAULT_SEPARATORS, FIELD_SEPARATOR, NEW_LINE;

    FIELD_SEPARATOR = '|';
    COMPONENT_SEPARATOR = '^';
    SUBCOMPONENT_SEPARATOR = '&';
    REPEAT_SEPARATOR = '~';
    NEW_LINE = '\r';
    DEFAULT_SEPARATORS = "" + COMPONENT_SEPARATOR + REPEAT_SEPARATOR + "\\" + SUBCOMPONENT_SEPARATOR;

    function Encoder2(options) {
        if (options != null ? options.NEW_LINE : void 0) {
            NEW_LINE = options.NEW_LINE;
        }
    }

    var encode = function(message) {
        var encodedMessage, segments;
        segments = encodeAllObjectPropertiesAsSegments(message);
        encodedMessage = segments.join(NEW_LINE);
        return encodedMessage;
    };

    var encodeAllObjectPropertiesAsSegments = function(object) {
        var segmentName, value, _results;
        _results = [];
        for (segmentName in object) {
            if (segmentName === '$'){
                continue;
            }
            if (!_.isArray(object[segmentName])){
                object[segmentName] = [object[segmentName]];
            }
            _.each(object[segmentName], function(value){
                _results.push(encodeSegment(segmentName, value));
            });
        }
        return _results;
    };

    var encodeSegment = function(name, segment) {
        if (name === 'MSH') {
            if (!segment['MSH.2']) {
                segment['MSH.2'] = DEFAULT_SEPARATORS;
            }
            return name + joinAllFieldsInSparseArrayIntoASingleLine(segment, name);
        }else{
            return name + FIELD_SEPARATOR + joinAllFieldsInSparseArrayIntoASingleLine(segment, name);
        }
    };

    var joinAllFieldsInSparseArrayIntoASingleLine = function(fields, name) {
        if (_.isString(fields)){
            return fields;
        }

        var len = _(fields).keys().max(function (k){ return parseInt(k.split('.').pop(), 10);});
        len = parseInt((len+"").split('.').pop(),10);
        var result = '';
        if (!len || len < 2) {
            return result;
        }

        for (var i = 1; i <= len; i++){
            result += encodeField(fields[name + '.' + i], name + '.' + i);
            result += FIELD_SEPARATOR;
        }
        return result;
    };

    var encodeField = function(value, name) {
        var result = [];
        if (!value) {
            return '';
        }
        if (_.isString(value)) {
            return value;
        }else{
            _.each(value, function (val){
                console.log(val);
                var len = _(val).keys().max(function (k){ return parseInt(k.split('.').pop(), 10);});
                len = parseInt((len+"").split('.').pop(),10);
                var components = [];

                for (var i = 1; i <= len; i++) {
                    if (_.isString(val[name + '.' + i]) || !val[name + '.' + i]){
                        components.push(val[name + '.' + i] || "");
                    }else{
                        components.push(_.map(val[name + '.' + i], function (subvalue){
                            return subvalue;
                        }).join(SUBCOMPONENT_SEPARATOR));
                    }
                }
                result.push(components.join(COMPONENT_SEPARATOR));

            });
            return result.join(REPEAT_SEPARATOR);
        }
    };

    return {
        encode: encode,
        encodeSegment: encodeSegment,
        encodeField: encodeField
    };

};

module.exports = Encoder2;
<|endoftext|>