<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<string, Array<BufferMarker>>
  messages: Map<string, LinterMessage>
  textEditor: TextEditor
  showTooltip: boolean
  subscriptions: CompositeDisposable
  cursorPosition: ?Point
  gutterPosition: boolean
  tooltipFollows: string
  showDecorations: boolean
  showProviderName: boolean
  ignoreTooltipInvocation: boolean
  currentLineMarker: ?BufferMarker
  lastRange: ?Range
  lastEmpty: ?Range

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

    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 => {
        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 (this.lastRange && this.lastRange.isEqual(linesRange) && currentEmpty === this.lastEmpty) return
          if (this.currentLineMarker) {
            this.currentLineMarker.destroy()
            this.currentLineMarker = null
          }
          this.lastRange = linesRange
          this.lastEmpty = currentEmpty

          this.currentLineMarker = 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(this.currentLineMarker, {
            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 (this.currentLineMarker) {
              this.currentLineMarker.destroy()
              this.currentLineMarker = null
            }
          }),
        )
        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((markers: Array<BufferMarker>, key: string) => {
      const message = this.messages.get(key)
      if (message) {
        for (const marker of markers) {
          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)

    // save markers of the tooltip (for destorying them in this.apply)
    messages.forEach(message => {
      // $FlowIgnore: this.tooltip is not null
      this.saveMarker(message.key, this.tooltip.marker)
    })

    // $FlowIgnore: this.tooltip is not null
    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]
      this.destroyMarker(message.key)
    }

    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.decorateMarker(message, marker)
      marker.onDidChange(({ oldHeadPosition, newHeadPosition, isValid }) => {
        if (!isValid || (newHeadPosition.row === 0 && oldHeadPosition.row !== 0)) {
          return
        }
        if (message.version === 2) {
          message.location.position = marker.previousEventState.range
        }
      })
    }

    this.updateTooltip(this.cursorPosition)
  }
  decorateMarker(message: LinterMessage, marker: BufferMarker, paint: 'gutter' | 'editor' | 'both' = 'both') {
    this.saveMarker(message.key, marker)
    this.messages.set(message.key, message)

    if (paint === 'both' || paint === 'editor') {
      this.textEditor.decorateMarker(marker, {
        type: 'text',
        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-gutter-${message.severity} icon icon-${message.icon || 'primitive-dot'}`
      gutter.decorateMarker(marker, {
        class: 'linter-row',
        item: element,
      })
    }
  }

  // add marker to the message => marker map
  saveMarker(key: string, marker: BufferMarker) {
    const allMarkers = this.markers.get(key) || []
    allMarkers.push(marker)
    this.markers.set(key, allMarkers)
  }

  // destroy markers of a key
  destroyMarker(key: string) {
    const markers = this.markers.get(key)
    if (markers) {
      markers.forEach(marker => {
        if (marker) {
          marker.destroy()
        }
      })
    }
    this.markers.delete(key)
    this.messages.delete(key)
  }

  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>save lastCursorPositions as a class property<commit_after>/* @flow */

import debounce from 'sb-debounce'
import disposableEvent from 'disposable-event'
import { CompositeDisposable, Disposable, Emitter, Range } from 'atom'
// $FlowIgnore: Cursor is a type
import type { TextEditor, BufferMarker, TextEditorGutter, Point, Cursor } 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<string, Array<BufferMarker>>
  messages: Map<string, LinterMessage>
  textEditor: TextEditor
  showTooltip: boolean
  subscriptions: CompositeDisposable
  cursorPosition: ?Point
  gutterPosition: boolean
  tooltipFollows: string
  showDecorations: boolean
  showProviderName: boolean
  ignoreTooltipInvocation: boolean
  currentLineMarker: ?BufferMarker
  lastRange: ?Range
  lastEmpty: ?Range
  lastCursorPositions: WeakMap<Cursor, Point>

  constructor(textEditor: TextEditor) {
    this.tooltip = null
    this.emitter = new Emitter()
    this.markers = new Map()
    this.messages = new Map()
    this.textEditor = textEditor
    this.subscriptions = new CompositeDisposable()
    this.ignoreTooltipInvocation = false
    this.currentLineMarker = null
    this.lastRange = null
    this.lastEmpty = null
    this.lastCursorPositions = new WeakMap()

    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(({ cursor, newBufferPosition }) => {
        const lastBufferPosition = this.lastCursorPositions.get(cursor)
        if (!lastBufferPosition || !lastBufferPosition.isEqual(newBufferPosition)) {
          this.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 => {
          this.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 => {
        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 (this.lastRange && this.lastRange.isEqual(linesRange) && currentEmpty === this.lastEmpty) return
          if (this.currentLineMarker) {
            this.currentLineMarker.destroy()
            this.currentLineMarker = null
          }
          this.lastRange = linesRange
          this.lastEmpty = currentEmpty

          this.currentLineMarker = 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(this.currentLineMarker, {
            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 (this.currentLineMarker) {
              this.currentLineMarker.destroy()
              this.currentLineMarker = null
            }
          }),
        )
        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((markers: Array<BufferMarker>, key: string) => {
      const message = this.messages.get(key)
      if (message) {
        for (const marker of markers) {
          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)

    // save markers of the tooltip (for destorying them in this.apply)
    messages.forEach(message => {
      // $FlowIgnore: this.tooltip is not null
      this.saveMarker(message.key, this.tooltip.marker)
    })

    // $FlowIgnore: this.tooltip is not null
    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]
      this.destroyMarker(message.key)
    }

    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.decorateMarker(message, marker)
      marker.onDidChange(({ oldHeadPosition, newHeadPosition, isValid }) => {
        if (!isValid || (newHeadPosition.row === 0 && oldHeadPosition.row !== 0)) {
          return
        }
        if (message.version === 2) {
          message.location.position = marker.previousEventState.range
        }
      })
    }

    this.updateTooltip(this.cursorPosition)
  }
  decorateMarker(message: LinterMessage, marker: BufferMarker, paint: 'gutter' | 'editor' | 'both' = 'both') {
    this.saveMarker(message.key, marker)
    this.messages.set(message.key, message)

    if (paint === 'both' || paint === 'editor') {
      this.textEditor.decorateMarker(marker, {
        type: 'text',
        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-gutter-${message.severity} icon icon-${message.icon || 'primitive-dot'}`
      gutter.decorateMarker(marker, {
        class: 'linter-row',
        item: element,
      })
    }
  }

  // add marker to the message => marker map
  saveMarker(key: string, marker: BufferMarker) {
    const allMarkers = this.markers.get(key) || []
    allMarkers.push(marker)
    this.markers.set(key, allMarkers)
  }

  // destroy markers of a key
  destroyMarker(key: string) {
    const markers = this.markers.get(key)
    if (markers) {
      markers.forEach(marker => {
        if (marker) {
          marker.destroy()
        }
      })
    }
    this.markers.delete(key)
    this.messages.delete(key)
  }

  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>/*
  Template Haskell communication

  reads messages from stdin, sends over stderr
  (Haskell stderr stream is redirected to stdout)

  messages are prefixed with the following data:
    - UInt32BE: message length
    - UInt32BE: message target:
                  0: control message from compiler to TH server
                  n: response to request n
*/

// set this to true to record each message and the received JS code to a file
// you can then replay the TH session later with 'node thrunner.js replay'
var h$THRecord = // true ||
      !!process.argv['GHCJS_RECORD_TH'];

var h$THReplay = process.argv.length > 0 && process.argv[process.argv.length-1] === 'replay';

var h$TH = { nWaiters: 0
           , waiters: {}
           , data: null
           , requestId: 1
           , loadedSymbol: null
           , sendRequest: h$sendRequest
           , awaitMessage: h$awaitMessage
           , sendMessage: h$sendMessage
           , loadCode: h$loadCode
           , loadCodeStr: h$loadCodeStr
           , bufSize: h$bufSize
           , getMemoryUsage: h$getMemoryUsage
           };

global.h$TH = h$TH;
global.require = require;
global.module = module;

var h$THfs = require('fs');

require('jsdom-global')();

// start listening
function h$initTH() {
    process.stdin.setEncoding('utf8');
    process.stderr.setEncoding('binary');
    process.on('uncaughtException', function(err) { console.log(err); });
    h$awaitMessageRaw(0, h$loadInitialCode);
    process.stdin.on('readable', function() {
        while(true) {
            var str = process.stdin.read();
            if(str) {
                var buf = new Buffer(str, 'hex');
                h$TH.data = h$TH.data ? Buffer.concat([h$TH.data, buf]) : buf;
                h$processQueue();
            } else {
                return;
            }
        }
    });
    process.stdin.on('close', function() { process.exit(0); });
}

function h$getMemoryUsage() {
  var m = process.memoryUsage();
  // return m.rss;
  return (m.heapTotal + m.external)|0;
}

var h$THMessageN = 0;
function h$processQueue() {
    while(h$TH.nWaiters > 0 && h$TH.data && h$TH.data.length >= 8) {
        var msgLength = h$TH.data.readUInt32BE(0);
        var msgTarget = h$TH.data.readUInt32BE(4);
        if(h$TH.data.length >= msgLength + 8 && h$TH.waiters[msgTarget]) {
            var w = h$TH.waiters[msgTarget], b = h$TH.data.slice(8, msgLength + 8);
            h$TH.data = h$TH.data.slice(msgLength + 8);
            delete h$TH.waiters[msgTarget];
            h$TH.nWaiters--;
            if(h$THRecord && !h$THReplay)
                h$THfs.writeFileSync("thmessage." + (++h$THMessageN) + ".dat", b);
            w(b);
        } else {
            return;
        }
    }
}

function h$sendRequest(bs, offset, len, c) {
    var req = h$TH.requestId++;
    h$sendMessage(bs, offset, len, req, function() {});
    h$awaitMessage(req, c);
}

function h$sendMessage(bs, offset, len, req, c) {
    var msg = len === -1 ? new Buffer(bs.u8.subarray(offset))
                         : new Buffer(bs.u8.subarray(offset, len));
    var hdr = new Buffer(8);
    hdr.writeUInt32BE(msg.length, 0);
    hdr.writeUInt32BE(req, 4);
    process.stderr.write(Buffer.concat([hdr, msg]), 'binary', function() { c(); });
}

function h$awaitMessage(req, c) {
    h$awaitMessageRaw(req, function(buf) {
        c(h$THWrapBuffer(h$BufferToArrayBuffer(buf),false),0);
    });
}

var h$THReplayMessageN = 0;
function h$awaitMessageRaw(req, c) {
    if(h$TH.waiters[req]) throw ("h$awaitMessage: already waiting for " + req);
    if(h$THReplay) {
        try {
            c(h$THfs.readFileSync('thmessage.' + (++h$THReplayMessageN) + '.dat'));
            return;
        } catch(e) { }
    }
    h$TH.nWaiters++;
    h$TH.waiters[req] = c;
    h$processQueue();
}

function h$bufSize(buf, buf_offset) {
    if(buf === null) return 0;
    return buf.len;
}

// load the RTS and start the server
function h$loadInitialCode(buf) {
    var code = buf.toString('utf8');
    h$loadCodeStr(code, true);
    // don't allow Haskell to read from stdin
    h$base_stdin_fd.read = function(fd, fdo, buf, buf_offset, n, c) { c(0); }
    // redirect Haskell's stderr to stdout since we use stderr to communicate
    h$base_stderr_fd.write = h$base_stdout_fd.write;
    h$main(h$ghcjszmthZCGHCJSziPrimziTHziEvalzirunTHServer);

}

var h$THCodeN = 0;
function h$loadCodeStr(str, isFirst) {
    if(h$THReplay) {
        try {
            str = h$THfs.readFileSync("thcode." + (++h$THCodeN) + ".js").toString('utf8');
        } catch(e) { }
    } else if(h$THRecord) {
        h$THfs.writeFileSync("thcode." + (++h$THCodeN) + ".js", str);
    }
    eval.call(null, str);
}

// load additional code and run the initializers for it, the code should
// assign the h$TH.loadedSymbol variable
function h$loadCode(buf, off, len) {
    h$TH.loadedSymbol = null;
    var str = new Buffer(buf.u8).toString('utf8',off, off+len);
    h$TH.loadCodeStr(str, false);
    // h$runInitStatic();
    if(h$TH.loadedSymbol === null) throw "h$loadCode: error loading code"
    return h$TH.loadedSymbol;
}

function h$BufferToArrayBuffer(buf) {
    if(buf.toArrayBuffer) return buf.toArrayBuffer();
    return new Uint8Array(buf).buffer;
}

// copied from src/mem.js
function h$THWrapBuffer(buf, unalignedOk, offset, length) {
    if(!unalignedOk && offset && offset % 8 !== 0) {
        throw ("h$THWrapBuffer: offset not aligned:" + offset);
    }
    if(!buf || !(buf instanceof ArrayBuffer))
        throw "h$THWrapBuffer: not an ArrayBuffer"
    if(!offset) { offset = 0; }
    if(!length || length < 0) { length = buf.byteLength - offset; }
    // console.log("wrapping buf: " + length + " " + offset);
    return { buf: buf
             , len: length
             , i3: (offset%4) ? null : new Int32Array(buf, offset, length >> 2)
             , u8: new Uint8Array(buf, offset, length)
             , u1: (offset%2) ? null : new Uint16Array(buf, offset, length >> 1)
             , f3: (offset%4) ? null : new Float32Array(buf, offset, length >> 2)
             , f6: (offset%8) ? null : new Float64Array(buf, offset, length >> 3)
             , dv: new DataView(buf, offset, length)
           };
}

h$initTH();
<commit_msg>fix quadratic slowdown in Template Haskell runner buffer handling<commit_after>/*
  Template Haskell communication

  reads messages from stdin, sends over stderr
  (Haskell stderr stream is redirected to stdout)

  messages are prefixed with the following data:
    - UInt32BE: message length
    - UInt32BE: message target:
                  0: control message from compiler to TH server
                  n: response to request n
*/

var h$THfs = require('fs');

require('jsdom-global')();

// set this to true to record each message and the received JS code to a file
// you can then replay the TH session later with 'node thrunner.js replay'
var h$THRecord = // true ||
      !!process.env['GHCJS_RECORD_TH'];

var h$THReplay = process.argv.length > 0 && process.argv[process.argv.length-1] === 'replay';

var h$TH = { nWaiters: 0
           , waiters: {}
           , data: []
           , dataLen: 0
           , requestId: 1
           , loadedSymbol: null
           , sendRequest: h$sendRequest
           , awaitMessage: h$awaitMessage
           , sendMessage: h$sendMessage
           , loadCode: h$loadCode
           , loadCodeStr: h$loadCodeStr
           , bufSize: h$bufSize
           , getMemoryUsage: h$getMemoryUsage
           };

global.h$TH = h$TH;
global.require = require;
global.module = module;

// start listening
function h$initTH() {
    process.stdin.setEncoding('utf8');
    process.stderr.setEncoding('binary');
    process.on('uncaughtException', function(err) { console.log(err); });
    h$awaitMessageRaw(0, h$loadInitialCode);
    var leftover = null;
    process.stdin.on('readable', function() {
        while(true) {
            var str = process.stdin.read();
            if(str) {
                // save incomplete hex pair if needed
                str = str.toString();
                if(leftover) str = leftover + str;
                str = str.replace(/\s/gm, '');
                if(str.length % 2) {
                  leftover = str.slice(str.length-1);
                  str = str.slice(0,str.length-1);
                } else {
                  leftover = null;
                }
                var buf = new Buffer(str, 'hex');

                // make sure the first 8 bytes into data[0]
                // otherwise delay copying the buffers until a complete message
                // has been received
                if(h$TH.data.length < 1 || h$TH.data[0].length >= 8) {
                  h$TH.data.push(buf);
                } else {
                  h$TH.data[0] = Buffer.concat([h$TH.data[0], buf]);
                }
                h$TH.dataLen += buf.length;
                h$processQueue();
            } else {
                return;
            }
        }
    });
    process.stdin.on('close', function() { process.exit(0); });
}

function h$getMemoryUsage() {
  var m = process.memoryUsage();
  // return m.rss;
  return (m.heapTotal + m.external)|0;
}

var h$THMessageN = 0;
function h$processQueue() {
    while(h$TH.nWaiters > 0 && h$TH.data && h$TH.dataLen >= 8) {
        // if we have at least 8 bytes, they are all in data[0]
        var msgLength = h$TH.data[0].readUInt32BE(0);
        var msgTarget = h$TH.data[0].readUInt32BE(4);
        var msgBytes  = msgLength + 8;
        if(h$TH.dataLen >= msgBytes && h$TH.waiters[msgTarget]) {
            var bb         = Buffer.concat(h$TH.data);
            var w          = h$TH.waiters[msgTarget]
            var msgPayload = bb.slice(8, msgBytes);

            h$TH.data = [bb.slice(msgBytes)];
            h$TH.dataLen -= msgBytes;
            delete h$TH.waiters[msgTarget];
            h$TH.nWaiters--;
            if(h$THRecord && !h$THReplay)
                h$THfs.writeFileSync("thmessage." + (++h$THMessageN) + ".dat", msgPayload);
            w(msgPayload);
        } else {
            return;
        }
    }
}

function h$sendRequest(bs, offset, len, c) {
    var req = h$TH.requestId++;
    h$sendMessage(bs, offset, len, req, function() {});
    h$awaitMessage(req, c);
}

function h$sendMessage(bs, offset, len, req, c) {
    var msg = len === -1 ? new Buffer(bs.u8.subarray(offset))
                         : new Buffer(bs.u8.subarray(offset, len));
    var hdr = new Buffer(8);
    hdr.writeUInt32BE(msg.length, 0);
    hdr.writeUInt32BE(req, 4);
    process.stderr.write(Buffer.concat([hdr, msg]), 'binary', function() { c(); });
}

function h$awaitMessage(req, c) {
    h$awaitMessageRaw(req, function(buf) {
        c(h$THWrapBuffer(h$BufferToArrayBuffer(buf),false),0);
    });
}

var h$THReplayMessageN = 0;
function h$awaitMessageRaw(req, c) {
    if(h$TH.waiters[req]) throw ("h$awaitMessage: already waiting for " + req);
    if(h$THReplay) {
        try {
            c(h$THfs.readFileSync('thmessage.' + (++h$THReplayMessageN) + '.dat'));
            return;
        } catch(e) { }
    }
    h$TH.nWaiters++;
    h$TH.waiters[req] = c;
    h$processQueue();
}

function h$bufSize(buf, buf_offset) {
    if(buf === null) return 0;
    return buf.len;
}

// load the RTS and start the server
function h$loadInitialCode(buf) {
    var code = buf.toString('utf8');
    h$loadCodeStr(code, true);
    // don't allow Haskell to read from stdin
    h$base_stdin_fd.read = function(fd, fdo, buf, buf_offset, n, c) { c(0); }
    // redirect Haskell's stderr to stdout since we use stderr to communicate
    h$base_stderr_fd.write = h$base_stdout_fd.write;
    h$main(h$ghcjszmthZCGHCJSziPrimziTHziEvalzirunTHServer);

}

var h$THCodeN = 0;
function h$loadCodeStr(str, isFirst) {
    if(h$THReplay) {
        try {
            str = h$THfs.readFileSync("thcode." + (++h$THCodeN) + ".js").toString('utf8');
        } catch(e) { }
    } else if(h$THRecord) {
        h$THfs.writeFileSync("thcode." + (++h$THCodeN) + ".js", str);
    }
    eval.call(null, str);
}

// load additional code and run the initializers for it, the code should
// assign the h$TH.loadedSymbol variable
function h$loadCode(buf, off, len) {
    h$TH.loadedSymbol = null;
    var str = new Buffer(buf.u8).toString('utf8',off, off+len);
    h$TH.loadCodeStr(str, false);
    // h$runInitStatic();
    if(h$TH.loadedSymbol === null) throw "h$loadCode: error loading code"
    return h$TH.loadedSymbol;
}

function h$BufferToArrayBuffer(buf) {
    if(buf.toArrayBuffer) return buf.toArrayBuffer();
    return new Uint8Array(buf).buffer;
}

// copied from src/mem.js
function h$THWrapBuffer(buf, unalignedOk, offset, length) {
    if(!unalignedOk && offset && offset % 8 !== 0) {
        throw ("h$THWrapBuffer: offset not aligned:" + offset);
    }
    if(!buf || !(buf instanceof ArrayBuffer))
        throw "h$THWrapBuffer: not an ArrayBuffer"
    if(!offset) { offset = 0; }
    if(!length || length < 0) { length = buf.byteLength - offset; }
    // console.log("wrapping buf: " + length + " " + offset);
    return { buf: buf
             , len: length
             , i3: (offset%4) ? null : new Int32Array(buf, offset, length >> 2)
             , u8: new Uint8Array(buf, offset, length)
             , u1: (offset%2) ? null : new Uint16Array(buf, offset, length >> 1)
             , f3: (offset%4) ? null : new Float32Array(buf, offset, length >> 2)
             , f6: (offset%8) ? null : new Float64Array(buf, offset, length >> 3)
             , dv: new DataView(buf, offset, length)
           };
}

h$initTH();
<|endoftext|>


<commit_before>var themeServer = process.env.THEME_SERVER || 'http://themes.jsonresume.org/theme/';
var registryServer = process.env.REGISTRY_SERVER || 'http://registry.jsonresume.org';

var request = require('superagent');
var http = require('http');
var fs = require('fs');
var read = require('read');
var spinner = require("char-spinner");

function exportResume(resumeJson, fileName, program, callback) {
    var theme = program.theme;

    if (!fileName) {
        read({
            prompt: "Provide a file name: ",
            default: 'resume'
        }, function(er, fileName) {
            if (er) return console.log();
            var fileName = fileName;

            formatSelectMenu(program.format, function(format) {
                if (format === '.html') {
                    sendExportRequest(resumeJson, fileName, theme, format, function() {
                        callback(null, fileName, format);
                    });
                } else if (format === '.pdf') {
                    sendExportPDFRequest(resumeJson, fileName, theme, format, function() {
                        callback(null, fileName, format);
                    });
                }
            });
        });
    } else {
        formatSelectMenu(program.format, function(format) {
            if (format === '.html') {
                sendExportRequest(resumeJson, fileName, theme, format, function() {
                    callback(null, fileName, format);
                });
            } else if (format === '.pdf') {
                sendExportPDFRequest(resumeJson, fileName, theme, format, function() {
                    callback(null, fileName, format);
                });
            }
        });
    }
}

function sendExportRequest(resumeJson, fileName, theme, format, callback) {
    spinner();
    request
        .post(themeServer + theme)
        .send({
            resume: resumeJson
        })
        .set('Accept', 'application/json')
        .end(function(response) {
            fs.writeFileSync(process.cwd() + '/' + fileName + format, response.text);
            callback();
        });
    return;
}

function sendExportPDFRequest(resumeJson, fileName, theme, format, callback) {
    spinner();
    var stream = fs.createWriteStream(process.cwd() + '/' + fileName + format);
    var req = request

    .get(registryServer + '/pdf')
        .send({
            resume: resumeJson,
            theme: theme
        })
        .set('Accept', 'application/json');

    req.pipe(stream);
    stream.on('finish', function() {
        stream.close(callback);
    });
    return;
}

function formatSelectMenu(format, callback) {
    if (format) {
        callback('.' + format);
    } else {
        var menu = require('terminal-menu')({
            width: 29,
            x: 4,
            y: 2,
            bg: 'black',
            fg: 'cyan'
        });
        menu.reset();
        menu.write('Select file format for export\n');
        menu.write('-------------------------\n');
        menu.add('.html');
        // menu.add('.md');
        // menu.add('.txt');
        menu.add('.pdf');
        menu.on('select', function(format) {
            menu.close();
            console.log('SELECTED: ' + format);
            callback(format);
        });
        menu.createStream().pipe(process.stdout);
    }
}
module.exports = exportResume;<commit_msg>Get file format from file name during export<commit_after>var themeServer = process.env.THEME_SERVER || 'http://themes.jsonresume.org/theme/';
var registryServer = process.env.REGISTRY_SERVER || 'http://registry.jsonresume.org';

var request = require('superagent');
var http = require('http');
var fs = require('fs');
var read = require('read');
var spinner = require("char-spinner");

function exportResume(resumeJson, fileName, program, callback) {
    var theme = program.theme;

    if (!fileName) {
        read({
            prompt: "Provide a file name: ",
            default: 'resume'
        }, function(er, fileName) {
            if (er) return console.log();
            var fileName = fileName;
            var fileFormatFound = extractFileFormat(fileName);
            var fileFormatToUse = program.format;

            if (program.format && fileFormatFound
                && program.format === fileFormatFound) {
                    fileName = fileName.substring(0, fileName.lastIndexOf('.'));
            } else if (fileFormatFound) {
                    fileFormatToUse = fileFormatFound;
                    fileName = fileName.substring(0, fileName.lastIndexOf('.'));
            }

            formatSelectMenu(fileFormatToUse, function(format) {
                if (format === '.html') {
                    sendExportRequest(resumeJson, fileName, theme, format, function() {
                        callback(null, fileName, format);
                    });
                } else if (format === '.pdf') {
                    sendExportPDFRequest(resumeJson, fileName, theme, format, function() {
                        callback(null, fileName, format);
                    });
                }
            });
        });
    } else {
        var fileFormatFound = extractFileFormat(fileName);
        var fileFormatToUse = program.format;

        if (program.format && fileFormatFound
            && program.format === fileFormatFound) {
               fileName = fileName.substring(0, fileName.lastIndexOf('.'));
        } else if (fileFormatFound) {
               fileFormatToUse = fileFormatFound;
               fileName = fileName.substring(0, fileName.lastIndexOf('.'));
        }

        formatSelectMenu(fileFormatToUse, function(format) {
            if (format === '.html') {
                sendExportRequest(resumeJson, fileName, theme, format, function() {
                    callback(null, fileName, format);
                });
            } else if (format === '.pdf') {
                sendExportPDFRequest(resumeJson, fileName, theme, format, function() {
                    callback(null, fileName, format);
                });
            }
        });
    }
}

function extractFileFormat(fileName) {
    var dotPos = fileName.lastIndexOf('.');
    if (dotPos === -1) {
        return null;
    }
    return fileName.substring(dotPos + 1).toLowerCase();
}

function sendExportRequest(resumeJson, fileName, theme, format, callback) {
    spinner();
    request
        .post(themeServer + theme)
        .send({
            resume: resumeJson
        })
        .set('Accept', 'application/json')
        .end(function(response) {
            fs.writeFileSync(process.cwd() + '/' + fileName + format, response.text);
            callback();
        });
    return;
}

function sendExportPDFRequest(resumeJson, fileName, theme, format, callback) {
    spinner();
    var stream = fs.createWriteStream(process.cwd() + '/' + fileName + format);
    var req = request

    .get(registryServer + '/pdf')
        .send({
            resume: resumeJson,
            theme: theme
        })
        .set('Accept', 'application/json');

    req.pipe(stream);
    stream.on('finish', function() {
        stream.close(callback);
    });
    return;
}

function formatSelectMenu(format, callback) {
    if (format) {
        callback('.' + format);
    } else {
        var menu = require('terminal-menu')({
            width: 29,
            x: 4,
            y: 2,
            bg: 'black',
            fg: 'cyan'
        });
        menu.reset();
        menu.write('Select file format for export\n');
        menu.write('-------------------------\n');
        menu.add('.html');
        // menu.add('.md');
        // menu.add('.txt');
        menu.add('.pdf');
        menu.on('select', function(format) {
            menu.close();
            console.log('SELECTED: ' + format);
            callback(format);
        });
        menu.createStream().pipe(process.stdout);
    }
}
module.exports = exportResume;<|endoftext|>


<commit_before>/* eslint-disable no-magic-numbers,no-undef,semi */

user_pref('accessibility.force_disabled', 1);
user_pref('app.shield.optoutstudies.enabled', false);
user_pref('browser.cache.cache_isolation', true);
user_pref('browser.discovery.enabled', false);
user_pref('browser.fixup.alternate.enabled', false);
user_pref('browser.formfill.enable', false);
user_pref('browser.link.open_newwindow', 1);
user_pref('browser.newtabpage.enabled', false);
user_pref('browser.safebrowsing.downloads.remote.enabled', false);
user_pref('browser.startup.homepage', 'about:blank');
user_pref('browser.urlbar.doubleClickSelectsAll', false);
user_pref('browser.urlbar.openViewOnFocus', false);
user_pref('browser.urlbar.speculativeConnect.enabled', false);
user_pref('devtools.debugger.remote-enabled', false);
user_pref('devtools.inspector.activeSidebar', 'ruleview');
user_pref('devtools.inspector.color-scheme-simulation.enabled', true);
user_pref('devtools.inspector.showUserAgentStyles', true);
user_pref('devtools.inspector.three-pane-enabled', false);
user_pref('devtools.inspector.three-pane-first-run', false);
user_pref('dom.gamepad.enabled', false);
user_pref('dom.targetBlankNoOpener.enabled', true);
user_pref('extensions.activeThemeID', 'firefox-compact-light@mozilla.org');
user_pref('extensions.fxmonitor.enabled', false);
user_pref('extensions.htmlaboutaddons.recommendations.enabled', false);
user_pref('extensions.pocket.enabled', false);
user_pref('font.default.x-western', 'sans-serif');
user_pref('font.name.monospace.x-western', 'PragmataPro Mono Liga');
user_pref('font.name.sans-serif.el', 'Source Sans Pro');
user_pref('font.name.sans-serif.ja', 'Source Han Sans');
user_pref('font.name.sans-serif.ko', 'Source Han Sans K');
user_pref('font.name.sans-serif.x-cyrillic', 'Source Sans Pro');
user_pref('font.name.sans-serif.x-unicode', 'Source Sans Pro');
user_pref('font.name.sans-serif.x-western', 'Source Sans Pro');
user_pref('font.name.sans-serif.zh-CN', 'Source Han Sans SC');
user_pref('font.name.sans-serif.zh-HK', 'Source Han Sans HC');
user_pref('font.name.sans-serif.zh-TW', 'Source Han Sans TC');
user_pref('font.name.sans.x-western', 'Source Sans Pro');
user_pref('font.name.serif.el', 'Source Serif Pro');
user_pref('font.name.serif.ja', 'Source Han Serif');
user_pref('font.name.serif.ko', 'Source Han Serif K');
user_pref('font.name.serif.x-cyrillic', 'Source Serif Pro');
user_pref('font.name.serif.x-unicode', 'Source Sans Pro');
user_pref('font.name.serif.x-western', 'Source Serif Pro');
user_pref('font.name.serif.zh-CN', 'Source Han Serif SC');
user_pref('font.name.serif.zh-HK', 'Source Han Serif TC');
user_pref('font.name.serif.zh-TW', 'Source Han Serif TC');
user_pref('font.size.monospace.x-western', '16');
user_pref('gfx.color_management.enablev4', true);
user_pref('gfx.color_management.mode', 1);
user_pref('media.autoplay.default', 5);
user_pref('media.peerconnection.ice.default_address_only', true);
user_pref('media.peerconnection.ice.no_host', true);
user_pref('middlemouse.paste', false);
user_pref('network.IDN_show_punycode', true);
user_pref('network.cookie.cookieBehavior', 1);
user_pref('network.cookie.thirdparty.sessionOnly', true);
user_pref('network.dns.disablePrefetch', true);
user_pref('network.http.referer.XOriginPolicy', 1);
user_pref('network.http.referer.XOriginTrimmingPolicy', 2);
user_pref('network.http.speculative-parallel-limit', 0);
user_pref('network.manage-offline-status', false);
user_pref('network.predictor.enabled', false);
user_pref('network.prefetch-next', false);
user_pref('network.proxy.socks_remote_dns', true);
user_pref('network.trr.mode', 5);
user_pref('permissions.default.desktop-notification', 2);
user_pref('permissions.default.geo', 2);
user_pref('plugin.state.flash', 0);
user_pref('privacy.clearOnShutdown.offlineApps', true);
user_pref('privacy.clearOnShutdown.siteSettings', true);
user_pref('privacy.firstparty.isolate', true);
user_pref('privacy.firstparty.isolate.restrict_opener_access', true);
user_pref('privacy.trackingprotection.cryptomining.enabled', true);
user_pref('privacy.trackingprotection.enabled', false); // Disable only if using uBlock Origin or similar
user_pref('privacy.trackingprotection.fingerprinting.enabled', true);
user_pref('privacy.trackingprotection.pbmode.enabled', false); // Disable only if using uBlock Origin or similar
user_pref('reader.content_width', 4);
user_pref('reader.font_size', 5);
user_pref('reader.font_type', 'sans-serif');
user_pref('reader.line_height', 3);
user_pref('security.mixed_content.block_display_content', true);
user_pref('security.mixed_content.block_object_subrequest', true);
user_pref('security.mixed_content.upgrade_display_content', true);
user_pref('security.OCSP.enabled', 0);
user_pref('signon.rememberSignons', false);
user_pref('toolkit.legacyUserProfileCustomizations.stylesheets', true);
user_pref('toolkit.telemetry.archive.enabled', false);
user_pref('toolkit.telemetry.enabled', false);
user_pref('toolkit.telemetry.unified', false);
<commit_msg>firefox: Enable HTTPS-only mode<commit_after>/* eslint-disable no-magic-numbers,no-undef,semi */

user_pref('accessibility.force_disabled', 1);
user_pref('app.shield.optoutstudies.enabled', false);
user_pref('browser.cache.cache_isolation', true);
user_pref('browser.discovery.enabled', false);
user_pref('browser.fixup.alternate.enabled', false);
user_pref('browser.formfill.enable', false);
user_pref('browser.link.open_newwindow', 1);
user_pref('browser.newtabpage.enabled', false);
user_pref('browser.safebrowsing.downloads.remote.enabled', false);
user_pref('browser.startup.homepage', 'about:blank');
user_pref('browser.urlbar.doubleClickSelectsAll', false);
user_pref('browser.urlbar.openViewOnFocus', false);
user_pref('browser.urlbar.speculativeConnect.enabled', false);
user_pref('devtools.debugger.remote-enabled', false);
user_pref('devtools.inspector.activeSidebar', 'ruleview');
user_pref('devtools.inspector.color-scheme-simulation.enabled', true);
user_pref('devtools.inspector.showUserAgentStyles', true);
user_pref('devtools.inspector.three-pane-enabled', false);
user_pref('devtools.inspector.three-pane-first-run', false);
user_pref('dom.gamepad.enabled', false);
user_pref('dom.security.https_only_mode', true);
user_pref('dom.security.https_only_mode_ever_enabled', true);
user_pref('dom.targetBlankNoOpener.enabled', true);
user_pref('extensions.activeThemeID', 'firefox-compact-light@mozilla.org');
user_pref('extensions.fxmonitor.enabled', false);
user_pref('extensions.htmlaboutaddons.recommendations.enabled', false);
user_pref('extensions.pocket.enabled', false);
user_pref('font.default.x-western', 'sans-serif');
user_pref('font.name.monospace.x-western', 'PragmataPro Mono Liga');
user_pref('font.name.sans-serif.el', 'Source Sans Pro');
user_pref('font.name.sans-serif.ja', 'Source Han Sans');
user_pref('font.name.sans-serif.ko', 'Source Han Sans K');
user_pref('font.name.sans-serif.x-cyrillic', 'Source Sans Pro');
user_pref('font.name.sans-serif.x-unicode', 'Source Sans Pro');
user_pref('font.name.sans-serif.x-western', 'Source Sans Pro');
user_pref('font.name.sans-serif.zh-CN', 'Source Han Sans SC');
user_pref('font.name.sans-serif.zh-HK', 'Source Han Sans HC');
user_pref('font.name.sans-serif.zh-TW', 'Source Han Sans TC');
user_pref('font.name.sans.x-western', 'Source Sans Pro');
user_pref('font.name.serif.el', 'Source Serif Pro');
user_pref('font.name.serif.ja', 'Source Han Serif');
user_pref('font.name.serif.ko', 'Source Han Serif K');
user_pref('font.name.serif.x-cyrillic', 'Source Serif Pro');
user_pref('font.name.serif.x-unicode', 'Source Sans Pro');
user_pref('font.name.serif.x-western', 'Source Serif Pro');
user_pref('font.name.serif.zh-CN', 'Source Han Serif SC');
user_pref('font.name.serif.zh-HK', 'Source Han Serif TC');
user_pref('font.name.serif.zh-TW', 'Source Han Serif TC');
user_pref('font.size.monospace.x-western', '16');
user_pref('gfx.color_management.enablev4', true);
user_pref('gfx.color_management.mode', 1);
user_pref('media.autoplay.default', 5);
user_pref('media.peerconnection.ice.default_address_only', true);
user_pref('media.peerconnection.ice.no_host', true);
user_pref('middlemouse.paste', false);
user_pref('network.IDN_show_punycode', true);
user_pref('network.cookie.cookieBehavior', 1);
user_pref('network.cookie.thirdparty.sessionOnly', true);
user_pref('network.dns.disablePrefetch', true);
user_pref('network.http.referer.XOriginPolicy', 1);
user_pref('network.http.referer.XOriginTrimmingPolicy', 2);
user_pref('network.http.speculative-parallel-limit', 0);
user_pref('network.manage-offline-status', false);
user_pref('network.predictor.enabled', false);
user_pref('network.prefetch-next', false);
user_pref('network.proxy.socks_remote_dns', true);
user_pref('network.trr.mode', 5);
user_pref('permissions.default.desktop-notification', 2);
user_pref('permissions.default.geo', 2);
user_pref('plugin.state.flash', 0);
user_pref('privacy.clearOnShutdown.offlineApps', true);
user_pref('privacy.clearOnShutdown.siteSettings', true);
user_pref('privacy.firstparty.isolate', true);
user_pref('privacy.firstparty.isolate.restrict_opener_access', true);
user_pref('privacy.trackingprotection.cryptomining.enabled', true);
user_pref('privacy.trackingprotection.enabled', false); // Disable only if using uBlock Origin or similar
user_pref('privacy.trackingprotection.fingerprinting.enabled', true);
user_pref('privacy.trackingprotection.pbmode.enabled', false); // Disable only if using uBlock Origin or similar
user_pref('reader.content_width', 4);
user_pref('reader.font_size', 5);
user_pref('reader.font_type', 'sans-serif');
user_pref('reader.line_height', 3);
user_pref('security.mixed_content.block_display_content', true);
user_pref('security.mixed_content.block_object_subrequest', true);
user_pref('security.mixed_content.upgrade_display_content', true);
user_pref('security.OCSP.enabled', 0);
user_pref('signon.rememberSignons', false);
user_pref('toolkit.legacyUserProfileCustomizations.stylesheets', true);
user_pref('toolkit.telemetry.archive.enabled', false);
user_pref('toolkit.telemetry.enabled', false);
user_pref('toolkit.telemetry.unified', false);
<|endoftext|>


<commit_before>/* eslint-disable no-magic-numbers,no-undef,semi */

user_pref('accessibility.force_disabled', 1);
user_pref('app.shield.optoutstudies.enabled', false);
user_pref('browser.discovery.enabled', false);
user_pref('browser.fixup.alternate.enabled', false);
user_pref('browser.formfill.enable', false);
user_pref('browser.newtabpage.enabled', false);
user_pref('browser.safebrowsing.downloads.remote.enabled', false);
user_pref('browser.urlbar.speculativeConnect.enabled', false);
user_pref('extensions.pocket.enabled', false);
user_pref('gfx.color_management.enablev4', true);
user_pref('gfx.color_management.mode', 1);
user_pref('media.peerconnection.ice.default_address_only', true);
user_pref('media.peerconnection.ice.no_host', true);
user_pref('network.IDN_show_punycode', true);
user_pref('network.cookie.cookieBehavior', 3);
user_pref('network.cookie.thirdparty.sessionOnly', true);
user_pref('network.dns.disablePrefetch', true);
user_pref('network.http.referer.XOriginPolicy', 1);
user_pref('network.http.referer.XOriginTrimmingPolicy', 2);
user_pref('network.http.speculative-parallel-limit', 0);
user_pref('network.manage-offline-status', false);
user_pref('network.predictor.enabled', false);
user_pref('network.prefetch-next', false);
user_pref('network.proxy.socks_remote_dns', true);
user_pref('plugin.state.flash', 0);
user_pref('privacy.clearOnShutdown.offlineApps', true);
user_pref('privacy.clearOnShutdown.siteSettings', true);
user_pref('privacy.firstparty.isolate', true);
user_pref('privacy.firstparty.isolate.restrict_opener_access', true);
user_pref('privacy.trackingprotection.cryptomining.enabled', true);
user_pref('privacy.trackingprotection.enabled', false); // Disable only if using uBlock Origin or similar
user_pref('privacy.trackingprotection.fingerprinting.enabled', true);
user_pref('privacy.trackingprotection.pbmode.enabled', false); // Disable only if using uBlock Origin or similar
user_pref('security.mixed_content.block_display_content', true);
user_pref('security.mixed_content.block_object_subrequest', true);
user_pref('security.mixed_content.upgrade_display_content', true);
user_pref('signon.rememberSignons', false);
user_pref('toolkit.telemetry.archive.enabled', false);
user_pref('toolkit.telemetry.enabled', false);
user_pref('toolkit.telemetry.unified', false);
<commit_msg>firefox: Plug the rel=noopener privacy flaw by default<commit_after>/* eslint-disable no-magic-numbers,no-undef,semi */

user_pref('accessibility.force_disabled', 1);
user_pref('app.shield.optoutstudies.enabled', false);
user_pref('browser.discovery.enabled', false);
user_pref('browser.fixup.alternate.enabled', false);
user_pref('browser.formfill.enable', false);
user_pref('browser.newtabpage.enabled', false);
user_pref('browser.safebrowsing.downloads.remote.enabled', false);
user_pref('browser.urlbar.speculativeConnect.enabled', false);
user_pref('dom.targetBlankNoOpener.enabled', true);
user_pref('extensions.pocket.enabled', false);
user_pref('gfx.color_management.enablev4', true);
user_pref('gfx.color_management.mode', 1);
user_pref('media.peerconnection.ice.default_address_only', true);
user_pref('media.peerconnection.ice.no_host', true);
user_pref('network.IDN_show_punycode', true);
user_pref('network.cookie.cookieBehavior', 3);
user_pref('network.cookie.thirdparty.sessionOnly', true);
user_pref('network.dns.disablePrefetch', true);
user_pref('network.http.referer.XOriginPolicy', 1);
user_pref('network.http.referer.XOriginTrimmingPolicy', 2);
user_pref('network.http.speculative-parallel-limit', 0);
user_pref('network.manage-offline-status', false);
user_pref('network.predictor.enabled', false);
user_pref('network.prefetch-next', false);
user_pref('network.proxy.socks_remote_dns', true);
user_pref('plugin.state.flash', 0);
user_pref('privacy.clearOnShutdown.offlineApps', true);
user_pref('privacy.clearOnShutdown.siteSettings', true);
user_pref('privacy.firstparty.isolate', true);
user_pref('privacy.firstparty.isolate.restrict_opener_access', true);
user_pref('privacy.trackingprotection.cryptomining.enabled', true);
user_pref('privacy.trackingprotection.enabled', false); // Disable only if using uBlock Origin or similar
user_pref('privacy.trackingprotection.fingerprinting.enabled', true);
user_pref('privacy.trackingprotection.pbmode.enabled', false); // Disable only if using uBlock Origin or similar
user_pref('security.mixed_content.block_display_content', true);
user_pref('security.mixed_content.block_object_subrequest', true);
user_pref('security.mixed_content.upgrade_display_content', true);
user_pref('signon.rememberSignons', false);
user_pref('toolkit.telemetry.archive.enabled', false);
user_pref('toolkit.telemetry.enabled', false);
user_pref('toolkit.telemetry.unified', false);
<|endoftext|>


<commit_before>var ccss, compile, inject, parseRules, preparser, uuid, vfl, vgl;

preparser = require('gss-preparser');

ccss = require('ccss-compiler');

vfl = require('vfl-compiler');

vgl = require('vgl-compiler');

uuid = function() {
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
    var r, v;
    r = Math.random() * 16 | 0;
    v = (c === "x" ? r : r & 0x3 | 0x8);
    return v.toString(16);
  });
};

