code:
'use strict';

var hasSymbols = require('has-symbols/shams')();
var forEach = require('for-each');
var hasOwn = require('hasown');
var mockProperty = require('mock-property');

module.exports = function (assign, t) {
	t.test('error cases', function (st) {
		st['throws'](function () { assign(null); }, TypeError, 'target must be an object');
		st['throws'](function () { assign(undefined); }, TypeError, 'target must be an object');
		st['throws'](function () { assign(null, {}); }, TypeError, 'target must be an object');
		st['throws'](function () { assign(undefined, {}); }, TypeError, 'target must be an object');
		st.end();
	});

	t.test('non-object target, no sources', function (st) {
		var bool = assign(true);
		st.equal(typeof bool, 'object', 'bool is object');
		st.equal(Boolean.prototype.valueOf.call(bool), true, 'bool coerces to `true`');

		var number = assign(1);
		st.equal(typeof number, 'object', 'number is object');
		st.equal(Number.prototype.valueOf.call(number), 1, 'number coerces to `1`');

		var string = assign('1');
		st.equal(typeof string, 'object', 'number is object');
		st.equal(String.prototype.valueOf.call(string), '1', 'number coerces to `"1"`');

		st.end();
	});

	t.test('non-object target, with sources', function (st) {
		var signal = {};

		st.test('boolean', function (st2) {
			var bool = assign(true, { a: signal });
			st2.equal(typeof bool, 'object', 'bool is object');
			st2.equal(Boolean.prototype.valueOf.call(bool), true, 'bool coerces to `true`');
			st2.equal(bool.a, signal, 'source properties copied');
			st2.end();
		});

		st.test('number', function (st2) {
			var number = assign(1, { a: signal });
			st2.equal(typeof number, 'object', 'number is object');
			st2.equal(Number.prototype.valueOf.call(number), 1, 'number coerces to `1`');
			st2.equal(number.a, signal, 'source properties copied');
			st2.end();
		});

		st.test('string', function (st2) {
			var string = assign('1', { a: signal });
			st2.equal(typeof string, 'object', 'number is object');
			st2.equal(String.prototype.valueOf.call(string), '1', 'number coerces to `"1"`');
			st2.equal(string.a, signal, 'source properties copied');
			st2.end();
		});

		st.end();
	});

	t.test('non-object sources', function (st) {
		st.deepEqual(assign({ a: 1 }, null, { b: 2 }), { a: 1, b: 2 }, 'ignores null source');
		st.deepEqual(assign({ a: 1 }, { b: 2 }, undefined), { a: 1, b: 2 }, 'ignores undefined source');
		st.end();
	});

	t.test('returns the modified target object', function (st) {
		var target = {};
		var returned = assign(target, { a: 1 });
		st.equal(returned, target, 'returned object is the same reference as the target object');
		st.end();
	});

	t.test('has the right length', function (st) {
		st.equal(assign.length, 2, 'length is 2 => 2 required arguments');
		st.end();
	});

	t.test('merge two objects', function (st) {
		var target = { a: 1 };
		var returned = assign(target, { b: 2 });
		st.deepEqual(returned, { a: 1, b: 2 }, 'returned object has properties from both');
		st.end();
	});

	t.test('works with functions', function (st) {
		var target = function () {};
		target.a = 1;
		var returned = assign(target, { b: 2 });
		st.equal(target, returned, 'returned object is target');
		st.equal(returned.a, 1);
		st.equal(returned.b, 2);
		st.end();
	});

	t.test('works with primitives', function (st) {
		var target = 2;
		var source = { b: 42 };
		var returned = assign(target, source);
		st.equal(Object.prototype.toString.call(returned), '[object Number]', 'returned is object form of number primitive');
		st.equal(Number(returned), target, 'returned and target have same valueOf');
		st.equal(returned.b, source.b);
		st.end();
	});

	/* globals window */
	t.test('works with window.location', { skip: typeof window === 'undefined' }, function (st) {
		var target = {};
		assign(target, window.location);
		for (var prop in window.location) {
			if (hasOwn(window.location, prop)) {
				st.deepEqual(target[prop], window.location[prop], prop + ' is copied');
			}
		}
		st.end();
	});

	t.test('merge N objects', function (st) {
		var target = { a: 1 };
		var source1 = { b: 2 };
		var source2 = { c: 3 };
		var returned = assign(target, source1, source2);
		st.deepEqual(returned, { a: 1, b: 2, c: 3 }, 'returned object has properties from all sources');
		st.end();
	});

	t.test('only iterates over own keys', function (st) {
		var Foo = function () {};
		Foo.prototype.bar = true;
		var foo = new Foo();
		foo.baz = true;
		var target = { a: 1 };
		var returned = assign(target, foo);
		st.equal(returned, target, 'returned object is the same reference as the target object');
		st.deepEqual(target, { a: 1, baz: true }, 'returned object has only own properties from both');
		st.end();
	});

	t.test('includes enumerable symbols, after keys', { skip: !hasSymbols }, function (st) {
		var visited = [];
		var obj = {};
		Object.defineProperty(obj, 'a', { enumerable: true, get: function () { visited.push('a'); return 42; } });
		var symbol = Symbol('enumerable');
		Object.defineProperty(obj, symbol, {
			enumerable: true,
			get: function () { visited.push(symbol); return Infinity; }
		});
		var nonEnumSymbol = Symbol('non-enumerable');
		Object.defineProperty(obj, nonEnumSymbol, {
			enumerable: false,
			get: function () { visited.push(nonEnumSymbol); return -Infinity; }
		});
		var target = assign({}, obj);
		st.deepEqual(visited, ['a', symbol], 'key is visited first, then symbol');
		st.equal(target.a, 42, 'target.a is 42');
		st.equal(target[symbol], Infinity, 'target[symbol] is Infinity');
		st.notEqual(target[nonEnumSymbol], -Infinity, 'target[nonEnumSymbol] is not -Infinity');
		st.end();
	});

	t.test('does not fail when symbols are not present', { skip: !Object.isFrozen || Object.isFrozen(Object) }, function (st) {
		st.teardown(mockProperty(Object, 'getOwnPropertySymbols', { 'delete': true }));

		var visited = [];
		var obj = {};
		Object.defineProperty(obj, 'a', { enumerable: true, get: function () { visited.push('a'); return 42; } });
		var keys = ['a'];
		if (hasSymbols) {
			var symbol = Symbol('sym');
			Object.defineProperty(obj, symbol, {
				enumerable: true,
				get: function () { visited.push(symbol); return Infinity; }
			});
			keys.push(symbol);
		}
		var target = assign({}, obj);
		st.deepEqual(visited, keys, 'assign visits expected keys');
		st.equal(target.a, 42, 'target.a is 42');

		if (hasSymbols) {
			st.equal(target[symbol], Infinity);
		}
		st.end();
	});

	t.test('preserves correct property enumeration order', function (st) {
		var str = 'abcdefghijklmnopqrst';
		var letters = {};
		forEach(str.split(''), function (letter) {
			letters[letter] = letter;
		});

		var n = 5;
		st.comment('run the next test ' + n + ' times');
		var object = assign({}, letters);
		var actual = '';
		for (var k in object) {
			actual += k;
		}
		for (var i = 0; i < n; ++i) {
			st.equal(actual, str, 'property enumeration order should be followed');
		}
		st.end();
	});

	t.test('checks enumerability and existence, in case of modification during [[Get]]', { skip: !Object.defineProperty }, function (st) {
		var targetBvalue = {};
		var targetCvalue = {};
		var target = { b: targetBvalue, c: targetCvalue };
		var source = {};
		Object.defineProperty(source, 'a', {
			enumerable: true,
			get: function () {
				delete this.b;
				Object.defineProperty(this, 'c', { enumerable: false });
				return 'a';
			}
		});
		var sourceBvalue = {};
		var sourceCvalue = {};
		source.b = sourceBvalue;
		source.c = sourceCvalue;
		var result = assign(target, source);
		st.equal(result, target, 'sanity check: result is === target');
		st.equal(result.b, targetBvalue, 'target key not overwritten by deleted source key');
		st.equal(result.c, targetCvalue, 'target key not overwritten by non-enumerable source key');

		st.end();
	});
};


code:
# obuf - Offset buffer implementation.

Byte buffer specialized for data in chunks with special cases for dropping
bytes in the front, merging bytes in to various integer types and
abandoning buffer without penalty for previous chunk merges.

Used in spyd-transport, part of spdy support for http2.

This software is licensed under the MIT License.

By Fedor Indutny, 2015.

license: mit


code:
var Buffer = require('buffer').Buffer;

function OffsetBuffer() {
  this.offset = 0;
  this.size = 0;
  this.buffers = [];
}
module.exports = OffsetBuffer;

OffsetBuffer.prototype.isEmpty = function isEmpty() {
  return this.size === 0;
};

OffsetBuffer.prototype.clone = function clone(size) {
  var r = new OffsetBuffer();
  r.offset = this.offset;
  r.size = size;
  r.buffers = this.buffers.slice();
  return r;
};

OffsetBuffer.prototype.toChunks = function toChunks() {
  if (this.size === 0)
    return [];

  // We are going to slice it anyway
  if (this.offset !== 0) {
    this.buffers[0] = this.buffers[0].slice(this.offset);
    this.offset = 0;
  }

  var chunks = [ ];
  var off = 0;
  for (var i = 0; off <= this.size && i < this.buffers.length; i++) {
    var buf = this.buffers[i];
    off += buf.length;

    // Slice off last buffer
    if (off > this.size) {
      buf = buf.slice(0, buf.length - (off - this.size));
      this.buffers[i] = buf;
    }

    chunks.push(buf);
  }

  // If some buffers were skipped - trim length
  if (i < this.buffers.length)
    this.buffers.length = i;

  return chunks;
};

OffsetBuffer.prototype.toString = function toString(enc) {
  return this.toChunks().map(function(c) {
    return c.toString(enc);
  }).join('');
};

OffsetBuffer.prototype.use = function use(buf, off, n) {
  this.buffers = [ buf ];
  this.offset = off;
  this.size = n;
};

OffsetBuffer.prototype.push = function push(data) {
  // Ignore empty writes
  if (data.length === 0)
    return;

  this.size += data.length;
  this.buffers.push(data);
};

OffsetBuffer.prototype.has = function has(n) {
  return this.size >= n;
};

OffsetBuffer.prototype.skip = function skip(n) {
  if (this.size === 0)
    return;

  this.size -= n;

  // Fast case, skip bytes in a first buffer
  if (this.offset + n < this.buffers[0].length) {
    this.offset += n;
    return;
  }

  var left = n - (this.buffers[0].length - this.offset);
  this.offset = 0;

  for (var shift = 1; left > 0 && shift < this.buffers.length; shift++) {
    var buf = this.buffers[shift];
    if (buf.length > left) {
      this.offset = left;
      break;
    }
    left -= buf.length;
  }
  this.buffers = this.buffers.slice(shift);
};

OffsetBuffer.prototype.copy = function copy(target, targetOff, off, n) {
  if (this.size === 0)
    return;
  if (off !== 0)
    throw new Error('Unsupported offset in .copy()');

  var toff = targetOff;
  var first = this.buffers[0];
  var toCopy = Math.min(n, first.length - this.offset);
  first.copy(target, toff, this.offset, this.offset + toCopy);

  toff += toCopy;
  var left = n - toCopy;
  for (var i = 1; left > 0 && i < this.buffers.length; i++) {
    var buf = this.buffers[i];
    var toCopy = Math.min(left, buf.length);

    buf.copy(target, toff, 0, toCopy);

    toff += toCopy;
    left -= toCopy;
  }
};

OffsetBuffer.prototype.take = function take(n) {
  if (n === 0)
    return new Buffer(0);

  this.size -= n;

  // Fast cases
  var first = this.buffers[0].length - this.offset;
  if (first === n) {
    var r = this.buffers.shift();
    if (this.offset !== 0) {
      r = r.slice(this.offset);
      this.offset = 0;
    }
    return r;
  } else if (first > n) {
    var r = this.buffers[0].slice(this.offset, this.offset + n);
    this.offset += n;
    return r;
  }

  // Allocate and fill buffer
  var out = new Buffer(n);
  var toOff = 0;
  var startOff = this.offset;
  for (var i = 0; toOff !== n && i < this.buffers.length; i++) {
    var buf = this.buffers[i];
    var toCopy = Math.min(buf.length - startOff, n - toOff);

    buf.copy(out, toOff, startOff, startOff + toCopy);
    if (startOff + toCopy < buf.length) {
      this.offset = startOff + toCopy;
      break;
    } else {
      toOff += toCopy;
      startOff = 0;
    }
  }

  this.buffers = this.buffers.slice(i);
  if (this.buffers.length === 0)
    this.offset = 0;

  return out;
};

