<commit_before>define(['jquery', 'bootstrap-notify', './bridge', './translate'], function ($, _, Bridge, Translate) {
    return function (aceEditor, settings) {

        var editor = this,
            currentFilepath = null,
            currentFiletype = null,
            statusCurrentFileUnsaved = false,
            statusCurrentFileExternallyChanged = false,
            modes = {
                '.rb':   'ace/mode/ruby_sketchup',
                '.css' : 'ace/mode/css',
                '.html': 'ace/mode/html',
                '.htm':  'ace/mode/html',
                '.js':   'ace/mode/javascript',
                '.json': 'ace/mode/json'
            };
        this.aceEditor = aceEditor;

        function initialize () {
            configureAce(aceEditor);
            // Listen for changes in the document to keep the (un)saved status variable up-to-date.
            aceEditor.on('change', function(){
                statusCurrentFileUnsaved = true;
            });
        }

        // Implementation of Observer/Observable

        this.addListener = function (eventName, fn) {
            $(editor).on(eventName, function (event, args) {
                fn.apply(undefined, args);
            });
        };

        function trigger (eventName, data) {
            var args = Array.prototype.slice.call(arguments).slice(1);
            $(editor).trigger(eventName, [args]);
        }

        this.focus = function () {
            aceEditor.focus();
        };

        this.setContent = function (content) {
            aceEditor.session.setValue(content);
        };

        this.getContent = function () {
            return aceEditor.session.getValue();
        };

        this.getCurrentFilepath = function () {
            return currentFilepath;
        };

        this.newDocument = function () {
            // Ask whether to save changes if there are unsaved changes.
            if ((statusCurrentFileUnsaved && confirmSaveChanges()) || (statusCurrentFileExternallyChanged && confirmSaveAndIgnoreExternalChanges())) {
                return editor.save().then(function() {
                    return editor.newDocument();
                });
            } else {
                if (currentFilepath) closeCurrentFile();
                editor.setContent('');
                currentFilepath = null;
                // Set the mode to plain text or ruby or leave the current mode.
                // aceEditor.session.setMode('ace/mode/text');
                // Since the file is new, it has not been saved yet.
                statusCurrentFileUnsaved = true;
                statusCurrentFileExternallyChanged = false;
                // Trigger an event.
                trigger('opened', '');
                return new Bridge.Promise.resolve(true); // Return a resolved promise
            }
        };

        this.open = function (filepath, lineNumber) {
            // Do not load the file again if it is already opened.
            if (filepath == currentFilepath) {
                if (lineNumber) aceEditor.gotoLine(lineNumber);
                return Bridge.Promise.resolve(true); // Return a resolved promise
            }
            // Ask whether to save changes if there are unsaved changes.
            if ((statusCurrentFileUnsaved && confirmSaveChanges()) || (statusCurrentFileExternallyChanged && confirmSaveAndIgnoreExternalChanges())) {
                return editor.save().then(function() {
                    return editor.open(filepath);
                });
            } else {
                if (currentFilepath) closeCurrentFile();
                // Read the file and load its content into the ace editor.
                return loadFile(filepath).then(function () {
                    addToRecentlyOpenedFiles(filepath);
                    // Try to determine the ace mode from the file name.
                    var extensionMatch = filepath.match(/\.\w{1,3}$/i);
                    if (extensionMatch && extensionMatch[0]) {
                        currentFiletype = extensionMatch[0].toLowerCase();
                        aceEditor.session.setMode(modes[currentFiletype] || 'ace/mode/text');
                    } else {
                        currentFiletype = undefined;
                        aceEditor.session.setMode('ace/mode/text');
                    }
                    // Try to jump to the line number where the file was last accessed.
                    if (!lineNumber) lineNumber = settings.get('recently_focused_lines', {})[filepath] || 1;
                    aceEditor.gotoLine(lineNumber); // one-based
                }, function (error) {
                    // notification: File failed to open
                    alert('Failed to open file "' + filepath + '": \n' + error);
                });
            }
        };

        this.save = function () {
            // Ask where to save the file if no file path is associated.
            if (typeof currentFilepath !== 'string') {
                return editor.saveAs();
            } else {
                // Write the content to the file.
                return Bridge.get('writefile', currentFilepath, editor.getContent()).then(function () {
                    // success
                    // The file has been saved successfully, so there are no unsaved changes anymore.
                    statusCurrentFileUnsaved = false;
                    statusCurrentFileExternallyChanged = false;
                    // Trigger an event.
                    trigger('saved', currentFilepath);
                }, function (error) {
                    // error notification
                    alert(Translate.get('Failed to save file') + ' "' + currentFilepath + '": \n' + error);
                });
            }
        };

        this.saveAs = function () {
            // Ask where to save the file.
            return Bridge.get('savepanel', Translate.get('Save the current file as…'), currentFilepath).then(function (filepath) {
                currentFilepath = filepath;
                return editor.save();
            });
        };

        function configureAce (aceEditor) {
            // Set the mode to Ruby.
            aceEditor.session.setMode('ace/mode/ruby_sketchup');
        }

        function alert (message, type) {
            var notify = $.notify(message, {
                type: type || 'danger',
                element: $('#editorContentWrapper'),
                placement: { from: 'top', align: 'center' },
                offset: { x: 0, y: 0 },
                allow_dismiss: true
            });
        }

        function confirm (message, action1, action2) {
            return new Bridge.Promise(function (resolve, reject) {
                var notify = $.notify('', {
                    type: 'warning',
                    element: $('#editorContentWrapper'), // TODO: Remove binding to a specific HTML element.
                    placement: { from: 'top', align: 'center' },
                    offset: { x: 0, y: 0 },
                    allow_dismiss: true,
                    delay: 0,
                    template: '
' +
                        Translate.get(message) + ' <button class="notify_button_action1">' +
                        Translate.get(action1 || 'Yes') + '</button> <button type="button" class="notify_button_action2">' +
                        Translate.get(action2 || 'No') + '</button>
'
                });
                $('.notify_button_action1', notify.$ele).on('click', function() {
                    resolve();
                    notify.close();
                });
                $('.notify_button_action2', notify.$ele).on('click', function() {
                    reject();
                    notify.close();
                });
            });
        }

        function confirmSaveChanges () {
            return window.confirm('Save changes to current file?');
        }

        function confirmSaveAndIgnoreExternalChanges () {
            return window.confirm('The file has been changed externally.\nOverwrite external changes and save the current file?');
        }

        function addToRecentlyOpenedFiles (filepath) {
            var recentlyOpened = settings.get('recently_opened_files', []);
            var index = recentlyOpened.indexOf(filepath);
            if (index != -1) recentlyOpened.splice(index, 1);
            if (index !=  0) recentlyOpened.unshift(filepath);
            if (recentlyOpened.length > 10) recentlyOpened.length = 10;
            settings.set('recently_opened_files', recentlyOpened);
        }

        function loadFile (filepath) {
            if (!filepath) filepath = currentFilepath;
            return Bridge.get('readfile', filepath).then(function (content) {
                editor.setContent(content);
                // Since the file has just been loaded, it has no unsaved changes yet.
                statusCurrentFileUnsaved = false;
                statusCurrentFileExternallyChanged = false;
                currentFilepath = filepath;
                // Notify on external changes of the just opened file.
                Bridge.get('observe_external_file_changes', filepath).then(function (path) {
                    if (path == filepath) {
                        confirm('The file was changed externally.', 'Reload', 'Ignore')
                        .then(loadFile);
                    }
                });
                // Trigger an event.
                trigger('opened', filepath);
            });
        }

        function closeCurrentFile () {
            if (currentFilepath) {
                // Remember the currently focused line for this file.
                var lineNumber = aceEditor.getCursorPosition().row; // zero-based
                var recentlyFocusedLines = settings.get('recently_focused_lines', {});
                recentlyFocusedLines[currentFilepath] = lineNumber+1;
                settings.set('recently_focused_lines', recentlyFocusedLines); // Using the set function to trigger saving.
            }
        }

        initialize();
    };
});
<commit_msg>Add filepath parameter to saveAs, savepanel only optional<commit_after>define(['jquery', 'bootstrap-notify', './bridge', './translate'], function ($, _, Bridge, Translate) {
    return function (aceEditor, settings) {

        var editor = this,
            currentFilepath = null,
            currentFiletype = null,
            statusCurrentFileUnsaved = false,
            statusCurrentFileExternallyChanged = false,
            modes = {
                '.rb':   'ace/mode/ruby_sketchup',
                '.css' : 'ace/mode/css',
                '.html': 'ace/mode/html',
                '.htm':  'ace/mode/html',
                '.js':   'ace/mode/javascript',
                '.json': 'ace/mode/json'
            };
        this.aceEditor = aceEditor;

        function initialize () {
            configureAce(aceEditor);
            // Listen for changes in the document to keep the (un)saved status variable up-to-date.
            aceEditor.on('change', function(){
                statusCurrentFileUnsaved = true;
            });
        }

        // Implementation of Observer/Observable

        this.addListener = function (eventName, fn) {
            $(editor).on(eventName, function (event, args) {
                fn.apply(undefined, args);
            });
        };

        function trigger (eventName, data) {
            var args = Array.prototype.slice.call(arguments).slice(1);
            $(editor).trigger(eventName, [args]);
        }

        this.focus = function () {
            aceEditor.focus();
        };

        this.setContent = function (content) {
            aceEditor.session.setValue(content);
        };

        this.getContent = function () {
            return aceEditor.session.getValue();
        };

        this.getCurrentFilepath = function () {
            return currentFilepath;
        };

        this.newDocument = function () {
            // Ask whether to save changes if there are unsaved changes.
            if ((statusCurrentFileUnsaved && confirmSaveChanges()) || (statusCurrentFileExternallyChanged && confirmSaveAndIgnoreExternalChanges())) {
                return editor.save().then(function() {
                    return editor.newDocument();
                });
            } else {
                if (currentFilepath) closeCurrentFile();
                editor.setContent('');
                currentFilepath = null;
                // Set the mode to plain text or ruby or leave the current mode.
                // aceEditor.session.setMode('ace/mode/text');
                // Since the file is new, it has not been saved yet.
                statusCurrentFileUnsaved = true;
                statusCurrentFileExternallyChanged = false;
                // Trigger an event.
                trigger('opened', '');
                return new Bridge.Promise.resolve(true); // Return a resolved promise
            }
        };

        this.open = function (filepath, lineNumber) {
            // Do not load the file again if it is already opened.
            if (filepath == currentFilepath) {
                if (lineNumber) aceEditor.gotoLine(lineNumber);
                return Bridge.Promise.resolve(true); // Return a resolved promise
            }
            // Ask whether to save changes if there are unsaved changes.
            if ((statusCurrentFileUnsaved && confirmSaveChanges()) || (statusCurrentFileExternallyChanged && confirmSaveAndIgnoreExternalChanges())) {
                return editor.save().then(function() {
                    return editor.open(filepath);
                });
            } else {
                if (currentFilepath) closeCurrentFile();
                // Read the file and load its content into the ace editor.
                return loadFile(filepath).then(function () {
                    addToRecentlyOpenedFiles(filepath);
                    // Try to determine the ace mode from the file name.
                    var extensionMatch = filepath.match(/\.\w{1,3}$/i);
                    if (extensionMatch && extensionMatch[0]) {
                        currentFiletype = extensionMatch[0].toLowerCase();
                        aceEditor.session.setMode(modes[currentFiletype] || 'ace/mode/text');
                    } else {
                        currentFiletype = undefined;
                        aceEditor.session.setMode('ace/mode/text');
                    }
                    // Try to jump to the line number where the file was last accessed.
                    if (!lineNumber) lineNumber = settings.get('recently_focused_lines', {})[filepath] || 1;
                    aceEditor.gotoLine(lineNumber); // one-based
                }, function (error) {
                    // notification: File failed to open
                    alert('Failed to open file "' + filepath + '": \n' + error);
                });
            }
        };

        this.save = function () {
            // Ask where to save the file if no file path is associated.
            if (typeof currentFilepath !== 'string') {
                return editor.saveAs();
            } else {
                // Write the content to the file.
                return Bridge.get('writefile', currentFilepath, editor.getContent()).then(function () {
                    // success
                    // The file has been saved successfully, so there are no unsaved changes anymore.
                    statusCurrentFileUnsaved = false;
                    statusCurrentFileExternallyChanged = false;
                    // Trigger an event.
                    trigger('saved', currentFilepath);
                }, function (error) {
                    // error notification
                    alert(Translate.get('Failed to save file') + ' "' + currentFilepath + '": \n' + error);
                });
            }
        };

        this.saveAs = function (filepath) {
            if (typeof filepath !== 'string') {
                // Ask where to save the file.
                return Bridge.get('savepanel', Translate.get('Save the current file as…'), currentFilepath).then(function (filepath) {
                    currentFilepath = filepath;
                    return editor.save();
                });
            } else {
                currentFilepath = filepath;
                return editor.save();
            }
        };

        function configureAce (aceEditor) {
            // Set the mode to Ruby.
            aceEditor.session.setMode('ace/mode/ruby_sketchup');
        }

        function alert (message, type) {
            var notify = $.notify(message, {
                type: type || 'danger',
                element: $('#editorContentWrapper'),
                placement: { from: 'top', align: 'center' },
                offset: { x: 0, y: 0 },
                allow_dismiss: true
            });
        }

        function confirm (message, action1, action2) {
            return new Bridge.Promise(function (resolve, reject) {
                var notify = $.notify('', {
                    type: 'warning',
                    element: $('#editorContentWrapper'), // TODO: Remove binding to a specific HTML element.
                    placement: { from: 'top', align: 'center' },
                    offset: { x: 0, y: 0 },
                    allow_dismiss: true,
                    delay: 0,
                    template: '
' +
                        Translate.get(message) + ' <button class="notify_button_action1">' +
                        Translate.get(action1 || 'Yes') + '</button> <button type="button" class="notify_button_action2">' +
                        Translate.get(action2 || 'No') + '</button>
'
                });
                $('.notify_button_action1', notify.$ele).on('click', function() {
                    resolve();
                    notify.close();
                });
                $('.notify_button_action2', notify.$ele).on('click', function() {
                    reject();
                    notify.close();
                });
            });
        }

        function confirmSaveChanges () {
            return window.confirm('Save changes to current file?');
        }

        function confirmSaveAndIgnoreExternalChanges () {
            return window.confirm('The file has been changed externally.\nOverwrite external changes and save the current file?');
        }

        function addToRecentlyOpenedFiles (filepath) {
            var recentlyOpened = settings.get('recently_opened_files', []);
            var index = recentlyOpened.indexOf(filepath);
            if (index != -1) recentlyOpened.splice(index, 1);
            if (index !=  0) recentlyOpened.unshift(filepath);
            if (recentlyOpened.length > 10) recentlyOpened.length = 10;
            settings.set('recently_opened_files', recentlyOpened);
        }

        function loadFile (filepath) {
            if (!filepath) filepath = currentFilepath;
            return Bridge.get('readfile', filepath).then(function (content) {
                editor.setContent(content);
                // Since the file has just been loaded, it has no unsaved changes yet.
                statusCurrentFileUnsaved = false;
                statusCurrentFileExternallyChanged = false;
                currentFilepath = filepath;
                // Notify on external changes of the just opened file.
                Bridge.get('observe_external_file_changes', filepath).then(function (path) {
                    if (path == filepath) {
                        confirm('The file was changed externally.', 'Reload', 'Ignore')
                        .then(loadFile);
                    }
                });
                // Trigger an event.
                trigger('opened', filepath);
            });
        }

        function closeCurrentFile () {
            if (currentFilepath) {
                // Remember the currently focused line for this file.
                var lineNumber = aceEditor.getCursorPosition().row; // zero-based
                var recentlyFocusedLines = settings.get('recently_focused_lines', {});
                recentlyFocusedLines[currentFilepath] = lineNumber+1;
                settings.set('recently_focused_lines', recentlyFocusedLines); // Using the set function to trigger saving.
            }
        }

        initialize();
    };
});
<|endoftext|>


