<commit_before>const {Emitter, CompositeDisposable} = require("atom")
const {shrinkRangeEndToBeforeNewLine, collectRangeInBufferRow} = require("./utils")

module.exports = class OccurrenceManager {
  constructor(vimState) {
    this.vimState = vimState
    const {editor} = vimState

    vimState.onDidDestroy(() => this.destroy())

    this.emitter = new Emitter()
    this.patterns = []

    this.markerLayer = editor.addMarkerLayer()
    this.decorationLayer = editor.decorateMarkerLayer(this.markerLayer, {
      type: "highlight",
      class: "vim-mode-plus-occurrence-base",
    })

    // All maker create/destroy/css-update is done by reacting patters's change.
    // -------------------------
    this.onDidChangePatterns(({pattern, occurrenceType}) => {
      if (pattern) {
        this.markBufferRangeByPattern(pattern, occurrenceType)
      } else {
        this.clearMarkers()
      }
      this.updateEditorElement()
    })

    this.markerLayer.onDidUpdate(() => {
      this.destroyMarkers(this.getMarkers().filter(marker => !marker.isValid()))
    })
  }

  markBufferRangeByPattern(pattern, occurrenceType) {
    const markRange = range => this.markerLayer.markBufferRange(range, {invalidate: "inside"})
    const {editor} = this.vimState
    if (occurrenceType === "subword") {
      const subwordRegex = editor.getLastCursor().subwordRegExp()
      const cache = {}
      editor.scan(pattern, ({range}) => {
        const {row} = range.start
        if (!cache[row]) cache[row] = collectRangeInBufferRow(editor, row, subwordRegex)
        if (cache[row].some(subwordRange => subwordRange.isEqual(range))) markRange(range)
      })
    } else {
      const ranges = []
      editor.scan(pattern, ({range}) => ranges.push(range))
      if (this.canCreateMarkersForLength(ranges.length)) ranges.forEach(markRange)
    }
  }

  canCreateMarkersForLength(length) {
    const threshold = this.vimState.getConfig("confirmThresholdOnOccurrenceOperation")
    if (threshold >= length) {
      return true
    } else {
      const options = {
        message: `Too many(${length}) matches. Do you want to continue?`,
        detailedMessage: `If you want increase threshold(current: ${threshold}), change "Confirm Threshold On Create Preset Occurrences" configuration.`,
        buttons: ["Continue", "Cancel"],
      }
      return atom.confirm(options) === 0
    }
  }

  updateEditorElement() {
    this.vimState.editorElement.classList.toggle("has-occurrence", this.hasMarkers())
  }

  // Callback get passed following object
  // - pattern: can be undefined on reset event
  onDidChangePatterns(fn) {
    return this.emitter.on("did-change-patterns", fn)
  }

  destroy() {
    this.decorationLayer.destroy()
    this.markerLayer.destroy()
  }

  // Patterns
  hasPatterns() {
    return this.patterns.length > 0
  }

  resetPatterns() {
    this.patterns = []
    this.emitter.emit("did-change-patterns", {})
  }

  addPattern(pattern = null, {reset, occurrenceType = "base"} = {}) {
    if (reset) this.clearMarkers()

    this.patterns.push(pattern)
    this.emitter.emit("did-change-patterns", {pattern, occurrenceType})
  }

  saveLastPattern(occurrenceType) {
    this.vimState.globalState.set("lastOccurrencePattern", this.buildPattern())
    this.vimState.globalState.set("lastOccurrenceType", occurrenceType)
  }

  // Return regex representing final pattern.
  // Used to cache final pattern to each instance of operator so that we can
  // repeat recorded operation by `.`.
  // Pattern can be added interactively one by one, but we save it as union pattern.
  buildPattern() {
    return new RegExp(this.patterns.map(regex => regex.source).join("|"), "g")
  }

  // Markers
  // -------------------------
  clearMarkers() {
    this.markerLayer.clear()
  }

  destroyMarkers(markers) {
    markers.forEach(marker => marker.destroy())
    // whenerver we destroy marker, we should sync `has-occurrence` scope in marker state..
    this.updateEditorElement()
  }

  hasMarkers() {
    return this.markerLayer.getMarkerCount() > 0
  }

  getMarkers() {
    return this.markerLayer.getMarkers()
  }

  getMarkerBufferRanges() {
    return this.markerLayer.getMarkers().map(marker => marker.getBufferRange())
  }

  getMarkerCount() {
    return this.markerLayer.getMarkerCount()
  }

  // Return occurrence markers intersecting given ranges
  getMarkersIntersectsWithSelection(selection, exclusive = false) {
    // findmarkers()'s intersectsBufferRange param have no exclusive control
    // So need extra check to filter out unwanted marker.
    // But basically I should prefer findMarker since It's fast than iterating
    // whole markers manually.
    const range = shrinkRangeEndToBeforeNewLine(selection.getBufferRange())
    return this.markerLayer
      .findMarkers({intersectsBufferRange: range})
      .filter(marker => range.intersectsWith(marker.getBufferRange(), exclusive))
  }

  getMarkerAtPoint(point) {
    // We have to check all returned marker until found, since we do aditional marker validation.
    // e.g. For text `abc()`, mark for `abc` and `(`. cursor on `(` char return multiple marker
    // and we pick `(` by isGreaterThan check.
    return this.markerLayer
      .findMarkers({containsBufferPosition: point})
      .find(marker => marker.getBufferRange().end.isGreaterThan(point))
  }

  // Select occurrence marker bufferRange intersecting current selections.
  // - Return: true/false to indicate success or fail
  //
  // Do special handling for which occurrence range become lastSelection
  // e.g.
  //  - c(change): So that autocomplete+popup shows at original cursor position or near.
  //  - g U(upper-case): So that undo/redo can respect last cursor position.
  select(wise) {
    const isVisualMode = this.vimState.mode === "visual"
    const closestRangeIndexByOriginalSelection = new Map()
    const rangesToSelect = []
    const markersSelected = []
    const {editor} = this.vimState

    for (const selection of editor.getSelections()) {
      const markers = this.getMarkersIntersectsWithSelection(selection, isVisualMode)
      if (!markers.length) continue

      const ranges = markers.map(marker => marker.getBufferRange())
      markersSelected.push(...markers)
      // [HACK] Find closest occurrence range and move it to last item in ranges array.
      // Purpose of this is to make closest range become **last-selection**.
      // It is important to show autocomplete+ popup at proper position( popup shows up at last-selection ).
      // E.g. `c o f`(change occurrence in a-function) show autocomplete+ popup at closest occurrence.
      const closestRange = this.getClosestRangeForSelection(ranges, selection)
      ranges.splice(ranges.indexOf(closestRange), 1) // remove
      ranges.push(closestRange) // then push to last

      rangesToSelect.push(...ranges)

      const closestRangeIndex = rangesToSelect.indexOf(closestRange)
      // Remember connection between originalSelection and index of closestRange.
      // After select occurrence, selection is re-created, then we have to migrate mutation info using this info.
      closestRangeIndexByOriginalSelection.set(selection, closestRangeIndex)
    }

    if (rangesToSelect.length) {
      const reversed = editor.getLastSelection().isReversed()

      if (isVisualMode) {
        // To avoid selected occurrence ruined by normalization when disposing current submode to shift to new submode.
        this.vimState.modeManager.deactivate()
        this.vimState.submode = null
      }

      editor.setSelectedBufferRanges(rangesToSelect, {reversed})
      const selections = editor.getSelections()
      closestRangeIndexByOriginalSelection.forEach((closestRangeIndex, originalSelection) => {
        this.vimState.mutationManager.migrateMutation(originalSelection, selections[closestRangeIndex])
      })
      this.destroyMarkers(markersSelected)
      for (const $selection of this.vimState.swrap.getSelections(editor)) {
        $selection.saveProperties()
      }

      if (wise === "linewise") {
        // In linewise-occurence operation, what happens is here
        // 1. select linewise by applyWise(linewise)
        // 2. Observe selection.onDidDestroy to remember which selection is detroyed on mergeSelectionsOnSameRows in next step(step3).
        // 3. mergeSelectionsOnSameRows(), it merge selections in adjacent row, as a result, it destroy some selections.
        // 4. For destroyed selection, we migrate mutaion for destroyed selection with new selection info.

        for (const $selection of this.vimState.swrap.getSelections(editor)) {
          $selection.applyWise("linewise")
        }

        const {mutationsBySelection} = this.vimState.mutationManager
        const disposables = new CompositeDisposable()
        const rangeByMutation = new Map()
        const orphanedMutations = []

        for (const [selection, mutation] of mutationsBySelection) {
          // Need preserve range while it's alive.
          rangeByMutation.set(mutation, selection.getBufferRange())
          disposables.add(selection.onDidDestroy(() => orphanedMutations.push(mutation)))
        }
        editor.mergeSelectionsOnSameRows() // This destroy merged selection.
        disposables.dispose()

        for (const $selection of this.vimState.swrap.getSelections(editor)) {
          $selection.saveProperties()
        }

        const selections = editor.getSelections()
        for (const mutation of orphanedMutations) {
          mutationsBySelection.delete(mutation.selection)

          const range = rangeByMutation.get(mutation)
          // Selection contains original mutation range is merger selection we should migrate to.
          const selection = selections.find(selection => selection.getBufferRange().containsRange(range))
          mutation.selection = selection
          mutationsBySelection.set(selection, mutation)
        }
      }

      return true
    } else {
      return false
    }
  }

  // Which occurrence become lastSelection is determined by following order
  //  1. Occurrence under original cursor position
  //  2. forwarding in same row
  //  3. first occurrence in same row
  //  4. forwarding (wrap-end)
  getClosestRangeForSelection(ranges, selection) {
    const point = this.vimState.mutationManager.mutationsBySelection.get(selection).initialPoint

    const range = ranges.find(range => range.containsPoint(point))
    if (range) return range

    const rangesInSameRow = ranges.filter(range => range.start.row === point.row)
    if (rangesInSameRow.length) ranges = rangesInSameRow
    return ranges.find(range => range.start.isGreaterThan(point)) || ranges[0]
  }
}
<commit_msg>:memo: for confirmation msg<commit_after>const {Emitter, CompositeDisposable} = require("atom")
const {shrinkRangeEndToBeforeNewLine, collectRangeInBufferRow} = require("./utils")

