<commit_before>/* @flow */

import debounce from 'sb-debounce'
import disposableEvent from 'disposable-event'
import { CompositeDisposable, Disposable, Emitter, Range } from 'atom'
import type { TextEditor, BufferMarker, TextEditorGutter, Point } from 'atom'

import Tooltip from '../tooltip'
import { $range, filterMessagesByRangeOrPoint } from '../helpers'
import { hasParent, mouseEventNearPosition, getBufferPositionFromMouseEvent } from './helpers'
import type { LinterMessage } from '../types'

class Editor {
  gutter: ?TextEditorGutter;
  tooltip: ?Tooltip;
  emitter: Emitter;
  markers: Map<LinterMessage, BufferMarker>;
  messages: Set<LinterMessage>;
  textEditor: TextEditor;
  showTooltip: boolean;
  subscriptions: CompositeDisposable;
  cursorPosition: ?Point;
  gutterPosition: boolean;
  tooltipFollows: string;
  showDecorations: boolean;
  showProviderName: boolean;
  ignoreTooltipInvocation: boolean;

  constructor(textEditor: TextEditor) {
    this.tooltip = null
    this.emitter = new Emitter()
    this.markers = new Map()
    this.messages = new Set()
    this.textEditor = textEditor
    this.subscriptions = new CompositeDisposable()
    this.ignoreTooltipInvocation = false

    this.subscriptions.add(this.emitter)
    this.subscriptions.add(atom.config.observe('linter-ui-default.showTooltip', (showTooltip) => {
      this.showTooltip = showTooltip
      if (!this.showTooltip && this.tooltip) {
        this.removeTooltip()
      }
    }))
    this.subscriptions.add(atom.config.observe('linter-ui-default.showProviderName', (showProviderName) => {
      this.showProviderName = showProviderName
    }))
    this.subscriptions.add(atom.config.observe('linter-ui-default.showDecorations', (showDecorations) => {
      const notInitial = typeof this.showDecorations !== 'undefined'
      this.showDecorations = showDecorations
      if (notInitial) {
        this.updateGutter()
      }
    }))
    this.subscriptions.add(atom.config.observe('linter-ui-default.gutterPosition', (gutterPosition) => {
      const notInitial = typeof this.gutterPosition !== 'undefined'
      this.gutterPosition = gutterPosition
      if (notInitial) {
        this.updateGutter()
      }
    }))
    this.subscriptions.add(textEditor.onDidDestroy(() => {
      this.dispose()
    }))

    let tooltipSubscription
    this.subscriptions.add(atom.config.observe('linter-ui-default.tooltipFollows', (tooltipFollows) => {
      this.tooltipFollows = tooltipFollows
      if (tooltipSubscription) {
        tooltipSubscription.dispose()
      }
      tooltipSubscription = new CompositeDisposable()
      if (tooltipFollows === 'Mouse' || tooltipFollows === 'Both') {
        tooltipSubscription.add(this.listenForMouseMovement())
      }
      if (tooltipFollows === 'Keyboard' || tooltipFollows === 'Both') {
        tooltipSubscription.add(this.listenForKeyboardMovement())
      }
      this.removeTooltip()
    }))
    this.subscriptions.add(new Disposable(function() {
      tooltipSubscription.dispose()
    }))
    this.subscriptions.add(textEditor.onDidChangeCursorPosition(() => {
      this.ignoreTooltipInvocation = false
      if (this.tooltipFollows === 'Mouse') {
        this.removeTooltip()
      }
    }))
    this.subscriptions.add(textEditor.getBuffer().onDidChangeText(() => {
      if (this.tooltipFollows !== 'Mouse') {
        this.ignoreTooltipInvocation = true
        this.removeTooltip()
      }
    }))
    this.updateGutter()
    this.listenForCurrentLine()
  }
  listenForCurrentLine() {
    this.subscriptions.add(this.textEditor.observeCursors((cursor) => {
      let marker
      let lastRange
      let lastEmpty
      const handlePositionChange = ({ start, end }) => {
        const gutter = this.gutter
        if (!gutter || this.subscriptions.disposed) return
        // We need that Range.fromObject hack below because when we focus index 0 on multi-line selection
        // end.column is the column of the last line but making a range out of two and then accesing
        // the end seems to fix it (black magic?)
        const currentRange = Range.fromObject([start, end])
        const linesRange = Range.fromObject([[start.row, 0], [end.row, Infinity]])
        const currentEmpty = currentRange.isEmpty()

        // NOTE: Atom does not paint gutter if multi-line and last line has zero index
        if (start.row !== end.row && currentRange.end.column === 0) {
          linesRange.end.row--
        }
        if (lastRange && lastRange.isEqual(linesRange) && currentEmpty === lastEmpty) return
        if (marker) marker.destroy()
        lastRange = linesRange
        lastEmpty = currentEmpty

        marker = this.textEditor.markScreenRange(linesRange, {
          invalidate: 'never',
        })
        const item = document.createElement('span')
        item.className = `line-number cursor-line linter-cursor-line ${currentEmpty ? 'cursor-line-no-selection' : ''}`
        gutter.decorateMarker(marker, {
          item,
          class: 'linter-row',
        })
      }

      const cursorMarker = cursor.getMarker()
      const subscriptions = new CompositeDisposable()
      subscriptions.add(cursorMarker.onDidChange(({ newHeadScreenPosition, newTailScreenPosition }) => {
        handlePositionChange({ start: newHeadScreenPosition, end: newTailScreenPosition })
      }))
      subscriptions.add(cursor.onDidDestroy(() => {
        this.subscriptions.remove(subscriptions)
        subscriptions.dispose()
      }))
      subscriptions.add(new Disposable(function() {
        if (marker) marker.destroy()
      }))
      this.subscriptions.add(subscriptions)
      handlePositionChange(cursorMarker.getScreenRange())
    }))
  }
  listenForMouseMovement() {
    const editorElement = atom.views.getView(this.textEditor)

    return disposableEvent(editorElement, 'mousemove', debounce((event) => {
      if (!editorElement.component || this.subscriptions.disposed || !hasParent(event.target, 'div.scroll-view')) {
        return
      }
      const tooltip = this.tooltip
      if (tooltip && mouseEventNearPosition({
        event,
        editor: this.textEditor,
        editorElement,
        tooltipElement: tooltip.element,
        screenPosition: tooltip.marker.getStartScreenPosition(),
      })) {
        return
      }

      this.cursorPosition = getBufferPositionFromMouseEvent(event, this.textEditor, editorElement)
      this.ignoreTooltipInvocation = false
      if (this.textEditor.largeFileMode) {
        // NOTE: Ignore if file is too large
        this.cursorPosition = null
      }
      if (this.cursorPosition) {
        this.updateTooltip(this.cursorPosition)
      } else {
        this.removeTooltip()
      }
    }, 300, true))
  }
  listenForKeyboardMovement() {
    return this.textEditor.onDidChangeCursorPosition(debounce(({ newBufferPosition }) => {
      this.cursorPosition = newBufferPosition
      this.updateTooltip(newBufferPosition)
    }, 16))
  }
  updateGutter() {
    this.removeGutter()
    if (!this.showDecorations) {
      this.gutter = null
      return
    }
    const priority = this.gutterPosition === 'Left' ? -100 : 100
    this.gutter = this.textEditor.addGutter({
      name: 'linter-ui-default',
      priority,
    })
    this.markers.forEach((marker, message) => {
      this.decorateMarker(message, marker, 'gutter')
    })
  }
  removeGutter() {
    if (this.gutter) {
      try {
        this.gutter.destroy()
      } catch (_) {
        /* This throws when the text editor is disposed */
      }
    }
  }
  updateTooltip(position: ?Point) {
    if (!position || (this.tooltip && this.tooltip.isValid(position, this.messages))) {
      return
    }
    this.removeTooltip()
    if (!this.showTooltip) {
      return
    }
    if (this.ignoreTooltipInvocation) {
      return
    }

    const messages = filterMessagesByRangeOrPoint(this.messages, this.textEditor.getPath(), position)
    if (!messages.length) {
      return
    }

    this.tooltip = new Tooltip(messages, position, this.textEditor)
    this.tooltip.onDidDestroy(() => {
      this.tooltip = null
    })
  }
  removeTooltip() {
    if (this.tooltip) {
      this.tooltip.marker.destroy()
    }
  }
  apply(added: Array<LinterMessage>, removed: Array<LinterMessage>) {
    const textBuffer = this.textEditor.getBuffer()

    for (let i = 0, length = removed.length; i < length; i++) {
      const message = removed[i]
      const marker = this.markers.get(message)
      if (marker) {
        marker.destroy()
      }
      this.messages.delete(message)
      this.markers.delete(message)
    }

    for (let i = 0, length = added.length; i < length; i++) {
      const message = added[i]
      const markerRange = $range(message)
      if (!markerRange) {
        // Only for backward compatibility
        continue
      }
      const marker = textBuffer.markRange(markerRange, {
        invalidate: 'never',
      })
      this.markers.set(message, marker)
      this.messages.add(message)
      marker.onDidChange(({ oldHeadPosition, newHeadPosition, isValid }) => {
        if (!isValid || (newHeadPosition.row === 0 && oldHeadPosition.row !== 0)) {
          return
        }
        if (message.version === 1) {
          message.range = marker.previousEventState.range
        } else {
          message.location.position = marker.previousEventState.range
        }
      })
      this.decorateMarker(message, marker)
    }

    this.updateTooltip(this.cursorPosition)
  }
  decorateMarker(message: LinterMessage, marker: Object, paint: 'gutter' | 'editor' | 'both' = 'both') {
    if (paint === 'both' || paint === 'editor') {
      this.textEditor.decorateMarker(marker, {
        type: 'highlight',
        class: `linter-highlight linter-${message.severity}`,
      })
    }

    const gutter = this.gutter
    if (gutter && (paint === 'both' || paint === 'gutter')) {
      const element = document.createElement('span')
      element.className = `linter-gutter linter-highlight linter-${message.severity} icon icon-${message.icon || 'primitive-dot'}`
      gutter.decorateMarker(marker, {
        class: 'linter-row',
        item: element,
      })
    }
  }
  onDidDestroy(callback: Function): Disposable {
    return this.emitter.on('did-destroy', callback)
  }
  dispose() {
    this.emitter.emit('did-destroy')
    this.subscriptions.dispose()
    this.removeGutter()
    this.removeTooltip()
  }
}

module.exports = Editor
<commit_msg>:bug: Fix race condition where tooltip would cover autocomplete<commit_after>/* @flow */

import debounce from 'sb-debounce'
import disposableEvent from 'disposable-event'
import { CompositeDisposable, Disposable, Emitter, Range } from 'atom'
import type { TextEditor, BufferMarker, TextEditorGutter, Point } from 'atom'

import Tooltip from '../tooltip'
import { $range, filterMessagesByRangeOrPoint } from '../helpers'
import { hasParent, mouseEventNearPosition, getBufferPositionFromMouseEvent } from './helpers'
import type { LinterMessage } from '../types'

class Editor {
  gutter: ?TextEditorGutter;
  tooltip: ?Tooltip;
  emitter: Emitter;
  markers: Map<LinterMessage, BufferMarker>;
  messages: Set<LinterMessage>;
  textEditor: TextEditor;
  showTooltip: boolean;
  subscriptions: CompositeDisposable;
  cursorPosition: ?Point;
  gutterPosition: boolean;
  tooltipFollows: string;
  showDecorations: boolean;
  showProviderName: boolean;
  ignoreTooltipInvocation: boolean;

