<commit_before>describe('configuration', function () {
  var assert = chai.assert;

  var origConfig;

  beforeEach(function() {
    // backup current config
    function clone(o) {
      return JSON.parse(JSON.stringify(o));
    }
    origConfig = clone(chai.config);
  });

  afterEach(function() {
    // restore config
    Object.keys(origConfig).forEach(function(key) {
      chai.config[key] = origConfig[key];
    });
  });

  function fooThrows () {
    chai.expect('foo').to.be.equal('bar');
  }
  function fooPropThrows () {
    chai.expect('foo').to.not.exist;
  }

  it('includeStack is true for method assertions', function () {
    chai.config.includeStack = true;

    try {
      fooThrows();
      assert.ok(false, 'should not get here because error thrown');
    } catch (err) {
      // not all browsers support err.stack
      if ('undefined' !== typeof err.stack) {
        assert.include(err.stack, 'assertEqual', 'should have internal stack trace in error message');
        assert.include(err.stack, 'fooThrows', 'should have user stack trace in error message');
      }
    }

  });

  it('includeStack is false for method assertions', function () {
    chai.config.includeStack = false;

    try {
      fooThrows();
      assert.ok(false, 'should not get here because error thrown');
    } catch (err) {
      // IE 10 supports err.stack in Chrome format, but without
      // `Error.captureStackTrace` support that allows tuning of the error
      // message.
      if ('undefined' !== typeof err.stack && 'undefined' !== typeof Error.captureStackTrace) {
        assert.notInclude(err.stack, 'assertEqual', 'should not have internal stack trace in error message');
        assert.include(err.stack, 'fooThrows', 'should have user stack trace in error message');
      }
    }
  });

  it('includeStack is true for property assertions', function () {
    chai.config.includeStack = true;

    try {
      fooPropThrows();
      assert.ok(false, 'should not get here because error thrown');
    } catch (err) {
      // not all browsers support err.stack
      // Phantom does not include function names for getter exec
      if ('undefined' !== typeof err.stack && 'undefined' !== typeof Error.captureStackTrace) {
        assert.include(err.stack, 'addProperty', 'should have internal stack trace in error message');
        assert.include(err.stack, 'fooPropThrows', 'should have user stack trace in error message');
      }
    }

  });

  it('includeStack is false for property assertions', function () {
    chai.config.includeStack = false;

    try {
      fooPropThrows();
      assert.ok(false, 'should not get here because error thrown');
    } catch (err) {
      // IE 10 supports err.stack in Chrome format, but without
      // `Error.captureStackTrace` support that allows tuning of the error
      // message.
      if ('undefined' !== typeof err.stack && 'undefined' !== typeof Error.captureStackTrace) {
        assert.notInclude(err.stack, 'addProperty', 'should not have internal stack trace in error message');
        assert.include(err.stack, 'fooPropThrows', 'should have user stack trace in error message');
      }
    }
  });

  describe('truncateThreshold', function() {
    it('is 20', function() {
      chai.config.truncateThreshold = 20;

      err(function() {
        assert.deepEqual({v: 'something longer than 20'}, {v: 'x'});
      }, "expected { Object (v) } to deeply equal { v: 'x' }");
    });

    it('is 0', function() {
      chai.config.truncateThreshold = 0;

      err(function() {
        assert.deepEqual({v: 'something longer than 20'}, {v: 'x'});
      }, "expected { v: 'something longer than 20' } to deeply equal { v: 'x' }");
    });
  });

  describe('deprecated properties', function() {
    var origWarnFn;
    var warnings;

    beforeEach(function() {
      origWarnFn = console.warn;
      warnings = [];
      console.warn = function(message) {
        warnings.push(message);
      };
    });

    afterEach(function() {
      console.warn = origWarnFn;
    });

    it('Assertion.includeStack warns that it is deprecated', function() {
      chai.Assertion.includeStack;

      assert.equal(warnings.length, 1);
      assert.equal(warnings[0], 'Assertion.includeStack is deprecated, use chai.config.includeStack instead.');

      chai.Assertion.includeStack = true;

      assert.equal(warnings.length, 2);
      assert.equal(warnings[1], 'Assertion.includeStack is deprecated, use chai.config.includeStack instead.');
    });

    it('Assertion.includeStack is kept in sync with config.includeStack', function() {
      assert.equal(chai.Assertion.includeStack, chai.config.includeStack);
      chai.Assertion.includeStack = !chai.Assertion.includeStack;
      assert.equal(chai.Assertion.includeStack, chai.config.includeStack);
      chai.config.includeStack = !chai.config.includeStack;
      assert.equal(chai.Assertion.includeStack, chai.config.includeStack);
    });

    it('Assertion.showDiff warns that it is deprecated', function() {
      chai.Assertion.showDiff;

      assert.equal(warnings.length, 1);
      assert.equal(warnings[0], 'Assertion.showDiff is deprecated, use chai.config.showDiff instead.');

      chai.Assertion.showDiff = true;

      assert.equal(warnings.length, 2);
      assert.equal(warnings[1], 'Assertion.showDiff is deprecated, use chai.config.showDiff instead.');
    });

    it('Assertion.showDiff is kept in sync with config.showDiff', function() {
      assert.equal(chai.Assertion.showDiff, chai.config.showDiff);
      chai.Assertion.showDiff = !chai.Assertion.showDiff;
      assert.equal(chai.Assertion.showDiff, chai.config.showDiff);
      chai.config.showDiff = !chai.config.showDiff;
      assert.equal(chai.Assertion.showDiff, chai.config.showDiff);
    });

  });
});
<commit_msg>test/configuration: Use describe('includeStack') block<commit_after>describe('configuration', function () {
  var assert = chai.assert;

  var origConfig;

  beforeEach(function() {
    // backup current config
    function clone(o) {
      return JSON.parse(JSON.stringify(o));
    }
    origConfig = clone(chai.config);
  });

  afterEach(function() {
    // restore config
    Object.keys(origConfig).forEach(function(key) {
      chai.config[key] = origConfig[key];
    });
  });

  function fooThrows () {
    chai.expect('foo').to.be.equal('bar');
  }
  function fooPropThrows () {
    chai.expect('foo').to.not.exist;
  }

  describe('includeStack', function() {
    it('is true for method assertions', function () {
      chai.config.includeStack = true;

      try {
        fooThrows();
        assert.ok(false, 'should not get here because error thrown');
      } catch (err) {
        // not all browsers support err.stack
        if ('undefined' !== typeof err.stack) {
          assert.include(err.stack, 'assertEqual', 'should have internal stack trace in error message');
          assert.include(err.stack, 'fooThrows', 'should have user stack trace in error message');
        }
      }

    });

    it('is false for method assertions', function () {
      chai.config.includeStack = false;

      try {
        fooThrows();
        assert.ok(false, 'should not get here because error thrown');
      } catch (err) {
        // IE 10 supports err.stack in Chrome format, but without
        // `Error.captureStackTrace` support that allows tuning of the error
        // message.
        if ('undefined' !== typeof err.stack && 'undefined' !== typeof Error.captureStackTrace) {
          assert.notInclude(err.stack, 'assertEqual', 'should not have internal stack trace in error message');
          assert.include(err.stack, 'fooThrows', 'should have user stack trace in error message');
        }
      }
    });

    it('is true for property assertions', function () {
      chai.config.includeStack = true;

      try {
        fooPropThrows();
        assert.ok(false, 'should not get here because error thrown');
      } catch (err) {
        // not all browsers support err.stack
        // Phantom does not include function names for getter exec
        if ('undefined' !== typeof err.stack && 'undefined' !== typeof Error.captureStackTrace) {
          assert.include(err.stack, 'addProperty', 'should have internal stack trace in error message');
          assert.include(err.stack, 'fooPropThrows', 'should have user stack trace in error message');
        }
      }

    });

    it('is false for property assertions', function () {
      chai.config.includeStack = false;

      try {
        fooPropThrows();
        assert.ok(false, 'should not get here because error thrown');
      } catch (err) {
        // IE 10 supports err.stack in Chrome format, but without
        // `Error.captureStackTrace` support that allows tuning of the error
        // message.
        if ('undefined' !== typeof err.stack && 'undefined' !== typeof Error.captureStackTrace) {
          assert.notInclude(err.stack, 'addProperty', 'should not have internal stack trace in error message');
          assert.include(err.stack, 'fooPropThrows', 'should have user stack trace in error message');
        }
      }
    });
  });

  describe('truncateThreshold', function() {
    it('is 20', function() {
      chai.config.truncateThreshold = 20;

      err(function() {
        assert.deepEqual({v: 'something longer than 20'}, {v: 'x'});
      }, "expected { Object (v) } to deeply equal { v: 'x' }");
    });

    it('is 0', function() {
      chai.config.truncateThreshold = 0;

      err(function() {
        assert.deepEqual({v: 'something longer than 20'}, {v: 'x'});
      }, "expected { v: 'something longer than 20' } to deeply equal { v: 'x' }");
    });
  });

  describe('deprecated properties', function() {
    var origWarnFn;
    var warnings;

    beforeEach(function() {
      origWarnFn = console.warn;
      warnings = [];
      console.warn = function(message) {
        warnings.push(message);
      };
    });

    afterEach(function() {
      console.warn = origWarnFn;
    });

    it('Assertion.includeStack warns that it is deprecated', function() {
      chai.Assertion.includeStack;

      assert.equal(warnings.length, 1);
      assert.equal(warnings[0], 'Assertion.includeStack is deprecated, use chai.config.includeStack instead.');

      chai.Assertion.includeStack = true;

      assert.equal(warnings.length, 2);
      assert.equal(warnings[1], 'Assertion.includeStack is deprecated, use chai.config.includeStack instead.');
    });

    it('Assertion.includeStack is kept in sync with config.includeStack', function() {
      assert.equal(chai.Assertion.includeStack, chai.config.includeStack);
      chai.Assertion.includeStack = !chai.Assertion.includeStack;
      assert.equal(chai.Assertion.includeStack, chai.config.includeStack);
      chai.config.includeStack = !chai.config.includeStack;
      assert.equal(chai.Assertion.includeStack, chai.config.includeStack);
    });

    it('Assertion.showDiff warns that it is deprecated', function() {
      chai.Assertion.showDiff;

      assert.equal(warnings.length, 1);
      assert.equal(warnings[0], 'Assertion.showDiff is deprecated, use chai.config.showDiff instead.');

      chai.Assertion.showDiff = true;

      assert.equal(warnings.length, 2);
      assert.equal(warnings[1], 'Assertion.showDiff is deprecated, use chai.config.showDiff instead.');
    });

    it('Assertion.showDiff is kept in sync with config.showDiff', function() {
      assert.equal(chai.Assertion.showDiff, chai.config.showDiff);
      chai.Assertion.showDiff = !chai.Assertion.showDiff;
      assert.equal(chai.Assertion.showDiff, chai.config.showDiff);
      chai.config.showDiff = !chai.config.showDiff;
      assert.equal(chai.Assertion.showDiff, chai.config.showDiff);
    });

  });
});
<|endoftext|>