<commit_before>    // Copyright: 2015 AlignAlytics
    // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt"
    // Source: /src/methods/_parentWidth.js
    dimple._parentWidth = function (parent) {
        // This one seems to work in Chrome - good old Chrome!
        var returnValue = parent.offsetWidth;
        // This does it for IE
        if (!returnValue || returnValue < 0) {
            returnValue = parent.clientWidth;
        }
        // FireFox is the hard one this time.  See this bug report:
        // https://bugzilla.mozilla.org/show_bug.cgi?id=649285//
        // It's dealt with by trying to recurse up the dom until we find something
        // we can get a size for.  Usually the parent of the SVG.  It's a bit costly
        // but I don't know of any other way.
        if (!returnValue || returnValue < 0) {
            if (!parent.parentNode) {
                // Give up - Recursion Exit Point
                returnValue = 0;
            } else {
                // Get the size from the parent recursively
                returnValue = dimple._parentWidth(parent.parentNode);
            }
        }
        return returnValue;
    };
<commit_msg>Update _parentWidth.js<commit_after>    // Copyright: 2015 AlignAlytics
    // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt"
    // Source: /src/methods/_parentWidth.js
    dimple._parentWidth = function (parent) {
         // Let's be explicit about what we are trying to get here
        var returnValue = parent.getBoundingClientRect().width;
        // If it returns nothing then go with "clientWidth"
        if (!returnValue || returnValue < 0) {
            returnValue = parent.clientWidth;
        }
        return returnValue;
    };
<|endoftext|>


<commit_before>/** Types used in this file
 * type STV = {
 *   result: STVResult;
 *   log: STVEvent[];
 *   thr: number;
 * };
 *
 * type Alternative = {
 *   _id: string;
 *   description: string;
 *   election: string;
 * };
 *
 * type Vote = {
 *   _id: string;
 *   priorities: Alternative[];
 *   hash: string;
 *   weight: number;
 * };
 *
 * type STVEvent =
 *   | {
 *       action: 'ITERATION';
 *       iteration: number;
 *       winners: Alternative[];
 *       alternatives: Alternative[];
 *       votes: Vote[];
 *       counts: { [key: string]: number };
 *     }
 *   | {
 *       action: 'WIN';
 *       alternative: Alternative;
 *       voteCount: number;
 *     }
 *   | {
 *       action: 'ELIMINATE';
 *       alternative: Alternative;
 *       minScore: number;
 *     }
 *   | {
 *       action: 'TIEELIMINATE';
 *       alternative: Alternative;
 *       minScore: number;
 *     }
 *   | {
 *       action: 'TIE';
 *       description: string;
 *     };
 * type STVResult =
 *   | {
 *       status: 'RESOLVED';
 *       winners: Alternative[];
 *     }
 *   | {
 *       status: 'UNRESOLVED';
 *       winners: Alternative[];
 *     };
 */

/**
 * The Droop qouta https://en.wikipedia.org/wiki/Droop_quota
 * @param { Vote[] } votes - All votes for the election
 * @param { int } seats - The number of seats in this election
 *
 * @return { int } The amount votes needed to be elected
 */
const winningThreshold = (votes, seats) => {
  return Math.floor(votes.length / (seats + 1) + 1);
};

// Epsilon value used in comparisons of floating point errors. See dataset5.js.
const EPSILON = 0.000001;

/**
 * Will calculate the election result using Single Transferable Vote
 * @param { Vote[] } votes - All votes for the election
 * @param { Alternative[] } alternatives - All possible alternatives for the election
 * @param { int } seats - The number of seats in this election
 *
 * @return { STV } The full election, including result, log and threshold value
 */