  constructor(textEditor: TextEditor) {
    this.tooltip = null
    this.emitter = new Emitter()
    this.markers = new Map()
    this.messages = new Set()
    this.textEditor = textEditor
    this.subscriptions = new CompositeDisposable()
    this.ignoreTooltipInvocation = false

    this.subscriptions.add(this.emitter)
    this.subscriptions.add(atom.config.observe('linter-ui-default.showTooltip', (showTooltip) => {
      this.showTooltip = showTooltip
      if (!this.showTooltip && this.tooltip) {
        this.removeTooltip()
      }
    }))
    this.subscriptions.add(atom.config.observe('linter-ui-default.showProviderName', (showProviderName) => {
      this.showProviderName = showProviderName
    }))
    this.subscriptions.add(atom.config.observe('linter-ui-default.showDecorations', (showDecorations) => {
      const notInitial = typeof this.showDecorations !== 'undefined'
      this.showDecorations = showDecorations
      if (notInitial) {
        this.updateGutter()
      }
    }))
    this.subscriptions.add(atom.config.observe('linter-ui-default.gutterPosition', (gutterPosition) => {
      const notInitial = typeof this.gutterPosition !== 'undefined'
      this.gutterPosition = gutterPosition
      if (notInitial) {
        this.updateGutter()
      }
    }))
    this.subscriptions.add(textEditor.onDidDestroy(() => {
      this.dispose()
    }))

    let tooltipSubscription
    this.subscriptions.add(atom.config.observe('linter-ui-default.tooltipFollows', (tooltipFollows) => {
      this.tooltipFollows = tooltipFollows
      if (tooltipSubscription) {
        tooltipSubscription.dispose()
      }
      tooltipSubscription = new CompositeDisposable()
      if (tooltipFollows === 'Mouse' || tooltipFollows === 'Both') {
        tooltipSubscription.add(this.listenForMouseMovement())
      }
      if (tooltipFollows === 'Keyboard' || tooltipFollows === 'Both') {
        tooltipSubscription.add(this.listenForKeyboardMovement())
      }
      this.removeTooltip()
    }))
    this.subscriptions.add(new Disposable(function() {
      tooltipSubscription.dispose()
    }))

    const lastCursorPositions = new WeakMap()
    this.subscriptions.add(textEditor.onDidChangeCursorPosition(({ cursor, newBufferPosition }) => {
      const lastBufferPosition = lastCursorPositions.get(cursor)
      if (!lastBufferPosition || !lastBufferPosition.isEqual(newBufferPosition)) {
        lastCursorPositions.set(cursor, newBufferPosition)
        this.ignoreTooltipInvocation = false
      }
      if (this.tooltipFollows === 'Mouse') {
        this.removeTooltip()
      }
    }))
    this.subscriptions.add(textEditor.getBuffer().onDidChangeText(() => {
      const cursors = textEditor.getCursors()
      cursors.forEach((cursor) => {
        lastCursorPositions.set(cursor, cursor.getBufferPosition())
      })
      if (this.tooltipFollows !== 'Mouse') {
        this.ignoreTooltipInvocation = true
        this.removeTooltip()
      }
    }))
    this.updateGutter()
    this.listenForCurrentLine()
  }
  listenForCurrentLine() {
    this.subscriptions.add(this.textEditor.observeCursors((cursor) => {
      let marker
      let lastRange
      let lastEmpty
      const handlePositionChange = ({ start, end }) => {
        const gutter = this.gutter
        if (!gutter || this.subscriptions.disposed) return
        // We need that Range.fromObject hack below because when we focus index 0 on multi-line selection
        // end.column is the column of the last line but making a range out of two and then accesing
        // the end seems to fix it (black magic?)
        const currentRange = Range.fromObject([start, end])
        const linesRange = Range.fromObject([[start.row, 0], [end.row, Infinity]])
        const currentEmpty = currentRange.isEmpty()

        // NOTE: Atom does not paint gutter if multi-line and last line has zero index
        if (start.row !== end.row && currentRange.end.column === 0) {
          linesRange.end.row--
        }
        if (lastRange && lastRange.isEqual(linesRange) && currentEmpty === lastEmpty) return
        if (marker) marker.destroy()
        lastRange = linesRange
        lastEmpty = currentEmpty

        marker = this.textEditor.markScreenRange(linesRange, {
          invalidate: 'never',
        })
        const item = document.createElement('span')
        item.className = `line-number cursor-line linter-cursor-line ${currentEmpty ? 'cursor-line-no-selection' : ''}`
        gutter.decorateMarker(marker, {
          item,
          class: 'linter-row',
        })
      }

      const cursorMarker = cursor.getMarker()
      const subscriptions = new CompositeDisposable()
      subscriptions.add(cursorMarker.onDidChange(({ newHeadScreenPosition, newTailScreenPosition }) => {
        handlePositionChange({ start: newHeadScreenPosition, end: newTailScreenPosition })
      }))
      subscriptions.add(cursor.onDidDestroy(() => {
        this.subscriptions.remove(subscriptions)
        subscriptions.dispose()
      }))
      subscriptions.add(new Disposable(function() {
        if (marker) marker.destroy()
      }))
      this.subscriptions.add(subscriptions)
      handlePositionChange(cursorMarker.getScreenRange())
    }))
  }
  listenForMouseMovement() {
    const editorElement = atom.views.getView(this.textEditor)

    return disposableEvent(editorElement, 'mousemove', debounce((event) => {
      if (!editorElement.component || this.subscriptions.disposed || !hasParent(event.target, 'div.scroll-view')) {
        return
      }
      const tooltip = this.tooltip
      if (tooltip && mouseEventNearPosition({
        event,
        editor: this.textEditor,
        editorElement,
        tooltipElement: tooltip.element,
        screenPosition: tooltip.marker.getStartScreenPosition(),
      })) {
        return
      }

      this.cursorPosition = getBufferPositionFromMouseEvent(event, this.textEditor, editorElement)
      this.ignoreTooltipInvocation = false
      if (this.textEditor.largeFileMode) {
        // NOTE: Ignore if file is too large
        this.cursorPosition = null
      }
      if (this.cursorPosition) {
        this.updateTooltip(this.cursorPosition)
      } else {
        this.removeTooltip()
      }
    }, 300, true))
  }
  listenForKeyboardMovement() {
    return this.textEditor.onDidChangeCursorPosition(debounce(({ newBufferPosition }) => {
      this.cursorPosition = newBufferPosition
      this.updateTooltip(newBufferPosition)
    }, 16))
  }
  updateGutter() {
    this.removeGutter()
    if (!this.showDecorations) {
      this.gutter = null
      return
    }
    const priority = this.gutterPosition === 'Left' ? -100 : 100
    this.gutter = this.textEditor.addGutter({
      name: 'linter-ui-default',
      priority,
    })
    this.markers.forEach((marker, message) => {
      this.decorateMarker(message, marker, 'gutter')
    })
  }
  removeGutter() {
    if (this.gutter) {
      try {
        this.gutter.destroy()
      } catch (_) {
        /* This throws when the text editor is disposed */
      }
    }
  }
  updateTooltip(position: ?Point) {
    if (!position || (this.tooltip && this.tooltip.isValid(position, this.messages))) {
      return
    }
    this.removeTooltip()
    if (!this.showTooltip) {
      return
    }
    if (this.ignoreTooltipInvocation) {
      return
    }

    const messages = filterMessagesByRangeOrPoint(this.messages, this.textEditor.getPath(), position)
    if (!messages.length) {
      return
    }

    this.tooltip = new Tooltip(messages, position, this.textEditor)
    this.tooltip.onDidDestroy(() => {
      this.tooltip = null
    })
  }
  removeTooltip() {
    if (this.tooltip) {
      this.tooltip.marker.destroy()
    }
  }
  apply(added: Array<LinterMessage>, removed: Array<LinterMessage>) {
    const textBuffer = this.textEditor.getBuffer()

    for (let i = 0, length = removed.length; i < length; i++) {
      const message = removed[i]
      const marker = this.markers.get(message)
      if (marker) {
        marker.destroy()
      }
      this.messages.delete(message)
      this.markers.delete(message)
    }

    for (let i = 0, length = added.length; i < length; i++) {
      const message = added[i]
      const markerRange = $range(message)
      if (!markerRange) {
        // Only for backward compatibility
        continue
      }
      const marker = textBuffer.markRange(markerRange, {
        invalidate: 'never',
      })
      this.markers.set(message, marker)
      this.messages.add(message)
      marker.onDidChange(({ oldHeadPosition, newHeadPosition, isValid }) => {
        if (!isValid || (newHeadPosition.row === 0 && oldHeadPosition.row !== 0)) {
          return
        }
        if (message.version === 1) {
          message.range = marker.previousEventState.range
        } else {
          message.location.position = marker.previousEventState.range
        }
      })
      this.decorateMarker(message, marker)
    }

    this.updateTooltip(this.cursorPosition)
  }
  decorateMarker(message: LinterMessage, marker: Object, paint: 'gutter' | 'editor' | 'both' = 'both') {
    if (paint === 'both' || paint === 'editor') {
      this.textEditor.decorateMarker(marker, {
        type: 'highlight',
        class: `linter-highlight linter-${message.severity}`,
      })
    }

    const gutter = this.gutter
    if (gutter && (paint === 'both' || paint === 'gutter')) {
      const element = document.createElement('span')
      element.className = `linter-gutter linter-highlight linter-${message.severity} icon icon-${message.icon || 'primitive-dot'}`
      gutter.decorateMarker(marker, {
        class: 'linter-row',
        item: element,
      })
    }
  }
  onDidDestroy(callback: Function): Disposable {
    return this.emitter.on('did-destroy', callback)
  }
  dispose() {
    this.emitter.emit('did-destroy')
    this.subscriptions.dispose()
    this.removeGutter()
    this.removeTooltip()
  }
}

module.exports = Editor
<|endoftext|>


<commit_before>/**
 * Copyright  2009-2012 A. Matas Quezada
 */

(function() {

	if (typeof window === 'undefined')
		module.exports = EventEmitter;
	else
		this.EventEmitter = EventEmitter;

	if (typeof Class === 'function' && typeof Class.extend === 'function')
		EventEmitter.extend = Class.extend;

	function EventEmitter() {
		this._listeners = {};
	}
	EventEmitter.prototype = {
		constructor: EventEmitter,

		emit: function(event, var_args) {
			var list = this._listeners[event];
			if (!list)
				return;

			var args = Array.prototype.slice.call(arguments, 1);
			for (var i = 0, len = list.length; i < len; i++)
				list[i].apply(null, args);
		},

		on: function(event, listener) {
			if (!this._listeners[event])
				this._listeners[event] = [];

			this._listeners[event].push(listener);
			this.emit('newListener', event, listener);
		},

		once: function(event, listener) {
			var self = this;

			this.on(event, function() {
				self.removeListener(event, listener);
				listener.apply(null, arguments);
			});
		},

		removeListener: function(event, listener) {
			var list = this._listeners[event];
			if (!list)
				return;

			var index = list.indexOf(listener);
			if (index !== -1)
				list.splice(index, 1);
		},

		removeAllListeners: function(event) {
			var list = this._listeners[event];
			if (list)
				list.length = 0;
		}
	};

})();<commit_msg>Renamed removeListener to off<commit_after>/**
 * Copyright  2009-2012 A. Matas Quezada
 */

(function() {

	if (typeof window === 'undefined')
		module.exports = EventEmitter;
	else
		this.EventEmitter = EventEmitter;

	if (typeof Class === 'function' && typeof Class.extend === 'function')
		EventEmitter.extend = Class.extend;

	function EventEmitter() {
		this._listeners = {};
	}
	EventEmitter.prototype = {
		constructor: EventEmitter,

		emit: function(event, var_args) {
			var list = this._listeners[event];
			if (!list)
				return;

			var args = Array.prototype.slice.call(arguments, 1);
			for (var i = 0, len = list.length; i < len; i++)
				list[i].apply(null, args);
		},

		on: function(event, listener) {
			if (!this._listeners[event])
				this._listeners[event] = [];

			this._listeners[event].push(listener);
			this.emit('newListener', event, listener);
		},

		once: function(event, listener) {
			var self = this;

			this.on(event, function() {
				self.removeListener(event, listener);
				listener.apply(null, arguments);
			});
		},

		off: function(event, listener) {
			var list = this._listeners[event];
			if (!list)
				return;

			var index = list.indexOf(listener);
			if (index !== -1)
				list.splice(index, 1);

		}
	};

})();<|endoftext|>


<commit_before>
// Express - Core - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)

/**
 * Module dependencies.
 */

var multipart = require('multipart'),
    utils = require('express/utils'),
    events = require('events'),
    fs = require('fs')

utils.mixin(require('sys'))
utils.mixin(require('express/exceptions'))
utils.mixin(require('express/collection'))
utils.mixin(require('express/event'))
utils.mixin(require('express/request'))
utils.mixin(require('express/plugin'))
utils.mixin(require('express/dsl'))