<commit_before>'use strict';

var fs = require('fs');
var async = require('async');
var ics = require('ics-parser');
var Anyfetch = require('anyfetch');

function generateIdentifier(baseIdentifier, event) {
  var identifier = event.type;

  if(event.uid) {
    identifier = event.uid;
  }
  else {
    if(event.summary) {
      identifier += "-" + event.summary;
    }
    if(event.start) {
      identifier += "-" + event.start;
    }
    if(event.end) {
      identifier += "-" + event.end;
    }
  }

  return baseIdentifier + " - " + (new Buffer(identifier).toString('base64'));
}

module.exports = function(path, document, changes, finalCb) {
  fs.readFile(path, function(err, data) {
    if(err) {
      finalCb(err);
    }
    var events = ics(data.toString());

    var anyfetch = new Anyfetch(document.access_token);
    anyfetch.setApiUrl(finalCb.apiUrl);

    async.each(events, function(event, cb) {
      if(event.type === "VEVENT") {
        var eventDocument = {
          document_type: "event",
          actions: document.actions,
          creation_date: new Date(document.creation_date),
          modification_date: new Date(document.modification_date),
          identifier: generateIdentifier(document.identifier, event),
          metadata: {},
          user_access: document.user_access
        };

        if(event.geo) {
          event.geolocation = event.geo.latitude + ";" + event.geo.longitude;
        }

        ['startDate', 'endDate', 'name', 'description', 'attendee', 'location', 'geolocation', 'organizer'].forEach(function(param) {
          if(event[param]) {
            eventDocument.metadata[param] = event[param];
          }
        });

        anyfetch.postDocument(eventDocument, cb);
      }
      else {
        cb();
      }
    },
    function(err) {
      if(err) {
        return finalCb(err);
      }
      anyfetch.deleteDocumentByIdentifier(document.identifier, function(err) {
        finalCb(err, null);
      });
    });
  });
};
<commit_msg>Add organizer to attendee<commit_after>'use strict';

var fs = require('fs');
var async = require('async');
var ics = require('ics-parser');
var Anyfetch = require('anyfetch');

function generateIdentifier(baseIdentifier, event) {
  var identifier = event.type;

  if(event.uid) {
    identifier = event.uid;
  }
  else {
    if(event.summary) {
      identifier += "-" + event.summary;
    }
    if(event.start) {
      identifier += "-" + event.start;
    }
    if(event.end) {
      identifier += "-" + event.end;
    }
  }

  return baseIdentifier + " - " + (new Buffer(identifier).toString('base64'));
}

module.exports = function(path, document, changes, finalCb) {
  fs.readFile(path, function(err, data) {
    if(err) {
      finalCb(err);
    }
    var events = ics(data.toString());

    var anyfetch = new Anyfetch(document.access_token);
    anyfetch.setApiUrl(finalCb.apiUrl);

    async.each(events, function(event, cb) {
      if(event.type === "VEVENT") {
        var eventDocument = {
          document_type: "event",
          actions: document.actions,
          creation_date: new Date(document.creation_date),
          modification_date: new Date(document.modification_date),
          identifier: generateIdentifier(document.identifier, event),
          metadata: {},
          user_access: document.user_access
        };

        if(event.geo) {
          event.geolocation = event.geo.latitude + ";" + event.geo.longitude;
        }

        ['startDate', 'endDate', 'name', 'description', 'attendee', 'location', 'geolocation', 'organizer'].forEach(function(param) {
          if(event[param]) {
            eventDocument.metadata[param] = event[param];
          }
        });

        eventDocument.metadata.attendee = eventDocument.metadata.attendee || [];

        if(eventDocument.metadata.organizer) {
          eventDocument.metadata.attendee.push(eventDocument.metadata.organizer);
        }

        for(var i = 0; i < eventDocument.metadata.attendee.length; i += 1) {
          for(var j = i + 1; j < eventDocument.metadata.attendee.length; j += 1) {
            if((eventDocument.metadata.attendee[i].name && eventDocument.metadata.attendee[i].name === eventDocument.metadata.attendee[j].name) ||
              (eventDocument.metadata.attendee[i].mail && eventDocument.metadata.attendee[i].mail === eventDocument.metadata.attendee[j].mail)) {
              eventDocument.metadata.attendee[i].name = eventDocument.metadata.attendee[i].name || eventDocument.metadata.attendee[j].name;
              eventDocument.metadata.attendee[i].mail = eventDocument.metadata.attendee[i].mail || eventDocument.metadata.attendee[j].mail;

              eventDocument.metadata.attendee.splice(j, 1);
            }
          }
        }

        anyfetch.postDocument(eventDocument, cb);
      }
      else {
        cb();
      }
    },
    function(err) {
      if(err) {
        return finalCb(err);
      }
      anyfetch.deleteDocumentByIdentifier(document.identifier, function(err) {
        finalCb(err, null);
      });
    });
  });
};
<|endoftext|>


<commit_before>// Copyright 2022, University of Colorado Boulder

