<commit_before>import {
  IncrementalData, Document,
  PropertyIndex, AnnotationIndex,
  DocumentNodeFactory
} from '../model'
import { uuid } from '../util'
import { DefaultDOMElement } from '../dom'

import ParentNodeHook from './ParentNodeHook'
import XMLEditingInterface from './XMLEditingInterface'

export default
class XMLDocument extends Document {

  _initialize() {
    this.nodeFactory = new DocumentNodeFactory(this)
    this.data = new IncrementalData(this.schema, this.nodeFactory)
    // all by type
    this.addIndex('type', new PropertyIndex('type'))
    // special index for (property-scoped) annotations
    this.addIndex('annotations', new AnnotationIndex())
    ParentNodeHook.register(this)
  }

  toXML() {
    let dom = DefaultDOMElement.createDocument('xml')
    dom.setDocType(...this.getDocTypeParams())
    let rootElement = this.getRootNode().toXML()
    dom.append(rootElement)
    return dom
  }

  getDocTypeParams() {
    // return [qualifiedNameStr, publicId, systemId]
    throw new Error('This method is abstract')
  }

  getXMLSchema() {
    // should provide an XMLSchema instance
    throw new Error('This method is abstract')
  }

  getRootNode() {
    // should provide the root-element
    throw new Error('This method is abstract')
  }

  /*
    Provide a <!DOCTYPE ...> element as a string here
  */
  getDocTypeAsString() {
    return new Error('This method is abstract')
  }

  createEditingInterface() {
    return new XMLEditingInterface(this)
  }

  find(cssSelector) {
    return this.getRootNode().find(cssSelector)
  }

  findAll(cssSelector) {
    return this.getRootNode().findAll(cssSelector)
  }

  createElement(tagName) {
    let node = this.create({
      id: uuid(tagName),
      type: tagName
    })
    return node
  }

  getElementSchema(type) {
    return this.getXMLSchema().getElementSchema(type)
  }

  _validateChange(change) {
    let changed = {}
    let deleted = []
    change.ops.forEach((op) => {
      switch (op.type) {
        case "delete": {
          deleted.push(op.val.id)
          break
        }
        case "create": {
          changed[op.val.id] = true
          break
        }
        default: {
          changed[op.path[0]] = true
        }
      }
    })
    // do not validate deleted nodes
    deleted.forEach(id => delete changed[id])

    const xmlSchema = this.getXMLSchema()
    let errors = []
    Object.keys(changed).forEach((id) => {
      let node = this.get(id)
      let res = xmlSchema.validateElement(node)
      if (!res.ok) {
        errors = errors.concat(res.errors)
      }
    })
    return {
      ok: errors.length === 0,
      errors
    }
  }

  /*
    Experimental: analyzing the change on-the-fly
    so that we can track changes hierarchically
  */
  _apply(documentChange) {
    // TODO is this save?
    if (!documentChange.ops) return

    let created = {}
    let deleted = {}
    let updated = {}
    let _recordUpdate = (id, level, op) => {
      let record = updated[id]
      if (!record) {
        record = updated[id] = { ops: [], level }
      } else {
        record.level = Math.min(level, record.level)
      }
      if (op) record.ops.push(op)
      return record
    }
    let _recordAncestorUpdates = (node) => {
      this._forEachAncestor(node, (ancestor, level) => {
        _recordUpdate(ancestor.id, level)
      })
    }
    documentChange.ops.forEach((op) => {
      // before applying the change we need to track the change
      // Note: this does not work for general ops,
      // only for ops generated via the XMLDocument API
      switch(op.type) {
        case 'create': {
          created[op.path[0]] = op.val
          break
        }
        case 'delete': {
          let node = this.get(op.path[0])
          _recordAncestorUpdates(node)
          deleted[node.id] = node
          break
        }
        case 'update':
        case 'set': {
          updated[op.path] = true
          let node = this.get(op.path[0])
          _recordUpdate(node.id, 0, op)
          _recordAncestorUpdates(node)
          break
        }
        default:
          //
      }
      this._applyOp(op)
    })
    // clear updates for deleted nodes
    Object.keys(deleted).forEach((id) => {
      delete updated[id]
    })
    documentChange.created = created
    documentChange.updated = updated
    documentChange.deleted = deleted
    documentChange._extracted = true
  }

  _forEachAncestor(node, fn) {
    let level = 0
    while ((node = node.parentNode)) {
      level++
      fn(node, level)
    }
  }

}

XMLDocument.prototype._isXMLDocument = true
<commit_msg>Detect updates caused by annotations in XMLDocument.<commit_after>import {
  IncrementalData, Document,
  PropertyIndex, AnnotationIndex,
  DocumentNodeFactory
} from '../model'
import { uuid } from '../util'
import { DefaultDOMElement } from '../dom'

import ParentNodeHook from './ParentNodeHook'
import XMLEditingInterface from './XMLEditingInterface'

export default
class XMLDocument extends Document {

  _initialize() {
    this.nodeFactory = new DocumentNodeFactory(this)
    this.data = new IncrementalData(this.schema, this.nodeFactory)
    // all by type
    this.addIndex('type', new PropertyIndex('type'))
    // special index for (property-scoped) annotations
    this.addIndex('annotations', new AnnotationIndex())
    ParentNodeHook.register(this)
  }

  toXML() {
    let dom = DefaultDOMElement.createDocument('xml')
    dom.setDocType(...this.getDocTypeParams())
    let rootElement = this.getRootNode().toXML()
    dom.append(rootElement)
    return dom
  }

  getDocTypeParams() {
    // return [qualifiedNameStr, publicId, systemId]
    throw new Error('This method is abstract')
  }

  getXMLSchema() {
    // should provide an XMLSchema instance
    throw new Error('This method is abstract')
  }

  getRootNode() {
    // should provide the root-element
    throw new Error('This method is abstract')
  }

  /*
    Provide a <!DOCTYPE ...> element as a string here
  */
  getDocTypeAsString() {
    return new Error('This method is abstract')
  }

  createEditingInterface() {
    return new XMLEditingInterface(this)
  }

  find(cssSelector) {
    return this.getRootNode().find(cssSelector)
  }

  findAll(cssSelector) {
    return this.getRootNode().findAll(cssSelector)
  }

  createElement(tagName) {
    let node = this.create({
      id: uuid(tagName),
      type: tagName
    })
    return node
  }

  getElementSchema(type) {
    return this.getXMLSchema().getElementSchema(type)
  }

  _validateChange(change) {
    let changed = {}
    let deleted = []
    change.ops.forEach((op) => {
      switch (op.type) {
        case "delete": {
          deleted.push(op.val.id)
          break
        }
        case "create": {
          changed[op.val.id] = true
          break
        }
        default: {
          changed[op.path[0]] = true
        }
      }
    })
    // do not validate deleted nodes
    deleted.forEach(id => delete changed[id])

    const xmlSchema = this.getXMLSchema()
    let errors = []
    Object.keys(changed).forEach((id) => {
      let node = this.get(id)
      let res = xmlSchema.validateElement(node)
      if (!res.ok) {
        errors = errors.concat(res.errors)
      }
    })
    return {
      ok: errors.length === 0,
      errors
    }
  }

  /*
    Experimental: analyzing the change on-the-fly
    so that we can track changes hierarchically
  */
  _apply(documentChange) {
    // TODO is this save?
    if (!documentChange.ops) return

    let created = {}
    let deleted = {}
    let updated = {}
    let affectedContainerAnnos = []

    const doc = this

    function _recordUpdate(id, level, op) {
      let record = updated[id]
      if (!record) {
        record = updated[id] = { ops: [], level }
      } else {
        record.level = Math.min(level, record.level)
      }
      if (op) record.ops.push(op)
      return record
    }

    function _recordAncestorUpdates(node) {
      doc._forEachAncestor(node, (ancestor, level) => {
        _recordUpdate(ancestor.id, level)
      })
    }

    // TODO: we will introduce a special operation type for coordinates
    function _checkAnnotation(op) {
      // HACK: detecting annotation changes in an opportunistic way
      switch (op.type) {
        case "create":
        case "delete": {
          const annoData = op.val
          if (annoData.hasOwnProperty('start')) {
            updated[annoData.start.path] = true
            let node = doc.get(annoData.start.path[0])
            if (node) {
              _recordUpdate(node.id, 0, op)
              _recordAncestorUpdates(node)
            }
          }
          if (annoData.hasOwnProperty('end')) {
            updated[annoData.end.path] = true
            let node = doc.get(annoData.start.path[0])
            if (node) {
              _recordUpdate(node.id, 0, op)
              _recordAncestorUpdates(node)
            }
          }
          break
        }
        case "update":
        case "set": {
          let anno = doc.get(op.path[0])
          if (anno) {
            if (anno.isPropertyAnnotation()) {
              updated[anno.start.path] = true
              let node = doc.get(anno.start.path[0])
              if (node) {
                _recordUpdate(node.id, 0, op)
                _recordAncestorUpdates(node)
              }
            } else if (anno.isContainerAnnotation()) {
              affectedContainerAnnos.push(anno)
            }
          }
          break
        }
        default:
          /* istanbul ignore next */
          throw new Error('Illegal state')
      }
    }

    documentChange.ops.forEach((op) => {
      // before applying the change we need to track the change
      // Note: this does not work for general ops,
      // only for ops generated via the XMLDocument API
      switch(op.type) {
        case 'create': {
          created[op.path[0]] = op.val
          break
        }
        case 'delete': {
          let node = this.get(op.path[0])
          _recordAncestorUpdates(node)
          deleted[node.id] = node
          break
        }
        case 'update':
        case 'set': {
          updated[op.path] = true
          let node = this.get(op.path[0])
          _recordUpdate(node.id, 0, op)
          _recordAncestorUpdates(node)
          break
        }
        default:
          //
      }
      // record impacts of changes to annotations
      _checkAnnotation(op)

      this._applyOp(op)
    })
    // clear updates for deleted nodes
    Object.keys(deleted).forEach((id) => {
      delete updated[id]
    })
    documentChange.created = created
    documentChange.updated = updated
    documentChange.deleted = deleted
    documentChange._extracted = true
  }

  _forEachAncestor(node, fn) {
    let level = 0
    while ((node = node.parentNode)) {
      level++
      fn(node, level)
    }
  }

}

XMLDocument.prototype._isXMLDocument = true
<|endoftext|>


<commit_before>import {Pos} from "../model"

export class Chunk {
  constructor(before, sizeBefore, after, sizeAfter) {
    this.before = before
    this.sizeBefore = sizeBefore
    this.after = after
    this.sizeAfter = sizeAfter
  }

  get endAfter() {
    return new Pos(this.after.path, this.after.offset + this.sizeAfter)
  }
  get endBefore() {
    return new Pos(this.before.path, this.before.offset + this.sizeBefore)
  }

  toString() {
    return this.before + "+" + this.sizeBefore + " -> " + this.after + "+" + this.sizeAfter
  }
}

export class Result {
  constructor(before, after) {
    this.before = before
    this.doc = after
    this.chunks = []
  }

  chunk(before, sizeBefore, after, sizeAfter=sizeBefore) {
    if (before.cmp(after) || sizeBefore != sizeAfter)
      this.chunks.push(new Chunk(before, sizeBefore, after, sizeAfter))
  }

  // FIXME This is too suble and messy. At some point, try to clean it up.
  mapDir(pos, back, offset) {
    let deletedID = 0, insertedID = 0
    let isRecover = offset && offset.rangeID != null

    for (let i = 0; i < this.chunks.length; i++) {
      let sizeBefore, sizeAfter, before, after
      if (back) {
        ({sizeBefore: sizeAfter, sizeAfter: sizeBefore, before: after, after: before}) = this.chunks[i]
      } else {
        ({sizeBefore, sizeAfter, before, after}) = this.chunks[i]
      }

      if (sizeBefore == 0 && sizeAfter > 0) { // Inserted chunk
        if (isRecover && insertedID == offset.rangeID)
          return after.extend(offset.offset)
        ++insertedID
        continue
      }

      let deleted = sizeBefore > 0 && sizeAfter == 0
      if (!isRecover && pos.cmp(before) >= 0) {
        if (Pos.cmp(pos.path, pos.offset, before.path, before.offset + sizeBefore) <= 0) {
          let depth = before.path.length
          if (deleted) {
            if (offset) offset({rangeID: deletedID, offset: pos.baseOn(before)})
            return Pos.after(this.doc, after) || Pos.before(this.doc, after)
          } else if (pos.path.length > depth) {
            let offset = after.offset + (pos.path[depth] - before.offset)
            return new Pos(after.path.concat(offset).concat(pos.path.slice(depth + 1)), pos.offset)
          } else {
            return new Pos(after.path, after.offset + (pos.offset - before.offset))
          }
        }
      } else if (!isRecover && !back) {
        break
      }
      if (deleted) ++deletedID
    }
    return pos
  }