OffsetBuffer.prototype.peekUInt8 = function peekUInt8() {
  return this.buffers[0][this.offset];
};

OffsetBuffer.prototype.readUInt8 = function readUInt8() {
  this.size -= 1;
  var first = this.buffers[0];
  var r = first[this.offset];
  if (++this.offset === first.length) {
    this.offset = 0;
    this.buffers.shift();
  }

  return r;
};

OffsetBuffer.prototype.readUInt16LE = function readUInt16LE() {
  var first = this.buffers[0];
  this.size -= 2;

  var r;
  var shift;

  // Fast case - first buffer has all bytes
  if (first.length - this.offset >= 2) {
    r = first.readUInt16LE(this.offset);
    shift = 0;
    this.offset += 2;

  // One byte here - one byte there
  } else {
    r = first[this.offset] | (this.buffers[1][0] << 8);
    shift = 1;
    this.offset = 1;
  }

  if (this.offset === this.buffers[shift].length) {
    this.offset = 0;
    shift++;
  }
  if (shift !== 0)
    this.buffers = this.buffers.slice(shift);

  return r;
};

OffsetBuffer.prototype.readUInt24LE = function readUInt24LE() {
  var first = this.buffers[0];

  var r;
  var shift;
  var firstHas = first.length - this.offset;

  // Fast case - first buffer has all bytes
  if (firstHas >= 3) {
    r = first.readUInt16LE(this.offset) | (first[this.offset + 2] << 16);
    shift = 0;
    this.offset += 3;

  // First buffer has 2 of 3 bytes
  } else if (firstHas >= 2) {
    r = first.readUInt16LE(this.offset) | (this.buffers[1][0] << 16);
    shift = 1;
    this.offset = 1;

  // Slow case: First buffer has 1 of 3 bytes
  } else {
    r = first[this.offset];
    this.offset = 0;
    this.buffers.shift();
    this.size -= 1;

    r |= this.readUInt16LE() << 8;
    return r;
  }

  this.size -= 3;
  if (this.offset === this.buffers[shift].length) {
    this.offset = 0;
    shift++;
  }
  if (shift !== 0)
    this.buffers = this.buffers.slice(shift);

  return r;
};

OffsetBuffer.prototype.readUInt32LE = function readUInt32LE() {
  var first = this.buffers[0];

  var r;
  var shift;
  var firstHas = first.length - this.offset;

  // Fast case - first buffer has all bytes
  if (firstHas >= 4) {
    r = first.readUInt32LE(this.offset);
    shift = 0;
    this.offset += 4;

  // First buffer has 3 of 4 bytes
  } else if (firstHas >= 3) {
    r = (first.readUInt16LE(this.offset) |
         (first[this.offset + 2] << 16)) +
        (this.buffers[1][0] * 0x1000000);
    shift = 1;
    this.offset = 1;

  // Slow case: First buffer has 2 of 4 bytes
  } else if (firstHas >= 2) {
    r = first.readUInt16LE(this.offset);
    this.offset = 0;
    this.buffers.shift();
    this.size -= 2;

    r += this.readUInt16LE() * 0x10000;
    return r;

  // Slow case: First buffer has 1 of 4 bytes
  } else {
    r = first[this.offset];
    this.offset = 0;
    this.buffers.shift();
    this.size -= 1;

    r += this.readUInt24LE() * 0x100;
    return r;
  }

  this.size -= 4;
  if (this.offset === this.buffers[shift].length) {
    this.offset = 0;
    shift++;
  }
  if (shift !== 0)
    this.buffers = this.buffers.slice(shift);

  return r;
};

OffsetBuffer.prototype.readUInt16BE = function readUInt16BE() {
  var r = this.readUInt16LE();

  return ((r & 0xff) << 8) | (r >> 8);
};

OffsetBuffer.prototype.readUInt24BE = function readUInt24BE() {
  var r = this.readUInt24LE();

  return ((r & 0xff) << 16) | (((r >> 8) & 0xff) << 8) | (r >> 16);
};

OffsetBuffer.prototype.readUInt32BE = function readUInt32BE() {
  var r = this.readUInt32LE();

  return (((r & 0xff) << 24) |
          (((r >>> 8) & 0xff) << 16) |
          (((r >>> 16) & 0xff) << 8) |
          (r >>> 24)) >>> 0;
};

// Signed number APIs

function signedInt8(num) {
  if (num >= 0x80)
    return -(0xff ^ num) - 1;
  else
    return num;
}

OffsetBuffer.prototype.peekInt8 = function peekInt8() {
  return signedInt8(this.peekUInt8());
};

OffsetBuffer.prototype.readInt8 = function readInt8() {
  return signedInt8(this.readUInt8());
};

function signedInt16(num) {
  if (num >= 0x8000)
    return -(0xffff ^ num) - 1;
  else
    return num;
}

OffsetBuffer.prototype.readInt16BE = function readInt16BE() {
  return signedInt16(this.readUInt16BE());
};

OffsetBuffer.prototype.readInt16LE = function readInt16LE() {
  return signedInt16(this.readUInt16LE());
};

function signedInt24(num) {
  if (num >= 0x800000)
    return -(0xffffff ^ num) - 1;
  else
    return num;
}

OffsetBuffer.prototype.readInt24BE = function readInt24BE() {
  return signedInt24(this.readUInt24BE());
};

OffsetBuffer.prototype.readInt24LE = function readInt24LE() {
  return signedInt24(this.readUInt24LE());
};

function signedInt32(num) {
  if (num >= 0x80000000)
    return -(0xffffffff ^ num) - 1;
  else
    return num;
}

OffsetBuffer.prototype.readInt32BE = function readInt32BE() {
  return signedInt32(this.readUInt32BE());
};

OffsetBuffer.prototype.readInt32LE = function readInt32LE() {
  return signedInt32(this.readUInt32LE());
};


obuf

test: mocha test/**/*-test.js

type: git

keywords: Offset Buffer reader

author: Fedor Indutny <fedor@indutny.com>

license: MIT


code:
var assert = require('assert');
var OffsetBuffer = require('../');

describe('OffsetBuffer', function() {
  var o;
  beforeEach(function() {
    o = new OffsetBuffer();
  });

  describe('.take()', function() {
    it('should return empty buffer', function() {
      var b = new Buffer('hello world');
      o.push(b);
      var r = o.take(0);
      assert.equal(r.length, 0);
      assert.equal(o.size, b.length);
    });

    it('should return the first buffer itself', function() {
      var b = new Buffer('hello world');
      o.push(b);
      var r = o.take(b.length);
      assert(r === b);
      assert(o.isEmpty());
    });

    it('should return the slice of the buffer ', function() {
      var b = new Buffer('hello world');
      o.push(b);
      assert.equal(o.take(5).toString(), 'hello');
      assert.equal(o.take(1).toString(), ' ');
      assert.equal(o.take(5).toString(), 'world');
      assert(o.isEmpty());
    });

    it('should concat buffers', function() {
      o.push(new Buffer('hello'));
      o.push(new Buffer(' '));
      o.push(new Buffer('world!'));
      assert.equal(o.take(11).toString(), 'hello world');
      assert.equal(o.take(1).toString(), '!');
      assert(o.isEmpty());
    });
  });

  describe('.skip', function() {
    it('should skip bytes', function() {
      o.push(new Buffer('hello '));
      o.push(new Buffer('world'));
      o.push(new Buffer(' oh gosh'));

      assert.equal(o.take(2).toString(), 'he');
      o.skip(1);
      assert.equal(o.take(2).toString(), 'lo');
      o.skip(1);
      assert.equal(o.take(2).toString(), 'wo');
      o.skip(4);
      assert.equal(o.take(7).toString(), 'oh gosh');

      assert(o.isEmpty());
    });
  });

  describe('.peekUInt8', function() {
    it('should return and not move by one byte', function() {
      o.push(new Buffer([ 0x1, 0x2 ]));
      assert.equal(o.peekUInt8(), 1);
      assert.equal(o.readUInt8(), 1);
      assert.equal(o.peekUInt8(), 2);
      assert.equal(o.readUInt8(), 2);
      assert(o.isEmpty());
    });
  });

  describe('.peekInt8', function() {
    it('should return signed number', function() {
      o.push(new Buffer([ 0x80 ]));
      assert.equal(o.peekInt8(), -128);
      assert.equal(o.readInt8(), -128);
      assert(o.isEmpty());
    });
  });

  describe('.readUInt8', function() {
    it('should return and move by one byte', function() {
      o.push(new Buffer([ 0x1, 0x2 ]));
      o.push(new Buffer([ 0x3, 0x4 ]));
      assert.equal(o.readUInt8(), 1);
      assert.equal(o.readUInt8(), 2);
      assert.equal(o.readUInt8(), 3);
      assert.equal(o.readUInt8(), 4);
      assert(o.isEmpty());
    });
  });

  describe('.readInt8', function() {
    it('should return signed number', function() {
      o.push(new Buffer([ 0x8f, 0x7f ]));
      assert.equal(o.readInt8(), -113);
      assert.equal(o.readInt8(), 127);
      assert(o.isEmpty());
    });
  });

  describe('.readUInt16LE', function() {
    it('should return and move by two bytes', function() {
      o.push(new Buffer([ 0x1, 0x2, 0x3 ]));
      o.push(new Buffer([ 0x4, 0x5, 0x6 ]));
      assert.equal(o.readUInt16LE(), 0x0201);
      assert.equal(o.readUInt16LE(), 0x0403);
      assert.equal(o.readUInt16LE(), 0x0605);
      assert(o.isEmpty());
    });

    it('should return and move by two bytes (regression #1)', function() {
      o.push(new Buffer([ 0x1 ]));
      o.push(new Buffer([ 0x2, 0x3, 0x4 ]));
      assert.equal(o.readUInt16LE(), 0x0201);
      assert.equal(o.readUInt16LE(), 0x0403);
      assert(o.isEmpty());
    });
  });

  describe('.readInt16LE', function() {
    it('should return signed number', function() {
      o.push(new Buffer([ 0x23, 0x81 ]));
      assert.equal(o.readInt16LE(), -32477);
      assert(o.isEmpty());
    });
  });

  describe('.readUInt24LE', function() {
    it('should return and move by three bytes', function() {
      o.push(new Buffer([ 0x1, 0x2, 0x3, 0x4, 0x5 ]));
      o.push(new Buffer([ 0x6, 0x7 ]));
      o.push(new Buffer([ 0x8, 0x9 ]));
      assert.equal(o.readUInt24LE(), 0x030201);
      assert.equal(o.readUInt24LE(), 0x060504);
      assert.equal(o.readUInt24LE(), 0x090807);
      assert(o.isEmpty());
    });

    it('should return and move by three bytes (regression #1)', function() {
      o.push(new Buffer([ 0x1, 0x2 ]));
      o.push(new Buffer([ 0x3 ]));
      assert.equal(o.readUInt24LE(), 0x030201);
      assert.equal(o.buffers.length, 0);
      assert(o.isEmpty());
    });
  });

  describe('.readInt24LE', function() {
    it('should return signed number', function() {
      o.push(new Buffer([ 0x23, 0x45, 0x81 ]));
      assert.equal(o.readInt24LE(), -8305373);
      assert(o.isEmpty());
    });
  });

  describe('.readUInt32LE', function() {
    it('should return and move by four bytes', function() {
      o.push(new Buffer([ 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 ]));
      o.push(new Buffer([ 0x8, 0x9, 0xa ]));
      o.push(new Buffer([ 0xb, 0xc, 0xd ]));
      o.push(new Buffer([ 0xe, 0xf, 0x10 ]));
      assert.equal(o.readUInt32LE(), 0x04030201);
      assert.equal(o.readUInt32LE(), 0x08070605);
      assert.equal(o.readUInt32LE(), 0x0c0b0a09);
      assert.equal(o.readUInt32LE(), 0x100f0e0d);
      assert(o.isEmpty());
    });

    it('should return and move by four bytes (regression #1)', function() {
      o.push(new Buffer([ 0x1, 0x2, 0x3 ]));
      o.push(new Buffer([ 0x4 ]));
      assert.equal(o.readUInt32LE(), 0x04030201);
      assert.equal(o.buffers.length, 0);
      assert(o.isEmpty());
    });
  });

  describe('.readInt32LE', function() {
    it('should return signed number', function() {
      o.push(new Buffer([ 0xff, 0xff, 0xff, 0xff ]));
      assert.equal(o.readInt32LE(), -1);
      assert(o.isEmpty());
    });
  });

  describe('.readUInt16BE', function() {
    it('should return and move by two bytes', function() {
      o.push(new Buffer([ 0x1, 0x2, 0x3 ]));
      o.push(new Buffer([ 0x4, 0x5, 0x6 ]));
      assert.equal(o.readUInt16BE(), 0x0102);
      assert.equal(o.readUInt16BE(), 0x0304);
      assert.equal(o.readUInt16BE(), 0x0506);
      assert(o.isEmpty());
    });
  });

  describe('.readInt16BE', function() {
    it('should return signed number', function() {
      o.push(new Buffer([ 0x81, 0x23 ]));
      assert.equal(o.readInt16BE(), -32477);
      assert(o.isEmpty());
    });
  });

  describe('.readUInt24BE', function() {
    it('should return and move by three bytes', function() {
      o.push(new Buffer([ 0x1, 0x2, 0x3, 0x4, 0x5 ]));
      o.push(new Buffer([ 0x6, 0x7 ]));
      o.push(new Buffer([ 0x8, 0x9 ]));
      assert.equal(o.readUInt24BE(), 0x010203);
      assert.equal(o.readUInt24BE(), 0x040506);
      assert.equal(o.readUInt24BE(), 0x070809);
      assert(o.isEmpty());
    });
  });

  describe('.readInt24BE', function() {
    it('should return signed number', function() {
      o.push(new Buffer([ 0x81, 0x45, 0x23 ]));
      assert.equal(o.readInt24BE(), -8305373);
      assert(o.isEmpty());
    });
  });

  describe('.readUInt32BE', function() {
    it('should return and move by four bytes', function() {
      o.push(new Buffer([ 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 ]));
      o.push(new Buffer([ 0x8, 0x9, 0xa ]));
      o.push(new Buffer([ 0xb, 0xc, 0xd ]));
      o.push(new Buffer([ 0xe, 0xf, 0x10 ]));
      assert.equal(o.readUInt32BE(), 0x01020304);
      assert.equal(o.readUInt32BE(), 0x05060708);
      assert.equal(o.readUInt32BE(), 0x090a0b0c);
      assert.equal(o.readUInt32BE(), 0x0d0e0f10);
      assert(o.isEmpty());
    });

    it('should return positive values', function() {
      o.push(new Buffer([ 0xff, 0xff, 0xff, 0xff ]));
      assert.equal(o.readUInt32BE(), 0xffffffff);
      assert(o.isEmpty());
    });
  });

  describe('.readInt32BE', function() {
    it('should return signed number', function() {
      o.push(new Buffer([ 0xff, 0xff, 0xff, 0xff ]));
      assert.equal(o.readInt32BE(), -1);
      assert(o.isEmpty());
    });
  });

  describe('.has', function() {
    it('should properly check the amount of the remaining bytes', function() {
      o.push(new Buffer([ 1, 2, 3 ]));
      assert(o.has(3));
      assert.equal(o.readUInt8(), 0x01);
      assert(!o.has(3));
      assert(o.has(2));
      assert.equal(o.readUInt16BE(), 0x0203);
      assert(!o.has(1));
    });
  });
});