/**
 * The tsc type checker outputs type error using relative paths only, which are not hyperlinked in WebStorm and IntelliJ.
 * This thin wrapper uses a heuristic to convert the relative paths to absolute paths.  Combined with an "output filter",
 * this makes the type errors clickable in the tool output panel.
 *
 * Configure WebStorm with the following external tool:
 * program: node
 * arguments: perennial/js/scripts/absolute-tsc.js ${dir with a tsconfig, like chipper/tsconfig/all} ${path to replace, like ../../../}
 * working dir: ${the root of the checkout, like /Users/samreid/apache-document-root/main/}
 *
 * This makes the files paths clickable
 * output filters: $FILE_PATH$\($LINE$\,$COLUMN$\)
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
const start = Date.now();
const execute = require( '../common/execute' );

const args = process.argv.slice( 2 );
if ( !args || args.length === 0 ) {
  console.log( 'usage: path to dir with a tsconfig file' );
}

( async () => {
  const results = await execute( 'node', [ `${args[ 1 ]}/chipper/node_modules/typescript/bin/tsc` ], args[ 0 ], {
    errors: 'resolve'
  } );

  // If there was a problem running tsc, report it here.  The type errors are reported on stdout below.
  if ( results.stderr.length > 0 ) {
    console.log( results );
  }
  const end = Date.now();
  const elapsed = end - start;

  if ( results.stdout.trim().length === 0 ) {

    console.log( `0 errors in ${elapsed}ms` );
  }
  else {
    const lines = results.stdout.trim().split( '\n' );
    const mapped = lines.map( line => {

      return line.trim().split( args[ 1 ] ).join( process.cwd() + '/' );
    } );

    console.log( mapped.join( '\n' ) );
    console.log( `${mapped.length} ${mapped.length === 1 ? 'error' : 'errors'} in ${elapsed}ms` );
  }
} )();
<commit_msg>work on Windows too, https://github.com/phetsims/chipper/issues/1247<commit_after>// Copyright 2022, University of Colorado Boulder

/**
 * The tsc type checker outputs type error using relative paths only, which are not hyperlinked in WebStorm and IntelliJ.
 * This thin wrapper uses a heuristic to convert the relative paths to absolute paths.  Combined with an "output filter",
 * this makes the type errors clickable in the tool output panel.
 *
 * Configure WebStorm with the following external tool:
 * program: node
 * arguments: perennial/js/scripts/absolute-tsc.js ${dir with a tsconfig, like chipper/tsconfig/all} ${path to replace, like ../../../}
 * working dir: ${the root of the checkout, like /Users/samreid/apache-document-root/main/}
 *
 * IMPORTANT!!! This makes the files paths clickable in Webstorm:
 * output filters: $FILE_PATH$\($LINE$\,$COLUMN$\)
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
const start = Date.now();
const execute = require( '../common/execute' );
const path = require( 'path' );

const args = process.argv.slice( 2 );
if ( !args || args.length === 0 ) {
  console.log( 'usage: path to dir with a tsconfig file' );
}

( async () => {
  const results = await execute( 'node', [ `${args[ 1 ]}/chipper/node_modules/typescript/bin/tsc` ], args[ 0 ], {
    errors: 'resolve'
  } );

  // If there was a problem running tsc, report it here.  The type errors are reported on stdout below.
  if ( results.stderr.length > 0 ) {
    console.log( results );
  }
  const end = Date.now();
  const elapsed = end - start;

  if ( results.stdout.trim().length === 0 ) {

    console.log( `0 errors in ${elapsed}ms` );
  }
  else {
    const lines = results.stdout.trim().split( '\n' );
    const mapped = lines.map( line => {

      return line.trim().split( args[ 1 ] ).join( process.cwd() + path.sep ).split( '/' ).join( path.sep );
    } );

    console.log( mapped.join( '\n' ) );
    console.log( `${mapped.length} ${mapped.length === 1 ? 'error' : 'errors'} in ${elapsed}ms` );
  }
} )();
<|endoftext|>


<commit_before>// Generated by CoffeeScript 1.8.0
(function() {
  var Dateline, dayNames, formatHours, formatMinutes, getDayOfWeek, monthNames, showMinutes, showYear, topOfHour, useDayName, withinSevenDays;

  Dateline = function(dateObj) {
    if (dateObj == null) {
      dateObj = new Date();
    }
    dateObj.getAPTime = function(options) {
      var hour, hours, minute, minutes, timeOfDay;
      if (options == null) {
        options = {};
      }
      hours = dateObj.getHours();
      minutes = dateObj.getMinutes();
      if (hours === 0 && minutes === 0) {
        return 'midnight';
      }
      if (hours === 12 && minutes === 0) {
        return 'noon';
      }
      timeOfDay = hours < 12 ? 'a.m.' : 'p.m.';
      hour = formatHours(hours);
      if (showMinutes(minutes, options)) {
        return "" + hour + " " + timeOfDay;
      }
      minute = formatMinutes(minutes);
      return "" + hour + ":" + minute + " " + timeOfDay;
    };
    dateObj.getAPDate = function(options) {
      var date, month, monthName, year;
      if (options == null) {
        options = {};
      }
      month = dateObj.getMonth();
      date = dateObj.getDate();
      year = dateObj.getFullYear();
      monthName = monthNames[month];
      if (useDayName(dateObj, options)) {
        return getDayOfWeek(dateObj);
      } else if (showYear(year, options)) {
        return monthName + " " + date;
      } else {
        return monthName + " " + date + ", " + year;
      }
    };
    return dateObj;
  };

  if (typeof module !== "undefined" && module !== null ? module.exports : void 0) {
    module.exports = Dateline;
  } else {
    this.Dateline = Dateline;
  }

  formatHours = function(hours) {
    if (hours === 0) {
      return 12;
    }
    if (hours > 12) {
      return hours - 12;
    }
    return hours;
  };

  topOfHour = function(minutes) {
    return minutes === 0;
  };

  formatMinutes = function(minutes) {
    if (minutes < 10) {
      return "0" + minutes;
    }
    return minutes;
  };

  showMinutes = function(minutes, options) {
    return topOfHour(minutes) && (options.includeMinutes == null);
  };

  monthNames = ['Jan.', 'Feb.', 'March', 'April', 'May', 'June', 'July', 'Aug.', 'Sept.', 'Oct.', 'Nov.', 'Dec.'];

  dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];

  showYear = function(year, options) {
    return (year === new Date().getFullYear()) && (options.includeYear == null);
  };

  getDayOfWeek = function(dateObj) {
    return dayNames[dateObj.getDay()];
  };

  useDayName = function(dateObj, options) {
    return (options.useDayNameForLastWeek != null) && withinSevenDays(new Date(), dateObj);
  };

  withinSevenDays = function(date1, date2) {
    var oneDayInMs, _ref;
    oneDayInMs = 1000 * 60 * 60 * 24;
    return (-7 < (_ref = (date2 - date1) / oneDayInMs) && _ref < 0);
  };

}).call(this);
<commit_msg>Rebuild with updated coffeescript<commit_after>// Generated by CoffeeScript 1.9.2
(function() {
  var Dateline, dayNames, formatHours, formatMinutes, getDayOfWeek, monthNames, showMinutes, showYear, topOfHour, useDayName, withinSevenDays;

  Dateline = function(dateObj) {
    if (dateObj == null) {
      dateObj = new Date();
    }
    dateObj.getAPTime = function(options) {
      var hour, hours, minute, minutes, timeOfDay;
      if (options == null) {
        options = {};
      }
      hours = dateObj.getHours();
      minutes = dateObj.getMinutes();
      if (hours === 0 && minutes === 0) {
        return 'midnight';
      }
      if (hours === 12 && minutes === 0) {
        return 'noon';
      }
      timeOfDay = hours < 12 ? 'a.m.' : 'p.m.';
      hour = formatHours(hours);
      if (showMinutes(minutes, options)) {
        return hour + " " + timeOfDay;
      }
      minute = formatMinutes(minutes);
      return hour + ":" + minute + " " + timeOfDay;
    };
    dateObj.getAPDate = function(options) {
      var date, month, monthName, year;
      if (options == null) {
        options = {};
      }
      month = dateObj.getMonth();
      date = dateObj.getDate();
      year = dateObj.getFullYear();
      monthName = monthNames[month];
      if (useDayName(dateObj, options)) {
        return getDayOfWeek(dateObj);
      } else if (showYear(year, options)) {
        return monthName + " " + date;
      } else {
        return monthName + " " + date + ", " + year;
      }
    };
    return dateObj;
  };

  if (typeof module !== "undefined" && module !== null ? module.exports : void 0) {
    module.exports = Dateline;
  } else {
    this.Dateline = Dateline;
  }

  formatHours = function(hours) {
    if (hours === 0) {
      return 12;
    }
    if (hours > 12) {
      return hours - 12;
    }
    return hours;
  };

  topOfHour = function(minutes) {
    return minutes === 0;
  };

  formatMinutes = function(minutes) {
    if (minutes < 10) {
      return "0" + minutes;
    }
    return minutes;
  };

  showMinutes = function(minutes, options) {
    return topOfHour(minutes) && (options.includeMinutes == null);
  };

  monthNames = ['Jan.', 'Feb.', 'March', 'April', 'May', 'June', 'July', 'Aug.', 'Sept.', 'Oct.', 'Nov.', 'Dec.'];

  dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];

  showYear = function(year, options) {
    return (year === new Date().getFullYear()) && (options.includeYear == null);
  };

  getDayOfWeek = function(dateObj) {
    return dayNames[dateObj.getDay()];
  };

  useDayName = function(dateObj, options) {
    return (options.useDayNameForLastWeek != null) && withinSevenDays(new Date(), dateObj);
  };

  withinSevenDays = function(date1, date2) {
    var oneDayInMs, ref;
    oneDayInMs = 1000 * 60 * 60 * 24;
    return (-7 < (ref = (date2 - date1) / oneDayInMs) && ref < 0);
  };

}).call(this);
<|endoftext|>


<commit_before>import React from 'react'
import ReactDOM,{render} from 'react-dom'
import styled,{keyframes} from 'styled-components'
import DomToImage from 'dom-to-image'
/**
 * 此处花了太多精力写转场动画，实现dialog部分代码极少，动画部分的写的比较乱，没办法，react里面做这部分比较难，可以暂时忽略。
 */

const swiftIn=keyframes`
    0%{
        transform: scale(0) translate(-50%,-50%);
        left: ${props=>props.winWidth+'px'};
        top: -300px;
        filter:blur(10px);
    }
    80%{
        transform: scale(1.2) translate(-50%,-50%);
        left:${props=>props.winWidth/2+'px'};
        top:${props=>props.winHeight/2+'px'};
        filter:blur(5px);
    }
    100%{
        transform: scale(.99) translate(-50%,-50%);
        left:${props=>props.winWidth/2+'px'};
        top:${props=>props.winHeight/2+'px'};
        filter:blur(0);
    }
`;
const swiftOut=keyframes`
    0%{
        opacity:1;
    }
    30%{
        transform: scale(1) translate(-50%,-50%);
        border-radius: 50%;
        width: 300px;
        height:300px;
        left: 10%;
        top: 80%;
        opacity: .9;
    }
    100%{
        transform: scale(6) translate(0,0);
        border-radius: 50%;
        width: 300px;
        height:300px;
        opacity: 0.5;
    }
`;
const Root=styled.div`
    position:fixed;
    left:0;
    top:0;
    z-index:1000;
    background:rgba(0,0,0,.1);
    &>.dialog-root{
        min-width:500px;
        min-height:100px
        overflow:hidden;
        position:fixed;
        left:${props=>props.winWidth/2+'px'};
        top:${props=>props.winHeight/2+'px'};
        transform: scale(1) translate(-50%,-50%);
        border-radius: 10px;
        background: rgba(255, 255, 255, .9);
        box-shadow: 10px 10px 80px -10px rgba(30, 30, 30, .7);
        display:flex;
        flex-direction:column;
        align-items:center;
        &.swift-in{
            animation:${swiftIn} .3s ease-out forwards;
        }
        &.swift-out{
            animation:${swiftOut} .3s ease-out;
        }
        .btn-close{
            position:absolute;
            top:5px;
            right:5px;
            background:transparent;
            width:20px;
            height:20px;
            line-height:20px;
            border:none;
            outline:none;
            font-size:20px;
            border-radius:5px;
        }
        .btn-close:hover{
            color:red;
        }
        .dialog-content{
            align-items:center;
            justify-content:center;
        }
    }
`;
const BgLayer=styled.div`
    background-image:${props=>props.bgUrl?'url('+props.bgUrl+')':''};
    width:${props=>props.bgWidth+'px'};
    height:${props=>props.bgHeight+'px'};
    position:absolute;
    left:${props=>0-props.bgLeft+'px'};
    top:${props=>0-props.bgTop+'px'};
    filter:blur(10px);
    z-index:-1;
`;
class Dialog extends React.Component{