// --- Route

Route = Class({

  /**
   * Initialize a route with the given _method_,
   * _path_, and callback _fn_.
   *
   * The given _path_ becomes #originalPath,
   * #path is then a normalized version converted
   * to a regular expression for routing.
   *
   * @param  {string} method
   * @param  {string} path
   * @param  {function} fn
   * @param  {hash} options
   * @api private
   */

  init: function(method, path, fn, options){
    this.method = method
    this.originalPath = path
    this.path = this.normalize(path)
    this.fn = fn
  },

  /**
   * Normalize _path_. When a RegExp it is simply returned,
   * otherwise a string is converted to a regular expression
   * surrounded by ^$. So /user/:id/edit would become:
   *
   * /^\/user\/([^\/]+)\/edit$/i
   *
   * Each param key (:id) will be captured and placed in the
   * params array, so param('id') would give the string captured.
   *
   * The following are valid routes:
   *
   *  - /user/:id         Ex: '/user/12'
   *  - /user/:id?        Ex: '/user', '/user/12'
   *  - /report.:format   Ex: '/report.pdf', 'report.csv'
   *  - /public/*         Ex: '/public/app.js', '/public/javascripts/app.js'
   *  - /public/*.*       Ex: '/public/app.js', '/public/javascripts/app.js'
   *
   * @param  {string} path
   * @return {regexp}
   * @api private
   */

  normalize: function(path) {
    var self = this
    this.keys = []
    if (path instanceof RegExp) return path
    return new RegExp('^' + utils.escapeRegexp(normalizePath(path), '.')
      .replace(/\*/g, '(.+)')
      .replace(/(\/|\\\.):(\w+)\?/g, function(_, c, key){
        self.keys.push(key)
        return '(?:' + c + '([^\/]+))?'
      })
      .replace(/:(\w+)/g, function(_, key){
        self.keys.push(key)
        return '([^\/]+)'
      }) + '$', 'i')
  }
})

// --- Router

Router = Class({

  /**
   * Initialize with _request_ and parse url.
   *
   * @param  {Request} request
   * @api private
   */

  init: function(request) {
    this.request = request
  },

  /**
   * Evaluate the matched route against #request.
   *
   * @return {mixed}
   * @api private
   */

  route: function(){
    var route = this.matchingRoute()
    if (route)
      return route.fn.apply(this.request, this.request.captures.slice(1))
    else if (this.request.accepts('html') && set('helpful 404'))
      this.request.halt(404, require('express/pages/not-found').render(this.request))
    else
      this.request.halt()
  },

  /**
   * Attempt to match a route from Express.routes.
   *
   * @return {Route}
   * @api private
   */

  matchingRoute: function(){
    var self = this
    return $(Express.routes).find(function(route){
      return self.match(route)
    })
  },

  /**
   * Check if _route_ matches the current request.
   * If so populate #captures and #params.
   *
   * @param  {object} route
   * @return {bool}
   * @api private
   */

  match: function(route) {
    if (this.request.method.toLowerCase() == route.method)
      if (this.request.captures = this.request.url.pathname.match(route.path)) {
        this.mapParams(route)
        return true
      }
  },

  /**
   * Map #request.captures to #request.params.path based on the
   * given _route_ keys.
   *
   * @param  {Route} route
   * @api private
   */

  mapParams: function(route) {
    var self = this
    $(route.keys).each(function(key, i){
      self.request.params.path[key] = self.request.captures[++i]
    })
  }
})

// --- Server

Server = Class({

  /**
   * Default port number.
   */

  port: 3000,

  /**
   * Default host ip, when null node will accept requests on
   * all network addresses.
   */

  host: 'localhost',

  /**
   * Maximum number of queued connections.
   */

  backlog: 128,

  /**
   * Run Express.
   *
   *  - Buffers request bodies
   *  - Calls #route() once the request is complete
   *
   * @param  {int} port
   * @param  {string} host
   * @param  {int} backlog
   * @see run()
   * @api private
   */

  run: function(port, host, backlog){
    var self = this
    this.running = true
    if (host !== undefined) this.host = host
    if (port !== undefined) this.port = port
    if (backlog !== undefined) this.backlog = backlog
    require('http')
      .createServer(function(request, response){
        request.body = ''
        request.setBodyEncoding('binary')
        function callback(e, result) {
          if (e)
            self.error(e, request, response)
          else if (!pendingFiles)
            self.route(request, response)
        }
        if (request.headers['content-type'] &&
            request.headers['content-type'].indexOf('multipart/form-data') !== -1) {
          var stream = multipart.parse(request)
          var pendingFiles = 0
          request.params = { post: {}}
          stream
            .addListener('partBegin', function(part) {
              if (part.filename)
                ++pendingFiles,
                part.tempfile = '/tmp/express-' + Number(new Date) + utils.uid(),
                part.fileStream = fs.createWriteStream(part.tempfile)
              else
                part.buf = ''
            })
            .addListener('body', function(chunk) {
              if (stream.part.fileStream)
                stream.part.fileStream.write(chunk)
              else
                stream.part.buf += chunk
            })
            .addListener('partEnd', function(part) {
              if (!part.name) return
              if (part.fileStream)
                part.fileStream.close(function(){
                  --pendingFiles
                  callback()
                }),
                utils.mergeParam(part.name, { filename: part.filename, tempfile: part.tempfile }, request.params.post)
              else
                utils.mergeParam(part.name, part.buf, request.params.post)
            })
            .addListener('error', callback)
            .addListener('complete', callback)
        }
        else
          request
            .addListener('data', function(chunk){ request.body += chunk })
            .addListener('end', callback)
      })
      .listen(this.port, this.host, this.backlog)
    puts('Express started at http://' + this.host + ':' + this.port + '/ in ' + Express.environment + ' mode')
  },

  /**
   * Route the given _request_ and _response_.
   *
   * @param  {object} request
   * @param  {object} response
   * @api private
   */

  route: function(request, response){
    request = new Request(request, response)
    request.trigger('request')
    if (request.response.finished) return
    try {
      if (typeof (body = (new Router(request)).route()) == 'string')
        request.halt(200, body)
    }
    catch (e) {
      this.error(e, request)
    }
  },

  /**
   * Handle errors
   * @param {object} e
   * @param  {object} request
   * @param  {object} response
   * @api private
   */
  error: function (e, request, response) {
    if (e instanceof ExpressError)
      throw e
    if (!(request instanceof Request))
      request = new Request(request, response),
      request.trigger('request')
    if (request.accepts('html') && set('show exceptions'))
      request.halt(500, require('express/pages/show-exceptions').render(request, e))
    else
      request.halt(500)
    if (set('throw exceptions'))
      throw e
  }
})

// --- Express

Express = {
  version: '0.4.0',
  config: [],
  routes: [],
  plugins: [],
  settings: {},
  server: new Server
}

// --- Defaults

configure(function(){
  use(require('express/plugins/view').View)
  use(require('express/plugins/cache').Cache)
  use(require('express/plugins/redirect').Redirect)
  use(require('express/plugins/body-decoder').BodyDecoder)
})

configure('development', function(){
  enable('helpful 404')
  enable('show exceptions')
})

configure('test', function(){
  enable('throw exceptions')
})

configure('production', function(){
  enable('cache view contents')
  enable('cache static files')
})
<commit_msg>Fixing some comments<commit_after>
// Express - Core - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)

/**
 * Module dependencies.
 */

var multipart = require('multipart'),
    utils = require('express/utils'),
    events = require('events'),
    fs = require('fs')

utils.mixin(require('sys'))
utils.mixin(require('express/exceptions'))
utils.mixin(require('express/collection'))
utils.mixin(require('express/event'))
utils.mixin(require('express/request'))
utils.mixin(require('express/plugin'))
utils.mixin(require('express/dsl'))

// --- Route

Route = Class({

  /**
   * Initialize a route with the given _method_,
   * _path_, and callback _fn_.
   *
   * The given _path_ becomes #originalPath,
   * #path is then a normalized version converted
   * to a regular expression for routing.
   *
   * @param  {string} method
   * @param  {string} path
   * @param  {function} fn
   * @param  {hash} options
   * @api private
   */

  init: function(method, path, fn, options){
    this.method = method
    this.originalPath = path
    this.path = this.normalize(path)
    this.fn = fn
  },

  /**
   * Normalize _path_. When a RegExp it is simply returned,
   * otherwise a string is converted to a regular expression
   * surrounded by ^$. So /user/:id/edit would become:
   *
   * /^\/user\/([^\/]+)\/edit$/i
   *
   * Each param key (:id) will be captured and placed in the
   * params array, so param('id') would give the string captured.
   *
   * The following are valid routes:
   *
   *  - /user/:id         Ex: '/user/12'
   *  - /user/:id?        Ex: '/user', '/user/12'
   *  - /report.:format   Ex: '/report.pdf', 'report.csv'
   *  - /public/*         Ex: '/public/app.js', '/public/javascripts/app.js'
   *  - /public/*.*       Ex: '/public/app.js', '/public/javascripts/app.js'
   *
   * @param  {string} path
   * @return {regexp}
   * @api private
   */

  normalize: function(path) {
    var self = this
    this.keys = []
    if (path instanceof RegExp) return path
    return new RegExp('^' + utils.escapeRegexp(normalizePath(path), '.')
      .replace(/\*/g, '(.+)')
      .replace(/(\/|\\\.):(\w+)\?/g, function(_, c, key){
        self.keys.push(key)
        return '(?:' + c + '([^\/]+))?'
      })
      .replace(/:(\w+)/g, function(_, key){
        self.keys.push(key)
        return '([^\/]+)'
      }) + '$', 'i')
  }
})

// --- Router

Router = Class({

  /**
   * Initialize with _request_ and parse url.
   *
   * @param  {Request} request
   * @api private
   */

  init: function(request) {
    this.request = request
  },

  /**
   * Evaluate the matched route against #request.
   *
   * @return {mixed}
   * @api private
   */

  route: function(){
    var route = this.matchingRoute()
    if (route)
      return route.fn.apply(this.request, this.request.captures.slice(1))
    else if (this.request.accepts('html') && set('helpful 404'))
      this.request.halt(404, require('express/pages/not-found').render(this.request))
    else
      this.request.halt()
  },

  /**
   * Attempt to match a route from Express.routes.
   *
   * @return {Route}
   * @api private
   */

  matchingRoute: function(){
    var self = this
    return $(Express.routes).find(function(route){
      return self.match(route)
    })
  },

  /**
   * Check if _route_ matches the current request.
   * If so populate #captures and #params.
   *
   * @param  {object} route
   * @return {bool}
   * @api private
   */

  match: function(route) {
    if (this.request.method.toLowerCase() == route.method)
      if (this.request.captures = this.request.url.pathname.match(route.path)) {
        this.mapParams(route)
        return true
      }
  },

  /**
   * Map #request.captures to #request.params.path based on the
   * given _route_ keys.
   *
   * @param  {Route} route
   * @api private
   */

  mapParams: function(route) {
    var self = this
    $(route.keys).each(function(key, i){
      self.request.params.path[key] = self.request.captures[++i]
    })
  }
})

// --- Server