exports.calculateWinnerUsingSTV = (votes, alternatives, seats) => {
  // @let { STVEvent[] } log - Will hold the log for the entire election
  let log = [];

  // Stringify and clean the votes
  votes = votes.map((vote) => ({
    _id: String(vote._id),
    priorities: JSON.parse(JSON.stringify(vote.priorities)),
    hash: vote.hash,
    weight: 1,
  }));

  // Stringify and clean the alternatives
  alternatives = JSON.parse(JSON.stringify(alternatives));

  // @const { int } thr - The threshold value needed to win
  const thr = winningThreshold(votes, seats);

  // @let { Alternative[] } winners - Winners for the election
  let winners = [];

  // @let { int } iteration - The election is a while loop, and with each iteration
  // we count the number of first place votes each candidate has.
  let iteration = 0;
  while (votes.length > 0 && iteration < 100) {
    iteration += 1;

    // Remove empty votes, this happens after the threshold is calculated
    // in order to preserve "blank votes"
    votes = votes.filter((vote) => vote.priorities.length > 0);

    // @let { [key: string]: float } counts - Dict with the counts for each candidate
    let counts = {};

    for (let i in votes) {
      // @const { Vote } vote - The vote for this loop
      const vote = JSON.parse(JSON.stringify(votes[i]));

      // @const { Alternative } currentAlternative - We always count the first value (priorities[0])
      // because there is a mutation step that removed values that are "done". These are values
      // connected to candidates that have either won or been eliminated from the election.
      const currentAlternative = JSON.parse(JSON.stringify(vote.priorities[0]));

      // Use the alternatives description as key in the counts, and add one for each count
      counts[currentAlternative.description] =
        vote.weight + (counts[currentAlternative.description] || 0);
    }

    // Push Iteration to log
    log.push({
      action: 'ITERATION',
      iteration,
      winners: winners.slice(),
      // TODO Find a better way to this, the test assertions are slow AF with this
      //alternatives: alternatives.slice(),
      //votes: votes.slice(),
      counts: handleFloatsInOutput(counts),
    });

    // @let { [key: string]: {} } roundWinner - Dict of winners
    let roundWinners = {};
    // @let { [key: string]: float } excessFractions - Dict of excess fractions per key
    let excessFractions = {};
    // @let { [key: string]: {} } doneVotes - Dict of done votes
    let doneVotes = {};

    // Loop over the different alternatives
    for (let i in alternatives) {
      // @const { Alternative } alternative - Get an alternative
      const alternative = JSON.parse(JSON.stringify(alternatives[i]));
      // @const { float } voteCount - Find the number number of votes for this alternative
      const voteCount = counts[alternative.description] || 0;

      // If an alternative has enough votes, add them as round winner
      // Due to JavaScript float precision errors this voteCount is checked with a range
      if (voteCount >= thr - EPSILON) {
        // Calculate the excess fraction of votes, above the threshold
        excessFractions[alternative._id] = (voteCount - thr) / voteCount;

        // Add the alternatives ID to the dict of winners this round
        roundWinners[alternative._id] = {};

        // Push the whole alternative to the list of new winners
        winners.push(alternative);

        // Add the WIN action to the iteration log
        log.push({
          action: 'WIN',
          alternative,
          voteCount: Number(voteCount.toFixed(4)),
        });

        // Find the done Votes
        for (let i in votes) {
          // @const { Vote } vote - The vote for this loop
          const vote = JSON.parse(JSON.stringify(votes[i]));

          // Votes that have the winning alternative as their first pick
          if (vote.priorities[0]._id === alternative._id) doneVotes[i] = {};
        }
      }
    }

    // @let { [key: string]: {} } doneAlternatives - Have won or been eliminated
    let doneAlternatives = {};

    // @let { Vote[] } nextRoundVotes - The votes that will go on to the next round
    let nextRoundVotes = [];

    // If there are new winners this round
    if (Object.keys(roundWinners).length > 0) {
      // Check STV can terminate and return the RESOLVED winners
      if (winners.length === seats) {
        return {
          result: { status: 'RESOLVED', winners },
          log,
          thr,
        };
      }

      // Set the done alternatives as the roundwinners
      doneAlternatives = roundWinners;

      // The next rounds votes are votes that are not done.
      nextRoundVotes = votes.filter((_, i) => !doneVotes[i]);

      // Go through all done votes
      for (let i in doneVotes) {
        // @const { Vote } vote - The vote for this loop
        const vote = JSON.parse(JSON.stringify(votes[i]));

        // @const { Alternative } alternative - Take the first choice of the done vote
        const alternative = JSON.parse(JSON.stringify(vote.priorities[0]));

        // @const { float } fraction - Find the excess fraction for this alternative
        const fraction = excessFractions[alternative._id] || 0;

        // If the fraction is 0 (meaning no votes should be transferred) or if the vote
        // has no more priorities (meaning it's exhausted) we can continue without transfer
        if (fraction === 0 || vote.priorities.length === 1) continue;

        // Fractional transfer. We mutate the weight for these votes by a fraction
        vote['weight'] = vote.weight * fraction;
        // Push the mutated votes to the list of votes to be processed in the next iteration
        nextRoundVotes.push(vote);
      }
    } else {
      // Find the lowest score
      const minScore = Math.min(
        ...alternatives.map(
          (alternative) => counts[alternative.description] || 0
        )
      );

      // Find the candidates with the lowest score
      const minAlternatives = alternatives.filter(
        (alternative) =>
          (counts[alternative.description] || 0) <= minScore + EPSILON
      );

      // There is a tie for eliminating candidates. Per Scottish STV we must look at the previous rounds
      if (minAlternatives.length > 1) {
        let reverseIteration = iteration;

        // Log the Tie
        log.push({
          action: 'TIE',
          description: `There are ${
            minAlternatives.length
          } candidates with a score of ${Number(
            minScore.toFixed(4)
          )} at iteration ${reverseIteration}`,
        });

        // As long as the reveseindex is still larger then 1 we can look further back
        while (reverseIteration >= 1) {
          reverseIteration--;

          // Find the log object for the last iteration
          const logObject = log.find(
            (entry) => entry.iteration === reverseIteration
          );

          // Find the lowest score (with regard to the alternatives in the actual iteration)
          const iterationMinScore = Math.min(
            ...minAlternatives.map((a) => logObject.counts[a.description] || 0)
          );

          // Find the candidates (in regard to the actual iteration) that has the lowest score
          const iterationMinAlternatives = minAlternatives.filter(
            (alternative) =>
              (logObject.counts[alternative.description] || 0) <=
              iterationMinScore + EPSILON
          );

          // If we are at iteration lvl 1 and there is still a tie we cannot do anything
          if (reverseIteration === 1 && iterationMinAlternatives.length > 1) {
            log.push({
              action: 'TIE',
              description:
                'The backward checking went to iteration 1 without breaking the tie',
            });
            // Eliminate all candidates that are in the last iterationMinAlternatives
            iterationMinAlternatives.forEach((alternative) => {
              // Log the tie elimination
              log.push({
                action: 'TIEELIMINATE',
                alternative: alternative,
                minScore: Number(minScore.toFixed(4)),
              });
              doneAlternatives[alternative._id] = {};
            });
            break;
          }

          // If there is a tie at this iteration as well we must continue the loop
          if (iterationMinAlternatives.length > 1) continue;

          // There is only one candidate with the lowest score
          const minAlternative = iterationMinAlternatives[0];
          if (minAlternative) {
            log.push({
              action: 'ELIMINATE',
              alternative: minAlternative,
              minScore: Number(iterationMinScore.toFixed(4)),
            });
            doneAlternatives[minAlternative._id] = {};
          }
          break;
        }
      } else {
        // There is only one candidate with the lowest score
        const minAlternative = minAlternatives[0];
        if (minAlternative) {
          log.push({
            action: 'ELIMINATE',
            alternative: minAlternative,
            minScore: Number(minScore.toFixed(4)),
          });
          doneAlternatives[minAlternative._id] = {};
        }
      }

      // ==================================================================================
      nextRoundVotes = votes;
    }

    // We filter out the alternatives of the doneAlternatives from the list of nextRoundVotes
    votes = nextRoundVotes.map((vote) => {
      vote['priorities'] = vote.priorities.filter(
        (alternative) => !doneAlternatives[alternative._id]
      );
      return vote;
    });
    // Remove the alternatives that are done
    alternatives = alternatives.filter(
      (alternative) => !doneAlternatives[alternative._id]
    );
  }
  return {
    result: { status: 'UNRESOLVED', winners },
    log,
    thr,
  };
};

// Round floats to fixed in output
const handleFloatsInOutput = (obj) => {
  let newObj = {};
  Object.entries(obj).forEach(([k, v]) => (newObj[k] = Number(v.toFixed(4))));
  return newObj;
};
<commit_msg>Don't decrement iterations before current is checked<commit_after>/** Types used in this file
 * type STV = {
 *   result: STVResult;
 *   log: STVEvent[];
 *   thr: number;
 * };
 *
 * type Alternative = {
 *   _id: string;
 *   description: string;
 *   election: string;
 * };
 *
 * type Vote = {
 *   _id: string;
 *   priorities: Alternative[];
 *   hash: string;
 *   weight: number;
 * };
 *
 * type STVEvent =
 *   | {
 *       action: 'ITERATION';
 *       iteration: number;
 *       winners: Alternative[];
 *       alternatives: Alternative[];
 *       votes: Vote[];
 *       counts: { [key: string]: number };
 *     }
 *   | {
 *       action: 'WIN';
 *       alternative: Alternative;
 *       voteCount: number;
 *     }
 *   | {
 *       action: 'ELIMINATE';
 *       alternative: Alternative;
 *       minScore: number;
 *     }
 *   | {
 *       action: 'MULTI_TIE_ELIMINATIONS';
 *       alternatives: Alternative[];
 *       minScore: number;
 *     }
 *   | {
 *       action: 'TIE';
 *       description: string;
 *     };
 * type STVResult =
 *   | {
 *       status: 'RESOLVED';
 *       winners: Alternative[];
 *     }
 *   | {
 *       status: 'UNRESOLVED';
 *       winners: Alternative[];
 *     };
 */

/**
 * The Droop qouta https://en.wikipedia.org/wiki/Droop_quota
 * @param { Vote[] } votes - All votes for the election
 * @param { int } seats - The number of seats in this election
 *
 * @return { int } The amount votes needed to be elected
 */
const winningThreshold = (votes, seats) => {
  return Math.floor(votes.length / (seats + 1) + 1);
};

// Epsilon value used in comparisons of floating point errors. See dataset5.js.
const EPSILON = 0.000001;

/**
 * Will calculate the election result using Single Transferable Vote
 * @param { Vote[] } votes - All votes for the election
 * @param { Alternative[] } alternatives - All possible alternatives for the election
 * @param { int } seats - The number of seats in this election
 *
 * @return { STV } The full election, including result, log and threshold value
 */