code:
2.4.1 / 2022-02-22
==================

  * Fix error on early async hooks implementations

2.4.0 / 2022-02-21
==================

  * Prevent loss of async hooks context

2.3.0 / 2015-05-26
==================

  * Add defined behavior for HTTP `CONNECT` requests
  * Add defined behavior for HTTP `Upgrade` requests
  * deps: ee-first@1.1.1

2.2.1 / 2015-04-22
==================

  * Fix `isFinished(req)` when data buffered

2.2.0 / 2014-12-22
==================

  * Add message object to callback arguments

2.1.1 / 2014-10-22
==================

  * Fix handling of pipelined requests

2.1.0 / 2014-08-16
==================

  * Check if `socket` is detached
  * Return `undefined` for `isFinished` if state unknown

2.0.0 / 2014-08-16
==================

  * Add `isFinished` function
  * Move to `jshttp` organization
  * Remove support for plain socket argument
  * Rename to `on-finished`
  * Support both `req` and `res` as arguments
  * deps: ee-first@1.0.5

1.2.2 / 2014-06-10
==================

  * Reduce listeners added to emitters
    - avoids "event emitter leak" warnings when used multiple times on same request

1.2.1 / 2014-06-08
==================

  * Fix returned value when already finished

1.2.0 / 2014-06-05
==================

  * Call callback when called on already-finished socket

1.1.4 / 2014-05-27
==================

  * Support node.js 0.8

1.1.3 / 2014-04-30
==================

  * Make sure errors passed as instanceof `Error`

1.1.2 / 2014-04-18
==================

  * Default the `socket` to passed-in object

1.1.1 / 2014-01-16
==================

  * Rename module to `finished`

1.1.0 / 2013-12-25
==================

  * Call callback when called on already-errored socket

1.0.1 / 2013-12-20
==================

  * Actually pass the error to the callback

1.0.0 / 2013-12-20
==================

  * Initial release


code:
# on-finished

[![NPM Version][npm-version-image]][npm-url]
[![NPM Downloads][npm-downloads-image]][npm-url]
[![Node.js Version][node-image]][node-url]
[![Build Status][ci-image]][ci-url]
[![Coverage Status][coveralls-image]][coveralls-url]

Execute a callback when a HTTP request closes, finishes, or errors.

## Install

