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

    if (!global.cloudcmd)
        return console.log(
            '# commander.js'                                    + '\n'  +
            '# -----------'                                     + '\n'  +
            '# Module is part of Cloud Commander,'              + '\n'  +
            '# used for getting dir content.'                   + '\n'  +
            '# and forming html content'                        + '\n'  +
            '# http://cloudcmd.io'                              + '\n');

    var main                = global.cloudcmd.main,
        fs                  = main.fs,
        Util                = main.util,
        checkParams         = main.checkCallBackParams;

    exports.getDirContent = function(pPath, pCallBack) {
        var lRet = Util.isString(pPath);

        if (lRet)
            fs.readdir(pPath, Util.call(readDir, {
                callback: pCallBack,
                path    : pPath
            }));
        else
            Util.exec(pCallBack, "First parameter should be a string");

        return lRet;
    };

    /**
     * Функция читает ссылку или выводит информацию об ошибке
     * @param pError
     * @param pFiles
     */
    function readDir(pParams) {
        var lRet = checkParams(pParams);
            lRet = lRet && Util.checkObj(pParams.params, ['path']);

        if (lRet) {
            var p           = pParams,
                c           = pParams.params,
                lFiles      = p.data,
                lDirPath    = getDirPath(c.path);

            if (!p.error && lFiles) {
                lFiles.data = lFiles.sort();

                /* Получаем информацию о файлах */
                var n           = lFiles.length,
                    lStats      = {},
                    lFilesData  = {
                        files       : lFiles,
                        stats       : lStats,
                        callback    : c.callback,
                        path        : c.path
                    },

                    lFill       = Util.retFunc(fillJSON, lFilesData);

                if (n)
                    for (var i = 0; i < n; i++) {
                        var lName = lDirPath + lFiles[i],
                            lParams =  {
                                callback    : lFill,
                                count       : n,
                                name        : lFiles[i],
                                stats       : lStats,
                            };

                        fs.stat(lName, Util.call(getFilesStat, lParams));
                    }
                else
                    fillJSON(lFilesData);
            } else
                Util.exec(c.callback, p.error.toString());
        }
    }

    /**
     * async getting file states
     * and putting it to lStats object
     */
    function getFilesStat(pParams) {
        var lRet = checkParams(pParams);
            lRet = lRet && Util.checkObjTrue(pParams.params,
                ['callback', 'stats', 'name', 'count']);

        if (lRet) {
            var p = pParams,
                c = p.params;

            c.stats[c.name] = !p.error ? p.data   : {
                'mode'          : 0,
                'size'          : 0,
                'isDirectory'   : Util.retFalse
            };

            if (c.count === Object.keys(c.stats).length)
                Util.exec(c.callback);
        }
    }

    /**
     * Function fill JSON by file stats
     *
     * @param pStats - object, contain file stats.
     *          example {'1.txt': stat}
     *
     * @param pFiles - array of files of current directory
     */
    function fillJSON(pParams) {
        var lRet = Util.checkObjTrue(pParams, ['files', 'stats', 'path']);

        if (lRet) {
            var p           = pParams,
                i, n        = p.files.length || 0,

                /* данные о файлах в формате JSON*/
                lJSONFile   = {},
                lDirPath    = getDirPath(p.path),
                lJSON       = [{
                    path    : lDirPath,
                    size    : 'dir'
                }];

            var lName, lStats, lMode, lIsDir;
            for (i = 0; i < n; i++ ) {
                /* Переводим права доступа в 8-ричную систему */
                lName   = p.files[i];
                lStats  = p.stats[lName];

                if (lStats) {
                    lMode   = (lStats.mode - 0).toString(8);
                    lIsDir  = lStats.isDirectory();
                }

                /* Если папка - выводим пиктограмму папки   *
                 * В противоположном случае - файла         */
                lJSONFile = {
                    'name'  : p.files[i],
                    'size'  : lIsDir ? 'dir' : lStats.size,
                    'uid'   : lStats.uid,
                    'mode'  : lMode
                };

                lJSON.push(lJSONFile);
            }

            lJSON = changeOrder(lJSON);

            Util.exec(p.callback, null, lJSON);

        }
    }

     function changeOrder(json) {
        var file, i, n,
            files   = [],
            dirs    = [],
            current = [],
            path    = json[0],
            sorted  = [path];

        n = json.length;
        for (i = 1; i < n; i++) {
            current = json[i];

            if (current.size === "dir")
                dirs.push(current);
            else
                files.push(current);
        }

        n = dirs.length;
        for (i = 0; i < n; i++) {
            current = dirs[i];
            sorted.push(current);
        }

        n = files.length;
        for (i = 0; i < n; i++) {
            current = files[i];
            sorted.push(current);
        }

        return sorted;
    }

    function getDirPath(pPath) {
        var lRet = pPath;

        if (lRet !== '/')
            lRet += '/';

        return lRet;
    }

})();<commit_msg>refactor(commander) fillJSON<commit_after>(function() {
    'use strict';

    if (!global.cloudcmd)
        return console.log(
            '# commander.js'                                    + '\n'  +
            '# -----------'                                     + '\n'  +
            '# Module is part of Cloud Commander,'              + '\n'  +
            '# used for getting dir content.'                   + '\n'  +
            '# and forming html content'                        + '\n'  +
            '# http://cloudcmd.io'                              + '\n');

    var main                = global.cloudcmd.main,
        fs                  = main.fs,
        Util                = main.util,
        checkParams         = main.checkCallBackParams;

    exports.getDirContent = function(pPath, pCallBack) {
        var lRet = Util.isString(pPath);

        if (lRet)
            fs.readdir(pPath, Util.call(readDir, {
                callback: pCallBack,
                path    : pPath
            }));
        else
            Util.exec(pCallBack, "First parameter should be a string");

        return lRet;
    };

    /**
     * Функция читает ссылку или выводит информацию об ошибке
     * @param pError
     * @param pFiles
     */
    function readDir(pParams) {
        var lRet = checkParams(pParams);
            lRet = lRet && Util.checkObj(pParams.params, ['path']);

        if (lRet) {
            var p           = pParams,
                c           = pParams.params,
                lFiles      = p.data,
                lDirPath    = getDirPath(c.path);

            if (!p.error && lFiles) {
                lFiles.data = lFiles.sort();

                /* Получаем информацию о файлах */
                var n           = lFiles.length,
                    lStats      = {},
                    lFilesData  = {
                        files       : lFiles,
                        stats       : lStats,
                        callback    : c.callback,
                        path        : c.path
                    },

                    lFill       = Util.retFunc(fillJSON, lFilesData);

                if (n)
                    for (var i = 0; i < n; i++) {
                        var lName = lDirPath + lFiles[i],
                            lParams =  {
                                callback    : lFill,
                                count       : n,
                                name        : lFiles[i],
                                stats       : lStats,
                            };

                        fs.stat(lName, Util.call(getFilesStat, lParams));
                    }
                else
                    fillJSON(lFilesData);
            } else
                Util.exec(c.callback, p.error.toString());
        }
    }

    /**
     * async getting file states
     * and putting it to lStats object
     */
    function getFilesStat(pParams) {
        var lRet = checkParams(pParams);
            lRet = lRet && Util.checkObjTrue(pParams.params,
                ['callback', 'stats', 'name', 'count']);

        if (lRet) {
            var p = pParams,
                c = p.params;

            c.stats[c.name] = !p.error ? p.data   : {
                'mode'          : 0,
                'size'          : 0,
                'isDirectory'   : Util.retFalse
            };

            if (c.count === Object.keys(c.stats).length)
                Util.exec(c.callback);
        }
    }

    /**
     * Function fill JSON by file stats
     *
     * @param pStats - object, contain file stats.
     *          example {'1.txt': stat}
     *
     * @param pFiles - array of files of current directory
     */
    function fillJSON(pParams) {
        var name, stat, mode, isDir, size, uid,
            p, i, n, file, path, json,
            ret         = Util.checkObjTrue(pParams, ['files', 'stats', 'path']);

        if (ret) {
            p           = pParams;
            n           = p.files.length;

            /* данные о файлах в формате JSON*/
            file        = {};
            path        = getDirPath(p.path);
            json        = [{
                path    : path,
                size    : 'dir'
            }];

            for (i = 0; i < n; i++ ) {
                /* Переводим права доступа в 8-ричную систему */
                name    = p.files[i];
                stat    = p.stats[name];
                uid     = stat.uid;

                if (stat) {
                    mode    = Number(stat.mode).toString(8);
                    isDir   = stat.isDirectory();
                    size    = isDir ? 'dir' : stat.size;
                }

                /* Если папка - выводим пиктограмму папки   *
                 * В противоположном случае - файла         */
                file = {
                    'name'  : p.files[i],
                    'size'  : size,
                    'uid'   : uid,
                    'mode'  : mode
                };

                json.push(file);
            }

            json = changeOrder(json);

            Util.exec(p.callback, null, json);

        }
    }

     function changeOrder(json) {
        var file, i, n,
            files   = [],
            dirs    = [],
            current = [],
            path    = json[0],
            sorted  = [path];

        n = json.length;
        for (i = 1; i < n; i++) {
            current = json[i];

            if (current.size === "dir")
                dirs.push(current);
            else
                files.push(current);
        }

        n = dirs.length;
        for (i = 0; i < n; i++) {
            current = dirs[i];
            sorted.push(current);
        }

        n = files.length;
        for (i = 0; i < n; i++) {
            current = files[i];
            sorted.push(current);
        }

        return sorted;
    }

    function getDirPath(pPath) {
        var lRet = pPath;

        if (lRet !== '/')
            lRet += '/';

        return lRet;
    }

})();<|endoftext|>


<commit_before>$(document).ready(function() {

  var $obj = $('body');
  var employee = scrapeEmployee($obj, window.location.href);

  var vCard = createVCard(employee);
  console.log(vCard);

  var a = createVCFDownloadLink(employee, vCard);

  // create file object
  file = new Blob([vCard]);
  document.body.appendChild(a);
  $('#ctl00_main_shortEmpl_permissions_ctl00').after(a);

  // create download link for image
  var a = document.createElement('a');
  a.href = $('#ctl00_main_shortEmpl_EmployeeImage')[0].src;
  a.download = employee.nameFull.replace(/[^a-z0-9]/gi, '_').toLowerCase() + '.png';  // set a safe file name
  $('#ctl00_main_shortEmpl_EmployeeImage').wrap(a);

});
<commit_msg>duplicate code detected<commit_after>$(document).ready(function() {

  var $obj = $('body');
  var employee = scrapeEmployee($obj, window.location.href);

  var vCard = createVCard(employee);

  var a = createVCFDownloadLink(employee, vCard);
  document.body.appendChild(a);
  $('#ctl00_main_shortEmpl_permissions_ctl00').after(a);

  // create download link for image
  var a = document.createElement('a');
  a.href = $('#ctl00_main_shortEmpl_EmployeeImage')[0].src;
  a.download = employee.nameFull.replace(/[^a-z0-9]/gi, '_').toLowerCase() + '.png';  // set a safe file name
  $('#ctl00_main_shortEmpl_EmployeeImage').wrap(a);

});
<|endoftext|>