exports.calculateWinnerUsingSTV = (votes, alternatives, seats) => {
  // @let { STVEvent[] } log - Will hold the log for the entire election
  let log = [];

  // Stringify and clean the votes
  votes = votes.map((vote) => ({
    _id: String(vote._id),
    priorities: JSON.parse(JSON.stringify(vote.priorities)),
    hash: vote.hash,
    weight: 1,
  }));

  // Stringify and clean the alternatives
  alternatives = JSON.parse(JSON.stringify(alternatives));

  // @const { int } thr - The threshold value needed to win
  const thr = winningThreshold(votes, seats);

  // @let { Alternative[] } winners - Winners for the election
  let winners = [];

  // @let { int } iteration - The election is a while loop, and with each iteration
  // we count the number of first place votes each candidate has.
  let iteration = 0;
  while (votes.length > 0 && iteration < 100) {
    iteration += 1;

    // Remove empty votes, this happens after the threshold is calculated
    // in order to preserve "blank votes"
    votes = votes.filter((vote) => vote.priorities.length > 0);

    // @let { [key: string]: float } counts - Dict with the counts for each candidate
    let counts = {};

    for (let i in votes) {
      // @const { Vote } vote - The vote for this loop
      const vote = JSON.parse(JSON.stringify(votes[i]));

      // @const { Alternative } currentAlternative - We always count the first value (priorities[0])
      // because there is a mutation step that removed values that are "done". These are values
      // connected to candidates that have either won or been eliminated from the election.
      const currentAlternative = JSON.parse(JSON.stringify(vote.priorities[0]));

      // Use the alternatives description as key in the counts, and add one for each count
      counts[currentAlternative.description] =
        vote.weight + (counts[currentAlternative.description] || 0);
    }

    // Push Iteration to log
    log.push({
      action: 'ITERATION',
      iteration,
      winners: winners.slice(),
      // TODO Find a better way to this, the test assertions are slow AF with this
      //alternatives: alternatives.slice(),
      //votes: votes.slice(),
      counts: handleFloatsInOutput(counts),
    });

    // @let { [key: string]: {} } roundWinner - Dict of winners
    let roundWinners = {};
    // @let { [key: string]: float } excessFractions - Dict of excess fractions per key
    let excessFractions = {};
    // @let { [key: string]: {} } doneVotes - Dict of done votes
    let doneVotes = {};

    // Loop over the different alternatives
    for (let i in alternatives) {
      // @const { Alternative } alternative - Get an alternative
      const alternative = JSON.parse(JSON.stringify(alternatives[i]));
      // @const { float } voteCount - Find the number number of votes for this alternative
      const voteCount = counts[alternative.description] || 0;

      // If an alternative has enough votes, add them as round winner
      // Due to JavaScript float precision errors this voteCount is checked with a range
      if (voteCount >= thr - EPSILON) {
        // Calculate the excess fraction of votes, above the threshold
        excessFractions[alternative._id] = (voteCount - thr) / voteCount;

        // Add the alternatives ID to the dict of winners this round
        roundWinners[alternative._id] = {};

        // Push the whole alternative to the list of new winners
        winners.push(alternative);

        // Add the WIN action to the iteration log
        log.push({
          action: 'WIN',
          alternative,
          voteCount: Number(voteCount.toFixed(4)),
        });

        // Find the done Votes
        for (let i in votes) {
          // @const { Vote } vote - The vote for this loop
          const vote = JSON.parse(JSON.stringify(votes[i]));

          // Votes that have the winning alternative as their first pick
          if (vote.priorities[0]._id === alternative._id) doneVotes[i] = {};
        }
      }
    }

    // @let { [key: string]: {} } doneAlternatives - Have won or been eliminated
    let doneAlternatives = {};

    // @let { Vote[] } nextRoundVotes - The votes that will go on to the next round
    let nextRoundVotes = [];

    // If there are new winners this round
    if (Object.keys(roundWinners).length > 0) {
      // Check STV can terminate and return the RESOLVED winners
      if (winners.length === seats) {
        return {
          result: { status: 'RESOLVED', winners },
          log,
          thr,
        };
      }

      // Set the done alternatives as the roundwinners
      doneAlternatives = roundWinners;

      // The next rounds votes are votes that are not done.
      nextRoundVotes = votes.filter((_, i) => !doneVotes[i]);

      // Go through all done votes
      for (let i in doneVotes) {
        // @const { Vote } vote - The vote for this loop
        const vote = JSON.parse(JSON.stringify(votes[i]));

        // @const { Alternative } alternative - Take the first choice of the done vote
        const alternative = JSON.parse(JSON.stringify(vote.priorities[0]));

        // @const { float } fraction - Find the excess fraction for this alternative
        const fraction = excessFractions[alternative._id] || 0;

        // If the fraction is 0 (meaning no votes should be transferred) or if the vote
        // has no more priorities (meaning it's exhausted) we can continue without transfer
        if (fraction === 0 || vote.priorities.length === 1) continue;

        // Fractional transfer. We mutate the weight for these votes by a fraction
        vote['weight'] = vote.weight * fraction;
        // Push the mutated votes to the list of votes to be processed in the next iteration
        nextRoundVotes.push(vote);
      }
    } else {
      // Find the lowest score
      const minScore = Math.min(
        ...alternatives.map(
          (alternative) => counts[alternative.description] || 0
        )
      );

      // Find the candidates with the lowest score
      const minAlternatives = alternatives.filter(
        (alternative) =>
          (counts[alternative.description] || 0) <= minScore + EPSILON
      );

      // There is a tie for eliminating candidates. Per Scottish STV we must look at the previous rounds
      if (minAlternatives.length > 1) {
        let reverseIteration = iteration;

        // Log the Tie
        log.push({
          action: 'TIE',
          description: `There are ${
            minAlternatives.length
          } candidates with a score of ${Number(
            minScore.toFixed(4)
          )} at iteration ${reverseIteration}`,
        });

        // As long as the reveseindex is still larger then 1 we can look further back
        while (reverseIteration >= 1) {
          // Find the log object for the last iteration
          const logObject = log.find(
            (entry) => entry.iteration === reverseIteration
          );

          // Find the lowest score (with regard to the alternatives in the actual iteration)
          const iterationMinScore = Math.min(
            ...minAlternatives.map((a) => logObject.counts[a.description] || 0)
          );

          // Find the candidates (in regard to the actual iteration) that has the lowest score
          const iterationMinAlternatives = minAlternatives.filter(
            (alternative) =>
              (logObject.counts[alternative.description] || 0) <=
              iterationMinScore + EPSILON
          );

          // If we are at iteration lvl 1 and there is still a tie we cannot do anything
          if (reverseIteration === 1 && iterationMinAlternatives.length > 1) {
            log.push({
              action: 'TIE',
              description:
                'The backward checking went to iteration 1 without breaking the tie',
            });
            // Eliminate all candidates that are in the last iterationMinAlternatives
            log.push({
              action: 'MULTI_TIE_ELIMINATIONS',
              alternatives: alternatives,
              minScore: Number(minScore.toFixed(4)),
            });
            iterationMinAlternatives.forEach(
              (alternative) => (doneAlternatives[alternative._id] = {})
            );
            break;
          }

          reverseIteration--;
          // If there is a tie at this iteration as well we must continue the loop
          if (iterationMinAlternatives.length > 1) continue;

          // There is only one candidate with the lowest score
          const minAlternative = iterationMinAlternatives[0];
          if (minAlternative) {
            log.push({
              action: 'ELIMINATE',
              alternative: minAlternative,
              minScore: Number(iterationMinScore.toFixed(4)),
            });
            doneAlternatives[minAlternative._id] = {};
          }
          break;
        }
      } else {
        // There is only one candidate with the lowest score
        const minAlternative = minAlternatives[0];
        if (minAlternative) {
          log.push({
            action: 'ELIMINATE',
            alternative: minAlternative,
            minScore: Number(minScore.toFixed(4)),
          });
          doneAlternatives[minAlternative._id] = {};
        }
      }
      nextRoundVotes = votes;
    }

    // We filter out the alternatives of the doneAlternatives from the list of nextRoundVotes
    votes = nextRoundVotes.map((vote) => {
      vote['priorities'] = vote.priorities.filter(
        (alternative) => !doneAlternatives[alternative._id]
      );
      return vote;
    });
    // Remove the alternatives that are done
    alternatives = alternatives.filter(
      (alternative) => !doneAlternatives[alternative._id]
    );
  }
  return {
    result: { status: 'UNRESOLVED', winners },
    log,
    thr,
  };
};

// Round floats to fixed in output
const handleFloatsInOutput = (obj) => {
  let newObj = {};
  Object.entries(obj).forEach(([k, v]) => (newObj[k] = Number(v.toFixed(4))));
  return newObj;
};
<|endoftext|>


<commit_before>#! /usr/bin/env node

// Ref: http://docopt.org/

/*

Chain commands
Positional arguments
Named arguments
Required arguments
Limit arguments options
Set option type (e.g. string, number)
Set number of options (exact, min, & max)
Nest commands & arguments

*/

'use strict';

(function () {

  var jargs = require('../src/index');
  var Command = jargs.Command;
  var KWArg = jargs.KWArg;
  var Arg = jargs.Arg;

  var args = jargs.collect([
    Command(
      'init',
      null,
      Arg(
        'path',
        {required: true, type: 'string'}
      )
    ),
    Command(
      'build',
      null,
      KWArg(
        'config',
        {alias: 'c', type: 'string', default: 'config.json'}
      )
    ),
    KWArg(
      'help',
      {alias: 'h', type: 'boolean', description: 'Displays help & usage info'}
    ),
    KWArg(
      'version',
      {alias: 'v', type: 'boolean', description: 'Displays version number'}
    )
  ]);

  console.log(args);

})();
<commit_msg>Note about kwarg scope<commit_after>#! /usr/bin/env node

// Ref: http://docopt.org/

/*

Chain commands
Positional arguments
Named arguments
Required arguments
Limit arguments options
Set option type (e.g. string, number)
Set number of options (exact, min, & max)
Nest commands & arguments
Adjust kwarg scope (global / children)

*/

'use strict';

(function () {

  var jargs = require('../src/index');
  var Command = jargs.Command;
  var KWArg = jargs.KWArg;
  var Arg = jargs.Arg;

  var args = jargs.collect([
    Command(
      'init',
      null,
      Arg(
        'path',
        {required: true, type: 'string'}
      )
    ),
    Command(
      'build',
      null,
      KWArg(
        'config',
        {alias: 'c', type: 'string', default: 'config.json'}
      )
    ),
    KWArg(
      'help',
      {alias: 'h', type: 'boolean', description: 'Displays help & usage info'}
    ),
    KWArg(
      'version',
      {alias: 'v', type: 'boolean', description: 'Displays version number'}
    )
  ]);

  console.log(args);

})();
<|endoftext|>