This is a [Node.js](https://nodejs.org/en/) module available through the
[npm registry](https://www.npmjs.com/). Installation is done using the
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):

```sh
$ npm install on-finished
```

## API

```js
var onFinished = require('on-finished')
```

### onFinished(res, listener)

Attach a listener to listen for the response to finish. The listener will
be invoked only once when the response finished. If the response finished
to an error, the first argument will contain the error. If the response
has already finished, the listener will be invoked.

Listening to the end of a response would be used to close things associated
with the response, like open files.

Listener is invoked as `listener(err, res)`.

<!-- eslint-disable handle-callback-err -->

```js
onFinished(res, function (err, res) {
  // clean up open fds, etc.
  // err contains the error if request error'd
})
```

### onFinished(req, listener)

Attach a listener to listen for the request to finish. The listener will
be invoked only once when the request finished. If the request finished
to an error, the first argument will contain the error. If the request
has already finished, the listener will be invoked.

Listening to the end of a request would be used to know when to continue
after reading the data.

Listener is invoked as `listener(err, req)`.

<!-- eslint-disable handle-callback-err -->

```js
var data = ''

req.setEncoding('utf8')
req.on('data', function (str) {
  data += str
})

onFinished(req, function (err, req) {
  // data is read unless there is err
})
```

### onFinished.isFinished(res)

Determine if `res` is already finished. This would be useful to check and
not even start certain operations if the response has already finished.

### onFinished.isFinished(req)

Determine if `req` is already finished. This would be useful to check and
not even start certain operations if the request has already finished.

## Special Node.js requests

### HTTP CONNECT method

The meaning of the `CONNECT` method from RFC 7231, section 4.3.6:

> The CONNECT method requests that the recipient establish a tunnel to
> the destination origin server identified by the request-target and,
> if successful, thereafter restrict its behavior to blind forwarding
> of packets, in both directions, until the tunnel is closed.  Tunnels
> are commonly used to create an end-to-end virtual connection, through
> one or more proxies, which can then be secured using TLS (Transport
> Layer Security, [RFC5246]).

In Node.js, these request objects come from the `'connect'` event on
the HTTP server.

When this module is used on a HTTP `CONNECT` request, the request is
considered "finished" immediately, **due to limitations in the Node.js
interface**. This means if the `CONNECT` request contains a request entity,
the request will be considered "finished" even before it has been read.

There is no such thing as a response object to a `CONNECT` request in
Node.js, so there is no support for one.

### HTTP Upgrade request

The meaning of the `Upgrade` header from RFC 7230, section 6.1:

> The "Upgrade" header field is intended to provide a simple mechanism
> for transitioning from HTTP/1.1 to some other protocol on the same
> connection.

In Node.js, these request objects come from the `'upgrade'` event on
the HTTP server.

When this module is used on a HTTP request with an `Upgrade` header, the
request is considered "finished" immediately, **due to limitations in the
Node.js interface**. This means if the `Upgrade` request contains a request
entity, the request will be considered "finished" even before it has been
read.

There is no such thing as a response object to a `Upgrade` request in
Node.js, so there is no support for one.

## Example

The following code ensures that file descriptors are always closed
once the response finishes.

```js
var destroy = require('destroy')
var fs = require('fs')
var http = require('http')
var onFinished = require('on-finished')

http.createServer(function onRequest (req, res) {
  var stream = fs.createReadStream('package.json')
  stream.pipe(res)
  onFinished(res, function () {
    destroy(stream)
  })
})
```

## License

[MIT](LICENSE)

[ci-image]: https://badgen.net/github/checks/jshttp/on-finished/master?label=ci
[ci-url]: https://github.com/jshttp/on-finished/actions/workflows/ci.yml
[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/on-finished/master
[coveralls-url]: https://coveralls.io/r/jshttp/on-finished?branch=master
[node-image]: https://badgen.net/npm/node/on-finished
[node-url]: https://nodejs.org/en/download
[npm-downloads-image]: https://badgen.net/npm/dm/on-finished
[npm-url]: https://npmjs.org/package/on-finished
[npm-version-image]: https://badgen.net/npm/v/on-finished


code:
/*!
 * on-finished
 * Copyright(c) 2013 Jonathan Ong
 * Copyright(c) 2014 Douglas Christopher Wilson
 * MIT Licensed
 */

'use strict'

/**
 * Module exports.
 * @public
 */

module.exports = onFinished
module.exports.isFinished = isFinished

/**
 * Module dependencies.
 * @private
 */

var asyncHooks = tryRequireAsyncHooks()
var first = require('ee-first')

/**
 * Variables.
 * @private
 */

/* istanbul ignore next */
var defer = typeof setImmediate === 'function'
  ? setImmediate
  : function (fn) { process.nextTick(fn.bind.apply(fn, arguments)) }

/**
 * Invoke callback when the response has finished, useful for
 * cleaning up resources afterwards.
 *
 * @param {object} msg
 * @param {function} listener
 * @return {object}
 * @public
 */

function onFinished (msg, listener) {
  if (isFinished(msg) !== false) {
    defer(listener, null, msg)
    return msg
  }

  // attach the listener to the message
  attachListener(msg, wrap(listener))

  return msg
}

/**
 * Determine if message is already finished.
 *
 * @param {object} msg
 * @return {boolean}
 * @public
 */

function isFinished (msg) {
  var socket = msg.socket

  if (typeof msg.finished === 'boolean') {
    // OutgoingMessage
    return Boolean(msg.finished || (socket && !socket.writable))
  }

  if (typeof msg.complete === 'boolean') {
    // IncomingMessage
    return Boolean(msg.upgrade || !socket || !socket.readable || (msg.complete && !msg.readable))
  }

  // don't know
  return undefined
}

/**
 * Attach a finished listener to the message.
 *
 * @param {object} msg
 * @param {function} callback
 * @private
 */

function attachFinishedListener (msg, callback) {
  var eeMsg
  var eeSocket
  var finished = false

  function onFinish (error) {
    eeMsg.cancel()
    eeSocket.cancel()

    finished = true
    callback(error)
  }

  // finished on first message event
  eeMsg = eeSocket = first([[msg, 'end', 'finish']], onFinish)

  function onSocket (socket) {
    // remove listener
    msg.removeListener('socket', onSocket)

    if (finished) return
    if (eeMsg !== eeSocket) return

    // finished on first socket event
    eeSocket = first([[socket, 'error', 'close']], onFinish)
  }

  if (msg.socket) {
    // socket already assigned
    onSocket(msg.socket)
    return
  }

  // wait for socket to be assigned
  msg.on('socket', onSocket)

  if (msg.socket === undefined) {
    // istanbul ignore next: node.js 0.8 patch
    patchAssignSocket(msg, onSocket)
  }
}

/**
 * Attach the listener to the message.
 *
 * @param {object} msg
 * @return {function}
 * @private
 */

function attachListener (msg, listener) {
  var attached = msg.__onFinished

  // create a private single listener with queue
  if (!attached || !attached.queue) {
    attached = msg.__onFinished = createListener(msg)
    attachFinishedListener(msg, attached)
  }

  attached.queue.push(listener)
}

/**
 * Create listener on message.
 *
 * @param {object} msg
 * @return {function}
 * @private
 */

function createListener (msg) {
  function listener (err) {
    if (msg.__onFinished === listener) msg.__onFinished = null
    if (!listener.queue) return

    var queue = listener.queue
    listener.queue = null

    for (var i = 0; i < queue.length; i++) {
      queue[i](err, msg)
    }
  }

  listener.queue = []

  return listener
}

/**
 * Patch ServerResponse.prototype.assignSocket for node.js 0.8.
 *
 * @param {ServerResponse} res
 * @param {function} callback
 * @private
 */

// istanbul ignore next: node.js 0.8 patch
function patchAssignSocket (res, callback) {
  var assignSocket = res.assignSocket

  if (typeof assignSocket !== 'function') return

  // res.on('socket', callback) is broken in 0.8
  res.assignSocket = function _assignSocket (socket) {
    assignSocket.call(this, socket)
    callback(socket)
  }
}

/**
 * Try to require async_hooks
 * @private
 */

function tryRequireAsyncHooks () {
  try {
    return require('async_hooks')
  } catch (e) {
    return {}
  }
}

/**
 * Wrap function with async resource, if possible.
 * AsyncResource.bind static method backported.
 * @private
 */

function wrap (fn) {
  var res

  // create anonymous resource
  if (asyncHooks.AsyncResource) {
    res = new asyncHooks.AsyncResource(fn.name || 'bound-anonymous-fn')
  }

  // incompatible node.js
  if (!res || !res.runInAsyncScope) {
    return fn
  }

  // return bound function
  return res.runInAsyncScope.bind(res, fn, null)
}


on-finished

description: Execute a callback when a request closes, finishes, or errors

contributors: Douglas Christopher Wilson <doug@somethingdoug.com>

contributors: Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)

repository: jshttp/on-finished

lint: eslint .

test: mocha --reporter spec --bail --check-leaks test/

test-ci: nyc --reporter=lcovonly --reporter=text npm test

test-cov: nyc --reporter=html --reporter=text npm test


code:
1.0.2 / 2019-02-21
==================

  * Fix `res.writeHead` patch missing return value

1.0.1 / 2015-09-29
==================

  * perf: enable strict mode

1.0.0 / 2014-08-10
==================

  * Honor `res.statusCode` change in `listener`
  * Move to `jshttp` organization
  * Prevent `arguments`-related de-opt

0.0.0 / 2014-05-13
==================

  * Initial implementation


code:
# on-headers

[![NPM Version][npm-version-image]][npm-url]
[![NPM Downloads][npm-downloads-image]][npm-url]
[![Node.js Version][node-version-image]][node-version-url]
[![Build Status][travis-image]][travis-url]
[![Test Coverage][coveralls-image]][coveralls-url]

Execute a listener when a response is about to write headers.

## Installation

This is a [Node.js](https://nodejs.org/en/) module available through the
[npm registry](https://www.npmjs.com/). Installation is done using the
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):

```sh
$ npm install on-headers
```

## API

<!-- eslint-disable no-unused-vars -->

```js
var onHeaders = require('on-headers')
```

### onHeaders(res, listener)

This will add the listener `listener` to fire when headers are emitted for `res`.
The listener is passed the `response` object as it's context (`this`). Headers are
considered to be emitted only once, right before they are sent to the client.

When this is called multiple times on the same `res`, the `listener`s are fired
in the reverse order they were added.

## Examples

```js
var http = require('http')
var onHeaders = require('on-headers')

http
  .createServer(onRequest)
  .listen(3000)

function addPoweredBy () {
  // set if not set by end of request
  if (!this.getHeader('X-Powered-By')) {
    this.setHeader('X-Powered-By', 'Node.js')
  }
}

function onRequest (req, res) {
  onHeaders(res, addPoweredBy)

  res.setHeader('Content-Type', 'text/plain')
  res.end('hello!')
}
```

## Testing

```sh
$ npm test
```

## License

[MIT](LICENSE)

[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/on-headers/master
[coveralls-url]: https://coveralls.io/r/jshttp/on-headers?branch=master
[node-version-image]: https://badgen.net/npm/node/on-headers
[node-version-url]: https://nodejs.org/en/download
[npm-downloads-image]: https://badgen.net/npm/dm/on-headers
[npm-url]: https://npmjs.org/package/on-headers
[npm-version-image]: https://badgen.net/npm/v/on-headers
[travis-image]: https://badgen.net/travis/jshttp/on-headers/master
[travis-url]: https://travis-ci.org/jshttp/on-headers


code:
/*!
 * on-headers
 * Copyright(c) 2014 Douglas Christopher Wilson
 * MIT Licensed
 */

'use strict'

/**
 * Module exports.
 * @public
 */

module.exports = onHeaders

/**
 * Create a replacement writeHead method.
 *
 * @param {function} prevWriteHead
 * @param {function} listener
 * @private
 */

function createWriteHead (prevWriteHead, listener) {
  var fired = false

  // return function with core name and argument list
  return function writeHead (statusCode) {
    // set headers from arguments
    var args = setWriteHeadHeaders.apply(this, arguments)

    // fire listener
    if (!fired) {
      fired = true
      listener.call(this)

      // pass-along an updated status code
      if (typeof args[0] === 'number' && this.statusCode !== args[0]) {
        args[0] = this.statusCode
        args.length = 1
      }
    }

    return prevWriteHead.apply(this, args)
  }
}

/**
 * Execute a listener when a response is about to write headers.
 *
 * @param {object} res
 * @return {function} listener
 * @public
 */

function onHeaders (res, listener) {
  if (!res) {
    throw new TypeError('argument res is required')
  }

  if (typeof listener !== 'function') {
    throw new TypeError('argument listener must be a function')
  }

  res.writeHead = createWriteHead(res.writeHead, listener)
}

/**
 * Set headers contained in array on the response object.
 *
 * @param {object} res
 * @param {array} headers
 * @private
 */

function setHeadersFromArray (res, headers) {
  for (var i = 0; i < headers.length; i++) {
    res.setHeader(headers[i][0], headers[i][1])
  }
}

/**
 * Set headers contained in object on the response object.
 *
 * @param {object} res
 * @param {object} headers
 * @private
 */

function setHeadersFromObject (res, headers) {
  var keys = Object.keys(headers)
  for (var i = 0; i < keys.length; i++) {
    var k = keys[i]
    if (k) res.setHeader(k, headers[k])
  }
}

/**
 * Set headers and other properties on the response object.
 *
 * @param {number} statusCode
 * @private
 */

function setWriteHeadHeaders (statusCode) {
  var length = arguments.length
  var headerIndex = length > 1 && typeof arguments[1] === 'string'
    ? 2
    : 1

  var headers = length >= headerIndex + 1
    ? arguments[headerIndex]
    : undefined

  this.statusCode = statusCode

  if (Array.isArray(headers)) {
    // handle array case
    setHeadersFromArray(this, headers)
  } else if (headers) {
    // handle object case
    setHeadersFromObject(this, headers)
  }

  // copy leading arguments
  var args = new Array(Math.min(length, headerIndex))
  for (var i = 0; i < args.length; i++) {
    args[i] = arguments[i]
  }

  return args
}


on-headers

description: Execute a listener when a response is about to write headers

author: Douglas Christopher Wilson <doug@somethingdoug.com>

keywords: event headers http onheaders

repository: jshttp/on-headers

lint: eslint --plugin markdown --ext js,md .

test-cov: istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/

test-travis: istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/

version: node scripts/version-history.js && git add HISTORY.md


code:
# once

Only call a function once.

## usage

```javascript
var once = require('once')

function load (file, cb) {
  cb = once(cb)
  loader.load('file')
  loader.once('load', cb)
  loader.once('error', cb)
}
```

Or add to the Function.prototype in a responsible way:

```javascript
// only has to be done once
require('once').proto()

function load (file, cb) {
  cb = cb.once()
  loader.load('file')
  loader.once('load', cb)
  loader.once('error', cb)
}
```

Ironically, the prototype feature makes this module twice as
complicated as necessary.

To check whether you function has been called, use `fn.called`. Once the
function is called for the first time the return value of the original
function is saved in `fn.value` and subsequent calls will continue to
return this value.

```javascript
var once = require('once')

function load (cb) {
  cb = once(cb)
  var stream = createStream()
  stream.once('data', cb)
  stream.once('end', function () {
    if (!cb.called) cb(new Error('not found'))
  })
}
```

## `once.strict(func)`

Throw an error if the function is called twice.

Some functions are expected to be called only once. Using `once` for them would
potentially hide logical errors.

In the example below, the `greet` function has to call the callback only once:

```javascript
function greet (name, cb) {
  // return is missing from the if statement
  // when no name is passed, the callback is called twice
  if (!name) cb('Hello anonymous')
  cb('Hello ' + name)
}

function log (msg) {
  console.log(msg)
}

// this will print 'Hello anonymous' but the logical error will be missed
greet(null, once(msg))

// once.strict will print 'Hello anonymous' and throw an error when the callback will be called the second time
greet(null, once.strict(msg))
```


code:
var wrappy = require('wrappy')
module.exports = wrappy(once)
module.exports.strict = wrappy(onceStrict)

once.proto = once(function () {
  Object.defineProperty(Function.prototype, 'once', {
    value: function () {
      return once(this)
    },
    configurable: true
  })

  Object.defineProperty(Function.prototype, 'onceStrict', {
    value: function () {
      return onceStrict(this)
    },
    configurable: true
  })
})

function once (fn) {
  var f = function () {
    if (f.called) return f.value
    f.called = true
    return f.value = fn.apply(this, arguments)
  }
  f.called = false
  return f
}

function onceStrict (fn) {
  var f = function () {
    if (f.called)
      throw new Error(f.onceError)
    f.called = true
    return f.value = fn.apply(this, arguments)
  }
  var name = fn.name || 'Function wrapped with `once`'
  f.onceError = name + " shouldn't be called more than once"
  f.called = false
  return f
}


once

description: Run a function exactly one time

test: test

test: tap test/*.js

keywords: once function one single

author: Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)

license: ISC


code:
declare namespace onetime {
	interface Options {
		/**
		Throw an error when called more than once.

		@default false
		*/
		throw?: boolean;
	}
}

declare const onetime: {
	/**
	Ensure a function is only called once. When called multiple times it will return the return value from the first call.

	@param fn - Function that should only be called once.
	@returns A function that only calls `fn` once.

	@example
	```
	import onetime = require('onetime');

	let i = 0;

	const foo = onetime(() => ++i);

	foo(); //=> 1
	foo(); //=> 1
	foo(); //=> 1

	onetime.callCount(foo); //=> 3
	```
	*/
	<ArgumentsType extends unknown[], ReturnType>(
		fn: (...arguments: ArgumentsType) => ReturnType,
		options?: onetime.Options
	): (...arguments: ArgumentsType) => ReturnType;

	/**
	Get the number of times `fn` has been called.

	@param fn - Function to get call count from.
	@returns A number representing how many times `fn` has been called.

	@example
	```
	import onetime = require('onetime');

	const foo = onetime(() => {});
	foo();
	foo();
	foo();

	console.log(onetime.callCount(foo));
	//=> 3
	```
	*/
	callCount(fn: (...arguments: any[]) => unknown): number;

	// TODO: Remove this for the next major release
	default: typeof onetime;
};

export = onetime;


code:
'use strict';
const mimicFn = require('mimic-fn');

const calledFunctions = new WeakMap();

const onetime = (function_, options = {}) => {
	if (typeof function_ !== 'function') {
		throw new TypeError('Expected a function');
	}

	let returnValue;
	let callCount = 0;
	const functionName = function_.displayName || function_.name || '<anonymous>';

	const onetime = function (...arguments_) {
		calledFunctions.set(onetime, ++callCount);

		if (callCount === 1) {
			returnValue = function_.apply(this, arguments_);
			function_ = null;
		} else if (options.throw === true) {
			throw new Error(`Function \`${functionName}\` can only be called once`);
		}

		return returnValue;
	};

	mimicFn(onetime, function_);
	calledFunctions.set(onetime, callCount);

	return onetime;
};

module.exports = onetime;
// TODO: Remove this for the next major release
module.exports.default = onetime;

module.exports.callCount = function_ => {
	if (!calledFunctions.has(function_)) {
		throw new Error(`The given function \`${function_.name}\` is not wrapped by the \`onetime\` package`);
	}

	return calledFunctions.get(function_);
};


onetime

description: Ensure a function is only called once

repository: sindresorhus/onetime

test: xo && ava && tsd

keywords: once function one onetime func fn single call called prevent


code:
# onetime [![Build Status](https://travis-ci.com/sindresorhus/onetime.svg?branch=master)](https://travis-ci.com/github/sindresorhus/onetime)

> Ensure a function is only called once

When called multiple times it will return the return value from the first call.

*Unlike the module [once](https://github.com/isaacs/once), this one isn't naughty and extending `Function.prototype`.*

## Install

```
$ npm install onetime
```

## Usage

```js
const onetime = require('onetime');

let i = 0;

const foo = onetime(() => ++i);

foo(); //=> 1
foo(); //=> 1
foo(); //=> 1

onetime.callCount(foo); //=> 3
```

```js
const onetime = require('onetime');

const foo = onetime(() => {}, {throw: true});

foo();

foo();
//=> Error: Function `foo` can only be called once
```

## API

### onetime(fn, options?)

Returns a function that only calls `fn` once.

#### fn

Type: `Function`

Function that should only be called once.

#### options

Type: `object`

##### throw

Type: `boolean`\
Default: `false`

Throw an error when called more than once.

### onetime.callCount(fn)

Returns a number representing how many times `fn` has been called.

Note: It throws an error if you pass in a function that is not wrapped by `onetime`.

```js
const onetime = require('onetime');

const foo = onetime(() => {});

foo();
foo();
foo();

console.log(onetime.callCount(foo));
//=> 3
```

#### fn

Type: `Function`

Function to get call count from.

## onetime for enterprise

Available as part of the Tidelift Subscription.

The maintainers of onetime and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-onetime?utm_source=npm-onetime&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)


code:
import {ChildProcess} from 'child_process';

declare namespace open {
	interface Options {
		/**
		Wait for the opened app to exit before fulfilling the promise. If `false` it's fulfilled immediately when opening the app.

		Note that it waits for the app to exit, not just for the window to close.

		On Windows, you have to explicitly specify an app for it to be able to wait.

		@default false
		*/
		readonly wait?: boolean;

		/**
		__macOS only__

		Do not bring the app to the foreground.

		@default false
		*/
		readonly background?: boolean;

		/**
		__macOS only__

		Open a new instance of the app even it's already running.

		A new instance is always opened on other platforms.

		@default false
		*/
		readonly newInstance?: boolean;

		/**
		Specify the `name` of the app to open the `target` with, and optionally, app `arguments`. `app` can be an array of apps to try to open and `name` can be an array of app names to try. If each app fails, the last error will be thrown.

		The app name is platform dependent. Don't hard code it in reusable modules. For example, Chrome is `google chrome` on macOS, `google-chrome` on Linux and `chrome` on Windows. If possible, use [`open.apps`](#openapps) which auto-detects the correct binary to use.

		You may also pass in the app's full path. For example on WSL, this can be `/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe` for the Windows installation of Chrome.

		The app `arguments` are app dependent. Check the app's documentation for what arguments it accepts.
		*/
		readonly app?: App | readonly App[];

		/**
		Allow the opened app to exit with nonzero exit code when the `wait` option is `true`.

		We do not recommend setting this option. The convention for success is exit code zero.

		@default false
		*/
		readonly allowNonzeroExitCode?: boolean;
	}

	interface OpenAppOptions extends Omit<Options, 'app'> {
		/**
		Arguments passed to the app.

		These arguments are app dependent. Check the app's documentation for what arguments it accepts.
		*/
		readonly arguments?: readonly string[];
	}

	type AppName =
		| 'chrome'
		| 'firefox'
		| 'edge';

	type App = {
		name: string | readonly string[];
		arguments?: readonly string[];
	};
}

// eslint-disable-next-line no-redeclare
declare const open: {
	/**
	Open stuff like URLs, files, executables. Cross-platform.

	Uses the command `open` on macOS, `start` on Windows and `xdg-open` on other platforms.

	There is a caveat for [double-quotes on Windows](https://github.com/sindresorhus/open#double-quotes-on-windows) where all double-quotes are stripped from the `target`.

	@param target - The thing you want to open. Can be a URL, file, or executable. Opens in the default app for the file type. For example, URLs open in your default browser.
	@returns The [spawned child process](https://nodejs.org/api/child_process.html#child_process_class_childprocess). You would normally not need to use this for anything, but it can be useful if you'd like to attach custom event listeners or perform other operations directly on the spawned process.

	@example
	```
	import open = require('open');

	// Opens the image in the default image viewer
	await open('unicorn.png', {wait: true});
	console.log('The image viewer app closed');

	// Opens the url in the default browser
	await open('https://sindresorhus.com');

	// Opens the URL in a specified browser.
	await open('https://sindresorhus.com', {app: {name: 'firefox'}});

	// Specify app arguments.
	await open('https://sindresorhus.com', {app: {name: 'google chrome', arguments: ['--incognito']}});
	```
	*/
	(
		target: string,
		options?: open.Options
	): Promise<ChildProcess>;

	/**
	An object containing auto-detected binary names for common apps. Useful to work around cross-platform differences.

	@example
	```
	import open = require('open');

	await open('https://google.com', {
		app: {
			name: open.apps.chrome
		}
	});
	```
	*/
	apps: Record<open.AppName, string | readonly string[]>;

	/**
	Open an app. Cross-platform.

	Uses the command `open` on macOS, `start` on Windows and `xdg-open` on other platforms.

	@param name - The app you want to open. Can be either builtin supported `open.apps` names or other name supported in platform.
	@returns The [spawned child process](https://nodejs.org/api/child_process.html#child_process_class_childprocess). You would normally not need to use this for anything, but it can be useful if you'd like to attach custom event listeners or perform other operations directly on the spawned process.

	@example
	```
	const {apps, openApp} = require('open');

	// Open Firefox
	await openApp(apps.firefox);

	// Open Chrome incognito mode
	await openApp(apps.chrome, {arguments: ['--incognito']});

	// Open Xcode
	await openApp('xcode');
	```
	*/
	openApp: (name: open.App['name'], options?: open.OpenAppOptions) => Promise<ChildProcess>;
};

export = open;


code:
const path = require('path');
const childProcess = require('child_process');
const {promises: fs, constants: fsConstants} = require('fs');
const isWsl = require('is-wsl');
const isDocker = require('is-docker');
const defineLazyProperty = require('define-lazy-prop');

// Path to included `xdg-open`.
const localXdgOpenPath = path.join(__dirname, 'xdg-open');

const {platform, arch} = process;

// Podman detection
const hasContainerEnv = () => {
	try {
		fs.statSync('/run/.containerenv');
		return true;
	} catch {
		return false;
	}
};

let cachedResult;
function isInsideContainer() {
	if (cachedResult === undefined) {
		cachedResult = hasContainerEnv() || isDocker();
	}

	return cachedResult;
}

/**
Get the mount point for fixed drives in WSL.

@inner
@returns {string} The mount point.
*/
const getWslDrivesMountPoint = (() => {
	// Default value for "root" param
	// according to https://docs.microsoft.com/en-us/windows/wsl/wsl-config
	const defaultMountPoint = '/mnt/';

	let mountPoint;

	return async function () {
		if (mountPoint) {
			// Return memoized mount point value
			return mountPoint;
		}

		const configFilePath = '/etc/wsl.conf';

		let isConfigFileExists = false;
		try {
			await fs.access(configFilePath, fsConstants.F_OK);
			isConfigFileExists = true;
		} catch {}

		if (!isConfigFileExists) {
			return defaultMountPoint;
		}

		const configContent = await fs.readFile(configFilePath, {encoding: 'utf8'});
		const configMountPoint = /(?<!#.*)root\s*=\s*(?<mountPoint>.*)/g.exec(configContent);

		if (!configMountPoint) {
			return defaultMountPoint;
		}

		mountPoint = configMountPoint.groups.mountPoint.trim();
		mountPoint = mountPoint.endsWith('/') ? mountPoint : `${mountPoint}/`;

		return mountPoint;
	};
})();

const pTryEach = async (array, mapper) => {
	let latestError;

	for (const item of array) {
		try {
			return await mapper(item); // eslint-disable-line no-await-in-loop
		} catch (error) {
			latestError = error;
		}
	}

	throw latestError;
};

const baseOpen = async options => {
	options = {
		wait: false,
		background: false,
		newInstance: false,
		allowNonzeroExitCode: false,
		...options
	};

	if (Array.isArray(options.app)) {
		return pTryEach(options.app, singleApp => baseOpen({
			...options,
			app: singleApp
		}));
	}

	let {name: app, arguments: appArguments = []} = options.app || {};
	appArguments = [...appArguments];

	if (Array.isArray(app)) {
		return pTryEach(app, appName => baseOpen({
			...options,
			app: {
				name: appName,
				arguments: appArguments
			}
		}));
	}

	let command;
	const cliArguments = [];
	const childProcessOptions = {};

	if (platform === 'darwin') {
		command = 'open';

		if (options.wait) {
			cliArguments.push('--wait-apps');
		}

		if (options.background) {
			cliArguments.push('--background');
		}

		if (options.newInstance) {
			cliArguments.push('--new');
		}

		if (app) {
			cliArguments.push('-a', app);
		}
	} else if (platform === 'win32' || (isWsl && !isInsideContainer() && !app)) {
		const mountPoint = await getWslDrivesMountPoint();

		command = isWsl ?
			`${mountPoint}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe` :
			`${process.env.SYSTEMROOT}\\System32\\WindowsPowerShell\\v1.0\\powershell`;

		cliArguments.push(
			'-NoProfile',
			'-NonInteractive',
			'–ExecutionPolicy',
			'Bypass',
			'-EncodedCommand'
		);

		if (!isWsl) {
			childProcessOptions.windowsVerbatimArguments = true;
		}

		const encodedArguments = ['Start'];

		if (options.wait) {
			encodedArguments.push('-Wait');
		}

		if (app) {
			// Double quote with double quotes to ensure the inner quotes are passed through.
			// Inner quotes are delimited for PowerShell interpretation with backticks.
			encodedArguments.push(`"\`"${app}\`""`, '-ArgumentList');
			if (options.target) {
				appArguments.unshift(options.target);
			}
		} else if (options.target) {
			encodedArguments.push(`"${options.target}"`);
		}

		if (appArguments.length > 0) {
			appArguments = appArguments.map(arg => `"\`"${arg}\`""`);
			encodedArguments.push(appArguments.join(','));
		}

		// Using Base64-encoded command, accepted by PowerShell, to allow special characters.
		options.target = Buffer.from(encodedArguments.join(' '), 'utf16le').toString('base64');
	} else {
		if (app) {
			command = app;
		} else {
			// When bundled by Webpack, there's no actual package file path and no local `xdg-open`.
			const isBundled = !__dirname || __dirname === '/';

			// Check if local `xdg-open` exists and is executable.
			let exeLocalXdgOpen = false;
			try {
				await fs.access(localXdgOpenPath, fsConstants.X_OK);
				exeLocalXdgOpen = true;
			} catch {}

			const useSystemXdgOpen = process.versions.electron ||
				platform === 'android' || isBundled || !exeLocalXdgOpen;
			command = useSystemXdgOpen ? 'xdg-open' : localXdgOpenPath;
		}

		if (appArguments.length > 0) {
			cliArguments.push(...appArguments);
		}

		if (!options.wait) {
			// `xdg-open` will block the process unless stdio is ignored
			// and it's detached from the parent even if it's unref'd.
			childProcessOptions.stdio = 'ignore';
			childProcessOptions.detached = true;
		}
	}

	if (options.target) {
		cliArguments.push(options.target);
	}

	if (platform === 'darwin' && appArguments.length > 0) {
		cliArguments.push('--args', ...appArguments);
	}

	const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);

	if (options.wait) {
		return new Promise((resolve, reject) => {
			subprocess.once('error', reject);

			subprocess.once('close', exitCode => {
				if (!options.allowNonzeroExitCode && exitCode > 0) {
					reject(new Error(`Exited with code ${exitCode}`));
					return;
				}

				resolve(subprocess);
			});
		});
	}

	subprocess.unref();

	return subprocess;
};

const open = (target, options) => {
	if (typeof target !== 'string') {
		throw new TypeError('Expected a `target`');
	}

	return baseOpen({
		...options,
		target
	});
};

const openApp = (name, options) => {
	if (typeof name !== 'string') {
		throw new TypeError('Expected a `name`');
	}

	const {arguments: appArguments = []} = options || {};
	if (appArguments !== undefined && appArguments !== null && !Array.isArray(appArguments)) {
		throw new TypeError('Expected `appArguments` as Array type');
	}

	return baseOpen({
		...options,
		app: {
			name,
			arguments: appArguments
		}
	});
};

function detectArchBinary(binary) {
	if (typeof binary === 'string' || Array.isArray(binary)) {
		return binary;
	}

	const {[arch]: archBinary} = binary;

	if (!archBinary) {
		throw new Error(`${arch} is not supported`);
	}

	return archBinary;
}

function detectPlatformBinary({[platform]: platformBinary}, {wsl}) {
	if (wsl && isWsl) {
		return detectArchBinary(wsl);
	}

	if (!platformBinary) {
		throw new Error(`${platform} is not supported`);
	}

	return detectArchBinary(platformBinary);
}

const apps = {};

defineLazyProperty(apps, 'chrome', () => detectPlatformBinary({
	darwin: 'google chrome',
	win32: 'chrome',
	linux: ['google-chrome', 'google-chrome-stable', 'chromium']
}, {
	wsl: {
		ia32: '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe',
		x64: ['/mnt/c/Program Files/Google/Chrome/Application/chrome.exe', '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe']
	}
}));

defineLazyProperty(apps, 'firefox', () => detectPlatformBinary({
	darwin: 'firefox',
	win32: 'C:\\Program Files\\Mozilla Firefox\\firefox.exe',
	linux: 'firefox'
}, {
	wsl: '/mnt/c/Program Files/Mozilla Firefox/firefox.exe'
}));

defineLazyProperty(apps, 'edge', () => detectPlatformBinary({
	darwin: 'microsoft edge',
	win32: 'msedge',
	linux: ['microsoft-edge', 'microsoft-edge-dev']
}, {
	wsl: '/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'
}));

open.apps = apps;
open.openApp = openApp;

module.exports = open;


open

description: Open stuff like URLs, files, executables. Cross-platform.

repository: sindresorhus/open

test: xo && tsd

keywords: app open opener opens launch start xdg-open xdg default cmd browser editor executable exe url urls arguments args spawn exec child process website file


code:
# open

> Open stuff like URLs, files, executables. Cross-platform.

This is meant to be used in command-line tools and scripts, not in the browser.

If you need this for Electron, use [`shell.openPath()`](https://www.electronjs.org/docs/api/shell#shellopenpathpath) instead.

This package does not make any security guarantees. If you pass in untrusted input, it's up to you to properly sanitize it.

#### Why?

- Actively maintained.
- Supports app arguments.
- Safer as it uses `spawn` instead of `exec`.
- Fixes most of the original `node-open` issues.
- Includes the latest [`xdg-open` script](https://gitlab.freedesktop.org/xdg/xdg-utils/-/blob/master/scripts/xdg-open.in) for Linux.
- Supports WSL paths to Windows apps.

## Install

```sh
npm install open
```

## Usage

```js
const open = require('open');

// Opens the image in the default image viewer and waits for the opened app to quit.
await open('unicorn.png', {wait: true});
console.log('The image viewer app quit');

// Opens the URL in the default browser.
await open('https://sindresorhus.com');

// Opens the URL in a specified browser.
await open('https://sindresorhus.com', {app: {name: 'firefox'}});

// Specify app arguments.
await open('https://sindresorhus.com', {app: {name: 'google chrome', arguments: ['--incognito']}});

// Open an app
await open.openApp('xcode');

// Open an app with arguments
await open.openApp(open.apps.chrome, {arguments: ['--incognito']});
```

## API

It uses the command `open` on macOS, `start` on Windows and `xdg-open` on other platforms.

### open(target, options?)

Returns a promise for the [spawned child process](https://nodejs.org/api/child_process.html#child_process_class_childprocess). You would normally not need to use this for anything, but it can be useful if you'd like to attach custom event listeners or perform other operations directly on the spawned process.

#### target

Type: `string`

The thing you want to open. Can be a URL, file, or executable.

Opens in the default app for the file type. For example, URLs opens in your default browser.

#### options

Type: `object`

##### wait

Type: `boolean`\
Default: `false`

Wait for the opened app to exit before fulfilling the promise. If `false` it's fulfilled immediately when opening the app.

Note that it waits for the app to exit, not just for the window to close.

On Windows, you have to explicitly specify an app for it to be able to wait.

##### background (macOS only)

Type: `boolean`\
Default: `false`

Do not bring the app to the foreground.

##### newInstance (macOS only)

Type: `boolean`\
Default: `false`

Open a new instance of the app even it's already running.

A new instance is always opened on other platforms.

##### app

Type: `{name: string | string[], arguments?: string[]} | Array<{name: string | string[], arguments: string[]}>`

Specify the `name` of the app to open the `target` with, and optionally, app `arguments`. `app` can be an array of apps to try to open and `name` can be an array of app names to try. If each app fails, the last error will be thrown.

The app name is platform dependent. Don't hard code it in reusable modules. For example, Chrome is `google chrome` on macOS, `google-chrome` on Linux and `chrome` on Windows. If possible, use [`open.apps`](#openapps) which auto-detects the correct binary to use.

You may also pass in the app's full path. For example on WSL, this can be `/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe` for the Windows installation of Chrome.

The app `arguments` are app dependent. Check the app's documentation for what arguments it accepts.

##### allowNonzeroExitCode

Type: `boolean`\
Default: `false`

Allow the opened app to exit with nonzero exit code when the `wait` option is `true`.

We do not recommend setting this option. The convention for success is exit code zero.

### open.apps

An object containing auto-detected binary names for common apps. Useful to work around [cross-platform differences](#app).

```js
const open = require('open');

await open('https://google.com', {
	app: {
		name: open.apps.chrome
	}
});
```

#### Supported apps

- [`chrome`](https://www.google.com/chrome) - Web browser
- [`firefox`](https://www.mozilla.org/firefox) - Web browser
- [`edge`](https://www.microsoft.com/edge) - Web browser

### open.openApp(name, options?)

Open an app.

Returns a promise for the [spawned child process](https://nodejs.org/api/child_process.html#child_process_class_childprocess). You would normally not need to use this for anything, but it can be useful if you'd like to attach custom event listeners or perform other operations directly on the spawned process.

#### name

Type: `string`

The app name is platform dependent. Don't hard code it in reusable modules. For example, Chrome is `google chrome` on macOS, `google-chrome` on Linux and `chrome` on Windows. If possible, use [`open.apps`](#openapps) which auto-detects the correct binary to use.

You may also pass in the app's full path. For example on WSL, this can be `/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe` for the Windows installation of Chrome.

#### options

Type: `object`

Same options as [`open`](#options) except `app` and with the following additions:

##### arguments

Type: `string[]`\
Default: `[]`

Arguments passed to the app.

These arguments are app dependent. Check the app's documentation for what arguments it accepts.

## Related

- [open-cli](https://github.com/sindresorhus/open-cli) - CLI for this module
- [open-editor](https://github.com/sindresorhus/open-editor) - Open files in your editor at a specific line and column


code:
#!/bin/sh
#---------------------------------------------
#   xdg-open
#
#   Utility script to open a URL in the registered default application.
#
#   Refer to the usage() function below for usage.
#
#   Copyright 2009-2010, Fathi Boudra <fabo@freedesktop.org>
#   Copyright 2009-2010, Rex Dieter <rdieter@fedoraproject.org>
#   Copyright 2006, Kevin Krammer <kevin.krammer@gmx.at>
#   Copyright 2006, Jeremy White <jwhite@codeweavers.com>
#
#   LICENSE:
#
#   Permission is hereby granted, free of charge, to any person obtaining a
#   copy of this software and associated documentation files (the "Software"),
#   to deal in the Software without restriction, including without limitation
#   the rights to use, copy, modify, merge, publish, distribute, sublicense,
#   and/or sell copies of the Software, and to permit persons to whom the
#   Software is furnished to do so, subject to the following conditions:
#
#   The above copyright notice and this permission notice shall be included
#   in all copies or substantial portions of the Software.
#
#   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
#   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
#   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
#   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
#   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
#   OTHER DEALINGS IN THE SOFTWARE.
#
#---------------------------------------------

manualpage()
{
cat << _MANUALPAGE
Name

   xdg-open -- opens a file or URL in the user's preferred
   application

Synopsis

   xdg-open { file | URL }

   xdg-open { --help | --manual | --version }

Description

   xdg-open opens a file or URL in the user's preferred
   application. If a URL is provided the URL will be opened in the
   user's preferred web browser. If a file is provided the file
   will be opened in the preferred application for files of that
   type. xdg-open supports file, ftp, http and https URLs.

   xdg-open is for use inside a desktop session only. It is not
   recommended to use xdg-open as root.

Options

   --help
          Show command synopsis.

   --manual
          Show this manual page.

   --version
          Show the xdg-utils version information.

Exit Codes

   An exit code of 0 indicates success while a non-zero exit code
   indicates failure. The following failure codes can be returned:

   1
          Error in command line syntax.

   2
          One of the files passed on the command line did not
          exist.

   3
          A required tool could not be found.

   4
          The action failed.

See Also

   xdg-mime(1), xdg-settings(1), MIME applications associations
   specification

Examples

xdg-open 'http://www.freedesktop.org/'

   Opens the freedesktop.org website in the user's default
   browser.

xdg-open /tmp/foobar.png

   Opens the PNG image file /tmp/foobar.png in the user's default
   image viewing application.
_MANUALPAGE
}

usage()
{
cat << _USAGE
   xdg-open -- opens a file or URL in the user's preferred
   application

Synopsis

   xdg-open { file | URL }

   xdg-open { --help | --manual | --version }

_USAGE
}

#@xdg-utils-common@

#----------------------------------------------------------------------------
#   Common utility functions included in all XDG wrapper scripts
#----------------------------------------------------------------------------

DEBUG()
{
  [ -z "${XDG_UTILS_DEBUG_LEVEL}" ] && return 0;
  [ ${XDG_UTILS_DEBUG_LEVEL} -lt $1 ] && return 0;
  shift
  echo "$@" >&2
}

# This handles backslashes but not quote marks.
first_word()
{
    read first rest
    echo "$first"
}

#-------------------------------------------------------------
# map a binary to a .desktop file
binary_to_desktop_file()
{
    search="${XDG_DATA_HOME:-$HOME/.local/share}:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
    binary="`which "$1"`"
    binary="`readlink -f "$binary"`"
    base="`basename "$binary"`"
    IFS=:
    for dir in $search; do
        unset IFS
        [ "$dir" ] || continue
        [ -d "$dir/applications" ] || [ -d "$dir/applnk" ] || continue
        for file in "$dir"/applications/*.desktop "$dir"/applications/*/*.desktop "$dir"/applnk/*.desktop "$dir"/applnk/*/*.desktop; do
            [ -r "$file" ] || continue
            # Check to make sure it's worth the processing.
            grep -q "^Exec.*$base" "$file" || continue
            # Make sure it's a visible desktop file (e.g. not "preferred-web-browser.desktop").
            grep -Eq "^(NoDisplay|Hidden)=true" "$file" && continue
            command="`grep -E "^Exec(\[[^]=]*])?=" "$file" | cut -d= -f 2- | first_word`"
            command="`which "$command"`"
            if [ x"`readlink -f "$command"`" = x"$binary" ]; then
                # Fix any double slashes that got added path composition
                echo "$file" | sed -e 's,//*,/,g'
                return
            fi
        done
    done
}

#-------------------------------------------------------------
# map a .desktop file to a binary
desktop_file_to_binary()
{
    search="${XDG_DATA_HOME:-$HOME/.local/share}:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
    desktop="`basename "$1"`"
    IFS=:
    for dir in $search; do
        unset IFS
        [ "$dir" ] && [ -d "$dir/applications" ] || [ -d "$dir/applnk" ] || continue
        # Check if desktop file contains -
        if [ "${desktop#*-}" != "$desktop" ]; then
            vendor=${desktop%-*}
            app=${desktop#*-}
            if [ -r $dir/applications/$vendor/$app ]; then
                file_path=$dir/applications/$vendor/$app
            elif [ -r $dir/applnk/$vendor/$app ]; then
                file_path=$dir/applnk/$vendor/$app
            fi
        fi
        if test -z "$file_path" ; then
            for indir in "$dir"/applications/ "$dir"/applications/*/ "$dir"/applnk/ "$dir"/applnk/*/; do
                file="$indir/$desktop"
                if [ -r "$file" ]; then
                    file_path=$file
                    break
                fi
            done
        fi
        if [ -r "$file_path" ]; then
            # Remove any arguments (%F, %f, %U, %u, etc.).
            command="`grep -E "^Exec(\[[^]=]*])?=" "$file_path" | cut -d= -f 2- | first_word`"
            command="`which "$command"`"
            readlink -f "$command"
            return
        fi
    done
}

#-------------------------------------------------------------
# Exit script on successfully completing the desired operation

exit_success()
{
    if [ $# -gt 0 ]; then
        echo "$@"
        echo
    fi

    exit 0
}

#-----------------------------------------
# Exit script on malformed arguments, not enough arguments
# or missing required option.
# prints usage information

exit_failure_syntax()
{
    if [ $# -gt 0 ]; then
        echo "xdg-open: $@" >&2
        echo "Try 'xdg-open --help' for more information." >&2
    else
        usage
        echo "Use 'man xdg-open' or 'xdg-open --manual' for additional info."
    fi

    exit 1
}

#-------------------------------------------------------------
# Exit script on missing file specified on command line

exit_failure_file_missing()
{
    if [ $# -gt 0 ]; then
        echo "xdg-open: $@" >&2
    fi

    exit 2
}

#-------------------------------------------------------------
# Exit script on failure to locate necessary tool applications

exit_failure_operation_impossible()
{
    if [ $# -gt 0 ]; then
        echo "xdg-open: $@" >&2
    fi

    exit 3
}

#-------------------------------------------------------------
# Exit script on failure returned by a tool application

exit_failure_operation_failed()
{
    if [ $# -gt 0 ]; then
        echo "xdg-open: $@" >&2
    fi

    exit 4
}

#------------------------------------------------------------
# Exit script on insufficient permission to read a specified file

exit_failure_file_permission_read()
{
    if [ $# -gt 0 ]; then
        echo "xdg-open: $@" >&2
    fi

    exit 5
}

#------------------------------------------------------------
# Exit script on insufficient permission to write a specified file

exit_failure_file_permission_write()
{
    if [ $# -gt 0 ]; then
        echo "xdg-open: $@" >&2
    fi

    exit 6
}

check_input_file()
{
    if [ ! -e "$1" ]; then
        exit_failure_file_missing "file '$1' does not exist"
    fi
    if [ ! -r "$1" ]; then
        exit_failure_file_permission_read "no permission to read file '$1'"
    fi
}

check_vendor_prefix()
{
    file_label="$2"
    [ -n "$file_label" ] || file_label="filename"
    file=`basename "$1"`
    case "$file" in
       [[:alpha:]]*-*)
         return
         ;;
    esac

    echo "xdg-open: $file_label '$file' does not have a proper vendor prefix" >&2
    echo 'A vendor prefix consists of alpha characters ([a-zA-Z]) and is terminated' >&2
    echo 'with a dash ("-"). An example '"$file_label"' is '"'example-$file'" >&2
    echo "Use --novendor to override or 'xdg-open --manual' for additional info." >&2
    exit 1
}

check_output_file()
{
    # if the file exists, check if it is writeable
    # if it does not exists, check if we are allowed to write on the directory
    if [ -e "$1" ]; then
        if [ ! -w "$1" ]; then
            exit_failure_file_permission_write "no permission to write to file '$1'"
        fi
    else
        DIR=`dirname "$1"`
        if [ ! -w "$DIR" ] || [ ! -x "$DIR" ]; then
            exit_failure_file_permission_write "no permission to create file '$1'"
        fi
    fi
}

#----------------------------------------
# Checks for shared commands, e.g. --help

check_common_commands()
{
    while [ $# -gt 0 ] ; do
        parm="$1"
        shift

        case "$parm" in
            --help)
            usage
            echo "Use 'man xdg-open' or 'xdg-open --manual' for additional info."
            exit_success
            ;;

            --manual)
            manualpage
            exit_success
            ;;

            --version)
            echo "xdg-open 1.1.3"
            exit_success
            ;;
        esac
    done
}

check_common_commands "$@"

[ -z "${XDG_UTILS_DEBUG_LEVEL}" ] && unset XDG_UTILS_DEBUG_LEVEL;
if [ ${XDG_UTILS_DEBUG_LEVEL-0} -lt 1 ]; then
    # Be silent
    xdg_redirect_output=" > /dev/null 2> /dev/null"
else
    # All output to stderr
    xdg_redirect_output=" >&2"
fi

#--------------------------------------
# Checks for known desktop environments
# set variable DE to the desktop environments name, lowercase

detectDE()
{
    # see https://bugs.freedesktop.org/show_bug.cgi?id=34164
    unset GREP_OPTIONS

    if [ -n "${XDG_CURRENT_DESKTOP}" ]; then
      case "${XDG_CURRENT_DESKTOP}" in
         # only recently added to menu-spec, pre-spec X- still in use
         Cinnamon|X-Cinnamon)
           DE=cinnamon;
           ;;
         ENLIGHTENMENT)
           DE=enlightenment;
           ;;
         # GNOME, GNOME-Classic:GNOME, or GNOME-Flashback:GNOME
         GNOME*)
           DE=gnome;
           ;;
         KDE)
           DE=kde;
           ;;
         # Deepin Desktop Environments
         DEEPIN|Deepin|deepin)
           DE=dde;
           ;;
         LXDE)
           DE=lxde;
           ;;
         LXQt)
           DE=lxqt;
           ;;
         MATE)
           DE=mate;
           ;;
         XFCE)
           DE=xfce
           ;;
         X-Generic)
           DE=generic
           ;;
      esac
    fi

    if [ x"$DE" = x"" ]; then
      # classic fallbacks
      if [ x"$KDE_FULL_SESSION" != x"" ]; then DE=kde;
      elif [ x"$GNOME_DESKTOP_SESSION_ID" != x"" ]; then DE=gnome;
      elif [ x"$MATE_DESKTOP_SESSION_ID" != x"" ]; then DE=mate;
      elif `dbus-send --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.GetNameOwner string:org.gnome.SessionManager > /dev/null 2>&1` ; then DE=gnome;
      elif xprop -root _DT_SAVE_MODE 2> /dev/null | grep ' = \"xfce4\"$' >/dev/null 2>&1; then DE=xfce;
      elif xprop -root 2> /dev/null | grep -i '^xfce_desktop_window' >/dev/null 2>&1; then DE=xfce
      elif echo $DESKTOP | grep -q '^Enlightenment'; then DE=enlightenment;
      elif [ x"$LXQT_SESSION_CONFIG" != x"" ]; then DE=lxqt;
      fi
    fi

    if [ x"$DE" = x"" ]; then
      # fallback to checking $DESKTOP_SESSION
      case "$DESKTOP_SESSION" in
         gnome)
           DE=gnome;
           ;;
         LXDE|Lubuntu)
           DE=lxde;
           ;;
         MATE)
           DE=mate;
           ;;
         xfce|xfce4|'Xfce Session')
           DE=xfce;
           ;;
      esac
    fi

    if [ x"$DE" = x"" ]; then
      # fallback to uname output for other platforms
      case "$(uname 2>/dev/null)" in
        CYGWIN*)
          DE=cygwin;
          ;;
        Darwin)
          DE=darwin;
          ;;
      esac
    fi

    if [ x"$DE" = x"gnome" ]; then
      # gnome-default-applications-properties is only available in GNOME 2.x
      # but not in GNOME 3.x
      which gnome-default-applications-properties > /dev/null 2>&1  || DE="gnome3"
    fi

    if [ -f "$XDG_RUNTIME_DIR/flatpak-info" ]; then
      DE="flatpak"
    fi
}