<commit_before>import getRange from 'shared/parsing/getRangeForDygraph';

describe('getRangeForDygraphSpec', () => {
  it('gets the range for one timeSeries', () => {
    const timeSeries = [[new Date(1000), 1], [new Date(2000), 2], [new Date(3000), 3]];

    const actual = getRange(timeSeries);
    const expected = [1, 3];

    expect(actual).to.deep.equal(expected);
  });

  it('does not get range when a range is provided', () => {
    const timeSeries = [[new Date(1000), 1], [new Date(2000), 2], [new Date(3000), 3]];

    const providedRange = [0, 4];
    const actual = getRange(timeSeries, providedRange);

    expect(actual).to.deep.equal(providedRange);
  });

  it('gets the range for multiple timeSeries', () => {
    const timeSeries = [
      [new Date(1000), null, 1],
      [new Date(1000), 100, 1],
      [new Date(2000), null, 2],
      [new Date(3000), 200, 3],
    ];

    const actual = getRange(timeSeries);
    const expected = [1, 200];

    expect(actual).to.deep.equal(expected);
  });

  it('returns a null array of two elements when min and max are equal', () => {
    const timeSeries = [[new Date(1000), 1], [new Date(2000), 1], [new Date(3000), 1]];
    const actual = getRange(timeSeries);
    const expected = [null, null];

    expect(actual).to.deep.equal(expected);
  });

  it('returns a padded range when an additional value is provided that is near or exceeds range of timeSeries data', () => {
    const value0 = -10;
    const value1 = 20;
    const timeSeries = [[new Date(1000), value0], [new Date(2000), 1], [new Date(3000), value1]];
    const unpadded = getRange(timeSeries);

    const actualOne = getRange(timeSeries, undefined, value0);
    const actualTwo = getRange(timeSeries, undefined, value1);

    expect(actualOne[0]).to.be.below(unpadded[0]);
    expect(actualOne[1]).to.equal(unpadded[1]);
    expect(actualTwo[1]).to.be.above(unpadded[1]);
    expect(actualTwo[0]).to.equal(unpadded[0]);
  });

  it('returns an expected range when an additional value is provided that is well within range of timeSeries data', () => {
    const value = 0;
    const timeSeries = [[new Date(1000), -10], [new Date(2000), 1], [new Date(3000), 20]];
    const unpadded = getRange(timeSeries);

    const actual = getRange(timeSeries, undefined, value);

    expect(actual[0]).to.equal(unpadded[0]);
    expect(actual[1]).to.equal(unpadded[1]);
  });
});
<commit_msg>Refactor tests for clarity and simplicity<commit_after>import getRange from 'shared/parsing/getRangeForDygraph';

describe('getRangeForDygraphSpec', () => {
  it('gets the range for one timeSeries', () => {
    const timeSeries = [[new Date(1000), 1], [new Date(2000), 2], [new Date(3000), 3]];

    const actual = getRange(timeSeries);
    const expected = [1, 3];

    expect(actual).to.deep.equal(expected);
  });

  it('does not get range when a range is provided', () => {
    const timeSeries = [[new Date(1000), 1], [new Date(2000), 2], [new Date(3000), 3]];

    const providedRange = [0, 4];
    const actual = getRange(timeSeries, providedRange);

    expect(actual).to.deep.equal(providedRange);
  });

  it('gets the range for multiple timeSeries', () => {
    const timeSeries = [
      [new Date(1000), null, 1],
      [new Date(1000), 100, 1],
      [new Date(2000), null, 2],
      [new Date(3000), 200, 3],
    ];

    const actual = getRange(timeSeries);
    const expected = [1, 200];

    expect(actual).to.deep.equal(expected);
  });

  it('returns a null array of two elements when min and max are equal', () => {
    const timeSeries = [[new Date(1000), 1], [new Date(2000), 1], [new Date(3000), 1]];
    const actual = getRange(timeSeries);
    const expected = [null, null];

    expect(actual).to.deep.equal(expected);
  });

  describe('when user provides a rule value', () => {
    const defaultMax = 20;
    const defaultMin = -10;
    const timeSeries = [[new Date(1000), defaultMax], [new Date(2000), 1], [new Date(3000), defaultMin]];

    it('can pad positive values', () => {
      const value = 20;
      const [min, max] = getRange(timeSeries, undefined, value);

      expect(min).to.equal(defaultMin);
      expect(max).to.be.above(defaultMax);
    });

    it('can pad negative values', () => {
      const value = -10;
      const [min, max] = getRange(timeSeries, undefined, value);

      expect(min).to.be.below(defaultMin);
      expect(max).to.equal(defaultMax);
    });
  });

  describe('when user provides a rule range value', () => {
    const defaultMax = 20;
    const defaultMin = -10;
    const timeSeries = [[new Date(1000), defaultMax], [new Date(2000), 1], [new Date(3000), defaultMin]];

    it('can pad positive values', () => {
      const rangeValue = 20;
      const [min, max] = getRange(timeSeries, undefined, 0, rangeValue);

      expect(min).to.equal(defaultMin);
      expect(max).to.be.above(defaultMax);
    });

    it('can pad negative values', () => {
      const rangeValue = -10;
      const [min, max] = getRange(timeSeries, undefined, 0, rangeValue);

      expect(min).to.be.below(defaultMin);
      expect(max).to.equal(defaultMax);
    });
  });
});
<|endoftext|>


<commit_before>const
	http = require('http'),
	express = require('express');

module.exports.createServer = function (tracker, publisherSecret) {
	var app = express();

	app.use(express.bodyParser());

	app.post('/publish', function (request, response) {
		if (! ('secret' in request.body) || ! ('resource' in request.body) || ! ('version' in request.body)) {
			console.log('Received malformed publish request.');
			response.write(JSON.stringify({"ok": false, "error": "bad request"}));
			return response.end();
		}

		if (request.body.secret !== publisherSecret) {
			console.log('Received unauthorized publish request.');
			response.write(JSON.stringify({"ok": false, "error": "bad secret"}));
			return response.end();
		}

		if ('data' in request.body) {
			try {
				var data = JSON.parse(request.body.data);
			} catch (e) {
				console.log('Received malform publish request data.');
				response.write(JSON.stringify({"ok": false, "error": "bad data - invalid json"}));
				return response.end();
			}
		} else {
			var data = {};
		}

		tracker.publish(request.body.resource, request.body.version, data);
		response.write(JSON.stringify({"ok": true}));
		response.end();
	});

	return http.createServer(app);
};

<commit_msg>Use response.json shortcut<commit_after>const
	http = require('http'),
	express = require('express');

module.exports.createServer = function (tracker, publisherSecret) {
	var app = express();

	app.use(express.bodyParser());

	app.post('/publish', function (request, response) {
		if (! ('secret' in request.body) || ! ('resource' in request.body) || ! ('version' in request.body)) {
			console.log('Received malformed publish request.');
			return response.json({"ok": false, "error": "bad request"});
		}

		if (request.body.secret !== publisherSecret) {
			console.log('Received unauthorized publish request.');
			return response.json({"ok": false, "error": "bad secret"});
		}

		if ('data' in request.body) {
			try {
				var data = JSON.parse(request.body.data);
			} catch (e) {
				console.log('Received malform publish request data.');
				return response.json({"ok": false, "error": "bad data - invalid json"});
			}
		} else {
			var data = {};
		}

		tracker.publish(request.body.resource, request.body.version, data);
		return response.json({"ok": true});
	});

	return http.createServer(app);
};

<|endoftext|>


<commit_before>import { assert } from 'chai';

import DateConverter from './DateConverter';
import ParseError from './ParseError';

describe('DateConverter', () => {
  it('should convert date to string', () => {
    let dc = new DateConverter('MM/dd/yyyy');

    const date = new Date(2001, 0, 15);
    const dateAsString = dc.valueToString(date);

    assert.equal(dateAsString, '01/15/2001');

    const stringAsDate = dc.stringToValue(dateAsString);
    assert.equal(stringAsDate.toString(), date.toString());

    dc = new DateConverter('dd/MM/yy');
    assert.equal(dc.valueToString(date), '15/01/01');

    assert.throws(() => {
      assert.isNull(dc.stringToValue('aaaa'));
    }, ParseError, 'invalid parsed value [aaaa]');

    assert.throws(() => {
      assert.isNull(dc.stringToValue('11111'));
    }, ParseError, 'invalid parsed value [11111]');
  });
});
<commit_msg>Improved test of DateConverter.<commit_after>import { assert } from 'chai';

import DateConverter from './DateConverter';
import ParseError from './ParseError';

describe('DateConverter', () => {
  it('should convert date to string', () => {
    let dc = new DateConverter('MM/dd/yyyy');

    const date = new Date(2001, 0, 15);
    const dateAsString = dc.valueToString(date);

    assert.equal(dateAsString, '01/15/2001');

    const stringAsDate = dc.stringToValue(dateAsString);
    assert.equal(stringAsDate.toString(), date.toString());

    dc = new DateConverter('dd/MM/yy');
    assert.strictEqual(dc.valueToString(date), '15/01/01');

    assert.throws(() => {
      assert.isNull(dc.stringToValue('aaaa'));
    }, ParseError, 'invalid parsed value [aaaa]');

    assert.throws(() => {
      assert.isNull(dc.stringToValue('11111'));
    }, ParseError, 'invalid parsed value [11111]');
  });
});
<|endoftext|>


<commit_before>'use strict';

var dot = require('dot');
dot.templateSettings.strip = false;

var Node = require('../node/node');
var Source = require('../node/nodes/source');

var debug = require('../util/debug')('database-service');

var QueryRunner = require('../postgresql/query-runner');
var QueryParser = require('../postgresql/query-parser');
var BatchClient = require('../postgresql/batch-client');
var AnalysisTimeLogger = require('./analysis-time-logger');

var QUERY_RUNNER_READONLY_OP = true;
var QUERY_RUNNER_WRITE_OP = !QUERY_RUNNER_READONLY_OP;

var createQueryTemplate = dot.template([
    'CREATE TABLE {{=it._targetTableName}} AS',
    'SELECT * FROM ({{=it._query}}) _analysis_create_table_query',
    'LIMIT 0'
].join('\n'));

function DatabaseService(user, dbParams, batchParams, logger) {
    this.user = user;
    this.queryRunner = new QueryRunner(dbParams);
    this.queryParser = new QueryParser(this.queryRunner);

    var inlineExecution = batchParams.inlineExecution || false;
    var hostHeaderTemplate = batchParams.hostHeaderTemplate || '{{=it.username}}.localhost.lan';
    this.batchClient = new BatchClient(
        batchParams.endpoint,
        batchParams.username,
        batchParams.apiKey,
        hostHeaderTemplate,
        inlineExecution,
        this.queryRunner
    );

    this.logger = logger;
    this.analysisTimeLogger = new AnalysisTimeLogger(this.logger, this.batchClient);
}

module.exports = DatabaseService;

DatabaseService.prototype.setLogger = function (logger) {
    this.logger = logger;
    this.analysisTimeLogger.setLogger(logger);
};