  map(pos, offset = null) { return this.mapDir(pos, false, offset) }
  mapBack(pos, offset = null) { return this.mapDir(pos, true, offset) }
}

const transforms = Object.create(null)

export function defineTransform(name, impl) {
  transforms[name] = impl
}

export function applyTransform(doc, params) {
  let fn = transforms[params.name]
  if (!fn) throw new Error("Undefined transform " + params.name)
  return fn(doc, params)
}

export function flatTransform(doc, result) {
  return new Result(doc, result || doc)
}
<commit_msg>Clean up position mapping algorithm<commit_after>import {Pos} from "../model"

export class Chunk {
  constructor(before, sizeBefore, after, sizeAfter) {
    this.before = before
    this.sizeBefore = sizeBefore
    this.after = after
    this.sizeAfter = sizeAfter
  }

  get endAfter() {
    return new Pos(this.after.path, this.after.offset + this.sizeAfter)
  }
  get endBefore() {
    return new Pos(this.before.path, this.before.offset + this.sizeBefore)
  }

  toString() {
    return this.before + "+" + this.sizeBefore + " -> " + this.after + "+" + this.sizeAfter
  }
}

function isInChunk(pos, before, sizeBefore) {
  return pos.cmp(before) >= 0 &&
    Pos.cmp(pos.path, pos.offset, before.path, before.offset + sizeBefore) <= 0
}

export class Result {
  constructor(before, after) {
    this.before = before
    this.doc = after
    this.chunks = []
  }

  chunk(before, sizeBefore, after, sizeAfter=sizeBefore) {
    if (before.cmp(after) || sizeBefore != sizeAfter)
      this.chunks.push(new Chunk(before, sizeBefore, after, sizeAfter))
  }

  iterChunks(back, type, f) {
    for (let i = 0, off = 0; i < this.chunks.length; i++) {
      let sizeBefore, sizeAfter, before, after
      if (back) {
        ({sizeBefore: sizeAfter, sizeAfter: sizeBefore, before: after, after: before}) = this.chunks[i]
      } else {
        ({sizeBefore, sizeAfter, before, after}) = this.chunks[i]
      }

      if (type == (sizeBefore == sizeAfter ? "normal" : sizeBefore ? "deleted" : "inserted")) {
        let value = f(before, sizeBefore, after, sizeAfter, off++)
        if (value != null) return value
      }
    }
  }

  mapDir(pos, back, offset) {
    if (offset && offset.rangeID != null)
      return this.iterChunks(back, "inserted", (_before, _sizeBefore, after, _sizeAfter, i) => {
        if (i == offset.rangeID) return after.extend(offset.offset)
      })

    let normal = this.iterChunks(back, "normal", (before, sizeBefore, after) => {
      if (isInChunk(pos, before, sizeBefore)) {
        let depth = before.path.length
        if (pos.path.length > depth) {
          let offset = after.offset + (pos.path[depth] - before.offset)
          return new Pos(after.path.concat(offset).concat(pos.path.slice(depth + 1)), pos.offset)
        } else {
          return new Pos(after.path, after.offset + (pos.offset - before.offset))
        }
      }
    })
    if (normal) return normal

    let deleted = this.iterChunks(back, "deleted", (before, sizeBefore, after, _, i) => {
      if (isInChunk(pos, before, sizeBefore)) {
        if (offset) offset({rangeID: i, offset: pos.baseOn(before)})
        return Pos.after(this.doc, after) || Pos.before(this.doc, after)
      }
    })

    return deleted || pos
  }

  map(pos, offset = null) { return this.mapDir(pos, false, offset) }
  mapBack(pos, offset = null) { return this.mapDir(pos, true, offset) }
}

const transforms = Object.create(null)

export function defineTransform(name, impl) {
  transforms[name] = impl
}

export function applyTransform(doc, params) {
  let fn = transforms[params.name]
  if (!fn) throw new Error("Undefined transform " + params.name)
  return fn(doc, params)
}

export function flatTransform(doc, result) {
  return new Result(doc, result || doc)
}
<|endoftext|>


<commit_before>export default function(xy, dim) {
  // console.log('NODE', this.node());
  return this.node().getBBox ?
    this.attr('transform', function(d,i) {
      var p = typeof xy == 'function' ? xy.call(this, d,i) : xy;
      if (dim === 0) p = [p, 0]; else if (dim === 1) p = [0, p];
      return 'translate(' + p[0] +','+ p[1]+')';
    }) :
    this.style('transform', function(d,i) {
      var p = typeof xy == 'function' ? xy.call(this, d,i) : xy;
      if (dim === 0) p = [p, 0]; else if (dim === 1) p = [0, p];
      return 'translate(' + p[0] +'px,'+ p[1]+'px)';
    });
}
<commit_msg>no ugly comments<commit_after>export default function(xy, dim) {
  return this.node().getBBox ?
    this.attr('transform', function(d,i) {
      var p = typeof xy == 'function' ? xy.call(this, d,i) : xy;
      if (dim === 0) p = [p, 0]; else if (dim === 1) p = [0, p];
      return 'translate(' + p[0] +','+ p[1]+')';
    }) :
    this.style('transform', function(d,i) {
      var p = typeof xy == 'function' ? xy.call(this, d,i) : xy;
      if (dim === 0) p = [p, 0]; else if (dim === 1) p = [0, p];
      return 'translate(' + p[0] +'px,'+ p[1]+'px)';
    });
}
<|endoftext|>


<commit_before>/**
 * Adapted from angular2-webpack-starter
 */

const helpers = require('./helpers'),
    LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin');

/**
 * Webpack Plugins
 */

module.exports = {

    /**
     * Source map for Karma from the help of karma-sourcemap-loader &  karma-webpack
     *
     * Do not change, leave as is or it wont work.
     * See: https://github.com/webpack/karma-webpack#source-maps
     */
    devtool: 'inline-source-map',

    resolve: {
        extensions: ['.ts', '.js'],
        modules: [helpers.root('src'), 'node_modules']
    },

    module: {
        rules: [{
            enforce: 'pre',
            test: /\.ts$/,
            loader: 'tslint',
            exclude: [helpers.root('node_modules')]
        }, {
            enforce: 'pre',
            test: /\.js$/,
            loader: 'source-map-loader',
            exclude: [
                // these packages have problems with their sourcemaps
                helpers.root('node_modules/rxjs'),
                helpers.root('node_modules/@angular')
            ]
        }, {
            test: /\.ts$/,
            loader: 'awesome-typescript-loader',
            query: {
                // use inline sourcemaps for "karma-remap-coverage" reporter
                sourceMap: false,
                inlineSourceMap: true,
                module: "commonjs",
                removeComments: true
            },
            exclude: [/\.e2e\.ts$/]
        }, {
            enforce: 'post',
            test: /\.(js|ts)$/,
            loader: 'istanbul-instrumenter-loader',
            include: helpers.root('src'),
            exclude: [/\.spec\.ts$/, /\.e2e\.ts$/, /node_modules/]
        }],
    },

    plugins: [
        // fix the warning in ./~/@angular/core/src/linker/system_js_ng_module_factory_loader.js
        new webpack.ContextReplacementPlugin(
            /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
            helpers.root('./src')
        ),

        new LoaderOptionsPlugin({
            debug: true,
            options: {

                /**
                 * Static analysis linter for TypeScript advanced options configuration
                 * Description: An extensible linter for the TypeScript language.
                 *
                 * See: https://github.com/wbuchwalter/tslint-loader
                 */
                tslint: {
                    emitErrors: false,
                    failOnHint: false,
                    resourcePath: 'src'
                },

            }
        })
    ]
};
<commit_msg>chore: fixed tests config<commit_after>/**
 * Adapted from angular2-webpack-starter
 */

const helpers = require('./helpers'),
    webpack = require('webpack'),
    LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin');

/**
 * Webpack Plugins
 */

module.exports = {

    /**
     * Source map for Karma from the help of karma-sourcemap-loader &  karma-webpack
     *
     * Do not change, leave as is or it wont work.
     * See: https://github.com/webpack/karma-webpack#source-maps
     */
    devtool: 'inline-source-map',

    resolve: {
        extensions: ['.ts', '.js'],
        modules: [helpers.root('src'), 'node_modules']
    },

    module: {
        rules: [{
            enforce: 'pre',
            test: /\.ts$/,
            loader: 'tslint',
            exclude: [helpers.root('node_modules')]
        }, {
            enforce: 'pre',
            test: /\.js$/,
            loader: 'source-map-loader',
            exclude: [
                // these packages have problems with their sourcemaps
                helpers.root('node_modules/rxjs'),
                helpers.root('node_modules/@angular')
            ]
        }, {
            test: /\.ts$/,
            loader: 'awesome-typescript-loader',
            query: {
                // use inline sourcemaps for "karma-remap-coverage" reporter
                sourceMap: false,
                inlineSourceMap: true,
                module: "commonjs",
                removeComments: true
            },
            exclude: [/\.e2e\.ts$/]
        }, {
            enforce: 'post',
            test: /\.(js|ts)$/,
            loader: 'istanbul-instrumenter-loader',
            include: helpers.root('src'),
            exclude: [/\.spec\.ts$/, /\.e2e\.ts$/, /node_modules/]
        }],
    },

    plugins: [
        // fix the warning in ./~/@angular/core/src/linker/system_js_ng_module_factory_loader.js
        new webpack.ContextReplacementPlugin(
            /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
            helpers.root('./src')
        ),

        new LoaderOptionsPlugin({
            debug: true,
            options: {

                /**
                 * Static analysis linter for TypeScript advanced options configuration
                 * Description: An extensible linter for the TypeScript language.
                 *
                 * See: https://github.com/wbuchwalter/tslint-loader
                 */
                tslint: {
                    emitErrors: false,
                    failOnHint: false,
                    resourcePath: 'src'
                },

            }
        })
    ]
};
<|endoftext|>


<commit_before>var glob = require('glob');
var path = require('path');

function loadTasks(gulp, plugins, paths) {
	var taskNames = [];

	// Load all tasks from tasks folder
	glob.sync('../tasks/*.js').forEach(function(filePath) {
		var taskName = path.basename(filePath, '.js');

		taskNames.push(taskName);

		var task = require(filePath).task;
		var deps = require(filePath).deps || [];

		gulp.task(taskName, deps, function() {
			task(gulp, plugins, paths)
		});
	});

	return taskNames;
}

module.exports.loadTasks = loadTasks;<commit_msg>fix build tasks load<commit_after>var glob = require('glob');
var path = require('path');

function loadTasks(gulp, plugins, paths) {
	var taskNames = [];

	// Load all tasks from tasks folder
	glob.sync(path.resolve(__dirname, '../tasks/*.js')).forEach(function(filePath) {
		var taskName = path.basename(filePath, '.js');

		taskNames.push(taskName);

		var task = require(filePath).task;
		var deps = require(filePath).deps || [];

		gulp.task(taskName, deps, function() {
			task(gulp, plugins, paths)
		});
	});

	return taskNames;
}

module.exports.loadTasks = loadTasks;<|endoftext|>