#----------------------------------------------------------------------------
# kfmclient exec/openURL can give bogus exit value in KDE <= 3.5.4
# It also always returns 1 in KDE 3.4 and earlier
# Simply return 0 in such case

kfmclient_fix_exit_code()
{
    version=`LC_ALL=C.UTF-8 kde-config --version 2>/dev/null | grep '^KDE'`
    major=`echo $version | sed 's/KDE.*: \([0-9]\).*/\1/'`
    minor=`echo $version | sed 's/KDE.*: [0-9]*\.\([0-9]\).*/\1/'`
    release=`echo $version | sed 's/KDE.*: [0-9]*\.[0-9]*\.\([0-9]\).*/\1/'`
    test "$major" -gt 3 && return $1
    test "$minor" -gt 5 && return $1
    test "$release" -gt 4 && return $1
    return 0
}

#----------------------------------------------------------------------------
# Returns true if there is a graphical display attached.

has_display()
{
    if [ -n "$DISPLAY" ] || [ -n "$WAYLAND_DISPLAY" ]; then
        return 0
    else
        return 1
    fi
}

# This handles backslashes but not quote marks.
last_word()
{
    read first rest
    echo "$rest"
}

# Get the value of a key in a desktop file's Desktop Entry group.
# Example: Use get_key foo.desktop Exec
# to get the values of the Exec= key for the Desktop Entry group.
get_key()
{
    local file="${1}"
    local key="${2}"
    local desktop_entry=""

    IFS_="${IFS}"
    IFS=""
    while read line
    do
        case "$line" in
            "[Desktop Entry]")
                desktop_entry="y"
            ;;
            # Reset match flag for other groups
            "["*)
                desktop_entry=""
            ;;
            "${key}="*)
                # Only match Desktop Entry group
                if [ -n "${desktop_entry}" ]
                then
                    echo "${line}" | cut -d= -f 2-
                fi
        esac
    done < "${file}"
    IFS="${IFS_}"
}