module.exports = class OccurrenceManager {
  constructor(vimState) {
    this.vimState = vimState
    const {editor} = vimState

    vimState.onDidDestroy(() => this.destroy())

    this.emitter = new Emitter()
    this.patterns = []

    this.markerLayer = editor.addMarkerLayer()
    this.decorationLayer = editor.decorateMarkerLayer(this.markerLayer, {
      type: "highlight",
      class: "vim-mode-plus-occurrence-base",
    })

    // All maker create/destroy/css-update is done by reacting patters's change.
    // -------------------------
    this.onDidChangePatterns(({pattern, occurrenceType}) => {
      if (pattern) {
        this.markBufferRangeByPattern(pattern, occurrenceType)
      } else {
        this.clearMarkers()
      }
      this.updateEditorElement()
    })

    this.markerLayer.onDidUpdate(() => {
      this.destroyMarkers(this.getMarkers().filter(marker => !marker.isValid()))
    })
  }

  markBufferRangeByPattern(pattern, occurrenceType) {
    const markRange = range => this.markerLayer.markBufferRange(range, {invalidate: "inside"})
    const {editor} = this.vimState
    if (occurrenceType === "subword") {
      const subwordRegex = editor.getLastCursor().subwordRegExp()
      const cache = {}
      editor.scan(pattern, ({range}) => {
        const {row} = range.start
        if (!cache[row]) cache[row] = collectRangeInBufferRow(editor, row, subwordRegex)
        if (cache[row].some(subwordRange => subwordRange.isEqual(range))) markRange(range)
      })
    } else {
      const ranges = []
      editor.scan(pattern, ({range}) => ranges.push(range))
      if (this.canCreateMarkersForLength(ranges.length)) ranges.forEach(markRange)
    }
  }

  canCreateMarkersForLength(length) {
    const threshold = this.vimState.getConfig("confirmThresholdOnOccurrenceOperation")
    if (threshold >= length) {
      return true
    } else {
      const options = {
        message: `Too many(${length}) occurrences. Do you want to continue?`,
        detailedMessage: `If you want increase threshold(current: ${threshold}), change "Confirm Threshold On Create Preset Occurrences" configuration.`,
        buttons: ["Continue", "Cancel"],
      }
      return atom.confirm(options) === 0
    }
  }

  updateEditorElement() {
    this.vimState.editorElement.classList.toggle("has-occurrence", this.hasMarkers())
  }

  // Callback get passed following object
  // - pattern: can be undefined on reset event
  onDidChangePatterns(fn) {
    return this.emitter.on("did-change-patterns", fn)
  }

  destroy() {
    this.decorationLayer.destroy()
    this.markerLayer.destroy()
  }

  // Patterns
  hasPatterns() {
    return this.patterns.length > 0
  }

  resetPatterns() {
    this.patterns = []
    this.emitter.emit("did-change-patterns", {})
  }

  addPattern(pattern = null, {reset, occurrenceType = "base"} = {}) {
    if (reset) this.clearMarkers()

    this.patterns.push(pattern)
    this.emitter.emit("did-change-patterns", {pattern, occurrenceType})
  }

  saveLastPattern(occurrenceType) {
    this.vimState.globalState.set("lastOccurrencePattern", this.buildPattern())
    this.vimState.globalState.set("lastOccurrenceType", occurrenceType)
  }

  // Return regex representing final pattern.
  // Used to cache final pattern to each instance of operator so that we can
  // repeat recorded operation by `.`.
  // Pattern can be added interactively one by one, but we save it as union pattern.
  buildPattern() {
    return new RegExp(this.patterns.map(regex => regex.source).join("|"), "g")
  }

  // Markers
  // -------------------------
  clearMarkers() {
    this.markerLayer.clear()
  }

  destroyMarkers(markers) {
    markers.forEach(marker => marker.destroy())
    // whenerver we destroy marker, we should sync `has-occurrence` scope in marker state..
    this.updateEditorElement()
  }

  hasMarkers() {
    return this.markerLayer.getMarkerCount() > 0
  }

  getMarkers() {
    return this.markerLayer.getMarkers()
  }

  getMarkerBufferRanges() {
    return this.markerLayer.getMarkers().map(marker => marker.getBufferRange())
  }

  getMarkerCount() {
    return this.markerLayer.getMarkerCount()
  }

  // Return occurrence markers intersecting given ranges
  getMarkersIntersectsWithSelection(selection, exclusive = false) {
    // findmarkers()'s intersectsBufferRange param have no exclusive control
    // So need extra check to filter out unwanted marker.
    // But basically I should prefer findMarker since It's fast than iterating
    // whole markers manually.
    const range = shrinkRangeEndToBeforeNewLine(selection.getBufferRange())
    return this.markerLayer
      .findMarkers({intersectsBufferRange: range})
      .filter(marker => range.intersectsWith(marker.getBufferRange(), exclusive))
  }

  getMarkerAtPoint(point) {
    // We have to check all returned marker until found, since we do aditional marker validation.
    // e.g. For text `abc()`, mark for `abc` and `(`. cursor on `(` char return multiple marker
    // and we pick `(` by isGreaterThan check.
    return this.markerLayer
      .findMarkers({containsBufferPosition: point})
      .find(marker => marker.getBufferRange().end.isGreaterThan(point))
  }

  // Select occurrence marker bufferRange intersecting current selections.
  // - Return: true/false to indicate success or fail
  //
  // Do special handling for which occurrence range become lastSelection
  // e.g.
  //  - c(change): So that autocomplete+popup shows at original cursor position or near.
  //  - g U(upper-case): So that undo/redo can respect last cursor position.
  select(wise) {
    const isVisualMode = this.vimState.mode === "visual"
    const closestRangeIndexByOriginalSelection = new Map()
    const rangesToSelect = []
    const markersSelected = []
    const {editor} = this.vimState

    for (const selection of editor.getSelections()) {
      const markers = this.getMarkersIntersectsWithSelection(selection, isVisualMode)
      if (!markers.length) continue

      const ranges = markers.map(marker => marker.getBufferRange())
      markersSelected.push(...markers)
      // [HACK] Find closest occurrence range and move it to last item in ranges array.
      // Purpose of this is to make closest range become **last-selection**.
      // It is important to show autocomplete+ popup at proper position( popup shows up at last-selection ).
      // E.g. `c o f`(change occurrence in a-function) show autocomplete+ popup at closest occurrence.
      const closestRange = this.getClosestRangeForSelection(ranges, selection)
      ranges.splice(ranges.indexOf(closestRange), 1) // remove
      ranges.push(closestRange) // then push to last

      rangesToSelect.push(...ranges)

      const closestRangeIndex = rangesToSelect.indexOf(closestRange)
      // Remember connection between originalSelection and index of closestRange.
      // After select occurrence, selection is re-created, then we have to migrate mutation info using this info.
      closestRangeIndexByOriginalSelection.set(selection, closestRangeIndex)
    }

    if (rangesToSelect.length) {
      const reversed = editor.getLastSelection().isReversed()

      if (isVisualMode) {
        // To avoid selected occurrence ruined by normalization when disposing current submode to shift to new submode.
        this.vimState.modeManager.deactivate()
        this.vimState.submode = null
      }

      editor.setSelectedBufferRanges(rangesToSelect, {reversed})
      const selections = editor.getSelections()
      closestRangeIndexByOriginalSelection.forEach((closestRangeIndex, originalSelection) => {
        this.vimState.mutationManager.migrateMutation(originalSelection, selections[closestRangeIndex])
      })
      this.destroyMarkers(markersSelected)
      for (const $selection of this.vimState.swrap.getSelections(editor)) {
        $selection.saveProperties()
      }

      if (wise === "linewise") {
        // In linewise-occurence operation, what happens is here
        // 1. select linewise by applyWise(linewise)
        // 2. Observe selection.onDidDestroy to remember which selection is detroyed on mergeSelectionsOnSameRows in next step(step3).
        // 3. mergeSelectionsOnSameRows(), it merge selections in adjacent row, as a result, it destroy some selections.
        // 4. For destroyed selection, we migrate mutaion for destroyed selection with new selection info.

        for (const $selection of this.vimState.swrap.getSelections(editor)) {
          $selection.applyWise("linewise")
        }

        const {mutationsBySelection} = this.vimState.mutationManager
        const disposables = new CompositeDisposable()
        const rangeByMutation = new Map()
        const orphanedMutations = []

        for (const [selection, mutation] of mutationsBySelection) {
          // Need preserve range while it's alive.
          rangeByMutation.set(mutation, selection.getBufferRange())
          disposables.add(selection.onDidDestroy(() => orphanedMutations.push(mutation)))
        }
        editor.mergeSelectionsOnSameRows() // This destroy merged selection.
        disposables.dispose()

        for (const $selection of this.vimState.swrap.getSelections(editor)) {
          $selection.saveProperties()
        }

        const selections = editor.getSelections()
        for (const mutation of orphanedMutations) {
          mutationsBySelection.delete(mutation.selection)

          const range = rangeByMutation.get(mutation)
          // Selection contains original mutation range is merger selection we should migrate to.
          const selection = selections.find(selection => selection.getBufferRange().containsRange(range))
          mutation.selection = selection
          mutationsBySelection.set(selection, mutation)
        }
      }

      return true
    } else {
      return false
    }
  }

  // Which occurrence become lastSelection is determined by following order
  //  1. Occurrence under original cursor position
  //  2. forwarding in same row
  //  3. first occurrence in same row
  //  4. forwarding (wrap-end)
  getClosestRangeForSelection(ranges, selection) {
    const point = this.vimState.mutationManager.mutationsBySelection.get(selection).initialPoint

    const range = ranges.find(range => range.containsPoint(point))
    if (range) return range

    const rangesInSameRow = ranges.filter(range => range.start.row === point.row)
    if (rangesInSameRow.length) ranges = rangesInSameRow
    return ranges.find(range => range.start.isGreaterThan(point)) || ranges[0]
  }
}
<|endoftext|>


<commit_before>const TxModel     = require('../../models/transaction').Transaction;
const InputModel  = require('../../models/transaction').Input;
const OutputModel = require('../../models/transaction').Output;
const config      = require('../../config');
const util        = require('../../lib/util');
const logger      = require('../logger');

function parse(entry, txs) {
  txs.forEach((tx) => {
    const txHash    = util.revHex(tx.hash().toString('hex'));
    const blockHash = util.revHex(entry.hash);
    const json = tx.toJSON();

    const t = new TxModel({
      hash:        json.hash,
      witnessHash: json.witnessHash,
      fee:         json.fee,
      rate:        json.rate,
      ps:          json.ps,
      height:      entry.height,
      block:       entry.hash,
      ts:          entry.ts,
      date:        json.date,
      index:       json.index,
      version:     json.version,
      flag:        json.flag,
      inputs:      tx.inputs.map((input) => {
        const inputJSON = input.toJSON();
        return new InputModel({
          prevout:  inputJSON.prevout,
          script:   inputJSON.script,
          witness:  inputJSON.witness,
          sequence: inputJSON.sequence,
          address:  inputJSON.address,
        });
      }),
      outputs: tx.outputs.map((output) => {
        const outputJSON = output.toJSON();
        return new OutputModel({
          address: outputJSON.address,
          script:  outputJSON.script,
          value:   outputJSON.value,
        });
      }),
      lockTime: json.locktime,
      chain: config.bcoin.network,
    });
    t.save((err) => {
      if (err) {
        logger.log('error', err.message);
      }
    });
  });
}

module.exports = {
  parse,
};
<commit_msg>fix txJSON naming.<commit_after>const TxModel     = require('../../models/transaction').Transaction;
const InputModel  = require('../../models/transaction').Input;
const OutputModel = require('../../models/transaction').Output;
const config      = require('../../config');
const util        = require('../../lib/util');
const logger      = require('../logger');