Server = Class({

  /**
   * Default port number.
   */

  port: 3000,

  /**
   * Default host ip, when null node will accept requests on
   * all network addresses.
   */

  host: 'localhost',

  /**
   * Maximum number of queued connections.
   */

  backlog: 128,

  /**
   * Run Express.
   *
   *  - Buffers request bodies
   *  - Calls #route() once the request is complete
   *
   * @param  {int} port
   * @param  {string} host
   * @param  {int} backlog
   * @see run()
   * @api private
   */

  run: function(port, host, backlog){
    var self = this
    this.running = true
    if (host !== undefined) this.host = host
    if (port !== undefined) this.port = port
    if (backlog !== undefined) this.backlog = backlog
    require('http')
      .createServer(function(request, response){
        request.body = ''
        request.setBodyEncoding('binary')
        function callback(e, result) {
          if (e)
            self.error(e, request, response)
          else if (!pendingFiles)
            self.route(request, response)
        }
        if (request.headers['content-type'] &&
            request.headers['content-type'].indexOf('multipart/form-data') !== -1) {
          var stream = multipart.parse(request)
          var pendingFiles = 0
          request.params = { post: {}}
          stream
            .addListener('partBegin', function(part) {
              if (part.filename)
                ++pendingFiles,
                part.tempfile = '/tmp/express-' + Number(new Date) + utils.uid(),
                part.fileStream = fs.createWriteStream(part.tempfile)
              else
                part.buf = ''
            })
            .addListener('body', function(chunk) {
              if (stream.part.fileStream)
                stream.part.fileStream.write(chunk)
              else
                stream.part.buf += chunk
            })
            .addListener('partEnd', function(part) {
              if (!part.name) return
              if (part.fileStream)
                part.fileStream.close(function(){
                  --pendingFiles
                  callback()
                }),
                utils.mergeParam(part.name, { filename: part.filename, tempfile: part.tempfile }, request.params.post)
              else
                utils.mergeParam(part.name, part.buf, request.params.post)
            })
            .addListener('error', callback)
            .addListener('complete', callback)
        }
        else
          request
            .addListener('data', function(chunk){ request.body += chunk })
            .addListener('end', callback)
      })
      .listen(this.port, this.host, this.backlog)
    puts('Express started at http://' + this.host + ':' + this.port + '/ in ' + Express.environment + ' mode')
  },

  /**
   * Route the given _request_ and _response_.
   *
   * @param  {object} request
   * @param  {object} response
   * @api private
   */

  route: function(request, response){
    request = new Request(request, response)
    request.trigger('request')
    if (request.response.finished) return
    try {
      if (typeof (body = (new Router(request)).route()) == 'string')
        request.halt(200, body)
    }
    catch (e) {
      this.error(e, request)
    }
  },

  /**
   * Handle errors.
   *
   * @param {Error} e
   * @param {object} request
   * @param {object} response
   * @api private
   */

  error: function (e, request, response) {
    if (e instanceof ExpressError)
      throw e
    if (!(request instanceof Request))
      request = new Request(request, response),
      request.trigger('request')
    if (request.accepts('html') && set('show exceptions'))
      request.halt(500, require('express/pages/show-exceptions').render(request, e))
    else
      request.halt(500)
    if (set('throw exceptions'))
      throw e
  }
})

// --- Express

Express = {
  version: '0.4.0',
  config: [],
  routes: [],
  plugins: [],
  settings: {},
  server: new Server
}

// --- Defaults

configure(function(){
  use(require('express/plugins/view').View)
  use(require('express/plugins/cache').Cache)
  use(require('express/plugins/redirect').Redirect)
  use(require('express/plugins/body-decoder').BodyDecoder)
})

configure('development', function(){
  enable('helpful 404')
  enable('show exceptions')
})

configure('test', function(){
  enable('throw exceptions')
})

configure('production', function(){
  enable('cache view contents')
  enable('cache static files')
})
<|endoftext|>


<commit_before>var channel_name;
var network_name;

if(channel_name === undefined) {
    channel_name = "#korobi"
}
if(network_name === undefined) {
    network_name = "esper"
}
network_name.toLowerCase();

// Mock stuff
var channel = {
    channel: channel_name,
    command_prefix: ".",
    commands_enabled: true,
    key: null,
    last_activity: new Date(),
    last_valid_content_at: new Date(),
    logs_enabled: true,
    managers: [
        5474071
    ],
    network: network_name,
    permissions: [
        "grant.command.operator.modify",
        "grant.punishment.operator.warn",
        "grant.punishment.operator.warn.normal",
        "grant.punishment.operator.warn.normal.anonymous",
        "grant.punishment.operator.kick",
        "grant.punishment.operator.kick.normal",
        "grant.punishment.operator.kick.normal.anonymous",
        "grant.punishment.operator.ban",
        "grant.punishment.operator.ban.normal",
        "grant.punishment.operator.ban.normal.anonymous"
    ],
    punishments_enabled: true,
    repositories: [
        "korobi.Korobi"
    ],
    topic: {
        actor_host: "$Internal",
        actor_nick: "$Internal",
        time: new Date(),
        value: "\u000310Korobi \u000305| \u000312https://korobi.io | development at https://github.com/korobi/Web \u000305| \u000f@mbaxter | Kashike: Anything else, m'love?"
    }
}

var chats = [{
    actor_hostname: "bendem!bendem@irc.bendem.be",
    actor_name: "bendem",
    actor_prefix: "NORMAL",
    channel: channel_name,
    channel_mode: null,
    date: new Date(),
    message: "",
    network: network_name,
    recipient_hostname: null,
    recipient_name: null,
    recipient_prefix: null,
    type: "JOIN",
    channel_object_id: null
}, {
    actor_hostname: "Borg!Borg@resistance.is.futile.services.esper.net",
    actor_name: "Borg",
    actor_prefix: "OPERATOR",
    channel: channel_name,
    channel_mode: null,
    date: new Date(),
    imported: false,
    message: "+",
    network: network_name,
    recipient_hostname: "bendem!bendem@irc.bendem.be",
    recipient_name: "bendem",
    recipient_prefix: "OPERATOR",
    type: "MODE",
    channel_object_id: null
}, {
    actor_hostname: "kashike!kashike@im.tired.of.this.black.and.blue.kitteh.club",
    actor_name: "kashike",
    actor_prefix: "OPERATOR",
    channel: channel_name,
    channel_mode: null,
    date: new Date(),
    message: "bendem: You're the best",
    network: network_name,
    recipient_hostname: null,
    recipient_name: null,
    recipient_prefix: null,
    type: "MESSAGE",
    channel_object_id: null
}, {
    actor_hostname: "lol768!lol7681@bnc.lol768.com",
    actor_name: "lol768",
    actor_prefix: "OPERATOR",
    channel: channel_name,
    channel_mode: null,
    date: new Date(),
    message: "kashike, Ikr? He really is so cool!",
    network: network_name,
    recipient_hostname: null,
    recipient_name: null,
    recipient_prefix: null,
    type: "MESSAGE",
    channel_object_id: null
}, {
    actor_hostname: "bendem!bendem@irc.bendem.be",
    actor_name: "bendem",
    actor_prefix: "OPERATOR",
    channel: channel_name,
    channel_mode: null,
    date: new Date(),
    message: "Oh thanks, you're all so nice",
    network: network_name,
    recipient_hostname: null,
    recipient_name: null,
    recipient_prefix: null,
    type: "MESSAGE",
    channel_object_id: null
}]

var chat_index = {
    network: network_name,
    channel: channel_name,
    year: new Date().getFullYear(),
    day_of_year: null,
    has_valid_content: true
}

var channel_query = {
    network: network_name,
    channel: channel_name
}

// Insert stuff
if (db.networks.find({"slug": network_name}).count() === 0) {
    network_map = {
        "esper": "EsperNet",
        "spigotmc": "Spigot",
        "spigot": "Spigot",
        "freenode": "Freenode"
    }

    var network_friendly_name  = network_map[network_name] || network_name + " Network"

    db.networks.insert({
        "slug" : network_name,
        "name" : network_friendly_name,
        "network_id" : network_name
    })
}

if(db.channels.find(channel_query).count() === 0) {
    db.channels.insert(channel)
}
var ch = db.channels.findOne(channel_query)
var cid = ch._id

chats.map(function(c) {
    c.channel_object_id = cid
    return c
}).forEach(function(c) {
    db.chats.insert(c)
})

chat_index.day_of_year = db.channels.aggregate([
    { $match: { _id: cid } },
    { $project: { doy: { $dayOfYear: "$last_activity" } } }
]).toArray()[0].doy

if(db.chat_indexes.find(chat_index).count() === 0) {
    db.chat_indexes.insert(chat_index)
}
<commit_msg>Properly lowercase network_name<commit_after>var channel_name;
var network_name;

if(channel_name === undefined) {
    channel_name = "#korobi"
}
if(network_name === undefined) {
    network_name = "esper"
}
network_name = network_name.toLowerCase();

// Mock stuff
var channel = {
    channel: channel_name,
    command_prefix: ".",
    commands_enabled: true,
    key: null,
    last_activity: new Date(),
    last_valid_content_at: new Date(),
    logs_enabled: true,
    managers: [
        5474071
    ],
    network: network_name,
    permissions: [
        "grant.command.operator.modify",
        "grant.punishment.operator.warn",
        "grant.punishment.operator.warn.normal",
        "grant.punishment.operator.warn.normal.anonymous",
        "grant.punishment.operator.kick",
        "grant.punishment.operator.kick.normal",
        "grant.punishment.operator.kick.normal.anonymous",
        "grant.punishment.operator.ban",
        "grant.punishment.operator.ban.normal",
        "grant.punishment.operator.ban.normal.anonymous"
    ],
    punishments_enabled: true,
    repositories: [
        "korobi.Korobi"
    ],
    topic: {
        actor_host: "$Internal",
        actor_nick: "$Internal",
        time: new Date(),
        value: "\u000310Korobi \u000305| \u000312https://korobi.io | development at https://github.com/korobi/Web \u000305| \u000f@mbaxter | Kashike: Anything else, m'love?"
    }
}

var chats = [{
    actor_hostname: "bendem!bendem@irc.bendem.be",
    actor_name: "bendem",
    actor_prefix: "NORMAL",
    channel: channel_name,
    channel_mode: null,
    date: new Date(),
    message: "",
    network: network_name,
    recipient_hostname: null,
    recipient_name: null,
    recipient_prefix: null,
    type: "JOIN",
    channel_object_id: null
}, {
    actor_hostname: "Borg!Borg@resistance.is.futile.services.esper.net",
    actor_name: "Borg",
    actor_prefix: "OPERATOR",
    channel: channel_name,
    channel_mode: null,
    date: new Date(),
    imported: false,
    message: "+",
    network: network_name,
    recipient_hostname: "bendem!bendem@irc.bendem.be",
    recipient_name: "bendem",
    recipient_prefix: "OPERATOR",
    type: "MODE",
    channel_object_id: null
}, {
    actor_hostname: "kashike!kashike@im.tired.of.this.black.and.blue.kitteh.club",
    actor_name: "kashike",
    actor_prefix: "OPERATOR",
    channel: channel_name,
    channel_mode: null,
    date: new Date(),
    message: "bendem: You're the best",
    network: network_name,
    recipient_hostname: null,
    recipient_name: null,
    recipient_prefix: null,
    type: "MESSAGE",
    channel_object_id: null
}, {
    actor_hostname: "lol768!lol7681@bnc.lol768.com",
    actor_name: "lol768",
    actor_prefix: "OPERATOR",
    channel: channel_name,
    channel_mode: null,
    date: new Date(),
    message: "kashike, Ikr? He really is so cool!",
    network: network_name,
    recipient_hostname: null,
    recipient_name: null,
    recipient_prefix: null,
    type: "MESSAGE",
    channel_object_id: null
}, {
    actor_hostname: "bendem!bendem@irc.bendem.be",
    actor_name: "bendem",
    actor_prefix: "OPERATOR",
    channel: channel_name,
    channel_mode: null,
    date: new Date(),
    message: "Oh thanks, you're all so nice",
    network: network_name,
    recipient_hostname: null,
    recipient_name: null,
    recipient_prefix: null,
    type: "MESSAGE",
    channel_object_id: null
}]

var chat_index = {
    network: network_name,
    channel: channel_name,
    year: new Date().getFullYear(),
    day_of_year: null,
    has_valid_content: true
}

var channel_query = {
    network: network_name,
    channel: channel_name
}

// Insert stuff
if (db.networks.find({"slug": network_name}).count() === 0) {
    network_map = {
        "esper": "EsperNet",
        "spigotmc": "Spigot",
        "spigot": "Spigot",
        "freenode": "Freenode"
    }

    var network_friendly_name  = network_map[network_name] || network_name + " Network"

    db.networks.insert({
        "slug" : network_name,
        "name" : network_friendly_name,
        "network_id" : network_name
    })
}

if(db.channels.find(channel_query).count() === 0) {
    db.channels.insert(channel)
}
var ch = db.channels.findOne(channel_query)
var cid = ch._id

chats.map(function(c) {
    c.channel_object_id = cid
    return c
}).forEach(function(c) {
    db.chats.insert(c)
})