# Returns true if argument is a file:// URL or path
is_file_url_or_path()
{
    if echo "$1" | grep -q '^file://' \
            || ! echo "$1" | egrep -q '^[[:alpha:]+\.\-]+:'; then
        return 0
    else
        return 1
    fi
}

# If argument is a file URL, convert it to a (percent-decoded) path.
# If not, leave it as it is.
file_url_to_path()
{
    local file="$1"
    if echo "$file" | grep -q '^file:///'; then
        file=${file#file://}
        file=${file%%#*}
        file=$(echo "$file" | sed -r 's/\?.*$//')
        local printf=printf
        if [ -x /usr/bin/printf ]; then
            printf=/usr/bin/printf
        fi
        file=$($printf "$(echo "$file" | sed -e 's@%\([a-f0-9A-F]\{2\}\)@\\x\1@g')")
    fi
    echo "$file"
}

open_cygwin()
{
    cygstart "$1"

    if [ $? -eq 0 ]; then
        exit_success
    else
        exit_failure_operation_failed
    fi
}

open_darwin()
{
    open "$1"

    if [ $? -eq 0 ]; then
        exit_success
    else
        exit_failure_operation_failed
    fi
}

open_kde()
{
    if [ -n "${KDE_SESSION_VERSION}" ]; then
      case "${KDE_SESSION_VERSION}" in
        4)
          kde-open "$1"
        ;;
        5)
          kde-open${KDE_SESSION_VERSION} "$1"
        ;;
      esac
    else
        kfmclient exec "$1"
        kfmclient_fix_exit_code $?
    fi

    if [ $? -eq 0 ]; then
        exit_success
    else
        exit_failure_operation_failed
    fi
}