compile = function(gss) {
  var error, rules;
  try {
    rules = preparser.parse(gss.trim());
  } catch (_error) {
    error = _error;
    error.name = 'Preparse error';
    throw error;
  }
  rules = parseRules(rules);
  return rules;
};

parseRules = function(rules) {
  var ccssRule, ccssRules, chunk, css, error, key, parsed, subParsed, subrules, val, vflRule, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1;
  css = "";
  for (_i = 0, _len = rules.length; _i < _len; _i++) {
    chunk = rules[_i];
    parsed = {};
    switch (chunk.type) {
      case 'directive':
        switch (chunk.name) {
          case 'grid-template':
          case '-gss-grid-template':
          case 'grid-rows':
          case '-gss-rows':
          case 'grid-cols':
          case '-gss-grid-cols':
            try {
              subrules = vgl.parse("@" + chunk.name + " " + chunk.terms);
            } catch (_error) {
              error = _error;
              error.name = "VGL parse error: @" + chunk.name + " " + chunk.terms;
              throw error;
            }
            parsed = {
              selectors: [],
              commands: []
            };
            _ref = subrules.ccss;
            for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
              ccssRule = _ref[_j];
              try {
                subParsed = ccss.parse(ccssRule);
              } catch (_error) {
                error = _error;
                error.name = 'VGL generated CCSS parse error';
                throw error;
              }
              parsed.selectors = parsed.selectors.concat(subParsed.selectors);
              parsed.commands = parsed.commands.concat(subParsed.commands);
            }
            _ref1 = subrules.vfl;
            for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
              vflRule = _ref1[_k];
              try {
                subParsed = ccss.parse(vfl.parse(vflRule).join("; "));
              } catch (_error) {
                error = _error;
                error.name = 'VGL generated VFL parse error';
                throw error;
              }
              parsed.selectors = parsed.selectors.concat(subParsed.selectors);
              parsed.commands = parsed.commands.concat(subParsed.commands);
            }
            break;
          case 'horizontal':
          case 'vertical':
          case '-gss-horizontal':
          case '-gss-vertical':
          case 'h':
          case 'v':
          case '-gss-h':
          case '-gss-v':
            try {
              ccssRules = vfl.parse("@" + chunk.name + " " + chunk.terms);
            } catch (_error) {
              error = _error;
              error.name = "VFL parse error: @" + chunk.name + " " + chunk.terms;
              throw error;
            }
            parsed = {
              selectors: [],
              commands: []
            };
            for (_l = 0, _len3 = ccssRules.length; _l < _len3; _l++) {
              ccssRule = ccssRules[_l];
              try {
                subParsed = ccss.parse(ccssRule);
              } catch (_error) {
                error = _error;
                error.name = 'VFL generated CCSS parse error';
                throw error;
              }
              parsed.selectors = parsed.selectors.concat(subParsed.selectors);
              parsed.commands = parsed.commands.concat(subParsed.commands);
            }
            break;
          case 'if':
          case 'elseif':
          case 'else':
            if (chunk.terms.length > 0) {
              try {
                parsed = ccss.parse("@cond" + chunk.terms + ";");
              } catch (_error) {
                error = _error;
                error.name = 'CCSS conditional parse error';
                throw error;
              }
              parsed.clause = parsed.commands[0];
              delete parsed.commands;
            } else {
              parsed.clause = null;
            }
        }
        break;
      case 'constraint':
        try {
          parsed = ccss.parse(chunk.cssText);
        } catch (_error) {
          error = _error;
          error.name = 'Constraint parse error';
          throw error;
        }
    }
    for (key in parsed) {
      val = parsed[key];
      chunk[key] = val;
    }
    if (chunk.rules) {
      parseRules(chunk.rules);
    }
  }
  return rules;
};