<commit_before>var customLaunchers = {
  'DartiumWithWebPlatform': {
    base: 'Dartium',
    flags: ['--enable-experimental-web-platform-features'] },
  'ChromeNoSandbox': {
    base: 'Chrome',
    flags: ['--no-sandbox'] },
  'SL_CHROME': {
    base: 'SauceLabs',
    browserName: 'chrome',
    version: '46'
  },
  'SL_CHROMEBETA': {
    base: 'SauceLabs',
    browserName: 'chrome',
    version: 'beta'
  },
  'SL_CHROMEDEV': {
    base: 'SauceLabs',
    browserName: 'chrome',
    version: 'dev'
  },
  'SL_FIREFOX': {
    base: 'SauceLabs',
    browserName: 'firefox',
    version: '42'
  },
  'SL_FIREFOXBETA': {
    base: 'SauceLabs',
    browserName: 'firefox',
    version: 'beta'
  },
  'SL_FIREFOXDEV': {
    base: 'SauceLabs',
    browserName: 'firefox',
    version: 'dev'
  },
  'SL_SAFARI7': {
    base: 'SauceLabs',
    browserName: 'safari',
    platform: 'OS X 10.9',
    version: '7'
  },
  'SL_SAFARI8': {
    base: 'SauceLabs',
    browserName: 'safari',
    platform: 'OS X 10.10',
    version: '8'
  },
  'SL_SAFARI9.0': {
    base: 'SauceLabs',
    browserName: 'safari',
    platform: 'OS X 10.11',
    version: '9.0'
  },
  'SL_IOS7': {
    base: 'SauceLabs',
    browserName: 'iphone',
    platform: 'OS X 10.10',
    version: '7.1'
  },
  'SL_IOS8': {
    base: 'SauceLabs',
    browserName: 'iphone',
    platform: 'OS X 10.10',
    version: '8.4'
  },
  'SL_IOS9': {
    base: 'SauceLabs',
    browserName: 'iphone',
    platform: 'OS X 10.10',
    version: '9.1'
  },
  'SL_IE9': {
    base: 'SauceLabs',
    browserName: 'internet explorer',
    platform: 'Windows 2008',
    version: '9'
  },
  'SL_IE10': {
    base: 'SauceLabs',
    browserName: 'internet explorer',
    platform: 'Windows 2012',
    version: '10'
  },
  'SL_IE11': {
    base: 'SauceLabs',
    browserName: 'internet explorer',
    platform: 'Windows 8.1',
    version: '11'
  },
  'SL_EDGE': {
    base: 'SauceLabs',
    browserName: 'microsoftedge',
    platform: 'Windows 10',
    version: '20.10240'
  },
  'SL_ANDROID4.1': {
    base: 'SauceLabs',
    browserName: 'android',
    platform: 'Linux',
    version: '4.1'
  },
  'SL_ANDROID4.2': {
    base: 'SauceLabs',
    browserName: 'android',
    platform: 'Linux',
    version: '4.2'
  },
  'SL_ANDROID4.3': {
    base: 'SauceLabs',
    browserName: 'android',
    platform: 'Linux',
    version: '4.3'
  },
  'SL_ANDROID4.4': {
    base: 'SauceLabs',
    browserName: 'android',
    platform: 'Linux',
    version: '4.4'
  },
  'SL_ANDROID5.1': {
    base: 'SauceLabs',
    browserName: 'android',
    platform: 'Linux',
    version: '5.1'
  },

  'BS_CHROME': {
    base: 'BrowserStack',
    browser: 'chrome',
    os: 'OS X',
    os_version: 'Yosemite'
  },
  'BS_FIREFOX': {
    base: 'BrowserStack',
    browser: 'firefox',
    os: 'Windows',
    os_version: '10'
  },
  'BS_SAFARI7': {
    base: 'BrowserStack',
    browser: 'safari',
    os: 'OS X',
    os_version: 'Mavericks'
  },
  'BS_SAFARI8': {
    base: 'BrowserStack',
    browser: 'safari',
    os: 'OS X',
    os_version: 'Yosemite'
  },
  'BS_SAFARI9': {
    base: 'BrowserStack',
    browser: 'safari',
    os: 'OS X',
    os_version: 'El Capitan'
  },
  'BS_IOS7': {
    base: 'BrowserStack',
    device: 'iPhone 5S',
    os: 'ios',
    os_version: '7.0'
  },
  'BS_IOS8': {
    base: 'BrowserStack',
    device: 'iPhone 6',
    os: 'ios',
    os_version: '8.3'
  },
  'BS_IOS9': {
    base: 'BrowserStack',
    device: 'iPhone 6S',
    os: 'ios',
    os_version: '9.0'
  },
  'BS_IE9': {
    base: 'BrowserStack',
    browser: 'ie',
    browser_version: '9.0',
    os: 'Windows',
    os_version: '7'
  },
  'BS_IE10': {
    base: 'BrowserStack',
    browser: 'ie',
    browser_version: '10.0',
    os: 'Windows',
    os_version: '8'
  },
  'BS_IE11': {
    base: 'BrowserStack',
    browser: 'ie',
    browser_version: '11.0',
    os: 'Windows',
    os_version: '10'
  },
  'BS_EDGE': {
    base: 'BrowserStack',
    browser: 'edge',
    os: 'Windows',
    os_version: '10'
  },
  'BS_WINDOWSPHONE' : {
    base: 'BrowserStack',
    device: 'Nokia Lumia 930',
    os: 'winphone',
    os_version: '8.1'
  },
  'BS_ANDROID5': {
    base: 'BrowserStack',
    device: 'Google Nexus 5',
    os: 'android',
    os_version: '5.0'
  },
  'BS_ANDROID4.4': {
    base: 'BrowserStack',
    device: 'HTC One M8',
    os: 'android',
    os_version: '4.4'
  },
  'BS_ANDROID4.3': {
    base: 'BrowserStack',
    device: 'Samsung Galaxy S4',
    os: 'android',
    os_version: '4.3'
  },
  'BS_ANDROID4.2': {
    base: 'BrowserStack',
    device: 'Google Nexus 4',
    os: 'android',
    os_version: '4.2'
  },
  'BS_ANDROID4.1': {
    base: 'BrowserStack',
    device: 'Google Nexus 7',
    os: 'android',
    os_version: '4.1'
  }
};

var sauceAliases = {
  'ALL': Object.keys(customLaunchers).filter(function(item) {return customLaunchers[item].base == 'SauceLabs';}),
  'DESKTOP': ['SL_CHROME', 'SL_FIREFOX', 'SL_IE9', 'SL_IE10', 'SL_IE11', 'SL_EDGE', 'SL_SAFARI7', 'SL_SAFARI8', 'SL_SAFARI9.0'],
  'MOBILE': ['SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5.1', 'SL_IOS7', 'SL_IOS8', 'SL_IOS9'],
  'ANDROID': ['SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5.1'],
  'IE': ['SL_IE9', 'SL_IE10', 'SL_IE11'],
  'IOS': ['SL_IOS7', 'SL_IOS8', 'SL_IOS9'],
  'SAFARI': ['SL_SAFARI7', 'SL_SAFARI8', 'SL_SAFARI9.0'],
  'BETA': ['SL_CHROMEBETA', 'SL_FIREFOXBETA'],
  'DEV': ['SL_CHROMEDEV', 'SL_FIREFOXDEV'],
  'CI': ['SL_CHROME', 'SL_ANDROID5.1', 'SL_SAFARI7', 'SL_SAFARI8', 'SL_SAFARI9.0', 'SL_IOS7', 'SL_IOS8', 'SL_IOS9',
         'SL_FIREFOX', 'SL_IE9', 'SL_IE10', 'SL_IE11', 'SL_EDGE', 'SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4',
         'SL_CHROMEDEV', 'SL_FIREFOXBETA']
};

var browserstackAliases = {
  'ALL': Object.keys(customLaunchers).filter(function(item) {return customLaunchers[item].base == 'BrowserStack';}),
  'DESKTOP': ['BS_CHROME', 'BS_FIREFOX', 'BS_IE9', 'BS_IE10', 'BS_IE11', 'BS_EDGE', 'BS_SAFARI7', 'BS_SAFARI8', 'BS_SAFARI9'],
  'MOBILE': ['BS_ANDROID4.3', 'BS_ANDROID4.4', 'BS_IOS7', 'BS_IOS8', 'BS_IOS9', 'BS_WINDOWSPHONE'],
  'ANDROID': ['BS_ANDROID4.3', 'BS_ANDROID4.4'],
  'IE': ['BS_IE9', 'BS_IE10', 'BS_IE11'],
  'IOS': ['BS_IOS7', 'BS_IOS8', 'BS_IOS9'],
  'SAFARI': ['BS_SAFARI7', 'BS_SAFARI8', 'BS_SAFARI9'],
  'CI': ['BS_CHROME', 'BS_SAFARI7', 'BS_SAFARI8', 'BS_SAFARI9', 'BS_IOS7', 'BS_IOS8', 'BS_IOS9',
    'BS_FIREFOX', 'BS_IE9', 'BS_IE10', 'BS_IE11', 'BS_EDGE', 'BS_WINDOWSPHONE', 'BS_ANDROID4.3', 'BS_ANDROID4.4']
};

module.exports = {
  customLaunchers: customLaunchers,
  sauceAliases: sauceAliases,
  browserstackAliases: browserstackAliases
}

if (process.env.TRAVIS) {
  process.env.SAUCE_ACCESS_KEY = process.env.SAUCE_ACCESS_KEY.split('').reverse().join('');
  process.env.BROWSER_STACK_ACCESS_KEY = process.env.BROWSER_STACK_ACCESS_KEY.split('').reverse().join('');
}
<commit_msg>chore(build): remove iOS9 from CI<commit_after>var customLaunchers = {
  'DartiumWithWebPlatform': {
    base: 'Dartium',
    flags: ['--enable-experimental-web-platform-features'] },
  'ChromeNoSandbox': {
    base: 'Chrome',
    flags: ['--no-sandbox'] },
  'SL_CHROME': {
    base: 'SauceLabs',
    browserName: 'chrome',
    version: '46'
  },
  'SL_CHROMEBETA': {
    base: 'SauceLabs',
    browserName: 'chrome',
    version: 'beta'
  },
  'SL_CHROMEDEV': {
    base: 'SauceLabs',
    browserName: 'chrome',
    version: 'dev'
  },
  'SL_FIREFOX': {
    base: 'SauceLabs',
    browserName: 'firefox',
    version: '42'
  },
  'SL_FIREFOXBETA': {
    base: 'SauceLabs',
    browserName: 'firefox',
    version: 'beta'
  },
  'SL_FIREFOXDEV': {
    base: 'SauceLabs',
    browserName: 'firefox',
    version: 'dev'
  },
  'SL_SAFARI7': {
    base: 'SauceLabs',
    browserName: 'safari',
    platform: 'OS X 10.9',
    version: '7'
  },
  'SL_SAFARI8': {
    base: 'SauceLabs',
    browserName: 'safari',
    platform: 'OS X 10.10',
    version: '8'
  },
  'SL_SAFARI9.0': {
    base: 'SauceLabs',
    browserName: 'safari',
    platform: 'OS X 10.11',
    version: '9.0'
  },
  'SL_IOS7': {
    base: 'SauceLabs',
    browserName: 'iphone',
    platform: 'OS X 10.10',
    version: '7.1'
  },
  'SL_IOS8': {
    base: 'SauceLabs',
    browserName: 'iphone',
    platform: 'OS X 10.10',
    version: '8.4'
  },
  'SL_IOS9': {
    base: 'SauceLabs',
    browserName: 'iphone',
    platform: 'OS X 10.10',
    version: '9.1'
  },
  'SL_IE9': {
    base: 'SauceLabs',
    browserName: 'internet explorer',
    platform: 'Windows 2008',
    version: '9'
  },
  'SL_IE10': {
    base: 'SauceLabs',
    browserName: 'internet explorer',
    platform: 'Windows 2012',
    version: '10'
  },
  'SL_IE11': {
    base: 'SauceLabs',
    browserName: 'internet explorer',
    platform: 'Windows 8.1',
    version: '11'
  },
  'SL_EDGE': {
    base: 'SauceLabs',
    browserName: 'microsoftedge',
    platform: 'Windows 10',
    version: '20.10240'
  },
  'SL_ANDROID4.1': {
    base: 'SauceLabs',
    browserName: 'android',
    platform: 'Linux',
    version: '4.1'
  },
  'SL_ANDROID4.2': {
    base: 'SauceLabs',
    browserName: 'android',
    platform: 'Linux',
    version: '4.2'
  },
  'SL_ANDROID4.3': {
    base: 'SauceLabs',
    browserName: 'android',
    platform: 'Linux',
    version: '4.3'
  },
  'SL_ANDROID4.4': {
    base: 'SauceLabs',
    browserName: 'android',
    platform: 'Linux',
    version: '4.4'
  },
  'SL_ANDROID5.1': {
    base: 'SauceLabs',
    browserName: 'android',
    platform: 'Linux',
    version: '5.1'
  },

  'BS_CHROME': {
    base: 'BrowserStack',
    browser: 'chrome',
    os: 'OS X',
    os_version: 'Yosemite'
  },
  'BS_FIREFOX': {
    base: 'BrowserStack',
    browser: 'firefox',
    os: 'Windows',
    os_version: '10'
  },
  'BS_SAFARI7': {
    base: 'BrowserStack',
    browser: 'safari',
    os: 'OS X',
    os_version: 'Mavericks'
  },
  'BS_SAFARI8': {
    base: 'BrowserStack',
    browser: 'safari',
    os: 'OS X',
    os_version: 'Yosemite'
  },
  'BS_SAFARI9': {
    base: 'BrowserStack',
    browser: 'safari',
    os: 'OS X',
    os_version: 'El Capitan'
  },
  'BS_IOS7': {
    base: 'BrowserStack',
    device: 'iPhone 5S',
    os: 'ios',
    os_version: '7.0'
  },
  'BS_IOS8': {
    base: 'BrowserStack',
    device: 'iPhone 6',
    os: 'ios',
    os_version: '8.3'
  },
  'BS_IOS9': {
    base: 'BrowserStack',
    device: 'iPhone 6S',
    os: 'ios',
    os_version: '9.0'
  },
  'BS_IE9': {
    base: 'BrowserStack',
    browser: 'ie',
    browser_version: '9.0',
    os: 'Windows',
    os_version: '7'
  },
  'BS_IE10': {
    base: 'BrowserStack',
    browser: 'ie',
    browser_version: '10.0',
    os: 'Windows',
    os_version: '8'
  },
  'BS_IE11': {
    base: 'BrowserStack',
    browser: 'ie',
    browser_version: '11.0',
    os: 'Windows',
    os_version: '10'
  },
  'BS_EDGE': {
    base: 'BrowserStack',
    browser: 'edge',
    os: 'Windows',
    os_version: '10'
  },
  'BS_WINDOWSPHONE' : {
    base: 'BrowserStack',
    device: 'Nokia Lumia 930',
    os: 'winphone',
    os_version: '8.1'
  },
  'BS_ANDROID5': {
    base: 'BrowserStack',
    device: 'Google Nexus 5',
    os: 'android',
    os_version: '5.0'
  },
  'BS_ANDROID4.4': {
    base: 'BrowserStack',
    device: 'HTC One M8',
    os: 'android',
    os_version: '4.4'
  },
  'BS_ANDROID4.3': {
    base: 'BrowserStack',
    device: 'Samsung Galaxy S4',
    os: 'android',
    os_version: '4.3'
  },
  'BS_ANDROID4.2': {
    base: 'BrowserStack',
    device: 'Google Nexus 4',
    os: 'android',
    os_version: '4.2'
  },
  'BS_ANDROID4.1': {
    base: 'BrowserStack',
    device: 'Google Nexus 7',
    os: 'android',
    os_version: '4.1'
  }
};