    state={
        visible:true,
        winWidth:0,
        winHeight:0,
        bgUrl:'',
        bgWidth:0,
        bgHeight:0,
        bgPositionX:0,
        bgPositionY:0,
        bgLeft:0,
        bgTop:0
    }

     constructor(props){
         super(props);
     }

     componentDidMount(){

         this.setState({
             winWidth:window.innerWidth,
             winHeight:window.innerHeight
         })

         this.renderBg()
     }

     renderBg(){
        let root=this.Root
        root.style.display='inherit'
        setTimeout(()=>{
            root.classList.remove('swift-in')
            this.content.style.display='flex'
            this.bgLayer && (this.bgLayer.style.display='inherit')
            const appRoot=document.querySelector('#app-main')
            DomToImage.toPng(appRoot,{quality:0.1})
                .then(url=>{
                    this.setState({bgUrl:url})
                })
            this.setState({
                    bgWidth:appRoot.offsetWidth,
                    bgHeight:appRoot.offsetHeight,
                    bgLeft:this.Root.offsetLeft-this.Root.offsetWidth/2-appRoot.offsetLeft,
                    bgTop:this.Root.offsetTop-this.Root.offsetHeight/2-appRoot.offsetTop,
                    bgPositionX:0-this.Root.offsetLeft,
                    bgPositionY:0-this.Root.offsetTop
                })
        },500)
     }

     onClose(){
         this.setState({visible:false})
         setTimeout(()=>{
            DialogOpt.hide(this.props.dkey)
         },500)
     }

     render(){
        const {content,bgBlur}=this.props
        const contentProps={
             ...this.props,
             content:null,
             mainBlur:null,
             bgBlur:null
        }
         const {visible,bgUrl,bgPositionX,bgPositionY,bgWidth,bgHeight,bgLeft,bgTop}=this.state
         return(
             <Root className='dialog-wrapper'
                winWidth={this.state.winWidth} winHeight={this.state.winHeight}
                style={{display:visible?'inherit':'',width:this.state.winWidth,height:this.state.winHeight}}>

this.Root=root}
                    className={"dialog-root"+(visible?' swift-in':' swift-out')}
                style={{display:visible?'inherit':''}}>

this.content=ct}
                        className="dialog-content"
                        style={{display:visible?'flex':'none',width:'100%',flexGrow:1}}>
                        {bgBlur?
                            <BgLayer innerRef={bgLayer=>this.bgLayer=bgLayer} style={{display:visible?'none':'none'}} bgUrl={bgUrl} bgPositionX={bgPositionX} bgPositionY={bgPositionY}
                                bgWidth={bgWidth} bgHeight={bgHeight}
                                bgLeft={bgLeft} bgTop={bgTop}/>
                            :''
                        }
                        <button className="btn-close" onClick={this.onClose.bind(this)}>×</button>
                        {
                            content?
                                typeof content==='function'?<this.props.content {...contentProps} onClose={this.onClose.bind(this)}/>
                                :typeof content==='string'?content:''
                            :''
                        }

             </Root>
         )
     }
 }
 const DialogOpt={
    dialogs:[],
    observers:[],
    onChange(){
        this.observers.forEach(observer=>{
            if (typeof observer==='function'){
                observer()
            }
        })
    },
    subscribe(observer){
        if(!this.observers.find(observer)){
            this.observers.push(observer)
        }
    },
    show(props){
        let dialog=<Dialog dkey={this.dialogs.length} key={this.dialogs.length}
            {...props}
            content={props.content || props}
            bgBlur={props.bgBlur || !props.mainBlur}/>
        this.dialogs.push({
            id:this.dialogs.length,
            dialog,
            mainBlur:props.mainBlur
        })
        this.onChange()
    },
    hide(id){
        if(id){
            this.dialogs=this.dialogs.filter(dialog=>dialog.id!==id)
        }else{
            this.dialogs.pop()
        }
        this.onChange()
    }
 }
 export default DialogOpt<commit_msg>update dialog<commit_after>import React from 'react'
import ReactDOM,{render} from 'react-dom'
import styled,{keyframes} from 'styled-components'
import DomToImage from 'dom-to-image'
/**
 * 此处花了太多精力写转场动画，实现dialog部分代码极少，动画部分的写的比较乱，没办法，react里面做这部分比较难，可以暂时忽略。
 */

const swiftIn=keyframes`
    0%{
        transform: scale(0) translate(-50%,-50%);
        left: ${props=>props.winWidth+'px'};
        top: -300px;
        filter:blur(10px);
    }
    80%{
        transform: scale(1.2) translate(-50%,-50%);
        left:${props=>props.winWidth/2+'px'};
        top:${props=>props.winHeight/2+'px'};
        filter:blur(5px);
    }
    100%{
        transform: scale(.99) translate(-50%,-50%);
        left:${props=>props.winWidth/2+'px'};
        top:${props=>props.winHeight/2+'px'};
        filter:blur(0);
    }
`;
const swiftOut=keyframes`
    0%{
        opacity:1;
    }
    30%{
        transform: scale(1) translate(-50%,-50%);
        border-radius: 50%;
        width: 300px;
        height:300px;
        left: 10%;
        top: 80%;
        opacity: .9;
    }
    100%{
        transform: scale(6) translate(0,0);
        border-radius: 50%;
        width: 300px;
        height:300px;
        opacity: 0.5;
    }
`;
const Root=styled.div`
    position:fixed;
    left:0;
    top:0;
    z-index:1000;
    background:rgba(0,0,0,.1);
    &>.dialog-root{
        min-width:500px;
        min-height:100px
        overflow:hidden;
        position:fixed;
        left:${props=>props.winWidth/2+'px'};
        top:${props=>props.winHeight/2+'px'};
        transform: scale(1) translate(-50%,-50%);
        border-radius: 10px;
        background: rgba(255, 255, 255, .9);
        box-shadow: 10px 10px 80px -10px rgba(30, 30, 30, .7);
        display:flex;
        flex-direction:column;
        align-items:center;
        &.swift-in{
            animation:${swiftIn} .3s ease-out forwards;
        }
        &.swift-out{
            animation:${swiftOut} .3s ease-out;
        }
        .btn-close{
            position:absolute;
            top:5px;
            right:5px;
            background:transparent;
            width:20px;
            height:20px;
            line-height:20px;
            border:none;
            outline:none;
            font-size:20px;
            border-radius:5px;
        }
        .btn-close:hover{
            color:red;
        }
        .dialog-content{
            align-items:center;
            justify-content:center;
        }
    }
    &.bg-blur{
        .dialog-root{
            -webkit-backdrop-filter: blur(10px);
            background: rgba(255, 255, 255, .6);
            @supports not (-webkit-backdrop-filter:blur(10px)){
                background: rgba(255, 255, 255, .95);
            }
        }
    }
`;
const BgLayer=styled.div`
    background-image:${props=>props.bgUrl?'url('+props.bgUrl+')':''};
    width:${props=>props.bgWidth+'px'};
    height:${props=>props.bgHeight+'px'};
    position:absolute;
    left:${props=>0-props.bgLeft+'px'};
    top:${props=>0-props.bgTop+'px'};
    filter:blur(10px);
    z-index:-1;
`;
class Dialog extends React.Component{

    state={
        visible:true,
        winWidth:0,
        winHeight:0,
        bgUrl:'',
        bgWidth:0,
        bgHeight:0,
        bgPositionX:0,
        bgPositionY:0,
        bgLeft:0,
        bgTop:0
    }

     constructor(props){
         super(props);
     }

     componentDidMount(){

         this.setState({
             winWidth:window.innerWidth,
             winHeight:window.innerHeight
         })

        //  this.renderBg()
     }

     renderBg(){
        let root=this.Root
        root.style.display='inherit'
        setTimeout(()=>{
            root.classList.remove('swift-in')
            this.content.style.display='flex'
            this.bgLayer && (this.bgLayer.style.display='inherit')
            const appRoot=document.querySelector('#app-main')
            DomToImage.toPng(appRoot,{quality:0.1})
                .then(url=>{
                    this.setState({bgUrl:url})
                })
            this.setState({
                    bgWidth:appRoot.offsetWidth,
                    bgHeight:appRoot.offsetHeight,
                    bgLeft:this.Root.offsetLeft-this.Root.offsetWidth/2-appRoot.offsetLeft,
                    bgTop:this.Root.offsetTop-this.Root.offsetHeight/2-appRoot.offsetTop,
                    bgPositionX:0-this.Root.offsetLeft,
                    bgPositionY:0-this.Root.offsetTop
                })
        },500)
     }

     onClose(){
        this.setState({visible:false})
        this.Root.addEventListener('animationend',()=>{
            DialogOpt.hide(this.props.dkey)
        })
     }

     render(){
        const {content,bgBlur}=this.props
        const contentProps={
             ...this.props,
             content:null,
             mainBlur:null,
             bgBlur:null
        }
         const {visible}=this.state
         return(
             <Root className={'dialog-wrapper'+(bgBlur?' bg-blur' :'')}
                winWidth={this.state.winWidth} winHeight={this.state.winHeight}
                style={{display:visible?'inherit':'',width:this.state.winWidth,height:this.state.winHeight}}>

this.Root=root}
                    className={"dialog-root"+(visible?' swift-in':' swift-out')}
                style={{display:visible?'inherit':''}}>

this.content=ct}
                        className="dialog-content"
                        style={{display:visible?'flex':'none',width:'100%',flexGrow:1}}>
                        <button className="btn-close" onClick={this.onClose.bind(this)}>×</button>
                        {
                            content?
                                typeof content==='function'?<this.props.content {...contentProps} onClose={this.onClose.bind(this)}/>
                                :typeof content==='string'?content:''
                            :''
                        }