inject = function(chunks) {
  var _inject;
  _inject = function(_rules, parent) {
    var rule, _i, _len, _ref, _results;
    _results = [];
    for (_i = 0, _len = _rules.length; _i < _len; _i++) {
      rule = _rules[_i];
      rule._uuid = uuid();
      if (parent) {
        rule._parent_uuid = parent._uuid;
      }
      if (((_ref = rule.rules) != null ? _ref.length : void 0) > 0) {
        _results.push(_inject(rule.rules, rule));
      } else {
        _results.push(void 0);
      }
    }
    return _results;
  };
  _inject(chunks);
  return chunks;
};

exports.compile = compile;
<commit_msg>Add changes to compiled version.<commit_after>var ccss, compile, inject, parseRules, preparser, uuid, vfl, vgl;

preparser = require('gss-preparser');

ccss = require('ccss-compiler');

vfl = require('vfl-compiler');

vgl = require('vgl-compiler');

uuid = function() {
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
    var r, v;
    r = Math.random() * 16 | 0;
    v = (c === "x" ? r : r & 0x3 | 0x8);
    return v.toString(16);
  });
};

compile = function(gss) {
  var error, rules;
  try {
    rules = preparser.parse(gss.trim());
  } catch (_error) {
    error = _error;
    error.name = 'Preparse error';
    throw error;
  }
  rules = parseRules(rules);
  return rules;
};