DatabaseService.prototype.run = function(query, callback) {
    this.queryRunner.run(query, QUERY_RUNNER_READONLY_OP, callback);
};

var affectedTableRegexCache = {
    bbox: /!bbox!/g,
    scaleDenominator: /!scale_denominator!/g,
    pixelWidth: /!pixel_width!/g,
    pixelHeight: /!pixel_height!/g
};

DatabaseService.prototype.getLastUpdatedTimeFromAffectedTables = function(node, skip, callback) {
    if (!!skip || node.type !== Source.TYPE || node.getUpdatedAt() !== null) {
        return callback(null, node.getUpdatedAt());
    }

    var sql = node.sql()
        .replace(affectedTableRegexCache.bbox, 'ST_MakeEnvelope(0,0,0,0)')
        .replace(affectedTableRegexCache.scaleDenominator, '0')
        .replace(affectedTableRegexCache.pixelWidth, '1')
        .replace(affectedTableRegexCache.pixelHeight, '1');
    sql = 'SELECT max(updated_at) FROM CDB_QueryTables_Updated_At($camshaft$' + sql + '$camshaft$)';

    this.queryRunner.run(sql, QUERY_RUNNER_READONLY_OP, function(err, resultSet) {
        if (err) {
            return callback(err);
        }

        var defaultLastUpdatedTime = new Date('1970-01-01T00:00:00.000Z');
        var rows = resultSet.rows || [defaultLastUpdatedTime];
        var lastUpdatedTimeFromAffectedTables = (rows[0] && rows[0].max) || defaultLastUpdatedTime;

        return callback(null, lastUpdatedTimeFromAffectedTables);
    });
};

DatabaseService.prototype.createTable = function(targetTableName, query, callback) {
    var createQuery = createQueryTemplate({
        _targetTableName: targetTableName,
        _query: query
    });

    this.queryRunner.run(createQuery, QUERY_RUNNER_WRITE_OP, function(err, resultSet) {
        debug('TODO: register table');
        return callback(err, resultSet);
    });
};

DatabaseService.prototype.tableExists = function(targetTableName, callback) {
    var existsQuery = 'select * from ' + targetTableName + ' limit 0';
    this.queryRunner.run(existsQuery, QUERY_RUNNER_READONLY_OP, function(err, resultSet) {
        var tableExists = !err && resultSet;
        return callback(null, tableExists);
    });
};

var DUPLICATED_TABLE_ERROR_CODES = {
    '42P07': true, // duplicate_table
    '42710': true, // duplicate_object
    '23505': true  // unique_violation
};

DatabaseService.prototype.createTableIfNotExists = function(targetTableName, outputQuery, callback) {
    var self = this;
    this.tableExists(targetTableName, function(err, tableExists) {
        if (tableExists) {
            return callback(null, tableExists);
        }

        self.createTable(targetTableName, outputQuery, function(err) {
            if (err && !DUPLICATED_TABLE_ERROR_CODES[err.code]) {
                return callback(err, tableExists);
            }

            return callback(null, tableExists);
        });
    });
};

DatabaseService.prototype.getColumnNames = function(query, callback) {
    this.queryParser.getColumnNames(query, callback);
};

DatabaseService.prototype.getColumns = function(query, callback) {
    this.queryParser.getColumns(query, callback);
};

DatabaseService.prototype.enqueue = function(queries, callback) {
    this.batchClient.enqueue(queries, callback);
};

var registerNodeQueryTemplate = dot.template([
    'SELECT',
    '    \'{{=it._user}}\',',
    '    \'{{=it._nodeId}}\',',
    '    $json_def${{=it._analysisDef}}$json_def$::json,',
    '    {{=it._inputNodes}},',
    '    {{=it._cacheTables}},',
    '    {{=it._updatedAt}},',
    '    \'{{=it._status}}\'',
    'WHERE NOT EXISTS (SELECT * FROM cdb_analysis_catalog WHERE node_id = \'{{=it._nodeId}}\')'
].join('\n'));

var EMPTY_ARRAY_SQL = '\'{}\'';
function pgArray(input, cast) {
    if (input.length === 0) {
        return cast ? ('ARRAY[]::' + cast + '[]') : EMPTY_ARRAY_SQL;
    }
    return 'ARRAY[' + input.join(', ') + ']';
}

function pgQuoteCastMapper(cast) {
    return function(input) {
        return '\'' + input + '\'' + (cast ? ('::' + cast) : '');
    };
}

DatabaseService.prototype.registerAnalysisInCatalog = function(analysis, callback) {
    var nodes = analysis.getSortedNodes();
    if (!Array.isArray(nodes) || nodes.length === 0 || analysis.getRoot().type === Source.TYPE) {
        return callback(null);
    }
    var user = this.user;
    var nodesSql = [];
    nodes.forEach(function(node) {
        debug(node.type, node.getUpdatedAt());
        var updatedAt = node.getUpdatedAt();
        updatedAt = (updatedAt === null ? 'NULL' : 'TIMESTAMP WITH TIME ZONE \'' + updatedAt.toISOString() + '\'');
        var inputNodes = pgArray(
            node.getInputNodes().map(function(node) { return node.id(); }).map(pgQuoteCastMapper()), 'char[40]'
        );
        var analysisDef = JSON.stringify(node.toJSON());

        var cacheTables = pgArray(
            node.shouldCacheQuery() ? [pgQuoteCastMapper('regclass')(node.getTargetTable())] : [], 'regclass[]'
        );

        nodesSql.push(registerNodeQueryTemplate({
            _user: user,
            _nodeId: node.id(),
            _inputNodes: inputNodes,
            _cacheTables: cacheTables,
            _updatedAt: updatedAt,
            _status: node.getStatus(),
            _analysisDef: analysisDef
        }));
        // we want to register also the cache version of the node
        if (node.shouldCacheQuery()) {
            nodesSql.push(registerNodeQueryTemplate({
                _user: user,
                _nodeId: node.cachedNodeId(),
                _inputNodes: inputNodes,
                _cacheTables: cacheTables,
                _updatedAt: updatedAt,
                _status: node.getStatus(),
                _analysisDef: analysisDef
            }));
        }
    });
    var sql = 'INSERT INTO cdb_analysis_catalog' +
        '(username, node_id, analysis_def, input_nodes, cache_tables, updated_at, status)\n' +
        nodesSql.join('\nUNION ALL\n') +
        'ON CONFLICT (node_id) DO NOTHING';

    debug('Registering node with SQL: %s', sql);
    this.queryRunner.run(sql, QUERY_RUNNER_WRITE_OP, function(err, resultSet) {
        debug(err, resultSet);
        return callback(err);
    });
};

function transactionQuery(queries, nodeId) {
    return ['/* camshaft_node_id: ' + nodeId + ' */ BEGIN'].concat(queries).concat('COMMIT').join(';') + ';';
}

function deleteFromCacheTableQuery(targetTableName) {
    return 'DELETE FROM ' + targetTableName;
}

function populateCacheTableQuery(targetTableName, outputQuery) {
    return 'INSERT INTO ' + targetTableName + ' ' + outputQuery;
}

function updateNodeAtAnalysisCatalogQuery(nodeIds, columns) {
    nodeIds = Array.isArray(nodeIds) ? nodeIds : [nodeIds];
    return [
        'UPDATE cdb_analysis_catalog SET',
        columns.join(','),
        'WHERE node_id IN (' + nodeIds.map(pgQuoteCastMapper()).join(', ') + ')'
    ].join('\n');
}

function updateNodeStatusAtAnalysisCatalogQuery(nodeIds, status) {
    return updateNodeAtAnalysisCatalogQuery(nodeIds, [
        'status = \'' + status + '\'',
        'updated_at = NOW()'
    ]);
}

function updateNodeAtAnalysisCatalogForJobResultQuery(nodeIds, status, isError) {
    var columns = [
        'status = \'' + status + '\'',
        'last_modified_by = \'<%= job_id %>\'',
        'updated_at = NOW()'
    ];

    if (isError) {
        columns.push('last_error_message = $last_error_message$<%= error_message %>$last_error_message$');
    }

    return updateNodeAtAnalysisCatalogQuery(nodeIds, columns);
}

DatabaseService.prototype.queueAnalysisOperations = function(analysis, callback) {
    if (analysis.getRoot().type === Source.TYPE) {
        return callback(null);
    }

    var self = this;

    var sortedNodes = analysis.getSortedNodes();
    var sortedNodesIds = sortedNodes.map(function(node) {
        return node.cachedNodeId();
    });
    var nodesById = analysis.getNodes().reduce(function(byId, node) {
        if (!byId.hasOwnProperty(node.cachedNodeId())) {
            byId[node.cachedNodeId()] = [];
        }
        byId[node.cachedNodeId()].push(node);
        return byId;
    }, {});
    var query = [
        'SELECT node_id, analysis_def::json->>\'type\' as node_type,',
        ' updated_at, status, last_error_message, last_modified_by',
        'FROM cdb_analysis_catalog',
        'WHERE node_id IN (',
            sortedNodesIds
                .filter(function(node) { return node.type !== Source.TYPE; })
                .map(pgQuoteCastMapper()),
        ')'
    ].join('\n');

    this.queryRunner.run(query, function(err, resultSet) {
        if (err) {
            return callback(err);
        }

        var rows = resultSet.rows || [];

        rows.forEach(function(row) {
            var nodeId = row.node_id;
            var nodes = nodesById[nodeId];
            if (nodes) {
                nodes.forEach(function(node) {
                    node.setUpdatedAt(row.updated_at || null);
                    node.setStatus(row.status);
                    node.setErrorMessage(row.last_error_message || null);
                });
            }
        });

        // log after this function and scheduled I/O finish
        setImmediate(self.analysisTimeLogger.log.bind(self, rows));

        var nodesToQueueForUpdate = sortedNodes.filter(function(node) {
            return node.isOutdated();
        });

        var asyncQueries = [];

        // we want to mark all nodes updated_at to NOW() before doing any work on them
        nodesToQueueForUpdate.forEach(function(nodeToUpdate) {
            nodeToUpdate.setStatusFromInputNodes();
            var nodeIds = [nodeToUpdate.id(), nodeToUpdate.cachedNodeId()];
            asyncQueries.push({
                query: updateNodeStatusAtAnalysisCatalogQuery(nodeIds, nodeToUpdate.getStatus()),
                onerror: updateNodeStatusAtAnalysisCatalogQuery(nodeIds, Node.STATUS.FAILED)
            });
        });

        nodesToQueueForUpdate.forEach(function(nodeToUpdate) {
            if (nodeToUpdate.shouldCacheQuery()) {
                var nodeIds = [nodeToUpdate.id(), nodeToUpdate.cachedNodeId()];
                var targetTableName = nodeToUpdate.getTargetTable();
                asyncQueries.push({
                    query: updateNodeStatusAtAnalysisCatalogQuery(nodeIds, Node.STATUS.RUNNING)
                });
                asyncQueries.push({
                    query: transactionQuery([
                        deleteFromCacheTableQuery(targetTableName),
                        populateCacheTableQuery(targetTableName, nodeToUpdate.sql())
                    ], nodeToUpdate.cachedNodeId()),
                    onsuccess: updateNodeAtAnalysisCatalogForJobResultQuery(nodeIds, Node.STATUS.READY),
                    onerror: updateNodeAtAnalysisCatalogForJobResultQuery(nodeIds, Node.STATUS.FAILED, true)
                });
            } else {
                asyncQueries.push({
                    query: updateNodeStatusAtAnalysisCatalogQuery(nodeToUpdate.id(), Node.STATUS.READY),
                    onerror: updateNodeStatusAtAnalysisCatalogQuery(nodeToUpdate.id(), Node.STATUS.FAILED)
                });
            }
        });

        if (asyncQueries.length) {
            return self.enqueue(asyncQueries, callback);
        } else {
            return callback(null);
        }
    });
};