// iOS9 deactivated as not reliable in both providers
// TODO(mlaval): reactivate after https://github.com/angular/angular/issues/5408

var sauceAliases = {
  'ALL': Object.keys(customLaunchers).filter(function(item) {return customLaunchers[item].base == 'SauceLabs';}),
  'DESKTOP': ['SL_CHROME', 'SL_FIREFOX', 'SL_IE9', 'SL_IE10', 'SL_IE11', 'SL_EDGE', 'SL_SAFARI7', 'SL_SAFARI8', 'SL_SAFARI9.0'],
  'MOBILE': ['SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5.1', 'SL_IOS7', 'SL_IOS8', 'SL_IOS9'],
  'ANDROID': ['SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5.1'],
  'IE': ['SL_IE9', 'SL_IE10', 'SL_IE11'],
  'IOS': ['SL_IOS7', 'SL_IOS8', 'SL_IOS9'],
  'SAFARI': ['SL_SAFARI7', 'SL_SAFARI8', 'SL_SAFARI9.0'],
  'BETA': ['SL_CHROMEBETA', 'SL_FIREFOXBETA'],
  'DEV': ['SL_CHROMEDEV', 'SL_FIREFOXDEV'],
  'CI': ['SL_CHROME', 'SL_ANDROID5.1', 'SL_SAFARI7', 'SL_SAFARI8', 'SL_SAFARI9.0', 'SL_IOS7', 'SL_IOS8',
         'SL_FIREFOX', 'SL_IE9', 'SL_IE10', 'SL_IE11', 'SL_EDGE', 'SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4',
         'SL_CHROMEDEV', 'SL_FIREFOXBETA']
};

var browserstackAliases = {
  'ALL': Object.keys(customLaunchers).filter(function(item) {return customLaunchers[item].base == 'BrowserStack';}),
  'DESKTOP': ['BS_CHROME', 'BS_FIREFOX', 'BS_IE9', 'BS_IE10', 'BS_IE11', 'BS_EDGE', 'BS_SAFARI7', 'BS_SAFARI8', 'BS_SAFARI9'],
  'MOBILE': ['BS_ANDROID4.3', 'BS_ANDROID4.4', 'BS_IOS7', 'BS_IOS8', 'BS_IOS9', 'BS_WINDOWSPHONE'],
  'ANDROID': ['BS_ANDROID4.3', 'BS_ANDROID4.4'],
  'IE': ['BS_IE9', 'BS_IE10', 'BS_IE11'],
  'IOS': ['BS_IOS7', 'BS_IOS8', 'BS_IOS9'],
  'SAFARI': ['BS_SAFARI7', 'BS_SAFARI8', 'BS_SAFARI9'],
  'CI': ['BS_CHROME', 'BS_SAFARI7', 'BS_SAFARI8', 'BS_SAFARI9', 'BS_IOS7', 'BS_IOS8',
    'BS_FIREFOX', 'BS_IE9', 'BS_IE10', 'BS_IE11', 'BS_EDGE', 'BS_WINDOWSPHONE', 'BS_ANDROID4.3', 'BS_ANDROID4.4']
};

module.exports = {
  customLaunchers: customLaunchers,
  sauceAliases: sauceAliases,
  browserstackAliases: browserstackAliases
}

if (process.env.TRAVIS) {
  process.env.SAUCE_ACCESS_KEY = process.env.SAUCE_ACCESS_KEY.split('').reverse().join('');
  process.env.BROWSER_STACK_ACCESS_KEY = process.env.BROWSER_STACK_ACCESS_KEY.split('').reverse().join('');
}
<|endoftext|>


<commit_before>/**
 * Build a root-relative URL to the organization's pipeline list screen.
 * @param organization
 */
export const buildOrganizationUrl = (organization) =>
    `/organizations/${encodeURIComponent(organization)}`;

/**
 * Build a root-relative URL to the pipeline details screen.
 * @param organization
 * @param fullName
 * @param tabName
 * @returns {string}
 */
export const buildPipelineUrl = (organization, fullName, tabName) => {
    const baseUrl = `/organizations/${encodeURIComponent(organization)}/` +
        `${encodeURIComponent(fullName)}`;

    return tabName ? `${baseUrl}/${tabName}` : baseUrl;
};

/**
 * Build a root-relative URL to the run details screen.
 * @param organization
 * @param pipeline
 * @param branch
 * @param runId
 * @param tabName
 */
export const buildRunDetailsUrl = (organization, pipeline, branch, runId, tabName) => {
    const baseUrl = `/organizations/${encodeURIComponent(organization)}/` +
        `${encodeURIComponent(pipeline)}/detail/` +
        `${encodeURIComponent(branch)}/${encodeURIComponent(runId)}`;
    return tabName ? `${baseUrl}/${tabName}` : baseUrl;
};

/*
 * helper to clean the path replace(/%2F/g, '%252F')
 * @param input
 */
export const uriString = (input) => encodeURIComponent(input).replace(/%2F/g, '%252F');

// general fetchAllTrigger
export const fetchAllSuffix = '?start=0';

// Add fetchAllSuffix in case it is needed
export function applyFetchAll(config, url) {
// if we pass fetchAll means we want the full log -> start=0 will trigger that on the server
    if (config.fetchAll && !url.includes(fetchAllSuffix)) {
        return `${url}${fetchAllSuffix}`;
    }
    return url;
}

// using the hook 'location.search'.includes('start=0') to trigger fetchAll
export function calculateFetchAll(props) {
    const { location: { search } } = props;

    if (search) {
        const stepReg = /start=([0-9]{1,})/;
        const match = stepReg.exec(search);
        if (match && match[1] && Number(match[1]) === 0) {
            return true;
        }
    }
    return false;
}

// using the hook 'location.search'.includes('view=0') to trigger the logConsole view instead of steps
export const calculateLogView = function (props) {
    const { location: { search } } = props;

    if (search) {
        const viewReg = /view=([0-9]{1,})/;
        const match = viewReg.exec(search);
        if (match && match[1] && Number(match[1]) === 0) {
            return true;
        }
    }
    return false;
};
/*
 * helper to calculate log url. When we have a node we get create a special url, otherwise we use the url passed to us
 * @param config { nodesBaseUrl, node, url}
 */
export const calculateLogUrl = (config) => {
    let returnUrl = config.url;
    if (config.node) {
        const { nodesBaseUrl, node } = config;
        returnUrl = `${nodesBaseUrl}/${node.id}/log/`;
    }
    return applyFetchAll(config, returnUrl);
};

/*
 * helper to calculate node(flowNodes) url.
 * If we have multibranch we generate a slightly different url
 * @param config { name, runId, branch, _appURLBase, isMultiBranch}
 */
export function calculateNodeBaseUrl(config) {
    const { name, runId, branch, _appURLBase, isMultiBranch } = config;
    const baseUrl =
        `${_appURLBase}/rest/organizations/jenkins/` +
        `pipelines/${name}`;
    if (isMultiBranch) {
        return `${baseUrl}/branches/${uriString(branch)}/runs/${runId}/nodes/`;
    }
    return `${baseUrl}/runs/${runId}/nodes/`;
}

/*
 * helper to calculate steps(flowNodes) url.
 * If we have multibranch we generate a slightly different url
 * and if there are node in we request the steps for this node
 * @param config { name, runId, branch, _appURLBase, isMultiBranch, node}
 */
export function calculateStepsBaseUrl(config) {
    const { name, runId, branch, _appURLBase, isMultiBranch, node } = config;
    let baseUrl =
        `${_appURLBase}/rest/organizations/jenkins/` +
        `pipelines/${name}`;
    if (isMultiBranch) {
        baseUrl = `${baseUrl}/branches/${uriString(branch)}`;
    }
    if (node && node !== null) {
        return `${baseUrl}/runs/${runId}/nodes/${node}/steps/`;
    }
    return `${baseUrl}/runs/${runId}/steps/`;
}
/*
 * helper to calculate general log url, includes filename.
 * If we have multibranch we generate a slightly different url
 * @param config { name, runId, branch, _appURLBase, isMultiBranch}
 */
export function calculateRunLogURLObject(config) {
    const { name, runId, branch, _appURLBase, isMultiBranch } = config;
    const baseUrl = `${_appURLBase}/rest/organizations/jenkins` +
        `/pipelines/${name}`;
    let url;
    let fileName;
    if (isMultiBranch) {
        url = `${baseUrl}/branches/${uriString(branch)}/runs/${runId}/log/`;
        fileName = `${branch}-${runId}.txt`;
    } else {
        url = `${baseUrl}/runs/${runId}/log/`;
        fileName = `${runId}.txt`;
    }
    url = applyFetchAll(config, url);
    return {
        url,
        fileName,
    };
}

/**
 * Provide a pagination function for the generic
 * blueocean pagination
 */
export function paginateUrl(url) {
    const sep = url.indexOf('?') >= 0 ? '&' : '?';
    return (start, limit) => `${url}${sep}start=${start}&limit=${limit}`;
}

/**
 * Examines the provided object for:
 * organization, pipeline, branch, runId
 * and builds a path to the thing as best it can...
 */
export function getRestUrl({ organization, pipeline, branch, runId }) {
    const pipelineName = typeof pipeline === 'object' ? pipeline.fullName : pipeline;
    const organizationName = organization ||
        (typeof pipeline === 'object' ? pipeline.organization : '');
    const jenkinsUrl = require('../config').getJenkinsRootURL();
    let url = `${jenkinsUrl}/blue/rest/organizations/${encodeURIComponent(organizationName)}`;
    if (pipelineName) {
        // pipelineName might include a folder path, don't encode it
        url += `/pipelines/${pipelineName}`;
    }
    if (branch) {
        // JENKINS-37712 branch needs to be double-encoded for some reason
        url += `/branches/${encodeURIComponent(encodeURIComponent(branch))}`;
    }
    if (runId) {
        url += `/runs/${encodeURIComponent(runId)}`;
    }
    return url;
}