parseRules = function(rules) {
  var ccssRule, ccssRules, chunk, css, error, info, key, parsed, subParsed, subrules, val, vflRule, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1;
  css = "";
  for (_i = 0, _len = rules.length; _i < _len; _i++) {
    chunk = rules[_i];
    parsed = {};
    switch (chunk.type) {
      case 'directive':
        switch (chunk.name) {
          case 'grid-template':
          case '-gss-grid-template':
          case 'grid-rows':
          case '-gss-rows':
          case 'grid-cols':
          case '-gss-grid-cols':
            try {
              subrules = vgl.parse("@" + chunk.name + " " + chunk.terms);
            } catch (_error) {
              error = _error;
              error.name = "VGL parse error: @" + chunk.name + " " + chunk.terms;
              throw error;
            }
            parsed = {
              selectors: [],
              commands: []
            };
            _ref = subrules.ccss;
            for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
              ccssRule = _ref[_j];
              try {
                subParsed = ccss.parse(ccssRule);
              } catch (_error) {
                error = _error;
                error.name = 'VGL generated CCSS parse error';
                throw error;
              }
              parsed.selectors = parsed.selectors.concat(subParsed.selectors);
              parsed.commands = parsed.commands.concat(subParsed.commands);
            }
            _ref1 = subrules.vfl;
            for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
              vflRule = _ref1[_k];
              try {
                subParsed = ccss.parse(vfl.parse(vflRule).join("; "));
              } catch (_error) {
                error = _error;
                error.name = 'VGL generated VFL parse error';
                throw error;
              }
              parsed.selectors = parsed.selectors.concat(subParsed.selectors);
              parsed.commands = parsed.commands.concat(subParsed.commands);
            }
            break;
          case 'horizontal':
          case 'vertical':
          case '-gss-horizontal':
          case '-gss-vertical':
          case 'h':
          case 'v':
          case '-gss-h':
          case '-gss-v':
            try {
              ccssRules = vfl.parse("@" + chunk.name + " " + chunk.terms);
            } catch (_error) {
              error = _error;
              error.name = "VFL parse error: @" + chunk.name + " " + chunk.terms;
              throw error;
            }
            parsed = {
              selectors: [],
              commands: []
            };
            for (_l = 0, _len3 = ccssRules.length; _l < _len3; _l++) {
              ccssRule = ccssRules[_l];
              try {
                subParsed = ccss.parse(ccssRule);
              } catch (_error) {
                error = _error;
                info = [];
                info.push('Generated by VFL statement:');
                info.push('');
                info.push("@" + chunk.name + " " + chunk.terms);
                console.info(info.join('\n'));
                error.name = 'VFL generated CCSS parse error';
                throw error;
              }
              parsed.selectors = parsed.selectors.concat(subParsed.selectors);
              parsed.commands = parsed.commands.concat(subParsed.commands);
            }
            break;
          case 'if':
          case 'elseif':
          case 'else':
            if (chunk.terms.length > 0) {
              try {
                parsed = ccss.parse("@cond" + chunk.terms + ";");
              } catch (_error) {
                error = _error;
                error.name = 'CCSS conditional parse error';
                throw error;
              }
              parsed.clause = parsed.commands[0];
              delete parsed.commands;
            } else {
              parsed.clause = null;
            }
        }
        break;
      case 'constraint':
        try {
          parsed = ccss.parse(chunk.cssText);
        } catch (_error) {
          error = _error;
          error.name = 'Constraint parse error';
          throw error;
        }
    }
    for (key in parsed) {
      val = parsed[key];
      chunk[key] = val;
    }
    if (chunk.rules) {
      parseRules(chunk.rules);
    }
  }
  return rules;
};