var trackNodeQueryTemplate = dot.template([
    'WITH RECURSIVE search_dag(node_id, input_nodes) AS (',
    '    SELECT node_id, input_nodes FROM cdb_analysis_catalog WHERE node_id = \'{{=it._nodeId}}\'',
    '    UNION',
    '    -- recursive search',
    '    SELECT c.node_id, c.input_nodes',
    '    FROM search_dag s, cdb_analysis_catalog c',
    '    WHERE c.node_id = ANY(s.input_nodes)',
    ')',
    'UPDATE cdb_analysis_catalog',
    'SET',
    '    used_at = NOW(),',
    '    hits = hits + 1,',
    '    last_used_from = \'{{=it._nodeId}}\'',
    'WHERE node_id IN (select node_id from search_dag)'
].join('\n'));

DatabaseService.prototype.trackAnalysis = function(analysis, callback) {
    var node = analysis.getRoot();

    // we don't want to track source nodes as high level nodes
    if (node.type === Source.TYPE) {
        return callback(null);
    }

    var sql = trackNodeQueryTemplate({ _nodeId: node.id() });
    debug('Tracking analysis with SQL: %s', sql);
    this.queryRunner.run(sql, QUERY_RUNNER_WRITE_OP, function(err, resultSet) {
        debug(err, resultSet);
        return callback(err);
    });
};
<commit_msg>Create analysis logger only when config is provided<commit_after>'use strict';

var dot = require('dot');
dot.templateSettings.strip = false;

var Node = require('../node/node');
var Source = require('../node/nodes/source');

var debug = require('../util/debug')('database-service');

var QueryRunner = require('../postgresql/query-runner');
var QueryParser = require('../postgresql/query-parser');
var BatchClient = require('../postgresql/batch-client');
var AnalysisLogger = require('./analysis-logger');

var QUERY_RUNNER_READONLY_OP = true;
var QUERY_RUNNER_WRITE_OP = !QUERY_RUNNER_READONLY_OP;

var createQueryTemplate = dot.template([
    'CREATE TABLE {{=it._targetTableName}} AS',
    'SELECT * FROM ({{=it._query}}) _analysis_create_table_query',
    'LIMIT 0'
].join('\n'));

function DatabaseService(user, dbParams, batchParams, loggerConfig) {
    this.user = user;
    this.queryRunner = new QueryRunner(dbParams);
    this.queryParser = new QueryParser(this.queryRunner);

    var inlineExecution = batchParams.inlineExecution || false;
    var hostHeaderTemplate = batchParams.hostHeaderTemplate || '{{=it.username}}.localhost.lan';
    this.batchClient = new BatchClient(
        batchParams.endpoint,
        batchParams.username,
        batchParams.apiKey,
        hostHeaderTemplate,
        inlineExecution,
        this.queryRunner
    );

    if (loggerConfig) {
      this.analysisLogger = new AnalysisLogger(loggerConfig, this.batchClient);
    }
}

module.exports = DatabaseService;

DatabaseService.prototype.run = function(query, callback) {
    this.queryRunner.run(query, QUERY_RUNNER_READONLY_OP, callback);
};

var affectedTableRegexCache = {
    bbox: /!bbox!/g,
    scaleDenominator: /!scale_denominator!/g,
    pixelWidth: /!pixel_width!/g,
    pixelHeight: /!pixel_height!/g
};

DatabaseService.prototype.getLastUpdatedTimeFromAffectedTables = function(node, skip, callback) {
    if (!!skip || node.type !== Source.TYPE || node.getUpdatedAt() !== null) {
        return callback(null, node.getUpdatedAt());
    }

    var sql = node.sql()
        .replace(affectedTableRegexCache.bbox, 'ST_MakeEnvelope(0,0,0,0)')
        .replace(affectedTableRegexCache.scaleDenominator, '0')
        .replace(affectedTableRegexCache.pixelWidth, '1')
        .replace(affectedTableRegexCache.pixelHeight, '1');
    sql = 'SELECT max(updated_at) FROM CDB_QueryTables_Updated_At($camshaft$' + sql + '$camshaft$)';

    this.queryRunner.run(sql, QUERY_RUNNER_READONLY_OP, function(err, resultSet) {
        if (err) {
            return callback(err);
        }

        var defaultLastUpdatedTime = new Date('1970-01-01T00:00:00.000Z');
        var rows = resultSet.rows || [defaultLastUpdatedTime];
        var lastUpdatedTimeFromAffectedTables = (rows[0] && rows[0].max) || defaultLastUpdatedTime;

        return callback(null, lastUpdatedTimeFromAffectedTables);
    });
};

DatabaseService.prototype.createTable = function(targetTableName, query, callback) {
    var createQuery = createQueryTemplate({
        _targetTableName: targetTableName,
        _query: query
    });

    this.queryRunner.run(createQuery, QUERY_RUNNER_WRITE_OP, function(err, resultSet) {
        debug('TODO: register table');
        return callback(err, resultSet);
    });
};

DatabaseService.prototype.tableExists = function(targetTableName, callback) {
    var existsQuery = 'select * from ' + targetTableName + ' limit 0';
    this.queryRunner.run(existsQuery, QUERY_RUNNER_READONLY_OP, function(err, resultSet) {
        var tableExists = !err && resultSet;
        return callback(null, tableExists);
    });
};

var DUPLICATED_TABLE_ERROR_CODES = {
    '42P07': true, // duplicate_table
    '42710': true, // duplicate_object
    '23505': true  // unique_violation
};

DatabaseService.prototype.createTableIfNotExists = function(targetTableName, outputQuery, callback) {
    var self = this;
    this.tableExists(targetTableName, function(err, tableExists) {
        if (tableExists) {
            return callback(null, tableExists);
        }

        self.createTable(targetTableName, outputQuery, function(err) {
            if (err && !DUPLICATED_TABLE_ERROR_CODES[err.code]) {
                return callback(err, tableExists);
            }

            return callback(null, tableExists);
        });
    });
};

DatabaseService.prototype.getColumnNames = function(query, callback) {
    this.queryParser.getColumnNames(query, callback);
};

DatabaseService.prototype.getColumns = function(query, callback) {
    this.queryParser.getColumns(query, callback);
};

DatabaseService.prototype.enqueue = function(queries, callback) {
    this.batchClient.enqueue(queries, callback);
};

var registerNodeQueryTemplate = dot.template([
    'SELECT',
    '    \'{{=it._user}}\',',
    '    \'{{=it._nodeId}}\',',
    '    $json_def${{=it._analysisDef}}$json_def$::json,',
    '    {{=it._inputNodes}},',
    '    {{=it._cacheTables}},',
    '    {{=it._updatedAt}},',
    '    \'{{=it._status}}\'',
    'WHERE NOT EXISTS (SELECT * FROM cdb_analysis_catalog WHERE node_id = \'{{=it._nodeId}}\')'
].join('\n'));

var EMPTY_ARRAY_SQL = '\'{}\'';
function pgArray(input, cast) {
    if (input.length === 0) {
        return cast ? ('ARRAY[]::' + cast + '[]') : EMPTY_ARRAY_SQL;
    }
    return 'ARRAY[' + input.join(', ') + ']';
}

function pgQuoteCastMapper(cast) {
    return function(input) {
        return '\'' + input + '\'' + (cast ? ('::' + cast) : '');
    };
}

DatabaseService.prototype.registerAnalysisInCatalog = function(analysis, callback) {
    var nodes = analysis.getSortedNodes();
    if (!Array.isArray(nodes) || nodes.length === 0 || analysis.getRoot().type === Source.TYPE) {
        return callback(null);
    }
    var user = this.user;
    var nodesSql = [];

    nodes.forEach(function(node) {
        debug(node.type, node.getUpdatedAt());
        var updatedAt = node.getUpdatedAt();
        updatedAt = (updatedAt === null ? 'NULL' : 'TIMESTAMP WITH TIME ZONE \'' + updatedAt.toISOString() + '\'');
        var inputNodes = pgArray(
            node.getInputNodes().map(function(node) { return node.id(); }).map(pgQuoteCastMapper()), 'char[40]'
        );
        var analysisDef = JSON.stringify(node.toJSON());

        var cacheTables = pgArray(
            node.shouldCacheQuery() ? [pgQuoteCastMapper('regclass')(node.getTargetTable())] : [], 'regclass[]'
        );

        nodesSql.push(registerNodeQueryTemplate({
            _user: user,
            _nodeId: node.id(),
            _inputNodes: inputNodes,
            _cacheTables: cacheTables,
            _updatedAt: updatedAt,
            _status: node.getStatus(),
            _analysisDef: analysisDef
        }));
        // we want to register also the cache version of the node
        if (node.shouldCacheQuery()) {
            nodesSql.push(registerNodeQueryTemplate({
                _user: user,
                _nodeId: node.cachedNodeId(),
                _inputNodes: inputNodes,
                _cacheTables: cacheTables,
                _updatedAt: updatedAt,
                _status: node.getStatus(),
                _analysisDef: analysisDef
            }));
        }
    });
    var sql = 'INSERT INTO cdb_analysis_catalog' +
        '(username, node_id, analysis_def, input_nodes, cache_tables, updated_at, status)\n' +
        nodesSql.join('\nUNION ALL\n') +
        'ON CONFLICT (node_id) DO NOTHING';

    debug('Registering node with SQL: %s', sql);
    this.queryRunner.run(sql, QUERY_RUNNER_WRITE_OP, function(err, resultSet) {
        debug(err, resultSet);
        return callback(err);
    });
};

function transactionQuery(queries, nodeId) {
    return ['/* camshaft_node_id: ' + nodeId + ' */ BEGIN'].concat(queries).concat('COMMIT').join(';') + ';';
}

function deleteFromCacheTableQuery(targetTableName) {
    return 'DELETE FROM ' + targetTableName;
}

function populateCacheTableQuery(targetTableName, outputQuery) {
    return 'INSERT INTO ' + targetTableName + ' ' + outputQuery;
}

function updateNodeAtAnalysisCatalogQuery(nodeIds, columns) {
    nodeIds = Array.isArray(nodeIds) ? nodeIds : [nodeIds];
    return [
        'UPDATE cdb_analysis_catalog SET',
        columns.join(','),
        'WHERE node_id IN (' + nodeIds.map(pgQuoteCastMapper()).join(', ') + ')'
    ].join('\n');
}

