<commit_before>/**
 * Pimcore
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.pimcore.org/license
 *
 * @copyright  Copyright (c) 2009-2014 pimcore GmbH (http://www.pimcore.org)
 * @license    http://www.pimcore.org/license     New BSD License
 */

pimcore.registerNS("pimcore.settings.translations");
pimcore.settings.translations = Class.create({

    filterField: null,
    preconfiguredFilter: "",

    initialize: function (filter) {

        this.filterField = new Ext.form.TextField({
            xtype: "textfield",
            width: 200,
            style: "margin: 0 10px 0 0;",
            enableKeyEvents: true,
            value: this.preconfiguredFilter,
            listeners: {
                "keydown" : function (field, key) {
                    if (key.getKey() == key.ENTER) {
                        var input = field;
                        this.store.baseParams.filter = input.getValue();
                        this.store.load();
                    }
                }.bind(this)
            }
        });

        this.preconfiguredFilter = filter;
        this.filterField.setValue(filter);
        this.getAvailableLanguages();
    },

    getRowEditor: function () {

        var StateFullProvider = Ext.extend(Ext.state.Provider, {
            namespace: "default",

            constructor : function(config){
                StateFullProvider.superclass.constructor.call(this);
                Ext.apply(this, config);

                var data = localStorage.getItem(this.namespace);
                if (!data) {
                    this.state = {};
                } else {
                    data = JSON.parse(data);
                    if (data.state && data.user == pimcore.currentuser.id) {
                        this.state = data.state;
                    } else {
                        this.state = {};
                    }
                }
            },

            get : function(name, defaultValue){
                try {
                    if (typeof this.state[name] == "undefined") {
                        return defaultValue
                    } else {
                        return this.decodeValue(this.state[name])
                    }
                } catch (e) {
                    this.clear(name);
                    return defaultValue;
                }
            },
            set : function(name, value){
                try {
                    if (typeof value == "undefined" || value === null) {
                        this.clear(name);
                        return;
                    }
                    this.state[name] = this.encodeValue(value)

                    var data = {
                        state: this.state,
                        user: pimcore.currentuser.id
                    };
                    var json = JSON.stringify(data);

                    localStorage.setItem(this.namespace, json);
                } catch (e) {
                    this.clear(name);
                }

                this.fireEvent("statechange", this, name, value);
            }
        });

        var provider = new StateFullProvider({
            namespace : "pimcore_ui_states"
        });

        Ext.state.Manager.setProvider(provider);

        var stateId = "tr_" + this.translationType;
        var applyInitialSettings = false;
        var showInfo = false;
        var state = provider.get(stateId, null);
        var languages = this.languages;

        var maxCols = 7;   // include creation date / modification date / action column)
        var maxLanguages = maxCols - 3;

        if (state == null) {
            applyInitialSettings = true;
            if (languages.length > maxLanguages) {
                showInfo = true;
            }
        } else {
            if (state.columns) {
                for (var i = 0; i < state.columns.length; i++) {
                    var colState = state.columns[i];
                    if (colState.hidden) {
                        showInfo = true;
                        break;
                    }
                }
            }
        }

        var proxy = new Ext.data.HttpProxy({
            url: this.dataUrl,
            method: 'post'
        });

        var readerFields = [
            {name: 'key', allowBlank: false},
            {name: 'creationDate', allowBlank: true},
            {name: 'modificationDate', allowBlank: true}
        ];

        var typesColumns = [
            {header: t("key"), sortable: true, dataIndex: 'key', editable: false}

        ];

        for (var i = 0; i < languages.length; i++) {

            readerFields.push({name: languages[i]});
            var columnConfig = {header: languages[i].toUpperCase(), sortable: false, dataIndex: languages[i],
                editor: new Ext.form.TextField({})};
            if (applyInitialSettings) {
                var hidden = i > maxLanguages;
                columnConfig.hidden = hidden;
            }

            typesColumns.push(columnConfig);
        }

        if (showInfo) {
            pimcore.helpers.showNotification(t("info"), t("there_are_more_columns"), null, null, 2000);
        }

        typesColumns.push({header: t("creationDate"), sortable: true, dataIndex: 'creationDate', editable: false,
            renderer: function(d) {
                var date = new Date(d * 1000);
                return date.format("Y-m-d H:i:s");
            }});
        typesColumns.push({header: t("modificationDate"), sortable: true, dataIndex: 'modificationDate', editable: false,
            renderer: function(d) {
                var date = new Date(d * 1000);
                return date.format("Y-m-d H:i:s");
            }});

        typesColumns.push({
            xtype: 'actioncolumn',
            width: 30,
            items: [{
                tooltip: t('delete'),
                icon: "/pimcore/static/img/icon/cross.png",
                handler: function (grid, rowIndex) {
                    grid.getStore().removeAt(rowIndex);
                }.bind(this)
            }]
        });

        var reader = new Ext.data.JsonReader({
            totalProperty: 'total',
            successProperty: 'success',
            root: 'data',
            idProperty: 'key'
        }, readerFields);

        var writer = new Ext.data.JsonWriter();

        var itemsPerPage = 20;
        this.store = new Ext.data.Store({
            id: 'translation_store',
            restful: false,
            proxy: proxy,
            reader: reader,
            writer: writer,
            remoteSort: true,
            baseParams: {
                limit: itemsPerPage,
                filter: this.preconfiguredFilter
            },
            listeners: {
                write : function(store, action, result, response, rs) {
                }
            }
        });
        this.store.load();

        this.pagingtoolbar = new Ext.PagingToolbar({
            pageSize: itemsPerPage,
            store: this.store,
            displayInfo: true,
            displayMsg: '{0} - {1} / {2}',
            emptyMsg: t("no_items_found")
        });

        // add per-page selection
        this.pagingtoolbar.add("-");

        this.pagingtoolbar.add(new Ext.Toolbar.TextItem({
            text: t("items_per_page")
        }));
        this.pagingtoolbar.add(new Ext.form.ComboBox({
            store: [
                [10, "10"],
                [20, "20"],
                [40, "40"],
                [60, "60"],
                [80, "80"],
                [100, "100"]
            ],
            mode: "local",
            width: 50,
            value: 20,
            triggerAction: "all",
            listeners: {
                select: function (box, rec, index) {
                    this.pagingtoolbar.pageSize = intval(rec.data.field1);
                    this.pagingtoolbar.moveFirst();
                }.bind(this)
            }
        }));

        this.grid = new Ext.grid.EditorGridPanel({
            frame: false,
            autoScroll: true,
            store: this.store,
            columnLines: true,
            stripeRows: true,
            columns : typesColumns,
            trackMouseOver: true,
            bbar: this.pagingtoolbar,
            stateful: true,
            stateId: stateId,
            stateEvents: ['columnmove', 'columnresize', 'sortchange', 'groupchange'],
            sm: new Ext.grid.RowSelectionModel({singleSelect:true}),
            tbar: [
                {
                    text: t('add'),
                    handler: this.onAdd.bind(this),
                    iconCls: "pimcore_icon_add"
                },
                '-',{
                    text: this.getHint(),
                    xtype: "tbtext",
                    style: "margin: 0 10px 0 0;"
                },
                "->",
                {
                    text: t('cleanup'),
                    handler: this.cleanup.bind(this),
                    iconCls: "pimcore_icon_cleanup"
                },{
                    text: t('import_csv'),
                    handler: this.doImport.bind(this),
                    iconCls: "pimcore_icon_import"
                },
                '-',
                {
                    text: t('export_csv'),
                    handler: this.doExport.bind(this),
                    iconCls: "pimcore_icon_export"
                },'-',{
                    text: t("filter") + "/" + t("search"),
                    xtype: "tbtext",
                    style: "margin: 0 10px 0 0;"
                },this.filterField
            ],
            viewConfig: {
                forceFit: true
            }
        });

        return this.grid;
    },

    doImport:function(){
        pimcore.helpers.uploadDialog(this.importUrl, "Filedata", function() {
            this.store.reload();
        }.bind(this), function () {
            Ext.MessageBox.alert(t("error"), t("error"));
        });
    },

    doExport:function(){

        if(this.filterField.getValue()) {
            Ext.MessageBox.confirm("", t("filter_active_message"), function (buttonValue) {
                if (buttonValue == "yes") {
                    window.open(Ext.urlAppend(this.exportUrl, "filter=" + this.filterField.getValue()));
                } else {
                    window.open(this.exportUrl);
                }
            }.bind(this));
        } else {
            window.open(this.exportUrl);
        }
    },

    onAdd: function (btn, ev) {

        Ext.MessageBox.prompt("", t("please_enter_the_new_name"), function (button, value) {
            if(button == "ok") {
                var u = new this.grid.store.recordType();
                u.set("key", value);
                u.markDirty();

                this.grid.store.insert(0, u);
            }

        }.bind(this));
    },

    cleanup: function () {
        Ext.Ajax.request({
            url: this.cleanupUrl,
            success: function (response) {
                this.store.reload();
            }.bind(this)
        });
    }
});

<commit_msg>PIMCORE-2509 Optimize shared translations view when having dozends of languages activated  - improvement - load store data after grid is configured - this also conserves the sort options<commit_after>/**
 * Pimcore
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.pimcore.org/license
 *
 * @copyright  Copyright (c) 2009-2014 pimcore GmbH (http://www.pimcore.org)
 * @license    http://www.pimcore.org/license     New BSD License
 */