chat_index.day_of_year = db.channels.aggregate([
    { $match: { _id: cid } },
    { $project: { doy: { $dayOfYear: "$last_activity" } } }
]).toArray()[0].doy

if(db.chat_indexes.find(chat_index).count() === 0) {
    db.chat_indexes.insert(chat_index)
}
<|endoftext|>


<commit_before>
/*!
 * Express - View
 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var extname = require('path').extname
  , utils = require('connect').utils
  , http = require('http')
  , fs = require('fs')
  , mime = utils.mime;

/**
 * Cache supported template engine exports to
 * increase performance by lowering the number
 * of calls to `require()`.
 *
 * @type Object
 */

var cache = {};

/**
 * Cache view contents to prevent I/O hits.
 *
 * @type Object
 */

var viewCache = {};

/**
 * Cache view path derived names.
 *
 * @type Object
 */

var viewNameCache = {};

/**
 * Synchronously cache view at the given `path`.
 *
 * @param {String} path
 * @return {String}
 * @api private
 */

function cacheViewSync(path) {
  return viewCache[path] = fs.readFileSync(path, 'utf8');
}

/**
 * Return view root path for the given `app`.
 *
 * @param {express.Server} app
 * @return {String}
 * @api private
 */

function viewRoot(app) {
  return app.set('views') || process.cwd() + '/views';
}

/**
 * Register the given template engine `exports`
 * as `ext`. For example we may wish to map ".html"
 * files to jade:
 *
 *    app.register('.html', require('jade'));
 *
 * This is also useful for libraries that may not
 * match extensions correctly. For example my haml.js
 * library is installed from npm as "hamljs" so instead
 * of layout.hamljs, we can register the engine as ".haml":
 *
 *    app.register('.haml', require('haml-js'));
 *
 * For engines that do not comply with the Express
 * specification, we can also wrap their api this way.
 *
 *    app.register('.foo', {
 *        render: function(str, options) {
 *            // perhaps their api is
 *            // return foo.toHTML(str, options);
 *        }
 *    });
 *
 * @param {String} ext
 * @param {Object} obj
 * @api public
 */

exports.register = function(ext, exports) {
  cache[ext] = exports;
};

/**
 * Render `view` partial with the given `options`.
 *
 * Options:
 *   - `object` Single object with name derived from the view (unless `as` is present)
 *
 *   - `as` Variable name for each `collection` value, defaults to the view name.
 *     * as: 'something' will add the `something` local variable
 *     * as: this will use the collection value as the template context
 *     * as: global will merge the collection value's properties with `locals`
 *
 *   - `collection` Array of objects, the name is derived from the view name itself.
 *     For example _video.html_ will have a object _video_ available to it.
 *
 * @param  {String} view
 * @param  {Object|Array} options, collection, or object
 * @return {String}
 * @api public
 */

http.ServerResponse.prototype.partial = function(view, options, ext, locals){
  locals = locals || {};

  // Inherit parent view extension when not present
  if (ext && view.indexOf('.') < 0) {
    view += ext;
  }

  // Allow collection to be passed as second param
  if (options) {
    if ('length' in options) {
      options = { collection: options };
    } else if (!options.collection && !options.locals && !options.object) {
      options = { object: options };
    }
  } else {
    options = {};
  }

  // Inherit locals from parent
  options.locals = options.locals
    ? utils.merge(locals, options.locals)
    : locals;

  // Partials dont need layouts
  options.partial = true;
  options.layout = false;

  // Deduce name from view path
  var name = options.as
    || viewNameCache[view]
    || (viewNameCache[view] = view.split('/').slice(-1)[0].split('.')[0]);

  // Collection support
  var collection = options.collection;
  if (collection) {
    var len = collection.length
      , buf = '';
    delete options.collection;
    options.locals.collectionLength = len;
    for (var i = 0; i < len; ++i) {
      var val = collection[i];
      options.locals.firstInCollection = i === 0;
      options.locals.indexInCollection = i;
      options.locals.lastInCollection = i === len - 1;
      options.object = val;
      buf += this.partial(view, options);
    }
    return buf;
  } else {
    if (options.object) {
      if ('string' == typeof name) {
        options.locals[name] = options.object;
      } else if (name === global) {
        utils.merge(options.locals, options.object);
      } else {
        options.scope = options.object;
      }
    }
    return this.render(view, options);
  }
};

/**
 * Render `view` with the given `options` and optional callback `fn`.
 * When a callback function is given a response will _not_ be made
 * automatically, however otherwise a response of _200_ and _text/html_ is given.
 *
 * Options:
 *
 *  Most engines accept one or more of the following options,
 *  both _haml_ and _jade_ accept all:
 *
 *  - `scope`     Template evaluation context (the value of `this`)
 *  - `locals`    Object containing local variables
 *  - `debug`     Output debugging information
 *  - `status`    Response status code, defaults to 200
 *  - `headers`   Response headers object
 *
 * @param  {String} view
 * @param  {Object|Function} options or callback function
 * @param  {Function} fn
 * @api public
 */

http.ServerResponse.prototype.render = function(view, options, fn){
  // Support callback function as second arg
  if (typeof options === 'function') {
    fn = options, options = {};
  }

  var options = options || {}
    , app = this.app
    , viewOptions = app.settings['view options']
    , defaultEngine = app.settings['view engine'];

  // Mixin "view options"
  if (viewOptions) options.__proto__ = viewOptions;

  // Support "view engine" setting
  if (view.indexOf('.') < 0 && defaultEngine) {
    view += '.' + defaultEngine;
  }

  // Defaults
  var self = this
    , helpers = this.app.viewHelpers
    , dynamicHelpers = this.app.dynamicViewHelpers
    , root = viewRoot(this.app)
    , ext = extname(view)
    , partial = options.partial
    , layout = options.layout === undefined ? true : options.layout
    , layout = layout === true
      ? 'layout' + ext
      : layout;

  // Allow layout name without extension
  if (typeof layout === 'string' && layout.indexOf('.') < 0) {
    layout += ext;
  }

  // Default execution scope to the response
  options.scope = options.scope || this.req;

  // Auto-cache in production
  if (this.app.set('env') === 'production') {
    options.cache = true;
  }

  // Partials support
  if (options.partial) {
    root = app.settings.partials || root + '/partials';
  }

  // View path
  var path = view[0] === '/'
    ? view
    : root + '/' + view;

  // Pass filename to the engine and view
  var locals = options.locals = options.locals || {};
  options.locals.__filename = options.filename = path;

  // Dynamic helper support
  if (false !== options.dynamicHelpers) {
    // cache
    if (!this.__dynamicHelpers) {
      this.__dynamicHelpers = {};
      var keys = Object.keys(dynamicHelpers);
      for (var i = 0, len = keys.length; i < len; ++i) {
        var key = keys[i]
          , val = dynamicHelpers[key];
        if (typeof val === 'function') {
          this.__dynamicHelpers[key] = val.call(
                this.app
              , this.req
              , this);
        }
      }
    }

    // apply
    helpers.__proto__ = this.__dynamicHelpers;
  }

  // Merge view helpers
  options.locals.__proto__ = helpers;

  // Always expose partial() as a local
  options.locals.partial = function(view, options){
    return self.partial.call(self, view, options, ext, locals);
  };

  function error(err) {
    if (fn) {
      fn(err);
    } else {
      self.req.next(err);
    }
  }

  // Cache contents
  try {
    var str = (options.cache ? viewCache[path] : null) || cacheViewSync(path);
  } catch (err) {
    return error(err);
  }

  // Cache template engine exports
  var engine = cache[ext] || (cache[ext] = require(ext.substr(1)));

  // Attempt render
  try {
    var str = engine.render(str, options);
  } catch (err) {
    return error(err);
  }

  // Layout support
  if (layout) {
    options.layout = false;
    options.locals.body = str;
    options.isLayout = true;
    self.render(layout, options, fn);
  } else if (partial) {
    return str;
  } else if (fn) {
    fn(null, str);
  } else {
    this.send(str, options.headers, options.status);
  }
};
<commit_msg>Added objectName()<commit_after>
/*!
 * Express - View
 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var extname = require('path').extname
  , utils = require('connect').utils
  , http = require('http')
  , fs = require('fs')
  , mime = utils.mime;

/**
 * Cache supported template engine exports to
 * increase performance by lowering the number
 * of calls to `require()`.
 *
 * @type Object
 */

var cache = {};

/**
 * Cache view contents to prevent I/O hits.
 *
 * @type Object
 */

var viewCache = {};

/**
 * Cache view path derived names.
 *
 * @type Object
 */

var viewNameCache = {};

/**
 * Synchronously cache view at the given `path`.
 *
 * @param {String} path
 * @return {String}
 * @api private
 */

function cacheViewSync(path) {
  return viewCache[path] = fs.readFileSync(path, 'utf8');
}

/**
 * Return view root path for the given `app`.
 *
 * @param {express.Server} app
 * @return {String}
 * @api private
 */

function viewRoot(app) {
  return app.set('views') || process.cwd() + '/views';
}

/**
 * Return object name deduced from the given `view` path.
 *
 * Examples:
 *
 *    "movie/director" -> director
 *    "movie.director" -> director
 *    "forum/thread/post" -> post
 *    "forum/thread.post" -> post
 *
 * @param {String} view
 * @return {String}
 * @api private
 */

function objectName(view) {
  return view.split('/').slice(-1)[0].split('.')[0];
}

/**
 * Register the given template engine `exports`
 * as `ext`. For example we may wish to map ".html"
 * files to jade:
 *
 *    app.register('.html', require('jade'));
 *
 * This is also useful for libraries that may not
 * match extensions correctly. For example my haml.js
 * library is installed from npm as "hamljs" so instead
 * of layout.hamljs, we can register the engine as ".haml":
 *
 *    app.register('.haml', require('haml-js'));
 *
 * For engines that do not comply with the Express
 * specification, we can also wrap their api this way.
 *
 *    app.register('.foo', {
 *        render: function(str, options) {
 *            // perhaps their api is
 *            // return foo.toHTML(str, options);
 *        }
 *    });
 *
 * @param {String} ext
 * @param {Object} obj
 * @api public
 */

exports.register = function(ext, exports) {
  cache[ext] = exports;
};

/**
 * Render `view` partial with the given `options`.
 *
 * Options:
 *   - `object` Single object with name derived from the view (unless `as` is present)
 *
 *   - `as` Variable name for each `collection` value, defaults to the view name.
 *     * as: 'something' will add the `something` local variable
 *     * as: this will use the collection value as the template context
 *     * as: global will merge the collection value's properties with `locals`
 *
 *   - `collection` Array of objects, the name is derived from the view name itself.
 *     For example _video.html_ will have a object _video_ available to it.
 *
 * @param  {String} view
 * @param  {Object|Array} options, collection, or object
 * @return {String}
 * @api public
 */

http.ServerResponse.prototype.partial = function(view, options, ext, locals){
  locals = locals || {};

  // Inherit parent view extension when not present
  if (ext && view.indexOf('.') < 0) {
    view += ext;
  }

  // Allow collection to be passed as second param
  if (options) {
    if ('length' in options) {
      options = { collection: options };
    } else if (!options.collection && !options.locals && !options.object) {
      options = { object: options };
    }
  } else {
    options = {};
  }

  // Inherit locals from parent
  options.locals = options.locals
    ? utils.merge(locals, options.locals)
    : locals;

  // Partials dont need layouts
  options.partial = true;
  options.layout = false;

  // Deduce name from view path
  var name = options.as
    || viewNameCache[view]
    || (viewNameCache[view] = objectName(view));

  // Collection support
  var collection = options.collection;
  if (collection) {
    var len = collection.length
      , buf = '';
    delete options.collection;
    options.locals.collectionLength = len;
    for (var i = 0; i < len; ++i) {
      var val = collection[i];
      options.locals.firstInCollection = i === 0;
      options.locals.indexInCollection = i;
      options.locals.lastInCollection = i === len - 1;
      options.object = val;
      buf += this.partial(view, options);
    }
    return buf;
  } else {
    if (options.object) {
      if ('string' == typeof name) {
        options.locals[name] = options.object;
      } else if (name === global) {
        utils.merge(options.locals, options.object);
      } else {
        options.scope = options.object;
      }
    }
    return this.render(view, options);
  }
};