function updateNodeStatusAtAnalysisCatalogQuery(nodeIds, status) {
    return updateNodeAtAnalysisCatalogQuery(nodeIds, [
        'status = \'' + status + '\'',
        'updated_at = NOW()'
    ]);
}

function updateNodeAtAnalysisCatalogForJobResultQuery(nodeIds, status, isError) {
    var columns = [
        'status = \'' + status + '\'',
        'last_modified_by = \'<%= job_id %>\'',
        'updated_at = NOW()'
    ];

    if (isError) {
        columns.push('last_error_message = $last_error_message$<%= error_message %>$last_error_message$');
    }

    return updateNodeAtAnalysisCatalogQuery(nodeIds, columns);
}

DatabaseService.prototype.queueAnalysisOperations = function(analysis, callback) {
    if (analysis.getRoot().type === Source.TYPE) {
        return callback(null);
    }

    var self = this;

    var sortedNodes = analysis.getSortedNodes();
    var sortedNodesIds = sortedNodes.map(function(node) {
        return node.cachedNodeId();
    });
    var nodesById = analysis.getNodes().reduce(function(byId, node) {
        if (!byId.hasOwnProperty(node.cachedNodeId())) {
            byId[node.cachedNodeId()] = [];
        }
        byId[node.cachedNodeId()].push(node);
        return byId;
    }, {});
    var query = [
        'SELECT node_id, analysis_def::json->>\'type\' as node_type,',
        ' updated_at, status, last_error_message, last_modified_by',
        'FROM cdb_analysis_catalog',
        'WHERE node_id IN (',
            sortedNodesIds
                .filter(function(node) { return node.type !== Source.TYPE; })
                .map(pgQuoteCastMapper()),
        ')'
    ].join('\n');

    this.queryRunner.run(query, function(err, resultSet) {
        if (err) {
            return callback(err);
        }

        var rows = resultSet.rows || [];

        rows.forEach(function(row) {
            var nodeId = row.node_id;
            var nodes = nodesById[nodeId];
            if (nodes) {
                nodes.forEach(function(node) {
                    node.setUpdatedAt(row.updated_at || null);
                    node.setStatus(row.status);
                    node.setErrorMessage(row.last_error_message || null);
                });
            }
        });

        if (self.analysisLogger) {
            setImmediate(self.analysisLogger.log.bind(self.analysisLogger, analysis, rows, self.user));
        }

        var nodesToQueueForUpdate = sortedNodes.filter(function(node) {
            return node.isOutdated();
        });

        var asyncQueries = [];

        // we want to mark all nodes updated_at to NOW() before doing any work on them
        nodesToQueueForUpdate.forEach(function(nodeToUpdate) {
            nodeToUpdate.setStatusFromInputNodes();
            var nodeIds = [nodeToUpdate.id(), nodeToUpdate.cachedNodeId()];
            asyncQueries.push({
                query: updateNodeStatusAtAnalysisCatalogQuery(nodeIds, nodeToUpdate.getStatus()),
                onerror: updateNodeStatusAtAnalysisCatalogQuery(nodeIds, Node.STATUS.FAILED)
            });
        });

        nodesToQueueForUpdate.forEach(function(nodeToUpdate) {
            if (nodeToUpdate.shouldCacheQuery()) {
                var nodeIds = [nodeToUpdate.id(), nodeToUpdate.cachedNodeId()];
                var targetTableName = nodeToUpdate.getTargetTable();
                asyncQueries.push({
                    query: updateNodeStatusAtAnalysisCatalogQuery(nodeIds, Node.STATUS.RUNNING)
                });
                asyncQueries.push({
                    query: transactionQuery([
                        deleteFromCacheTableQuery(targetTableName),
                        populateCacheTableQuery(targetTableName, nodeToUpdate.sql())
                    ], nodeToUpdate.cachedNodeId()),
                    onsuccess: updateNodeAtAnalysisCatalogForJobResultQuery(nodeIds, Node.STATUS.READY),
                    onerror: updateNodeAtAnalysisCatalogForJobResultQuery(nodeIds, Node.STATUS.FAILED, true)
                });
            } else {
                asyncQueries.push({
                    query: updateNodeStatusAtAnalysisCatalogQuery(nodeToUpdate.id(), Node.STATUS.READY),
                    onerror: updateNodeStatusAtAnalysisCatalogQuery(nodeToUpdate.id(), Node.STATUS.FAILED)
                });
            }
        });

        if (asyncQueries.length) {
            return self.enqueue(asyncQueries, callback);
        } else {
            return callback(null);
        }
    });
};

var trackNodeQueryTemplate = dot.template([
    'WITH RECURSIVE search_dag(node_id, input_nodes) AS (',
    '    SELECT node_id, input_nodes FROM cdb_analysis_catalog WHERE node_id = \'{{=it._nodeId}}\'',
    '    UNION',
    '    -- recursive search',
    '    SELECT c.node_id, c.input_nodes',
    '    FROM search_dag s, cdb_analysis_catalog c',
    '    WHERE c.node_id = ANY(s.input_nodes)',
    ')',
    'UPDATE cdb_analysis_catalog',
    'SET',
    '    used_at = NOW(),',
    '    hits = hits + 1,',
    '    last_used_from = \'{{=it._nodeId}}\'',
    'WHERE node_id IN (select node_id from search_dag)'
].join('\n'));

DatabaseService.prototype.trackAnalysis = function(analysis, callback) {
    var node = analysis.getRoot();

    // we don't want to track source nodes as high level nodes
    if (node.type === Source.TYPE) {
        return callback(null);
    }

    var sql = trackNodeQueryTemplate({ _nodeId: node.id() });
    debug('Tracking analysis with SQL: %s', sql);
    this.queryRunner.run(sql, QUERY_RUNNER_WRITE_OP, function(err, resultSet) {
        debug(err, resultSet);
        return callback(err);
    });
};
<|endoftext|>


<commit_before>"use strict";

var async = require("async"),
    _ = require("underscore");

var log = require("./logger").get("shared"),
    config = require("./config.js"),
    environment = require("./environment.js"),
    alamidValidators = require("./helpers/alamidValidators.js");

/**
 * calls the remote validator via request and returns the result
 * @param {String} modelUrl
 * @param {!Object} modelData
 * @param {Function} callback
 */
function remoteValidation(modelUrl, modelData, callback) {

    var request = require("../client/request.js");

    if(environment.isServer()) {
        throw new Error("(alamid) You cannot call remoteValidation on the server");
    }

    //make full path
    var url = "";

    if(config.basePath !== undefined && config.basePath !== null) {
        url += config.basePath;
    }

    url += "/validators/" + modelUrl;

    //make request here
    request("create", url, modelData, function(response) {
        callback(response.data);
    });
}

function localValidation(schema, modelData, callback) {
    var result = {
        result : true,
        fields : {},
        failedFields : {}
    };

    function runFieldValidation(field, fieldValidationDoneCallback) {

        var validationCallResult, //only used for sync response
            validationDoneCalled = false;

        function markResult(validationResult) {
             //set the result
            result.fields[field] = validationResult;

            if(validationResult !== true) {

                if(result.failedFields[field] === undefined) {
                    result.failedFields[field] = [];
                }

                result.failedFields[field].push(validationResult);

                result.result = false; //overall result
            }
        }

        function runValidatorCallback(validationResult) {

            if (validationDoneCalled) {
                throw new Error("(alamid) Function '" +
                    Object.prototype.toString.call(null, schema[field].validator) +
                    "' returns multiple values");
            }

            validationDoneCalled = true;

            markResult(validationResult);

            fieldValidationDoneCallback();
        }

        //default alamid validators
        if(schema[field].required === true) {
            if(alamidValidators.checkRequired(modelData[field]) !== true) {
                runValidatorCallback("required");
                return;
            }
        }
        if(schema[field].enum instanceof Array) {
            if(alamidValidators.checkEnum(modelData[field], schema[field].enum) !== true) {
                runValidatorCallback("enum");
                return;
            }
        }
        if(typeof schema[field].min === "number") {
            if(alamidValidators.checkMin(modelData[field], schema[field].min) !== true) {
                runValidatorCallback("min");
                return;
            }
        }
        if(typeof schema[field].max === "number") {
            if(alamidValidators.checkMax(modelData[field], schema[field].max) !== true) {
                runValidatorCallback("max");
                return;
            }
        }

        //does validation result exist?
        if(schema[field].validate !== undefined) {
            //async code - directly calls the callback
            //maybe use apply
            validationCallResult = schema[field].validate.call(modelData, modelData[field], runValidatorCallback);

            //sync code
            if (validationCallResult !== undefined) {
                runValidatorCallback(validationCallResult);
            }
        }
        else {
            //no validation found
            runValidatorCallback(true);
        }
    }

    //START
    async.forEach(Object.keys(schema), runFieldValidation, function(err){

        if(err) {
            throw err;
        }

        callback(result);
    });
}

/**
 * merge failedFields of localValidation to the global failedFields object
 * @param overallResult
 * @param resultToAppend
 * @return {*}
 */
function extendFailedFields(overallResult, resultToAppend) {

    _(resultToAppend.failedFields).each(function(failedField, key) {

        if(overallResult.failedFields[key] === undefined) {
            overallResult.failedFields[key] = failedField;
        }
        else {
            overallResult.failedFields[key] = overallResult.failedFields[key].concat(failedField);
        }
    });

    return overallResult;
}

/**
 * Validate model-data on shared, local and remote schemas.
 * If used on client, the validator will also call remote validation
 * @param {Object} sharedSchema
 * @param {Object} localSchema
 * @param {String} modelUrl
 * @param {Object} modelData
 * @param {String} remote
 * @param {Function} callback
 */
function validate(sharedSchema, localSchema, modelUrl, modelData, remote, callback) {

    var overallResult = {
        result : false,
        failedFields : {}
    };

    async.series({
            localSharedValidation: function(cb){
                //SHARED
                localValidation(sharedSchema, modelData, function(sharedResult) {

                    overallResult.result = sharedResult.result;
                    overallResult.shared = sharedResult;

                    //maybe don't skip here
                    if(sharedResult.result === false) {
                        extendFailedFields(overallResult, sharedResult);
                    }

                    //check for second full-validation if shared worked out
                    cb(null);
                });
            },
            localValidation: function(cb){

                if(sharedSchema === localSchema) {
                    //SharedSchema and LocalSchema are identically. Skipping localSchema-Validation.
                    cb(null);
                    return;
                }

                //LOCAL
                localValidation(localSchema, modelData, function(localResult) {

                    overallResult.local = localResult;

                    if(localResult.result === false) {

                        overallResult.result = false;
                        extendFailedFields(overallResult, localResult);

                        cb(new Error("Validation failed"));
                        return;
                    }

                    cb(null);
                });
            },
            remoteValidation : function(cb) {

                if(remote === false || environment.isServer()) {
                    //needed to call the final callback
                    cb(new Error("Remote is disabled. Skipping."));
                    return;
                }

                //REMOTE
                remoteValidation(modelUrl, modelData, function(remoteResult) {

                    if(remoteResult.result === false) {
                        overallResult.result = false;
                        extendFailedFields(overallResult, remoteResult);
                    }

                    overallResult.remote = remoteResult;

                    cb();
                });
            }
        },
        function(err) {
            callback(overallResult);
        });
}