pimcore.registerNS("pimcore.settings.translations");
pimcore.settings.translations = Class.create({

    filterField: null,
    preconfiguredFilter: "",

    initialize: function (filter) {

        this.filterField = new Ext.form.TextField({
            xtype: "textfield",
            width: 200,
            style: "margin: 0 10px 0 0;",
            enableKeyEvents: true,
            value: this.preconfiguredFilter,
            listeners: {
                "keydown" : function (field, key) {
                    if (key.getKey() == key.ENTER) {
                        var input = field;
                        this.store.baseParams.filter = input.getValue();
                        this.store.load();
                    }
                }.bind(this)
            }
        });

        this.preconfiguredFilter = filter;
        this.filterField.setValue(filter);
        this.getAvailableLanguages();
    },

    getRowEditor: function () {

        var StateFullProvider = Ext.extend(Ext.state.Provider, {
            namespace: "default",

            constructor : function(config){
                StateFullProvider.superclass.constructor.call(this);
                Ext.apply(this, config);

                var data = localStorage.getItem(this.namespace);
                if (!data) {
                    this.state = {};
                } else {
                    data = JSON.parse(data);
                    if (data.state && data.user == pimcore.currentuser.id) {
                        this.state = data.state;
                    } else {
                        this.state = {};
                    }
                }
            },

            get : function(name, defaultValue){
                try {
                    if (typeof this.state[name] == "undefined") {
                        return defaultValue
                    } else {
                        return this.decodeValue(this.state[name])
                    }
                } catch (e) {
                    this.clear(name);
                    return defaultValue;
                }
            },
            set : function(name, value){
                try {
                    if (typeof value == "undefined" || value === null) {
                        this.clear(name);
                        return;
                    }
                    this.state[name] = this.encodeValue(value)

                    var data = {
                        state: this.state,
                        user: pimcore.currentuser.id
                    };
                    var json = JSON.stringify(data);

                    localStorage.setItem(this.namespace, json);
                } catch (e) {
                    this.clear(name);
                }

                this.fireEvent("statechange", this, name, value);
            }
        });

        var provider = new StateFullProvider({
            namespace : "pimcore_ui_states"
        });

        Ext.state.Manager.setProvider(provider);

        var stateId = "tr_" + this.translationType;
        var applyInitialSettings = false;
        var showInfo = false;
        var state = provider.get(stateId, null);
        var languages = this.languages;

        var maxCols = 7;   // include creation date / modification date / action column)
        var maxLanguages = maxCols - 3;

        if (state == null) {
            applyInitialSettings = true;
            if (languages.length > maxLanguages) {
                showInfo = true;
            }
        } else {
            if (state.columns) {
                for (var i = 0; i < state.columns.length; i++) {
                    var colState = state.columns[i];
                    if (colState.hidden) {
                        showInfo = true;
                        break;
                    }
                }
            }
        }

        var proxy = new Ext.data.HttpProxy({
            url: this.dataUrl,
            method: 'post'
        });

        var readerFields = [
            {name: 'key', allowBlank: false},
            {name: 'creationDate', allowBlank: true},
            {name: 'modificationDate', allowBlank: true}
        ];

        var typesColumns = [
            {header: t("key"), sortable: true, dataIndex: 'key', editable: false}

        ];

        for (var i = 0; i < languages.length; i++) {

            readerFields.push({name: languages[i]});
            var columnConfig = {header: languages[i].toUpperCase(), sortable: false, dataIndex: languages[i],
                editor: new Ext.form.TextField({})};
            if (applyInitialSettings) {
                var hidden = i > maxLanguages;
                columnConfig.hidden = hidden;
            }

            typesColumns.push(columnConfig);
        }

        if (showInfo) {
            pimcore.helpers.showNotification(t("info"), t("there_are_more_columns"), null, null, 2000);
        }

        typesColumns.push({header: t("creationDate"), sortable: true, dataIndex: 'creationDate', editable: false,
            renderer: function(d) {
                var date = new Date(d * 1000);
                return date.format("Y-m-d H:i:s");
            }});
        typesColumns.push({header: t("modificationDate"), sortable: true, dataIndex: 'modificationDate', editable: false,
            renderer: function(d) {
                var date = new Date(d * 1000);
                return date.format("Y-m-d H:i:s");
            }});

        typesColumns.push({
            xtype: 'actioncolumn',
            width: 30,
            items: [{
                tooltip: t('delete'),
                icon: "/pimcore/static/img/icon/cross.png",
                handler: function (grid, rowIndex) {
                    grid.getStore().removeAt(rowIndex);
                }.bind(this)
            }]
        });

        var reader = new Ext.data.JsonReader({
            totalProperty: 'total',
            successProperty: 'success',
            root: 'data',
            idProperty: 'key'
        }, readerFields);

        var writer = new Ext.data.JsonWriter();

        var itemsPerPage = 20;
        this.store = new Ext.data.Store({
            id: 'translation_store',
            restful: false,
            proxy: proxy,
            reader: reader,
            writer: writer,
            remoteSort: true,
            baseParams: {
                limit: itemsPerPage,
                filter: this.preconfiguredFilter
            },
            listeners: {
                write : function(store, action, result, response, rs) {
                }
            }
        });

        this.pagingtoolbar = new Ext.PagingToolbar({
            pageSize: itemsPerPage,
            store: this.store,
            displayInfo: true,
            displayMsg: '{0} - {1} / {2}',
            emptyMsg: t("no_items_found")
        });

        // add per-page selection
        this.pagingtoolbar.add("-");

        this.pagingtoolbar.add(new Ext.Toolbar.TextItem({
            text: t("items_per_page")
        }));
        this.pagingtoolbar.add(new Ext.form.ComboBox({
            store: [
                [10, "10"],
                [20, "20"],
                [40, "40"],
                [60, "60"],
                [80, "80"],
                [100, "100"]
            ],
            mode: "local",
            width: 50,
            value: 20,
            triggerAction: "all",
            listeners: {
                select: function (box, rec, index) {
                    this.pagingtoolbar.pageSize = intval(rec.data.field1);
                    this.pagingtoolbar.moveFirst();
                }.bind(this)
            }
        }));

        this.grid = new Ext.grid.EditorGridPanel({
            frame: false,
            autoScroll: true,
            store: this.store,
            columnLines: true,
            stripeRows: true,
            columns : typesColumns,
            trackMouseOver: true,
            bbar: this.pagingtoolbar,
            stateful: true,
            stateId: stateId,
            stateEvents: ['columnmove', 'columnresize', 'sortchange', 'groupchange'],
            sm: new Ext.grid.RowSelectionModel({singleSelect:true}),
            tbar: [
                {
                    text: t('add'),
                    handler: this.onAdd.bind(this),
                    iconCls: "pimcore_icon_add"
                },
                '-',{
                    text: this.getHint(),
                    xtype: "tbtext",
                    style: "margin: 0 10px 0 0;"
                },
                "->",
                {
                    text: t('cleanup'),
                    handler: this.cleanup.bind(this),
                    iconCls: "pimcore_icon_cleanup"
                },{
                    text: t('import_csv'),
                    handler: this.doImport.bind(this),
                    iconCls: "pimcore_icon_import"
                },
                '-',
                {
                    text: t('export_csv'),
                    handler: this.doExport.bind(this),
                    iconCls: "pimcore_icon_export"
                },'-',{
                    text: t("filter") + "/" + t("search"),
                    xtype: "tbtext",
                    style: "margin: 0 10px 0 0;"
                },this.filterField
            ],
            viewConfig: {
                forceFit: true
            }
        });

        this.store.load();

        return this.grid;
    },

    doImport:function(){
        pimcore.helpers.uploadDialog(this.importUrl, "Filedata", function() {
            this.store.reload();
        }.bind(this), function () {
            Ext.MessageBox.alert(t("error"), t("error"));
        });
    },

    doExport:function(){

        if(this.filterField.getValue()) {
            Ext.MessageBox.confirm("", t("filter_active_message"), function (buttonValue) {
                if (buttonValue == "yes") {
                    window.open(Ext.urlAppend(this.exportUrl, "filter=" + this.filterField.getValue()));
                } else {
                    window.open(this.exportUrl);
                }
            }.bind(this));
        } else {
            window.open(this.exportUrl);
        }
    },

    onAdd: function (btn, ev) {

        Ext.MessageBox.prompt("", t("please_enter_the_new_name"), function (button, value) {
            if(button == "ok") {
                var u = new this.grid.store.recordType();
                u.set("key", value);
                u.markDirty();

                this.grid.store.insert(0, u);
            }

        }.bind(this));
    },

    cleanup: function () {
        Ext.Ajax.request({
            url: this.cleanupUrl,
            success: function (response) {
                this.store.reload();
            }.bind(this)
        });
    }
});

<|endoftext|>


<commit_before>'use strict';

exports.name = 'nue';
exports.version = '0.5.0';
exports.flow = flow;
exports.parallel = parallel;
exports.as = as;

var util = require('util');
var assert = require('assert');
var debugMode = process.env.NODE_DEBUG && /\bnue\b/.test(process.env.NODE_DEBUG);
var flowId = 0;

function flow() {
  if (arguments.length === 1 && typeof arguments[0] === 'string') {
    var flowName = arguments[0];
    return function () {
      return  deferFlow(flowName, normalizeArgs(arguments));
    };
  } else {
    return deferFlow('', normalizeArgs(arguments));
  }

  function deferFlow(flowName, functions) {
    function startFlow() {
      var flow = {
        flowName: flowName,
        flowId: flowId++,
        context: this,
        isTopLevel: !(this instanceof StepContext),
        data: {},
        err: null
      };
      var steps = functions.length > 0 ? functions : [function () { this.next(); }];
      var head = chainSteps(flow, steps);
      head.apply(this, arguments);
    }
    startFlow.stepName = flowName;
    return startFlow;
  }

  function chainSteps(flow, steps) {
    var len = steps.length;
    var lastIndex = len - 1;
    var last = makeLastStep(steps[lastIndex], lastIndex);
    var isThrown = false;
    if (len === 1) {
      return last;
    }

    return steps.reduceRight(function chain(prev, curr, index) {
      assert.equal(typeof curr, 'function', 'Each argument for `flow` must be a function.');
      return function step() {
        var next = index === len - 2 ? last : prev;
        var context = new StepContext(flow, curr.stepName || curr.name, index, next, last);
        try {
          runStep(curr, context, arguments);
        } catch (e) {
          if (isThrown) {
            throw e;
          }
          StepContext.prototype.endWith.call(context, e);
        }
      };
    });

    function makeLastStep(fn, index) {
      assert.equal(typeof fn, 'function', 'Each argument for `flow` must be a function.');
      return function lastStep() {
        var context = new StepContext(flow, fn.stepName || fn.name, index, exit, exit);
        try {
          runStep(fn, context, arguments, true);
        } catch (e) {
          isThrown = true;
          throw e;
        }
      };

      function exit() {
        if (flow.isTopLevel) {
          if (flow.err) {
            var message = 'An error must be handled in a last step of its flow. ' +
              'To indicate error handling completed, set null to `this.err` before exiting the last step. ' +
              'cause: ' + util.inspect(flow.err);
            var err = new Error(message);
            err.name = 'NueUnhandledError';
            throw err;
          }
        } else {
          if (flow.err) {
            flow.context.endWith.call(flow.context, flow.err);
          } else {
            flow.context.next.apply(flow.context, arguments);
          }
        }
      }
    }

    function runStep(step, context, args) {
      if (debugMode) {
        debug('BEGIN flow: %s(%d), step: %s(%d), args:',
          context.flowName || '<anonymous>', context._flow.flowId,
          context.stepName || '<anonymous>', context.stepIndex, Array.prototype.slice.call(args));
      }
      try {
        step.apply(context, args);
      } catch (e) {
        if (debugMode) {
          debug('ERROR flow: %s(%d), step: %s(%d), err:',
            context.flowName || '<anonymous>', context._flow.flowId,
            context.stepName || '<anonymous>', context.stepIndex, e);
        }
        throw e;
      }
    }
  }
}

function parallel() {
  if (arguments.length === 1 && typeof arguments[0] === 'string') {
    var flowName = arguments[0];
    return function () {
      return deferParallel(flowName, normalizeArgs(arguments));
    };
  } else {
    return deferParallel('', normalizeArgs(arguments));
  }

  function deferParallel(flowName, functions) {
    function startParallel() {
      assert(this instanceof StepContext, 'A `parallel` must be inside a flow or another parallel.');
      var self = this;
      var args = Array.prototype.slice.call(arguments);
      self.asyncEach(1)(functions, function (fn, group) {
        assert.equal(typeof fn, 'function', 'Each argument for `parallel` must be a function.');
        var callback = group.async(As.ALL);
        var end = function end() {
          if (this.err) {
            self.endWith(this.err);
          } else {
            callback.apply(null, arguments);
          }
        };
        end.stepName = (fn.stepName || fn.name) + '_end';
        flow(flowName)(fn, end).apply(self, args);
      });
    }
    startParallel.stepName = flowName;
    return startParallel;
  }
}

function as(index) {
  return new As(index);
}

function normalizeArgs(args) {
  return (args.length === 1 && Array.isArray(args[0])) ? args[0] : Array.prototype.slice.call(args);
}

function getLocation(target) {
  var originalPrepareStackTrace = Error.prepareStackTrace;
  var originalStackTraceLimit = Error.stackTraceLimit;
  Error.prepareStackTrace = prepareStackTrace;
  Error.stackTraceLimit = 1;
  var err = {};
  Error.captureStackTrace(err, target);
  var stack = err.stack;
  Error.prepareStackTrace = originalPrepareStackTrace;
  Error.stackTraceLimit = originalStackTraceLimit;
  return {
    functionName: stack.getFunctionName(),
    fileName: stack.getFileName(),
    lineNumber: stack.getLineNumber(),
    columnNumber: stack.getColumnNumber()
  };

  function prepareStackTrace() {
    return arguments[1][0];
  }
}

function debug() {
  var args = Array.prototype.slice.call(arguments);
  args[0] = 'NUE ' + args[0];
  console.error.apply(console, args);
}

function As(index) {
  this.index = index;
}

As.ALL = {description: 'all'};

function StepContext(flow, name, index, next, last) {
  this._flow = flow;
  this._next = next;
  this._last = last;
  this._async = new Async(false, this.next.bind(this), this.endWith.bind(this));
  this.err = flow.err;
  this.data = flow.data;
  this.flowName = flow.flowName;
  this.stepName = name;
  this.stepIndex = index;
}

StepContext.DEFAULT_CONCURRENCY = 10;

StepContext.noop = function noop() {};

StepContext.prototype._disable = function _disable() {
  this.next = StepContext.noop;
  this.end = StepContext.noop;
  this.endWith = StepContext.noop;
};

StepContext.prototype.next = function next() {
  this._disable();
  this._flow.err = this.err;
  this._next.apply(null, arguments);
};

StepContext.prototype.end = function end() {
  this._disable();
  this._flow.err = this.err;
  this._last.apply(null, arguments);
};

StepContext.prototype.endWith = function endWith(err) {
  this._disable();
  this._flow.err = err;
  this._last.call(null);
};

StepContext.prototype.async = function async(mapping) {
  return this._async.makeCallback.call(this._async, mapping, async);
};