<commit_before>/*global defineSuite*/
defineSuite([
        'DataSources/ModelGraphics',
        'DataSources/ConstantProperty'
    ], function(
        ModelGraphics,
        ConstantProperty) {
    "use strict";
    /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn*/

    it('creates expected instance from raw assignment and construction', function() {
        var options = {
            uri : '0',
            scale : 1,
            show : false,
            minimumPixelSize : 2
        };

        var model = new ModelGraphics(options);
        expect(model.uri).toBeInstanceOf(ConstantProperty);
        expect(model.scale).toBeInstanceOf(ConstantProperty);
        expect(model.show).toBeInstanceOf(ConstantProperty);
        expect(model.minimumPixelSize).toBeInstanceOf(ConstantProperty);

        expect(model.uri.getValue()).toEqual(options.uri);
        expect(model.scale.getValue()).toEqual(options.scale);
        expect(model.show.getValue()).toEqual(options.show);
        expect(model.minimumPixelSize.getValue()).toEqual(options.minimumPixelSize);
    });

    it('merge assigns unassigned properties', function() {
        var source = new ModelGraphics();
        source.uri = new ConstantProperty('');
        source.show = new ConstantProperty(true);
        source.scale = new ConstantProperty(1.0);
        source.minimumPixelSize = new ConstantProperty(2.0);

        var target = new ModelGraphics();
        target.merge(source);

        expect(target.uri).toBe(source.uri);
        expect(target.show).toBe(source.show);
        expect(target.scale).toBe(source.scale);
        expect(target.minimumPixelSize).toBe(source.minimumPixelSize);
    });

    it('merge does not assign assigned properties', function() {
        var source = new ModelGraphics();
        source.uri = new ConstantProperty('');
        source.show = new ConstantProperty(true);
        source.scale = new ConstantProperty(1.0);
        source.minimumPixelSize = new ConstantProperty(2.0);

        var uri = new ConstantProperty('');
        var show = new ConstantProperty(true);
        var scale = new ConstantProperty(1.0);
        var minimumPixelSize = new ConstantProperty(2.0);

        var target = new ModelGraphics();
        target.uri = uri;
        target.show = show;
        target.scale = scale;
        target.minimumPixelSize = minimumPixelSize;

        target.merge(source);

        expect(target.uri).toBe(uri);
        expect(target.show).toBe(show);
        expect(target.scale).toBe(scale);
        expect(target.minimumPixelSize).toBe(minimumPixelSize);
    });

    it('clone works', function() {
        var source = new ModelGraphics();
        source.uri = new ConstantProperty('');
        source.show = new ConstantProperty(true);
        source.scale = new ConstantProperty(1.0);
        source.minimumPixelSize = new ConstantProperty(2.0);

        var result = source.clone();
        expect(result.uri).toBe(source.uri);
        expect(result.show).toBe(source.show);
        expect(result.scale).toBe(source.scale);
        expect(result.minimumPixelSize).toBe(source.minimumPixelSize);
    });

    it('merge throws if source undefined', function() {
        var target = new ModelGraphics();
        expect(function() {
            target.merge(undefined);
        }).toThrowDeveloperError();
    });
});<commit_msg>Adds ModelGraphics unit tests<commit_after>/*global defineSuite*/
defineSuite([
        'DataSources/ModelGraphics',
        'DataSources/ConstantProperty'
    ], function(
        ModelGraphics,
        ConstantProperty) {
    "use strict";
    /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn*/

    it('creates expected instance from raw assignment and construction', function() {
        var options = {
            uri : '0',
            scale : 1,
            show : false,
            minimumPixelSize : 2,
            maximumScale: 200
        };

        var model = new ModelGraphics(options);
        expect(model.uri).toBeInstanceOf(ConstantProperty);
        expect(model.scale).toBeInstanceOf(ConstantProperty);
        expect(model.show).toBeInstanceOf(ConstantProperty);
        expect(model.minimumPixelSize).toBeInstanceOf(ConstantProperty);
        expect(model.maximumScale).toBeInstanceOf(ConstantProperty);

        expect(model.uri.getValue()).toEqual(options.uri);
        expect(model.scale.getValue()).toEqual(options.scale);
        expect(model.show.getValue()).toEqual(options.show);
        expect(model.minimumPixelSize.getValue()).toEqual(options.minimumPixelSize);
        expect(model.maximumScale.getValue()).toEqual(options.maximumScale);
    });

    it('merge assigns unassigned properties', function() {
        var source = new ModelGraphics();
        source.uri = new ConstantProperty('');
        source.show = new ConstantProperty(true);
        source.scale = new ConstantProperty(1.0);
        source.minimumPixelSize = new ConstantProperty(2.0);
        source.maximumScale = new ConstantProperty(200.0);

        var target = new ModelGraphics();
        target.merge(source);

        expect(target.uri).toBe(source.uri);
        expect(target.show).toBe(source.show);
        expect(target.scale).toBe(source.scale);
        expect(target.minimumPixelSize).toBe(source.minimumPixelSize);
        expect(target.maximumScale).toBe(source.maximumScale);
    });

    it('merge does not assign assigned properties', function() {
        var source = new ModelGraphics();
        source.uri = new ConstantProperty('');
        source.show = new ConstantProperty(true);
        source.scale = new ConstantProperty(1.0);
        source.minimumPixelSize = new ConstantProperty(2.0);
        source.maximumScale = new ConstantProperty(200.0);

        var uri = new ConstantProperty('');
        var show = new ConstantProperty(true);
        var scale = new ConstantProperty(1.0);
        var minimumPixelSize = new ConstantProperty(2.0);
        var maximumScale = new ConstantProperty(200.0);

        var target = new ModelGraphics();
        target.uri = uri;
        target.show = show;
        target.scale = scale;
        target.minimumPixelSize = minimumPixelSize;
        target.maximumScale = maximumScale;

        target.merge(source);

        expect(target.uri).toBe(uri);
        expect(target.show).toBe(show);
        expect(target.scale).toBe(scale);
        expect(target.minimumPixelSize).toBe(minimumPixelSize);
        expect(target.maximumScale).toBe(maximumScale);
    });

    it('clone works', function() {
        var source = new ModelGraphics();
        source.uri = new ConstantProperty('');
        source.show = new ConstantProperty(true);
        source.scale = new ConstantProperty(1.0);
        source.minimumPixelSize = new ConstantProperty(2.0);
        source.maximumScale = new ConstantProperty(200.0);

        var result = source.clone();
        expect(result.uri).toBe(source.uri);
        expect(result.show).toBe(source.show);
        expect(result.scale).toBe(source.scale);
        expect(result.minimumPixelSize).toBe(source.minimumPixelSize);
        expect(result.maximumScale).toBe(source.maximumScale);
    });

    it('merge throws if source undefined', function() {
        var target = new ModelGraphics();
        expect(function() {
            target.merge(undefined);
        }).toThrowDeveloperError();
    });
});<|endoftext|>


<commit_before>"use strict";

var fs = require("fs");

var scraperjs = require("scraperjs");
var scrapers = require("../scrapers/");
var _ = require("lodash");
var async = require("async");

var debug = require("debug")("airlineData:links");

function getDestinations(options, callback) {
  var letter = options.charAt(options.length - 1);

  debug("Getting scraper for %s from %s", letter, options);
  scraperjs.StaticScraper.create(options)
    .scrape(scrapers["destinations"])
    .then(function (destinations) {
      callback(null, destinations);
    });
}

function getAllLinks(options, callback) {
  var url = options.urls;

  scraperjs.StaticScraper.create(url)
    .scrape(scrapers["destinations_link"])
    .then(function (destinations) {
      // console.log(destinations);
      callback(null, destinations);
    });
}

function getAllDestinations(options, callback) {
  var urls;

  ensureDirectoryExist(function () {
    // body...
    if (process.env.NODE_ENV === "test") {
      urls = [options.urls];
      mapUrl(urls);
    } else {
      urls = getAllLinks(options, function (err, urls) {
        mapUrl(urls);
      });
    }
  });

  function ensureDirectoryExist( callback) {
    fs.access(options.destinationsFile, function (err) {
      if (err) {
        fs.mkdir("./data/", function () {
          debug("created data directory"); // eslint-disable-line no-console
          callback();
        });
      } else {
        callback();
      }
    });

  }

  function mapUrl(urls) {
    var destinationsFile = options.destinationsFile;

    async.map(urls, function (options, callback) {
      getDestinations(options, callback);
    }, function (err, results) {
      if (err) {
        throw err;
      }
      var airlines = _.uniq(_.flatten(results, true), "name");

      fs.writeFile(destinationsFile, JSON.stringify(airlines, null, 2), function (err) {
        if (err) {
          throw (err);
        }
      });
      debug("Saved %s", destinationsFile); // eslint-disable-line no-console
      callback(null, airlines);
    });
  }

}

module.exports.getDestinations = getDestinations;
module.exports.getAllDestinations = getAllDestinations;
module.exports.getAllLinks = getAllLinks;
<commit_msg>duplicates note, uniq method doesn't work with objects<commit_after>"use strict";

var fs = require("fs");

var scraperjs = require("scraperjs");
var scrapers = require("../scrapers/");
var _ = require("lodash");
var async = require("async");

var debug = require("debug")("airlineData:links");

function getDestinations(options, callback) {
  var letter = options.charAt(options.length - 1);

  debug("Getting scraper for %s from %s", letter, options);
  scraperjs.StaticScraper.create(options)
    .scrape(scrapers["destinations"])
    .then(function (destinations) {
      callback(null, destinations);
    });
}

function getAllLinks(options, callback) {
  var url = options.urls;

  scraperjs.StaticScraper.create(url)
    .scrape(scrapers["destinations_link"])
    .then(function (destinations) {
      // console.log(destinations);
      callback(null, destinations);
    });
}

function getAllDestinations(options, callback) {
  var urls;

  ensureDirectoryExist(function () {
    // body...
    if (process.env.NODE_ENV === "test") {
      urls = [options.urls];
      mapUrl(urls);
    } else {
      urls = getAllLinks(options, function (err, urls) {
        mapUrl(urls);
      });
    }
  });

  function ensureDirectoryExist( callback) {
    fs.access(options.destinationsFile, function (err) {
      if (err) {
        fs.mkdir("./data/", function () {
          debug("created data directory"); // eslint-disable-line no-console
          callback();
        });
      } else {
        callback();
      }
    });

  }

  function mapUrl(urls) {
    var destinationsFile = options.destinationsFile;

    async.map(urls, function (options, callback) {
      getDestinations(options, callback);
    }, function (err, results) {
      if (err) {
        throw err;
      }
      // todo this seems it's not cleaning the duplicates.
      var airlines = _.uniq(_.flatten(results, true), "name");

      fs.writeFile(destinationsFile, JSON.stringify(airlines, null, 2), function (err) {
        if (err) {
          throw (err);
        }
      });
      debug("Saved %s", destinationsFile); // eslint-disable-line no-console
      callback(null, airlines);
    });
  }

}

module.exports.getDestinations = getDestinations;
module.exports.getAllDestinations = getAllDestinations;
module.exports.getAllLinks = getAllLinks;
<|endoftext|>


<commit_before>"use strict";

const {strict: assert} = require("assert");

const {mock_cjs, mock_esm, set_global, zrequire} = require("../zjsunit/namespace");
const {run_test} = require("../zjsunit/test");
const $ = require("../zjsunit/zjquery");

mock_cjs("jquery", $);

const noop = () => {};

set_global("page_params", {});

set_global("document", {
    location: {}, // we need this to load compose.js
    to_$: () => $("document-stub"),
});

const channel = mock_esm("../../static/js/channel");
const compose_fade = mock_esm("../../static/js/compose_fade", {
    clear_compose: noop,
});
const compose_pm_pill = mock_esm("../../static/js/compose_pm_pill");
const hash_util = mock_esm("../../static/js/hash_util");
const narrow_state = mock_esm("../../static/js/narrow_state", {
    set_compose_defaults: noop,
});
mock_esm("../../static/js/notifications", {
    clear_compose_notifications: noop,
});
mock_esm("../../static/js/reload_state", {
    is_in_progress: () => false,
});
mock_esm("../../static/js/drafts", {
    update_draft: noop,
});
mock_esm("../../static/js/common", {
    status_classes: "status_classes",
});
mock_esm("../../static/js/unread_ops", {
    notify_server_message_read: noop,
});
set_global("current_msg_list", {
    can_mark_messages_read() {
        return true;
    },
});

const people = zrequire("people");

const compose_ui = zrequire("compose_ui");
const compose = zrequire("compose");
const compose_state = zrequire("compose_state");
const compose_actions = zrequire("compose_actions");
const stream_data = zrequire("stream_data");

const start = compose_actions.start;
const cancel = compose_actions.cancel;
const get_focus_area = compose_actions._get_focus_area;
const respond_to_message = compose_actions.respond_to_message;
const reply_with_mention = compose_actions.reply_with_mention;
const quote_and_reply = compose_actions.quote_and_reply;

function stub_selected_message(msg) {
    current_msg_list.selected_message = () => msg;
}

function stub_channel_get(success_value) {
    channel.get = (opts) => {
        opts.success(success_value);
    };
}

function assert_visible(sel) {
    assert($(sel).visible());
}

function assert_hidden(sel) {
    assert(!$(sel).visible());
}

function override_private_message_recipient(override) {
    override(
        compose_state,
        "private_message_recipient",
        (function () {
            let recipient;

            return function (arg) {
                if (arg === undefined) {
                    return recipient;
                }

                recipient = arg;
                return undefined;
            };
        })(),
    );
}

function test(label, f) {
    run_test(label, (override) => {
        people.init();
        f(override);
    });
}

test("initial_state", () => {
    assert.equal(compose_state.composing(), false);
    assert.equal(compose_state.get_message_type(), false);
    assert.equal(compose_state.has_message_content(), false);
});