/**
 * Returns a new string which ends with a slash, or the
 * original if it already does
 */
export function endSlash(str) {
    if (!str) {
        return str;
    }
    if (str.charAt(str.length - 1) !== '/') {
        return `${str}/`;
    }
    return str;
}

/**
 * Constructs an escaped url based on the arguments, with forward slashes between them
 * e.g. buildURL('organizations', orgName, 'runs', runId) => organizations/my%20org/runs/34
 */
export function buildUrl(...args) {
    let url = '';
    for (let i = 0; i < args.length; i++) {
        if (i > 0) {
            url += '/';
        }
        url += encodeURIComponent(args[i]);
    }
    return url;
}

/**
 * Returns a relative URL based on the current location
 */
export function relativeUrl(location, ...args) {
    return endSlash(location.pathname) + buildUrl.apply(null, args);
}
<commit_msg>[JENKINS-37838] Calls to backend MUST end with / (#454)<commit_after>/**
 * Build a root-relative URL to the organization's pipeline list screen.
 * @param organization
 */
export const buildOrganizationUrl = (organization) =>
    `/organizations/${encodeURIComponent(organization)}`;

/**
 * Build a root-relative URL to the pipeline details screen.
 * @param organization
 * @param fullName
 * @param tabName
 * @returns {string}
 */
export const buildPipelineUrl = (organization, fullName, tabName) => {
    const baseUrl = `/organizations/${encodeURIComponent(organization)}/` +
        `${encodeURIComponent(fullName)}`;

    return tabName ? `${baseUrl}/${tabName}` : baseUrl;
};

/**
 * Build a root-relative URL to the run details screen.
 * @param organization
 * @param pipeline
 * @param branch
 * @param runId
 * @param tabName
 */
export const buildRunDetailsUrl = (organization, pipeline, branch, runId, tabName) => {
    const baseUrl = `/organizations/${encodeURIComponent(organization)}/` +
        `${encodeURIComponent(pipeline)}/detail/` +
        `${encodeURIComponent(branch)}/${encodeURIComponent(runId)}`;
    return tabName ? `${baseUrl}/${tabName}` : baseUrl;
};

/*
 * helper to clean the path replace(/%2F/g, '%252F')
 * @param input
 */
export const uriString = (input) => encodeURIComponent(input).replace(/%2F/g, '%252F');

// general fetchAllTrigger
export const fetchAllSuffix = '?start=0';

// Add fetchAllSuffix in case it is needed
export function applyFetchAll(config, url) {
// if we pass fetchAll means we want the full log -> start=0 will trigger that on the server
    if (config.fetchAll && !url.includes(fetchAllSuffix)) {
        return `${url}${fetchAllSuffix}`;
    }
    return url;
}

// using the hook 'location.search'.includes('start=0') to trigger fetchAll
export function calculateFetchAll(props) {
    const { location: { search } } = props;

    if (search) {
        const stepReg = /start=([0-9]{1,})/;
        const match = stepReg.exec(search);
        if (match && match[1] && Number(match[1]) === 0) {
            return true;
        }
    }
    return false;
}

// using the hook 'location.search'.includes('view=0') to trigger the logConsole view instead of steps
export const calculateLogView = function (props) {
    const { location: { search } } = props;

    if (search) {
        const viewReg = /view=([0-9]{1,})/;
        const match = viewReg.exec(search);
        if (match && match[1] && Number(match[1]) === 0) {
            return true;
        }
    }
    return false;
};
/*
 * helper to calculate log url. When we have a node we get create a special url, otherwise we use the url passed to us
 * @param config { nodesBaseUrl, node, url}
 */
export const calculateLogUrl = (config) => {
    let returnUrl = config.url;
    if (config.node) {
        const { nodesBaseUrl, node } = config;
        returnUrl = `${nodesBaseUrl}/${node.id}/log/`;
    }
    return applyFetchAll(config, returnUrl);
};

/*
 * helper to calculate node(flowNodes) url.
 * If we have multibranch we generate a slightly different url
 * @param config { name, runId, branch, _appURLBase, isMultiBranch}
 */
export function calculateNodeBaseUrl(config) {
    const { name, runId, branch, _appURLBase, isMultiBranch } = config;
    const baseUrl =
        `${_appURLBase}/rest/organizations/jenkins/` +
        `pipelines/${name}`;
    if (isMultiBranch) {
        return `${baseUrl}/branches/${uriString(branch)}/runs/${runId}/nodes/`;
    }
    return `${baseUrl}/runs/${runId}/nodes/`;
}

/*
 * helper to calculate steps(flowNodes) url.
 * If we have multibranch we generate a slightly different url
 * and if there are node in we request the steps for this node
 * @param config { name, runId, branch, _appURLBase, isMultiBranch, node}
 */
export function calculateStepsBaseUrl(config) {
    const { name, runId, branch, _appURLBase, isMultiBranch, node } = config;
    let baseUrl =
        `${_appURLBase}/rest/organizations/jenkins/` +
        `pipelines/${name}`;
    if (isMultiBranch) {
        baseUrl = `${baseUrl}/branches/${uriString(branch)}`;
    }
    if (node && node !== null) {
        return `${baseUrl}/runs/${runId}/nodes/${node}/steps/`;
    }
    return `${baseUrl}/runs/${runId}/steps/`;
}
/*
 * helper to calculate general log url, includes filename.
 * If we have multibranch we generate a slightly different url
 * @param config { name, runId, branch, _appURLBase, isMultiBranch}
 */
export function calculateRunLogURLObject(config) {
    const { name, runId, branch, _appURLBase, isMultiBranch } = config;
    const baseUrl = `${_appURLBase}/rest/organizations/jenkins` +
        `/pipelines/${name}`;
    let url;
    let fileName;
    if (isMultiBranch) {
        url = `${baseUrl}/branches/${uriString(branch)}/runs/${runId}/log/`;
        fileName = `${branch}-${runId}.txt`;
    } else {
        url = `${baseUrl}/runs/${runId}/log/`;
        fileName = `${runId}.txt`;
    }
    url = applyFetchAll(config, url);
    return {
        url,
        fileName,
    };
}

/**
 * Provide a pagination function for the generic
 * blueocean pagination
 */
export function paginateUrl(url) {
    const sep = url.indexOf('?') >= 0 ? '&' : '?';
    return (start, limit) => `${url}${sep}start=${start}&limit=${limit}`;
}

/**
 * Returns a new string which ends with a slash, or the
 * original if it already does
 */
export function endSlash(str) {
    if (!str) {
        return str;
    }
    if (str.charAt(str.length - 1) !== '/') {
        return `${str}/`;
    }
    return str;
}

/**
 * Examines the provided object for:
 * organization, pipeline, branch, runId
 * and builds a path to the thing as best it can...
 */
export function getRestUrl({ organization, pipeline, branch, runId }) {
    const pipelineName = typeof pipeline === 'object' ? pipeline.fullName : pipeline;
    const organizationName = organization ||
        (typeof pipeline === 'object' ? pipeline.organization : '');
    const jenkinsUrl = require('../config').getJenkinsRootURL();
    let url = `${jenkinsUrl}/blue/rest/organizations/${encodeURIComponent(organizationName)}`;
    if (pipelineName) {
        // pipelineName might include a folder path, don't encode it
        url += `/pipelines/${pipelineName}`;
    }
    if (branch) {
        // JENKINS-37712 branch needs to be double-encoded for some reason
        url += `/branches/${encodeURIComponent(encodeURIComponent(branch))}`;
    }
    if (runId) {
        url += `/runs/${encodeURIComponent(runId)}`;
    }
    return endSlash(url);
}

/**
 * Constructs an escaped url based on the arguments, with forward slashes between them
 * e.g. buildURL('organizations', orgName, 'runs', runId) => organizations/my%20org/runs/34
 */
export function buildUrl(...args) {
    let url = '';
    for (let i = 0; i < args.length; i++) {
        if (i > 0) {
            url += '/';
        }
        url += encodeURIComponent(args[i]);
    }
    return url;
}

/**
 * Returns a relative URL based on the current location
 */
export function relativeUrl(location, ...args) {
    return endSlash(location.pathname) + buildUrl.apply(null, args);
}
<|endoftext|>


<commit_before>// ---------------------------------------------------------------------------
// Usage:
//
//      npm run export-exchanges
// ---------------------------------------------------------------------------

"use strict";

const fs        = require ('fs')
const countries = require ('./countries')
const asTable   = require ('as-table')
const execSync  = require ('child_process').execSync
const log       = require ('ololog').unlimited
const ansi      = require ('ansicolor').nice
const { keys, values } = Object

// ---------------------------------------------------------------------------

let exchanges
let verbose = false

// ---------------------------------------------------------------------------

let wikiPath = 'wiki'
let gitWikiPath = 'build/ccxt.wiki'
let ccxtCertifiedBadge = '[![CCXT Certified](https://img.shields.io/badge/CCXT-certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification)'
let spacing = ' '.repeat (7)
let logoHeading = spacing + 'logo' + spacing
let tableHeadings = [ logoHeading, 'id', 'name', 'ver', 'doc', 'certified', ]
let exchangesByCountryHeading = [ 'country / region', ... tableHeadings ]

if (!fs.existsSync (gitWikiPath)) {
    log.bright.cyan ('Checking out ccxt.wiki...')
    execSync ('git clone https://github.com/ccxt/ccxt.wiki.git ' + gitWikiPath)
}

// ---------------------------------------------------------------------------

function replaceInFile (filename, regex, replacement) {
    log.bright.cyan ('Exporting exchanges →', filename.yellow)
    let contents = fs.readFileSync (filename, 'utf8')
    const newContents = contents.replace (regex, replacement)
    fs.truncateSync (filename)
    fs.writeFileSync (filename, newContents)
}

// ---------------------------------------------------------------------------

const includedIds = fs.readFileSync ('exchanges.cfg')
    .toString () // Buffer → String
    .split ('\n') // String → Array
    .map (line => line.split ('#')[0].trim ()) // trim comments
    .filter (exchange => exchange); // filter empty lines

const isIncluded = (id) => ((includedIds.length === 0) || includedIds.includes (id))

log.bright.cyan ('Exporting exchanges...'.yellow)

const ids = fs.readdirSync ('./js/')
    .filter (file => file.includes ('.js'))
    .map (file => file.slice (0, -3))
    .filter (isIncluded);

const pad = function (string, n) {
    return (string + ' '.repeat (n)).slice (0, n)
}

;[
    {
        file: './ccxt.js',
        regex:  /(?:const|var)\s+exchanges\s+\=\s+\{[^\}]+\}/,
        replacement: "const exchanges = {\n" + ids.map (id => pad ("    '" + id + "':", 30) + " require ('./js/" + id + ".js'),").join ("\n") + "    \n}",
    },
    {
        file: './python/ccxt/__init__.py',
        regex: /exchanges \= \[[^\]]+\]/,
        replacement: "exchanges = [\n" + "    '" + ids.join ("',\n    '") + "'," + "\n]",
    },
    {
        file: './python/ccxt/__init__.py',
        regex: /(?:from ccxt\.[^\.]+ import [^\s]+\s+\# noqa\: F401[\r]?[\n])+[\r]?[\n]exchanges/,
        replacement: ids.map (id => pad ('from ccxt.' + id + ' import ' + id, 60) + '# noqa: F401').join ("\n") + "\n\nexchanges",
    },
    {
        file: './python/ccxt/async_support/__init__.py',
        regex: /(?:from ccxt\.async_support\.[^\.]+ import [^\s]+\s+\# noqa\: F401[\r]?[\n])+[\r]?[\n]exchanges/,
        replacement: ids.map (id => pad ('from ccxt.async_support.' + id + ' import ' + id, 74) + '# noqa: F401').join ("\n") + "\n\nexchanges",
    },
    {
        file: './python/ccxt/async_support/__init__.py',
        regex: /exchanges \= \[[^\]]+\]/,
        replacement: "exchanges = [\n" + "    '" + ids.join ("',\n    '") + "'," + "\n]",
    },
    {
        file: './php/Exchange.php',
        regex: /public static \$exchanges \= array \([^\)]+\)/,
        replacement: "public static $exchanges = array (\n        '" + ids.join ("',\n        '") + "',\n    )",
    },

].forEach (({ file, regex, replacement }) => {
    replaceInFile (file, regex, replacement)
})