inject = function(chunks) {
  var _inject;
  _inject = function(_rules, parent) {
    var rule, _i, _len, _ref, _results;
    _results = [];
    for (_i = 0, _len = _rules.length; _i < _len; _i++) {
      rule = _rules[_i];
      rule._uuid = uuid();
      if (parent) {
        rule._parent_uuid = parent._uuid;
      }
      if (((_ref = rule.rules) != null ? _ref.length : void 0) > 0) {
        _results.push(_inject(rule.rules, rule));
      } else {
        _results.push(void 0);
      }
    }
    return _results;
  };
  _inject(chunks);
  return chunks;
};

exports.compile = compile;
<|endoftext|>


<commit_before>var fs = require('fs');
var p = require('path');
var hogan = require('hogan.js');

var cache = {};
var ctx = {};

var isObj = function(obj) {
  return obj && obj.toString && obj.toString() === '[object Object]';
};

var extend = function(dest, ...srcs) {
  return srcs.reduce(function(memo, src) {
    for (var k in src) {
      if (!src.hasOwnProperty(k)) {
        continue;
      }
      if (isObj(src[k])) {
        memo[k] = extend({}, src[k]);
      } else {
        memo[k] = src[k];
      }
    }

    return memo;
  }, dest);
};

var read = function(path, options, fn) {
  var str = cache[path];

  if (options.cache && str) {
    return fn(null, str);
  }

  fs.readFile(path, 'utf8', function(err, str) {
    if (err) {
      return fn(err);
    }

    str = str.replace(/^\uFEFF/, '');

    if (options.cache) {
      cache[path] = str;
    }

    return fn(null, str);
  });
};