test("start", (override) => {
    override_private_message_recipient(override);
    override(compose_actions, "autosize_message_content", () => {});
    override(compose_actions, "expand_compose_box", () => {});
    override(compose_actions, "set_focus", () => {});
    override(compose_actions, "complete_starting_tasks", () => {});
    override(compose_actions, "blur_compose_inputs", () => {});
    override(compose_actions, "clear_textarea", () => {});

    let compose_defaults;
    override(narrow_state, "set_compose_defaults", () => compose_defaults);

    // Start stream message
    compose_defaults = {
        stream: "stream1",
        topic: "topic1",
    };

    let opts = {};
    start("stream", opts);

    assert_visible("#stream-message");
    assert_hidden("#private-message");

    assert.equal($("#stream_message_recipient_stream").val(), "stream1");
    assert.equal($("#stream_message_recipient_topic").val(), "topic1");
    assert.equal(compose_state.get_message_type(), "stream");
    assert(compose_state.composing());

    // Autofill stream field for single subscription
    const denmark = {
        subscribed: true,
        color: "blue",
        name: "Denmark",
        stream_id: 1,
    };
    stream_data.add_sub(denmark);

    compose_defaults = {
        trigger: "new topic button",
    };

    opts = {};
    start("stream", opts);
    assert.equal($("#stream_message_recipient_stream").val(), "Denmark");
    assert.equal($("#stream_message_recipient_topic").val(), "");

    compose_defaults = {
        trigger: "compose_hotkey",
    };

    opts = {};
    start("stream", opts);
    assert.equal($("#stream_message_recipient_stream").val(), "Denmark");
    assert.equal($("#stream_message_recipient_topic").val(), "");

    const social = {
        subscribed: true,
        color: "red",
        name: "social",
        stream_id: 2,
    };
    stream_data.add_sub(social);

    // More than 1 subscription, do not autofill
    opts = {};
    start("stream", opts);
    assert.equal($("#stream_message_recipient_stream").val(), "");
    assert.equal($("#stream_message_recipient_topic").val(), "");
    stream_data.clear_subscriptions();

    // Start PM
    compose_defaults = {
        private_message_recipient: "foo@example.com",
    };

    opts = {
        content: "hello",
    };

    start("private", opts);

    assert_hidden("#stream-message");
    assert_visible("#private-message");

    assert.equal(compose_state.private_message_recipient(), "foo@example.com");
    assert.equal($("#compose-textarea").val(), "hello");
    assert.equal(compose_state.get_message_type(), "private");
    assert(compose_state.composing());

    // Cancel compose.
    let pill_cleared;
    compose_pm_pill.clear = () => {
        pill_cleared = true;
    };

    let abort_xhr_called = false;
    override(compose, "abort_xhr", () => {
        abort_xhr_called = true;
    });

    $("#compose-textarea").set_height(50);

    assert_hidden("#compose_controls");
    cancel();
    assert(abort_xhr_called);
    assert(pill_cleared);
    assert_visible("#compose_controls");
    assert_hidden("#private-message");
    assert(!compose_state.composing());
});

test("respond_to_message", (override) => {
    override(compose_actions, "set_focus", () => {});
    override(compose_actions, "complete_starting_tasks", () => {});
    override(compose_actions, "clear_textarea", () => {});
    override_private_message_recipient(override);

    // Test PM
    const person = {
        user_id: 22,
        email: "alice@example.com",
        full_name: "Alice",
    };
    people.add_active_user(person);

    let msg = {
        type: "private",
        sender_id: person.user_id,
    };
    stub_selected_message(msg);

    let opts = {
        reply_type: "personal",
    };

    respond_to_message(opts);
    assert.equal(compose_state.private_message_recipient(), "alice@example.com");

    // Test stream
    msg = {
        type: "stream",
        stream: "devel",
        topic: "python",
    };
    stub_selected_message(msg);

    opts = {};

    respond_to_message(opts);
    assert.equal($("#stream_message_recipient_stream").val(), "devel");
});

test("reply_with_mention", (override) => {
    override(compose_actions, "set_focus", () => {});
    override(compose_actions, "complete_starting_tasks", () => {});
    override(compose_actions, "clear_textarea", () => {});
    override_private_message_recipient(override);

    const msg = {
        type: "stream",
        stream: "devel",
        topic: "python",
        sender_full_name: "Bob Roberts",
        sender_id: 40,
    };
    stub_selected_message(msg);

    let syntax_to_insert;
    override(compose_ui, "insert_syntax_and_focus", (syntax) => {
        syntax_to_insert = syntax;
    });

    const opts = {};

    reply_with_mention(opts);
    assert.equal($("#stream_message_recipient_stream").val(), "devel");
    assert.equal(syntax_to_insert, "@**Bob Roberts**");

    // Test for extended mention syntax
    const bob_1 = {
        user_id: 30,
        email: "bob1@example.com",
        full_name: "Bob Roberts",
    };
    people.add_active_user(bob_1);
    const bob_2 = {
        user_id: 40,
        email: "bob2@example.com",
        full_name: "Bob Roberts",
    };
    people.add_active_user(bob_2);

    reply_with_mention(opts);
    assert.equal($("#stream_message_recipient_stream").val(), "devel");
    assert.equal(syntax_to_insert, "@**Bob Roberts|40**");
});

test("quote_and_reply", (override) => {
    const steve = {
        user_id: 90,
        email: "steve@example.com",
        full_name: "Steve Stephenson",
    };
    people.add_active_user(steve);

    override(compose_actions, "set_focus", () => {});
    override(compose_actions, "complete_starting_tasks", () => {});
    override(compose_actions, "clear_textarea", () => {});
    override_private_message_recipient(override);

    let selected_message;
    override(current_msg_list, "selected_message", () => selected_message);

    let expected_replacement;
    let replaced;
    override(compose_ui, "replace_syntax", (syntax, replacement) => {
        assert.equal(syntax, "[Quoting…]");
        assert.equal(replacement, expected_replacement);
        replaced = true;
    });

    selected_message = {
        type: "stream",
        stream: "devel",
        topic: "python",
        sender_full_name: "Steve Stephenson",
        sender_id: 90,
    };
    hash_util.by_conversation_and_time_uri = () => "link_to_message";
    stub_channel_get({
        raw_content: "Testing.",
    });

    current_msg_list.selected_id = () => 100;

    override(compose_ui, "insert_syntax_and_focus", (syntax) => {
        assert.equal(syntax, "[Quoting…]\n");
    });

    const opts = {
        reply_type: "personal",
    };

    $("#compose-textarea").caret = (pos) => {
        assert.equal(pos, 0);
    };

    replaced = false;
    expected_replacement =
        "@_**Steve Stephenson|90** [said](link_to_message):\n```quote\nTesting.\n```";

    quote_and_reply(opts);
    assert(replaced);

    selected_message = {
        type: "stream",
        stream: "devel",
        topic: "test",
        sender_full_name: "Steve Stephenson",
        sender_id: 90,
        raw_content: "Testing.",
    };

    channel.get = () => {
        assert.fail("channel.get should not be used if raw_content is present");
    };

    replaced = false;
    quote_and_reply(opts);
    assert(replaced);

    selected_message = {
        type: "stream",
        stream: "devel",
        topic: "test",
        sender_full_name: "Steve Stephenson",
        sender_id: 90,
        raw_content: "```\nmultiline code block\nshoudln't mess with quotes\n```",
    };

    replaced = false;
    expected_replacement =
        "@_**Steve Stephenson|90** [said](link_to_message):\n````quote\n```\nmultiline code block\nshoudln't mess with quotes\n```\n````";
    quote_and_reply(opts);
    assert(replaced);
});

test("get_focus_area", () => {
    assert.equal(get_focus_area("private", {}), "#private_message_recipient");
    assert.equal(
        get_focus_area("private", {
            private_message_recipient: "bob@example.com",
        }),
        "#compose-textarea",
    );
    assert.equal(get_focus_area("stream", {}), "#stream_message_recipient_stream");
    assert.equal(get_focus_area("stream", {stream: "fun"}), "#stream_message_recipient_topic");
    assert.equal(get_focus_area("stream", {stream: "fun", topic: "more"}), "#compose-textarea");
    assert.equal(
        get_focus_area("stream", {stream: "fun", topic: "more", trigger: "new topic button"}),
        "#stream_message_recipient_topic",
    );
});

test("focus_in_empty_compose", (override) => {
    $("#compose-textarea").is = (attr) => {
        assert.equal(attr, ":focus");
        return $("#compose-textarea").is_focused;
    };

    override(compose_state, "composing", () => true);
    $("#compose-textarea").val("");
    $("#compose-textarea").trigger("focus");
    assert(compose_state.focus_in_empty_compose());

    override(compose_state, "composing", () => false);
    assert(!compose_state.focus_in_empty_compose());

    $("#compose-textarea").val("foo");
    assert(!compose_state.focus_in_empty_compose());

    $("#compose-textarea").trigger("blur");
    assert(!compose_state.focus_in_empty_compose());
});

test("on_narrow", (override) => {
    let narrowed_by_topic_reply;
    override(narrow_state, "narrowed_by_topic_reply", () => narrowed_by_topic_reply);

    let narrowed_by_pm_reply;
    override(narrow_state, "narrowed_by_pm_reply", () => narrowed_by_pm_reply);

    let has_message_content;
    override(compose_state, "has_message_content", () => has_message_content);

    let cancel_called = false;
    override(compose_actions, "cancel", () => {
        cancel_called = true;
    });
    compose_actions.on_narrow({
        force_close: true,
    });
    assert(cancel_called);

    let on_topic_narrow_called = false;
    override(compose_actions, "on_topic_narrow", () => {
        on_topic_narrow_called = true;
    });
    narrowed_by_topic_reply = true;
    compose_actions.on_narrow({
        force_close: false,
    });
    assert(on_topic_narrow_called);

    let update_message_list_called = false;
    narrowed_by_topic_reply = false;
    compose_fade.update_message_list = () => {
        update_message_list_called = true;
    };
    has_message_content = true;
    compose_actions.on_narrow({
        force_close: false,
    });
    assert(update_message_list_called);

    has_message_content = false;
    let start_called = false;
    override(compose_actions, "start", () => {
        start_called = true;
    });
    narrowed_by_pm_reply = true;
    compose_actions.on_narrow({
        force_close: false,
        trigger: "not-search",
        private_message_recipient: "not@empty.com",
    });
    assert(start_called);

    start_called = false;
    compose_actions.on_narrow({
        force_close: false,
        trigger: "search",
        private_message_recipient: "",
    });
    assert(!start_called);

    narrowed_by_pm_reply = false;
    cancel_called = false;
    compose_actions.on_narrow({
        force_close: false,
    });
    assert(cancel_called);
});
<commit_msg>node tests: Remove stub_selected_message.<commit_after>"use strict";

const {strict: assert} = require("assert");

const {mock_cjs, mock_esm, set_global, zrequire} = require("../zjsunit/namespace");
const {run_test} = require("../zjsunit/test");
const $ = require("../zjsunit/zjquery");

mock_cjs("jquery", $);

const noop = () => {};

set_global("page_params", {});