StepContext.prototype.asyncEach = function asyncEach() {
  var self = this;
  var callback = self.async(As.ALL);
  var asyncObj = new Async(true, callback.bind(self), self.endWith.bind(self));
  var group = {
    async: function async(mapping) {
      return asyncObj.makeCallback.call(asyncObj, mapping, async);
    }
  };

  if (arguments.length === 1 && typeof arguments[0] === 'number') {
    var concurrency = arguments[0];
    return function () {
      validateAndStart(concurrency, arguments[0], arguments[1]);
    };
  } else {
    validateAndStart(StepContext.DEFAULT_CONCURRENCY, arguments[0], arguments[1]);
  }

  function validateAndStart(concurrency, array, worker) {
    assert(Array.isArray(array), 'An argument `array` must be an array.');
    assert.equal(typeof worker, 'function', 'An argument `worker` must be a function.');
    process.nextTick(function startEach() {
      each(concurrency, array, worker, 0);
    });
  }

  function each(concurrency, array, worker, index) {
    var len = array.length;
    for (var i = 0; i < concurrency && index < len; i++, index++) {
      worker.call(self, array[index], group, index, array);
    }
    if (index === len) {
      process.nextTick(asyncObj.makeCallback(Async.SIGNAL_UNLOCK));
    } else {
      process.nextTick(function nextEach() {
        each(concurrency, array, worker, index);
      });
    }
  }
};

StepContext.prototype.exec = function exec(fn) {
  assert(arguments.length > 1, 'Arguments length must be more than 1.');
  assert.equal(typeof fn, 'function', 'The first argument must be a function.');
  assert.equal(typeof arguments[arguments.length - 1], 'function', 'The last argument must be a function.');
  var self = this;
  var callback = arguments[arguments.length - 1];
  var args = Array.prototype.slice.call(arguments, 1, arguments.length - 1);
  var end = function end() {
    callback.apply(null, [this.err].concat(Array.prototype.slice.call(arguments)));
  };
  end.stepName = (fn.stepName || fn.name) + '_end';
  process.nextTick(function execFlow() {
    flow('exec')(fn, end).apply(self, args);
  });
};

function Async(lock, next, endWith) {
  this.lock = lock;
  this.next = next;
  this.endWith = endWith;
  this.index = 0;
  this.pending = 0;
  this.isCanceled = false;
  this.results = [];
}

Async.SIGNAL_UNLOCK = {description: 'signal_unlock'};

Async.prototype.makeCallback = function makeCallback(mapping, caller) {
  assert(typeof mapping === 'object' && mapping !== null, 'An argument `mapping` must be an object');
  this.pending++;
  if (this.index === 0 && !this.lock) {
    this.lock = true;
    process.nextTick(this.makeCallback(Async.SIGNAL_UNLOCK));
  }
  var index = mapping === Async.SIGNAL_UNLOCK ? -1 : this.index++;
  var location = getLocation(caller);
  var self = this;

  return function asyncCallback(err) {
    self.pending--;
    if (!self.isCanceled) {
      if (err && isErrorHandleRequired(mapping)) {
        self.isCanceled = true;
        self.endWith.call(null, makeAsyncError(err));
      } else {
        if (mapping === Async.SIGNAL_UNLOCK) {
          self.lock = false;
        } else {
          self.results[index] = mapArguments(mapping, arguments);
        }
        if (self.pending === 0 && !self.lock) {
          self.next.apply(null, self.results);
        }
      }
    }
  };

  function isErrorHandleRequired(mapping) {
    if (mapping === Async.SIGNAL_UNLOCK || mapping === As.ALL) {
      return false;
    }
    if (mapping instanceof As) {
      return mapping.index !== 0;
    }
    return Object.keys(mapping).every(function (key) {
      var value = mapping[key];
      if (value instanceof As) {
        return value.index !== 0;
      }
      return true;
    });
  }

  function mapArguments(mapping, args) {
    if (mapping === As.ALL) {
      return Array.prototype.slice.call(args);
    }
    if (mapping instanceof As) {
      return args[mapping.index];
    }
    return Object.keys(mapping).reduce(function (result, key) {
      var value = mapping[key];
      if (value instanceof As) {
        result[key] = args[value.index];
      } else {
        result[key] = value;
      }
      return result;
    }, {});
  }

  function makeAsyncError(err) {
    var log = {
      'function': location.functionName,
      location: util.format('%s:%d:%d', location.fileName, location.lineNumber, location.columnNumber),
      mapping: mapping
    };
    var cause;
    var history;
    if (err.name === 'NueAsyncError') {
      cause = err.cause;
      history = err.asyncCallHistory.concat([log]);
    } else {
      cause = err;
      history = [log];
    }
    var e = new Error('An error occurred in an async call.');
    e.name = 'NueAsyncError';
    e.cause = cause;
    e.asyncCallHistory = history;
    e.message += util.format('\ncause stack is ...\n  %s\nasync call history is ...\n',
      e.cause.stack, history);
    return e;
  }
};
<commit_msg>improve debug message to indicate top level flow beginning.<commit_after>'use strict';

exports.name = 'nue';
exports.version = '0.5.0';
exports.flow = flow;
exports.parallel = parallel;
exports.as = as;

var util = require('util');
var assert = require('assert');
var debugMode = process.env.NODE_DEBUG && /\bnue\b/.test(process.env.NODE_DEBUG);
var flowId = 0;

function flow() {
  if (arguments.length === 1 && typeof arguments[0] === 'string') {
    var flowName = arguments[0];
    return function () {
      return deferFlow(flowName, normalizeArgs(arguments));
    };
  } else {
    return deferFlow('', normalizeArgs(arguments));
  }

  function deferFlow(flowName, functions) {
    function startFlow() {
      var flow = {
        flowName: flowName,
        flowId: flowId++,
        context: this,
        isTopLevel: !(this instanceof StepContext),
        data: {},
        err: null
      };
      var steps = functions.length > 0 ? functions : [function () { this.next(); }];
      if (debugMode && flow.isTopLevel) {
        var location = getLocation(startFlow);
        debug('begin TOP_LEVEL_FLOW. flow: %s(%d), calledAt: %s:%d:%d, args:',
          flow.flowName || '<anonymous>', flow.flowId,
          location.fileName, location.lineNumber, location.columnNumber,
          Array.prototype.slice.call(arguments));
      }
      var head = chainSteps(flow, steps);
      head.apply(this, arguments);
    }
    startFlow.stepName = flowName;
    return startFlow;
  }

  function chainSteps(flow, steps) {
    var len = steps.length;
    var lastIndex = len - 1;
    var last = makeLastStep(steps[lastIndex], lastIndex);
    var isThrown = false;
    if (len === 1) {
      return last;
    }

    return steps.reduceRight(function chain(prev, curr, index) {
      assert.equal(typeof curr, 'function', 'Each argument for `flow` must be a function.');
      return function step() {
        var next = index === len - 2 ? last : prev;
        var context = new StepContext(flow, curr.stepName || curr.name, index, next, last);
        try {
          runStep(curr, context, arguments);
        } catch (e) {
          if (isThrown) {
            throw e;
          }
          StepContext.prototype.endWith.call(context, e);
        }
      };
    });

    function makeLastStep(fn, index) {
      assert.equal(typeof fn, 'function', 'Each argument for `flow` must be a function.');
      return function lastStep() {
        var context = new StepContext(flow, fn.stepName || fn.name, index, exit, exit);
        try {
          runStep(fn, context, arguments, true);
        } catch (e) {
          isThrown = true;
          throw e;
        }
      };

      function exit() {
        if (flow.isTopLevel) {
          if (flow.err) {
            var message = 'An error must be handled in a last step of its flow. ' +
              'To indicate error handling completed, set null to `this.err` before exiting the last step. ' +
              'cause: ' + util.inspect(flow.err);
            var err = new Error(message);
            err.name = 'NueUnhandledError';
            throw err;
          }
        } else {
          if (flow.err) {
            flow.context.endWith.call(flow.context, flow.err);
          } else {
            flow.context.next.apply(flow.context, arguments);
          }
        }
      }
    }

    function runStep(step, context, args) {
      if (debugMode) {
        debug('begin STEP. flow: %s(%d), step: %s(%d), args:',
          context.flowName || '<anonymous>', context._flow.flowId,
          context.stepName || '<anonymous>', context.stepIndex, Array.prototype.slice.call(args));
      }
      try {
        step.apply(context, args);
      } catch (e) {
        if (debugMode) {
          debug('error STEP. flow: , step: %s(%d), err:',
            context.flowName || '<anonymous>', context._flow.flowId,
            context.stepName || '<anonymous>', context.stepIndex, e);
        }
        throw e;
      }
    }
  }
}

function parallel() {
  if (arguments.length === 1 && typeof arguments[0] === 'string') {
    var flowName = arguments[0];
    return function () {
      return deferParallel(flowName, normalizeArgs(arguments));
    };
  } else {
    return deferParallel('', normalizeArgs(arguments));
  }

  function deferParallel(flowName, functions) {
    function startParallel() {
      assert(this instanceof StepContext, 'A `parallel` must be inside a flow or another parallel.');
      var self = this;
      var args = Array.prototype.slice.call(arguments);
      self.asyncEach(1)(functions, function (fn, group) {
        assert.equal(typeof fn, 'function', 'Each argument for `parallel` must be a function.');
        var callback = group.async(As.ALL);
        var end = function end() {
          if (this.err) {
            self.endWith(this.err);
          } else {
            callback.apply(null, arguments);
          }
        };
        end.stepName = (fn.stepName || fn.name) + '_end';
        flow(flowName)(fn, end).apply(self, args);
      });
    }
    startParallel.stepName = flowName;
    return startParallel;
  }
}

function as(index) {
  return new As(index);
}

function normalizeArgs(args) {
  return (args.length === 1 && Array.isArray(args[0])) ? args[0] : Array.prototype.slice.call(args);
}

function getLocation(target) {
  var originalPrepareStackTrace = Error.prepareStackTrace;
  var originalStackTraceLimit = Error.stackTraceLimit;
  Error.prepareStackTrace = prepareStackTrace;
  Error.stackTraceLimit = 1;
  var err = {};
  Error.captureStackTrace(err, target);
  var stack = err.stack;
  Error.prepareStackTrace = originalPrepareStackTrace;
  Error.stackTraceLimit = originalStackTraceLimit;
  return {
    functionName: stack ? stack.getFunctionName() : '',
    fileName: stack ? stack.getFileName() : '',
    lineNumber: stack ? stack.getLineNumber() : -1,
    columnNumber: stack ? stack.getColumnNumber() : -1
  };

  function prepareStackTrace() {
    return arguments[1][0];
  }
}

function debug() {
  var args = Array.prototype.slice.call(arguments);
  args[0] = 'NUE: ' + args[0];
  console.error.apply(console, args);
}

function As(index) {
  this.index = index;
}

As.ALL = {description: 'all'};

function StepContext(flow, name, index, next, last) {
  this._flow = flow;
  this._next = next;
  this._last = last;
  this._async = new Async(false, this.next.bind(this), this.endWith.bind(this));
  this.err = flow.err;
  this.data = flow.data;
  this.flowName = flow.flowName;
  this.stepName = name;
  this.stepIndex = index;
}

StepContext.DEFAULT_CONCURRENCY = 10;

StepContext.noop = function noop() {};

StepContext.prototype._disable = function _disable() {
  this.next = StepContext.noop;
  this.end = StepContext.noop;
  this.endWith = StepContext.noop;
};

StepContext.prototype.next = function next() {
  this._disable();
  this._flow.err = this.err;
  this._next.apply(null, arguments);
};

StepContext.prototype.end = function end() {
  this._disable();
  this._flow.err = this.err;
  this._last.apply(null, arguments);
};

StepContext.prototype.endWith = function endWith(err) {
  this._disable();
  this._flow.err = err;
  this._last.call(null);
};

StepContext.prototype.async = function async(mapping) {
  return this._async.makeCallback.call(this._async, mapping, async);
};

StepContext.prototype.asyncEach = function asyncEach() {
  var self = this;
  var callback = self.async(As.ALL);
  var asyncObj = new Async(true, callback.bind(self), self.endWith.bind(self));
  var group = {
    async: function async(mapping) {
      return asyncObj.makeCallback.call(asyncObj, mapping, async);
    }
  };

  if (arguments.length === 1 && typeof arguments[0] === 'number') {
    var concurrency = arguments[0];
    return function () {
      validateAndStart(concurrency, arguments[0], arguments[1]);
    };
  } else {
    validateAndStart(StepContext.DEFAULT_CONCURRENCY, arguments[0], arguments[1]);
  }

  function validateAndStart(concurrency, array, worker) {
    assert(Array.isArray(array), 'An argument `array` must be an array.');
    assert.equal(typeof worker, 'function', 'An argument `worker` must be a function.');
    process.nextTick(function startEach() {
      each(concurrency, array, worker, 0);
    });
  }

  function each(concurrency, array, worker, index) {
    var len = array.length;
    for (var i = 0; i < concurrency && index < len; i++, index++) {
      worker.call(self, array[index], group, index, array);
    }
    if (index === len) {
      process.nextTick(asyncObj.makeCallback(Async.SIGNAL_UNLOCK));
    } else {
      process.nextTick(function nextEach() {
        each(concurrency, array, worker, index);
      });
    }
  }
};