             </Root>
         )
     }
 }
 const DialogOpt={
    dialogs:[],
    observers:[],
    onChange(){
        this.observers.forEach(observer=>{
            if (typeof observer==='function'){
                observer()
            }
        })
    },
    subscribe(observer){
        if(!this.observers.find(observer)){
            this.observers.push(observer)
        }
    },
    show(props){
        let dialog=<Dialog dkey={this.dialogs.length} key={this.dialogs.length}
            {...props}
            content={props.content || props}
            bgBlur={props.bgBlur || !props.mainBlur}/>
        this.dialogs.push({
            id:this.dialogs.length,
            dialog,
            mainBlur:props.mainBlur
        })
        this.onChange()
    },
    hide(id){
        if(id){
            this.dialogs=this.dialogs.filter(dialog=>dialog.id!==id)
        }else{
            this.dialogs.pop()
        }
        this.onChange()
    }
 }
 export default DialogOpt<|endoftext|>


<commit_before>var expect = require('chai').expect;
var Crossing = require('../crossing');

describe("Crossing Tests", function() {

  var urlList = {};

  beforeEach(function() {
    urlList = {
      'discussion:detail': '<team_slug>/<discussion_id>/<slug>/',
      'search': 'search/',
      'team:detail': '<slug>/',
      'api:lastread': '/api/v2/discussion/<discussion_id>/read/'
    };
  });

  afterEach(function() {
    urlList = {};
  });

  describe("#new Crossing()", function() {
    it('can be instantiated', function() {
      var urls = new Crossing();
      expect(urls).to.be.ok;
    });
    it('cannot be used without instantiation', function() {
      expect(Crossing).to.throw('Crossing can not be called without instatiation. Please use "new Crossing()" instead');
    });
  });

  describe("#load()", function() {
    var urls = new Crossing();

    it("loads a set of urls", function(){
      urls.load(urlList);
      expect(urls).to.be.ok;
    });
    it("has the right number of compiled urls", function() {
      expect(Object.keys(urls._compiled)).to.have.length(4);
    });
    it("has the urls properly matching the regular expressions", function() {
      expect('test/test2/test-3/'.match(urls._compiled['discussion:detail'])).to.have.length(4);
    });
  });

  describe("#get() with kwargs", function() {
    var urls = new Crossing();

    it("can get urls without parameters", function () {
      urls.load(urlList);
      expect(urls.get('search')).to.equal('search/');
    });
    it("can get urls with one parameter", function () {
      expect(urls.get('team:detail', {'slug': 'loop'})).to.equal('loop/');
    });
    it("can get urls with multiple parameters", function () {
      expect(urls.get('discussion:detail', {'team_slug': 'loop', 'discussion_id': '3', 'slug': 'discussion'})).to.equal('loop/3/discussion/');
    });
    it("can not get urls with missing parameters", function () {
      try {
        var url = urls.get('discussion:detail');
        expect(url).to.not.be.ok;
      } catch (e) {
        expect(e).to.be.instanceOf(Error);
        expect(e.message).to.equal("Missing arguments (<team_slug>, <discussion_id>, <slug>) for url <team_slug>/<discussion_id>/<slug>/");
      }
    });
  });

  describe("#get() with args", function() {
    var urls = new Crossing();

    it("can get urls with one parameter at the start", function () {
      urls.load(urlList);
      expect(urls.get('team:detail', 'loop')).to.equal('loop/');
    });
    it("can get urls with one numeric parameter at the middle", function () {
      urls.load(urlList);
      expect(urls.get('api:lastread', 16468)).to.equal('/api/v2/discussion/16468/read/');
    });
    it("can get urls with multiple parameters", function () {
      expect(urls.get('discussion:detail', 'loop', '3', 'discussion')).to.equal('loop/3/discussion/');
    });
    it("can not get urls with missing parameters", function () {
      try {
        var url = urls.get('discussion:detail', 'loop');
        expect(url).to.not.be.ok;
      } catch (e) {
        expect(e).to.be.instanceOf(Error);
        expect(e.message).to.equal("Missing arguments (<discussion_id>, <slug>) for url <team_slug>/<discussion_id>/<slug>/");
      }
    });
  });

  describe("#resolve()", function() {
    var urls = new Crossing();

    it("can resolve urls with parameters", function () {
      urls.load(urlList);
      var resolved = urls.resolve('loop/23/discussion-name/');
      expect(resolved.name).to.equal('discussion:detail');
      expect(Object.keys(resolved.kwargs).length).to.equal(3);
      expect(resolved.kwargs.team_slug).to.equal('loop');
      expect(resolved.kwargs.discussion_id).to.equal('23');
      expect(resolved.kwargs.slug).to.equal('discussion-name');
    });
    it("can resolve urls without parameters", function () {
      urls.load(urlList);
      expect(urls.resolve('search/').name).to.equal('search');
      expect(Object.keys(urls.resolve('search/').kwargs).length).to.equal(0);
    });

  });

  describe("#override matched param format", function () {
    var urls = new Crossing(new RegExp(':([a-zA-Z0-9-_%]{1,})', 'g'));
    var reactRouterPaths = {
      'discussion:detail': ':team_slug/:discussion_id/:slug/',
      'search': 'search/',
      'team:detail': ':slug/',
    };

    it("can resolve urls with parameters", function () {
      urls.load(reactRouterPaths);
      var resolved = urls.resolve('loop/23/discussion-name/');
      expect(resolved.name).to.equal('discussion:detail');
      expect(Object.keys(resolved.kwargs).length).to.equal(3);
      expect(resolved.kwargs.team_slug).to.equal('loop');
      expect(resolved.kwargs.discussion_id).to.equal('23');
      expect(resolved.kwargs.slug).to.equal('discussion-name');
    });
    it("can resolve urls without parameters", function () {
      urls.load(reactRouterPaths);
      expect(urls.resolve('search/').name).to.equal('search');
      expect(Object.keys(urls.resolve('search/').kwargs).length).to.equal(0);
    });

    it("can get urls with one parameter", function () {
      expect(urls.get('team:detail', {'slug': 'loop'})).to.equal('loop/');
    });

    it("can get urls with multiple parameters", function () {
      expect(urls.get('discussion:detail', {'team_slug': 'loop', 'discussion_id': '3', 'slug': 'discussion'})).to.equal('loop/3/discussion/');
    });
  });

  describe("#trailing slash", function () {
    it('should be sensitive to trailing slash by default', function () {
      var urls = new Crossing();
      urls.load({
        'discussion:detail': '<team_slug>/<discussion_id>/<slug>/',
        'search': 'search'
      });
      expect(urls.resolve('loop/23/discussion-name')).to.equal(undefined);
      expect(urls.resolve('loop/23/discussion-name/').name).to.equal('discussion:detail');
      expect(urls.resolve('search').name).to.equal('search');
      expect(urls.resolve('search/')).to.equal(undefined);
    });

    it('should resolve url with or without trailing slash if ignoreTrailingSlash param is true', function () {
      var urls = new Crossing(null, true);
      urls.load({
        'discussion:detail': '<team_slug>/<discussion_id>/<slug>',
        'search': 'search'
      });
      expect(urls.resolve('loop/23/discussion-name').name).to.equal('discussion:detail');
      expect(urls.resolve('loop/23/discussion-name/').name).to.equal('discussion:detail');
      expect(urls.resolve('search').name).to.equal('search');
      expect(urls.resolve('search/').name).to.equal('search');
    });
  });

});
<commit_msg>test for url with void kwargs<commit_after>var expect = require('chai').expect;
var Crossing = require('../crossing');