/**
 * Render `view` with the given `options` and optional callback `fn`.
 * When a callback function is given a response will _not_ be made
 * automatically, however otherwise a response of _200_ and _text/html_ is given.
 *
 * Options:
 *
 *  Most engines accept one or more of the following options,
 *  both _haml_ and _jade_ accept all:
 *
 *  - `scope`     Template evaluation context (the value of `this`)
 *  - `locals`    Object containing local variables
 *  - `debug`     Output debugging information
 *  - `status`    Response status code, defaults to 200
 *  - `headers`   Response headers object
 *
 * @param  {String} view
 * @param  {Object|Function} options or callback function
 * @param  {Function} fn
 * @api public
 */

http.ServerResponse.prototype.render = function(view, options, fn){
  // Support callback function as second arg
  if (typeof options === 'function') {
    fn = options, options = {};
  }

  var options = options || {}
    , app = this.app
    , viewOptions = app.settings['view options']
    , defaultEngine = app.settings['view engine'];

  // Mixin "view options"
  if (viewOptions) options.__proto__ = viewOptions;

  // Support "view engine" setting
  if (view.indexOf('.') < 0 && defaultEngine) {
    view += '.' + defaultEngine;
  }

  // Defaults
  var self = this
    , helpers = this.app.viewHelpers
    , dynamicHelpers = this.app.dynamicViewHelpers
    , root = viewRoot(this.app)
    , ext = extname(view)
    , partial = options.partial
    , layout = options.layout === undefined ? true : options.layout
    , layout = layout === true
      ? 'layout' + ext
      : layout;

  // Allow layout name without extension
  if (typeof layout === 'string' && layout.indexOf('.') < 0) {
    layout += ext;
  }

  // Default execution scope to the response
  options.scope = options.scope || this.req;

  // Auto-cache in production
  if (this.app.set('env') === 'production') {
    options.cache = true;
  }

  // Partials support
  if (options.partial) {
    root = app.settings.partials || root + '/partials';
  }

  // View path
  var path = view[0] === '/'
    ? view
    : root + '/' + view;

  // Pass filename to the engine and view
  var locals = options.locals = options.locals || {};
  options.locals.__filename = options.filename = path;

  // Dynamic helper support
  if (false !== options.dynamicHelpers) {
    // cache
    if (!this.__dynamicHelpers) {
      this.__dynamicHelpers = {};
      var keys = Object.keys(dynamicHelpers);
      for (var i = 0, len = keys.length; i < len; ++i) {
        var key = keys[i]
          , val = dynamicHelpers[key];
        if (typeof val === 'function') {
          this.__dynamicHelpers[key] = val.call(
                this.app
              , this.req
              , this);
        }
      }
    }

    // apply
    helpers.__proto__ = this.__dynamicHelpers;
  }

  // Merge view helpers
  options.locals.__proto__ = helpers;

  // Always expose partial() as a local
  options.locals.partial = function(view, options){
    return self.partial.call(self, view, options, ext, locals);
  };

  function error(err) {
    if (fn) {
      fn(err);
    } else {
      self.req.next(err);
    }
  }

  // Cache contents
  try {
    var str = (options.cache ? viewCache[path] : null) || cacheViewSync(path);
  } catch (err) {
    return error(err);
  }

  // Cache template engine exports
  var engine = cache[ext] || (cache[ext] = require(ext.substr(1)));

  // Attempt render
  try {
    var str = engine.render(str, options);
  } catch (err) {
    return error(err);
  }

  // Layout support
  if (layout) {
    options.layout = false;
    options.locals.body = str;
    options.isLayout = true;
    self.render(layout, options, fn);
  } else if (partial) {
    return str;
  } else if (fn) {
    fn(null, str);
  } else {
    this.send(str, options.headers, options.status);
  }
};
<|endoftext|>


<commit_before>'use strict'

var utils = require('./utils.js');
var Q = require('q');
require('shelljs/global');

var ffmppegLogLevel = utils.defaults.loggerLevel === 'TRACE' ? 'verbose' : 'quiet';

/*
 * Crops video part about "to" from "start" time by real video url.
 * "to" is second.
 */
var cropVideoFromUrl = function(start, to, resolution, realUrl, videoId) {
  utils.logger.trace('ENTER - Crop video from url. Start:', start, ', To:', to, ', Resolution:', resolution, ', RealUrl:', realUrl, ', VideoId:', videoId);

  var deferred = Q.defer();

  // Check the passed resolution value and decide scale ratio. This scale rule reduce the resolution fifty percent.
  var scale = ' ';
  if (resolution) {
    var horizontalRes = Number(resolution.res.split('x')[0]);
    scale = (horizontalRes < 640 ? ' ' : ' -vf "scale=trunc(iw/4)*2:trunc(ih/4)*2" ');
  }

  var croppedFilename = utils.md5(start + '_' + to + '_' + videoId) + '_' + utils.defaults.cropFilePostfix + '.mp4';

  utils.logger.trace('Cropped filename:', croppedFilename, 'for the Start:', start, ', To:', to, ', VideoId:', videoId);

  var croppedFileWithPath = utils.defaults.videoBowl + '/' + croppedFilename;

  realUrl = realUrl.trim();
  var ffmpegCommand = '';
  if (realUrl.indexOf('m3u8') > -1) {
    ffmpegCommand = 'ffmpeg -loglevel ' + ffmppegLogLevel + ' -i "' + realUrl + '" -c copy -ss ' + start + ' -t ' + to + ' -y "' + croppedFileWithPath + '"';
  } else {
    ffmpegCommand = 'ffmpeg -loglevel ' + ffmppegLogLevel + ' -ss ' + start + ' -i "' + realUrl + '" -t ' + to + scale + ' -y "' + croppedFileWithPath + '"';
  }

  utils.logger.trace(ffmpegCommand);

  exec(ffmpegCommand, {
    silent: true
  }, function(code, stdout, stderr) {
    utils.logger.trace('ffmpeg video conversion from url command status:', code);

    if (Number(code) === 0) {
      utils.logger.trace('Checking video health.');

      var ffprobeCommand = 'ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "' + croppedFileWithPath + '"';

      exec(ffprobeCommand, {silent: true}, function(code, stdout, stderr) {
        if (Number(stdout) > 0) {
          utils.logger.trace('Video is healty.');

          deferred.resolve(croppedFilename);
        } else {
          // Remove the cropped file.
          utils.logger.trace('Removing the cropped file:', croppedFileWithPath);
          exec('rm -rf ' + croppedFileWithPath);

          deferred.reject('Video is corrupted!');
        }
      });
    } else {
      deferred.reject('Video cannot be created properly!');
    }

    utils.logger.trace('EXIT - Crop video from url. Start:', start, ', To:', to, ', Resolution:', resolution, ', RealUrl:', realUrl, ', VideoId:', videoId);
  });

  return deferred.promise;
};
module.exports.cropVideoFromUrl = cropVideoFromUrl;

/*
 * Embed the logo to the upper left of the specified video and returns again.
 */
var embedLogoAsWatermark = function(croppedFilename, extractor) {
  utils.logger.trace('ENTER - Embed logo image to video. CroppedFilename:', croppedFilename, ', Extractor:', extractor);

  var deferred = Q.defer();

  var videoNameWithoutPostfix = croppedFilename.split('_')[0];
  var finalFilename = extractor + '_' + videoNameWithoutPostfix + '_' + utils.defaults.finalFilePostfix + '.mp4';

  utils.logger.trace('Final filename:', finalFilename);

  var croppedFileWithPath = utils.defaults.videoBowl + '/' + croppedFilename;
  var finalcroppedFileWithPath = utils.defaults.videoBowl + '/' + finalFilename;
  var imageFileWithPath = utils.defaults.imageDir + '/' + utils.defaults.watermarkImage;

  // Locate the image upper left of the video.
  //var filterComplex = '"overlay=5:5:format=rgb,format=yuv420p"';
  var filterComplex = '"[0:v][1:v] overlay=5:5:enable=\'between(t,0,20)\':format=rgb,format=yuv420p"';

  var ffmpegCommand = 'ffmpeg -loglevel ' + ffmppegLogLevel + ' -i "' + croppedFileWithPath + '" -i "' + imageFileWithPath + '" -filter_complex ' + filterComplex + ' -c:v libx264 -preset veryfast -crf 30 -y "' + finalcroppedFileWithPath + '"';

  utils.logger.trace(ffmpegCommand);

  exec(ffmpegCommand, {
    silent: true
  }, function(code, stdout, stderr) {
    utils.logger.trace('ffmpeg video image embed command status:', code);

    // Remove the cropped file.
    utils.logger.trace('Removing the cropped file:', croppedFileWithPath);
    exec('rm -rf ' + croppedFileWithPath);

    if (Number(code) === 0) {
      deferred.resolve(finalFilename);
    } else {
      deferred.reject('The logo cannot be embedded to the video properly!');
    }

    utils.logger.trace('EXIT - Embed logo image to video. VideoFile:', croppedFilename);
  });

  return deferred.promise;
};
module.exports.embedLogoAsWatermark = embedLogoAsWatermark;

<commit_msg>Audio support added to the gifs. The maximum duration range increased. Quality of the images improved. Other little bug fixes.<commit_after>'use strict'

var utils = require('./utils.js');
var Q = require('q');
require('shelljs/global');

var ffmppegLogLevel = utils.defaults.loggerLevel === 'TRACE' ? 'verbose' : 'quiet';

/*
 * Crops video part about "to" from "start" time by real video url.
 * "to" is second.
 */
var cropVideoFromUrl = function(start, to, resolution, realUrl, videoId) {
  utils.logger.trace('ENTER - Crop video from url. Start:', start, ', To:', to, ', Resolution:', resolution, ', RealUrl:', realUrl, ', VideoId:', videoId);

  var deferred = Q.defer();

  // Check the passed resolution value and decide scale ratio. This scale rule reduce the resolution fifty percent.
  var scale = ' ';
  if (resolution) {
    var horizontalRes = Number(resolution.res.split('x')[0]);
    scale = (horizontalRes < 640 ? ' ' : ' -vf "scale=trunc(iw/4)*2:trunc(ih/4)*2" ');
  }

  var croppedFilename = utils.md5(start + '_' + to + '_' + videoId) + '_' + utils.defaults.cropFilePostfix + '.mp4';

  utils.logger.trace('Cropped filename:', croppedFilename, 'for the Start:', start, ', To:', to, ', VideoId:', videoId);

  var croppedFileWithPath = utils.defaults.videoBowl + '/' + croppedFilename;

  realUrl = realUrl.trim();
  var ffmpegCommand = '';
  if (realUrl.indexOf('m3u8') > -1) {
    ffmpegCommand = 'ffmpeg -loglevel ' + ffmppegLogLevel + ' -i "' + realUrl + '" -c copy -ss ' + start + ' -t ' + to + ' -y "' + croppedFileWithPath + '"';
  } else {
    ffmpegCommand = 'ffmpeg -loglevel ' + ffmppegLogLevel + ' -ss ' + start + ' -i "' + realUrl + '" -t ' + to + scale + ' -y "' + croppedFileWithPath + '"';
  }

  utils.logger.trace(ffmpegCommand);

  exec(ffmpegCommand, {
    silent: true
  }, function(code, stdout, stderr) {
    utils.logger.trace('ffmpeg video conversion from url command status:', code);

    if (Number(code) === 0) {
      utils.logger.trace('Checking video health.');

      var ffprobeCommand = 'ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "' + croppedFileWithPath + '"';

      exec(ffprobeCommand, {silent: true}, function(code, stdout, stderr) {
        if (Number(stdout) > 0) {
          utils.logger.trace('Video is healty.');

          deferred.resolve(croppedFilename);
        } else {
          // Remove the cropped file.
          utils.logger.trace('Removing the cropped file:', croppedFileWithPath);
          exec('rm -rf ' + croppedFileWithPath);

          deferred.reject('Video is corrupted!');
        }
      });
    } else {
      deferred.reject('Video cannot be created properly!');
    }

    utils.logger.trace('EXIT - Crop video from url. Start:', start, ', To:', to, ', Resolution:', resolution, ', RealUrl:', realUrl, ', VideoId:', videoId);
  });

  return deferred.promise;
};
module.exports.cropVideoFromUrl = cropVideoFromUrl;