exports.localValidation = localValidation;
exports.validate = validate;
exports.remoteValidation = remoteValidation;
<commit_msg>- refactored validator - checking all validators for field at once, even if a single validator fails - removed result.fields, because we only want to know about failedFields - use result.failedFields in the future - related to #119<commit_after>"use strict";

var async = require("async"),
    _ = require("underscore");

var log = require("./logger").get("shared"),
    config = require("./config.js"),
    environment = require("./environment.js"),
    alamidValidators = require("./helpers/alamidValidators.js");

/**
 * calls the remote validator via request and returns the result
 * @param {String} modelUrl
 * @param {!Object} modelData
 * @param {Function} callback
 */
function remoteValidation(modelUrl, modelData, callback) {

    var request = require("../client/request.js");

    if(environment.isServer()) {
        throw new Error("(alamid) You cannot call remoteValidation on the server");
    }

    //make full path
    var url = "";

    if(config.basePath !== undefined && config.basePath !== null) {
        url += config.basePath;
    }

    url += "/validators/" + modelUrl;

    //make request here
    request("create", url, modelData, function(response) {
        callback(response.data);
    });
}

function localValidation(schema, modelData, callback) {

    var result = {
        result : true,
        failedFields : {}
    };

    function runFieldValidation(field, fieldValidationDoneCallback) {

        var validationCallResult, //only used for sync response
            validationDoneCalled = false;

        function markResult(validationResult) {

            if(validationResult !== true) {

                if(result.failedFields[field] === undefined) {
                    result.failedFields[field] = [];
                }

                result.failedFields[field].push(validationResult);

                result.result = false; //overall result
            }
        }

        function runValidatorCallback(validationResult) {

            if (validationDoneCalled) {
                throw new Error("(alamid) Function '" +
                    Object.prototype.toString.call(null, schema[field].validator) +
                    "' returns multiple values");
            }

            validationDoneCalled = true;

            if(validationResult !== undefined) {
                markResult(validationResult);
            }

            fieldValidationDoneCallback();
        }

        //default alamid validators
        if(schema[field].required === true) {
            if(alamidValidators.checkRequired(modelData[field]) !== true) {
                markResult("required");
            }
        }
        if(schema[field].enum instanceof Array) {
            if(alamidValidators.checkEnum(modelData[field], schema[field].enum) !== true) {
                markResult("enum");
            }
        }
        if(typeof schema[field].min === "number") {
            if(alamidValidators.checkMin(modelData[field], schema[field].min) !== true) {
                markResult("min");
            }
        }
        if(typeof schema[field].max === "number") {
            if(alamidValidators.checkMax(modelData[field], schema[field].max) !== true) {
                markResult("max");
            }
        }

        //does validation result exist?
        if(schema[field].validate !== undefined) {
            //async code - directly calls the callback
            //maybe use apply
            validationCallResult = schema[field].validate.call(modelData, modelData[field], runValidatorCallback);

            //sync code
            if (validationCallResult !== undefined) {
                runValidatorCallback(validationCallResult);
            }
        }
        else {
            //no validation found
            runValidatorCallback(true);
        }
    }

    //START
    async.forEach(Object.keys(schema), runFieldValidation, function(err){

        if(err) {
            throw err;
        }

        callback(result);
    });
}

/**
 * merge failedFields of localValidation to the global failedFields object
 * @param overallResult
 * @param resultToAppend
 * @return {*}
 */
function extendFailedFields(overallResult, resultToAppend) {

    _(resultToAppend.failedFields).each(function(failedField, key) {

        if(overallResult.failedFields[key] === undefined) {
            overallResult.failedFields[key] = failedField;
        }
        else {
            overallResult.failedFields[key] = overallResult.failedFields[key].concat(failedField);
        }
    });

    return overallResult;
}

/**
 * Validate model-data on shared, local and remote schemas.
 * If used on client, the validator will also call remote validation
 * @param {Object} sharedSchema
 * @param {Object} localSchema
 * @param {String} modelUrl
 * @param {Object} modelData
 * @param {String} remote
 * @param {Function} callback
 */
function validate(sharedSchema, localSchema, modelUrl, modelData, remote, callback) {

    var overallResult = {
        result : false,
        failedFields : {}
    };

    async.series({
            localSharedValidation: function(cb){
                //SHARED
                localValidation(sharedSchema, modelData, function(sharedResult) {

                    overallResult.result = sharedResult.result;
                    overallResult.shared = sharedResult;

                    //maybe don't skip here
                    if(sharedResult.result === false) {
                        extendFailedFields(overallResult, sharedResult);
                    }

                    //check for second full-validation if shared worked out
                    cb(null);
                });
            },
            localValidation: function(cb){

                if(sharedSchema === localSchema) {
                    //SharedSchema and LocalSchema are identically. Skipping localSchema-Validation.
                    cb(null);
                    return;
                }

                //LOCAL
                localValidation(localSchema, modelData, function(localResult) {

                    overallResult.local = localResult;

                    if(localResult.result === false) {

                        overallResult.result = false;
                        extendFailedFields(overallResult, localResult);

                        cb(new Error("Validation failed"));
                        return;
                    }

                    cb(null);
                });
            },
            remoteValidation : function(cb) {

                if(remote === false || environment.isServer()) {
                    //needed to call the final callback
                    cb(new Error("Remote is disabled. Skipping."));
                    return;
                }

                //REMOTE
                remoteValidation(modelUrl, modelData, function(remoteResult) {

                    if(remoteResult.result === false) {
                        overallResult.result = false;
                        extendFailedFields(overallResult, remoteResult);
                    }

                    overallResult.remote = remoteResult;

                    cb();
                });
            }
        },
        function(err) {
            callback(overallResult);
        });
}

exports.localValidation = localValidation;
exports.validate = validate;
exports.remoteValidation = remoteValidation;
<|endoftext|>


<commit_before>"use strict";

var async = require("async"),
    _ = require("underscore");

var log = require("./logger").get("data"),
    config = require("./config.js"),
    routes,
    env = require("./env.js"),
    alamidValidators = require("./helpers/alamidValidators.js");

/**
 * calls the remote validator via request and returns the result
 * @param {String} modelUrl
 * @param {!Object} modelData
 * @param {Function} callback
 */
function remoteValidation(modelUrl, modelData, callback) {

    var request,
    //will be set after app.init
        routes = config.routes;

    if (env.isServer()) {
        throw new Error("(alamid) You cannot call remoteValidation on the server");
    }

    request = require("../client/helpers/request.js");

    //make full path
    var url = "";

    if (config.basePath !== undefined && config.basePath !== null) {
        url += config.basePath;
    }

    url += routes.validators + "/" + modelUrl;

    //make request here
    request("create", url, modelData, function (err, response) {

        if (err) {
            response = {
                status : "error",
                message : "(alamid) Request-Error '" + err.message + "'",
                data : {
                    error : "requestError"
                }
            };
        }

        callback(response.data);
    });
}

/**
 * does the actual validation at the local environment (used on server & client)
 * @param {!Object} schema
 * @param {!Object} modelData
 * @param {!Function} callback
 */
function localValidation(schema, modelData, callback) {

    var result = {
        result : true,
        failedFields : {}
    };

    function runFieldValidation(field, fieldValidationDoneCallback) {

        var syncFieldValidationResponse,
            expectedCalls = 0,
            calls = 0;

        function markResult(validationResult) {

            if (validationResult !== true) {

                if (result.failedFields[field] === undefined) {
                    result.failedFields[field] = [];
                }

                result.failedFields[field].push(validationResult);

                result.result = false; //overall result
            }
        }

        function runValidatorCallback(validationResult) {

            calls++;

            if (validationResult !== undefined) {
                markResult(validationResult);
            }

            if(calls === expectedCalls) {
                fieldValidationDoneCallback();
            }
        }

        //default alamid validators
        if (schema[field].required === true) {
            if (alamidValidators.checkRequired(modelData[field]) !== true) {
                markResult("required");
            }
        }
        if (schema[field]["enum"] instanceof Array) {
            if (alamidValidators.checkEnum(modelData[field], schema[field]["enum"]) !== true) {
                markResult("enum");
            }
        }
        if (typeof schema[field].min === "number") {
            if (alamidValidators.checkMin(modelData[field], schema[field].min) !== true) {
                markResult("min");
            }
        }
        if (typeof schema[field].max === "number") {
            if (alamidValidators.checkMax(modelData[field], schema[field].max) !== true) {
                markResult("max");
            }
        }

        //does validation result exist?
        if (schema[field].validate !== undefined) {

            var validators = schema[field].validate;

            //arrayify!
            if (!_(validators).isArray()) {
                validators = [validators];
            }

            expectedCalls = validators.length;

            _(validators).each(function (validator) {

                //async code - directly calls the callback
                syncFieldValidationResponse = validator.call(modelData, modelData[field], function (asyncFieldValidationResult) {
                    runValidatorCallback(asyncFieldValidationResult);
                });

                //sync code
                if (syncFieldValidationResponse !== undefined) {
                    runValidatorCallback(syncFieldValidationResponse);
                }
            });
        }
        else {
            expectedCalls++;
            //no validation found
            runValidatorCallback(true);
        }
    }

    //START
    async.forEach(_.keys(schema), runFieldValidation, function (err) {

        if (err) {
            throw err;
        }

        callback(result);
    });
}

/**
 * merge failedFields of localValidation to the global failedFields object
 * @param overallResult
 * @param resultToAppend
 * @return {*}
 */
function extendFailedFields(overallResult, resultToAppend) {

    _(resultToAppend.failedFields).each(function (failedField, key) {

        if (overallResult.failedFields[key] === undefined) {
            overallResult.failedFields[key] = failedField;
        }
        else {
            overallResult.failedFields[key] = overallResult.failedFields[key].concat(failedField);
        }
    });

    return overallResult;
}

/**
 * Validate model-data on shared, local and remote schemas.
 * If used on client, the validator will also call remote validation
 * @param {Object} sharedSchema
 * @param {Object} localSchema
 * @param {String} modelUrl
 * @param {Object} modelData
 * @param {String} remote
 * @param {Function} callback
 */