open_dde()
{
    if dde-open -version >/dev/null 2>&1; then
        dde-open "$1"
    else
        open_generic "$1"
    fi

    if [ $? -eq 0 ]; then
        exit_success
    else
        exit_failure_operation_failed
    fi
}

open_gnome3()
{
    if gio help open 2>/dev/null 1>&2; then
        gio open "$1"
    elif gvfs-open --help 2>/dev/null 1>&2; then
        gvfs-open "$1"
    else
        open_generic "$1"
    fi

    if [ $? -eq 0 ]; then
        exit_success
    else
        exit_failure_operation_failed
    fi
}

open_gnome()
{
    if gio help open 2>/dev/null 1>&2; then
        gio open "$1"
    elif gvfs-open --help 2>/dev/null 1>&2; then
        gvfs-open "$1"
    elif gnome-open --help 2>/dev/null 1>&2; then
        gnome-open "$1"
    else
        open_generic "$1"
    fi

    if [ $? -eq 0 ]; then
        exit_success
    else
        exit_failure_operation_failed
    fi
}

open_mate()
{
    if gio help open 2>/dev/null 1>&2; then
        gio open "$1"
    elif gvfs-open --help 2>/dev/null 1>&2; then
        gvfs-open "$1"
    elif mate-open --help 2>/dev/null 1>&2; then
        mate-open "$1"
    else
        open_generic "$1"
    fi

    if [ $? -eq 0 ]; then
        exit_success
    else
        exit_failure_operation_failed
    fi
}