exchanges = {}
ids.forEach (id => {
    exchanges[id] = { 'verbose': verbose, 'apiKey': '', 'secret': '' }
})

log.bright.green ('Base sources updated successfully.')

// ----------------------------------------------------------------------------
// strategically placed exactly here

const ccxt = require ('../ccxt.js')

// ----------------------------------------------------------------------------

for (let id in exchanges) {
    ccxt[id].prototype.checkRequiredDependencies = () => {}
    exchanges[id] = new (ccxt)[id] (exchanges[id])
    exchanges[id].verbose = verbose
}

var countryName = function (code) {
    return ((countries[code] !== undefined) ? countries[code] : code)
}

// ---------------------------------------------------------------------------
// list all supported exchanges

let tableData = values (exchanges).map (exchange => {
    let logo = exchange.urls['logo']
    let website = Array.isArray (exchange.urls.www) ? exchange.urls.www[0] : exchange.urls.www
    let url = exchange.urls.referral || website
    let countries = Array.isArray (exchange.countries) ? exchange.countries.map (countryName).join (', ') : countryName (exchange.countries)
    let doc = Array.isArray (exchange.urls.doc) ? exchange.urls.doc[0] : exchange.urls.doc
    let version = exchange.version ? exchange.version : '\*'
    let matches = version.match (/[^0-9]*([0-9].*)/)
    if (matches)
        version = matches[1];
    return [
        '[![' + exchange.id + '](' + logo + ')](' + url + ')',
        exchange.id,
        '[' + exchange.name + '](' + url + ')',
        version,
        '[API](' + doc + ')',
        exchange.certified ? ccxtCertifiedBadge : '',
        countries,
    ]
})

tableData.splice (0, 0, tableHeadings)

function makeTable (jsonArray) {
    let table = asTable.configure ({ 'delimiter': ' | ' }) (jsonArray)
    let lines = table.split ("\n")
    lines.splice (1,0, lines[0].replace (/[^\|]/g, '-'))
    let headerLine = lines[1].split ('|')
    headerLine[3] = ':' + headerLine[3].slice (1, headerLine[3].length - 1) + ':'
    headerLine[4] = ':' + headerLine[4].slice (1, headerLine[4].length - 1) + ':'
    lines[1] = headerLine.join ('|')
    return lines.map (line => '|' + line + '|').join ("\n")
}

let exchangesTable = makeTable (tableData)
let numExchanges = keys (exchanges).length
let beginning = "The ccxt library currently supports the following "
let ending = " cryptocurrency exchange markets and trading APIs:\n\n"
let totalString = beginning + numExchanges + ending
let howMany = totalString + exchangesTable + "$1"
let allExchangesRegex = new RegExp ("[^\n]+[\n]{2}\\|[^#]+\\|([\n][\n]|[\n]$|$)", 'm')
replaceInFile ('README.md', allExchangesRegex, howMany)
replaceInFile (wikiPath + '/Manual.md', allExchangesRegex, howMany)
replaceInFile (wikiPath + '/Exchange-Markets.md', allExchangesRegex, howMany)

let certifiedFieldIndex = tableHeadings.indexOf ('certified')
let certified = tableData.filter ((x) => x[certifiedFieldIndex] !== '' )
let allCertifiedRegex = new RegExp ("^(## Certified Cryptocurrency Exchanges\n{3})(?:\\|.+\\|$\n)+", 'm')
let certifiedTable = makeTable (certified)
let certifiedTableReplacement = '$1' + certifiedTable + "\n"
replaceInFile ('README.md', allCertifiedRegex, certifiedTableReplacement)

let exchangesByCountries = []
keys (countries).forEach (code => {
    let country = countries[code]
    let result = []
    keys (exchanges).forEach (id => {
        let exchange = exchanges[id]
        let logo = exchange.urls['logo']
        let website = Array.isArray (exchange.urls.www) ? exchange.urls.www[0] : exchange.urls.www
        let url = exchange.urls.referral || website
        let doc = Array.isArray (exchange.urls.doc) ? exchange.urls.doc[0] : exchange.urls.doc
        let version = exchange.version ? exchange.version : '\*'
        let matches = version.match (/[^0-9]*([0-9].*)/)
        if (matches)
            version = matches[1];
        let shouldInclude = false
        if (Array.isArray (exchange.countries)) {
            if (exchange.countries.indexOf (code) > -1)
                shouldInclude = true
        } else {
            if (code == exchange.countries)
                shouldInclude = true
        }
        if (shouldInclude) {
            let entry = [
                country,
                '[![' + exchange.id + '](' + logo + ')](' + url + ')',
                exchange.id,
                '[' + exchange.name + '](' + url + ')',
                version,
                '[API](' + doc + ')',
                // doesn't fit in width
                // exchange.certified ? ccxtCertifiedBadge : '',
            ]
            result.push (entry)
        }
    })
    exchangesByCountries = exchangesByCountries.concat (result)
});

let countryKeyIndex = exchangesByCountryHeading.indexOf ('country / region')
exchangesByCountries = exchangesByCountries.sort ((a, b) => {
    let countryA = a[countryKeyIndex].toLowerCase ()
    let countryB = b[countryKeyIndex].toLowerCase ()
    if (countryA > countryB) {
        return 1
    } else if (countryA < countryB) {
        return -1;
    } else {
        if (a['id'] > b['id'])
            return 1;
        else if (a['id'] < b['id'])
            return -1;
        else
            return 0;
    }
})

exchangesByCountries.splice (0, 0, exchangesByCountryHeading)
let lines = makeTable (exchangesByCountries)
let result = "# Exchanges By Country\n\nThe ccxt library currently supports the following cryptocurrency exchange markets and trading APIs:\n\n" + lines + "\n\n"
let filename = wikiPath + '/Exchange-Markets-By-Country.md'
fs.truncateSync (filename)
fs.writeFileSync (filename, result)

log.bright ('Exporting exchange ids to'.cyan, 'exchanges.json'.yellow)
fs.writeFileSync ('exchanges.json', JSON.stringify ({ ids: keys (exchanges) }, null, 4))

// ----------------------------------------------------------------------------

const ccxtWikiFileMapping = {
    'README.md': 'Home.md',
    'Install.md': 'Install.md',
    'Manual.md': 'Manual.md',
    'Exchange-Markets.md': 'Exchange-Markets.md',
    'Exchange-Markets-By-Country.md': 'Exchange-Markets-By-Country.md',
}

keys (ccxtWikiFileMapping)
    .forEach (file =>
        fs.writeFileSync (gitWikiPath + '/' + ccxtWikiFileMapping[file], fs.readFileSync (wikiPath + '/' + file)))

// ----------------------------------------------------------------------------

log.bright ('Exporting exchange keywords to'.cyan, 'package.json'.yellow)

const packageJSON = require ('./package.json')
const keywords = new Set (packageJSON.keywords)

for (const ex of values (exchanges)) {
    for (const url of Array.isArray (ex.urls.www) ? ex.urls.www : [ex.urls.www]) {
        keywords.add (url.replace (/(http|https):\/\/(www\.)?/, '').replace (/\/.*/, ''))
    }
    keywords.add (ex.name)
}

packageJSON.keywords = [...keywords]
fs.writeFileSync ('./package.json', JSON.stringify (packageJSON, null, 2))

// ----------------------------------------------------------------------------

log.bright.green ('Exchanges exported successfully.')
<commit_msg>fix to export-exchanges (wrong package.json require path after merging master)<commit_after>// ---------------------------------------------------------------------------
// Usage:
//
//      npm run export-exchanges
// ---------------------------------------------------------------------------

"use strict";

const fs        = require ('fs')
const countries = require ('./countries')
const asTable   = require ('as-table')
const execSync  = require ('child_process').execSync
const log       = require ('ololog').unlimited
const ansi      = require ('ansicolor').nice
const { keys, values } = Object

// ---------------------------------------------------------------------------

let exchanges
let verbose = false

// ---------------------------------------------------------------------------

let wikiPath = 'wiki'
let gitWikiPath = 'build/ccxt.wiki'
let ccxtCertifiedBadge = '[![CCXT Certified](https://img.shields.io/badge/CCXT-certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification)'
let spacing = ' '.repeat (7)
let logoHeading = spacing + 'logo' + spacing
let tableHeadings = [ logoHeading, 'id', 'name', 'ver', 'doc', 'certified', ]
let exchangesByCountryHeading = [ 'country / region', ... tableHeadings ]

if (!fs.existsSync (gitWikiPath)) {
    log.bright.cyan ('Checking out ccxt.wiki...')
    execSync ('git clone https://github.com/ccxt/ccxt.wiki.git ' + gitWikiPath)
}

// ---------------------------------------------------------------------------

function replaceInFile (filename, regex, replacement) {
    log.bright.cyan ('Exporting exchanges →', filename.yellow)
    let contents = fs.readFileSync (filename, 'utf8')
    const newContents = contents.replace (regex, replacement)
    fs.truncateSync (filename)
    fs.writeFileSync (filename, newContents)
}

// ---------------------------------------------------------------------------

const includedIds = fs.readFileSync ('exchanges.cfg')
    .toString () // Buffer → String
    .split ('\n') // String → Array
    .map (line => line.split ('#')[0].trim ()) // trim comments
    .filter (exchange => exchange); // filter empty lines

const isIncluded = (id) => ((includedIds.length === 0) || includedIds.includes (id))

log.bright.cyan ('Exporting exchanges...'.yellow)

const ids = fs.readdirSync ('./js/')
    .filter (file => file.includes ('.js'))
    .map (file => file.slice (0, -3))
    .filter (isIncluded);

const pad = function (string, n) {
    return (string + ' '.repeat (n)).slice (0, n)
}

;[
    {
        file: './ccxt.js',
        regex:  /(?:const|var)\s+exchanges\s+\=\s+\{[^\}]+\}/,
        replacement: "const exchanges = {\n" + ids.map (id => pad ("    '" + id + "':", 30) + " require ('./js/" + id + ".js'),").join ("\n") + "    \n}",
    },
    {
        file: './python/ccxt/__init__.py',
        regex: /exchanges \= \[[^\]]+\]/,
        replacement: "exchanges = [\n" + "    '" + ids.join ("',\n    '") + "'," + "\n]",
    },
    {
        file: './python/ccxt/__init__.py',
        regex: /(?:from ccxt\.[^\.]+ import [^\s]+\s+\# noqa\: F401[\r]?[\n])+[\r]?[\n]exchanges/,
        replacement: ids.map (id => pad ('from ccxt.' + id + ' import ' + id, 60) + '# noqa: F401').join ("\n") + "\n\nexchanges",
    },
    {
        file: './python/ccxt/async_support/__init__.py',
        regex: /(?:from ccxt\.async_support\.[^\.]+ import [^\s]+\s+\# noqa\: F401[\r]?[\n])+[\r]?[\n]exchanges/,
        replacement: ids.map (id => pad ('from ccxt.async_support.' + id + ' import ' + id, 74) + '# noqa: F401').join ("\n") + "\n\nexchanges",
    },
    {
        file: './python/ccxt/async_support/__init__.py',
        regex: /exchanges \= \[[^\]]+\]/,
        replacement: "exchanges = [\n" + "    '" + ids.join ("',\n    '") + "'," + "\n]",
    },
    {
        file: './php/Exchange.php',
        regex: /public static \$exchanges \= array \([^\)]+\)/,
        replacement: "public static $exchanges = array (\n        '" + ids.join ("',\n        '") + "',\n    )",
    },

].forEach (({ file, regex, replacement }) => {
    replaceInFile (file, regex, replacement)
})

exchanges = {}
ids.forEach (id => {
    exchanges[id] = { 'verbose': verbose, 'apiKey': '', 'secret': '' }
})

log.bright.green ('Base sources updated successfully.')

// ----------------------------------------------------------------------------
// strategically placed exactly here

const ccxt = require ('../ccxt.js')

// ----------------------------------------------------------------------------

for (let id in exchanges) {
    ccxt[id].prototype.checkRequiredDependencies = () => {}
    exchanges[id] = new (ccxt)[id] (exchanges[id])
    exchanges[id].verbose = verbose
}

var countryName = function (code) {
    return ((countries[code] !== undefined) ? countries[code] : code)
}

// ---------------------------------------------------------------------------
// list all supported exchanges