function parse(entry, txs) {
  txs.forEach((tx) => {
    const txHash    = util.revHex(tx.hash().toString('hex'));
    const blockHash = util.revHex(entry.hash);
    const txJSON = tx.totxJSON();

    const t = new TxModel({
      hash:        txJSON.hash,
      witnessHash: txJSON.witnessHash,
      fee:         txJSON.fee,
      rate:        txJSON.rate,
      ps:          txJSON.ps,
      height:      entry.height,
      block:       entry.hash,
      ts:          entry.ts,
      date:        txJSON.date,
      index:       txJSON.index,
      version:     txJSON.version,
      flag:        txJSON.flag,
      inputs:      tx.inputs.map((input) => {
        const inputJSON = input.toJSON();
        return new InputModel({
          prevout:  inputJSON.prevout,
          script:   inputJSON.script,
          witness:  inputJSON.witness,
          sequence: inputJSON.sequence,
          address:  inputJSON.address,
        });
      }),
      outputs: tx.outputs.map((output) => {
        const outputJSON = output.toJSON();
        return new OutputModel({
          address: outputJSON.address,
          script:  outputJSON.script,
          value:   outputJSON.value,
        });
      }),
      lockTime: txJSON.locktime,
      chain: config.bcoin.network,
    });
    t.save((err) => {
      if (err) {
        logger.log('error', err.message);
      }
    });
  });
}

module.exports = {
  parse,
};
<|endoftext|>


<commit_before>/* global describe, it, before, beforeEach, after, afterEach */
import HtmlReady from './HtmlReady';

beforeEach(() => {
    global.$STM_Config = {};
});

describe('htmlready', () => {
    it('should return an empty string if input cannot be parsed', () => {
        const teststring = 'teststring lol'; // this string causes the xmldom parser to fail & error out
        expect(HtmlReady(teststring).html).toEqual('');
    });

    it('should allow links where the text portion and href contains steemit.com', () => {
        const dirty =
            '<xml xmlns="http://www.w3.org/1999/xhtml">https://steemit.com/signup</xml>';
        const res = HtmlReady(dirty).html;
        expect(res).toEqual(dirty);
    });

    it('should allow in-page links ', () => {
        const dirty =
            '<xml xmlns="http://www.w3.org/1999/xhtml">a link location</xml>';
        const res = HtmlReady(dirty).html;
        expect(res).toEqual(dirty);

        const externalDomainDirty =
            '<xml xmlns="http://www.w3.org/1999/xhtml">Another website\'s apple section</xml>';
        const externalDomainResult = HtmlReady(externalDomainDirty).html;
        expect(externalDomainResult).toEqual(externalDomainDirty);
    });

    it('should not allow links where the text portion contains steemit.com but the link does not', () => {
        // There isn't an easy way to mock counterpart, even with proxyquire, so we just test for the missing translation message -- ugly but ok

        const dirty =
            '<xml xmlns="http://www.w3.org/1999/xhtml">https://steemit.com/signup</xml>';
        const cleansed =
            '<xml xmlns="http://www.w3.org/1999/xhtml">
https://steemit.com/signup / https://steamit.com/signup
</xml>';
        const res = HtmlReady(dirty).html;
        expect(res).toEqual(cleansed);

        const cased =
            '<xml xmlns="http://www.w3.org/1999/xhtml">https://Steemit.com/signup</xml>';
        const cleansedcased =
            '<xml xmlns="http://www.w3.org/1999/xhtml">
https://Steemit.com/signup / https://steamit.com/signup
</xml>';
        const rescased = HtmlReady(cased).html;
        expect(rescased).toEqual(cleansedcased);

        const withuser =
            '<xml xmlns="http://www.w3.org/1999/xhtml">https://official@steemit.com/signup</xml>';
        const cleansedwithuser =
            '<xml xmlns="http://www.w3.org/1999/xhtml">
https://official@steemit.com/signup / https://steamit.com/signup
</xml>';
        const reswithuser = HtmlReady(withuser).html;
        expect(reswithuser).toEqual(cleansedwithuser);

        const noendingslash =
            '<xml xmlns="http://www.w3.org/1999/xhtml">https://steemit.com</xml>';
        const cleansednoendingslash =
            '<xml xmlns="http://www.w3.org/1999/xhtml">
https://steemit.com / https://steamit.com
</xml>';
        const resnoendingslash = HtmlReady(noendingslash).html;
        expect(resnoendingslash).toEqual(cleansednoendingslash);

        //make sure extra-domain in-page links are also caught by our phishy link scan.
        const domainInpage =
            '<xml xmlns="http://www.w3.org/1999/xhtml">https://steemit.com</xml>';
        const cleanDomainInpage =
            '<xml xmlns="http://www.w3.org/1999/xhtml">
https://steemit.com / https://steamit.com#really-evil-inpage-component
</xml>';
        const resDomainInpage = HtmlReady(domainInpage).html;
        expect(resDomainInpage).toEqual(cleanDomainInpage);

        //misleading in-page links should also be caught
        const inpage =
            '<xml xmlns="http://www.w3.org/1999/xhtml">Go down lower for https://steemit.com info!</xml>';
        const cleanInpage =
            '<xml xmlns="http://www.w3.org/1999/xhtml">
Go down lower for https://steemit.com info! / #https://steamit.com/unlikelyinpagelink
</xml>';
        const resinpage = HtmlReady(inpage).html;
        expect(resinpage).toEqual(cleanInpage);

        const noprotocol =
            '<xml xmlns="http://www.w3.org/1999/xhtml">for a good time, visit steemit.com today</xml>';
        const cleansednoprotocol =
            '<xml xmlns="http://www.w3.org/1999/xhtml">
for a good time, visit steemit.com today / https://steamit.com/
</xml>';
        const resnoprotocol = HtmlReady(noprotocol).html;
        expect(resnoprotocol).toEqual(cleansednoprotocol);
    });

    it('should allow more than one link per post', () => {
        const somanylinks =
            '<xml xmlns="http://www.w3.org/1999/xhtml">https://foo.com and https://blah.com</xml>';
        const htmlified =
            '<xml xmlns="http://www.w3.org/1999/xhtml">https://foo.com and https://blah.com</xml>';
        const res = HtmlReady(somanylinks).html;
        expect(res).toEqual(htmlified);
    });

    it('should link usernames', () => {
        const textwithmentions =
            '<xml xmlns="http://www.w3.org/1999/xhtml">@username (@a1b2, whatever</xml>';
        const htmlified =
            '<xml xmlns="http://www.w3.org/1999/xhtml">@username (@a1b2, whatever</xml>';
        const res = HtmlReady(textwithmentions).html;
        expect(res).toEqual(htmlified);
    });

    it('should detect only valid mentions', () => {
        const textwithmentions =
            '@abc @xx (@aaa1) @_x @eee, @fff! https://x.com/@zzz/test';
        const res = HtmlReady(textwithmentions, { mutate: false });
        const usertags = Array.from(res.usertags).join(',');
        expect(usertags).toEqual('abc,aaa1,eee,fff');
    });

    it('should not link usernames at the front of linked text', () => {
        const nameinsidelinkfirst =
            '<xml xmlns="http://www.w3.org/1999/xhtml">@hihi</xml>';
        const htmlified =
            '<xml xmlns="http://www.w3.org/1999/xhtml">@hihi</xml>';
        const res = HtmlReady(nameinsidelinkfirst).html;
        expect(res).toEqual(htmlified);
    });

    it('should not link usernames in the middle of linked text', () => {
        const nameinsidelinkmiddle =
            '<xml xmlns="http://www.w3.org/1999/xhtml">hi @hihi</xml>';
        const htmlified =
            '<xml xmlns="http://www.w3.org/1999/xhtml">hi @hihi</xml>';
        const res = HtmlReady(nameinsidelinkmiddle).html;
        expect(res).toEqual(htmlified);
    });
});
<commit_msg>add failing test for #2567<commit_after>/* global describe, it, before, beforeEach, after, afterEach */
import HtmlReady from './HtmlReady';

beforeEach(() => {
    global.$STM_Config = {};
});

describe('htmlready', () => {
    it('should return an empty string if input cannot be parsed', () => {
        const teststring = 'teststring lol'; // this string causes the xmldom parser to fail & error out
        expect(HtmlReady(teststring).html).toEqual('');
    });

    it('should allow links where the text portion and href contains steemit.com', () => {
        const dirty =
            '<xml xmlns="http://www.w3.org/1999/xhtml">https://steemit.com/signup</xml>';
        const res = HtmlReady(dirty).html;
        expect(res).toEqual(dirty);
    });

    it('should allow in-page links ', () => {
        const dirty =
            '<xml xmlns="http://www.w3.org/1999/xhtml">a link location</xml>';
        const res = HtmlReady(dirty).html;
        expect(res).toEqual(dirty);

        const externalDomainDirty =
            '<xml xmlns="http://www.w3.org/1999/xhtml">Another website\'s apple section</xml>';
        const externalDomainResult = HtmlReady(externalDomainDirty).html;
        expect(externalDomainResult).toEqual(externalDomainDirty);
    });

    it('should not allow links where the text portion contains steemit.com but the link does not', () => {
        // There isn't an easy way to mock counterpart, even with proxyquire, so we just test for the missing translation message -- ugly but ok

        const dirty =
            '<xml xmlns="http://www.w3.org/1999/xhtml">https://steemit.com/signup</xml>';
        const cleansed =
            '<xml xmlns="http://www.w3.org/1999/xhtml">
https://steemit.com/signup / https://steamit.com/signup
</xml>';
        const res = HtmlReady(dirty).html;
        expect(res).toEqual(cleansed);

        const cased =
            '<xml xmlns="http://www.w3.org/1999/xhtml">https://Steemit.com/signup</xml>';
        const cleansedcased =
            '<xml xmlns="http://www.w3.org/1999/xhtml">
https://Steemit.com/signup / https://steamit.com/signup
</xml>';
        const rescased = HtmlReady(cased).html;
        expect(rescased).toEqual(cleansedcased);

        const withuser =
            '<xml xmlns="http://www.w3.org/1999/xhtml">https://official@steemit.com/signup</xml>';
        const cleansedwithuser =
            '<xml xmlns="http://www.w3.org/1999/xhtml">
https://official@steemit.com/signup / https://steamit.com/signup
</xml>';
        const reswithuser = HtmlReady(withuser).html;
        expect(reswithuser).toEqual(cleansedwithuser);

        const noendingslash =
            '<xml xmlns="http://www.w3.org/1999/xhtml">https://steemit.com</xml>';
        const cleansednoendingslash =
            '<xml xmlns="http://www.w3.org/1999/xhtml">
https://steemit.com / https://steamit.com
</xml>';
        const resnoendingslash = HtmlReady(noendingslash).html;
        expect(resnoendingslash).toEqual(cleansednoendingslash);

        //make sure extra-domain in-page links are also caught by our phishy link scan.
        const domainInpage =
            '<xml xmlns="http://www.w3.org/1999/xhtml">https://steemit.com</xml>';
        const cleanDomainInpage =
            '<xml xmlns="http://www.w3.org/1999/xhtml">
https://steemit.com / https://steamit.com#really-evil-inpage-component
</xml>';
        const resDomainInpage = HtmlReady(domainInpage).html;
        expect(resDomainInpage).toEqual(cleanDomainInpage);

        // anchor links including steemit.com should be allowed
        const inpage =
            '<xml xmlns="http://www.w3.org/1999/xhtml">Go down lower for https://steemit.com info!</xml>';
        const cleanInpage =
            '<xml xmlns="http://www.w3.org/1999/xhtml">Go down lower for https://steemit.com info!</xml>';
        const resinpage = HtmlReady(inpage).html;
        expect(resinpage).toEqual(cleanInpage);

        const noprotocol =
            '<xml xmlns="http://www.w3.org/1999/xhtml">for a good time, visit steemit.com today</xml>';
        const cleansednoprotocol =
            '<xml xmlns="http://www.w3.org/1999/xhtml">
for a good time, visit steemit.com today / https://steamit.com/
</xml>';
        const resnoprotocol = HtmlReady(noprotocol).html;
        expect(resnoprotocol).toEqual(cleansednoprotocol);
    });

    it('should allow more than one link per post', () => {
        const somanylinks =
            '<xml xmlns="http://www.w3.org/1999/xhtml">https://foo.com and https://blah.com</xml>';
        const htmlified =
            '<xml xmlns="http://www.w3.org/1999/xhtml">https://foo.com and https://blah.com</xml>';
        const res = HtmlReady(somanylinks).html;
        expect(res).toEqual(htmlified);
    });

    it('should link usernames', () => {
        const textwithmentions =
            '<xml xmlns="http://www.w3.org/1999/xhtml">@username (@a1b2, whatever</xml>';
        const htmlified =
            '<xml xmlns="http://www.w3.org/1999/xhtml">@username (@a1b2, whatever</xml>';
        const res = HtmlReady(textwithmentions).html;
        expect(res).toEqual(htmlified);
    });

    it('should detect only valid mentions', () => {
        const textwithmentions =
            '@abc @xx (@aaa1) @_x @eee, @fff! https://x.com/@zzz/test';
        const res = HtmlReady(textwithmentions, { mutate: false });
        const usertags = Array.from(res.usertags).join(',');
        expect(usertags).toEqual('abc,aaa1,eee,fff');
    });

    it('should not link usernames at the front of linked text', () => {
        const nameinsidelinkfirst =
            '<xml xmlns="http://www.w3.org/1999/xhtml">@hihi</xml>';
        const htmlified =
            '<xml xmlns="http://www.w3.org/1999/xhtml">@hihi</xml>';
        const res = HtmlReady(nameinsidelinkfirst).html;
        expect(res).toEqual(htmlified);
    });

    it('should not link usernames in the middle of linked text', () => {
        const nameinsidelinkmiddle =
            '<xml xmlns="http://www.w3.org/1999/xhtml">hi @hihi</xml>';
        const htmlified =
            '<xml xmlns="http://www.w3.org/1999/xhtml">hi @hihi</xml>';
        const res = HtmlReady(nameinsidelinkmiddle).html;
        expect(res).toEqual(htmlified);
    });
});
<|endoftext|>