StepContext.prototype.exec = function exec(fn) {
  assert(arguments.length > 1, 'Arguments length must be more than 1.');
  assert.equal(typeof fn, 'function', 'The first argument must be a function.');
  assert.equal(typeof arguments[arguments.length - 1], 'function', 'The last argument must be a function.');
  var self = this;
  var callback = arguments[arguments.length - 1];
  var args = Array.prototype.slice.call(arguments, 1, arguments.length - 1);
  var end = function end() {
    callback.apply(null, [this.err].concat(Array.prototype.slice.call(arguments)));
  };
  end.stepName = (fn.stepName || fn.name) + '_end';
  process.nextTick(function execFlow() {
    flow('exec')(fn, end).apply(self, args);
  });
};

function Async(lock, next, endWith) {
  this.lock = lock;
  this.next = next;
  this.endWith = endWith;
  this.index = 0;
  this.pending = 0;
  this.isCanceled = false;
  this.results = [];
}

Async.SIGNAL_UNLOCK = {description: 'signal_unlock'};

Async.prototype.makeCallback = function makeCallback(mapping, caller) {
  assert(typeof mapping === 'object' && mapping !== null, 'An argument `mapping` must be an object');
  this.pending++;
  if (this.index === 0 && !this.lock) {
    this.lock = true;
    process.nextTick(this.makeCallback(Async.SIGNAL_UNLOCK));
  }
  var index = mapping === Async.SIGNAL_UNLOCK ? -1 : this.index++;
  var location = getLocation(caller);
  var self = this;

  return function asyncCallback(err) {
    self.pending--;
    if (!self.isCanceled) {
      if (err && isErrorHandleRequired(mapping)) {
        self.isCanceled = true;
        self.endWith.call(null, makeAsyncError(err));
      } else {
        if (mapping === Async.SIGNAL_UNLOCK) {
          self.lock = false;
        } else {
          self.results[index] = mapArguments(mapping, arguments);
        }
        if (self.pending === 0 && !self.lock) {
          self.next.apply(null, self.results);
        }
      }
    }
  };

  function isErrorHandleRequired(mapping) {
    if (mapping === Async.SIGNAL_UNLOCK || mapping === As.ALL) {
      return false;
    }
    if (mapping instanceof As) {
      return mapping.index !== 0;
    }
    return Object.keys(mapping).every(function (key) {
      var value = mapping[key];
      if (value instanceof As) {
        return value.index !== 0;
      }
      return true;
    });
  }

  function mapArguments(mapping, args) {
    if (mapping === As.ALL) {
      return Array.prototype.slice.call(args);
    }
    if (mapping instanceof As) {
      return args[mapping.index];
    }
    return Object.keys(mapping).reduce(function (result, key) {
      var value = mapping[key];
      if (value instanceof As) {
        result[key] = args[value.index];
      } else {
        result[key] = value;
      }
      return result;
    }, {});
  }

  function makeAsyncError(err) {
    var log = {
      'function': location.functionName,
      location: util.format('%s:%d:%d', location.fileName, location.lineNumber, location.columnNumber),
      mapping: mapping
    };
    var cause;
    var history;
    if (err.name === 'NueAsyncError') {
      cause = err.cause;
      history = err.asyncCallHistory.concat([log]);
    } else {
      cause = err;
      history = [log];
    }
    var e = new Error('An error occurred in an async call.');
    e.name = 'NueAsyncError';
    e.cause = cause;
    e.asyncCallHistory = history;
    e.message += util.format('\ncause stack is ...\n  %s\nasync call history is ...\n',
      e.cause.stack, history);
    return e;
  }
};
<|endoftext|>


<commit_before>function MarkdownTableFormatter() {
}

////////////////////////////////////////////////////////////////////////////////

MarkdownTableFormatter.prototype.format_table = function(input_table) {
  console.log("Starting MarkdownTableFormatter.format_table()");
  this.input_table = input_table;
  this.set_intput_lines(input_table);
  this.get_column_widths();
  console.log("Finishing MarkdownTableFormatter.format_table()");
  return input_table;
};

////////////////////////////////////////////////////////////////////////////////

MarkdownTableFormatter.prototype.get_column_widths = function() {

  this.column_widths = new Array();

  for (var lines_i = 0, lines_l = this.input_lines.length; lines_i < lines_l; lines_i = lines_i + 1) {

    var cols = this.input_lines[lines_i].split("\|");

    // Loop through the items in each column
    for (var cols_i = 0, cols_l = cols.length; cols_i < cols_l; cols_i = cols_i + 1) {
      if (typeof this.column_widths[cols_i] === 'undefined') {
        this.column_widths[cols_i] = cols[cols_i].length;
      }
      else if (this.column_widths[cols_i] < cols[cols_i].length) {
        this.column_widths[cols_i] = cols[cols_i].length;
      }
    }
  }
};

////////////////////////////////////////////////////////////////////////////////

MarkdownTableFormatter.prototype.set_column_widths = function(input_cells) {

  this.column_widths = new Array();

  for (var row_i = 0, row_l = input_cells.length; row_i < row_l; row_i = row_i + 1) {
    for (var col_i = 0, col_l = input_cells[row_i].length; col_i < col_l; col_i = col_i + 1) {
      if (typeof this.column_widths[col_i] === 'undefined') {
        this.column_widths[col_i] = input_cells[row_i][col_i].length;
      }
      else if (this.column_widths[col_i] < input_cells[row_i][col_i].length) {
        this.column_widths[col_i] = input_cells[row_i][col_i].length;
      }
    }
  }
}

////////////////////////////////////////////////////////////////////////////////

MarkdownTableFormatter.prototype.set_input_cells = function(input_table) {

  this.input_cells = new Array();

  var input_line_array = input_table.split("\n");

  for (var lines_i = 0, lines_l = input_line_array.length; lines_i < lines_l; lines_i = lines_i + 1) {
    this.input_cells[lines_i] = new Array();
    var current_cols_array = input_line_array[lines_i].split("\|");

    for (var cols_i = 0, cols_l = current_cols_array.length; cols_i < cols_l; cols_i = cols_i + 1) {
      this.input_cells[lines_i][cols_i] = current_cols_array[cols_i];
    }
  }
}

////////////////////////////////////////////////////////////////////////////////

MarkdownTableFormatter.prototype.set_intput_lines = function(input_table) {

  this.input_lines = input_table.split("\n");

}

<commit_msg>Refactored out 'set_intput_lines' and 'get_column_widths' with 'set_input_cells' and 'set_column_widths'.<commit_after>function MarkdownTableFormatter() {
}

////////////////////////////////////////////////////////////////////////////////

MarkdownTableFormatter.prototype.format_table = function(input_table) {
  console.log("Starting MarkdownTableFormatter.format_table()");
  this.input_table = input_table;
  this.set_input_cells(input_table);
  this.set_column_widths(this.input_cells);
  console.log("Finishing MarkdownTableFormatter.format_table()");
  return input_table;
};

////////////////////////////////////////////////////////////////////////////////

MarkdownTableFormatter.prototype.set_column_widths = function(input_cells) {

  this.column_widths = new Array();

  for (var row_i = 0, row_l = input_cells.length; row_i < row_l; row_i = row_i + 1) {
    for (var col_i = 0, col_l = input_cells[row_i].length; col_i < col_l; col_i = col_i + 1) {
      if (typeof this.column_widths[col_i] === 'undefined') {
        this.column_widths[col_i] = input_cells[row_i][col_i].length;
      }
      else if (this.column_widths[col_i] < input_cells[row_i][col_i].length) {
        this.column_widths[col_i] = input_cells[row_i][col_i].length;
      }
    }
  }
}

////////////////////////////////////////////////////////////////////////////////

MarkdownTableFormatter.prototype.set_input_cells = function(input_table) {

  this.input_cells = new Array();

  var input_line_array = input_table.split("\n");

  for (var lines_i = 0, lines_l = input_line_array.length; lines_i < lines_l; lines_i = lines_i + 1) {
    this.input_cells[lines_i] = new Array();
    var current_cols_array = input_line_array[lines_i].split("\|");

    for (var cols_i = 0, cols_l = current_cols_array.length; cols_i < cols_l; cols_i = cols_i + 1) {
      this.input_cells[lines_i][cols_i] = current_cols_array[cols_i];
    }
  }
}
<|endoftext|>


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

/**
 * Convenience Oid constructor.
 *
 * @constructor
 * @param {git.raw.Oid} [rawOid = new git.rawOid] Raw Oid object.
 */
var Oid = function(rawOid) {
  if(rawOid instanceof git.raw.Oid) {
    this.rawOid = rawOid;
  }
};

/**
 * @return {git.raw.Oid} The wrapped raw Oid object.
 */
Oid.prototype.getRawOid = function() {
  return this.rawOid;
};

/**
 * Create Oid object from string.
 *
 * @param  {String} sha
 * @param  {Oid~fromStringCallback} callback
 */
Oid.prototype.fromString = function(sha, callback) {
  /**
   * @callback Oid~fromStringCallback Callback executed after raw Oid is created.
   * @param {GitError|null} error An Error or null if successful.
   * @param {Oid|null} oid The new Oid object.
   */
  var self = this;
  self.rawOid.fromString(sha, function(error, rawOid) {
    if (success(error, rawOid)) {
      self.rawOid = rawOid;
      callback(null, self);
    }
  });
};

/**
 * Convert the raw Oid to a SHA
 *
 * @param  {Oid~shaCallback} callback
 */
Oid.prototype.sha = function(callback) {
  /**
   * @callback Oid~shaCallback Callback executed after SHA is retrieved.
   * @param {GitError|null} error An Error or null if successful.
   * @param {String|null} sha The SHA.
   */
  callback(null, this.rawOid.sha());
};

exports.oid = Oid;
<commit_msg>Fixed minor bugs in oid<commit_after>var git = require('../'),
    success = require('./utilities').success;

/**
 * Convenience Oid constructor.
 *
 * @constructor
 * @param {git.raw.Oid} [rawOid = new git.rawOid] Raw Oid object.
 */
var Oid = function(rawOid) {
  if(rawOid instanceof git.raw.Oid) {
    this.rawOid = rawOid;
  } else {
    this.rawOid = new git.raw.Oid();
  }
};

/**
 * @return {git.raw.Oid} The wrapped raw Oid object.
 */
Oid.prototype.getRawOid = function() {
  return this.rawOid;
};

/**
 * Create Oid object from string.
 *
 * @param  {String} sha
 * @param  {Oid~fromStringCallback} callback
 */
Oid.prototype.fromString = function(sha, callback) {
  /**
   * @callback Oid~fromStringCallback Callback executed after raw Oid is created.
   * @param {GitError|null} error An Error or null if successful.
   * @param {Oid|null} oid The new Oid object.
   */
  var self = this;
  self.rawOid.fromString(sha, function(error, rawOid) {
    if (success(error, callback)) {
      self.rawOid = rawOid;
      callback(null, self);
    }
  });
};

/**
 * Convert the raw Oid to a SHA
 *
 * @param  {Oid~shaCallback} callback
 */
Oid.prototype.sha = function(callback) {
  /**
   * @callback Oid~shaCallback Callback executed after SHA is retrieved.
   * @param {GitError|null} error An Error or null if successful.
   * @param {String|null} sha The SHA.
   */
  callback(null, this.rawOid.sha());
};

exports.oid = Oid;
<|endoftext|>