describe("Crossing Tests", function() {

  var urlList = {};

  beforeEach(function() {
    urlList = {
      'discussion:detail': '<team_slug>/<discussion_id>/<slug>/',
      'search': 'search/',
      'team:detail': '<slug>/',
      'api:lastread': '/api/v2/discussion/<discussion_id>/read/'
    };
  });

  afterEach(function() {
    urlList = {};
  });

  describe("#new Crossing()", function() {
    it('can be instantiated', function() {
      var urls = new Crossing();
      expect(urls).to.be.ok;
    });
    it('cannot be used without instantiation', function() {
      expect(Crossing).to.throw('Crossing can not be called without instatiation. Please use "new Crossing()" instead');
    });
  });

  describe("#load()", function() {
    var urls = new Crossing();

    it("loads a set of urls", function(){
      urls.load(urlList);
      expect(urls).to.be.ok;
    });
    it("has the right number of compiled urls", function() {
      expect(Object.keys(urls._compiled)).to.have.length(4);
    });
    it("has the urls properly matching the regular expressions", function() {
      expect('test/test2/test-3/'.match(urls._compiled['discussion:detail'])).to.have.length(4);
    });
  });

  describe("#get() with kwargs", function() {
    var urls = new Crossing();

    it("can get urls without parameters", function () {
      urls.load(urlList);
      expect(urls.get('search')).to.equal('search/');
    });
    it("can get urls with void kwargs", function () {
      urls.load(urlList);
      expect(urls.get('search',{})).to.equal('search/');
    });
    it("can get urls with one parameter", function () {
      expect(urls.get('team:detail', {'slug': 'loop'})).to.equal('loop/');
    });
    it("can get urls with multiple parameters", function () {
      expect(urls.get('discussion:detail', {'team_slug': 'loop', 'discussion_id': '3', 'slug': 'discussion'})).to.equal('loop/3/discussion/');
    });
    it("can not get urls with missing parameters", function () {
      try {
        var url = urls.get('discussion:detail');
        expect(url).to.not.be.ok;
      } catch (e) {
        expect(e).to.be.instanceOf(Error);
        expect(e.message).to.equal("Missing arguments (<team_slug>, <discussion_id>, <slug>) for url <team_slug>/<discussion_id>/<slug>/");
      }
    });
  });

  describe("#get() with args", function() {
    var urls = new Crossing();

    it("can get urls with one parameter at the start", function () {
      urls.load(urlList);
      expect(urls.get('team:detail', 'loop')).to.equal('loop/');
    });
    it("can get urls with one numeric parameter at the middle", function () {
      urls.load(urlList);
      expect(urls.get('api:lastread', 16468)).to.equal('/api/v2/discussion/16468/read/');
    });
    it("can get urls with multiple parameters", function () {
      expect(urls.get('discussion:detail', 'loop', '3', 'discussion')).to.equal('loop/3/discussion/');
    });
    it("can not get urls with missing parameters", function () {
      try {
        var url = urls.get('discussion:detail', 'loop');
        expect(url).to.not.be.ok;
      } catch (e) {
        expect(e).to.be.instanceOf(Error);
        expect(e.message).to.equal("Missing arguments (<discussion_id>, <slug>) for url <team_slug>/<discussion_id>/<slug>/");
      }
    });
  });

  describe("#resolve()", function() {
    var urls = new Crossing();

    it("can resolve urls with parameters", function () {
      urls.load(urlList);
      var resolved = urls.resolve('loop/23/discussion-name/');
      expect(resolved.name).to.equal('discussion:detail');
      expect(Object.keys(resolved.kwargs).length).to.equal(3);
      expect(resolved.kwargs.team_slug).to.equal('loop');
      expect(resolved.kwargs.discussion_id).to.equal('23');
      expect(resolved.kwargs.slug).to.equal('discussion-name');
    });
    it("can resolve urls without parameters", function () {
      urls.load(urlList);
      expect(urls.resolve('search/').name).to.equal('search');
      expect(Object.keys(urls.resolve('search/').kwargs).length).to.equal(0);
    });

  });

  describe("#override matched param format", function () {
    var urls = new Crossing(new RegExp(':([a-zA-Z0-9-_%]{1,})', 'g'));
    var reactRouterPaths = {
      'discussion:detail': ':team_slug/:discussion_id/:slug/',
      'search': 'search/',
      'team:detail': ':slug/',
    };

    it("can resolve urls with parameters", function () {
      urls.load(reactRouterPaths);
      var resolved = urls.resolve('loop/23/discussion-name/');
      expect(resolved.name).to.equal('discussion:detail');
      expect(Object.keys(resolved.kwargs).length).to.equal(3);
      expect(resolved.kwargs.team_slug).to.equal('loop');
      expect(resolved.kwargs.discussion_id).to.equal('23');
      expect(resolved.kwargs.slug).to.equal('discussion-name');
    });
    it("can resolve urls without parameters", function () {
      urls.load(reactRouterPaths);
      expect(urls.resolve('search/').name).to.equal('search');
      expect(Object.keys(urls.resolve('search/').kwargs).length).to.equal(0);
    });

    it("can get urls with one parameter", function () {
      expect(urls.get('team:detail', {'slug': 'loop'})).to.equal('loop/');
    });

    it("can get urls with multiple parameters", function () {
      expect(urls.get('discussion:detail', {'team_slug': 'loop', 'discussion_id': '3', 'slug': 'discussion'})).to.equal('loop/3/discussion/');
    });
  });

  describe("#trailing slash", function () {
    it('should be sensitive to trailing slash by default', function () {
      var urls = new Crossing();
      urls.load({
        'discussion:detail': '<team_slug>/<discussion_id>/<slug>/',
        'search': 'search'
      });
      expect(urls.resolve('loop/23/discussion-name')).to.equal(undefined);
      expect(urls.resolve('loop/23/discussion-name/').name).to.equal('discussion:detail');
      expect(urls.resolve('search').name).to.equal('search');
      expect(urls.resolve('search/')).to.equal(undefined);
    });

    it('should resolve url with or without trailing slash if ignoreTrailingSlash param is true', function () {
      var urls = new Crossing(null, true);
      urls.load({
        'discussion:detail': '<team_slug>/<discussion_id>/<slug>',
        'search': 'search'
      });
      expect(urls.resolve('loop/23/discussion-name').name).to.equal('discussion:detail');
      expect(urls.resolve('loop/23/discussion-name/').name).to.equal('discussion:detail');
      expect(urls.resolve('search').name).to.equal('search');
      expect(urls.resolve('search/').name).to.equal('search');
    });
  });

});
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var Items = require('items');
var Boom = require('boom');
var Fs = require('fs');
var Hoek = require('hoek');
var Path = require('path');

var _applyPoints = ['onRequest',
    'onPreAuth',
    'onPostAuth',
    'onPreHandler',
    'onPostHandler',
    'onPreResponse'];

var hasValidApplyPoint = function (policy) {

    return !policy.applyPoint || _applyPoints.indexOf(policy.applyPoint) !== -1;
};

// Memoizes by converting ['policy1', ..., 'policyN'] to 'policy1,...,policyN' as key
var determineAggregateApplyPoint = _.memoize(function (policyNames) {

    Hoek.assert(Array.isArray(policyNames), 'Requires array of policy names.');
    Hoek.assert(policyNames.length > 0, 'Requires non-empty array of policy names.');
    Hoek.assert(_.intersection(data.names, policyNames).length === policyNames.length, 'Requires loaded policy names.');

    var firstPolicy = policyNames[0];

    var applyPoint;
    for (var i = 0; i < _applyPoints.length; ++i) {

        // Check if the first policy appears to have this apply point.
        if (!applyPoint &&
            Object.keys(data[_applyPoints[i]]).indexOf(firstPolicy) !== -1) {

            applyPoint = _applyPoints[i];
        }
    }

    Hoek.assert(applyPoint, 'Policies must be in a valid applyPoint.');
    Hoek.assert(_.intersection(Object.keys(data[applyPoint]), policyNames).length === policyNames.length,
                'Aggregate policies must be from same applyPoint.');

    return applyPoint;
});

var data = {
    names: [],
    rawPolicies: {},
    setHandlers: {}
};

/* adding arrays, to hold the policies */
_applyPoints.forEach(function (applyPoint) {

    data[applyPoint] = {};
});

var runPolicies = function (policiesToRun, request, reply) {

    var checkPolicy = function (policy, next) {

        policy(request, reply, function (err, canContinue, message) {

            if (err) {
                // You can provide a custom hapi error object here
                return next(err);
            }
            if (canContinue) {
                return next(null, true);
            }
            return next(Boom.forbidden(message));
        });
    };

    // Use eachSeries to get quick fails and ordering
    Items.serial(policiesToRun, checkPolicy, function (err) {

        if (!reply._replied) {
            if (err) {
                return reply(err);
            }

            reply.continue();
        }
    });
};

/* generate handlers, one handler for each application point */
var handlers = {};
_applyPoints.forEach(function (applyPoint) {

    handlers[applyPoint] = function (request, reply) {

        var applyPointPolicies = data[applyPoint];
        var routePolicies = Hoek.reach(request, 'route.settings.plugins.policies');
        if (!routePolicies) {
            return reply.continue();
        }

        var repliedWithError = false;
        var policiesToRun = routePolicies.reduce(function (tmpList, routePolicy) {

            // Already replied
            if (repliedWithError) {
                return;
            }

            // Transform array to parallel, determine apply point in advance
            var aggregateApplyPoint;
            if (Array.isArray(routePolicy)) {

                aggregateApplyPoint = determineAggregateApplyPoint(routePolicy);

                if (aggregateApplyPoint === applyPoint) {
                    routePolicy = exports.parallel.apply(this, routePolicy);
                } else {
                    routePolicy = null;
                }
            }

            if (typeof routePolicy === 'string') {

                // Look for missing policies.  Probably due to misspelling.
                if (data.names.indexOf(routePolicy) === -1) {
                    repliedWithError = true;
                    return reply(Boom.notImplemented('Missing policy: ' + routePolicy));
                }

                if (applyPointPolicies[routePolicy]) {
                    tmpList.push(applyPointPolicies[routePolicy]);
                }

            } else if (typeof routePolicy === 'function') {

                if (!aggregateApplyPoint && routePolicy.runs && !routePolicy.applyPoint) {

                    aggregateApplyPoint = determineAggregateApplyPoint(routePolicy.runs);
                }

                if (!hasValidApplyPoint(routePolicy)) {
                    repliedWithError = true;
                    return reply(Boom.badImplementation('Trying to use incorrect applyPoint for the dynamic policy: ' + routePolicy.applyPoint));
                }

                var effectiveApplyPoint = routePolicy.applyPoint || aggregateApplyPoint || request.server.plugins.mrhorse.defaultApplyPoint;

                if (effectiveApplyPoint === applyPoint) {
                    tmpList.push(routePolicy);
                }

            } else if (routePolicy !== null) {

                repliedWithError = true;
                return reply(Boom.badImplementation('Policy not specified by name or by function.'));
            }

            return tmpList;
        }, []);

        // Already replied
        if (repliedWithError) {
            return;
        }

        runPolicies(policiesToRun, request, reply);
    };
});