open_xfce()
{
    if exo-open --help 2>/dev/null 1>&2; then
        exo-open "$1"
    elif gio help open 2>/dev/null 1>&2; then
        gio open "$1"
    elif gvfs-open --help 2>/dev/null 1>&2; then
        gvfs-open "$1"
    else
        open_generic "$1"
    fi

    if [ $? -eq 0 ]; then
        exit_success
    else
        exit_failure_operation_failed
    fi
}

open_enlightenment()
{
    if enlightenment_open --help 2>/dev/null 1>&2; then
        enlightenment_open "$1"
    else
        open_generic "$1"
    fi

    if [ $? -eq 0 ]; then
        exit_success
    else
        exit_failure_operation_failed
    fi
}

open_flatpak()
{
    gdbus call --session \
        --dest org.freedesktop.portal.Desktop \
        --object-path /org/freedesktop/portal/desktop \
        --method org.freedesktop.portal.OpenURI.OpenURI \
        "" "$1" {}

    if [ $? -eq 0 ]; then
        exit_success
    else
        exit_failure_operation_failed
    fi
}

#-----------------------------------------
# Recursively search .desktop file

search_desktop_file()
{
    local default="$1"
    local dir="$2"
    local target="$3"

    local file=""
    # look for both vendor-app.desktop, vendor/app.desktop
    if [ -r "$dir/$default" ]; then
      file="$dir/$default"
    elif [ -r "$dir/`echo $default | sed -e 's|-|/|'`" ]; then
      file="$dir/`echo $default | sed -e 's|-|/|'`"
    fi

    if [ -r "$file" ] ; then
        command="$(get_key "${file}" "Exec" | first_word)"
        command_exec=`which $command 2>/dev/null`
        icon="$(get_key "${file}" "Icon")"
        # FIXME: Actually LC_MESSAGES should be used as described in
        # http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s04.html
        localised_name="$(get_key "${file}" "Name")"
        set -- $(get_key "${file}" "Exec" | last_word)
        # We need to replace any occurrence of "%f", "%F" and
        # the like by the target file. We examine each
        # argument and append the modified argument to the
        # end then shift.
        local args=$#
        local replaced=0
        while [ $args -gt 0 ]; do
            case $1 in
                %[c])
                    replaced=1
                    arg="${localised_name}"
                    shift
                    set -- "$@" "$arg"
                    ;;
                %[fFuU])
                    replaced=1
                    arg="$target"
                    shift
                    set -- "$@" "$arg"
                    ;;
                %[i])
                    replaced=1
                    shift
                    set -- "$@" "--icon" "$icon"
                    ;;
                *)
                    arg="$1"
                    shift
                    set -- "$@" "$arg"
                    ;;
            esac
            args=$(( $args - 1 ))
        done
        [ $replaced -eq 1 ] || set -- "$@" "$target"
        "$command_exec" "$@"

        if [ $? -eq 0 ]; then
            exit_success
        fi
    fi

    for d in $dir/*/; do
        [ -d "$d" ] && search_desktop_file "$default" "$d" "$target"
    done
}

open_generic_xdg_mime()
{
    filetype="$2"
    default=`xdg-mime query default "$filetype"`
    if [ -n "$default" ] ; then
        xdg_user_dir="$XDG_DATA_HOME"
        [ -n "$xdg_user_dir" ] || xdg_user_dir="$HOME/.local/share"

        xdg_system_dirs="$XDG_DATA_DIRS"
        [ -n "$xdg_system_dirs" ] || xdg_system_dirs=/usr/local/share/:/usr/share/

DEBUG 3 "$xdg_user_dir:$xdg_system_dirs"
        for x in `echo "$xdg_user_dir:$xdg_system_dirs" | sed 's/:/ /g'`; do
            search_desktop_file "$default" "$x/applications/" "$1"
        done
    fi
}

open_generic_xdg_file_mime()
{
    filetype=`xdg-mime query filetype "$1" | sed "s/;.*//"`
    open_generic_xdg_mime "$1" "$filetype"
}

open_generic_xdg_x_scheme_handler()
{
    scheme="`echo $1 | sed -n 's/\(^[[:alnum:]+\.-]*\):.*$/\1/p'`"
    if [ -n $scheme ]; then
        filetype="x-scheme-handler/$scheme"
        open_generic_xdg_mime "$1" "$filetype"
    fi
}

has_single_argument()
{
  test $# = 1
}

open_envvar()
{
    local oldifs="$IFS"
    local browser browser_with_arg

    IFS=":"
    for browser in $BROWSER; do
        IFS="$oldifs"

        if [ -z "$browser" ]; then
            continue
        fi

        if echo "$browser" | grep -q %s; then
            # Avoid argument injection.
            # See https://bugs.freedesktop.org/show_bug.cgi?id=103807
            # URIs don't have IFS characters spaces anyway.
            has_single_argument $1 && $(printf "$browser" "$1")
        else
            $browser "$1"
        fi

        if [ $? -eq 0 ]; then
            exit_success
        fi
    done
}

open_generic()
{
    if is_file_url_or_path "$1"; then
        local file="$(file_url_to_path "$1")"

        check_input_file "$file"

        if has_display; then
            filetype=`xdg-mime query filetype "$file" | sed "s/;.*//"`
            open_generic_xdg_mime "$file" "$filetype"
        fi

        if which run-mailcap 2>/dev/null 1>&2; then
            run-mailcap --action=view "$file"
            if [ $? -eq 0 ]; then
                exit_success
            fi
        fi

        if has_display && mimeopen -v 2>/dev/null 1>&2; then
            mimeopen -L -n "$file"
            if [ $? -eq 0 ]; then
                exit_success
            fi
        fi
    fi

    if has_display; then
        open_generic_xdg_x_scheme_handler "$1"
    fi

    if [ -n "$BROWSER" ]; then
        open_envvar "$1"
    fi

    # if BROWSER variable is not set, check some well known browsers instead
    if [ x"$BROWSER" = x"" ]; then
        BROWSER=www-browser:links2:elinks:links:lynx:w3m
        if has_display; then
            BROWSER=x-www-browser:firefox:iceweasel:seamonkey:mozilla:epiphany:konqueror:chromium:chromium-browser:google-chrome:microsoft-edge:$BROWSER
        fi
    fi

    open_envvar "$1"

    exit_failure_operation_impossible "no method available for opening '$1'"
}

open_lxde()
{

    # pcmanfm only knows how to handle file:// urls and filepaths, it seems.
    if pcmanfm --help >/dev/null 2>&1 && is_file_url_or_path "$1"; then
        local file="$(file_url_to_path "$1")"

        # handle relative paths
        if ! echo "$file" | grep -q ^/; then
            file="$(pwd)/$file"
        fi

        pcmanfm "$file"
    else
        open_generic "$1"
    fi

    if [ $? -eq 0 ]; then
        exit_success
    else
        exit_failure_operation_failed
    fi
}

open_lxqt()
{
    open_generic "$1"
}

[ x"$1" != x"" ] || exit_failure_syntax

url=
while [ $# -gt 0 ] ; do
    parm="$1"
    shift

    case "$parm" in
      -*)
        exit_failure_syntax "unexpected option '$parm'"
        ;;

      *)
        if [ -n "$url" ] ; then
            exit_failure_syntax "unexpected argument '$parm'"
        fi
        url="$parm"
        ;;
    esac
done

if [ -z "${url}" ] ; then
    exit_failure_syntax "file or URL argument missing"
fi

detectDE

if [ x"$DE" = x"" ]; then
    DE=generic
fi

DEBUG 2 "Selected DE $DE"

# sanitize BROWSER (avoid caling ourselves in particular)
case "${BROWSER}" in
    *:"xdg-open"|"xdg-open":*)
        BROWSER=$(echo $BROWSER | sed -e 's|:xdg-open||g' -e 's|xdg-open:||g')
        ;;
    "xdg-open")
        BROWSER=
        ;;
esac

case "$DE" in
    kde)
    open_kde "$url"
    ;;

    dde)
    open_dde "$url"
    ;;

    gnome3|cinnamon)
    open_gnome3 "$url"
    ;;

    gnome)
    open_gnome "$url"
    ;;

    mate)
    open_mate "$url"
    ;;

    xfce)
    open_xfce "$url"
    ;;

    lxde)
    open_lxde "$url"
    ;;

    lxqt)
    open_lxqt "$url"
    ;;

    enlightenment)
    open_enlightenment "$url"
    ;;

    cygwin)
    open_cygwin "$url"
    ;;

    darwin)
    open_darwin "$url"
    ;;

    flatpak)
    open_flatpak "$url"
    ;;

    generic)
    open_generic "$url"
    ;;

    *)
    exit_failure_operation_impossible "no method available for opening '$url'"
    ;;
esac


code:
# It Opens Stuff

That is, in your desktop environment. This will make *actual windows pop up*, with stuff in them:

```bash
npm install opener -g

opener http://google.com
opener ./my-file.txt
opener firefox
opener npm run lint
```

Also if you want to use it programmatically you can do that too:

```js
var opener = require("opener");

opener("http://google.com");
opener("./my-file.txt");
opener("firefox");
opener("npm run lint");
```

Plus, it returns the child process created, so you can do things like let your script exit while the window stays open:

```js
var editor = opener("documentation.odt");
editor.unref();
// These other unrefs may be necessary if your OS's opener process
// exits before the process it started is complete.
editor.stdin.unref();
editor.stdout.unref();
editor.stderr.unref();
```

## Use It for Good

Like opening the user's browser with a test harness in your package's test script:

```json
{
    "scripts": {
        "test": "opener ./test/runner.html"
    },
    "devDependencies": {
        "opener": "*"
    }
}
```

## Why

Because Windows has `start`, Macs have `open`, and *nix has `xdg-open`. At least [according to some person on StackOverflow](http://stackoverflow.com/q/1480971/3191). And I like things that work on all three. Like Node.js. And Opener.


code:
#!/usr/bin/env node
"use strict";

var opener = require("..");

opener(process.argv.slice(2), function (error) {
    if (error) {
        throw error;
    }
});


code:
"use strict";
var childProcess = require("child_process");
var os = require("os");

module.exports = function opener(args, options, callback) {
    var platform = process.platform;

    // Attempt to detect Windows Subystem for Linux (WSL). WSL  itself as Linux (which works in most cases), but in
    // this specific case we need to treat it as actually being Windows. The "Windows-way" of opening things through
    // cmd.exe works just fine here, whereas using xdg-open does not, since there is no X Windows in WSL.
    if (platform === "linux" && os.release().indexOf("Microsoft") !== -1) {
        platform = "win32";
    }

    // http://stackoverflow.com/q/1480971/3191, but see below for Windows.
    var command;
    switch (platform) {
        case "win32": {
            command = "cmd.exe";
            break;
        }
        case "darwin": {
            command = "open";
            break;
        }
        default: {
            command = "xdg-open";
            break;
        }
    }

    if (typeof args === "string") {
        args = [args];
    }

    if (typeof options === "function") {
        callback = options;
        options = {};
    }

    if (options && typeof options === "object" && options.command) {
        if (platform === "win32") {
            // *always* use cmd on windows
            args = [options.command].concat(args);
        } else {
            command = options.command;
        }
    }

    if (platform === "win32") {
        // On Windows, we really want to use the "start" command. But, the rules regarding arguments with spaces, and
        // escaping them with quotes, can get really arcane. So the easiest way to deal with this is to pass off the
        // responsibility to "cmd /c", which has that logic built in.
        //
        // Furthermore, if "cmd /c" double-quoted the first parameter, then "start" will interpret it as a window title,
        // so we need to add a dummy empty-string window title: http://stackoverflow.com/a/154090/3191
        //
        // Additionally, on Windows ampersand and caret need to be escaped when passed to "start"
        args = args.map(function (value) {
            return value.replace(/[&^]/g, "^$&");
        });
        args = ["/c", "start", "\"\""].concat(args);
    }

    return childProcess.execFile(command, args, options, callback);
};


opener

description: Opens stuff, like webpages and files and executables, cross-platform

author: Domenic Denicola <d@domenic.me> (https://domenic.me/)

license: (WTFPL OR MIT)

repository: domenic/opener

files: lib/ bin/