<commit_before>/*global XRegExp*/
(function() {
    'use strict';

    var LATIN_MAP = {
        'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE',
        'Ç': 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I',
        'Î': 'I', 'Ï': 'I', 'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O',
        'Õ': 'O', 'Ö': 'O', 'Ő': 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U',
        'Ü': 'U', 'Ű': 'U', 'Ý': 'Y', 'Þ': 'TH', 'Ÿ': 'Y', 'ß': 'ss', 'à': 'a',
        'á': 'a', 'â': 'a', 'ã': 'a', 'ä': 'a', 'å': 'a', 'æ': 'ae', 'ç': 'c',
        'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e', 'ì': 'i', 'í': 'i', 'î': 'i',
        'ï': 'i', 'ð': 'd', 'ñ': 'n', 'ò': 'o', 'ó': 'o', 'ô': 'o', 'õ': 'o',
        'ö': 'o', 'ő': 'o', 'ø': 'o', 'ù': 'u', 'ú': 'u', 'û': 'u', 'ü': 'u',
        'ű': 'u', 'ý': 'y', 'þ': 'th', 'ÿ': 'y'
    };
    var LATIN_SYMBOLS_MAP = {
        '©': '(c)'
    };
    var GREEK_MAP = {
        'α': 'a', 'β': 'b', 'γ': 'g', 'δ': 'd', 'ε': 'e', 'ζ': 'z', 'η': 'h',
        'θ': '8', 'ι': 'i', 'κ': 'k', 'λ': 'l', 'μ': 'm', 'ν': 'n', 'ξ': '3',
        'ο': 'o', 'π': 'p', 'ρ': 'r', 'σ': 's', 'τ': 't', 'υ': 'y', 'φ': 'f',
        'χ': 'x', 'ψ': 'ps', 'ω': 'w', 'ά': 'a', 'έ': 'e', 'ί': 'i', 'ό': 'o',
        'ύ': 'y', 'ή': 'h', 'ώ': 'w', 'ς': 's', 'ϊ': 'i', 'ΰ': 'y', 'ϋ': 'y',
        'ΐ': 'i', 'Α': 'A', 'Β': 'B', 'Γ': 'G', 'Δ': 'D', 'Ε': 'E', 'Ζ': 'Z',
        'Η': 'H', 'Θ': '8', 'Ι': 'I', 'Κ': 'K', 'Λ': 'L', 'Μ': 'M', 'Ν': 'N',
        'Ξ': '3', 'Ο': 'O', 'Π': 'P', 'Ρ': 'R', 'Σ': 'S', 'Τ': 'T', 'Υ': 'Y',
        'Φ': 'F', 'Χ': 'X', 'Ψ': 'PS', 'Ω': 'W', 'Ά': 'A', 'Έ': 'E', 'Ί': 'I',
        'Ό': 'O', 'Ύ': 'Y', 'Ή': 'H', 'Ώ': 'W', 'Ϊ': 'I', 'Ϋ': 'Y'
    };
    var TURKISH_MAP = {
        'ş': 's', 'Ş': 'S', 'ı': 'i', 'İ': 'I', 'ç': 'c', 'Ç': 'C', 'ü': 'u',
        'Ü': 'U', 'ö': 'o', 'Ö': 'O', 'ğ': 'g', 'Ğ': 'G'
    };
    var ROMANIAN_MAP = {
        'ă': 'a', 'î': 'i', 'ș': 's', 'ț': 't', 'â': 'a',
        'Ă': 'A', 'Î': 'I', 'Ș': 'S', 'Ț': 'T', 'Â': 'A'
    };
    var RUSSIAN_MAP = {
        'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'yo',
        'ж': 'zh', 'з': 'z', 'и': 'i', 'й': 'j', 'к': 'k', 'л': 'l', 'м': 'm',
        'н': 'n', 'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u',
        'ф': 'f', 'х': 'h', 'ц': 'c', 'ч': 'ch', 'ш': 'sh', 'щ': 'sh', 'ъ': '',
        'ы': 'y', 'ь': '', 'э': 'e', 'ю': 'yu', 'я': 'ya',
        'А': 'A', 'Б': 'B', 'В': 'V', 'Г': 'G', 'Д': 'D', 'Е': 'E', 'Ё': 'Yo',
        'Ж': 'Zh', 'З': 'Z', 'И': 'I', 'Й': 'J', 'К': 'K', 'Л': 'L', 'М': 'M',
        'Н': 'N', 'О': 'O', 'П': 'P', 'Р': 'R', 'С': 'S', 'Т': 'T', 'У': 'U',
        'Ф': 'F', 'Х': 'H', 'Ц': 'C', 'Ч': 'Ch', 'Ш': 'Sh', 'Щ': 'Sh', 'Ъ': '',
        'Ы': 'Y', 'Ь': '', 'Э': 'E', 'Ю': 'Yu', 'Я': 'Ya'
    };
    var UKRAINIAN_MAP = {
        'Є': 'Ye', 'І': 'I', 'Ї': 'Yi', 'Ґ': 'G', 'є': 'ye', 'і': 'i',
        'ї': 'yi', 'ґ': 'g'
    };
    var CZECH_MAP = {
        'č': 'c', 'ď': 'd', 'ě': 'e', 'ň': 'n', 'ř': 'r', 'š': 's', 'ť': 't',
        'ů': 'u', 'ž': 'z', 'Č': 'C', 'Ď': 'D', 'Ě': 'E', 'Ň': 'N', 'Ř': 'R',
        'Š': 'S', 'Ť': 'T', 'Ů': 'U', 'Ž': 'Z'
    };
    var SLOVAK_MAP = {
        'á': 'a', 'ä': 'a', 'č': 'c', 'ď': 'd', 'é': 'e', 'í': 'i', 'ľ': 'l',
        'ĺ': 'l', 'ň': 'n', 'ó': 'o', 'ô': 'o', 'ŕ': 'r', 'š': 's', 'ť': 't',
        'ú': 'u', 'ý': 'y', 'ž': 'z',
        'Á': 'a', 'Ä': 'A', 'Č': 'C', 'Ď': 'D', 'É': 'E', 'Í': 'I', 'Ľ': 'L',
        'Ĺ': 'L', 'Ň': 'N', 'Ó': 'O', 'Ô': 'O', 'Ŕ': 'R', 'Š': 'S', 'Ť': 'T',
        'Ú': 'U', 'Ý': 'Y', 'Ž': 'Z'
    };
    var POLISH_MAP = {
        'ą': 'a', 'ć': 'c', 'ę': 'e', 'ł': 'l', 'ń': 'n', 'ó': 'o', 'ś': 's',
        'ź': 'z', 'ż': 'z',
        'Ą': 'A', 'Ć': 'C', 'Ę': 'E', 'Ł': 'L', 'Ń': 'N', 'Ó': 'O', 'Ś': 'S',
        'Ź': 'Z', 'Ż': 'Z'
    };
    var LATVIAN_MAP = {
        'ā': 'a', 'č': 'c', 'ē': 'e', 'ģ': 'g', 'ī': 'i', 'ķ': 'k', 'ļ': 'l',
        'ņ': 'n', 'š': 's', 'ū': 'u', 'ž': 'z',
        'Ā': 'A', 'Č': 'C', 'Ē': 'E', 'Ģ': 'G', 'Ī': 'I', 'Ķ': 'K', 'Ļ': 'L',
        'Ņ': 'N', 'Š': 'S', 'Ū': 'U', 'Ž': 'Z'
    };
    var ARABIC_MAP = {
        'أ': 'a', 'ب': 'b', 'ت': 't', 'ث': 'th', 'ج': 'g', 'ح': 'h', 'خ': 'kh', 'د': 'd',
        'ذ': 'th', 'ر': 'r', 'ز': 'z', 'س': 's', 'ش': 'sh', 'ص': 's', 'ض': 'd', 'ط': 't',
        'ظ': 'th', 'ع': 'aa', 'غ': 'gh', 'ف': 'f', 'ق': 'k', 'ك': 'k', 'ل': 'l', 'م': 'm',
        'ن': 'n', 'ه': 'h', 'و': 'o', 'ي': 'y'
    };
    var LITHUANIAN_MAP = {
        'ą': 'a', 'č': 'c', 'ę': 'e', 'ė': 'e', 'į': 'i', 'š': 's', 'ų': 'u',
        'ū': 'u', 'ž': 'z',
        'Ą': 'A', 'Č': 'C', 'Ę': 'E', 'Ė': 'E', 'Į': 'I', 'Š': 'S', 'Ų': 'U',
        'Ū': 'U', 'Ž': 'Z'
    };
    var SERBIAN_MAP = {
        'ђ': 'dj', 'ј': 'j', 'љ': 'lj', 'њ': 'nj', 'ћ': 'c', 'џ': 'dz',
        'đ': 'dj', 'Ђ': 'Dj', 'Ј': 'j', 'Љ': 'Lj', 'Њ': 'Nj', 'Ћ': 'C',
        'Џ': 'Dz', 'Đ': 'Dj'
    };
    var AZERBAIJANI_MAP = {
        'ç': 'c', 'ə': 'e', 'ğ': 'g', 'ı': 'i', 'ö': 'o', 'ş': 's', 'ü': 'u',
        'Ç': 'C', 'Ə': 'E', 'Ğ': 'G', 'İ': 'I', 'Ö': 'O', 'Ş': 'S', 'Ü': 'U'
    };
    var GEORGIAN_MAP = {
        'ა': 'a', 'ბ': 'b', 'გ': 'g', 'დ': 'd', 'ე': 'e', 'ვ': 'v', 'ზ': 'z',
        'თ': 't', 'ი': 'i', 'კ': 'k', 'ლ': 'l', 'მ': 'm', 'ნ': 'n', 'ო': 'o',
        'პ': 'p', 'ჟ': 'j', 'რ': 'r', 'ს': 's', 'ტ': 't', 'უ': 'u', 'ფ': 'f',
        'ქ': 'q', 'ღ': 'g', 'ყ': 'y', 'შ': 'sh', 'ჩ': 'ch', 'ც': 'c', 'ძ': 'dz',
        'წ': 'w', 'ჭ': 'ch', 'ხ': 'x', 'ჯ': 'j', 'ჰ': 'h'
    };

    var ALL_DOWNCODE_MAPS = [
        LATIN_MAP,
        LATIN_SYMBOLS_MAP,
        GREEK_MAP,
        TURKISH_MAP,
        ROMANIAN_MAP,
        RUSSIAN_MAP,
        UKRAINIAN_MAP,
        CZECH_MAP,
        SLOVAK_MAP,
        POLISH_MAP,
        LATVIAN_MAP,
        ARABIC_MAP,
        LITHUANIAN_MAP,
        SERBIAN_MAP,
        AZERBAIJANI_MAP,
        GEORGIAN_MAP
    ];

    var Downcoder = {
        'Initialize': function() {
            if (Downcoder.map) {  // already made
                return;
            }
            Downcoder.map = {};
            Downcoder.chars = [];
            for (var i = 0; i < ALL_DOWNCODE_MAPS.length; i++) {
                var lookup = ALL_DOWNCODE_MAPS[i];
                for (var c in lookup) {
                    if (lookup.hasOwnProperty(c)) {
                        Downcoder.map[c] = lookup[c];
                    }
                }
            }
            for (var k in Downcoder.map) {
                if (Downcoder.map.hasOwnProperty(k)) {
                    Downcoder.chars.push(k);
                }
            }
            Downcoder.regex = new RegExp(Downcoder.chars.join('|'), 'g');
        }
    };

    function downcode(slug) {
        Downcoder.Initialize();
        return slug.replace(Downcoder.regex, function(m) {
            return Downcoder.map[m];
        });
    }

    function URLify(s, num_chars, allowUnicode) {
        // changes, e.g., "Petty theft" to "petty-theft"
        // remove all these words from the string before urlifying
        if (!allowUnicode) {
            s = downcode(s);
        }
        var hasUnicodeChars = /[^\u0000-\u007f]/.test(s);
        // Javascript RegExp does not identify word boundaries correctly
        // when dealing with unicode characters. If the string does not
        // contain unicode chars, it's safe to use the regular expression.
        if (!hasUnicodeChars) {
          var removelist = [
            "a", "an", "as", "at", "before", "but", "by", "for", "from", "is",
            "in", "into", "like", "of", "off", "on", "onto", "per", "since",
            "than", "the", "this", "that", "to", "up", "via", "with"
          ];
          var r = new RegExp('\\b(' + removelist.join('|') + ')\\b', 'gi');
          s = s.replace(r, '');
        }
        // if downcode doesn't hit, the char will be stripped here
        if (allowUnicode) {
            // Keep Unicode letters including both lowercase and uppercase
            // characters, whitespace, and dash; remove other characters.
            s = XRegExp.replace(s, XRegExp('[^-_\\p{L}\\p{N}\\s]', 'g'), '');
        } else {
            s = s.replace(/[^-\w\s]/g, '');  // remove unneeded chars
        }
        s = s.replace(/^\s+|\s+$/g, '');   // trim leading/trailing spaces
        s = s.replace(/[-\s]+/g, '-');     // convert spaces to hyphens
        s = s.toLowerCase();               // convert to lowercase
        return s.substring(0, num_chars);  // trim to first num_chars chars
    }
    window.URLify = URLify;
})();
<commit_msg>Replace urlify with Django's version from https://github.com/django/django/commit/b2bd08bb7a912a1504f5fb5018f5317e6b5423cd<commit_after>/*global XRegExp*/
(function() {
    'use strict';

    var LATIN_MAP = {
        'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE',
        'Ç': 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I',
        'Î': 'I', 'Ï': 'I', 'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O',
        'Õ': 'O', 'Ö': 'O', 'Ő': 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U',
        'Ü': 'U', 'Ű': 'U', 'Ý': 'Y', 'Þ': 'TH', 'Ÿ': 'Y', 'ß': 'ss', 'à': 'a',
        'á': 'a', 'â': 'a', 'ã': 'a', 'ä': 'a', 'å': 'a', 'æ': 'ae', 'ç': 'c',
        'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e', 'ì': 'i', 'í': 'i', 'î': 'i',
        'ï': 'i', 'ð': 'd', 'ñ': 'n', 'ò': 'o', 'ó': 'o', 'ô': 'o', 'õ': 'o',
        'ö': 'o', 'ő': 'o', 'ø': 'o', 'ù': 'u', 'ú': 'u', 'û': 'u', 'ü': 'u',
        'ű': 'u', 'ý': 'y', 'þ': 'th', 'ÿ': 'y'
    };
    var LATIN_SYMBOLS_MAP = {
        '©': '(c)'
    };
    var GREEK_MAP = {
        'α': 'a', 'β': 'b', 'γ': 'g', 'δ': 'd', 'ε': 'e', 'ζ': 'z', 'η': 'h',
        'θ': '8', 'ι': 'i', 'κ': 'k', 'λ': 'l', 'μ': 'm', 'ν': 'n', 'ξ': '3',
        'ο': 'o', 'π': 'p', 'ρ': 'r', 'σ': 's', 'τ': 't', 'υ': 'y', 'φ': 'f',
        'χ': 'x', 'ψ': 'ps', 'ω': 'w', 'ά': 'a', 'έ': 'e', 'ί': 'i', 'ό': 'o',
        'ύ': 'y', 'ή': 'h', 'ώ': 'w', 'ς': 's', 'ϊ': 'i', 'ΰ': 'y', 'ϋ': 'y',
        'ΐ': 'i', 'Α': 'A', 'Β': 'B', 'Γ': 'G', 'Δ': 'D', 'Ε': 'E', 'Ζ': 'Z',
        'Η': 'H', 'Θ': '8', 'Ι': 'I', 'Κ': 'K', 'Λ': 'L', 'Μ': 'M', 'Ν': 'N',
        'Ξ': '3', 'Ο': 'O', 'Π': 'P', 'Ρ': 'R', 'Σ': 'S', 'Τ': 'T', 'Υ': 'Y',
        'Φ': 'F', 'Χ': 'X', 'Ψ': 'PS', 'Ω': 'W', 'Ά': 'A', 'Έ': 'E', 'Ί': 'I',
        'Ό': 'O', 'Ύ': 'Y', 'Ή': 'H', 'Ώ': 'W', 'Ϊ': 'I', 'Ϋ': 'Y'
    };
    var TURKISH_MAP = {
        'ş': 's', 'Ş': 'S', 'ı': 'i', 'İ': 'I', 'ç': 'c', 'Ç': 'C', 'ü': 'u',
        'Ü': 'U', 'ö': 'o', 'Ö': 'O', 'ğ': 'g', 'Ğ': 'G'
    };
    var ROMANIAN_MAP = {
        'ă': 'a', 'î': 'i', 'ș': 's', 'ț': 't', 'â': 'a',
        'Ă': 'A', 'Î': 'I', 'Ș': 'S', 'Ț': 'T', 'Â': 'A'
    };
    var RUSSIAN_MAP = {
        'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'yo',
        'ж': 'zh', 'з': 'z', 'и': 'i', 'й': 'j', 'к': 'k', 'л': 'l', 'м': 'm',
        'н': 'n', 'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u',
        'ф': 'f', 'х': 'h', 'ц': 'c', 'ч': 'ch', 'ш': 'sh', 'щ': 'sh', 'ъ': '',
        'ы': 'y', 'ь': '', 'э': 'e', 'ю': 'yu', 'я': 'ya',
        'А': 'A', 'Б': 'B', 'В': 'V', 'Г': 'G', 'Д': 'D', 'Е': 'E', 'Ё': 'Yo',
        'Ж': 'Zh', 'З': 'Z', 'И': 'I', 'Й': 'J', 'К': 'K', 'Л': 'L', 'М': 'M',
        'Н': 'N', 'О': 'O', 'П': 'P', 'Р': 'R', 'С': 'S', 'Т': 'T', 'У': 'U',
        'Ф': 'F', 'Х': 'H', 'Ц': 'C', 'Ч': 'Ch', 'Ш': 'Sh', 'Щ': 'Sh', 'Ъ': '',
        'Ы': 'Y', 'Ь': '', 'Э': 'E', 'Ю': 'Yu', 'Я': 'Ya'
    };
    var UKRAINIAN_MAP = {
        'Є': 'Ye', 'І': 'I', 'Ї': 'Yi', 'Ґ': 'G', 'є': 'ye', 'і': 'i',
        'ї': 'yi', 'ґ': 'g'
    };
    var CZECH_MAP = {
        'č': 'c', 'ď': 'd', 'ě': 'e', 'ň': 'n', 'ř': 'r', 'š': 's', 'ť': 't',
        'ů': 'u', 'ž': 'z', 'Č': 'C', 'Ď': 'D', 'Ě': 'E', 'Ň': 'N', 'Ř': 'R',
        'Š': 'S', 'Ť': 'T', 'Ů': 'U', 'Ž': 'Z'
    };
    var SLOVAK_MAP = {
        'á': 'a', 'ä': 'a', 'č': 'c', 'ď': 'd', 'é': 'e', 'í': 'i', 'ľ': 'l',
        'ĺ': 'l', 'ň': 'n', 'ó': 'o', 'ô': 'o', 'ŕ': 'r', 'š': 's', 'ť': 't',
        'ú': 'u', 'ý': 'y', 'ž': 'z',
        'Á': 'a', 'Ä': 'A', 'Č': 'C', 'Ď': 'D', 'É': 'E', 'Í': 'I', 'Ľ': 'L',
        'Ĺ': 'L', 'Ň': 'N', 'Ó': 'O', 'Ô': 'O', 'Ŕ': 'R', 'Š': 'S', 'Ť': 'T',
        'Ú': 'U', 'Ý': 'Y', 'Ž': 'Z'
    };
    var POLISH_MAP = {
        'ą': 'a', 'ć': 'c', 'ę': 'e', 'ł': 'l', 'ń': 'n', 'ó': 'o', 'ś': 's',
        'ź': 'z', 'ż': 'z',
        'Ą': 'A', 'Ć': 'C', 'Ę': 'E', 'Ł': 'L', 'Ń': 'N', 'Ó': 'O', 'Ś': 'S',
        'Ź': 'Z', 'Ż': 'Z'
    };
    var LATVIAN_MAP = {
        'ā': 'a', 'č': 'c', 'ē': 'e', 'ģ': 'g', 'ī': 'i', 'ķ': 'k', 'ļ': 'l',
        'ņ': 'n', 'š': 's', 'ū': 'u', 'ž': 'z',
        'Ā': 'A', 'Č': 'C', 'Ē': 'E', 'Ģ': 'G', 'Ī': 'I', 'Ķ': 'K', 'Ļ': 'L',
        'Ņ': 'N', 'Š': 'S', 'Ū': 'U', 'Ž': 'Z'
    };
    var ARABIC_MAP = {
        'أ': 'a', 'ب': 'b', 'ت': 't', 'ث': 'th', 'ج': 'g', 'ح': 'h', 'خ': 'kh', 'د': 'd',
        'ذ': 'th', 'ر': 'r', 'ز': 'z', 'س': 's', 'ش': 'sh', 'ص': 's', 'ض': 'd', 'ط': 't',
        'ظ': 'th', 'ع': 'aa', 'غ': 'gh', 'ف': 'f', 'ق': 'k', 'ك': 'k', 'ل': 'l', 'م': 'm',
        'ن': 'n', 'ه': 'h', 'و': 'o', 'ي': 'y'
    };
    var LITHUANIAN_MAP = {
        'ą': 'a', 'č': 'c', 'ę': 'e', 'ė': 'e', 'į': 'i', 'š': 's', 'ų': 'u',
        'ū': 'u', 'ž': 'z',
        'Ą': 'A', 'Č': 'C', 'Ę': 'E', 'Ė': 'E', 'Į': 'I', 'Š': 'S', 'Ų': 'U',
        'Ū': 'U', 'Ž': 'Z'
    };
    var SERBIAN_MAP = {
        'ђ': 'dj', 'ј': 'j', 'љ': 'lj', 'њ': 'nj', 'ћ': 'c', 'џ': 'dz',
        'đ': 'dj', 'Ђ': 'Dj', 'Ј': 'j', 'Љ': 'Lj', 'Њ': 'Nj', 'Ћ': 'C',
        'Џ': 'Dz', 'Đ': 'Dj'
    };
    var AZERBAIJANI_MAP = {
        'ç': 'c', 'ə': 'e', 'ğ': 'g', 'ı': 'i', 'ö': 'o', 'ş': 's', 'ü': 'u',
        'Ç': 'C', 'Ə': 'E', 'Ğ': 'G', 'İ': 'I', 'Ö': 'O', 'Ş': 'S', 'Ü': 'U'
    };
    var GEORGIAN_MAP = {
        'ა': 'a', 'ბ': 'b', 'გ': 'g', 'დ': 'd', 'ე': 'e', 'ვ': 'v', 'ზ': 'z',
        'თ': 't', 'ი': 'i', 'კ': 'k', 'ლ': 'l', 'მ': 'm', 'ნ': 'n', 'ო': 'o',
        'პ': 'p', 'ჟ': 'j', 'რ': 'r', 'ს': 's', 'ტ': 't', 'უ': 'u', 'ფ': 'f',
        'ქ': 'q', 'ღ': 'g', 'ყ': 'y', 'შ': 'sh', 'ჩ': 'ch', 'ც': 'c', 'ძ': 'dz',
        'წ': 'w', 'ჭ': 'ch', 'ხ': 'x', 'ჯ': 'j', 'ჰ': 'h'
    };

    var ALL_DOWNCODE_MAPS = [
        LATIN_MAP,
        LATIN_SYMBOLS_MAP,
        GREEK_MAP,
        TURKISH_MAP,
        ROMANIAN_MAP,
        RUSSIAN_MAP,
        UKRAINIAN_MAP,
        CZECH_MAP,
        SLOVAK_MAP,
        POLISH_MAP,
        LATVIAN_MAP,
        ARABIC_MAP,
        LITHUANIAN_MAP,
        SERBIAN_MAP,
        AZERBAIJANI_MAP,
        GEORGIAN_MAP
    ];

    var Downcoder = {
        'Initialize': function() {
            if (Downcoder.map) { // already made
                return;
            }
            Downcoder.map = {};
            Downcoder.chars = [];
            for (var i = 0; i < ALL_DOWNCODE_MAPS.length; i++) {
                var lookup = ALL_DOWNCODE_MAPS[i];
                for (var c in lookup) {
                    if (lookup.hasOwnProperty(c)) {
                        Downcoder.map[c] = lookup[c];
                    }
                }
            }
            for (var k in Downcoder.map) {
                if (Downcoder.map.hasOwnProperty(k)) {
                    Downcoder.chars.push(k);
                }
            }
            Downcoder.regex = new RegExp(Downcoder.chars.join('|'), 'g');
        }
    };

    function downcode(slug) {
        Downcoder.Initialize();
        return slug.replace(Downcoder.regex, function(m) {
            return Downcoder.map[m];
        });
    }

    function URLify(s, num_chars, allowUnicode) {
        // changes, e.g., "Petty theft" to "petty-theft"
        // remove all these words from the string before urlifying
        if (!allowUnicode) {
            s = downcode(s);
        }
        var hasUnicodeChars = /[^\u0000-\u007f]/.test(s);
        // Remove English words only if the string contains ASCII (English)
        // characters.
        if (!hasUnicodeChars) {
            var removeList = [
                "a", "an", "as", "at", "before", "but", "by", "for", "from",
                "is", "in", "into", "like", "of", "off", "on", "onto", "per",
                "since", "than", "the", "this", "that", "to", "up", "via",
                "with"
            ];
            var r = new RegExp('\\b(' + removeList.join('|') + ')\\b', 'gi');
            s = s.replace(r, '');
        }
        s = s.toLowerCase(); // convert to lowercase
        // if downcode doesn't hit, the char will be stripped here
        if (allowUnicode) {
            // Keep Unicode letters including both lowercase and uppercase
            // characters, whitespace, and dash; remove other characters.
            s = XRegExp.replace(s, XRegExp('[^-_\\p{L}\\p{N}\\s]', 'g'), '');
        } else {
            s = s.replace(/[^-\w\s]/g, ''); // remove unneeded chars
        }
        s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces
        s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens
        s = s.substring(0, num_chars); // trim to first num_chars chars
        s = s.replace(/-+$/g, ''); // trim any trailing hyphens
        return s;
    }
    window.URLify = URLify;
})();
<|endoftext|>