function validate(sharedSchema, localSchema, modelUrl, modelData, remote, callback) {

    var overallResult = {
        result : false,
        failedFields : {}
    };

    async.series({
            //validate SHARED Schema locally
            localSharedVal : function (cb) {

                localValidation(sharedSchema, modelData, function (sharedResult) {

                    overallResult.result = sharedResult.result;
                    overallResult.shared = sharedResult;

                    //maybe don't skip here
                    if (sharedResult.result === false) {
                        extendFailedFields(overallResult, sharedResult);
                    }

                    //check for second full-validation if shared worked out
                    cb(null);
                });
            },
            //validate LocalSchema if existent locally
            localVal : function (cb) {

                if (sharedSchema === localSchema) {
                    //SharedSchema and LocalSchema are identically. Skipping localSchema-Validation.
                    cb(null);
                    return;
                }

                localValidation(localSchema, modelData, function (localResult) {

                    overallResult.local = localResult;

                    if (localResult.result === false) {

                        overallResult.result = false;
                        extendFailedFields(overallResult, localResult);

                        cb(new Error("Validation failed"));
                        return;
                    }

                    cb(null);
                });
            },
            //validate remotely
            remoteValidation : function (cb) {

                if (remote === false || env.isServer()) {
                    //needed to call the final callback
                    cb(new Error("Remote is disabled. Skipping."));
                    return;
                }

                remoteValidation(modelUrl, modelData, function (remoteResult) {

                    if (remoteResult.result === false) {
                        overallResult.result = false;
                        extendFailedFields(overallResult, remoteResult);
                    }

                    overallResult.remote = remoteResult;

                    cb();
                });
            }
        },
        function (err) {
            callback(overallResult);
        });
}

exports.localValidation = localValidation;
exports.validate = validate;
exports.remoteValidation = remoteValidation;<commit_msg>Add setImmediat<commit_after>"use strict";

var async = require("async"),
    _ = require("underscore");

var log = require("./logger").get("data"),
    config = require("./config.js"),
    routes,
    env = require("./env.js"),
    alamidValidators = require("./helpers/alamidValidators.js");

/**
 * calls the remote validator via request and returns the result
 * @param {String} modelUrl
 * @param {!Object} modelData
 * @param {Function} callback
 */
function remoteValidation(modelUrl, modelData, callback) {

    var request,
    //will be set after app.init
        routes = config.routes;

    if (env.isServer()) {
        throw new Error("(alamid) You cannot call remoteValidation on the server");
    }

    request = require("../client/helpers/request.js");

    //make full path
    var url = "";

    if (config.basePath !== undefined && config.basePath !== null) {
        url += config.basePath;
    }

    url += routes.validators + "/" + modelUrl;

    //make request here
    request("create", url, modelData, function (err, response) {

        if (err) {
            response = {
                status : "error",
                message : "(alamid) Request-Error '" + err.message + "'",
                data : {
                    error : "requestError"
                }
            };
        }

        callback(response.data);
    });
}

/**
 * does the actual validation at the local environment (used on server & client)
 * @param {!Object} schema
 * @param {!Object} modelData
 * @param {!Function} callback
 */
function localValidation(schema, modelData, callback) {

    var result = {
        result : true,
        failedFields : {}
    };

    function runFieldValidation(field, fieldValidationDoneCallback) {

        var syncFieldValidationResponse,
            expectedCalls = 0,
            calls = 0;

        function markResult(validationResult) {

            if (validationResult !== true) {

                if (result.failedFields[field] === undefined) {
                    result.failedFields[field] = [];
                }

                result.failedFields[field].push(validationResult);

                result.result = false; //overall result
            }
        }

        function runValidatorCallback(validationResult) {

            calls++;

            if (validationResult !== undefined) {
                markResult(validationResult);
            }

            if(calls === expectedCalls) {
                fieldValidationDoneCallback();
            }
        }

        //default alamid validators
        if (schema[field].required === true) {
            if (alamidValidators.checkRequired(modelData[field]) !== true) {
                markResult("required");
            }
        }
        if (schema[field]["enum"] instanceof Array) {
            if (alamidValidators.checkEnum(modelData[field], schema[field]["enum"]) !== true) {
                markResult("enum");
            }
        }
        if (typeof schema[field].min === "number") {
            if (alamidValidators.checkMin(modelData[field], schema[field].min) !== true) {
                markResult("min");
            }
        }
        if (typeof schema[field].max === "number") {
            if (alamidValidators.checkMax(modelData[field], schema[field].max) !== true) {
                markResult("max");
            }
        }

        //does validation result exist?
        if (schema[field].validate !== undefined) {

            var validators = schema[field].validate;

            //arrayify!
            if (!_(validators).isArray()) {
                validators = [validators];
            }

            expectedCalls = validators.length;

            _(validators).each(function (validator) {

                //async code - directly calls the callback
                syncFieldValidationResponse = validator.call(modelData, modelData[field], function (asyncFieldValidationResult) {
                    runValidatorCallback(asyncFieldValidationResult);
                });

                //sync code
                if (syncFieldValidationResponse !== undefined) {
                    runValidatorCallback(syncFieldValidationResponse);
                }
            });
        }
        else {
            expectedCalls++;
            //no validation found
            runValidatorCallback(true);
        }
    }

    //START
    async.forEach(_.keys(schema), runFieldValidation, function (err) {

        if (err) {
            throw err;
        }

        callback(result);
    });
}

/**
 * merge failedFields of localValidation to the global failedFields object
 * @param overallResult
 * @param resultToAppend
 * @return {*}
 */
function extendFailedFields(overallResult, resultToAppend) {

    _(resultToAppend.failedFields).each(function (failedField, key) {

        if (overallResult.failedFields[key] === undefined) {
            overallResult.failedFields[key] = failedField;
        }
        else {
            overallResult.failedFields[key] = overallResult.failedFields[key].concat(failedField);
        }
    });

    return overallResult;
}

/**
 * Validate model-data on shared, local and remote schemas.
 * If used on client, the validator will also call remote validation
 * @param {Object} sharedSchema
 * @param {Object} localSchema
 * @param {String} modelUrl
 * @param {Object} modelData
 * @param {String} remote
 * @param {Function} callback
 */
function validate(sharedSchema, localSchema, modelUrl, modelData, remote, callback) {

    var overallResult = {
        result : false,
        failedFields : {}
    };

    async.series({
            //validate SHARED Schema locally
            localSharedVal : function (cb) {

                localValidation(sharedSchema, modelData, function (sharedResult) {

                    overallResult.result = sharedResult.result;
                    overallResult.shared = sharedResult;

                    //maybe don't skip here
                    if (sharedResult.result === false) {
                        extendFailedFields(overallResult, sharedResult);
                    }

                    //check for second full-validation if shared worked out
                    cb(null);
                });
            },
            //validate LocalSchema if existent locally
            localVal : function (cb) {

                if (sharedSchema === localSchema) {
                    //SharedSchema and LocalSchema are identically. Skipping localSchema-Validation.
                    setImmediate(cb, null);
                    return;
                }

                localValidation(localSchema, modelData, function (localResult) {

                    overallResult.local = localResult;

                    if (localResult.result === false) {

                        overallResult.result = false;
                        extendFailedFields(overallResult, localResult);

                        cb(new Error("Validation failed"));
                        return;
                    }

                    cb(null);
                });
            },
            //validate remotely
            remoteValidation : function (cb) {

                if (remote === false || env.isServer()) {
                    //needed to call the final callback
                    cb(new Error("Remote is disabled. Skipping."));
                    return;
                }

                remoteValidation(modelUrl, modelData, function (remoteResult) {

                    if (remoteResult.result === false) {
                        overallResult.result = false;
                        extendFailedFields(overallResult, remoteResult);
                    }

                    overallResult.remote = remoteResult;

                    cb();
                });
            }
        },
        function (err) {
            callback(overallResult);
        });
}

exports.localValidation = localValidation;
exports.validate = validate;
exports.remoteValidation = remoteValidation;<|endoftext|>


<commit_before>import { onBlur } from "../display/focus.js"
import { getGutters, updateGutters } from "../display/gutters.js"
import { loadMode, resetModeState } from "../display/mode_state.js"
import { initScrollbars, updateScrollbars } from "../display/scrollbars.js"
import { updateSelection } from "../display/selection.js"
import { regChange } from "../display/view_tracking.js"
import { getKeyMap } from "../input/keymap.js"
import { defaultSpecialCharPlaceholder } from "../line/line_data.js"
import { Pos } from "../line/pos.js"
import { findMaxLine } from "../line/spans.js"
import { clearCaches, compensateForHScroll, estimateLineHeights } from "../measurement/position_measurement.js"
import { replaceRange } from "../model/changes.js"
import { mobile, windows } from "../util/browser.js"
import { addClass, rmClass } from "../util/dom.js"
import { off, on } from "../util/event.js"

import { themeChanged } from "./utils.js"

export let Init = {toString: function(){return "CodeMirror.Init"}}

export let defaults = {}
export let optionHandlers = {}

export function defineOptions(CodeMirror) {
  let optionHandlers = CodeMirror.optionHandlers

  function option(name, deflt, handle, notOnInit) {
    CodeMirror.defaults[name] = deflt
    if (handle) optionHandlers[name] =
      notOnInit ? (cm, val, old) => {if (old != Init) handle(cm, val, old)} : handle
  }

  CodeMirror.defineOption = option

  // Passed to option handlers when there is no old value.
  CodeMirror.Init = Init

  // These two are, on init, called from the constructor because they
  // have to be initialized before the editor can start at all.
  option("value", "", (cm, val) => cm.setValue(val), true)
  option("mode", null, (cm, val) => {
    cm.doc.modeOption = val
    loadMode(cm)
  }, true)

  option("indentUnit", 2, loadMode, true)
  option("indentWithTabs", false)
  option("smartIndent", true)
  option("tabSize", 4, cm => {
    resetModeState(cm)
    clearCaches(cm)
    regChange(cm)
  }, true)

  option("lineSeparator", null, (cm, val) => {
    cm.doc.lineSep = val
    if (!val) return
    let newBreaks = [], lineNo = cm.doc.first
    cm.doc.iter(line => {
      for (let pos = 0;;) {
        let found = line.text.indexOf(val, pos)
        if (found == -1) break
        pos = found + val.length
        newBreaks.push(Pos(lineNo, found))
      }
      lineNo++
    })
    for (let i = newBreaks.length - 1; i >= 0; i--)
      replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length))
  })
  option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200c\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g, (cm, val, old) => {
    cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g")
    if (old != Init) cm.refresh()
  })
  option("specialCharPlaceholder", defaultSpecialCharPlaceholder, cm => cm.refresh(), true)
  option("electricChars", true)
  option("inputStyle", mobile ? "contenteditable" : "textarea", () => {
    throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME
  }, true)
  option("spellcheck", false, (cm, val) => cm.getInputField().spellcheck = val, true)
  option("autocorrect", false, (cm, val) => cm.getInputField().autocorrect = val, true)
  option("autocapitalize", false, (cm, val) => cm.getInputField().autocapitalize = val, true)
  option("rtlMoveVisually", !windows)
  option("wholeLineUpdateBefore", true)

  option("theme", "default", cm => {
    themeChanged(cm)
    updateGutters(cm)
  }, true)
  option("keyMap", "default", (cm, val, old) => {
    let next = getKeyMap(val)
    let prev = old != Init && getKeyMap(old)
    if (prev && prev.detach) prev.detach(cm, next)
    if (next.attach) next.attach(cm, prev || null)
  })
  option("extraKeys", null)
  option("configureMouse", null)

  option("lineWrapping", false, wrappingChanged, true)
  option("gutters", [], (cm, val) => {
    cm.display.gutterSpecs = getGutters(val, cm.options.lineNumbers)
    updateGutters(cm)
  }, true)
  option("fixedGutter", true, (cm, val) => {
    cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"
    cm.refresh()
  }, true)
  option("coverGutterNextToScrollbar", false, cm => updateScrollbars(cm), true)
  option("scrollbarStyle", "native", cm => {
    initScrollbars(cm)
    updateScrollbars(cm)
    cm.display.scrollbars.setScrollTop(cm.doc.scrollTop)
    cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft)
  }, true)
  option("lineNumbers", false, (cm, val) => {
    cm.display.gutterSpecs = getGutters(cm.options.gutters, val)
    updateGutters(cm)
  }, true)
  option("firstLineNumber", 1, updateGutters, true)
  option("lineNumberFormatter", integer => integer, updateGutters, true)
  option("showCursorWhenSelecting", false, updateSelection, true)

  option("resetSelectionOnContextMenu", true)
  option("lineWiseCopyCut", true)
  option("pasteLinesPerSelection", true)
  option("selectionsMayTouch", false)

  option("readOnly", false, (cm, val) => {
    if (val == "nocursor") {
      onBlur(cm)
      cm.display.input.blur()
    }
    cm.display.input.readOnlyChanged(val)
  })

  option("screenReaderLabel", null, (cm, val) => {
    val = (val === '') ? null : val
    cm.display.input.screenReaderLabelChanged(val)
  })

  option("disableInput", false, (cm, val) => {if (!val) cm.display.input.reset()}, true)
  option("dragDrop", true, dragDropChanged)
  option("allowDropFileTypes", null)

  option("cursorBlinkRate", 530)
  option("cursorScrollMargin", 0)
  option("cursorHeight", 1, updateSelection, true)
  option("singleCursorHeightPerLine", true, updateSelection, true)
  option("workTime", 100)
  option("workDelay", 100)
  option("flattenSpans", true, resetModeState, true)
  option("addModeClass", false, resetModeState, true)
  option("pollInterval", 100)
  option("undoDepth", 200, (cm, val) => cm.doc.history.undoDepth = val)
  option("historyEventDelay", 1250)
  option("viewportMargin", 10, cm => cm.refresh(), true)
  option("maxHighlightLength", 10000, resetModeState, true)
  option("moveInputWithCursor", true, (cm, val) => {
    if (!val) cm.display.input.resetPosition()
  })

  option("tabindex", null, (cm, val) => cm.display.input.getField().tabIndex = val || "")
  option("autofocus", null)
  option("direction", "ltr", (cm, val) => cm.doc.setDirection(val), true)
  option("phrases", null)
}