set_global("document", {
    location: {}, // we need this to load compose.js
    to_$: () => $("document-stub"),
});

const channel = mock_esm("../../static/js/channel");
const compose_fade = mock_esm("../../static/js/compose_fade", {
    clear_compose: noop,
});
const compose_pm_pill = mock_esm("../../static/js/compose_pm_pill");
const hash_util = mock_esm("../../static/js/hash_util");
const narrow_state = mock_esm("../../static/js/narrow_state", {
    set_compose_defaults: noop,
});
mock_esm("../../static/js/notifications", {
    clear_compose_notifications: noop,
});
mock_esm("../../static/js/reload_state", {
    is_in_progress: () => false,
});
mock_esm("../../static/js/drafts", {
    update_draft: noop,
});
mock_esm("../../static/js/common", {
    status_classes: "status_classes",
});
mock_esm("../../static/js/unread_ops", {
    notify_server_message_read: noop,
});
set_global("current_msg_list", {
    can_mark_messages_read() {
        return true;
    },
});

const people = zrequire("people");

const compose_ui = zrequire("compose_ui");
const compose = zrequire("compose");
const compose_state = zrequire("compose_state");
const compose_actions = zrequire("compose_actions");
const stream_data = zrequire("stream_data");

const start = compose_actions.start;
const cancel = compose_actions.cancel;
const get_focus_area = compose_actions._get_focus_area;
const respond_to_message = compose_actions.respond_to_message;
const reply_with_mention = compose_actions.reply_with_mention;
const quote_and_reply = compose_actions.quote_and_reply;

function stub_channel_get(success_value) {
    channel.get = (opts) => {
        opts.success(success_value);
    };
}

function assert_visible(sel) {
    assert($(sel).visible());
}

function assert_hidden(sel) {
    assert(!$(sel).visible());
}

function override_private_message_recipient(override) {
    override(
        compose_state,
        "private_message_recipient",
        (function () {
            let recipient;

            return function (arg) {
                if (arg === undefined) {
                    return recipient;
                }

                recipient = arg;
                return undefined;
            };
        })(),
    );
}

function test(label, f) {
    run_test(label, (override) => {
        people.init();
        f(override);
    });
}

test("initial_state", () => {
    assert.equal(compose_state.composing(), false);
    assert.equal(compose_state.get_message_type(), false);
    assert.equal(compose_state.has_message_content(), false);
});

test("start", (override) => {
    override_private_message_recipient(override);
    override(compose_actions, "autosize_message_content", () => {});
    override(compose_actions, "expand_compose_box", () => {});
    override(compose_actions, "set_focus", () => {});
    override(compose_actions, "complete_starting_tasks", () => {});
    override(compose_actions, "blur_compose_inputs", () => {});
    override(compose_actions, "clear_textarea", () => {});

    let compose_defaults;
    override(narrow_state, "set_compose_defaults", () => compose_defaults);

    // Start stream message
    compose_defaults = {
        stream: "stream1",
        topic: "topic1",
    };

    let opts = {};
    start("stream", opts);

    assert_visible("#stream-message");
    assert_hidden("#private-message");

    assert.equal($("#stream_message_recipient_stream").val(), "stream1");
    assert.equal($("#stream_message_recipient_topic").val(), "topic1");
    assert.equal(compose_state.get_message_type(), "stream");
    assert(compose_state.composing());

    // Autofill stream field for single subscription
    const denmark = {
        subscribed: true,
        color: "blue",
        name: "Denmark",
        stream_id: 1,
    };
    stream_data.add_sub(denmark);

    compose_defaults = {
        trigger: "new topic button",
    };

    opts = {};
    start("stream", opts);
    assert.equal($("#stream_message_recipient_stream").val(), "Denmark");
    assert.equal($("#stream_message_recipient_topic").val(), "");

    compose_defaults = {
        trigger: "compose_hotkey",
    };

    opts = {};
    start("stream", opts);
    assert.equal($("#stream_message_recipient_stream").val(), "Denmark");
    assert.equal($("#stream_message_recipient_topic").val(), "");

    const social = {
        subscribed: true,
        color: "red",
        name: "social",
        stream_id: 2,
    };
    stream_data.add_sub(social);

    // More than 1 subscription, do not autofill
    opts = {};
    start("stream", opts);
    assert.equal($("#stream_message_recipient_stream").val(), "");
    assert.equal($("#stream_message_recipient_topic").val(), "");
    stream_data.clear_subscriptions();

    // Start PM
    compose_defaults = {
        private_message_recipient: "foo@example.com",
    };

    opts = {
        content: "hello",
    };

    start("private", opts);

    assert_hidden("#stream-message");
    assert_visible("#private-message");

    assert.equal(compose_state.private_message_recipient(), "foo@example.com");
    assert.equal($("#compose-textarea").val(), "hello");
    assert.equal(compose_state.get_message_type(), "private");
    assert(compose_state.composing());

    // Cancel compose.
    let pill_cleared;
    compose_pm_pill.clear = () => {
        pill_cleared = true;
    };

    let abort_xhr_called = false;
    override(compose, "abort_xhr", () => {
        abort_xhr_called = true;
    });

    $("#compose-textarea").set_height(50);

    assert_hidden("#compose_controls");
    cancel();
    assert(abort_xhr_called);
    assert(pill_cleared);
    assert_visible("#compose_controls");
    assert_hidden("#private-message");
    assert(!compose_state.composing());
});

test("respond_to_message", (override) => {
    override(compose_actions, "set_focus", () => {});
    override(compose_actions, "complete_starting_tasks", () => {});
    override(compose_actions, "clear_textarea", () => {});
    override_private_message_recipient(override);

    // Test PM
    const person = {
        user_id: 22,
        email: "alice@example.com",
        full_name: "Alice",
    };
    people.add_active_user(person);

    let msg = {
        type: "private",
        sender_id: person.user_id,
    };
    override(current_msg_list, "selected_message", () => msg);

    let opts = {
        reply_type: "personal",
    };

    respond_to_message(opts);
    assert.equal(compose_state.private_message_recipient(), "alice@example.com");

    // Test stream
    msg = {
        type: "stream",
        stream: "devel",
        topic: "python",
    };

    opts = {};

    respond_to_message(opts);
    assert.equal($("#stream_message_recipient_stream").val(), "devel");
});

test("reply_with_mention", (override) => {
    override(compose_actions, "set_focus", () => {});
    override(compose_actions, "complete_starting_tasks", () => {});
    override(compose_actions, "clear_textarea", () => {});
    override_private_message_recipient(override);

    const msg = {
        type: "stream",
        stream: "devel",
        topic: "python",
        sender_full_name: "Bob Roberts",
        sender_id: 40,
    };
    override(current_msg_list, "selected_message", () => msg);

    let syntax_to_insert;
    override(compose_ui, "insert_syntax_and_focus", (syntax) => {
        syntax_to_insert = syntax;
    });

    const opts = {};

    reply_with_mention(opts);
    assert.equal($("#stream_message_recipient_stream").val(), "devel");
    assert.equal(syntax_to_insert, "@**Bob Roberts**");

    // Test for extended mention syntax
    const bob_1 = {
        user_id: 30,
        email: "bob1@example.com",
        full_name: "Bob Roberts",
    };
    people.add_active_user(bob_1);
    const bob_2 = {
        user_id: 40,
        email: "bob2@example.com",
        full_name: "Bob Roberts",
    };
    people.add_active_user(bob_2);

    reply_with_mention(opts);
    assert.equal($("#stream_message_recipient_stream").val(), "devel");
    assert.equal(syntax_to_insert, "@**Bob Roberts|40**");
});

test("quote_and_reply", (override) => {
    const steve = {
        user_id: 90,
        email: "steve@example.com",
        full_name: "Steve Stephenson",
    };
    people.add_active_user(steve);

    override(compose_actions, "set_focus", () => {});
    override(compose_actions, "complete_starting_tasks", () => {});
    override(compose_actions, "clear_textarea", () => {});
    override_private_message_recipient(override);

    let selected_message;
    override(current_msg_list, "selected_message", () => selected_message);

    let expected_replacement;
    let replaced;
    override(compose_ui, "replace_syntax", (syntax, replacement) => {
        assert.equal(syntax, "[Quoting…]");
        assert.equal(replacement, expected_replacement);
        replaced = true;
    });

    selected_message = {
        type: "stream",
        stream: "devel",
        topic: "python",
        sender_full_name: "Steve Stephenson",
        sender_id: 90,
    };
    hash_util.by_conversation_and_time_uri = () => "link_to_message";
    stub_channel_get({
        raw_content: "Testing.",
    });

    current_msg_list.selected_id = () => 100;

    override(compose_ui, "insert_syntax_and_focus", (syntax) => {
        assert.equal(syntax, "[Quoting…]\n");
    });

    const opts = {
        reply_type: "personal",
    };

    $("#compose-textarea").caret = (pos) => {
        assert.equal(pos, 0);
    };

    replaced = false;
    expected_replacement =
        "@_**Steve Stephenson|90** [said](link_to_message):\n```quote\nTesting.\n```";

    quote_and_reply(opts);
    assert(replaced);

    selected_message = {
        type: "stream",
        stream: "devel",
        topic: "test",
        sender_full_name: "Steve Stephenson",
        sender_id: 90,
        raw_content: "Testing.",
    };

    channel.get = () => {
        assert.fail("channel.get should not be used if raw_content is present");
    };

    replaced = false;
    quote_and_reply(opts);
    assert(replaced);

    selected_message = {
        type: "stream",
        stream: "devel",
        topic: "test",
        sender_full_name: "Steve Stephenson",
        sender_id: 90,
        raw_content: "```\nmultiline code block\nshoudln't mess with quotes\n```",
    };

    replaced = false;
    expected_replacement =
        "@_**Steve Stephenson|90** [said](link_to_message):\n````quote\n```\nmultiline code block\nshoudln't mess with quotes\n```\n````";
    quote_and_reply(opts);
    assert(replaced);
});

test("get_focus_area", () => {
    assert.equal(get_focus_area("private", {}), "#private_message_recipient");
    assert.equal(
        get_focus_area("private", {
            private_message_recipient: "bob@example.com",
        }),
        "#compose-textarea",
    );
    assert.equal(get_focus_area("stream", {}), "#stream_message_recipient_stream");
    assert.equal(get_focus_area("stream", {stream: "fun"}), "#stream_message_recipient_topic");
    assert.equal(get_focus_area("stream", {stream: "fun", topic: "more"}), "#compose-textarea");
    assert.equal(
        get_focus_area("stream", {stream: "fun", topic: "more", trigger: "new topic button"}),
        "#stream_message_recipient_topic",
    );
});

test("focus_in_empty_compose", (override) => {
    $("#compose-textarea").is = (attr) => {
        assert.equal(attr, ":focus");
        return $("#compose-textarea").is_focused;
    };

    override(compose_state, "composing", () => true);
    $("#compose-textarea").val("");
    $("#compose-textarea").trigger("focus");
    assert(compose_state.focus_in_empty_compose());

    override(compose_state, "composing", () => false);
    assert(!compose_state.focus_in_empty_compose());

    $("#compose-textarea").val("foo");
    assert(!compose_state.focus_in_empty_compose());

    $("#compose-textarea").trigger("blur");
    assert(!compose_state.focus_in_empty_compose());
});