<commit_before>// This is the custom package for PLSQL to MD
var
  debug,
  extend,
  path = require('path'),
  dox = require('./dox.js')
;

var pmd = {};

// Constants
pmd.DOCTYPES = {
  FUNCTION: "function",
  PROCEDURE: "procedure",
  //Note: For constants or dataTypes to be triggered @constants or @types needs to be include
  CONSTANTS : "constants",
  DATATYPES: "types"
};

/**
 * Handles consistent error handling
 * Process will exit calling this functions
 *
 * @param msg Message to log
 * @param includeError optional - Prefix the logged message with "Error: "
 */
pmd.raiseError = function(msg, includeError){
  includeError = includeError == null ? true : includeError;
  console.error((includeError ? 'Error: ' : '') + msg);
  process.exit();
}//raiseError

/**
 * Verify that path exists. If not, an error will be raised
 *
 * @param fullPath
 * @param objName If fullPath doesn't exists, objName will be used in error message
 */
pmd.validatePathRef = function(fullPath, objName){
  if (fullPath.length == 0){
    pmd.raiseError('All ' + objName + ' must have a fully qualified path');
  }
  else if (!fs.existsSync(path.resolve(fullPath))){
    pmd.raiseError(objName + ': ' + fullPath + ' does not exist');
  }
}// validatePathRef