let tableData = values (exchanges).map (exchange => {
    let logo = exchange.urls['logo']
    let website = Array.isArray (exchange.urls.www) ? exchange.urls.www[0] : exchange.urls.www
    let url = exchange.urls.referral || website
    let countries = Array.isArray (exchange.countries) ? exchange.countries.map (countryName).join (', ') : countryName (exchange.countries)
    let doc = Array.isArray (exchange.urls.doc) ? exchange.urls.doc[0] : exchange.urls.doc
    let version = exchange.version ? exchange.version : '\*'
    let matches = version.match (/[^0-9]*([0-9].*)/)
    if (matches)
        version = matches[1];
    return [
        '[![' + exchange.id + '](' + logo + ')](' + url + ')',
        exchange.id,
        '[' + exchange.name + '](' + url + ')',
        version,
        '[API](' + doc + ')',
        exchange.certified ? ccxtCertifiedBadge : '',
        countries,
    ]
})

tableData.splice (0, 0, tableHeadings)

function makeTable (jsonArray) {
    let table = asTable.configure ({ 'delimiter': ' | ' }) (jsonArray)
    let lines = table.split ("\n")
    lines.splice (1,0, lines[0].replace (/[^\|]/g, '-'))
    let headerLine = lines[1].split ('|')
    headerLine[3] = ':' + headerLine[3].slice (1, headerLine[3].length - 1) + ':'
    headerLine[4] = ':' + headerLine[4].slice (1, headerLine[4].length - 1) + ':'
    lines[1] = headerLine.join ('|')
    return lines.map (line => '|' + line + '|').join ("\n")
}

let exchangesTable = makeTable (tableData)
let numExchanges = keys (exchanges).length
let beginning = "The ccxt library currently supports the following "
let ending = " cryptocurrency exchange markets and trading APIs:\n\n"
let totalString = beginning + numExchanges + ending
let howMany = totalString + exchangesTable + "$1"
let allExchangesRegex = new RegExp ("[^\n]+[\n]{2}\\|[^#]+\\|([\n][\n]|[\n]$|$)", 'm')
replaceInFile ('README.md', allExchangesRegex, howMany)
replaceInFile (wikiPath + '/Manual.md', allExchangesRegex, howMany)
replaceInFile (wikiPath + '/Exchange-Markets.md', allExchangesRegex, howMany)

let certifiedFieldIndex = tableHeadings.indexOf ('certified')
let certified = tableData.filter ((x) => x[certifiedFieldIndex] !== '' )
let allCertifiedRegex = new RegExp ("^(## Certified Cryptocurrency Exchanges\n{3})(?:\\|.+\\|$\n)+", 'm')
let certifiedTable = makeTable (certified)
let certifiedTableReplacement = '$1' + certifiedTable + "\n"
replaceInFile ('README.md', allCertifiedRegex, certifiedTableReplacement)

let exchangesByCountries = []
keys (countries).forEach (code => {
    let country = countries[code]
    let result = []
    keys (exchanges).forEach (id => {
        let exchange = exchanges[id]
        let logo = exchange.urls['logo']
        let website = Array.isArray (exchange.urls.www) ? exchange.urls.www[0] : exchange.urls.www
        let url = exchange.urls.referral || website
        let doc = Array.isArray (exchange.urls.doc) ? exchange.urls.doc[0] : exchange.urls.doc
        let version = exchange.version ? exchange.version : '\*'
        let matches = version.match (/[^0-9]*([0-9].*)/)
        if (matches)
            version = matches[1];
        let shouldInclude = false
        if (Array.isArray (exchange.countries)) {
            if (exchange.countries.indexOf (code) > -1)
                shouldInclude = true
        } else {
            if (code == exchange.countries)
                shouldInclude = true
        }
        if (shouldInclude) {
            let entry = [
                country,
                '[![' + exchange.id + '](' + logo + ')](' + url + ')',
                exchange.id,
                '[' + exchange.name + '](' + url + ')',
                version,
                '[API](' + doc + ')',
                // doesn't fit in width
                // exchange.certified ? ccxtCertifiedBadge : '',
            ]
            result.push (entry)
        }
    })
    exchangesByCountries = exchangesByCountries.concat (result)
});

let countryKeyIndex = exchangesByCountryHeading.indexOf ('country / region')
exchangesByCountries = exchangesByCountries.sort ((a, b) => {
    let countryA = a[countryKeyIndex].toLowerCase ()
    let countryB = b[countryKeyIndex].toLowerCase ()
    if (countryA > countryB) {
        return 1
    } else if (countryA < countryB) {
        return -1;
    } else {
        if (a['id'] > b['id'])
            return 1;
        else if (a['id'] < b['id'])
            return -1;
        else
            return 0;
    }
})

exchangesByCountries.splice (0, 0, exchangesByCountryHeading)
let lines = makeTable (exchangesByCountries)
let result = "# Exchanges By Country\n\nThe ccxt library currently supports the following cryptocurrency exchange markets and trading APIs:\n\n" + lines + "\n\n"
let filename = wikiPath + '/Exchange-Markets-By-Country.md'
fs.truncateSync (filename)
fs.writeFileSync (filename, result)

log.bright ('Exporting exchange ids to'.cyan, 'exchanges.json'.yellow)
fs.writeFileSync ('exchanges.json', JSON.stringify ({ ids: keys (exchanges) }, null, 4))

// ----------------------------------------------------------------------------

const ccxtWikiFileMapping = {
    'README.md': 'Home.md',
    'Install.md': 'Install.md',
    'Manual.md': 'Manual.md',
    'Exchange-Markets.md': 'Exchange-Markets.md',
    'Exchange-Markets-By-Country.md': 'Exchange-Markets-By-Country.md',
}

keys (ccxtWikiFileMapping)
    .forEach (file =>
        fs.writeFileSync (gitWikiPath + '/' + ccxtWikiFileMapping[file], fs.readFileSync (wikiPath + '/' + file)))

// ----------------------------------------------------------------------------

log.bright ('Exporting exchange keywords to'.cyan, 'package.json'.yellow)

const packageJSON = require ('../package.json')
const keywords = new Set (packageJSON.keywords)

for (const ex of values (exchanges)) {
    for (const url of Array.isArray (ex.urls.www) ? ex.urls.www : [ex.urls.www]) {
        keywords.add (url.replace (/(http|https):\/\/(www\.)?/, '').replace (/\/.*/, ''))
    }
    keywords.add (ex.name)
}

packageJSON.keywords = [...keywords]
fs.writeFileSync ('./package.json', JSON.stringify (packageJSON, null, 2))

// ----------------------------------------------------------------------------

log.bright.green ('Exchanges exported successfully.')
<|endoftext|>


<commit_before>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

const gulp = require('gulp');
const filter = require('gulp-filter');
const es = require('event-stream');
const gulptslint = require('gulp-tslint');
const gulpeslint = require('gulp-eslint');
const tsfmt = require('typescript-formatter');
const tslint = require('tslint');
const vfs = require('vinyl-fs');

/**
 * Hygiene works by creating cascading subsets of all our files and
 * passing them through a sequence of checks. Here are the current subsets,
 * named according to the checks performed on them. Each subset contains
 * the following one, as described in mathematical notation:
 *
 * all ⊃ eol ⊇ indentation ⊃ copyright ⊃ typescript
 */

const all = [
	'*',
	'build/**/*',
	'extensions/**/*',
	'scripts/**/*',
	'src/**/*',
	'test/**/*'
];

const eolFilter = [
	'**',
	'!ThirdPartyNotices.txt',
	'!LICENSE.txt',
	'!extensions/**/out/**',
	'!test/smoke/out/**',
	'!**/node_modules/**',
	'!**/fixtures/**',
	'!**/*.{svg,exe,png,bmp,scpt,bat,cmd,cur,ttf,woff,eot}',
	'!build/{lib,tslintRules}/**/*.js',
	'!build/monaco/**',
	'!build/win32/**',
	'!build/**/*.sh',
	'!build/tfs/**/*.js',
	'!**/Dockerfile'
];

const indentationFilter = [
	'**',
	'!ThirdPartyNotices.txt',
	'!**/*.md',
	'!**/*.ps1',
	'!**/*.template',
	'!**/*.yaml',
	'!**/*.yml',
	'!**/lib/**',
	'!extensions/**/*.d.ts',
	'!src/typings/**/*.d.ts',
	'!src/vs/*/**/*.d.ts',
	'!**/*.d.ts.recipe',
	'!test/assert.js',
	'!**/package.json',
	'!**/npm-shrinkwrap.json',
	'!**/octicons/**',
	'!**/vs/base/common/marked/raw.marked.js',
	'!**/vs/base/common/winjs.base.raw.js',
	'!**/vs/base/node/terminateProcess.sh',
	'!**/vs/nls.js',
	'!**/vs/css.js',
	'!**/vs/loader.js',
	'!extensions/**/snippets/**',
	'!extensions/**/syntaxes/**',
	'!extensions/**/themes/**',
	'!extensions/**/colorize-fixtures/**',
	'!extensions/vscode-api-tests/testWorkspace/**'
];

const copyrightFilter = [
	'**',
	'!**/*.desktop',
	'!**/*.json',
	'!**/*.html',
	'!**/*.template',
	'!**/*.md',
	'!**/*.bat',
	'!**/*.cmd',
	'!**/*.xml',
	'!**/*.sh',
	'!**/*.txt',
	'!**/*.xpm',
	'!**/*.opts',
	'!**/*.disabled',
	'!build/**/*.init',
	'!resources/linux/snap/snapcraft.yaml',
	'!resources/win32/bin/code.js',
	'!extensions/markdown/media/tomorrow.css',
	'!extensions/html/server/src/modes/typescript/*'
];

const eslintFilter = [
	'src/**/*.js',
	'build/gulpfile.*.js',
	'!src/vs/loader.js',
	'!src/vs/css.js',
	'!src/vs/nls.js',
	'!src/vs/css.build.js',
	'!src/vs/nls.build.js',
	'!src/**/winjs.base.raw.js',
	'!src/**/raw.marked.js',
	'!**/test/**'
];

const tslintFilter = [
	'src/**/*.ts',
	'test/**/*.ts',
	'extensions/**/*.ts',
	'!**/fixtures/**',
	'!**/typings/**',
	'!**/node_modules/**',
	'!extensions/typescript/test/colorize-fixtures/**',
	'!extensions/vscode-api-tests/testWorkspace/**',
	'!extensions/**/*.test.ts'
];

const copyrightHeader = [
	'/*---------------------------------------------------------------------------------------------',
	' *  Copyright (c) Microsoft Corporation. All rights reserved.',
	' *  Licensed under the MIT License. See License.txt in the project root for license information.',
	' *--------------------------------------------------------------------------------------------*/'
].join('\n');

function reportFailures(failures) {
	failures.forEach(failure => {
		const name = failure.name || failure.fileName;
		const position = failure.startPosition;
		const line = position.lineAndCharacter ? position.lineAndCharacter.line : position.line;
		const character = position.lineAndCharacter ? position.lineAndCharacter.character : position.character;

		console.error(`${name}:${line + 1}:${character + 1}:${failure.failure}`);
	});
}

gulp.task('eslint', () => {
	return gulp.src(all, { base: '.' })
		.pipe(filter(eslintFilter))
		.pipe(gulpeslint('src/.eslintrc'))
		.pipe(gulpeslint.formatEach('compact'))
		.pipe(gulpeslint.failAfterError());
});

gulp.task('tslint', () => {
	const options = { summarizeFailureOutput: true };

	return gulp.src(all, { base: '.' })
		.pipe(filter(tslintFilter))
		.pipe(gulptslint({ rulesDirectory: 'build/lib/tslint' }))
		.pipe(gulptslint.report(reportFailures, options));
});