test("on_narrow", (override) => {
    let narrowed_by_topic_reply;
    override(narrow_state, "narrowed_by_topic_reply", () => narrowed_by_topic_reply);

    let narrowed_by_pm_reply;
    override(narrow_state, "narrowed_by_pm_reply", () => narrowed_by_pm_reply);

    let has_message_content;
    override(compose_state, "has_message_content", () => has_message_content);

    let cancel_called = false;
    override(compose_actions, "cancel", () => {
        cancel_called = true;
    });
    compose_actions.on_narrow({
        force_close: true,
    });
    assert(cancel_called);

    let on_topic_narrow_called = false;
    override(compose_actions, "on_topic_narrow", () => {
        on_topic_narrow_called = true;
    });
    narrowed_by_topic_reply = true;
    compose_actions.on_narrow({
        force_close: false,
    });
    assert(on_topic_narrow_called);

    let update_message_list_called = false;
    narrowed_by_topic_reply = false;
    compose_fade.update_message_list = () => {
        update_message_list_called = true;
    };
    has_message_content = true;
    compose_actions.on_narrow({
        force_close: false,
    });
    assert(update_message_list_called);

    has_message_content = false;
    let start_called = false;
    override(compose_actions, "start", () => {
        start_called = true;
    });
    narrowed_by_pm_reply = true;
    compose_actions.on_narrow({
        force_close: false,
        trigger: "not-search",
        private_message_recipient: "not@empty.com",
    });
    assert(start_called);

    start_called = false;
    compose_actions.on_narrow({
        force_close: false,
        trigger: "search",
        private_message_recipient: "",
    });
    assert(!start_called);

    narrowed_by_pm_reply = false;
    cancel_called = false;
    compose_actions.on_narrow({
        force_close: false,
    });
    assert(cancel_called);
});
<|endoftext|>


<commit_before>var pendingData;
var displayedIds;
var lastDisplayedDatetime;
// if refresh requests return more than this, we don't know exact number of new messages
var maxObjects = 20;
var refreshTimeout = 5000;

// ISO 8601 date format function from:
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference:Global_Objects:Date#Example:_ISO_8601_formatted_dates
function ISODateString(d){
  function pad(n){return n<10 ? '0'+n : n}
  return d.getUTCFullYear()+'-'
      + pad(d.getUTCMonth()+1)+'-'
      + pad(d.getUTCDate())+'T'
      + pad(d.getUTCHours())+':'
      + pad(d.getUTCMinutes())+':'
      + pad(d.getUTCSeconds())+'Z'
}

var displayLogMessages = function(data) {
    var newDisplayedIds = [];
    $.each(data['objects'], function(i, el) {
        if (typeof displayedIds == 'undefined' || $.inArray(el['id'], displayedIds) !== -1) {
            // this is an old log message
            el['old'] = true;
        }
        newDisplayedIds.push(el['id']);
    });
    $(".content .table tbody").html(ich.log_messages(data));
    lastDisplayedDatetime = ISODateString(new Date());
    displayedIds = newDisplayedIds;
    $('#refresh_notice').hide();
};

var requestLogMessages = function(update, was_refresh) {
    var formData = $.map($('.filters').serializeArray(), function(n) {
        if (n.value == "") {
            return null;
        }
        return n;
    });

    if (was_refresh) {
        $('.content table').addClass('was-refreshed');
    } else if (update) {
        $('.content table').removeClass('was-refreshed');
    }

    if (! update) { // this is a refresh request, so we should only match new log messages
        if (typeof lastDisplayedDatetime !== 'undefined') {
            formData.push({ 'name': 'add_datetime__gte', 'value': lastDisplayedDatetime });
        }
    } else {
        $("#loading_indicator").fadeIn(300);
    }

    var url = "/api/logmessages/?" + $.param(formData);
    $.getJSON(url,
        function(data,textStatus,jqXHR) {
            if (update) {
                displayLogMessages(data, true);
                $("#loading_indicator").fadeOut(300);
            } else {
                pendingData = data;
                var count = data['objects'].length;
                if (count > 0) {
                    if (count >= maxObjects) {
                        count = count + "+";
                    }
                    $('#refresh_notice').html(ich.refresh_notice_template({ 'count': count }));
                    $('#refresh_notice').show();
                }
                window.setTimeout(function() { requestLogMessages(false); }, refreshTimeout);
            }
        }
    );
};

var refreshClickHandler = function(event) {
    requestLogMessages(true, true);
    $('#refresh_notice').hide();
};

$(document).ready(function() {
    var updateHandler = function() { requestLogMessages(true, false); };
    $('.filters input, .filters select').change(updateHandler);
    // disable until we have full text search
    // needs a timeout so it doesn't trigger a search for every key press right away
    // $('.filters .search-query').keypress(updateHandler);
    $('form.filters').submit(function(event){
        requestLogMessages(true, false);
        event.preventDefault();
    });
    $('#refresh_notice').bind('click', refreshClickHandler);
    requestLogMessages(true, false);
    window.setTimeout(function() { requestLogMessages(false, false); }, refreshTimeout);
});
<commit_msg>Started working on a rebuild of api request related events<commit_after>var streamingMode = true;
var timeoutTime = 5000;
var displayedIds;
var lastDisplayedDatetime;

// ISO 8601 date format function from:
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference:Global_Objects:Date#Example:_ISO_8601_formatted_dates
function ISODateString(d){
  function pad(n){return n<10 ? '0'+n : n}
  return d.getUTCFullYear()+'-'
      + pad(d.getUTCMonth()+1)+'-'
      + pad(d.getUTCDate())+'T'
      + pad(d.getUTCHours())+':'
      + pad(d.getUTCMinutes())+':'
      + pad(d.getUTCSeconds())+'Z'
};

var getFormData = function() {
    return $.map($('.filters').serializeArray(), function(n) {
        if (n.value == "") {
            return null;
        }
        return n;
    });
};

var displayLogMessages = function(data) {
    var newDisplayedIds = [];
    $.each(data['objects'], function(i, el) {
        if (typeof displayedIds == 'undefined' || $.inArray(el['id'], displayedIds) !== -1) {
            // this is an old log message
            el['old'] = true;
        }
        newDisplayedIds.push(el['id']);
    });
    $(".content .table tbody").html(ich.log_messages(data));
    displayedIds = newDisplayedIds;

    lastDisplayedDatetime = ISODateString(new Date());
    $('#refresh_notice').hide();
};

var updateLogTable = function(data) {
    displayLogMessages(data);
};

var requestLogMessages = function(formData,callback) {
    var url = "/api/logmessages/?" + $.param(formData);
    $.getJSON(url, function(data,textStatus,jqXHR) {
        callback(data);
    });
};

var handleTimeout = function() {
    if (streamingMode) {
        // Auto update table
        requestLogMessages(getFormData(),updateLogTable);
    } else {
        // Make request and show notice
    }
    window.setTimeout(handleTimeout,timeoutTime);
};

$(document).ready(function() {
    var updateHandler = function() { requestLogMessages(getFormData(),updateLogTable); };
    $('.filters input, .filters select').change(updateHandler);

    requestLogMessages(getFormData(),updateLogTable);
    window.setTimeout(handleTimeout,timeoutTime);
});
<|endoftext|>


<commit_before>'use strict';

var pins = require('./pins.js');
var util = require('util');
var fs = require('fs');
var child_process = require('child_process');
var exec = child_process.exec;
var events = require("events");

function getPinPath(pin) {
  return util.format('/sys/class/gpio/gpio%d/value', pin.id);
}

var modes = {
  in: 'in',
  out: 'out',
  high: 'hi',
  low: 'low'
};

function Watch(pin) {
  this.pin = pin;
  var pinFile = getPinPath(pin);
  console.log(pinFile);
  fs.watchFile(pinFile, function (curr, prev) {
    console.log('the current is: ' + curr);
    console.log('the previous was: ' + prev);
  });

  events.EventEmitter.call(this);
}
util.inherits(Watch, events.EventEmitter);

function Gpio() {
  this.modes = modes;
  this.pins = pins;
};

Gpio.prototype.set = function set(pin, value, cb) {
  var command = util.format('config-pin %s %s', pin.name, value);
  exec(command, function (error, stdout, stderr) {
    if (stderr) {
      console.log('stderr: ' + stderr);
    }

    cb(error);
  });
}

Gpio.prototype.get = function get(pin, cb) {
  fs.readFile(getPinPath(pin), function (err, data) {
    if (err) throw err;
    return cb(null, parseInt(data));
  });
}

Gpio.prototype.watch = function watch(pin) {
  return new Watch(pin);
}

module.exports = new Gpio();

if (module === require.main) {
  console.log('BOOOYA');
  var gpio = module.exports;
  gpio.watch(pins.P8_14);
}
<commit_msg>Add watch<commit_after>'use strict';

var pins = require('./pins.js');
var util = require('util');
var fs = require('fs');
var child_process = require('child_process');
var exec = child_process.exec;
var events = require("events");

function getPinPath(pin) {
  return util.format('/sys/class/gpio/gpio%d/value', pin.id);
}

var modes = {
  in: 'in',
  out: 'out',
  high: 'hi',
  low: 'low'
}

util.inherits(Watch, events.EventEmitter);
function Watch(pins) {
  var self = this;
  function listen(pin, value) {
      self.emit('change', pin, value);
  }
  this.watch = pins.forEach(function(pin) {
    self.watchPin(pin, listen);
  });

  events.EventEmitter.call(this);
}

Watch.prototype.watchPin = function watchPin(pin, cb) {
  this.pin = pin;
  var self = this;

  gpio.get(pin, function(err, value) {
    if (err) throw err;
    self.value = value;

    fs.watch(util.format('/sys/class/gpio/gpio%d/direction', pin.id), function (event, filename) {
      gpio.get(pin, function(err, value) {
        if (err) throw err;

        if (value !== self.value) {
          self.value = value;
          return cb(pin, value);
        }
      })
    });
  });
}

function Gpio() {
  this.modes = modes;
  this.pins = pins;
};

Gpio.prototype.watch = function watch(pins) {
  return new Watch(pins);
}

Gpio.prototype.set = function set(pin, value, cb) {
  var command = util.format('config-pin %s %s', pin.name, value);
  exec(command, function (error, stdout, stderr) {
    if (stderr) {
      console.log('stderr: ' + stderr);
    }

    cb(error);
  });
}

Gpio.prototype.get = function get(pin, cb) {
  fs.readFile(getPinPath(pin), function (err, data) {
    if (err) throw err;
    return cb(null, parseInt(data));
  });
}

Gpio.prototype.watch = function watch(pin) {
  return new Watch(pin);
}

module.exports = new Gpio();

if (module === require.main) {
  console.log('BOOOYA');
  var gpio = module.exports;
  var watch = gpio.watch([pins.P8_10, pins.P8_12]);

  watch.on('change', function () {
    console.log('change', arguments)
  });
}
<|endoftext|>