/**
 * Processes a PL/SQL file to extract the JavaDoc contents
 *
 * @param file object {path} is required
 * @return JSON object with the JavaDoc entites
 */
pmd.processFile = function(file){
  var
    content = {}
  ;

  debug.log('\nProcessing:', file.path);

  content.data = fs.readFileSync(file.path,'utf8'),
  content.json = dox.parseComments(content.data);

  content.entities = []; //Holds list of entities for the object

  content.json.forEach(function(jsonData){
    var
      entity = {
        author:'',
        constants: [], // For package constants (used by @constant)
        created:'',
        example:'',
        return:'',
        isPrivate: jsonData.isPrivate,
        description:'',
        params:[],
        // typeDesc:[], // TODO mdsouza: better name for this
        code: '',
        issues: [],
        name: '',
        type: jsonData.ctx.type,
        types: [] // For package types (used by @type)
      },
      tagConstants = [], //temp array for tag with the name of @constant
      tagTypes = [] //temp array for tag with the name of @type
    ;

    if (jsonData.ignore) {
      debug.log('Ignoring:', jsonData.ctx)
      return; // Skip this loop since ignoring
    }

    jsonData.tags.forEach(function(tag){
      switch (tag.type) {
        case 'author':
          entity.author = tag.string;
          break;
        case 'created':
          entity.created = tag.string;
          break;
        case 'example':
          entity.example = tag.string;
          break;
        // Future: Devnotes
        // case 'devnotes':
        //   myMethod.devNotes = tag.string;
        //   break;
        case 'ignore':
          debug.log('TODO Ignore: ', ignore);
          break;
        case 'issue':
          //This will parse the current issue to be <issue reference> | <issue description>
          /^\s*([\S]+)\s*(.*)/.exec(tag.string);

          entity.issues.push({
            number: RegExp.$1.replace(/^#+/,''), //Remove any leading hashes to get the ticket number
            description: RegExp.$2
          })
          break;
        case 'param':
          entity.params.push({
            name: tag.name,
            description: tag.description.replace(/(^<p>|<\/p>$)/g, ''),
            optional: tag.optional
          });
          break;
        case 'return':
          entity.return = tag.string;
          break;
        case 'constant':
        case 'type':
          //This will parse the current issue to be <issue reference> | <issue description>
          /^\s*([\S]+)\s*(.*)/.exec(tag.string);
          var tempData = {
            name: RegExp.$1,
            description: RegExp.$2
          }
          if (tag.type === 'type'){tagTypes.push(tempData);}
          else if (tag.type === 'constant') {tagConstants.push(tempData);}
          break;
      }//switch
    })// jsonData.tags.forEach

    entity.description = jsonData.description ? jsonData.description : '';
    entity.code = jsonData.code ? jsonData.code : '';

    if (entity.code && entity.type === pmd.DOCTYPES.DATATYPES){

      entity.types = jsonData.ctx.types;
      // Loop over tags to see if there's one for this typeName
      for (var i in entity.types){
        entity.types[i].isPrivate = entity.isPrivate;

        tagTypes.forEach(function(tagType){
          if (entity.types[i].name === tagType.name){
            entity.types[i].description = tagType.description
          }
        });//tagTypes.forEach
      }//i in entity.types

    }//entity.code && entity.type === pmd.DOCTYPES.DATATYPES

    if (entity.code && entity.type === pmd.DOCTYPES.CONSTANTS){
      entity.constants = jsonData.ctx.constants;

      // Loop over constants to see if there's one for this constantName
      for (var i in entity.constants){
        entity.constants[i].isPrivate = entity.isPrivate;

        tagConstants.forEach(function(constantType){
          if (entity.constants[i].name === constantType.name){
            entity.constants[i].description = constantType.description
          }
        });//tagConstants.forEach
      }//i in entity.types
    }//entity.code && entity.type === pmd.DOCTYPES.CONSTANTS

    if (entity.code && (entity.type === pmd.DOCTYPES.FUNCTION || entity.type === pmd.DOCTYPES.PROCEDURE)){

      entity.name = jsonData.ctx.name;
      // TODO mdsouza: cleanup?
      // entity.name = entity.code.match(/^\s*(procedure|function){1}\s+\w+/ig)[0];
      // entity.name = entity.name.replace(/^\s*(procedure|function){1}/ig, "").trim();

      entity.header = jsonData.ctx.header;

    }//entity.docType === docTypes.method

    content.entities.push(entity);
  }); // content.json.forEach

  return content.entities;
}// processFile

/**
 * Returns the arguments JSON objects
 * Handles validation that all arguments are passed in
 *
 * @param process process object from calling function
 * @return arguments JSON object
 */
pmd.getArguments = function(process){
  var
    args = process.argv.slice(2), // Array of arguments
    arguments = {
      project :  args[0]
    }
  ;

  // Validation and Data Load
  if (args.length === 0){
    pmd.raiseError('To run call: node app <project>', false);
  }
  // Check that config file exists
  if (!fs.existsSync(path.resolve(__dirname + '/../','config.json'))){
    pmd.raiseError('config.json must be present. TODO run generateConfig.js to generate default');
  }

  return arguments;
}//getArguments

/**
 * Generates the data based on the files
 *
 * @param config Config JSON
 * @return objs array
 */
pmd.generateData = function(config){
  var objs = [];

  config.folders.forEach(function(folder){
    var
      files = fs.readdirSync(path.resolve(folder.source.path)),
      template = Handlebars.compile(folder.templateContent)
    ;

    // Create and wipe debug folder
    if (config.debug){
      // Will create (if not exists) and wipe
      fs.emptyDirSync(path.resolve(__dirname,'debug'));
    }//config.debug

    for (var i in files){
      var
        file = {
          ext: '',
          name: '',
          path: ''
        },
        data = {
          name:'',
          types: [],
          constants: [],
          methods: []
        },
        markdown,
        entities,
        skipFile = false //Skips the current file if no JavaDoc detected
      ;

      if (1==2 ||
        (folder.source.fileFilterRegexp instanceof RegExp && folder.source.fileFilterRegexp.test(files[i])) ||
        !folder.source.fileFilterRegexp instanceof RegExp){

        file.ext = path.extname(files[i]);
        file.name = path.basename(files[i], file.ext);
        file.path = path.resolve(folder.source.path, files[i]);

        data.name = file.name;
        entities = pmd.processFile(file, debug);

        // Load the data arrays with appropriate fields
        entities.forEach(function(entity){
          switch(entity.type){
            case pmd.DOCTYPES.DATATYPES:
              data.types = entity.types;
              break;
            case pmd.DOCTYPES.FUNCTION:
            case pmd.DOCTYPES.PROCEDURE:
              data.methods.push(entity);
              break;
            case pmd.DOCTYPES.CONSTANTS:
              data.constants = entity.constants;
              break;
            case undefined:
              debug.log('\nFile:', files[i], "doesn't appear to have any JavaDoc in it. Skipping");
              skipFile = true;
              break;
            default:
              debug.log('entity', entity);
              console.log('Unknown type: ', entity.type);
              process.exit();
              break;
          }//switch
        });//entities.forEach

        if (skipFile){
          continue; // Skip this loop iteration
        }

        // Output JSON and md data
        if (config.debug){
          debug.logFile(file.name + file.ext + '.json', JSON.stringify(data, null, '  '));
        }

        objs.push(
          {
            fileData: data,
            template: template,
            folder: folder
          }
        );
      }//if regexp pass or no regexp
    }// for i in files

  }); //config.folders.forEach

  return objs;
}//pmd.generateData

/**
 * Merge files (based on duplicate file names). Ex pks and pkb
 * Order is arbitrary
 *
 * @param objs Array of all the objects
 * @return Merged array
 */
pmd.mergeObjs = function(objs){
  objs.forEach(function(obj, i){
    //Seach for a matching element
    var
      data = obj.fileData,
      relatedData // Holds any
    ;

    // Loop over array but starting at next element
    for (var j = i+1; j < objs.length; j++){
      if (objs[j].fileData.name === data.name) {
        debug.log('Found matching entity:', objs[j].fileData.name);
        relatedData = objs[j].fileData;
        // Drop this entity as we'll merge it
        objs.splice(j, 1);
        break;
      }// if
    }

    // Merge data
    if (relatedData){
      data.constants = data.constants.concat(relatedData.constants);
      data.types = data.types.concat(relatedData.types);

      data.methods.forEach(function(myData){
        relatedData.methods.forEach(function(myRelatedElement, j){
          if (myData.name === myRelatedElement.name) {
            debug.log('Deleting common method:', myData.name);
            delete relatedData[j];
          }
        });
      })// data.forEach

      //Merge methods
      data.methods = extend(true, {}, data.methods, relatedData.methods);
    }// relatedData

    objs[i].fileData = data;
  }); //objs.forEach

  return objs;
}// pmd.mergeObjs

/**
 * Saves data to files
 *
 * @param objs array of all data
 */
pmd.saveToFile = function(objs){
  // Finally print out data
  objs.forEach(function(obj){

    var markdown = obj.template(obj.fileData);

    if (debug.debug){
      debug.logFile(obj.fileData.name + '.md', markdown);
    }
    fs.writeFileSync(path.resolve(obj.folder.output.path,obj.fileData.name + '.md'), markdown);
  });
}//saveToFile

/**
 * Generates Table of Conents (TOC)
 *
 * @issue 12: Original issue
 *
 * @params config
 * @params objs array of objs
 */
pmd.generateToc = function(config, objs){
  // #12 Generate Index file.
  if (config.toc.template){
    debug.log('\nCreated TOC');
    var
      indexData = {
        files: [],
        projectName: config.projectName
      },
      template,
      templateContent,
      markdown
    ;

    objs.forEach(function(obj){
      var file = {};

      file.fileName = obj.fileData.name + '.md';
      file.name = obj.fileData.name;

      indexData.files.push(file);
    })//objs.forEach

    // Sort based on names
    // http://stackoverflow.com/questions/979256/sorting-an-array-of-javascript-objects
    indexData.files.sort(function(a, b) {
      return a.name.localeCompare(b.name);
    });

    templateContent = fs.readFileSync(path.resolve(config.toc.template),'utf8'),
    template = Handlebars.compile(templateContent);
    markdown = template(indexData);

    fs.writeFileSync(path.resolve(config.folders[0].output.path, config.toc.fileName), markdown);
  }//config.templates.index
}// generateToc

module.exports = function(pDebug, pExtend){
  debug = pDebug;
  extend = pExtend;
  return pmd;
}
<commit_msg>Fix bug when merging common files.<commit_after>// This is the custom package for PLSQL to MD
var
  debug,
  extend,
  path = require('path'),
  dox = require('./dox.js')
;

var pmd = {};

// Constants
pmd.DOCTYPES = {
  FUNCTION: "function",
  PROCEDURE: "procedure",
  //Note: For constants or dataTypes to be triggered @constants or @types needs to be include
  CONSTANTS : "constants",
  DATATYPES: "types"
};

/**
 * Handles consistent error handling
 * Process will exit calling this functions
 *
 * @param msg Message to log
 * @param includeError optional - Prefix the logged message with "Error: "
 */
pmd.raiseError = function(msg, includeError){
  includeError = includeError == null ? true : includeError;
  console.error((includeError ? 'Error: ' : '') + msg);
  process.exit();
}//raiseError

/**
 * Verify that path exists. If not, an error will be raised
 *
 * @param fullPath
 * @param objName If fullPath doesn't exists, objName will be used in error message
 */
pmd.validatePathRef = function(fullPath, objName){
  if (fullPath.length == 0){
    pmd.raiseError('All ' + objName + ' must have a fully qualified path');
  }
  else if (!fs.existsSync(path.resolve(fullPath))){
    pmd.raiseError(objName + ': ' + fullPath + ' does not exist');
  }
}// validatePathRef

/**
 * Processes a PL/SQL file to extract the JavaDoc contents
 *
 * @param file object {path} is required
 * @return JSON object with the JavaDoc entites
 */
pmd.processFile = function(file){
  var
    content = {}
  ;

  debug.log('\nProcessing:', file.path);

  content.data = fs.readFileSync(file.path,'utf8'),
  content.json = dox.parseComments(content.data);

  content.entities = []; //Holds list of entities for the object

  content.json.forEach(function(jsonData){
    var
      entity = {
        author:'',
        constants: [], // For package constants (used by @constant)
        created:'',
        example:'',
        return:'',
        isPrivate: jsonData.isPrivate,
        description:'',
        params:[],
        // typeDesc:[], // TODO mdsouza: better name for this
        code: '',
        issues: [],
        name: '',
        type: jsonData.ctx.type,
        types: [] // For package types (used by @type)
      },
      tagConstants = [], //temp array for tag with the name of @constant
      tagTypes = [] //temp array for tag with the name of @type
    ;

    if (jsonData.ignore) {
      debug.log('Ignoring:', jsonData.ctx)
      return; // Skip this loop since ignoring
    }

    jsonData.tags.forEach(function(tag){
      switch (tag.type) {
        case 'author':
          entity.author = tag.string;
          break;
        case 'created':
          entity.created = tag.string;
          break;
        case 'example':
          entity.example = tag.string;
          break;
        // Future: Devnotes
        // case 'devnotes':
        //   myMethod.devNotes = tag.string;
        //   break;
        case 'ignore':
          debug.log('TODO Ignore: ', ignore);
          break;
        case 'issue':
          //This will parse the current issue to be <issue reference> | <issue description>
          /^\s*([\S]+)\s*(.*)/.exec(tag.string);

          entity.issues.push({
            number: RegExp.$1.replace(/^#+/,''), //Remove any leading hashes to get the ticket number
            description: RegExp.$2
          })
          break;
        case 'param':
          entity.params.push({
            name: tag.name,
            description: tag.description.replace(/(^<p>|<\/p>$)/g, ''),
            optional: tag.optional
          });
          break;
        case 'return':
          entity.return = tag.string;
          break;
        case 'constant':
        case 'type':
          //This will parse the current issue to be <issue reference> | <issue description>
          /^\s*([\S]+)\s*(.*)/.exec(tag.string);
          var tempData = {
            name: RegExp.$1,
            description: RegExp.$2
          }
          if (tag.type === 'type'){tagTypes.push(tempData);}
          else if (tag.type === 'constant') {tagConstants.push(tempData);}
          break;
      }//switch
    })// jsonData.tags.forEach

    entity.description = jsonData.description ? jsonData.description : '';
    entity.code = jsonData.code ? jsonData.code : '';

    if (entity.code && entity.type === pmd.DOCTYPES.DATATYPES){

      entity.types = jsonData.ctx.types;
      // Loop over tags to see if there's one for this typeName
      for (var i in entity.types){
        entity.types[i].isPrivate = entity.isPrivate;

        tagTypes.forEach(function(tagType){
          if (entity.types[i].name === tagType.name){
            entity.types[i].description = tagType.description
          }
        });//tagTypes.forEach
      }//i in entity.types

    }//entity.code && entity.type === pmd.DOCTYPES.DATATYPES

    if (entity.code && entity.type === pmd.DOCTYPES.CONSTANTS){
      entity.constants = jsonData.ctx.constants;

      // Loop over constants to see if there's one for this constantName
      for (var i in entity.constants){
        entity.constants[i].isPrivate = entity.isPrivate;

        tagConstants.forEach(function(constantType){
          if (entity.constants[i].name === constantType.name){
            entity.constants[i].description = constantType.description
          }
        });//tagConstants.forEach
      }//i in entity.types
    }//entity.code && entity.type === pmd.DOCTYPES.CONSTANTS

    if (entity.code && (entity.type === pmd.DOCTYPES.FUNCTION || entity.type === pmd.DOCTYPES.PROCEDURE)){

      entity.name = jsonData.ctx.name;
      // TODO mdsouza: cleanup?
      // entity.name = entity.code.match(/^\s*(procedure|function){1}\s+\w+/ig)[0];
      // entity.name = entity.name.replace(/^\s*(procedure|function){1}/ig, "").trim();

      entity.header = jsonData.ctx.header;

    }//entity.docType === docTypes.method

    content.entities.push(entity);
  }); // content.json.forEach

  return content.entities;
}// processFile

/**
 * Returns the arguments JSON objects
 * Handles validation that all arguments are passed in
 *
 * @param process process object from calling function
 * @return arguments JSON object
 */
pmd.getArguments = function(process){
  var
    args = process.argv.slice(2), // Array of arguments
    arguments = {
      project :  args[0]
    }
  ;

  // Validation and Data Load
  if (args.length === 0){
    pmd.raiseError('To run call: node app <project>', false);
  }
  // Check that config file exists
  if (!fs.existsSync(path.resolve(__dirname + '/../','config.json'))){
    pmd.raiseError('config.json must be present. TODO run generateConfig.js to generate default');
  }

  return arguments;
}//getArguments

/**
 * Generates the data based on the files
 *
 * @param config Config JSON
 * @return objs array
 */
pmd.generateData = function(config){
  var objs = [];

  config.folders.forEach(function(folder){
    var
      files = fs.readdirSync(path.resolve(folder.source.path)),
      template = Handlebars.compile(folder.templateContent)
    ;

    // Create and wipe debug folder
    if (config.debug){
      // Will create (if not exists) and wipe
      fs.emptyDirSync(path.resolve(__dirname,'debug'));
    }//config.debug

    for (var i in files){
      var
        file = {
          ext: '',
          name: '',
          path: ''
        },
        data = {
          name:'',
          types: [],
          constants: [],
          methods: []
        },
        markdown,
        entities,
        skipFile = false //Skips the current file if no JavaDoc detected
      ;

      if (1==2 ||
        (folder.source.fileFilterRegexp instanceof RegExp && folder.source.fileFilterRegexp.test(files[i])) ||
        !folder.source.fileFilterRegexp instanceof RegExp){

        file.ext = path.extname(files[i]);
        file.name = path.basename(files[i], file.ext);
        file.path = path.resolve(folder.source.path, files[i]);

        data.name = file.name;
        entities = pmd.processFile(file, debug);

        // Load the data arrays with appropriate fields
        entities.forEach(function(entity){
          switch(entity.type){
            case pmd.DOCTYPES.DATATYPES:
              data.types = entity.types;
              break;
            case pmd.DOCTYPES.FUNCTION:
            case pmd.DOCTYPES.PROCEDURE:
              data.methods.push(entity);
              break;
            case pmd.DOCTYPES.CONSTANTS:
              data.constants = entity.constants;
              break;
            case undefined:
              debug.log('\nFile:', files[i], "doesn't appear to have any JavaDoc in it. Skipping");
              skipFile = true;
              break;
            default:
              debug.log('entity', entity);
              console.log('Unknown type: ', entity.type);
              process.exit();
              break;
          }//switch
        });//entities.forEach

        if (skipFile){
          continue; // Skip this loop iteration
        }

        // Output JSON and md data
        if (config.debug){
          debug.logFile(file.name + file.ext + '.json', JSON.stringify(data, null, '  '));
        }

        objs.push(
          {
            fileData: data,
            template: template,
            folder: folder
          }
        );
      }//if regexp pass or no regexp
    }// for i in files

  }); //config.folders.forEach

  return objs;
}//pmd.generateData

/**
 * Merge files (based on duplicate file names). Ex pks and pkb
 * Order is arbitrary
 *
 * @param objs Array of all the objects
 * @return Merged array
 */
pmd.mergeObjs = function(objs){
  objs.forEach(function(obj, i){
    //Seach for a matching element
    var
      data = obj.fileData,
      relatedData // Holds any
    ;

    // Loop over array but starting at next element
    for (var j = i+1; j < objs.length; j++){
      if (objs[j].fileData.name === data.name) {
        debug.log('Found matching entity:', objs[j].fileData.name);
        relatedData = objs[j].fileData;
        // Drop this entity as we'll merge it
        objs.splice(j, 1);
        break;
      }// if
    }

    // Merge data
    if (relatedData){
      data.constants = data.constants.concat(relatedData.constants);
      data.types = data.types.concat(relatedData.types);

      data.methods.forEach(function(myData){
        relatedData.methods.forEach(function(myRelatedElement, j){
          if (myData.name === myRelatedElement.name) {
            debug.log('Deleting common method:', myData.name);
            delete relatedData.methods[j];
          }
        });
      })// data.forEach

      //Merge methods
      // Tried to use:
      // data.methods = extend(true, {}, data.methods, relatedData.methods);
      // But was merging them based on array position rather than merge
      // Do a custom merge
      relatedData.methods.forEach(function(method){
        data.methods.push(method);
      });

    }// relatedData

    objs[i].fileData = data;
  }); //objs.forEach

  return objs;
}// pmd.mergeObjs

/**
 * Saves data to files
 *
 * @param objs array of all data
 */
pmd.saveToFile = function(objs){
  // Finally print out data
  objs.forEach(function(obj){

    var markdown = obj.template(obj.fileData);

    if (debug.debug){
      debug.logFile(obj.fileData.name + '.md', markdown);
    }
    fs.writeFileSync(path.resolve(obj.folder.output.path,obj.fileData.name + '.md'), markdown);
  });
}//saveToFile

/**
 * Generates Table of Conents (TOC)
 *
 * @issue 12: Original issue
 *
 * @params config
 * @params objs array of objs
 */
pmd.generateToc = function(config, objs){
  // #12 Generate Index file.
  if (config.toc.template){
    debug.log('\nCreated TOC');
    var
      indexData = {
        files: [],
        projectName: config.projectName
      },
      template,
      templateContent,
      markdown
    ;

    objs.forEach(function(obj){
      var file = {};

      file.fileName = obj.fileData.name + '.md';
      file.name = obj.fileData.name;

      indexData.files.push(file);
    })//objs.forEach

    // Sort based on names
    // http://stackoverflow.com/questions/979256/sorting-an-array-of-javascript-objects
    indexData.files.sort(function(a, b) {
      return a.name.localeCompare(b.name);
    });

    templateContent = fs.readFileSync(path.resolve(config.toc.template),'utf8'),
    template = Handlebars.compile(templateContent);
    markdown = template(indexData);

    fs.writeFileSync(path.resolve(config.folders[0].output.path, config.toc.fileName), markdown);
  }//config.templates.index
}// generateToc

module.exports = function(pDebug, pExtend){
  debug = pDebug;
  extend = pExtend;
  return pmd;
}
<|endoftext|>