function dragDropChanged(cm, value, old) {
  let wasOn = old && old != Init
  if (!value != !wasOn) {
    let funcs = cm.display.dragFunctions
    let toggle = value ? on : off
    toggle(cm.display.scroller, "dragstart", funcs.start)
    toggle(cm.display.scroller, "dragenter", funcs.enter)
    toggle(cm.display.scroller, "dragover", funcs.over)
    toggle(cm.display.scroller, "dragleave", funcs.leave)
    toggle(cm.display.scroller, "drop", funcs.drop)
  }
}

function wrappingChanged(cm) {
  if (cm.options.lineWrapping) {
    addClass(cm.display.wrapper, "CodeMirror-wrap")
    cm.display.sizer.style.minWidth = ""
    cm.display.sizerWidth = null
  } else {
    rmClass(cm.display.wrapper, "CodeMirror-wrap")
    findMaxLine(cm)
  }
  estimateLineHeights(cm)
  regChange(cm)
  clearCaches(cm)
  setTimeout(() => updateScrollbars(cm), 100)
}
<commit_msg>Don't override the way zero-width non-joiners are rendered<commit_after>import { onBlur } from "../display/focus.js"
import { getGutters, updateGutters } from "../display/gutters.js"
import { loadMode, resetModeState } from "../display/mode_state.js"
import { initScrollbars, updateScrollbars } from "../display/scrollbars.js"
import { updateSelection } from "../display/selection.js"
import { regChange } from "../display/view_tracking.js"
import { getKeyMap } from "../input/keymap.js"
import { defaultSpecialCharPlaceholder } from "../line/line_data.js"
import { Pos } from "../line/pos.js"
import { findMaxLine } from "../line/spans.js"
import { clearCaches, compensateForHScroll, estimateLineHeights } from "../measurement/position_measurement.js"
import { replaceRange } from "../model/changes.js"
import { mobile, windows } from "../util/browser.js"
import { addClass, rmClass } from "../util/dom.js"
import { off, on } from "../util/event.js"

import { themeChanged } from "./utils.js"

export let Init = {toString: function(){return "CodeMirror.Init"}}

export let defaults = {}
export let optionHandlers = {}

export function defineOptions(CodeMirror) {
  let optionHandlers = CodeMirror.optionHandlers

  function option(name, deflt, handle, notOnInit) {
    CodeMirror.defaults[name] = deflt
    if (handle) optionHandlers[name] =
      notOnInit ? (cm, val, old) => {if (old != Init) handle(cm, val, old)} : handle
  }

  CodeMirror.defineOption = option

  // Passed to option handlers when there is no old value.
  CodeMirror.Init = Init

  // These two are, on init, called from the constructor because they
  // have to be initialized before the editor can start at all.
  option("value", "", (cm, val) => cm.setValue(val), true)
  option("mode", null, (cm, val) => {
    cm.doc.modeOption = val
    loadMode(cm)
  }, true)

  option("indentUnit", 2, loadMode, true)
  option("indentWithTabs", false)
  option("smartIndent", true)
  option("tabSize", 4, cm => {
    resetModeState(cm)
    clearCaches(cm)
    regChange(cm)
  }, true)

  option("lineSeparator", null, (cm, val) => {
    cm.doc.lineSep = val
    if (!val) return
    let newBreaks = [], lineNo = cm.doc.first
    cm.doc.iter(line => {
      for (let pos = 0;;) {
        let found = line.text.indexOf(val, pos)
        if (found == -1) break
        pos = found + val.length
        newBreaks.push(Pos(lineNo, found))
      }
      lineNo++
    })
    for (let i = newBreaks.length - 1; i >= 0; i--)
      replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length))
  })
  option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g, (cm, val, old) => {
    cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g")
    if (old != Init) cm.refresh()
  })
  option("specialCharPlaceholder", defaultSpecialCharPlaceholder, cm => cm.refresh(), true)
  option("electricChars", true)
  option("inputStyle", mobile ? "contenteditable" : "textarea", () => {
    throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME
  }, true)
  option("spellcheck", false, (cm, val) => cm.getInputField().spellcheck = val, true)
  option("autocorrect", false, (cm, val) => cm.getInputField().autocorrect = val, true)
  option("autocapitalize", false, (cm, val) => cm.getInputField().autocapitalize = val, true)
  option("rtlMoveVisually", !windows)
  option("wholeLineUpdateBefore", true)

  option("theme", "default", cm => {
    themeChanged(cm)
    updateGutters(cm)
  }, true)
  option("keyMap", "default", (cm, val, old) => {
    let next = getKeyMap(val)
    let prev = old != Init && getKeyMap(old)
    if (prev && prev.detach) prev.detach(cm, next)
    if (next.attach) next.attach(cm, prev || null)
  })
  option("extraKeys", null)
  option("configureMouse", null)

  option("lineWrapping", false, wrappingChanged, true)
  option("gutters", [], (cm, val) => {
    cm.display.gutterSpecs = getGutters(val, cm.options.lineNumbers)
    updateGutters(cm)
  }, true)
  option("fixedGutter", true, (cm, val) => {
    cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"
    cm.refresh()
  }, true)
  option("coverGutterNextToScrollbar", false, cm => updateScrollbars(cm), true)
  option("scrollbarStyle", "native", cm => {
    initScrollbars(cm)
    updateScrollbars(cm)
    cm.display.scrollbars.setScrollTop(cm.doc.scrollTop)
    cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft)
  }, true)
  option("lineNumbers", false, (cm, val) => {
    cm.display.gutterSpecs = getGutters(cm.options.gutters, val)
    updateGutters(cm)
  }, true)
  option("firstLineNumber", 1, updateGutters, true)
  option("lineNumberFormatter", integer => integer, updateGutters, true)
  option("showCursorWhenSelecting", false, updateSelection, true)

  option("resetSelectionOnContextMenu", true)
  option("lineWiseCopyCut", true)
  option("pasteLinesPerSelection", true)
  option("selectionsMayTouch", false)

  option("readOnly", false, (cm, val) => {
    if (val == "nocursor") {
      onBlur(cm)
      cm.display.input.blur()
    }
    cm.display.input.readOnlyChanged(val)
  })

  option("screenReaderLabel", null, (cm, val) => {
    val = (val === '') ? null : val
    cm.display.input.screenReaderLabelChanged(val)
  })

  option("disableInput", false, (cm, val) => {if (!val) cm.display.input.reset()}, true)
  option("dragDrop", true, dragDropChanged)
  option("allowDropFileTypes", null)

  option("cursorBlinkRate", 530)
  option("cursorScrollMargin", 0)
  option("cursorHeight", 1, updateSelection, true)
  option("singleCursorHeightPerLine", true, updateSelection, true)
  option("workTime", 100)
  option("workDelay", 100)
  option("flattenSpans", true, resetModeState, true)
  option("addModeClass", false, resetModeState, true)
  option("pollInterval", 100)
  option("undoDepth", 200, (cm, val) => cm.doc.history.undoDepth = val)
  option("historyEventDelay", 1250)
  option("viewportMargin", 10, cm => cm.refresh(), true)
  option("maxHighlightLength", 10000, resetModeState, true)
  option("moveInputWithCursor", true, (cm, val) => {
    if (!val) cm.display.input.resetPosition()
  })

  option("tabindex", null, (cm, val) => cm.display.input.getField().tabIndex = val || "")
  option("autofocus", null)
  option("direction", "ltr", (cm, val) => cm.doc.setDirection(val), true)
  option("phrases", null)
}

function dragDropChanged(cm, value, old) {
  let wasOn = old && old != Init
  if (!value != !wasOn) {
    let funcs = cm.display.dragFunctions
    let toggle = value ? on : off
    toggle(cm.display.scroller, "dragstart", funcs.start)
    toggle(cm.display.scroller, "dragenter", funcs.enter)
    toggle(cm.display.scroller, "dragover", funcs.over)
    toggle(cm.display.scroller, "dragleave", funcs.leave)
    toggle(cm.display.scroller, "drop", funcs.drop)
  }
}

function wrappingChanged(cm) {
  if (cm.options.lineWrapping) {
    addClass(cm.display.wrapper, "CodeMirror-wrap")
    cm.display.sizer.style.minWidth = ""
    cm.display.sizerWidth = null
  } else {
    rmClass(cm.display.wrapper, "CodeMirror-wrap")
    findMaxLine(cm)
  }
  estimateLineHeights(cm)
  regChange(cm)
  clearCaches(cm)
  setTimeout(() => updateScrollbars(cm), 100)
}
<|endoftext|>