const hygiene = exports.hygiene = (some, options) => {
	options = options || {};
	let errorCount = 0;

	const eol = es.through(function (file) {
		if (/\r\n?/g.test(file.contents.toString('utf8'))) {
			console.error(file.relative + ': Bad EOL found');
			errorCount++;
		}

		this.emit('data', file);
	});

	const indentation = es.through(function (file) {
		file.contents
			.toString('utf8')
			.split(/\r\n|\r|\n/)
			.forEach((line, i) => {
				if (/^\s*$/.test(line)) {
					// empty or whitespace lines are OK
				} else if (/^[\t]*[^\s]/.test(line)) {
					// good indent
				} else if (/^[\t]* \*/.test(line)) {
					// block comment using an extra space
				} else {
					console.error(file.relative + '(' + (i + 1) + ',1): Bad whitespace indentation');
					errorCount++;
				}
			});

		this.emit('data', file);
	});

	const copyrights = es.through(function (file) {
		if (file.contents.toString('utf8').indexOf(copyrightHeader) !== 0) {
			console.error(file.relative + ': Missing or bad copyright statement');
			errorCount++;
		}

		this.emit('data', file);
	});

	const formatting = es.map(function (file, cb) {
		tsfmt.processString(file.path, file.contents.toString('utf8'), {
			verify: true,
			tsfmt: true,
			// verbose: true
		}).then(result => {
			if (result.error) {
				console.error(result.message);
				errorCount++;
			}
			cb(null, file);

		}, err => {
			cb(err);
		});
	});

	const tsl = es.through(function (file) {
		const configuration = tslint.Configuration.findConfiguration(null, '.');
		const options = { formatter: 'json', rulesDirectory: 'build/lib/tslint' };
		const contents = file.contents.toString('utf8');
		const linter = new tslint.Linter(options);
		linter.lint(file.relative, contents, configuration.results);
		const result = linter.getResult();

		if (result.failureCount > 0) {
			reportFailures(result.failures);
			errorCount += result.failureCount;
		}

		this.emit('data', file);
	});

	const result = vfs.src(some || all, { base: '.', follow: true, allowEmpty: true })
		.pipe(filter(f => !f.stat.isDirectory()))
		.pipe(filter(eolFilter))
		.pipe(options.skipEOL ? es.through() : eol)
		.pipe(filter(indentationFilter))
		.pipe(indentation)
		.pipe(filter(copyrightFilter))
		.pipe(copyrights);

	const typescript = result
		.pipe(filter(tslintFilter))
		.pipe(formatting)
		.pipe(tsl);

	const javascript = result
		.pipe(filter(eslintFilter))
		.pipe(gulpeslint('src/.eslintrc'))
		.pipe(gulpeslint.formatEach('compact'))
		.pipe(gulpeslint.failAfterError());

	return es.merge(typescript, javascript)
		.pipe(es.through(null, function () {
			if (errorCount > 0) {
				this.emit('error', 'Hygiene failed with ' + errorCount + ' errors. Check \'build/gulpfile.hygiene.js\'.');
			} else {
				this.emit('end');
			}
		}));
};

gulp.task('hygiene', () => hygiene(''));

// this allows us to run hygiene as a git pre-commit hook
if (require.main === module) {
	const cp = require('child_process');

	process.on('unhandledRejection', (reason, p) => {
		console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
		process.exit(1);
	});

	cp.exec('git config core.autocrlf', (err, out) => {
		const skipEOL = out.trim() === 'true';

		if (process.argv.length > 2) {
			return hygiene(process.argv.slice(2), { skipEOL: skipEOL }).on('error', err => {
				console.error();
				console.error(err);
				process.exit(1);
			});
		}

		cp.exec('git diff --cached --name-only', { maxBuffer: 2000 * 1024 }, (err, out) => {
			if (err) {
				console.error();
				console.error(err);
				process.exit(1);
			}

			const some = out
				.split(/\r?\n/)
				.filter(l => !!l);

			hygiene(some, { skipEOL: skipEOL }).on('error', err => {
				console.error();
				console.error(err);
				process.exit(1);
			});
		});
	});
}
<commit_msg>fix eslint tslint tasks<commit_after>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

const gulp = require('gulp');
const filter = require('gulp-filter');
const es = require('event-stream');
const gulptslint = require('gulp-tslint');
const gulpeslint = require('gulp-eslint');
const tsfmt = require('typescript-formatter');
const tslint = require('tslint');
const vfs = require('vinyl-fs');

/**
 * Hygiene works by creating cascading subsets of all our files and
 * passing them through a sequence of checks. Here are the current subsets,
 * named according to the checks performed on them. Each subset contains
 * the following one, as described in mathematical notation:
 *
 * all ⊃ eol ⊇ indentation ⊃ copyright ⊃ typescript
 */

const all = [
	'*',
	'build/**/*',
	'extensions/**/*',
	'scripts/**/*',
	'src/**/*',
	'test/**/*'
];

const eolFilter = [
	'**',
	'!ThirdPartyNotices.txt',
	'!LICENSE.txt',
	'!extensions/**/out/**',
	'!test/smoke/out/**',
	'!**/node_modules/**',
	'!**/fixtures/**',
	'!**/*.{svg,exe,png,bmp,scpt,bat,cmd,cur,ttf,woff,eot}',
	'!build/{lib,tslintRules}/**/*.js',
	'!build/monaco/**',
	'!build/win32/**',
	'!build/**/*.sh',
	'!build/tfs/**/*.js',
	'!**/Dockerfile'
];

const indentationFilter = [
	'**',
	'!ThirdPartyNotices.txt',
	'!**/*.md',
	'!**/*.ps1',
	'!**/*.template',
	'!**/*.yaml',
	'!**/*.yml',
	'!**/lib/**',
	'!extensions/**/*.d.ts',
	'!src/typings/**/*.d.ts',
	'!src/vs/*/**/*.d.ts',
	'!**/*.d.ts.recipe',
	'!test/assert.js',
	'!**/package.json',
	'!**/npm-shrinkwrap.json',
	'!**/octicons/**',
	'!**/vs/base/common/marked/raw.marked.js',
	'!**/vs/base/common/winjs.base.raw.js',
	'!**/vs/base/node/terminateProcess.sh',
	'!**/vs/nls.js',
	'!**/vs/css.js',
	'!**/vs/loader.js',
	'!extensions/**/snippets/**',
	'!extensions/**/syntaxes/**',
	'!extensions/**/themes/**',
	'!extensions/**/colorize-fixtures/**',
	'!extensions/vscode-api-tests/testWorkspace/**'
];

const copyrightFilter = [
	'**',
	'!**/*.desktop',
	'!**/*.json',
	'!**/*.html',
	'!**/*.template',
	'!**/*.md',
	'!**/*.bat',
	'!**/*.cmd',
	'!**/*.xml',
	'!**/*.sh',
	'!**/*.txt',
	'!**/*.xpm',
	'!**/*.opts',
	'!**/*.disabled',
	'!build/**/*.init',
	'!resources/linux/snap/snapcraft.yaml',
	'!resources/win32/bin/code.js',
	'!extensions/markdown/media/tomorrow.css',
	'!extensions/html/server/src/modes/typescript/*'
];

const eslintFilter = [
	'src/**/*.js',
	'build/gulpfile.*.js',
	'!src/vs/loader.js',
	'!src/vs/css.js',
	'!src/vs/nls.js',
	'!src/vs/css.build.js',
	'!src/vs/nls.build.js',
	'!src/**/winjs.base.raw.js',
	'!src/**/raw.marked.js',
	'!**/test/**'
];

const tslintFilter = [
	'src/**/*.ts',
	'test/**/*.ts',
	'extensions/**/*.ts',
	'!**/fixtures/**',
	'!**/typings/**',
	'!**/node_modules/**',
	'!extensions/typescript/test/colorize-fixtures/**',
	'!extensions/vscode-api-tests/testWorkspace/**',
	'!extensions/**/*.test.ts'
];

const copyrightHeader = [
	'/*---------------------------------------------------------------------------------------------',
	' *  Copyright (c) Microsoft Corporation. All rights reserved.',
	' *  Licensed under the MIT License. See License.txt in the project root for license information.',
	' *--------------------------------------------------------------------------------------------*/'
].join('\n');

function reportFailures(failures) {
	failures.forEach(failure => {
		const name = failure.name || failure.fileName;
		const position = failure.startPosition;
		const line = position.lineAndCharacter ? position.lineAndCharacter.line : position.line;
		const character = position.lineAndCharacter ? position.lineAndCharacter.character : position.character;

		console.error(`${name}:${line + 1}:${character + 1}:${failure.failure}`);
	});
}

gulp.task('eslint', () => {
	return vfs.src(all, { base: '.', follow: true, allowEmpty: true })
		.pipe(filter(eslintFilter))
		.pipe(gulpeslint('src/.eslintrc'))
		.pipe(gulpeslint.formatEach('compact'))
		.pipe(gulpeslint.failAfterError());
});

gulp.task('tslint', () => {
	const options = { summarizeFailureOutput: true };

	return vfs.src(all, { base: '.', follow: true, allowEmpty: true })
		.pipe(filter(tslintFilter))
		.pipe(gulptslint({ rulesDirectory: 'build/lib/tslint' }))
		.pipe(gulptslint.report(reportFailures, options));
});

const hygiene = exports.hygiene = (some, options) => {
	options = options || {};
	let errorCount = 0;

	const eol = es.through(function (file) {
		if (/\r\n?/g.test(file.contents.toString('utf8'))) {
			console.error(file.relative + ': Bad EOL found');
			errorCount++;
		}

		this.emit('data', file);
	});

	const indentation = es.through(function (file) {
		file.contents
			.toString('utf8')
			.split(/\r\n|\r|\n/)
			.forEach((line, i) => {
				if (/^\s*$/.test(line)) {
					// empty or whitespace lines are OK
				} else if (/^[\t]*[^\s]/.test(line)) {
					// good indent
				} else if (/^[\t]* \*/.test(line)) {
					// block comment using an extra space
				} else {
					console.error(file.relative + '(' + (i + 1) + ',1): Bad whitespace indentation');
					errorCount++;
				}
			});

		this.emit('data', file);
	});

	const copyrights = es.through(function (file) {
		if (file.contents.toString('utf8').indexOf(copyrightHeader) !== 0) {
			console.error(file.relative + ': Missing or bad copyright statement');
			errorCount++;
		}

		this.emit('data', file);
	});

	const formatting = es.map(function (file, cb) {
		tsfmt.processString(file.path, file.contents.toString('utf8'), {
			verify: true,
			tsfmt: true,
			// verbose: true
		}).then(result => {
			if (result.error) {
				console.error(result.message);
				errorCount++;
			}
			cb(null, file);

		}, err => {
			cb(err);
		});
	});

	const tsl = es.through(function (file) {
		const configuration = tslint.Configuration.findConfiguration(null, '.');
		const options = { formatter: 'json', rulesDirectory: 'build/lib/tslint' };
		const contents = file.contents.toString('utf8');
		const linter = new tslint.Linter(options);
		linter.lint(file.relative, contents, configuration.results);
		const result = linter.getResult();

		if (result.failureCount > 0) {
			reportFailures(result.failures);
			errorCount += result.failureCount;
		}

		this.emit('data', file);
	});

	const result = vfs.src(some || all, { base: '.', follow: true, allowEmpty: true })
		.pipe(filter(f => !f.stat.isDirectory()))
		.pipe(filter(eolFilter))
		.pipe(options.skipEOL ? es.through() : eol)
		.pipe(filter(indentationFilter))
		.pipe(indentation)
		.pipe(filter(copyrightFilter))
		.pipe(copyrights);

	const typescript = result
		.pipe(filter(tslintFilter))
		.pipe(formatting)
		.pipe(tsl);

	const javascript = result
		.pipe(filter(eslintFilter))
		.pipe(gulpeslint('src/.eslintrc'))
		.pipe(gulpeslint.formatEach('compact'))
		.pipe(gulpeslint.failAfterError());

	return es.merge(typescript, javascript)
		.pipe(es.through(null, function () {
			if (errorCount > 0) {
				this.emit('error', 'Hygiene failed with ' + errorCount + ' errors. Check \'build/gulpfile.hygiene.js\'.');
			} else {
				this.emit('end');
			}
		}));
};

gulp.task('hygiene', () => hygiene(''));

// this allows us to run hygiene as a git pre-commit hook
if (require.main === module) {
	const cp = require('child_process');

	process.on('unhandledRejection', (reason, p) => {
		console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
		process.exit(1);
	});

	cp.exec('git config core.autocrlf', (err, out) => {
		const skipEOL = out.trim() === 'true';

		if (process.argv.length > 2) {
			return hygiene(process.argv.slice(2), { skipEOL: skipEOL }).on('error', err => {
				console.error();
				console.error(err);
				process.exit(1);
			});
		}

		cp.exec('git diff --cached --name-only', { maxBuffer: 2000 * 1024 }, (err, out) => {
			if (err) {
				console.error();
				console.error(err);
				process.exit(1);
			}

			const some = out
				.split(/\r?\n/)
				.filter(l => !!l);

			hygiene(some, { skipEOL: skipEOL }).on('error', err => {
				console.error();
				console.error(err);
				process.exit(1);
			});
		});
	});
}
<|endoftext|>