var renderPartials = function(partials, opt, fn) {
  var count = 1;
  var result = {};

  var handlePath = function(name, path) {
    return function(err, str) {
      if (!count) {
        return;
      }

      if (err) {
        count = 0;
        fn(err);
      }

      result[name] = str;
      if (!--count) {
        return fn(null, result);
      }
    };
  };

  for (var name in partials) {
    var path = partials[name];
    if (typeof path !== 'string') {
      continue;
    }

    if (!p.extname(path)) {
      path += ctx.ext;
    }

    path = ctx.lookup(path);
    count++;
    read(path, opt, handlePath(name, path));
  }

  if (!--count) {
    return fn(null, result);
  }
};

var renderLayout = function(path, opt, fn) {
  if (!path) {
    return fn(null, false);
  }

  if (!p.extname(path)) {
    path += ctx.ext;
  }

  path = ctx.lookup(path);

  if (!path) {
    return fn(null, false);
  }

  read(path, opt, function(err, str) {
    if (err) {
      return fn(err);
    }

    return fn(null, str);
  });
};

var customContent = function(str, tag, opt, partials) {
  var oTag = `{{#${ tag }}}`;
  var cTag = `{{/${ tag }}}`;
  var text = str.substring(str.indexOf(oTag) + oTag.length, str.indexOf(cTag));
  return hogan.compile(text, opt).render(opt, partials);
};