var loadPolicies = function (server, options, next) {

    var match = null;
    var re = /(.+)\.js$/;

    options.defaultApplyPoint = options.defaultApplyPoint || 'onPreHandler'; // default application point

    var policyFiles = Fs.readdirSync(options.policyDirectory);
    if (policyFiles.length === 0) {
        return next();
    }

    var addPolicy = function (filename, addPolicyNext) {

        // Only looking for .js files in the policies folder
        match = filename.match(re);
        if (match) {
            // Does this policy already exist
            if (data.names.indexOf(match[1]) !== -1) {
                server.log(['error'], 'Trying to add a duplicate policy: ' + match[1]);
                return addPolicyNext(new Error('Trying to add a duplicate policy: ' + match[1]));
            }

            // Add this policy function to the data object
            var policy = require(Path.join(options.policyDirectory, filename));

            // Check if the apply point is correct
            if (!hasValidApplyPoint(policy)) {
                server.log(['error'], 'Trying to set incorrect applyPoint for the policy: ' + policy.applyPoint);
                return addPolicyNext(new Error('Trying to set incorrect applyPoint for the policy: ' + policy.applyPoint));
            }

            // going further, filling the policies vs application points list
            if (policy.applyPoint === undefined || policy.applyPoint) {
                var applyPoint = policy.applyPoint || options.defaultApplyPoint;

                server.log(['info'], 'Adding a new policy called ' + match[1]);
                data[applyPoint][match[1]] = policy;
                data.rawPolicies[match[1]] = policy;
                data.names.push(match[1]);

                // connect the handler if this is the first pre policy
                if (!data.setHandlers[applyPoint]) {
                    server.ext(applyPoint, handlers[applyPoint]);
                    data.setHandlers[applyPoint] = true;
                }
            }
        }

        addPolicyNext();
    };

    Items.serial(policyFiles, addPolicy, function (err) {

        next(err);
    });
};

var reset = function reset () {

    data = {
        names: [],
        rawPolicies: {},
        setHandlers: {}
    };

    /* adding arrays to hold the policies */
    _applyPoints.forEach(function (applyPoint) {

        data[applyPoint] = {};
    });
};

exports.register = function register (server, options, next) {

    options.defaultApplyPoint = options.defaultApplyPoint || 'onPreHandler'; // default application point

    Hoek.assert(_applyPoints.indexOf(options.defaultApplyPoint) !== -1, 'Specified invalid defaultApplyPoint: ' + options.defaultApplyPoint);

    server.expose('loadPolicies', loadPolicies);
    server.expose('data', data);
    server.expose('reset', reset);
    server.expose('defaultApplyPoint', options.defaultApplyPoint);

    if (options.policyDirectory !== undefined) {
        loadPolicies(server, options, function (err) {

            next(err);
        });
    }
    else {
        next();
    }
};

exports.register.attributes = {
    pkg: require('../package.json')
};

/* Policy aggregation tools */
exports.parallel = function (/*policy1, policy2, [cb]*/) {

    Hoek.assert(arguments.length, 'Requires at least one argument.');

    var args = Array.prototype.slice.call(arguments);

    // This error aggregator is used by default, giving priority to error responses
    // by the policies' listed order.
    var defaultErrorHandler = function (ranPolicies, results, next) {

        var result;
        for (var i = 0; i < ranPolicies.length; ++i) {

            result = results[ranPolicies[i]];

            if (result.err || !result.canContinue) {

                next(result.err, result.canContinue, result.message);
                break;
            }

        }

        next(null, true);
    };

    // Determine the error handler and policies we're using
    var errorHandler;
    var policyNames;
    if (typeof args[args.length - 1] === 'function') {

        errorHandler = args[args.length - 1];
        policyNames = args.slice(0, -1);
    } else {

        errorHandler = defaultErrorHandler;
        policyNames = args;
    }

    Hoek.assert(_.uniq(policyNames).length === policyNames.length, 'Listed policies must be unique.');

    // Wraps policy for use in Items.parallel.execute, never errors.
    var wrapPolicy = function (policy, request, reply) {

        return function (next) {

            policy(request, reply, function (err, canContinue, message) {

                next(null, {
                    err: err,
                    canContinue: canContinue,
                    message: message
                });
            });
        };
    };

    // Aggregate policy
    var aggregatePolicy = function (request, reply, next) {

        var policies = _(data.rawPolicies)
                        .pick(policyNames)
                        .mapValues(function (policy) {

                            return wrapPolicy(policy, request, reply);
                        }).value();

        Items.parallel.execute(policies, function (err, results) {

            Hoek.assert(!err, 'There should never be an error here because of wrapPolicy.');

            errorHandler(policyNames, results, next);
        });
    };

    // Report to MrHorse handler which policies are going to be run
    aggregatePolicy.runs = policyNames;

    // Here ya go!
    return aggregatePolicy;
};
<commit_msg>on reset, clear memoize determineAggregateApplyPoint cache.<commit_after>'use strict';

var _ = require('lodash');
var Items = require('items');
var Boom = require('boom');
var Fs = require('fs');
var Hoek = require('hoek');
var Path = require('path');

var _applyPoints = ['onRequest',
    'onPreAuth',
    'onPostAuth',
    'onPreHandler',
    'onPostHandler',
    'onPreResponse'];

var hasValidApplyPoint = function (policy) {

    return !policy.applyPoint || _applyPoints.indexOf(policy.applyPoint) !== -1;
};

// Memoizes by converting ['policy1', ..., 'policyN'] to 'policy1,...,policyN' as key
var determineAggregateApplyPoint = _.memoize(function (policyNames) {

    Hoek.assert(Array.isArray(policyNames), 'Requires array of policy names.');
    Hoek.assert(policyNames.length > 0, 'Requires non-empty array of policy names.');
    Hoek.assert(_.intersection(data.names, policyNames).length === policyNames.length, 'Requires loaded policy names.');

    var firstPolicy = policyNames[0];

    var applyPoint;
    for (var i = 0; i < _applyPoints.length; ++i) {

        // Check if the first policy appears to have this apply point.
        if (!applyPoint &&
            Object.keys(data[_applyPoints[i]]).indexOf(firstPolicy) !== -1) {

            applyPoint = _applyPoints[i];
        }
    }

    Hoek.assert(applyPoint, 'Policies must be in a valid applyPoint.');
    Hoek.assert(_.intersection(Object.keys(data[applyPoint]), policyNames).length === policyNames.length,
                'Aggregate policies must be from same applyPoint.');

    return applyPoint;
});

var data = {
    names: [],
    rawPolicies: {},
    setHandlers: {}
};

/* adding arrays, to hold the policies */
_applyPoints.forEach(function (applyPoint) {

    data[applyPoint] = {};
});

var runPolicies = function (policiesToRun, request, reply) {

    var checkPolicy = function (policy, next) {

        policy(request, reply, function (err, canContinue, message) {

            if (err) {
                // You can provide a custom hapi error object here
                return next(err);
            }
            if (canContinue) {
                return next(null, true);
            }
            return next(Boom.forbidden(message));
        });
    };

    // Use eachSeries to get quick fails and ordering
    Items.serial(policiesToRun, checkPolicy, function (err) {

        if (!reply._replied) {
            if (err) {
                return reply(err);
            }

            reply.continue();
        }
    });
};

/* generate handlers, one handler for each application point */
var handlers = {};
_applyPoints.forEach(function (applyPoint) {

    handlers[applyPoint] = function (request, reply) {

        var applyPointPolicies = data[applyPoint];
        var routePolicies = Hoek.reach(request, 'route.settings.plugins.policies');
        if (!routePolicies) {
            return reply.continue();
        }

        var repliedWithError = false;
        var policiesToRun = routePolicies.reduce(function (tmpList, routePolicy) {

            // Already replied
            if (repliedWithError) {
                return;
            }

            // Transform array to parallel, determine apply point in advance
            var aggregateApplyPoint;
            if (Array.isArray(routePolicy)) {

                aggregateApplyPoint = determineAggregateApplyPoint(routePolicy);

                if (aggregateApplyPoint === applyPoint) {
                    routePolicy = exports.parallel.apply(this, routePolicy);
                } else {
                    routePolicy = null;
                }
            }

            if (typeof routePolicy === 'string') {

                // Look for missing policies.  Probably due to misspelling.
                if (data.names.indexOf(routePolicy) === -1) {
                    repliedWithError = true;
                    return reply(Boom.notImplemented('Missing policy: ' + routePolicy));
                }

                if (applyPointPolicies[routePolicy]) {
                    tmpList.push(applyPointPolicies[routePolicy]);
                }

            } else if (typeof routePolicy === 'function') {

                if (!aggregateApplyPoint && routePolicy.runs && !routePolicy.applyPoint) {

                    aggregateApplyPoint = determineAggregateApplyPoint(routePolicy.runs);
                }

                if (!hasValidApplyPoint(routePolicy)) {
                    repliedWithError = true;
                    return reply(Boom.badImplementation('Trying to use incorrect applyPoint for the dynamic policy: ' + routePolicy.applyPoint));
                }

                var effectiveApplyPoint = routePolicy.applyPoint || aggregateApplyPoint || request.server.plugins.mrhorse.defaultApplyPoint;

                if (effectiveApplyPoint === applyPoint) {
                    tmpList.push(routePolicy);
                }

            } else if (routePolicy !== null) {

                repliedWithError = true;
                return reply(Boom.badImplementation('Policy not specified by name or by function.'));
            }

            return tmpList;
        }, []);

        // Already replied
        if (repliedWithError) {
            return;
        }

        runPolicies(policiesToRun, request, reply);
    };
});