/*
 * Embed the logo to the upper left of the specified video and returns again.
 */
var embedLogoAsWatermark = function(croppedFilename, extractor) {
  utils.logger.trace('ENTER - Embed logo image to video. CroppedFilename:', croppedFilename, ', Extractor:', extractor);

  var deferred = Q.defer();

  var videoNameWithoutPostfix = croppedFilename.split('_')[0];
  var finalFilename = extractor + '_' + videoNameWithoutPostfix + '_' + utils.defaults.finalFilePostfix + '.mp4';

  utils.logger.trace('Final filename:', finalFilename);

  var croppedFileWithPath = utils.defaults.videoBowl + '/' + croppedFilename;
  var finalcroppedFileWithPath = utils.defaults.videoBowl + '/' + finalFilename;
  var imageFileWithPath = utils.defaults.imageDir + '/' + utils.defaults.watermarkImage;

  // Locate the image upper left of the video.
  //var filterComplex = '"overlay=5:5:format=rgb,format=yuv420p"';
  var filterComplex = '"[0:v][1:v] overlay=5:5:enable=\'between(t,0,20)\':format=rgb,format=yuv420p"';

  var ffmpegCommand = 'ffmpeg -loglevel ' + ffmppegLogLevel + ' -i "' + croppedFileWithPath + '" -i "' + imageFileWithPath + '" -filter_complex ' + filterComplex + ' -c:v libx264 -preset veryfast -crf 30 -y "' + finalcroppedFileWithPath + '"';

  utils.logger.trace(ffmpegCommand);

  exec(ffmpegCommand, {
    silent: true
  }, function(code, stdout, stderr) {
    utils.logger.trace('ffmpeg video image embed command status:', code);

    // Remove the cropped file.
    utils.logger.trace('Removing the cropped file:', croppedFileWithPath);
    exec('rm -rf ' + croppedFileWithPath);

    if (Number(code) === 0) {
      deferred.resolve(finalFilename);
    } else {
      deferred.reject('The logo cannot be embedded to the video properly!');
    }

    utils.logger.trace('EXIT - Embed logo image to video. VideoFile:', croppedFilename);
  });

  return deferred.promise;
};
module.exports.embedLogoAsWatermark = embedLogoAsWatermark;
<|endoftext|>


<commit_before>/**
 * @fileoverview Forbid DOM lookup in template creation callback
 * @author Dominik Ferber
 * @copyright 2016 Dominik Ferber. All rights reserved.
 * See LICENSE file in root directory for full license.
 */

import getPropertyName from '../util/ast/getPropertyName'

const errorMessage = 'Accessing DOM from "onCreated" is forbidden. Try from "onRendered" instead.'
const jQueryNames = new Set(['$', 'jQuery'])

const isJQueryCallee = node => (
  ( // $()
   node.type === 'Identifier' && jQueryNames.has(node.name)
  ) || ( // Template.instance().$()
    node.type === 'MemberExpression' &&
    node.property.type === 'Identifier' &&
    node.property.name === '$' &&
    node.object.type === 'CallExpression' &&
    node.object.callee.type === 'MemberExpression' &&
    node.object.callee.object.type === 'Identifier' &&
    node.object.callee.object.name === 'Template' &&
    getPropertyName(node.object.callee.property) === 'instance'
  )
)

const isRelevantTemplateCallExpression = node => (
  node.type === 'CallExpression' &&
  node.callee.type === 'MemberExpression' &&
  node.callee.object.type === 'MemberExpression' &&
  node.callee.object.object.type === 'Identifier' &&
  node.callee.object.object.name === 'Template' &&
  getPropertyName(node.callee.property) === 'onCreated'
)

const isInRelevantTemplateScope = ancestors => ancestors.some(isRelevantTemplateCallExpression)

export default context => ({
  CallExpression: (node) => {
    if (!isJQueryCallee(node.callee)) return
    if (!isInRelevantTemplateScope(context.getAncestors())) return

    context.report(node, errorMessage)
  },
})

export const schema = []
<commit_msg>style(no-dom-lookup-on-created): fix indentation<commit_after>/**
 * @fileoverview Forbid DOM lookup in template creation callback
 * @author Dominik Ferber
 * @copyright 2016 Dominik Ferber. All rights reserved.
 * See LICENSE file in root directory for full license.
 */

import getPropertyName from '../util/ast/getPropertyName'

const errorMessage = 'Accessing DOM from "onCreated" is forbidden. Try from "onRendered" instead.'
const jQueryNames = new Set(['$', 'jQuery'])

const isJQueryCallee = node => (
  ( // $()
    node.type === 'Identifier' && jQueryNames.has(node.name)
  ) || ( // Template.instance().$()
    node.type === 'MemberExpression' &&
    node.property.type === 'Identifier' &&
    node.property.name === '$' &&
    node.object.type === 'CallExpression' &&
    node.object.callee.type === 'MemberExpression' &&
    node.object.callee.object.type === 'Identifier' &&
    node.object.callee.object.name === 'Template' &&
    getPropertyName(node.object.callee.property) === 'instance'
  )
)

const isRelevantTemplateCallExpression = node => (
  node.type === 'CallExpression' &&
  node.callee.type === 'MemberExpression' &&
  node.callee.object.type === 'MemberExpression' &&
  node.callee.object.object.type === 'Identifier' &&
  node.callee.object.object.name === 'Template' &&
  getPropertyName(node.callee.property) === 'onCreated'
)

const isInRelevantTemplateScope = ancestors => ancestors.some(isRelevantTemplateCallExpression)

export default context => ({
  CallExpression: (node) => {
    if (!isJQueryCallee(node.callee)) return
    if (!isInRelevantTemplateScope(context.getAncestors())) return

    context.report(node, errorMessage)
  },
})

export const schema = []
<|endoftext|>


<commit_before>const Rule = require('../rule');

module.exports = class extends Rule {
  selector() {
    return 'a[role=button],a[href="#"],a[href="#!"],a[href^="javascript:"]';
  }

  test(el, utils) {
    if (utils.hidden(el)) {
      return null;
    }
    return 'use a button instead of a link';
  }
};
<commit_msg>link as button fix attribute<commit_after>const Rule = require('../rule');

module.exports = class extends Rule {
  selector() {
    return 'a[role~=button],a[href="#"],a[href="#!"],a[href^="javascript:"]';
  }

  test(el, utils) {
    if (utils.hidden(el)) {
      return null;
    }
    return 'use a button instead of a link';
  }
};
<|endoftext|>


<commit_before>// @flow

import fetch from 'isomorphic-fetch'
import {decode as decodePolyline} from 'polyline'
import {isEqual as coordinatesAreEqual} from '@conveyal/lonlat'
import qs from 'qs'
import lineString from 'turf-linestring'
import L from 'leaflet'

// This can be used for logging line strings to geojson.io URLs for easy
// debugging.
// import {logCoordsToGeojsonio} from '../../editor/util/debug'

import { coordIsOutOfBounds } from '../../editor/util/map'
import type {
  Coordinates,
  LatLng
} from '../../types'

type Instruction = {
  distance: number,
  heading: number,
  interval: [number, number],
  sign: number,
  street_name: string,
  text: string,
  time: number
}

type Path = {
  ascend: number,
  bbox: [number, number, number, number],
  descend: number,
  details: {},
  distance: number,
  instructions: Array<Instruction>,
  legs: [],
  points: string,
  points_encoded: boolean,
  snapped_waypoints: string,
  time: number,
  transfers: number,
  weight: number
}

type GraphHopperResponse = {
  hints: {
    'visited_nodes.average': string,
    'visited_nodes.sum': string
  },
  info: {
    copyrights: Array<string>,
    took: number
  },
  paths: Array<Path>
}

type GraphHopperAlternativeServer = {
  BBOX: Array<number>,
  KEY?: string,
  URL?: string
}

/**
 * Convert GraphHopper routing JSON response to polyline.
 */
function handleGraphHopperRouting (path: Path, individualLegs: boolean = false): any {
  const {instructions, points} = path
  // Decode polyline and reverse coordinates.
  const decodedPolyline = decodePolyline(points).map(c => ([c[1], c[0]]))
  if (individualLegs) {
    const segments = []
    // Keep track of the segment point intervals to split the line segment at.
    // This appears to be the most reliable way to split up the geometry
    // (previously distance was used here, but that provided inconstent results).
    const segmentPointIndices = [0]
    // Iterate over the instructions, accumulating segment point indices at each
    // waypoint encountered. Indices are used to slice the line geometry when
    // individual legs are needed. NOTE: Waypoint === routing point provided in
    // the request.
    instructions.forEach((instruction, i) => {
      if (instruction.text.match(/Waypoint (\d+)/) || i === instructions.length - 1) {
        segmentPointIndices.push(instruction.interval[0])
      }
    })
    // Once all of the indices have been found, slice the decoded polyline up
    // at the provided indices.
    if (segmentPointIndices.length > 2) {
      for (var i = 1; i < segmentPointIndices.length; i++) {
        // Get the indices of the points that the entire path should be sliced at
        // Note: 'to' index is incremented by one because it is not inclusive.
        const [from, to] = [segmentPointIndices[i - 1], segmentPointIndices[i] + 1]
        const segment = decodedPolyline.slice(from, to)
        segments.push(segment)
      }
      // console.log('individual legs', segments)
      return segments
    } else {
      // FIXME does this work for two input points?
      return [decodedPolyline]
    }
  } else {
    return decodedPolyline
  }
}

/**
 * Route between two or more points using external routing service.
 * @param  {[type]} points         array of two or more LatLng points
 * @param  {[type]} individualLegs whether to return coordinates as set of
 *                                 distinct segments for each pair of points
 * @return {[type]}                Array of coordinates or Array of arrays of coordinates.
 */
export async function polyline (
  points: Array<LatLng>,
  individualLegs?: boolean = false,
  avoidMotorways?: boolean = false
): Promise<any> {
  let json
  const geometry = []
  try {
    // Chunk points into sets no larger than the max # of points allowed by
    // GraphHopper plan.
    const pointLimit = +process.env.GRAPH_HOPPER_POINT_LIMIT
    // Default to chunks of 30 points if the point limit is less than 2. (There
    // must be at least two points passed in to routing method in order to
    // successfully route.)
    const chunk = pointLimit > 2 ? pointLimit : 30
    let count = 0
    const j = points.length
    for (let i = 0; i < j; i += chunk) {
      // Offset the slice indices so that the next chunk begins with the
      const offset = count * -1
      const beginIndex = i + offset
      const endIndex = i + chunk + offset
      const chunkedPoints = points.slice(beginIndex, endIndex)
      json = await routeWithGraphHopper(chunkedPoints, avoidMotorways)
      const path = json && json.paths && json.paths[0]
      // Route between chunked list of points
      if (path) {
        const result = handleGraphHopperRouting(path, individualLegs)
        geometry.push(...result)
      } else {
        // If any of the routed legs fails, default to straight line (return null).
        console.warn(`Error routing from point ${beginIndex} to ${endIndex}`, chunkedPoints)
        return null
      }
      count++
    }
    return geometry
  } catch (e) {
    console.log(e)
    return null
  }
}

export async function getSegment (
  points: Coordinates,
  followRoad: boolean,
  defaultToStraightLine: boolean = true,
  avoidMotorways: boolean = false
): Promise<?{
  coordinates: Coordinates,
  type: 'LineString'
}> {
  // Store geometry to be returned here.
  let geometry
  if (followRoad) {
    // if snapping to streets, use routing service.
    const coordinates = await polyline(
      points.map(p => ({lng: p[0], lat: p[1]})),
      false,
      avoidMotorways
    )
    if (!coordinates) {
      // If routing was unsuccessful, default to straight line (if desired by
      // caller).
      console.warn(`Routing unsuccessful. Returning ${defaultToStraightLine ? 'straight line' : 'null'}.`)
      if (defaultToStraightLine) {
        geometry = lineString(points).geometry
      } else {
        return null
      }
    } else {
      // If routing is successful, clean up shape if necessary
      const c0 = coordinates[0]
      const epsilon = 1e-6
      if (!coordinatesAreEqual(c0, points[0], epsilon)) {
        coordinates.unshift(points[0])
      }
      geometry = {
        type: 'LineString',
        coordinates
      }
    }
  } else {
    // If not snapping to streets, simply generate a line string from input
    // coordinates.
    geometry = lineString(points).geometry
  }
  return geometry
}