var render = function(path, opt, fn) {
  ctx = this;
  var partials = opt.settings.partials || {};

  if (opt.partials) {
    partials = extend(partials, opt.partials);
  }

  var lambdas = opt.settings.lambdas || {};

  if (opt.lambdas) {
    lambdas = extend(lambdas, opt.lambdas);
  }

  // create the lambdafied functions
  // this way of dealing with lambdas assumes you'll want
  // to call your function on the rendered content instead
  // of the original template string
  opt.lambdas = {};
  var lambdaIndexes = {};

  var handleLambda = function(name, lambda) {
    lambdaIndexes[name] = 0;
    opt.lambdas[name] = function() {
      var lambdaContext = this;
      return function(text) {
        if (!lambdaContext.lambdaVals) {
          lambdaContext.lambdaVals = {};
        }

        if (!lambdaContext.lambdaVals[name]) {
          lambdaContext.lambdaVals[name] = {};
        }

        // getting the context right here is important
        // it must account for "locals" and values in the current context
        //  ... particually interesting when applying within a list
        var lctx = extend({}, opt, opt._locals || {}, lambdaContext);
        return lambda(hogan.compile(text).render(lctx), lctx);
      };
    };
  };

  for (var name in lambdas) {
    handleLambda(name, lambdas[name]);
  }

  renderPartials(partials, opt, function(err, partials) {
    if (err) {
      return fn(err);
    }

    var layout = opt.layout || opt.settings.layout;
    renderLayout(layout, opt, function(err, layout) {
      read(path, opt, function(err, str) {
        if (err) {
          return fn(err);
        }
        try {
          var tmpl = hogan.compile(str, opt);
          var result = tmpl.render(opt, partials);
          var customTags = str.match(/({{#yield-\w+}})/g);
          var yields = {};
          if (customTags) {
            customTags.forEach(function(customTag) {
              var tag = customTag.match(/{{#([\w-]+)}}/)[1];
              if (tag) {
                if (layout) {
                  opt[tag] = customContent(str, tag, opt, partials);
                } else {
                  yields[tag.replace('yield-', '')] = customContent(str, tag, opt, partials);
                }
              }
            });
          }

          if (layout) {
            opt.yield = result;
            tmpl = hogan.compile(layout, opt);
            result = tmpl.render(opt, partials);
            return fn(null, result);
          }

          return fn(null, result, yields);
        } catch (err) {
          return fn(err);
        }
      });
    });
  });
};

module.exports = render;
<commit_msg>Make extend function node 4 and 5 compatible<commit_after>var fs = require('fs');
var p = require('path');
var hogan = require('hogan.js');

var cache = {};
var ctx = {};

var isObj = function(obj) {
  return obj && obj.toString && obj.toString() === '[object Object]';
};

var extend = function(dest) {
  var srcs = [].slice.call(arguments, 1);
  return srcs.reduce(function(memo, src) {
    for (var k in src) {
      if (!src.hasOwnProperty(k)) {
        continue;
      }
      if (isObj(src[k])) {
        memo[k] = extend({}, src[k]);
      } else {
        memo[k] = src[k];
      }
    }

    return memo;
  }, dest);
};

var read = function(path, options, fn) {
  var str = cache[path];

  if (options.cache && str) {
    return fn(null, str);
  }

  fs.readFile(path, 'utf8', function(err, str) {
    if (err) {
      return fn(err);
    }

    str = str.replace(/^\uFEFF/, '');

    if (options.cache) {
      cache[path] = str;
    }

    return fn(null, str);
  });
};

var renderPartials = function(partials, opt, fn) {
  var count = 1;
  var result = {};

  var handlePath = function(name, path) {
    return function(err, str) {
      if (!count) {
        return;
      }

      if (err) {
        count = 0;
        fn(err);
      }

      result[name] = str;
      if (!--count) {
        return fn(null, result);
      }
    };
  };

  for (var name in partials) {
    var path = partials[name];
    if (typeof path !== 'string') {
      continue;
    }

    if (!p.extname(path)) {
      path += ctx.ext;
    }

    path = ctx.lookup(path);
    count++;
    read(path, opt, handlePath(name, path));
  }

  if (!--count) {
    return fn(null, result);
  }
};

var renderLayout = function(path, opt, fn) {
  if (!path) {
    return fn(null, false);
  }

  if (!p.extname(path)) {
    path += ctx.ext;
  }

  path = ctx.lookup(path);

  if (!path) {
    return fn(null, false);
  }

  read(path, opt, function(err, str) {
    if (err) {
      return fn(err);
    }

    return fn(null, str);
  });
};

var customContent = function(str, tag, opt, partials) {
  var oTag = `{{#${ tag }}}`;
  var cTag = `{{/${ tag }}}`;
  var text = str.substring(str.indexOf(oTag) + oTag.length, str.indexOf(cTag));
  return hogan.compile(text, opt).render(opt, partials);
};

var render = function(path, opt, fn) {
  ctx = this;
  var partials = opt.settings.partials || {};

  if (opt.partials) {
    partials = extend(partials, opt.partials);
  }

  var lambdas = opt.settings.lambdas || {};

  if (opt.lambdas) {
    lambdas = extend(lambdas, opt.lambdas);
  }

  // create the lambdafied functions
  // this way of dealing with lambdas assumes you'll want
  // to call your function on the rendered content instead
  // of the original template string
  opt.lambdas = {};
  var lambdaIndexes = {};

  var handleLambda = function(name, lambda) {
    lambdaIndexes[name] = 0;
    opt.lambdas[name] = function() {
      var lambdaContext = this;
      return function(text) {
        if (!lambdaContext.lambdaVals) {
          lambdaContext.lambdaVals = {};
        }

        if (!lambdaContext.lambdaVals[name]) {
          lambdaContext.lambdaVals[name] = {};
        }

        // getting the context right here is important
        // it must account for "locals" and values in the current context
        //  ... particually interesting when applying within a list
        var lctx = extend({}, opt, opt._locals || {}, lambdaContext);
        return lambda(hogan.compile(text).render(lctx), lctx);
      };
    };
  };

  for (var name in lambdas) {
    handleLambda(name, lambdas[name]);
  }

  renderPartials(partials, opt, function(err, partials) {
    if (err) {
      return fn(err);
    }

    var layout = opt.layout || opt.settings.layout;
    renderLayout(layout, opt, function(err, layout) {
      read(path, opt, function(err, str) {
        if (err) {
          return fn(err);
        }
        try {
          var tmpl = hogan.compile(str, opt);
          var result = tmpl.render(opt, partials);
          var customTags = str.match(/({{#yield-\w+}})/g);
          var yields = {};
          if (customTags) {
            customTags.forEach(function(customTag) {
              var tag = customTag.match(/{{#([\w-]+)}}/)[1];
              if (tag) {
                if (layout) {
                  opt[tag] = customContent(str, tag, opt, partials);
                } else {
                  yields[tag.replace('yield-', '')] = customContent(str, tag, opt, partials);
                }
              }
            });
          }

          if (layout) {
            opt.yield = result;
            tmpl = hogan.compile(layout, opt);
            result = tmpl.render(opt, partials);
            return fn(null, result);
          }

          return fn(null, result, yields);
        } catch (err) {
          return fn(err);
        }
      });
    });
  });
};

module.exports = render;
<|endoftext|>