var loadPolicies = function (server, options, next) {

    var match = null;
    var re = /(.+)\.js$/;

    options.defaultApplyPoint = options.defaultApplyPoint || 'onPreHandler'; // default application point

    var policyFiles = Fs.readdirSync(options.policyDirectory);
    if (policyFiles.length === 0) {
        return next();
    }

    var addPolicy = function (filename, addPolicyNext) {

        // Only looking for .js files in the policies folder
        match = filename.match(re);
        if (match) {
            // Does this policy already exist
            if (data.names.indexOf(match[1]) !== -1) {
                server.log(['error'], 'Trying to add a duplicate policy: ' + match[1]);
                return addPolicyNext(new Error('Trying to add a duplicate policy: ' + match[1]));
            }

            // Add this policy function to the data object
            var policy = require(Path.join(options.policyDirectory, filename));

            // Check if the apply point is correct
            if (!hasValidApplyPoint(policy)) {
                server.log(['error'], 'Trying to set incorrect applyPoint for the policy: ' + policy.applyPoint);
                return addPolicyNext(new Error('Trying to set incorrect applyPoint for the policy: ' + policy.applyPoint));
            }

            // going further, filling the policies vs application points list
            if (policy.applyPoint === undefined || policy.applyPoint) {
                var applyPoint = policy.applyPoint || options.defaultApplyPoint;

                server.log(['info'], 'Adding a new policy called ' + match[1]);
                data[applyPoint][match[1]] = policy;
                data.rawPolicies[match[1]] = policy;
                data.names.push(match[1]);

                // connect the handler if this is the first pre policy
                if (!data.setHandlers[applyPoint]) {
                    server.ext(applyPoint, handlers[applyPoint]);
                    data.setHandlers[applyPoint] = true;
                }
            }
        }

        addPolicyNext();
    };

    Items.serial(policyFiles, addPolicy, function (err) {

        next(err);
    });
};

var reset = function reset () {

    data = {
        names: [],
        rawPolicies: {},
        setHandlers: {}
    };

    /* clear memoize cache */
    determineAggregateApplyPoint.cache.__data__ = {};

    /* adding arrays to hold the policies */
    _applyPoints.forEach(function (applyPoint) {

        data[applyPoint] = {};
    });
};

exports.register = function register (server, options, next) {

    options.defaultApplyPoint = options.defaultApplyPoint || 'onPreHandler'; // default application point

    Hoek.assert(_applyPoints.indexOf(options.defaultApplyPoint) !== -1, 'Specified invalid defaultApplyPoint: ' + options.defaultApplyPoint);

    server.expose('loadPolicies', loadPolicies);
    server.expose('data', data);
    server.expose('reset', reset);
    server.expose('defaultApplyPoint', options.defaultApplyPoint);

    if (options.policyDirectory !== undefined) {
        loadPolicies(server, options, function (err) {

            next(err);
        });
    }
    else {
        next();
    }
};

exports.register.attributes = {
    pkg: require('../package.json')
};

/* Policy aggregation tools */
exports.parallel = function (/*policy1, policy2, [cb]*/) {

    Hoek.assert(arguments.length, 'Requires at least one argument.');

    var args = Array.prototype.slice.call(arguments);

    // This error aggregator is used by default, giving priority to error responses
    // by the policies' listed order.
    var defaultErrorHandler = function (ranPolicies, results, next) {

        var result;
        for (var i = 0; i < ranPolicies.length; ++i) {

            result = results[ranPolicies[i]];

            if (result.err || !result.canContinue) {

                next(result.err, result.canContinue, result.message);
                break;
            }

        }

        next(null, true);
    };

    // Determine the error handler and policies we're using
    var errorHandler;
    var policyNames;
    if (typeof args[args.length - 1] === 'function') {

        errorHandler = args[args.length - 1];
        policyNames = args.slice(0, -1);
    } else {

        errorHandler = defaultErrorHandler;
        policyNames = args;
    }

    Hoek.assert(_.uniq(policyNames).length === policyNames.length, 'Listed policies must be unique.');

    // Wraps policy for use in Items.parallel.execute, never errors.
    var wrapPolicy = function (policy, request, reply) {

        return function (next) {

            policy(request, reply, function (err, canContinue, message) {

                next(null, {
                    err: err,
                    canContinue: canContinue,
                    message: message
                });
            });
        };
    };

    // Aggregate policy
    var aggregatePolicy = function (request, reply, next) {

        var policies = _(data.rawPolicies)
                        .pick(policyNames)
                        .mapValues(function (policy) {

                            return wrapPolicy(policy, request, reply);
                        }).value();

        Items.parallel.execute(policies, function (err, results) {

            Hoek.assert(!err, 'There should never be an error here because of wrapPolicy.');

            errorHandler(policyNames, results, next);
        });
    };

    // Report to MrHorse handler which policies are going to be run
    aggregatePolicy.runs = policyNames;

    // Here ya go!
    return aggregatePolicy;
};
<|endoftext|>


<commit_before>/*
 * Copyright 2016 Resin.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @module imageStream
 */

'use strict';

const Bluebird = require('bluebird');
const utils = require('./utils');
const handlers = require('./handlers');

/**
 * @summary Get an image stream from a file
 * @function
 * @public
 *
 * @description
 * This function resolves an object containing the following properties:
 *
 * - `Number size`: The input file size.
 *
 * - `ReadableStream stream`: The input file stream.
 *
 * - `TransformStream transform`: A transform stream that performs any
 * needed transformation to get the image out of the source input file
 * (for example, decompression).
 *
 * The purpose of separating the above components is to handle cases like
 * showing a progress bar when you can't know the final uncompressed size.
 *
 * In such case, you can pipe the `stream` through a progress stream using
 * the input file `size`, and apply the `transform` after the progress stream.
 *
 * @param {String} file - file path
 * @fulfil {Object} - image stream details
 * @returns {Promise}
 *
 * @example
 * const imageStream = require('etcher-image-stream');
 *
 * imageStream.getFromFilePath('path/to/rpi.img.xz').then(function(image) {
 *   image.stream
 *     .pipe(image.transform)
 *     .pipe(fs.createWriteStream('/dev/disk2'));
 * });
 */
exports.getFromFilePath = function(file) {
  return Bluebird.try(function() {
    const type = utils.getArchiveMimeType(file);

    if (!handlers[type]) {
      throw new Error('Invalid image');
    }

    return handlers[type](file);
  });
};

/**
 * @summary Supported file types
 * @type {String[]}
 * @public
 *
 * @example
 * const imageStream = require('etcher-image-stream');
 *
 * imageStream.supportedFileTypes.forEach(function(fileType) {
 *   console.log('Supported file type: ' + fileType.extension);
 * });
 */
exports.supportedFileTypes = [
  { extension: 'zip' , type: 'archive' },
  { extension: 'gz'  , type: 'compressed' },
  { extension: 'bz2' , type: 'compressed' },
  { extension: 'xz'  , type: 'compressed' },
  { extension: 'img' , type: 'image'      },
  { extension: 'iso' , type: 'image'      },
  { extension: 'dsk' , type: 'image'      }
];
<commit_msg>Add `hddimg` to the supported file types array (#11)<commit_after>/*
 * Copyright 2016 Resin.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @module imageStream
 */

'use strict';

const Bluebird = require('bluebird');
const utils = require('./utils');
const handlers = require('./handlers');

/**
 * @summary Get an image stream from a file
 * @function
 * @public
 *
 * @description
 * This function resolves an object containing the following properties:
 *
 * - `Number size`: The input file size.
 *
 * - `ReadableStream stream`: The input file stream.
 *
 * - `TransformStream transform`: A transform stream that performs any
 * needed transformation to get the image out of the source input file
 * (for example, decompression).
 *
 * The purpose of separating the above components is to handle cases like
 * showing a progress bar when you can't know the final uncompressed size.
 *
 * In such case, you can pipe the `stream` through a progress stream using
 * the input file `size`, and apply the `transform` after the progress stream.
 *
 * @param {String} file - file path
 * @fulfil {Object} - image stream details
 * @returns {Promise}
 *
 * @example
 * const imageStream = require('etcher-image-stream');
 *
 * imageStream.getFromFilePath('path/to/rpi.img.xz').then(function(image) {
 *   image.stream
 *     .pipe(image.transform)
 *     .pipe(fs.createWriteStream('/dev/disk2'));
 * });
 */
exports.getFromFilePath = function(file) {
  return Bluebird.try(function() {
    const type = utils.getArchiveMimeType(file);

    if (!handlers[type]) {
      throw new Error('Invalid image');
    }

    return handlers[type](file);
  });
};

/**
 * @summary Supported file types
 * @type {String[]}
 * @public
 *
 * @example
 * const imageStream = require('etcher-image-stream');
 *
 * imageStream.supportedFileTypes.forEach(function(fileType) {
 *   console.log('Supported file type: ' + fileType.extension);
 * });
 */
exports.supportedFileTypes = [
  { extension: 'zip'    , type: 'archive' },
  { extension: 'gz'     , type: 'compressed' },
  { extension: 'bz2'    , type: 'compressed' },
  { extension: 'xz'     , type: 'compressed' },
  { extension: 'img'    , type: 'image'      },
  { extension: 'iso'    , type: 'image'      },
  { extension: 'dsk'    , type: 'image'      },
  { extension: 'hddimg' , type: 'image'      }
];
<|endoftext|>