<commit_before>var BasicCourseTable = function () {
    this.tableId = '#basic-course-table';

    this.table = $(this.tableId).DataTable({
        "dom": '<"rankings-control">frtip',
        "sAjaxSource": "/sc/all/all/all/show.2/?order=enrolled.desc",
        "sAjaxDataProp": "data",
        "order": [],
        "columns": [
            {data: 0},
            {data: 6},
            null,
            {data: 3},
            {data: 2},
            {data: 1},
        ],
        "columnDefs": [
            {
                "targets": 2,
                "render": function (data, type, row, meta){
                    return dataviva.course_sc[row[6]].name
                }
            },
        ],
        "deferRender": true,
        "scrollY": 500,
        "scrollCollapse": true,
        "scroller": true,
        initComplete: function () {
            var select = $("<select></select>").attr("id", 'year-selector').addClass("year-selector form-control"),
                buttons = $("

").addClass("btn-group");

            var course_sc_2 = dataviva.dictionary['course_sc_2'],
                course_sc_5 = dataviva.dictionary['course_sc_5'],
                year = dataviva.dictionary['year'];

            select.append( $('<option value="">'+year+'</option>') );
            buttons.append($("<button>"+course_sc_2+"</button>").attr("id", 'basic-course-fields').addClass("btn btn-white"));
            buttons.append($("<button>"+course_sc_5+"</button>").attr("id", 'basic-course-courses').addClass("btn btn-white"));

            $('.rankings-content .rankings-control').append(buttons);
            $('.rankings-content .rankings-control').append(select);

            basicCourseTable.table
                .column( 0 )
                .cache( 'search' )
                .sort()
                .unique()
                .each( function ( d ) {
                    select.append( $('<option value="'+d+'">'+d+'</option>') );
                } );

            select.on( 'change', function () {
               basicCourseTable.table
                    .column( 0 )
                    .search( $(this).val() )
                    .draw();
            });

            $('#basic-course-table_filter input').removeClass('input-sm');
            $('#basic-course-table_filter').addClass('pull-right');

            $('#basic-course-fields').click(function() {
                basicCourseTable.table.ajax.url("/sc/all/all/all/show.2/?order=enrolled.desc").load();
            });

            $('#basic-course-courses').click(function() {
                basicCourseTable.table.ajax.url("/sc/all/all/all/show.5/?order=enrolled.desc").load();
            });
        }
    });
};

$(document).ready(function() {
    dataviva.requireAttrs(['course_sc'], function() {
        window.basicCourseTable = new BasicCourseTable();
    });
});<commit_msg>Translates DataTables<commit_after>var BasicCourseTable = function () {
    this.tableId = '#basic-course-table';

    this.table = $(this.tableId).DataTable({
        "dom": '<"rankings-control">frtip',
        "sAjaxSource": "/sc/all/all/all/show.2/?order=enrolled.desc",
        "sAjaxDataProp": "data",
        "order": [],
        "columns": [
            {data: 0},
            {data: 6},
            null,
            {data: 3},
            {data: 2},
            {data: 1},
        ],
        "columnDefs": [
            {
                "targets": 2,
                "render": function (data, type, row, meta){
                    return dataviva.course_sc[row[6]].name
                }
            },
        ],
        "deferRender": true,
        "language": {
            "info": "Mostrando _START_ a _END_ de _TOTAL_ resultados",
            "search": "Busca:"
        },
        "scrollY": 500,
        "scrollCollapse": true,
        "scroller": true,
        initComplete: function () {
            var select = $("<select></select>").attr("id", 'year-selector').addClass("year-selector form-control"),
                buttons = $("

").addClass("btn-group");

            var course_sc_2 = dataviva.dictionary['course_sc_2'],
                course_sc_5 = dataviva.dictionary['course_sc_5'],
                year = dataviva.dictionary['year'];

            select.append( $('<option value="">'+year+'</option>') );
            buttons.append($("<button>"+course_sc_2+"</button>").attr("id", 'basic-course-fields').addClass("btn btn-white"));
            buttons.append($("<button>"+course_sc_5+"</button>").attr("id", 'basic-course-courses').addClass("btn btn-white"));

            $('.rankings-content .rankings-control').append(buttons);
            $('.rankings-content .rankings-control').append(select);

            basicCourseTable.table
                .column( 0 )
                .cache( 'search' )
                .sort()
                .unique()
                .each( function ( d ) {
                    select.append( $('<option value="'+d+'">'+d+'</option>') );
                } );

            select.on( 'change', function () {
               basicCourseTable.table
                    .column( 0 )
                    .search( $(this).val() )
                    .draw();
            });

            $('#basic-course-table_filter input').removeClass('input-sm');
            $('#basic-course-table_filter').addClass('pull-right');

            $('#basic-course-fields').click(function() {
                basicCourseTable.table.ajax.url("/sc/all/all/all/show.2/?order=enrolled.desc").load();
            });

            $('#basic-course-courses').click(function() {
                basicCourseTable.table.ajax.url("/sc/all/all/all/show.5/?order=enrolled.desc").load();
            });
        }
    });
};

$(document).ready(function() {
    dataviva.requireAttrs(['course_sc'], function() {
        window.basicCourseTable = new BasicCourseTable();
    });
});<|endoftext|>


<commit_before>'use strict';

module.exports = ({ Plugin, types: t }) => {

  const VOID_0 = t.unaryExpression('void', t.literal(0), true);

  return new Plugin('simplify', {
    visitor: {
      // undefined -> void 0
      ReferencedIdentifier(node) {
        if (node.name === 'undefined') {
          return VOID_0;
        }
      },

      // { 'foo': 'bar' } -> { foo: 'bar' }
      Property: {
        exit(node) {
          let key = node.key;
          if (t.isLiteral(key) && t.isValidIdentifier(key.value)) {
            // 'foo': 'bar' -> foo: 'bar'
            node.key = t.identifier(key.value);
            node.computed = false;
          }
        },
      },

      // foo['bar'] -> foo.bar
      MemberExpression: {
        exit(node) {
          let prop = node.property;
          if (node.computed && t.isLiteral(prop) &&
            t.isValidIdentifier(prop.value)) {
            // foo['bar'] => foo.bar
            node.property = t.identifier(prop.value);
            node.computed = false;
          }
        },
      },

      // Number(foo) -> +foo
      CallExpression(node, parent) {
        if (t.isIdentifier(node.callee, { name: 'Number' }) &&
          node.arguments.length === 1) {
          return t.unaryExpression('+', node.arguments[0], true);
        }

        if (t.isIdentifier(node.callee, { name: 'String' }) &&
          node.arguments.length === 1) {
          return t.binaryExpression('+', node.arguments[0], t.literal(''));
        }

        if (t.isFunctionExpression(node.callee)
            && (t.isExpressionStatement(parent) || t.isSequenceExpression(parent))) {
          return t.callExpression(
            t.unaryExpression('!', node.callee),
            node.arguments
          );
        }
      },

      // !foo && bar -> foo || bar
      LogicalExpression(node) {
        if (node.operator === '&&' &&
          t.isUnaryExpression(node.left, { operator: '!' })) {
          node.operator = '||';
          node.left = node.left.argument;
        }
      },

      // shorten booleans to a negation
      // true -> !0
      // false -> !1
      Literal(node) {
        if (typeof node.value === 'boolean') {
          return t.unaryExpression('!', t.literal(+!node.value), true);
        }
      },

      BinaryExpression: {
        enter: [
          // flip comparisons with a pure right hand value, this ensures
          // consistency with comparisons and increases the length of
          // strings that gzip can match
          // typeof blah === 'function' -> 'function' === typeof blah
          function (node) {
            if (t.EQUALITY_BINARY_OPERATORS.indexOf(node.operator) >= 0 &&
              this.get('right').isPure()) {
              let left = node.left;
              node.left = node.right;
              node.right = left;
            }
          },

          // simplify comparison operations if we're 100% certain
          // that each value will always be of the same type
          function (node) {
            let op = node.operator;
            if (op !== '===' && op !== '!==') {
              return;
            }

            let left  = this.get('left');
            let right = this.get('right');
            if (left.baseTypeStrictlyMatches(right)) {
              node.operator = node.operator.slice(0, -1);
            }
          },
        ],
      },

      // !foo ? 'foo' : 'bar' -> foo ? 'bar' : 'foo'
      // foo !== 'lol' ? 'foo' : 'bar' -> foo === 'lol' ? 'bar' : 'foo'
      ConditionalExpression(node) {
        flipNegation(node);
      },

/* This is just removing functions for some reason?
      // hoist all function declarations
      Block(node) {
        let top = [];
        let bottom = [];

        for (let i = 0; i < node.body.length; i++) {
          let bodyNode = node.body[i];
          if (t.isFunctionDeclaration(bodyNode)) {
            top.push(bodyNode);
          } else {
            bottom.push(bodyNode);
          }
        }

        node.body = top.concat(bottom);
      },
*/

      // concat
      VariableDeclaration: {
        enter: [
          // concat variables of the same kind with their siblings
          function (node) {
            if (!this.inList) {
              return;
            }

            while (true) {
              let sibling = this.getSibling(this.key + 1);
              if (!sibling.isVariableDeclaration({ kind: node.kind })) {
                break;
              }

              node.declarations = node.declarations.concat(
                sibling.node.declarations
              );
              sibling.dangerouslyRemove();
            }
          },

          // concat variable declarations next to for loops with it's
          // initialisers if they're of the same variable kind
          function (node) {
            if (!this.inList) {
              return;
            }

            let next = this.getSibling(this.key + 1);
            if (!next.isForStatement()) {
              return;
            }

            let init = next.get('init');
            if (!init.isVariableDeclaration({ kind: node.kind })) {
              return;
            }

            init.node.declarations = node.declarations.concat(
              init.node.declarations
            );
            this.dangerouslyRemove();
          },
        ],
      },

      // turn a for loop block block with single statement
      // loops into just the single statement
      For(node, parent, scope) {
        let block = node.body;
        if (!block || !t.isBlockStatement(block)) {
          return;
        }

        let body = block.body;
        if (body.length !== 1) {
          return;
        }

        let first = body[0];
        node.body = first;
      },

      ForStatement(node, parent, scope) {
        if (!this.inList || (node.init && !t.isExpression(node.init))) {
          return;
        }

        const prev = this.getSibling(this.key - 1);
        let consumed = false;
        if (prev.isVariableDeclaration()) {
          if (!node.init) {
            node.init = prev.node;
            consumed = true;
          }
        } else if (prev.isExpressionStatement()) {
          const expr = prev.node.expression;
          if (node.init) {
            if (t.isSequenceExpression(expr)) {
              expr.expressions.push(node.init);
              node.init = expr;
            } else {
              node.init = t.sequenceExpression([expr, node.init]);
            }
          } else {
            node.init = expr;
          }
          consumed = true;
        }
        if (consumed) {
          prev.dangerouslyRemove();
        }
      },

      Program(node) {
        const statements = toMultipleSequenceExpressions(node.body);
        if (!statements.length) {
          return;
        }
        node.body = statements;
      },

      BlockStatement(node, parent, scope) {
        const statements = toMultipleSequenceExpressions(node.body);
        if (!statements.length) {
          return;
        }

        if (statements.length > 1 || (t.isFunction(parent) && node === parent.body) ||
            t.isTryStatement(parent) || t.isCatchClause(parent)) {
          return t.blockStatement(statements);
        }

        if (statements.length) {
          return statements[0];
        }
      },

      // Try to merge previous statements into a sequence
      ReturnStatement(node, parent, scope) {
        if (!this.inList) {
          return;
        }

        const prev = this.getSibling(this.key - 1);
        if (!prev.isExpressionStatement()) {
          return;
        }

        let seq = prev.node.expression;
        if (node.argument) {
          if (t.isSequenceExpression(seq)) {
            seq.expressions.push(node.argument);
          } else {
            seq = t.sequenceExpression([seq, node.argument]);
          }
        } else {
          if (t.isSequenceExpression(seq)) {
            const lastExpr = seq.expressions[seq.expressions.length - 1];
            seq.expressions[seq.expressions.length - 1] = t.unaryExpression('void', lastExpr);
          } else {
            seq = t.unaryExpression('void', seq);
          }
        }

        if (seq) {
          prev.dangerouslyRemove();
          return t.returnStatement(seq);
        }
      },
      // turn blocked ifs into single statements
      IfStatement: {
        exit: [
          function(node) {
            coerceIf('consequent');
            coerceIf('alternate');
            flipNegation(node);

            // No alternate, make into a guarded expression
            if (node.consequent && !node.alternate &&
              node.consequent.type === 'ExpressionStatement' &&
              !this.isCompletionRecord()) {
                return t.expressionStatement(
                  t.logicalExpression('&&', node.test, node.consequent.expression)
                );
            }

            // Easy, both are expressions, turn into ternary
            if (t.isExpressionStatement(node.consequent) &&
              t.isExpressionStatement(node.alternate)) {
                return t.conditionalExpression(
                  node.test, node.consequent.expression, node.alternate.expression
                );
            }

            // Alternate and consequent are returns turn into a return conditional
            if (t.isReturnStatement(node.consequent)
                && t.isReturnStatement(node.alternate)
                && !this.getSibling(this.key + 1).node) {

                  return t.returnStatement(
                    t.conditionalExpression(
                      node.test,
                      node.consequent.argument || VOID_0,
                      node.alternate.argument
                    )
                  );
            }

            const next = this.getSibling(this.key + 1);

            // No alternate but the next statement is a return
            // also turn into a return conditional
            if (t.isReturnStatement(node.consequent) &&
              !node.alternate && next.isReturnStatement()) {
                const nextArg = next.node.argument;
                next.dangerouslyRemove();
                return t.returnStatement(
                  t.conditionalExpression(
                    node.test, node.consequent.argument || VOID_0, nextArg
                  )
                );
            }

            // Next is the last expression, turn into a return while void'ing the exprs
            if (!this.getSibling(this.key + 2).node && t.isReturnStatement(node.consequent) &&
              !node.alternate && next.isExpressionStatement()) {
                const nextExpr = t.unaryExpression('void', next.node.expression);
                next.dangerouslyRemove();
                if (node.consequent.argument) {
                  return t.returnStatement(
                    t.conditionalExpression(
                      node.test,
                      node.consequent.argument,
                      nextExpr
                    )
                  );
                }

                return t.returnStatement(
                  t.logicalExpression('||', node.test, nextExpr)
                );
            }

            if (node.consequent && node.alternate &&
              (t.isReturnStatement(node.consequent) || (
                t.isBlockStatement(node.consequent)
                  && t.isReturnStatement(
                    node.consequent.body[node.consequent.body.length - 1]
                  )
              ))
            ) {
              this.insertAfter(
                t.isBlockStatement(node.alternate)
                  ? node.alternate.body
              : node.alternate
              );
              node.alternate = null;
              return;
            }

            function coerceIf(key) {
              let block = node[key];
              if (!block || !t.isBlockStatement(block)) {
                return;
              }

              let body = block.body;
              if (body.length !== 1) {
                return;
              }

              let first = body[0];
              if (t.isVariableDeclaration(first) && first.kind !== 'let') {
                return;
              }

              node[key] = first;
            }
          },

          function (node) {
            if (!t.isIfStatement(node.consequent)) {
              return;
            }

            node.test = t.logicalExpression('&&', node.test, node.consequent.test);
            node.consequent = node.consequent.consequent;
          },

          function(node, parent) {
            if (!this.inList || node.alternate ||
                !(t.isReturnStatement(node.consequent) && !node.consequent.argument) ||
                !this.parentPath.parentPath.isFunction()) {
              return;
            }

            const test = node.test;
            if (t.isBinaryExpression(test) && test.operator === '!==') {
              test.operator = '===';
            } else if (t.isBinaryExpression(test) && test.operator === '!=') {
              test.operator = '==';
            } else if (t.isUnaryExpression(test, { operator: '!' })) {
              node.test = test.argument;
            } else {
              node.test = t.unaryExpression('!', node.test);
            }

            const statements = this.container.slice(this.key + 1);
            if (!statements.length) {
              this.dangerouslyRemove();
              return;
            }

            let l = statements.length;
            while (l-- > 0) {
              this.getSibling(this.key + 1).dangerouslyRemove();
            }

            if (statements.length === 1) {
              node.consequent = statements[0];
            } else {
              node.consequent = t.blockStatement(statements);
            }
            this.visit();
          },
        ],
      },

      WhileStatement(node) {
        return t.forStatement(null, node.test, null, node.body);
      },
    },
  });

  function flipNegation(node) {
    if (!node.consequent || !node.alternate) {
      return;
    }

    let test = node.test;
    let flip = false;

    if (t.isBinaryExpression(test)) {
      if (test.operator === '!==') {
        test.operator = '===';
        flip = true;
      }

      if (test.operator === '!=') {
        test.operator = '==';
        flip = true;
      }
    }

    if (t.isUnaryExpression(test, { operator: '!' })) {
      node.test = test.argument;
      flip = true;
    }

    if (flip) {
      let consequent = node.consequent;
      node.consequent = node.alternate;
      node.alternate = consequent;
    }
  }

  function toMultipleSequenceExpressions(statements) {
    let retStatements = [];
    let bailed;
    do {
      let res = convert(statements);
      bailed = res.bailed;
      let {seq, bailedAtIndex} = res;
      if (seq) {
        retStatements.push(t.expressionStatement(seq));
      }
      if (bailed && statements[bailedAtIndex]) {
        retStatements.push(statements[bailedAtIndex]);
      }
      if (bailed) {
        statements = statements.slice(bailedAtIndex + 1);
        if (!statements.length) {
          bailed = false;
        }
      }
    } while (bailed);

    return retStatements;

    function convert(nodes) {
      let exprs = [];

      for (let i = 0; i < nodes.length; i++) {
        let bail = () => {
          let seq;
          if (exprs.length === 1) {
            seq = exprs[0];
          } else if (exprs.length) {
            seq = t.sequenceExpression(exprs);
          }

          return {
            seq,
            bailed: true,
            bailedAtIndex: i,
          };
        };

        let node = nodes[i];
        if (t.isExpression(node)) {
          exprs.push(node);
        } else if (t.isExpressionStatement(node)) {
          exprs.push(node.expression);
        } else if (t.isIfStatement(node)) {
          let consequent;
          if (node.consequent) {
            const res = convert([node.consequent]);
            if (res.bailed) {
              return bail();
            }
            consequent = res.seq;
          }
          let alternate;
          if (node.alternate) {
            const res = convert([node.alternate]);
            if (res.bailed) {
              return bail();
            }
            alternate = res.seq;
          }

          if (!alternate) {
            exprs.push(t.logicalExpression('&&', node.test, consequent));
          } else if (!consequent) {
            exprs.push(t.logicalExpression('||', node.test, alternate));
          } else {
            exprs.push(t.conditionalExpression(node.test, consequent, alternate));
          }
        } else if (t.isBlockStatement(node)) {
          const res = convert(node.body);
          if (res.bailed) {
            return bail();
          }
          exprs.push(res.seq);
        } else {
          return bail();
        }
      }

      let seq;
      if (exprs.length === 1) {
        seq = exprs[0];
      } else if (exprs.length) {
        seq = t.sequenceExpression(exprs);
      }

      return { seq };
    }
  }

};

/*
      // TODO: this doesn't take into account variable declerations
      // turn program body into sequence expression
      Program(node, parent, scope) {
        let seq = t.toSequenceExpression(node.body, scope);
        if (seq) {
          node.body = [seq];
        }
      },
*/
<commit_msg>remove buggy transform<commit_after>'use strict';

module.exports = ({ Plugin, types: t }) => {

  const VOID_0 = t.unaryExpression('void', t.literal(0), true);

  return new Plugin('simplify', {
    visitor: {
      // undefined -> void 0
      ReferencedIdentifier(node) {
        if (node.name === 'undefined') {
          return VOID_0;
        }
      },

      // { 'foo': 'bar' } -> { foo: 'bar' }
      Property: {
        exit(node) {
          let key = node.key;
          if (t.isLiteral(key) && t.isValidIdentifier(key.value)) {
            // 'foo': 'bar' -> foo: 'bar'
            node.key = t.identifier(key.value);
            node.computed = false;
          }
        },
      },

      // foo['bar'] -> foo.bar
      MemberExpression: {
        exit(node) {
          let prop = node.property;
          if (node.computed && t.isLiteral(prop) &&
            t.isValidIdentifier(prop.value)) {
            // foo['bar'] => foo.bar
            node.property = t.identifier(prop.value);
            node.computed = false;
          }
        },
      },

      // Number(foo) -> +foo
      CallExpression(node, parent) {
        if (t.isIdentifier(node.callee, { name: 'Number' }) &&
          node.arguments.length === 1) {
          return t.unaryExpression('+', node.arguments[0], true);
        }

        if (t.isIdentifier(node.callee, { name: 'String' }) &&
          node.arguments.length === 1) {
          return t.binaryExpression('+', node.arguments[0], t.literal(''));
        }

        if (t.isFunctionExpression(node.callee)
            && (t.isExpressionStatement(parent) || t.isSequenceExpression(parent))) {
          return t.callExpression(
            t.unaryExpression('!', node.callee),
            node.arguments
          );
        }
      },

      // shorten booleans to a negation
      // true -> !0
      // false -> !1
      Literal(node) {
        if (typeof node.value === 'boolean') {
          return t.unaryExpression('!', t.literal(+!node.value), true);
        }
      },

      BinaryExpression: {
        enter: [
          // flip comparisons with a pure right hand value, this ensures
          // consistency with comparisons and increases the length of
          // strings that gzip can match
          // typeof blah === 'function' -> 'function' === typeof blah
          function (node) {
            if (t.EQUALITY_BINARY_OPERATORS.indexOf(node.operator) >= 0 &&
              this.get('right').isPure()) {
              let left = node.left;
              node.left = node.right;
              node.right = left;
            }
          },

          // simplify comparison operations if we're 100% certain
          // that each value will always be of the same type
          function (node) {
            let op = node.operator;
            if (op !== '===' && op !== '!==') {
              return;
            }

            let left  = this.get('left');
            let right = this.get('right');
            if (left.baseTypeStrictlyMatches(right)) {
              node.operator = node.operator.slice(0, -1);
            }
          },
        ],
      },

      // !foo ? 'foo' : 'bar' -> foo ? 'bar' : 'foo'
      // foo !== 'lol' ? 'foo' : 'bar' -> foo === 'lol' ? 'bar' : 'foo'
      ConditionalExpression(node) {
        flipNegation(node);
      },

/* This is just removing functions for some reason?
      // hoist all function declarations
      Block(node) {
        let top = [];
        let bottom = [];

        for (let i = 0; i < node.body.length; i++) {
          let bodyNode = node.body[i];
          if (t.isFunctionDeclaration(bodyNode)) {
            top.push(bodyNode);
          } else {
            bottom.push(bodyNode);
          }
        }

        node.body = top.concat(bottom);
      },
*/

      // concat
      VariableDeclaration: {
        enter: [
          // concat variables of the same kind with their siblings
          function (node) {
            if (!this.inList) {
              return;
            }

            while (true) {
              let sibling = this.getSibling(this.key + 1);
              if (!sibling.isVariableDeclaration({ kind: node.kind })) {
                break;
              }

              node.declarations = node.declarations.concat(
                sibling.node.declarations
              );
              sibling.dangerouslyRemove();
            }
          },

          // concat variable declarations next to for loops with it's
          // initialisers if they're of the same variable kind
          function (node) {
            if (!this.inList) {
              return;
            }

            let next = this.getSibling(this.key + 1);
            if (!next.isForStatement()) {
              return;
            }

            let init = next.get('init');
            if (!init.isVariableDeclaration({ kind: node.kind })) {
              return;
            }

            init.node.declarations = node.declarations.concat(
              init.node.declarations
            );
            this.dangerouslyRemove();
          },
        ],
      },

      // turn a for loop block block with single statement
      // loops into just the single statement
      For(node, parent, scope) {
        let block = node.body;
        if (!block || !t.isBlockStatement(block)) {
          return;
        }

        let body = block.body;
        if (body.length !== 1) {
          return;
        }

        let first = body[0];
        node.body = first;
      },

      ForStatement(node, parent, scope) {
        if (!this.inList || (node.init && !t.isExpression(node.init))) {
          return;
        }

        const prev = this.getSibling(this.key - 1);
        let consumed = false;
        if (prev.isVariableDeclaration()) {
          if (!node.init) {
            node.init = prev.node;
            consumed = true;
          }
        } else if (prev.isExpressionStatement()) {
          const expr = prev.node.expression;
          if (node.init) {
            if (t.isSequenceExpression(expr)) {
              expr.expressions.push(node.init);
              node.init = expr;
            } else {
              node.init = t.sequenceExpression([expr, node.init]);
            }
          } else {
            node.init = expr;
          }
          consumed = true;
        }
        if (consumed) {
          prev.dangerouslyRemove();
        }
      },

      Program(node) {
        const statements = toMultipleSequenceExpressions(node.body);
        if (!statements.length) {
          return;
        }
        node.body = statements;
      },

      BlockStatement(node, parent, scope) {
        const statements = toMultipleSequenceExpressions(node.body);
        if (!statements.length) {
          return;
        }

        if (statements.length > 1 || (t.isFunction(parent) && node === parent.body) ||
            t.isTryStatement(parent) || t.isCatchClause(parent)) {
          return t.blockStatement(statements);
        }

        if (statements.length) {
          return statements[0];
        }
      },

      // Try to merge previous statements into a sequence
      ReturnStatement(node, parent, scope) {
        if (!this.inList) {
          return;
        }

        const prev = this.getSibling(this.key - 1);
        if (!prev.isExpressionStatement()) {
          return;
        }

        let seq = prev.node.expression;
        if (node.argument) {
          if (t.isSequenceExpression(seq)) {
            seq.expressions.push(node.argument);
          } else {
            seq = t.sequenceExpression([seq, node.argument]);
          }
        } else {
          if (t.isSequenceExpression(seq)) {
            const lastExpr = seq.expressions[seq.expressions.length - 1];
            seq.expressions[seq.expressions.length - 1] = t.unaryExpression('void', lastExpr);
          } else {
            seq = t.unaryExpression('void', seq);
          }
        }

        if (seq) {
          prev.dangerouslyRemove();
          return t.returnStatement(seq);
        }
      },
      // turn blocked ifs into single statements
      IfStatement: {
        exit: [
          function(node) {
            coerceIf('consequent');
            coerceIf('alternate');
            flipNegation(node);

            // No alternate, make into a guarded expression
            if (node.consequent && !node.alternate &&
              node.consequent.type === 'ExpressionStatement' &&
              !this.isCompletionRecord()) {
                return t.expressionStatement(
                  t.logicalExpression('&&', node.test, node.consequent.expression)
                );
            }

            // Easy, both are expressions, turn into ternary
            if (t.isExpressionStatement(node.consequent) &&
              t.isExpressionStatement(node.alternate)) {
                return t.conditionalExpression(
                  node.test, node.consequent.expression, node.alternate.expression
                );
            }

            // Alternate and consequent are returns turn into a return conditional
            if (t.isReturnStatement(node.consequent)
                && t.isReturnStatement(node.alternate)
                && !this.getSibling(this.key + 1).node) {

                  return t.returnStatement(
                    t.conditionalExpression(
                      node.test,
                      node.consequent.argument || VOID_0,
                      node.alternate.argument
                    )
                  );
            }

            const next = this.getSibling(this.key + 1);

            // No alternate but the next statement is a return
            // also turn into a return conditional
            if (t.isReturnStatement(node.consequent) &&
              !node.alternate && next.isReturnStatement()) {
                const nextArg = next.node.argument;
                next.dangerouslyRemove();
                return t.returnStatement(
                  t.conditionalExpression(
                    node.test, node.consequent.argument || VOID_0, nextArg
                  )
                );
            }

            // Next is the last expression, turn into a return while void'ing the exprs
            if (!this.getSibling(this.key + 2).node && t.isReturnStatement(node.consequent) &&
              !node.alternate && next.isExpressionStatement()) {
                const nextExpr = t.unaryExpression('void', next.node.expression);
                next.dangerouslyRemove();
                if (node.consequent.argument) {
                  return t.returnStatement(
                    t.conditionalExpression(
                      node.test,
                      node.consequent.argument,
                      nextExpr
                    )
                  );
                }

                return t.returnStatement(
                  t.logicalExpression('||', node.test, nextExpr)
                );
            }

            if (node.consequent && node.alternate &&
              (t.isReturnStatement(node.consequent) || (
                t.isBlockStatement(node.consequent)
                  && t.isReturnStatement(
                    node.consequent.body[node.consequent.body.length - 1]
                  )
              ))
            ) {
              this.insertAfter(
                t.isBlockStatement(node.alternate)
                  ? node.alternate.body
              : node.alternate
              );
              node.alternate = null;
              return;
            }

            function coerceIf(key) {
              let block = node[key];
              if (!block || !t.isBlockStatement(block)) {
                return;
              }

              let body = block.body;
              if (body.length !== 1) {
                return;
              }

              let first = body[0];
              if (t.isVariableDeclaration(first) && first.kind !== 'let') {
                return;
              }

              node[key] = first;
            }
          },

          function (node) {
            if (!t.isIfStatement(node.consequent)) {
              return;
            }

            node.test = t.logicalExpression('&&', node.test, node.consequent.test);
            node.consequent = node.consequent.consequent;
          },

          function(node, parent) {
            if (!this.inList || node.alternate ||
                !(t.isReturnStatement(node.consequent) && !node.consequent.argument) ||
                !this.parentPath.parentPath.isFunction()) {
              return;
            }

            const test = node.test;
            if (t.isBinaryExpression(test) && test.operator === '!==') {
              test.operator = '===';
            } else if (t.isBinaryExpression(test) && test.operator === '!=') {
              test.operator = '==';
            } else if (t.isUnaryExpression(test, { operator: '!' })) {
              node.test = test.argument;
            } else {
              node.test = t.unaryExpression('!', node.test);
            }

            const statements = this.container.slice(this.key + 1);
            if (!statements.length) {
              this.dangerouslyRemove();
              return;
            }

            let l = statements.length;
            while (l-- > 0) {
              this.getSibling(this.key + 1).dangerouslyRemove();
            }

            if (statements.length === 1) {
              node.consequent = statements[0];
            } else {
              node.consequent = t.blockStatement(statements);
            }
            this.visit();
          },
        ],
      },

      WhileStatement(node) {
        return t.forStatement(null, node.test, null, node.body);
      },
    },
  });

  function flipNegation(node) {
    if (!node.consequent || !node.alternate) {
      return;
    }

    let test = node.test;
    let flip = false;

    if (t.isBinaryExpression(test)) {
      if (test.operator === '!==') {
        test.operator = '===';
        flip = true;
      }

      if (test.operator === '!=') {
        test.operator = '==';
        flip = true;
      }
    }

    if (t.isUnaryExpression(test, { operator: '!' })) {
      node.test = test.argument;
      flip = true;
    }

    if (flip) {
      let consequent = node.consequent;
      node.consequent = node.alternate;
      node.alternate = consequent;
    }
  }

  function toMultipleSequenceExpressions(statements) {
    let retStatements = [];
    let bailed;
    do {
      let res = convert(statements);
      bailed = res.bailed;
      let {seq, bailedAtIndex} = res;
      if (seq) {
        retStatements.push(t.expressionStatement(seq));
      }
      if (bailed && statements[bailedAtIndex]) {
        retStatements.push(statements[bailedAtIndex]);
      }
      if (bailed) {
        statements = statements.slice(bailedAtIndex + 1);
        if (!statements.length) {
          bailed = false;
        }
      }
    } while (bailed);

    return retStatements;

    function convert(nodes) {
      let exprs = [];

      for (let i = 0; i < nodes.length; i++) {
        let bail = () => {
          let seq;
          if (exprs.length === 1) {
            seq = exprs[0];
          } else if (exprs.length) {
            seq = t.sequenceExpression(exprs);
          }

          return {
            seq,
            bailed: true,
            bailedAtIndex: i,
          };
        };

        let node = nodes[i];
        if (t.isExpression(node)) {
          exprs.push(node);
        } else if (t.isExpressionStatement(node)) {
          exprs.push(node.expression);
        } else if (t.isIfStatement(node)) {
          let consequent;
          if (node.consequent) {
            const res = convert([node.consequent]);
            if (res.bailed) {
              return bail();
            }
            consequent = res.seq;
          }
          let alternate;
          if (node.alternate) {
            const res = convert([node.alternate]);
            if (res.bailed) {
              return bail();
            }
            alternate = res.seq;
          }

          if (!alternate) {
            exprs.push(t.logicalExpression('&&', node.test, consequent));
          } else if (!consequent) {
            exprs.push(t.logicalExpression('||', node.test, alternate));
          } else {
            exprs.push(t.conditionalExpression(node.test, consequent, alternate));
          }
        } else if (t.isBlockStatement(node)) {
          const res = convert(node.body);
          if (res.bailed) {
            return bail();
          }
          exprs.push(res.seq);
        } else {
          return bail();
        }
      }

      let seq;
      if (exprs.length === 1) {
        seq = exprs[0];
      } else if (exprs.length) {
        seq = t.sequenceExpression(exprs);
      }

      return { seq };
    }
  }

};

/*
      // TODO: this doesn't take into account variable declerations
      // turn program body into sequence expression
      Program(node, parent, scope) {
        let seq = t.toSequenceExpression(node.body, scope);
        if (seq) {
          node.body = [seq];
        }
      },
*/
<|endoftext|>


<commit_before>var Plugin = require('./Plugin');
var path = require('path');
var extend = require('extend');
var HOOKS = require('../hooks');
var TemplateCompiler = require('../modules/TemplateCompiler');
var interpolateName = require('../utils/interpolateName');
var emitFile = require('../utils/emitFile');
var DocPage = require('../data/Page');
var Promise = require('bluebird');

var defaultOptions = {
  template: null,
  filename: 'docs/[path][name].[ext]/index.html',
  context: {}
};

var PagePlugin = Plugin.create(defaultOptions);

PagePlugin.prototype.apply = function (compiler) {
  var plugin = this;

  if (!this.options.template)
    throw new Error('Template not defined');

  compiler.plugin('compilation', function (compilation) {

    // Skip child compilations
    if (compilation.compiler.hasOwnProperty('parentCompilation'))
      return;

    compilation.plugin(HOOKS.EMIT_RESULTS, function(context, done) {
      var results = context.results;

      var promises = results.filter(function (result) {
        return result instanceof DocPage;
      })
      .map(function(page) {
        var filename = interpolateName(plugin.options.filename, {
          path: page.source.absolutePath,
          context: compilation.compiler.context
        });

        page.path = filename;

        return page;
      })
      .map(function (page) {
        var compiler = new TemplateCompiler(compilation, plugin.options.template, true);
        var templateContext = plugin.options.context;

        Object.keys(templateContext).forEach(function(key) {
          if (typeof templateContext[key] == 'function') {
            templateContext[key] = templateContext[key].call(null, context.plugin.docs);
          }
        });

        templateContext = extend(true, {}, templateContext, {
          page: page.serialize()
        });

        return compiler.run().then(function (template) {
          var result = template(templateContext);
          emitFile(compilation, page.path, result);
          return true;
        });
      });

      Promise.all(promises)
        .then(function () {
          done(null, context);
        })
        .catch(function (err) {
          done(err);
        });

    });
  });

};

module.exports = PagePlugin;<commit_msg>Add ability to override page URL in attr section<commit_after>var Plugin = require('./Plugin');
var path = require('path');
var extend = require('extend');
var HOOKS = require('../hooks');
var TemplateCompiler = require('../modules/TemplateCompiler');
var interpolateName = require('../utils/interpolateName');
var emitFile = require('../utils/emitFile');
var DocPage = require('../data/Page');
var Promise = require('bluebird');

var defaultOptions = {
  template: null,
  filename: 'docs/[path][name].[ext]/index.html',
  context: {}
};

var PagePlugin = Plugin.create(defaultOptions);

PagePlugin.prototype.apply = function (compiler) {
  var plugin = this;

  if (!this.options.template)
    throw new Error('Template not defined');

  compiler.plugin('compilation', function (compilation) {

    // Skip child compilations
    if (compilation.compiler.hasOwnProperty('parentCompilation'))
      return;

    compilation.plugin(HOOKS.EMIT_RESULTS, function(context, done) {
      var results = context.results;

      var promises = results.filter(function (result) {
        return result instanceof DocPage;
      })
      .map(function(page) {
        var filename;

        if (page.attrs.url) {
          filename = page.attrs.url;
        } else {
          filename = interpolateName(plugin.options.filename, {
            path: page.source.absolutePath,
            context: compilation.compiler.context
          });
        }

        page.path = filename;

        return page;
      })
      .map(function (page) {
        var compiler = new TemplateCompiler(compilation, plugin.options.template, true);
        var templateContext = plugin.options.context;

        Object.keys(templateContext).forEach(function(key) {
          if (typeof templateContext[key] == 'function') {
            templateContext[key] = templateContext[key].call(null, context.plugin.docs);
          }
        });

        templateContext = extend(true, {}, templateContext, {
          page: page.serialize()
        });

        return compiler.run().then(function (template) {
          var result = template(templateContext);
          emitFile(compilation, page.path, result);
          return true;
        });
      });

      Promise.all(promises)
        .then(function () {
          done(null, context);
        })
        .catch(function (err) {
          done(err);
        });

    });
  });

};

module.exports = PagePlugin;<|endoftext|>


<commit_before>/**
 * Template Plugin : Includes handlebars templates associated with a given file
 *      when said file is imported.
 *
 * Config:
 *    root:
 *      templates:
 *          template: Defines the template that is used to output the template in the module. See consts below.
 *          precompile:
 *            Flag/hash that enable precompilation. Truthy will enable precompilation. A hash with
 *            the key name "template" will override the rendering template. (See the template value above.)
 *          cache: Name of the javascript object that templates will be assigned to.
 *            Defaults to `$AppModule.templates` if an app module exists, otherwise `templates`
 *
 * Mixins:
 *  The template plugin will mixin any special values directly, giving priority to the local version.
 *
 */
var _ = require('underscore'),
    handlebars = require('handlebars'),
    templateUtil = require('../templateUtil');

handlebars.registerHelper('without-extension', function(str) {
  return str.replace(/\.[a-zA-Z0-9]+$/, '');
});

const DEFAULT_TEMPLATE_TEMPLATE = "/* handsfree : {{{name}}}*/\n{{{templateCache}}}['{{{name}}}'] = {{handlebarsCall}}({{{data}}});\n";

function ensureTemplateTemplates(context, complete) {
  if (!context.configCache.templateTemplate) {
    var templateTemplate = (context.config.attributes.templates && context.config.attributes.templates.template) || DEFAULT_TEMPLATE_TEMPLATE;
    context.fileUtil.loadTemplate(templateTemplate, false, function(err, compiled) {
      if (err) {
        complete(err);
      } else {
        context.configCache.templateTemplate = compiled;
        complete();
      }
    });
  } else {
    complete();
  }
}

function loadTemplate(name, resource, context, callback) {
  ensureTemplateTemplates(context, function(err) {
    if (err) {
      return callback(err);
    }
    context.fileUtil.readFileArtifact(name, 'template', function(err, cache) {
      if (err) {
        callback(new Error('Failed to load template "' + name + '"\n\t' + err));
        return;
      }

      var artifact = cache.artifact || {},
          data = artifact.data || cache.data.toString(),
          attr = context.config.attributes,
          templates = attr.templates || attr.views || {},
          appModule = context.config.scopedAppModuleName(context.module),
          templateCache = (context.config.attributes.templates && context.config.attributes.templates.cache)
            || context.config.attributes.templateCache
            || ((appModule ? appModule + '.' : '') + 'templates'),
          template = context.configCache.templateTemplate;

      // We have the template data, now convert it into the proper format
      name = templateUtil.escapeJsString(name);
      if (!cache.artifact) {
        if (templates.precompile) {
          var options = context.configCache.precompileTemplates;
          if (!options) {
            context.configCache.precompileTemplates = options = _.clone(templates.precompile);
            if (options.knownHelpers) {
              options.knownHelpers = options.knownHelpers.reduce(
                  function(value, helper) {
                    value[helper] = true;
                    return value;
                  }, {});
            }
          }
          try {
            data = handlebars.precompile(data, options);
          } catch (err) {
            return callback(err);
          }
        } else {
          data = "'" + templateUtil.escapeJsString(data) + "'";
        }
        context.fileUtil.setFileArtifact(name, 'template', {data: data, template: template});
      }

      var mixinRoot = (resource.mixin && resource.mixin.root) || '';
      if (name.indexOf(mixinRoot) === 0) {
        name = name.substring(mixinRoot.length);
      }
      if (templates.root && name.indexOf(templates.root) === 0) {
        name = name.substring(templates.root.length);
      }

      callback(
        undefined,
        template({
          name: name,
          handlebarsCall: templates.precompile ? 'Handlebars.template' : 'Handlebars.compile',
          templateCache: templateCache,
          data: data
        })
      );
    });
  });
}

module.exports = {
  mode: 'scripts',
  priority: 50,

  loadMixin: function(context, next, complete) {
    var mixinTemplates = context.loadedMixin.templates;
    if (mixinTemplates) {
      var templates = context.mixins.originalConfig.templates || {},
          configTemplates = _.clone(context.config.attributes.templates || templates),
          assigned = false;

      ['template', 'precompile', 'cache', 'root'].forEach(function(key) {
        if (_.has(mixinTemplates, key) && !_.has(templates, key)) {
          configTemplates[key] = mixinTemplates[key];
          assigned = true;
        }
      });

      if (assigned) {
        context.config.attributes.templates = configTemplates;
      }
    }
    next(complete);
  },

  resource: function(context, next, complete) {
    var resource = context.resource;

    if (/\.handlebars$/.test(resource.src) || resource.template) {
      var loadedTemplates = context.fileCache.loadedTemplates;
      if (!loadedTemplates) {
        loadedTemplates = context.fileCache.loadedTemplates = {};
      }

      var generator = function(buildContext, callback) {
        var output = [],
            inputs = [];
        context.fileUtil.fileList(resource.src, /\.handlebars$/, function(err, files) {
          if (err) {
            callback(err);
            return;
          }

          function ignore(file) {
            return file.dir || loadedTemplates[file.src || file];
          }
          function checkComplete() {
            if (inputs.length === files.length) {
              // Sorting is effectively sorting on the file name due to the name comment in the template
              callback(undefined, {
                inputs: inputs,
                data: output.sort().join(''),
                generated: true,
                noSeparator: true,
                ignoreWarnings: true
              });
              return true;
            }
          }

          inputs = files.filter(ignore);
          if (checkComplete()) {
            return;
          }

          files.forEach(function(file) {
            if (ignore(file)) {
              return;
            }

            var src = file.src || file;
            loadedTemplates[src] = true;
            console.log('template2', src);
            loadTemplate(src, resource, context, function(err, data) {
              if (err) {
                return callback(err);
              }

              output.push(data.data || data);
              inputs.push(src);
              checkComplete();
            });
          });
        });
      };
      generator.sourceFile = resource.src;
      complete(undefined, generator);
    } else {
      next(complete);
    }
  }
};
<commit_msg>Remove log statement<commit_after>/**
 * Template Plugin : Includes handlebars templates associated with a given file
 *      when said file is imported.
 *
 * Config:
 *    root:
 *      templates:
 *          template: Defines the template that is used to output the template in the module. See consts below.
 *          precompile:
 *            Flag/hash that enable precompilation. Truthy will enable precompilation. A hash with
 *            the key name "template" will override the rendering template. (See the template value above.)
 *          cache: Name of the javascript object that templates will be assigned to.
 *            Defaults to `$AppModule.templates` if an app module exists, otherwise `templates`
 *
 * Mixins:
 *  The template plugin will mixin any special values directly, giving priority to the local version.
 *
 */
var _ = require('underscore'),
    handlebars = require('handlebars'),
    templateUtil = require('../templateUtil');

handlebars.registerHelper('without-extension', function(str) {
  return str.replace(/\.[a-zA-Z0-9]+$/, '');
});

const DEFAULT_TEMPLATE_TEMPLATE = "/* handsfree : {{{name}}}*/\n{{{templateCache}}}['{{{name}}}'] = {{handlebarsCall}}({{{data}}});\n";

function ensureTemplateTemplates(context, complete) {
  if (!context.configCache.templateTemplate) {
    var templateTemplate = (context.config.attributes.templates && context.config.attributes.templates.template) || DEFAULT_TEMPLATE_TEMPLATE;
    context.fileUtil.loadTemplate(templateTemplate, false, function(err, compiled) {
      if (err) {
        complete(err);
      } else {
        context.configCache.templateTemplate = compiled;
        complete();
      }
    });
  } else {
    complete();
  }
}

function loadTemplate(name, resource, context, callback) {
  ensureTemplateTemplates(context, function(err) {
    if (err) {
      return callback(err);
    }
    context.fileUtil.readFileArtifact(name, 'template', function(err, cache) {
      if (err) {
        callback(new Error('Failed to load template "' + name + '"\n\t' + err));
        return;
      }

      var artifact = cache.artifact || {},
          data = artifact.data || cache.data.toString(),
          attr = context.config.attributes,
          templates = attr.templates || attr.views || {},
          appModule = context.config.scopedAppModuleName(context.module),
          templateCache = (context.config.attributes.templates && context.config.attributes.templates.cache)
            || context.config.attributes.templateCache
            || ((appModule ? appModule + '.' : '') + 'templates'),
          template = context.configCache.templateTemplate;

      // We have the template data, now convert it into the proper format
      name = templateUtil.escapeJsString(name);
      if (!cache.artifact) {
        if (templates.precompile) {
          var options = context.configCache.precompileTemplates;
          if (!options) {
            context.configCache.precompileTemplates = options = _.clone(templates.precompile);
            if (options.knownHelpers) {
              options.knownHelpers = options.knownHelpers.reduce(
                  function(value, helper) {
                    value[helper] = true;
                    return value;
                  }, {});
            }
          }
          try {
            data = handlebars.precompile(data, options);
          } catch (err) {
            return callback(err);
          }
        } else {
          data = "'" + templateUtil.escapeJsString(data) + "'";
        }
        context.fileUtil.setFileArtifact(name, 'template', {data: data, template: template});
      }

      var mixinRoot = (resource.mixin && resource.mixin.root) || '';
      if (name.indexOf(mixinRoot) === 0) {
        name = name.substring(mixinRoot.length);
      }
      if (templates.root && name.indexOf(templates.root) === 0) {
        name = name.substring(templates.root.length);
      }

      callback(
        undefined,
        template({
          name: name,
          handlebarsCall: templates.precompile ? 'Handlebars.template' : 'Handlebars.compile',
          templateCache: templateCache,
          data: data
        })
      );
    });
  });
}

module.exports = {
  mode: 'scripts',
  priority: 50,

  loadMixin: function(context, next, complete) {
    var mixinTemplates = context.loadedMixin.templates;
    if (mixinTemplates) {
      var templates = context.mixins.originalConfig.templates || {},
          configTemplates = _.clone(context.config.attributes.templates || templates),
          assigned = false;

      ['template', 'precompile', 'cache', 'root'].forEach(function(key) {
        if (_.has(mixinTemplates, key) && !_.has(templates, key)) {
          configTemplates[key] = mixinTemplates[key];
          assigned = true;
        }
      });

      if (assigned) {
        context.config.attributes.templates = configTemplates;
      }
    }
    next(complete);
  },

  resource: function(context, next, complete) {
    var resource = context.resource;

    if (/\.handlebars$/.test(resource.src) || resource.template) {
      var loadedTemplates = context.fileCache.loadedTemplates;
      if (!loadedTemplates) {
        loadedTemplates = context.fileCache.loadedTemplates = {};
      }

      var generator = function(buildContext, callback) {
        var output = [],
            inputs = [];
        context.fileUtil.fileList(resource.src, /\.handlebars$/, function(err, files) {
          if (err) {
            callback(err);
            return;
          }

          function ignore(file) {
            return file.dir || loadedTemplates[file.src || file];
          }
          function checkComplete() {
            if (inputs.length === files.length) {
              // Sorting is effectively sorting on the file name due to the name comment in the template
              callback(undefined, {
                inputs: inputs,
                data: output.sort().join(''),
                generated: true,
                noSeparator: true,
                ignoreWarnings: true
              });
              return true;
            }
          }

          inputs = files.filter(ignore);
          if (checkComplete()) {
            return;
          }

          files.forEach(function(file) {
            if (ignore(file)) {
              return;
            }

            var src = file.src || file;
            loadedTemplates[src] = true;
            loadTemplate(src, resource, context, function(err, data) {
              if (err) {
                return callback(err);
              }

              output.push(data.data || data);
              inputs.push(src);
              checkComplete();
            });
          });
        });
      };
      generator.sourceFile = resource.src;
      complete(undefined, generator);
    } else {
      next(complete);
    }
  }
};
<|endoftext|>


<commit_before>// Removing query parameters specific to prerendering
// so that cached values can be shared

const omitParameters = [
  '__nojs',
  '__refresh',
]

// Keep in sync with keys from https://raw.githubusercontent.com/inventaire/inventaire-i18n/dist/dist/languages_data.js
const  supportedLanguages = new Set([ 'ar', 'bn', 'ca', 'cs', 'da', 'de', 'el', 'en', 'eo', 'es', 'fr', 'hu', 'id', 'it', 'ja', 'nb', 'nl', 'pa', 'pl', 'pt', 'ro', 'ru', 'sk', 'sv', 'tr', 'uk' ])

module.exports = {
  init: () =>console.log('using rewrite url plugin'),

  requestReceived: (req, res, next) => {
    console.log('initial url:', req.prerender.url)
    req.prerender.url = rewriteUrl(req, req.prerender.url)
    console.log('final url  :', req.prerender.url)
    next()
  }
}

const rewriteUrl = (req, url) => {
  const [ path, queryString ] = Array.from(url.split('?'))

  query = new URLSearchParams(queryString)
  for (const parameter of omitParameters) {
    query.delete(parameter)
  }

  if (!query.has('lang')) {
    const headersLang = getLangFromHeaders(req.get('accept-language'))
    if (headersLang) query.set('lang', headersLang)
  }

  const updatedQueryString = query.toString()

  if (updatedQueryString.length === 0) return path
  else return `${path}?${updatedQueryString}`
}

const getLangFromHeaders = acceptedLanguages => {
  if (!acceptedLanguages || acceptedLanguages.length === 0) return

  const preferredLangs = acceptedLanguages
    .replace(/q=\d(\.\d)?,?/g, '')
    .split(';')
    .map(lang => lang.split(/\W/)[0])
    .filter(lang => supportedLanguages.has(lang))

  return preferredLangs[0]
}
<commit_msg>rewriteUrl: make lang parameter default to en<commit_after>// Removing query parameters specific to prerendering
// so that cached values can be shared

const omitParameters = [
  '__nojs',
  '__refresh',
]

// Keep in sync with keys from https://raw.githubusercontent.com/inventaire/inventaire-i18n/dist/dist/languages_data.js
const  supportedLanguages = new Set([ 'ar', 'bn', 'ca', 'cs', 'da', 'de', 'el', 'en', 'eo', 'es', 'fr', 'hu', 'id', 'it', 'ja', 'nb', 'nl', 'pa', 'pl', 'pt', 'ro', 'ru', 'sk', 'sv', 'tr', 'uk' ])

module.exports = {
  init: () =>console.log('using rewrite url plugin'),

  requestReceived: (req, res, next) => {
    console.log('initial url:', req.prerender.url)
    req.prerender.url = rewriteUrl(req, req.prerender.url)
    console.log('final url  :', req.prerender.url)
    next()
  }
}

const rewriteUrl = (req, url) => {
  const [ path, queryString ] = Array.from(url.split('?'))

  query = new URLSearchParams(queryString)
  for (const parameter of omitParameters) {
    query.delete(parameter)
  }

  if (!query.has('lang')) {
    const headersLang = getLangFromHeaders(req.get('accept-language'))
    if (headersLang) query.set('lang', headersLang)
    else query.set('lang', 'en')
  }

  const updatedQueryString = query.toString()

  if (updatedQueryString.length === 0) return path
  else return `${path}?${updatedQueryString}`
}

const getLangFromHeaders = acceptedLanguages => {
  if (!acceptedLanguages || acceptedLanguages.length === 0) return

  const preferredLangs = acceptedLanguages
    .replace(/q=\d(\.\d)?,?/g, '')
    .split(';')
    .map(lang => lang.split(/\W/)[0])
    .filter(lang => supportedLanguages.has(lang))

  return preferredLangs[0]
}
<|endoftext|>