/**
 * Call GraphHopper routing service with lat/lng coordinates.
 *
 * Example URL: https://graphhopper.com/api/1/route?point=49.932707,11.588051&point=50.3404,11.64705&vehicle=car&debug=true&&type=json
 */
export function routeWithGraphHopper (points: Array<LatLng>, avoidMotorways?: boolean): ?Promise<GraphHopperResponse> {
  if (points.length < 2) {
    console.warn('need at least two points to route with graphhopper', points)
    return null
  }
  if (!process.env.GRAPH_HOPPER_KEY) {
    throw new Error('GRAPH_HOPPER_KEY not set')
  }

  // Use custom url if it exists, otherwise default to the hosted service.
  let graphHopperUrl = process.env.GRAPH_HOPPER_URL || 'https://graphhopper.com/api/1/'
  let graphHopperKey = process.env.GRAPH_HOPPER_KEY

  if (process.env.GRAPH_HOPPER_ALTERNATES) {
    // $FlowFixMe This is a bit of a hack and now how env variables are supposed to work, but the yaml loader supports it.
    const alternates: Array<GraphHopperAlternativeServer> = process.env.GRAPH_HOPPER_ALTERNATES
    alternates.forEach(alternative => {
      const {BBOX} = alternative
      if (BBOX.length !== 4) {
        console.warn('Invalid BBOX for GRAPH_HOPPER_ALTERNATIVE')
        return
      }
      if (!alternative.URL && !alternative.KEY) {
        console.warn('No URL or key provided for alternative graphhopper server.')
        return
      }

      if (
        points.every(
          (point) =>
            !coordIsOutOfBounds(
              point,
              L.latLngBounds(
                [alternative.BBOX[1], alternative.BBOX[0]],
                [alternative.BBOX[3], alternative.BBOX[2]]
              )
            )
        )
      ) {
        graphHopperUrl = alternative.URL
        graphHopperKey = alternative.KEY ? alternative.KEY : null
      }
    })
  }

  const params = {
    key: graphHopperKey,
    vehicle: 'car',
    debug: true,
    type: 'json'
  }
  const locations = points.map(p => (`point=${p.lat},${p.lng}`)).join('&')
  // Avoiding motorways requires a POST request with a formatted body
  const graphHopperRequest = avoidMotorways
    ? fetch(`${graphHopperUrl}route?key=${params.key}`,
      {
        body: JSON.stringify({
          'ch.disable': true,
          // Custom model disincentives motorways
          custom_model: {
            'priority': [{
              'if': 'road_class == MOTORWAY',
              'multiply_by': 0.1
            }]
          },
          debug: params.debug,
          points: points.map(p => [p.lng, p.lat]),
          profile: params.vehicle
        }),
        headers: {
          'Content-Type': 'application/json'
        },
        method: 'POST'
      })
    : fetch(`${graphHopperUrl}route?${locations}&${qs.stringify(params)}`)

  return graphHopperRequest.then(res => res.json())
}
<commit_msg>refactor(valhalla): remove confusing ternary operator<commit_after>// @flow

import fetch from 'isomorphic-fetch'
import {decode as decodePolyline} from 'polyline'
import {isEqual as coordinatesAreEqual} from '@conveyal/lonlat'
import qs from 'qs'
import lineString from 'turf-linestring'
import L from 'leaflet'

// This can be used for logging line strings to geojson.io URLs for easy
// debugging.
// import {logCoordsToGeojsonio} from '../../editor/util/debug'

import { coordIsOutOfBounds } from '../../editor/util/map'
import type {
  Coordinates,
  LatLng
} from '../../types'

type Instruction = {
  distance: number,
  heading: number,
  interval: [number, number],
  sign: number,
  street_name: string,
  text: string,
  time: number
}

type Path = {
  ascend: number,
  bbox: [number, number, number, number],
  descend: number,
  details: {},
  distance: number,
  instructions: Array<Instruction>,
  legs: [],
  points: string,
  points_encoded: boolean,
  snapped_waypoints: string,
  time: number,
  transfers: number,
  weight: number
}

type GraphHopperResponse = {
  hints: {
    'visited_nodes.average': string,
    'visited_nodes.sum': string
  },
  info: {
    copyrights: Array<string>,
    took: number
  },
  paths: Array<Path>
}

type GraphHopperAlternativeServer = {
  BBOX: Array<number>,
  KEY?: string,
  URL?: string
}

/**
 * Convert GraphHopper routing JSON response to polyline.
 */
function handleGraphHopperRouting (path: Path, individualLegs: boolean = false): any {
  const {instructions, points} = path
  // Decode polyline and reverse coordinates.
  const decodedPolyline = decodePolyline(points).map(c => ([c[1], c[0]]))
  if (individualLegs) {
    const segments = []
    // Keep track of the segment point intervals to split the line segment at.
    // This appears to be the most reliable way to split up the geometry
    // (previously distance was used here, but that provided inconstent results).
    const segmentPointIndices = [0]
    // Iterate over the instructions, accumulating segment point indices at each
    // waypoint encountered. Indices are used to slice the line geometry when
    // individual legs are needed. NOTE: Waypoint === routing point provided in
    // the request.
    instructions.forEach((instruction, i) => {
      if (instruction.text.match(/Waypoint (\d+)/) || i === instructions.length - 1) {
        segmentPointIndices.push(instruction.interval[0])
      }
    })
    // Once all of the indices have been found, slice the decoded polyline up
    // at the provided indices.
    if (segmentPointIndices.length > 2) {
      for (var i = 1; i < segmentPointIndices.length; i++) {
        // Get the indices of the points that the entire path should be sliced at
        // Note: 'to' index is incremented by one because it is not inclusive.
        const [from, to] = [segmentPointIndices[i - 1], segmentPointIndices[i] + 1]
        const segment = decodedPolyline.slice(from, to)
        segments.push(segment)
      }
      // console.log('individual legs', segments)
      return segments
    } else {
      // FIXME does this work for two input points?
      return [decodedPolyline]
    }
  } else {
    return decodedPolyline
  }
}

/**
 * Route between two or more points using external routing service.
 * @param  {[type]} points         array of two or more LatLng points
 * @param  {[type]} individualLegs whether to return coordinates as set of
 *                                 distinct segments for each pair of points
 * @return {[type]}                Array of coordinates or Array of arrays of coordinates.
 */
export async function polyline (
  points: Array<LatLng>,
  individualLegs?: boolean = false,
  avoidMotorways?: boolean = false
): Promise<any> {
  let json
  const geometry = []
  try {
    // Chunk points into sets no larger than the max # of points allowed by
    // GraphHopper plan.
    const pointLimit = +process.env.GRAPH_HOPPER_POINT_LIMIT
    // Default to chunks of 30 points if the point limit is less than 2. (There
    // must be at least two points passed in to routing method in order to
    // successfully route.)
    const chunk = pointLimit > 2 ? pointLimit : 30
    let count = 0
    const j = points.length
    for (let i = 0; i < j; i += chunk) {
      // Offset the slice indices so that the next chunk begins with the
      const offset = count * -1
      const beginIndex = i + offset
      const endIndex = i + chunk + offset
      const chunkedPoints = points.slice(beginIndex, endIndex)
      json = await routeWithGraphHopper(chunkedPoints, avoidMotorways)
      const path = json && json.paths && json.paths[0]
      // Route between chunked list of points
      if (path) {
        const result = handleGraphHopperRouting(path, individualLegs)
        geometry.push(...result)
      } else {
        // If any of the routed legs fails, default to straight line (return null).
        console.warn(`Error routing from point ${beginIndex} to ${endIndex}`, chunkedPoints)
        return null
      }
      count++
    }
    return geometry
  } catch (e) {
    console.log(e)
    return null
  }
}

export async function getSegment (
  points: Coordinates,
  followRoad: boolean,
  defaultToStraightLine: boolean = true,
  avoidMotorways: boolean = false
): Promise<?{
  coordinates: Coordinates,
  type: 'LineString'
}> {
  // Store geometry to be returned here.
  let geometry
  if (followRoad) {
    // if snapping to streets, use routing service.
    const coordinates = await polyline(
      points.map(p => ({lng: p[0], lat: p[1]})),
      false,
      avoidMotorways
    )
    if (!coordinates) {
      // If routing was unsuccessful, default to straight line (if desired by
      // caller).
      console.warn(`Routing unsuccessful. Returning ${defaultToStraightLine ? 'straight line' : 'null'}.`)
      if (defaultToStraightLine) {
        geometry = lineString(points).geometry
      } else {
        return null
      }
    } else {
      // If routing is successful, clean up shape if necessary
      const c0 = coordinates[0]
      const epsilon = 1e-6
      if (!coordinatesAreEqual(c0, points[0], epsilon)) {
        coordinates.unshift(points[0])
      }
      geometry = {
        type: 'LineString',
        coordinates
      }
    }
  } else {
    // If not snapping to streets, simply generate a line string from input
    // coordinates.
    geometry = lineString(points).geometry
  }
  return geometry
}

/**
 * Call GraphHopper routing service with lat/lng coordinates.
 *
 * Example URL: https://graphhopper.com/api/1/route?point=49.932707,11.588051&point=50.3404,11.64705&vehicle=car&debug=true&&type=json
 */
export function routeWithGraphHopper (points: Array<LatLng>, avoidMotorways?: boolean): ?Promise<GraphHopperResponse> {
  if (points.length < 2) {
    console.warn('need at least two points to route with graphhopper', points)
    return null
  }
  if (!process.env.GRAPH_HOPPER_KEY) {
    throw new Error('GRAPH_HOPPER_KEY not set')
  }

  // Use custom url if it exists, otherwise default to the hosted service.
  let graphHopperUrl = process.env.GRAPH_HOPPER_URL || 'https://graphhopper.com/api/1/'
  let graphHopperKey = process.env.GRAPH_HOPPER_KEY

  if (process.env.GRAPH_HOPPER_ALTERNATES) {
    // $FlowFixMe This is a bit of a hack and now how env variables are supposed to work, but the yaml loader supports it.
    const alternates: Array<GraphHopperAlternativeServer> = process.env.GRAPH_HOPPER_ALTERNATES
    alternates.forEach(alternative => {
      const {BBOX} = alternative
      if (BBOX.length !== 4) {
        console.warn('Invalid BBOX for GRAPH_HOPPER_ALTERNATIVE')
        return
      }
      if (!alternative.URL && !alternative.KEY) {
        console.warn('No URL or key provided for alternative graphhopper server.')
        return
      }

      if (
        points.every(
          (point) =>
            !coordIsOutOfBounds(
              point,
              L.latLngBounds(
                [alternative.BBOX[1], alternative.BBOX[0]],
                [alternative.BBOX[3], alternative.BBOX[2]]
              )
            )
        )
      ) {
        if (alternative.URL) {
          graphHopperUrl = alternative.URL
        }
        if (alternative.KEY) {
          graphHopperKey = alternative.KEY
        }
      }
    })
  }

  const params = {
    key: graphHopperKey,
    vehicle: 'car',
    debug: true,
    type: 'json'
  }
  const locations = points.map(p => (`point=${p.lat},${p.lng}`)).join('&')
  // Avoiding motorways requires a POST request with a formatted body
  const graphHopperRequest = avoidMotorways
    ? fetch(`${graphHopperUrl}route?key=${params.key}`,
      {
        body: JSON.stringify({
          'ch.disable': true,
          // Custom model disincentives motorways
          custom_model: {
            'priority': [{
              'if': 'road_class == MOTORWAY',
              'multiply_by': 0.1
            }]
          },
          debug: params.debug,
          points: points.map(p => [p.lng, p.lat]),
          profile: params.vehicle
        }),
        headers: {
          'Content-Type': 'application/json'
        },
        method: 'POST'
      })
    : fetch(`${graphHopperUrl}route?${locations}&${qs.stringify(params)}`)

  return graphHopperRequest.then(res => res.json())
}
<|endoftext|>