Added logging, changed some directory structure

This commit is contained in:
2018-01-13 21:33:40 -05:00
parent f079a5f067
commit 8e72ffb917
73656 changed files with 35284 additions and 53718 deletions

View File

@@ -0,0 +1,129 @@
[![CircleCI](https://circleci.com/gh/amasad/sane.svg?style=svg)](https://circleci.com/gh/amasad/sane)
sane
----
I've been driven to insanity by node filesystem watcher wrappers.
Sane aims to be fast, small, and reliable file system watcher. It does that by:
* By default stays away from fs polling because it's very slow and cpu intensive
* Uses `fs.watch` by default and sensibly works around the various issues
* Maintains a consistent API across different platforms
* Where `fs.watch` is not reliable you have the choice of using the following alternatives:
* [the facebook watchman library](https://facebook.github.io/watchman/)
* polling
## Install
```
$ npm install sane
```
## How to choose a mode
Don't worry too much about choosing the correct mode upfront because sane
maintains the same API across all modes and will be easy to switch.
* If you're only supporting Linux and OS X, `watchman` would be the most reliable mode
* If you're using node > v0.10.0 use the default mode
* If you're running OS X and you're watching a lot of directories and you're running into https://github.com/joyent/node/issues/5463, use `watchman`
* If you're in an environment where native file system events aren't available (like Vagrant), you should use polling
* Otherwise, the default mode should work well for you
## API
### sane(dir, options)
Watches a directory and all it's descendant directories for changes, deletions, and additions on files and directories.
```js
var watcher = sane('path/to/dir', {glob: ['**/*.js', '**/*.css']});
watcher.on('ready', function () { console.log('ready') });
watcher.on('change', function (filepath, root, stat) { console.log('file changed', filepath); });
watcher.on('add', function (filepath, root, stat) { console.log('file added', filepath); });
watcher.on('delete', function (filepath, root) { console.log('file deleted', filepath); });
// close
watcher.close();
```
options:
* `glob`: a single string glob pattern or an array of them.
* `poll`: puts the watcher in polling mode. Under the hood that means `fs.watchFile`.
* `watchman`: makes the watcher use [watchman](https://facebook.github.io/watchman/).
* `dot`: enables watching files/directories that start with a dot.
* `ignored`: a glob, regex, function, or array of any combination.
For the glob pattern documentation, see [minimatch](https://github.com/isaacs/minimatch).
If you choose to use `watchman` you'll have to [install watchman yourself](https://facebook.github.io/watchman/docs/install.html)).
For the ignored options, see [anymatch](https://github.com/es128/anymatch).
### sane.NodeWatcher(dir, options)
The default watcher class. Uses `fs.watch` under the hood, and takes the same options as `sane(options, dir)`.
### sane.WatchmanWatcher(dir, options)
The watchman watcher class. Takes the same options as `sane(options, dir)`.
### sane.PollWatcher(dir, options)
The polling watcher class. Takes the same options as `sane(options, dir)` with the addition of:
* interval: indicates how often the files should be polled. (passed to fs.watchFile)
### sane.{Node|Watchman|Poll}Watcher#close
Stops watching.
### sane.{Node|Watchman|Poll}Watcher events
Emits the following events:
All events are passed the file/dir path relative to the root directory
* `ready` when the program is ready to detect events in the directory
* `change` when a file changes
* `add` when a file or directory has been added
* `delete` when a file or directory has been deleted
## CLI
This module includes a simple command line interface, which you can install with `npm install sane -g`.
```
Usage: sane <command> [...directory] [--glob=<filePattern>] [--poll] [--watchman] [--dot] [--wait=<seconds>]
OPTIONS:
--glob=<filePattern>
A single string glob pattern or an array of them.
--poll, -p
Use polling mode.
--watchman, -w
Use watchman (if available).
--dot, -d
Enables watching files/directories that start with a dot.
--wait=<seconds>
Duration, in seconds, that watching will be disabled
after running <command>. Setting this option will
throttle calls to <command> for the specified duration.
```
It will watch the given `directory` and run the given <command> every time a file changes.
### CLI example usage
- `sane 'echo "A command ran"'`
- `sane 'echo "A command ran"' --glob='**/*.css'`
- `sane 'echo "A command ran"' site/assets/css --glob='**/*.css'`
- `sane 'echo "A command ran"' --wait=3`
- `sane 'echo "A command ran"' -p`
## License
MIT
## Credits
The CLI was originally based on the [watch CLI](https://github.com/mikeal/watch). Watch is licensed under the Apache License Version 2.0.

View File

@@ -0,0 +1,24 @@
'use strict';
var NodeWatcher = require('./src/node_watcher');
var PollWatcher = require('./src/poll_watcher');
var WatchmanWatcher = require('./src/watchman_watcher');
function sane(dir, options) {
options = options || {};
if (options.watcher) {
var WatcherClass = require(options.watcher);
return new WatcherClass(dir, options);
} else if (options.poll) {
return new PollWatcher(dir, options);
} else if (options.watchman) {
return new WatchmanWatcher(dir, options);
} else {
return new NodeWatcher(dir, options);
}
}
module.exports = sane;
sane.NodeWatcher = NodeWatcher;
sane.PollWatcher = PollWatcher;
sane.WatchmanWatcher = WatchmanWatcher;

View File

@@ -0,0 +1,81 @@
# BSER Binary Serialization
BSER is a binary serialization scheme that can be used as an alternative to JSON.
BSER uses a framed encoding that makes it simpler to use to stream a sequence of
encoded values.
It is intended to be used for local-IPC only and strings are represented as binary
with no specific encoding; this matches the convention employed by most operating
system filename storage.
For more details about the serialization scheme see
[Watchman's docs](https://facebook.github.io/watchman/docs/bser.html).
## API
```js
var bser = require('bser');
```
### bser.loadFromBuffer
The is the synchronous decoder; given an input string or buffer,
decodes a single value and returns it. Throws an error if the
input is invalid.
```js
var obj = bser.loadFromBuffer(buf);
```
### bser.dumpToBuffer
Synchronously encodes a value as BSER.
```js
var encoded = bser.dumpToBuffer(['hello']);
console.log(bser.loadFromBuffer(encoded)); // ['hello']
```
### BunserBuf
The asynchronous decoder API is implemented in the BunserBuf object.
You may incrementally append data to this object and it will emit the
decoded values via its `value` event.
```js
var bunser = new bser.BunserBuf();
bunser.on('value', function(obj) {
console.log(obj);
});
```
Then in your socket `data` event:
```js
bunser.append(buf);
```
## Example
Read BSER from socket:
```js
var bunser = new bser.BunserBuf();
bunser.on('value', function(obj) {
console.log('data from socket', obj);
});
var socket = net.connect('/socket');
socket.on('data', function(buf) {
bunser.append(buf);
});
```
Write BSER to socket:
```js
socket.write(bser.dumpToBuffer(obj));
```

View File

@@ -0,0 +1,578 @@
/* Copyright 2015-present Facebook, Inc.
* Licensed under the Apache License, Version 2.0 */
var EE = require('events').EventEmitter;
var util = require('util');
var os = require('os');
var assert = require('assert');
var Int64 = require('node-int64');
// BSER uses the local endianness to reduce byte swapping overheads
// (the protocol is expressly local IPC only). We need to tell node
// to use the native endianness when reading various native values.
var isBigEndian = os.endianness() == 'BE';
// Find the next power-of-2 >= size
function nextPow2(size) {
return Math.pow(2, Math.ceil(Math.log(size) / Math.LN2));
}
// Expandable buffer that we can provide a size hint for
function Accumulator(initsize) {
this.buf = new Buffer(nextPow2(initsize || 8192));
this.readOffset = 0;
this.writeOffset = 0;
}
// For testing
exports.Accumulator = Accumulator
// How much we can write into this buffer without allocating
Accumulator.prototype.writeAvail = function() {
return this.buf.length - this.writeOffset;
}
// How much we can read
Accumulator.prototype.readAvail = function() {
return this.writeOffset - this.readOffset;
}
// Ensure that we have enough space for size bytes
Accumulator.prototype.reserve = function(size) {
if (size < this.writeAvail()) {
return;
}
// If we can make room by shunting down, do so
if (this.readOffset > 0) {
this.buf.copy(this.buf, 0, this.readOffset, this.writeOffset);
this.writeOffset -= this.readOffset;
this.readOffset = 0;
}
// If we made enough room, no need to allocate more
if (size < this.writeAvail()) {
return;
}
// Allocate a replacement and copy it in
var buf = new Buffer(nextPow2(this.buf.length + size - this.writeAvail()));
this.buf.copy(buf);
this.buf = buf;
}
// Append buffer or string. Will resize as needed
Accumulator.prototype.append = function(buf) {
if (Buffer.isBuffer(buf)) {
this.reserve(buf.length);
buf.copy(this.buf, this.writeOffset, 0, buf.length);
this.writeOffset += buf.length;
} else {
var size = Buffer.byteLength(buf);
this.reserve(size);
this.buf.write(buf, this.writeOffset);
this.writeOffset += size;
}
}
Accumulator.prototype.assertReadableSize = function(size) {
if (this.readAvail() < size) {
throw new Error("wanted to read " + size +
" bytes but only have " + this.readAvail());
}
}
Accumulator.prototype.peekString = function(size) {
this.assertReadableSize(size);
return this.buf.toString('utf-8', this.readOffset, this.readOffset + size);
}
Accumulator.prototype.readString = function(size) {
var str = this.peekString(size);
this.readOffset += size;
return str;
}
Accumulator.prototype.peekInt = function(size) {
this.assertReadableSize(size);
switch (size) {
case 1:
return this.buf.readInt8(this.readOffset, size);
case 2:
return isBigEndian ?
this.buf.readInt16BE(this.readOffset, size) :
this.buf.readInt16LE(this.readOffset, size);
case 4:
return isBigEndian ?
this.buf.readInt32BE(this.readOffset, size) :
this.buf.readInt32LE(this.readOffset, size);
case 8:
var big = this.buf.slice(this.readOffset, this.readOffset + 8);
if (isBigEndian) {
// On a big endian system we can simply pass the buffer directly
return new Int64(big);
}
// Otherwise we need to byteswap
return new Int64(byteswap64(big));
default:
throw new Error("invalid integer size " + size);
}
}
Accumulator.prototype.readInt = function(bytes) {
var ival = this.peekInt(bytes);
this.readOffset += bytes;
return ival;
}
Accumulator.prototype.peekDouble = function() {
this.assertReadableSize(8);
return isBigEndian ?
this.buf.readDoubleBE(this.readOffset) :
this.buf.readDoubleLE(this.readOffset);
}
Accumulator.prototype.readDouble = function() {
var dval = this.peekDouble();
this.readOffset += 8;
return dval;
}
Accumulator.prototype.readAdvance = function(size) {
if (size > 0) {
this.assertReadableSize(size);
} else if (size < 0 && this.readOffset + size < 0) {
throw new Error("advance with negative offset " + size +
" would seek off the start of the buffer");
}
this.readOffset += size;
}
Accumulator.prototype.writeByte = function(value) {
this.reserve(1);
this.buf.writeInt8(value, this.writeOffset);
++this.writeOffset;
}
Accumulator.prototype.writeInt = function(value, size) {
this.reserve(size);
switch (size) {
case 1:
this.buf.writeInt8(value, this.writeOffset);
break;
case 2:
if (isBigEndian) {
this.buf.writeInt16BE(value, this.writeOffset);
} else {
this.buf.writeInt16LE(value, this.writeOffset);
}
break;
case 4:
if (isBigEndian) {
this.buf.writeInt32BE(value, this.writeOffset);
} else {
this.buf.writeInt32LE(value, this.writeOffset);
}
break;
default:
throw new Error("unsupported integer size " + size);
}
this.writeOffset += size;
}
Accumulator.prototype.writeDouble = function(value) {
this.reserve(8);
if (isBigEndian) {
this.buf.writeDoubleBE(value, this.writeOffset);
} else {
this.buf.writeDoubleLE(value, this.writeOffset);
}
this.writeOffset += 8;
}
var BSER_ARRAY = 0x00;
var BSER_OBJECT = 0x01;
var BSER_STRING = 0x02;
var BSER_INT8 = 0x03;
var BSER_INT16 = 0x04;
var BSER_INT32 = 0x05;
var BSER_INT64 = 0x06;
var BSER_REAL = 0x07;
var BSER_TRUE = 0x08;
var BSER_FALSE = 0x09;
var BSER_NULL = 0x0a;
var BSER_TEMPLATE = 0x0b;
var BSER_SKIP = 0x0c;
var ST_NEED_PDU = 0; // Need to read and decode PDU length
var ST_FILL_PDU = 1; // Know the length, need to read whole content
var MAX_INT8 = 127;
var MAX_INT16 = 32768;
var MAX_INT32 = 2147483648;
function BunserBuf() {
EE.call(this);
this.buf = new Accumulator();
this.state = ST_NEED_PDU;
}
util.inherits(BunserBuf, EE);
exports.BunserBuf = BunserBuf;
BunserBuf.prototype.append = function(buf, synchronous) {
if (synchronous) {
this.buf.append(buf);
return this.process(synchronous);
}
try {
this.buf.append(buf);
} catch (err) {
this.emit('error', err);
return;
}
// Arrange to decode later. This allows the consuming
// application to make progress with other work in the
// case that we have a lot of subscription updates coming
// in from a large tree.
this.processLater();
}
BunserBuf.prototype.processLater = function() {
var self = this;
process.nextTick(function() {
try {
self.process(false);
} catch (err) {
self.emit('error', err);
}
});
}
// Do something with the buffer to advance our state.
// If we're running synchronously we'll return either
// the value we've decoded or undefined if we don't
// yet have enought data.
// If we're running asynchronously, we'll emit the value
// when it becomes ready and schedule another invocation
// of process on the next tick if we still have data we
// can process.
BunserBuf.prototype.process = function(synchronous) {
if (this.state == ST_NEED_PDU) {
if (this.buf.readAvail() < 2) {
return;
}
// Validate BSER header
this.expectCode(0);
this.expectCode(1);
this.pduLen = this.decodeInt(true /* relaxed */);
if (this.pduLen === false) {
// Need more data, walk backwards
this.buf.readAdvance(-2);
return;
}
// Ensure that we have a big enough buffer to read the rest of the PDU
this.buf.reserve(this.pduLen);
this.state = ST_FILL_PDU;
}
if (this.state == ST_FILL_PDU) {
if (this.buf.readAvail() < this.pduLen) {
// Need more data
return;
}
// We have enough to decode it
var val = this.decodeAny();
if (synchronous) {
return val;
}
this.emit('value', val);
this.state = ST_NEED_PDU;
}
if (!synchronous && this.buf.readAvail() > 0) {
this.processLater();
}
}
BunserBuf.prototype.raise = function(reason) {
throw new Error(reason + ", in Buffer of length " +
this.buf.buf.length + " (" + this.buf.readAvail() +
" readable) at offset " + this.buf.readOffset + " buffer: " +
JSON.stringify(this.buf.buf.slice(
this.buf.readOffset, this.buf.readOffset + 32).toJSON()));
}
BunserBuf.prototype.expectCode = function(expected) {
var code = this.buf.readInt(1);
if (code != expected) {
this.raise("expected bser opcode " + expected + " but got " + code);
}
}
BunserBuf.prototype.decodeAny = function() {
var code = this.buf.peekInt(1);
switch (code) {
case BSER_INT8:
case BSER_INT16:
case BSER_INT32:
case BSER_INT64:
return this.decodeInt();
case BSER_REAL:
this.buf.readAdvance(1);
return this.buf.readDouble();
case BSER_TRUE:
this.buf.readAdvance(1);
return true;
case BSER_FALSE:
this.buf.readAdvance(1);
return false;
case BSER_NULL:
this.buf.readAdvance(1);
return null;
case BSER_STRING:
return this.decodeString();
case BSER_ARRAY:
return this.decodeArray();
case BSER_OBJECT:
return this.decodeObject();
case BSER_TEMPLATE:
return this.decodeTemplate();
default:
this.raise("unhandled bser opcode " + code);
}
}
BunserBuf.prototype.decodeArray = function() {
this.expectCode(BSER_ARRAY);
var nitems = this.decodeInt();
var arr = [];
for (var i = 0; i < nitems; ++i) {
arr.push(this.decodeAny());
}
return arr;
}
BunserBuf.prototype.decodeObject = function() {
this.expectCode(BSER_OBJECT);
var nitems = this.decodeInt();
var res = {};
for (var i = 0; i < nitems; ++i) {
var key = this.decodeString();
var val = this.decodeAny();
res[key] = val;
}
return res;
}
BunserBuf.prototype.decodeTemplate = function() {
this.expectCode(BSER_TEMPLATE);
var keys = this.decodeArray();
var nitems = this.decodeInt();
var arr = [];
for (var i = 0; i < nitems; ++i) {
var obj = {};
for (var keyidx = 0; keyidx < keys.length; ++keyidx) {
if (this.buf.peekInt(1) == BSER_SKIP) {
this.buf.readAdvance(1);
continue;
}
var val = this.decodeAny();
obj[keys[keyidx]] = val;
}
arr.push(obj);
}
return arr;
}
BunserBuf.prototype.decodeString = function() {
this.expectCode(BSER_STRING);
var len = this.decodeInt();
return this.buf.readString(len);
}
// This is unusual compared to the other decode functions in that
// we may not have enough data available to satisfy the read, and
// we don't want to throw. This is only true when we're reading
// the PDU length from the PDU header; we'll set relaxSizeAsserts
// in that case.
BunserBuf.prototype.decodeInt = function(relaxSizeAsserts) {
if (relaxSizeAsserts && !this.buf.readAvail(1)) {
return false;
} else {
this.buf.assertReadableSize(1);
}
var code = this.buf.peekInt(1);
var size = 0;
switch (code) {
case BSER_INT8:
size = 1;
break;
case BSER_INT16:
size = 2;
break;
case BSER_INT32:
size = 4;
break;
case BSER_INT64:
size = 8;
break;
default:
this.raise("invalid bser int encoding " + code);
}
if (relaxSizeAsserts && !this.buf.readAvail(1 + size)) {
return false;
}
this.buf.readAdvance(1);
return this.buf.readInt(size);
}
// synchronously BSER decode a string and return the value
function loadFromBuffer(input) {
var buf = new BunserBuf();
var result = buf.append(input, true);
if (buf.buf.readAvail()) {
throw Error(
'excess data found after input buffer, use BunserBuf instead');
}
if (typeof result === 'undefined') {
throw Error(
'no bser found in string and no error raised!?');
}
return result;
}
exports.loadFromBuffer = loadFromBuffer
// Byteswap an arbitrary buffer, flipping from one endian
// to the other, returning a new buffer with the resultant data
function byteswap64(buf) {
var swap = new Buffer(buf.length);
for (var i = 0; i < buf.length; i++) {
swap[i] = buf[buf.length -1 - i];
}
return swap;
}
function dump_int64(buf, val) {
// Get the raw bytes. The Int64 buffer is big endian
var be = val.toBuffer();
if (isBigEndian) {
// We're a big endian system, so the buffer is exactly how we
// want it to be
buf.writeByte(BSER_INT64);
buf.append(be);
return;
}
// We need to byte swap to get the correct representation
var le = byteswap64(be);
buf.writeByte(BSER_INT64);
buf.append(le);
}
function dump_int(buf, val) {
var abs = Math.abs(val);
if (abs <= MAX_INT8) {
buf.writeByte(BSER_INT8);
buf.writeInt(val, 1);
} else if (abs <= MAX_INT16) {
buf.writeByte(BSER_INT16);
buf.writeInt(val, 2);
} else if (abs <= MAX_INT32) {
buf.writeByte(BSER_INT32);
buf.writeInt(val, 4);
} else {
dump_int64(buf, new Int64(val));
}
}
function dump_any(buf, val) {
switch (typeof(val)) {
case 'number':
buf.writeByte(BSER_REAL);
buf.writeDouble(val);
return;
case 'string':
buf.writeByte(BSER_STRING);
dump_int(buf, Buffer.byteLength(val));
buf.append(val);
return;
case 'boolean':
buf.writeByte(val ? BSER_TRUE : BSER_FALSE);
return;
case 'object':
if (val === null) {
buf.writeByte(BSER_NULL);
return;
}
if (val instanceof Int64) {
dump_int64(buf, val);
return;
}
if (Array.isArray(val)) {
buf.writeByte(BSER_ARRAY);
dump_int(buf, val.length);
for (var i = 0; i < val.length; ++i) {
dump_any(buf, val[i]);
}
return;
}
buf.writeByte(BSER_OBJECT);
var keys = Object.keys(val);
// First pass to compute number of defined keys
var num_keys = keys.length;
for (var i = 0; i < keys.length; ++i) {
var key = keys[i];
var v = val[key];
if (typeof(v) == 'undefined') {
num_keys--;
}
}
dump_int(buf, num_keys);
for (var i = 0; i < keys.length; ++i) {
var key = keys[i];
var v = val[key];
if (typeof(v) == 'undefined') {
// Don't include it
continue;
}
dump_any(buf, key);
try {
dump_any(buf, v);
} catch (e) {
throw new Error(
e.message + ' (while serializing object property with name `' +
key + "')");
}
}
return;
default:
throw new Error('cannot serialize type ' + typeof(val) + ' to BSER');
}
}
// BSER encode value and return a buffer of the contents
function dumpToBuffer(val) {
var buf = new Accumulator();
// Build out the header
buf.writeByte(0);
buf.writeByte(1);
// Reserve room for an int32 to hold our PDU length
buf.writeByte(BSER_INT32);
buf.writeInt(0, 4); // We'll come back and fill this in at the end
dump_any(buf, val);
// Compute PDU length
var off = buf.writeOffset;
var len = off - 7 /* the header length */;
buf.writeOffset = 3; // The length value to fill in
buf.writeInt(len, 4); // write the length in the space we reserved
buf.writeOffset = off;
return buf.buf.slice(0, off);
}
exports.dumpToBuffer = dumpToBuffer

View File

@@ -0,0 +1,62 @@
{
"_args": [
[
"bser@1.0.2",
"C:\\Users\\deranjer\\go\\src\\github.com\\deranjer\\goTorrent\\torrent-project"
]
],
"_from": "bser@1.0.2",
"_id": "bser@1.0.2",
"_inBundle": false,
"_integrity": "sha1-OBEWlwsqbe6lZG3RXdcnhES1YWk=",
"_location": "/react-scripts/sane/bser",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "bser@1.0.2",
"name": "bser",
"escapedName": "bser",
"rawSpec": "1.0.2",
"saveSpec": null,
"fetchSpec": "1.0.2"
},
"_requiredBy": [
"/react-scripts/sane/fb-watchman"
],
"_resolved": "https://registry.npmjs.org/bser/-/bser-1.0.2.tgz",
"_spec": "1.0.2",
"_where": "C:\\Users\\deranjer\\go\\src\\github.com\\deranjer\\goTorrent\\torrent-project",
"author": {
"name": "Wez Furlong",
"email": "wez@fb.com",
"url": "http://wezfurlong.org"
},
"bugs": {
"url": "https://github.com/facebook/watchman/issues"
},
"dependencies": {
"node-int64": "^0.4.0"
},
"description": "JavaScript implementation of the BSER Binary Serialization",
"directories": {
"test": "test"
},
"homepage": "https://facebook.github.io/watchman/docs/bser.html",
"keywords": [
"bser",
"binary",
"protocol"
],
"license": "Apache-2.0",
"main": "index.js",
"name": "bser",
"repository": {
"type": "git",
"url": "git+https://github.com/facebook/watchman.git"
},
"scripts": {
"test": "node test/bser.js"
},
"version": "1.0.2"
}

View File

@@ -0,0 +1,74 @@
var assert = require('assert');
var bser = require('../');
var Int64 = require('node-int64');
// This is a hard-coded template representation from the C test suite
var template = "\x00\x01\x03\x28" +
"\x0b\x00\x03\x02\x02\x03\x04\x6e\x61\x6d\x65\x02" +
"\x03\x03\x61\x67\x65\x03\x03\x02\x03\x04\x66\x72" +
"\x65\x64\x03\x14\x02\x03\x04\x70\x65\x74\x65\x03" +
"\x1e\x0c\x03\x19" ;
var val = bser.loadFromBuffer(template);
assert.deepEqual(val, [
{"name": "fred", "age": 20},
{"name": "pete", "age": 30},
{"age": 25}
]);
function roundtrip(val) {
var encoded = bser.dumpToBuffer(val);
var decoded = bser.loadFromBuffer(encoded);
assert.deepEqual(decoded, val);
}
var values_to_test = [
1,
"hello",
1.5,
false,
true,
new Int64('0x0123456789'),
127,
128,
129,
32767,
32768,
32769,
65534,
65536,
65537,
2147483647,
2147483648,
2147483649,
null,
[1, 2, 3],
{foo: "bar"},
{nested: {struct: "hello", list: [true, false, 1, "string"]}}
];
for (var i = 0; i < values_to_test.length; ++i) {
roundtrip(values_to_test[i]);
}
roundtrip(values_to_test);
// Verify Accumulator edge cases
var acc = new bser.Accumulator(8);
acc.append("hello");
assert.equal(acc.readAvail(), 5);
assert.equal(acc.readOffset, 0);
assert.equal(acc.readString(3), "hel");
assert.equal(acc.readOffset, 3);
assert.equal(acc.readAvail(), 2);
assert.equal(acc.writeAvail(), 3);
// This should trigger a shunt and not make the buffer bigger
acc.reserve(5);
assert.equal(acc.readOffset, 0, 'shunted');
assert.equal(acc.readAvail(), 2, 'still have 2 available to read');
assert.equal(acc.writeAvail(), 6, '2 left to read out of 8 total space');
assert.equal(acc.peekString(2), 'lo', 'have the correct remainder');
// Don't include keys that have undefined values
var res = bser.dumpToBuffer({expression: undefined});
assert.deepEqual(bser.loadFromBuffer(res), {});

View File

@@ -0,0 +1,34 @@
# fb-watchman
`fb-watchman` is a filesystem watcher that uses the
[Watchman](https://facebook.github.io/watchman/) file watching service from
Facebook.
Watchman provides file change notification services using very
efficient recursive watches and also allows more advanced change matching and
filesystem tree querying operations using
[a powerful expression syntax](https://facebook.github.io/watchman/docs/file-query.html#expressions).
## Install
You should [install Watchman](
https://facebook.github.io/watchman/docs/install.html) to make the most of this
module.
Then simply:
```
$ npm install fb-watchman
```
## Key Concepts
- Watchman recursively watches directories.
- Each watched directory is called a `root`.
- You must initiate a `watch` on a `root` using the `watch-project` command prior to subscribing to changes
- Rather than separately watching many sibling directories, `watch-project` consolidates and re-uses existing watches relative to a project root (the location of your `.watchmanconfig` or source control repository root)
- change notifications are relative to the project root
## How do I use it?
[Read the NodeJS watchman documentation](https://facebook.github.io/watchman/docs/nodejs.html)

View File

@@ -0,0 +1,322 @@
/* Copyright 2014-present Facebook, Inc.
* Licensed under the Apache License, Version 2.0 */
'use strict';
var net = require('net');
var EE = require('events').EventEmitter;
var util = require('util');
var childProcess = require('child_process');
var bser = require('bser');
// We'll emit the responses to these when they get sent down to us
var unilateralTags = ['subscription', 'log'];
/**
* @param options An object with the following optional keys:
* * 'watchmanBinaryPath' (string) Absolute path to the watchman binary.
* If not provided, the Client locates the binary using the PATH specified
* by the node child_process's default env.
*/
function Client(options) {
var self = this;
EE.call(this);
this.watchmanBinaryPath = 'watchman';
if (options && options.watchmanBinaryPath) {
this.watchmanBinaryPath = options.watchmanBinaryPath.trim();
};
this.commands = [];
}
util.inherits(Client, EE);
module.exports.Client = Client;
// Try to send the next queued command, if any
Client.prototype.sendNextCommand = function() {
if (this.currentCommand) {
// There's a command pending response, don't send this new one yet
return;
}
this.currentCommand = this.commands.shift();
if (!this.currentCommand) {
// No further commands are queued
return;
}
this.socket.write(bser.dumpToBuffer(this.currentCommand.cmd));
}
Client.prototype.cancelCommands = function(why) {
var error = new Error(why);
// Steal all pending commands before we start cancellation, in
// case something decides to schedule more commands
var cmds = this.commands;
this.commands = [];
if (this.currentCommand) {
cmds.unshift(this.currentCommand);
this.currentCommand = null;
}
// Synthesize an error condition for any commands that were queued
cmds.forEach(function(cmd) {
cmd.cb(error);
});
}
Client.prototype.connect = function() {
var self = this;
function makeSock(sockname) {
// bunser will decode the watchman BSER protocol for us
self.bunser = new bser.BunserBuf();
// For each decoded line:
self.bunser.on('value', function(obj) {
// Figure out if this is a unliteral response or if it is the
// response portion of a request-response sequence. At the time
// of writing, there are only two possible unilateral responses.
var unilateral = false;
for (var i = 0; i < unilateralTags.length; i++) {
var tag = unilateralTags[i];
if (tag in obj) {
unilateral = tag;
}
}
if (unilateral) {
self.emit(unilateral, obj);
} else if (self.currentCommand) {
var cmd = self.currentCommand;
self.currentCommand = null;
if ('error' in obj) {
var error = new Error(obj.error);
error.watchmanResponse = obj;
cmd.cb(error);
} else {
cmd.cb(null, obj);
}
}
// See if we can dispatch the next queued command, if any
self.sendNextCommand();
});
self.bunser.on('error', function(err) {
self.emit('error', err);
});
self.socket = net.createConnection(sockname);
self.socket.on('connect', function() {
self.connecting = false;
self.emit('connect');
self.sendNextCommand();
});
self.socket.on('error', function(err) {
self.connecting = false;
self.emit('error', err);
});
self.socket.on('data', function(buf) {
if (self.bunser) {
self.bunser.append(buf);
}
});
self.socket.on('end', function() {
self.socket = null;
self.bunser = null;
self.cancelCommands('The watchman connection was closed');
self.emit('end');
});
}
// triggers will export the sock path to the environment.
// If we're invoked in such a way, we can simply pick up the
// definition from the environment and avoid having to fork off
// a process to figure it out
if (process.env.WATCHMAN_SOCK) {
makeSock(process.env.WATCHMAN_SOCK);
return;
}
// We need to ask the client binary where to find it.
// This will cause the service to start for us if it isn't
// already running.
var args = ['--no-pretty', 'get-sockname'];
// We use the more elaborate spawn rather than exec because there
// are some error cases on Windows where process spawning can hang.
// It is desirable to pipe stderr directly to stderr live so that
// we can discover the problem.
var proc = null;
var spawnFailed = false;
function spawnError(error) {
if (spawnFailed) {
// For ENOENT, proc 'close' will also trigger with a negative code,
// let's suppress that second error.
return;
}
spawnFailed = true;
if (error.errno === 'EACCES') {
error.message = 'The Watchman CLI is installed but cannot ' +
'be spawned because of a permission problem';
} else if (error.errno === 'ENOENT') {
error.message = 'Watchman was not found in PATH. See ' +
'https://facebook.github.io/watchman/docs/install.html ' +
'for installation instructions';
}
console.error('Watchman: ', error.message);
self.emit('error', error);
}
try {
proc = childProcess.spawn(this.watchmanBinaryPath, args, {
stdio: ['ignore', 'pipe', 'pipe']
});
} catch (error) {
spawnError(error);
return;
}
var stdout = [];
var stderr = [];
proc.stdout.on('data', function(data) {
stdout.push(data);
});
proc.stderr.on('data', function(data) {
data = data.toString('utf8');
stderr.push(data);
console.error(data);
});
proc.on('error', function(error) {
spawnError(error);
});
proc.on('close', function (code, signal) {
if (code !== 0) {
spawnError(new Error(
self.watchmanBinaryPath + ' ' + args.join(' ') +
' returned with exit code=' + code + ', signal=' +
signal + ', stderr= ' + stderr.join('')));
return;
}
try {
var obj = JSON.parse(stdout.join(''));
if ('error' in obj) {
var error = new Error(obj.error);
error.watchmanResponse = obj;
self.emit('error', error);
return;
}
makeSock(obj.sockname);
} catch (e) {
self.emit('error', e);
}
});
}
Client.prototype.command = function(args, done) {
done = done || function() {};
// Queue up the command
this.commands.push({cmd: args, cb: done});
// Establish a connection if we don't already have one
if (!this.socket) {
if (!this.connecting) {
this.connecting = true;
this.connect();
return;
}
return;
}
// If we're already connected and idle, try sending the command immediately
this.sendNextCommand();
}
var cap_versions = {
"cmd-watch-del-all": "3.1.1",
"cmd-watch-project": "3.1",
"relative_root": "3.3",
"term-dirname": "3.1",
"term-idirname": "3.1",
"wildmatch": "3.7",
}
// Compares a vs b, returns < 0 if a < b, > 0 if b > b, 0 if a == b
function vers_compare(a, b) {
a = a.split('.');
b = b.split('.');
for (var i = 0; i < 3; i++) {
var d = parseInt(a[i] || '0') - parseInt(b[i] || '0');
if (d != 0) {
return d;
}
}
return 0; // Equal
}
function have_cap(vers, name) {
if (name in cap_versions) {
return vers_compare(vers, cap_versions[name]) >= 0;
}
return false;
}
// This is a helper that we expose for testing purposes
Client.prototype._synthesizeCapabilityCheck = function(
resp, optional, required) {
resp.capabilities = {}
var version = resp.version;
optional.forEach(function (name) {
resp.capabilities[name] = have_cap(version, name);
});
required.forEach(function (name) {
var have = have_cap(version, name);
resp.capabilities[name] = have;
if (!have) {
resp.error = 'client required capability `' + name +
'` is not supported by this server';
}
});
return resp;
}
Client.prototype.capabilityCheck = function(caps, done) {
var optional = caps.optional || [];
var required = caps.required || [];
var self = this;
this.command(['version', {
optional: optional,
required: required
}], function (error, resp) {
if (error) {
done(error);
return;
}
if (!('capabilities' in resp)) {
// Server doesn't support capabilities, so we need to
// synthesize the results based on the version
resp = self._synthesizeCapabilityCheck(resp, optional, required);
if (resp.error) {
error = new Error(resp.error);
error.watchmanResponse = resp;
done(error);
return;
}
}
done(null, resp);
});
}
// Close the connection to the service
Client.prototype.end = function() {
this.cancelCommands('The client was ended');
if (this.socket) {
this.socket.end();
this.socket = null;
}
this.bunser = null;
}

View File

@@ -0,0 +1,67 @@
{
"_args": [
[
"fb-watchman@1.9.2",
"C:\\Users\\deranjer\\go\\src\\github.com\\deranjer\\goTorrent\\torrent-project"
]
],
"_from": "fb-watchman@1.9.2",
"_id": "fb-watchman@1.9.2",
"_inBundle": false,
"_integrity": "sha1-okz0eCf4LTj7Waaa1wt247auc4M=",
"_location": "/react-scripts/sane/fb-watchman",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "fb-watchman@1.9.2",
"name": "fb-watchman",
"escapedName": "fb-watchman",
"rawSpec": "1.9.2",
"saveSpec": null,
"fetchSpec": "1.9.2"
},
"_requiredBy": [
"/react-scripts/sane"
],
"_resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-1.9.2.tgz",
"_spec": "1.9.2",
"_where": "C:\\Users\\deranjer\\go\\src\\github.com\\deranjer\\goTorrent\\torrent-project",
"author": {
"name": "Wez Furlong",
"email": "wez@fb.com",
"url": "http://wezfurlong.org"
},
"bugs": {
"url": "https://github.com/facebook/watchman/issues"
},
"dependencies": {
"bser": "1.0.2"
},
"description": "Bindings for the Watchman file watching service",
"files": [
"index.js"
],
"homepage": "https://facebook.github.io/watchman/",
"keywords": [
"facebook",
"watchman",
"file",
"watch",
"watcher",
"watching",
"fs.watch",
"fswatcher",
"fs",
"glob",
"utility"
],
"license": "Apache-2.0",
"main": "index.js",
"name": "fb-watchman",
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/facebook/watchman.git"
},
"version": "1.9.2"
}

View File

@@ -0,0 +1,8 @@
language: node_js
node_js:
- "0.8"
- "0.10"
- "0.12"
- "iojs"
before_install:
- npm install -g npm@~1.4.6

View File

@@ -0,0 +1,18 @@
This software is released under the MIT 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.

View File

@@ -0,0 +1,2 @@
var argv = require('../')(process.argv.slice(2));
console.dir(argv);

View File

@@ -0,0 +1,236 @@
module.exports = function (args, opts) {
if (!opts) opts = {};
var flags = { bools : {}, strings : {}, unknownFn: null };
if (typeof opts['unknown'] === 'function') {
flags.unknownFn = opts['unknown'];
}
if (typeof opts['boolean'] === 'boolean' && opts['boolean']) {
flags.allBools = true;
} else {
[].concat(opts['boolean']).filter(Boolean).forEach(function (key) {
flags.bools[key] = true;
});
}
var aliases = {};
Object.keys(opts.alias || {}).forEach(function (key) {
aliases[key] = [].concat(opts.alias[key]);
aliases[key].forEach(function (x) {
aliases[x] = [key].concat(aliases[key].filter(function (y) {
return x !== y;
}));
});
});
[].concat(opts.string).filter(Boolean).forEach(function (key) {
flags.strings[key] = true;
if (aliases[key]) {
flags.strings[aliases[key]] = true;
}
});
var defaults = opts['default'] || {};
var argv = { _ : [] };
Object.keys(flags.bools).forEach(function (key) {
setArg(key, defaults[key] === undefined ? false : defaults[key]);
});
var notFlags = [];
if (args.indexOf('--') !== -1) {
notFlags = args.slice(args.indexOf('--')+1);
args = args.slice(0, args.indexOf('--'));
}
function argDefined(key, arg) {
return (flags.allBools && /^--[^=]+$/.test(arg)) ||
flags.strings[key] || flags.bools[key] || aliases[key];
}
function setArg (key, val, arg) {
if (arg && flags.unknownFn && !argDefined(key, arg)) {
if (flags.unknownFn(arg) === false) return;
}
var value = !flags.strings[key] && isNumber(val)
? Number(val) : val
;
setKey(argv, key.split('.'), value);
(aliases[key] || []).forEach(function (x) {
setKey(argv, x.split('.'), value);
});
}
function setKey (obj, keys, value) {
var o = obj;
keys.slice(0,-1).forEach(function (key) {
if (o[key] === undefined) o[key] = {};
o = o[key];
});
var key = keys[keys.length - 1];
if (o[key] === undefined || flags.bools[key] || typeof o[key] === 'boolean') {
o[key] = value;
}
else if (Array.isArray(o[key])) {
o[key].push(value);
}
else {
o[key] = [ o[key], value ];
}
}
function aliasIsBoolean(key) {
return aliases[key].some(function (x) {
return flags.bools[x];
});
}
for (var i = 0; i < args.length; i++) {
var arg = args[i];
if (/^--.+=/.test(arg)) {
// Using [\s\S] instead of . because js doesn't support the
// 'dotall' regex modifier. See:
// http://stackoverflow.com/a/1068308/13216
var m = arg.match(/^--([^=]+)=([\s\S]*)$/);
var key = m[1];
var value = m[2];
if (flags.bools[key]) {
value = value !== 'false';
}
setArg(key, value, arg);
}
else if (/^--no-.+/.test(arg)) {
var key = arg.match(/^--no-(.+)/)[1];
setArg(key, false, arg);
}
else if (/^--.+/.test(arg)) {
var key = arg.match(/^--(.+)/)[1];
var next = args[i + 1];
if (next !== undefined && !/^-/.test(next)
&& !flags.bools[key]
&& !flags.allBools
&& (aliases[key] ? !aliasIsBoolean(key) : true)) {
setArg(key, next, arg);
i++;
}
else if (/^(true|false)$/.test(next)) {
setArg(key, next === 'true', arg);
i++;
}
else {
setArg(key, flags.strings[key] ? '' : true, arg);
}
}
else if (/^-[^-]+/.test(arg)) {
var letters = arg.slice(1,-1).split('');
var broken = false;
for (var j = 0; j < letters.length; j++) {
var next = arg.slice(j+2);
if (next === '-') {
setArg(letters[j], next, arg)
continue;
}
if (/[A-Za-z]/.test(letters[j]) && /=/.test(next)) {
setArg(letters[j], next.split('=')[1], arg);
broken = true;
break;
}
if (/[A-Za-z]/.test(letters[j])
&& /-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) {
setArg(letters[j], next, arg);
broken = true;
break;
}
if (letters[j+1] && letters[j+1].match(/\W/)) {
setArg(letters[j], arg.slice(j+2), arg);
broken = true;
break;
}
else {
setArg(letters[j], flags.strings[letters[j]] ? '' : true, arg);
}
}
var key = arg.slice(-1)[0];
if (!broken && key !== '-') {
if (args[i+1] && !/^(-|--)[^-]/.test(args[i+1])
&& !flags.bools[key]
&& (aliases[key] ? !aliasIsBoolean(key) : true)) {
setArg(key, args[i+1], arg);
i++;
}
else if (args[i+1] && /true|false/.test(args[i+1])) {
setArg(key, args[i+1] === 'true', arg);
i++;
}
else {
setArg(key, flags.strings[key] ? '' : true, arg);
}
}
}
else {
if (!flags.unknownFn || flags.unknownFn(arg) !== false) {
argv._.push(
flags.strings['_'] || !isNumber(arg) ? arg : Number(arg)
);
}
if (opts.stopEarly) {
argv._.push.apply(argv._, args.slice(i + 1));
break;
}
}
}
Object.keys(defaults).forEach(function (key) {
if (!hasKey(argv, key.split('.'))) {
setKey(argv, key.split('.'), defaults[key]);
(aliases[key] || []).forEach(function (x) {
setKey(argv, x.split('.'), defaults[key]);
});
}
});
if (opts['--']) {
argv['--'] = new Array();
notFlags.forEach(function(key) {
argv['--'].push(key);
});
}
else {
notFlags.forEach(function(key) {
argv._.push(key);
});
}
return argv;
};
function hasKey (obj, keys) {
var o = obj;
keys.slice(0,-1).forEach(function (key) {
o = (o[key] || {});
});
var key = keys[keys.length - 1];
return key in o;
}
function isNumber (x) {
if (typeof x === 'number') return true;
if (/^0x[0-9a-f]+$/i.test(x)) return true;
return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x);
}

View File

@@ -0,0 +1,76 @@
{
"_args": [
[
"minimist@1.2.0",
"C:\\Users\\deranjer\\go\\src\\github.com\\deranjer\\goTorrent\\torrent-project"
]
],
"_from": "minimist@1.2.0",
"_id": "minimist@1.2.0",
"_inBundle": false,
"_integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"_location": "/react-scripts/sane/minimist",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "minimist@1.2.0",
"name": "minimist",
"escapedName": "minimist",
"rawSpec": "1.2.0",
"saveSpec": null,
"fetchSpec": "1.2.0"
},
"_requiredBy": [
"/react-scripts/sane"
],
"_resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"_spec": "1.2.0",
"_where": "C:\\Users\\deranjer\\go\\src\\github.com\\deranjer\\goTorrent\\torrent-project",
"author": {
"name": "James Halliday",
"email": "mail@substack.net",
"url": "http://substack.net"
},
"bugs": {
"url": "https://github.com/substack/minimist/issues"
},
"description": "parse argument options",
"devDependencies": {
"covert": "^1.0.0",
"tap": "~0.4.0",
"tape": "^3.5.0"
},
"homepage": "https://github.com/substack/minimist",
"keywords": [
"argv",
"getopt",
"parser",
"optimist"
],
"license": "MIT",
"main": "index.js",
"name": "minimist",
"repository": {
"type": "git",
"url": "git://github.com/substack/minimist.git"
},
"scripts": {
"coverage": "covert test/*.js",
"test": "tap test/*.js"
},
"testling": {
"files": "test/*.js",
"browsers": [
"ie/6..latest",
"ff/5",
"firefox/latest",
"chrome/10",
"chrome/latest",
"safari/5.1",
"safari/latest",
"opera/12"
]
},
"version": "1.2.0"
}

View File

@@ -0,0 +1,91 @@
# minimist
parse argument options
This module is the guts of optimist's argument parser without all the
fanciful decoration.
[![browser support](https://ci.testling.com/substack/minimist.png)](http://ci.testling.com/substack/minimist)
[![build status](https://secure.travis-ci.org/substack/minimist.png)](http://travis-ci.org/substack/minimist)
# example
``` js
var argv = require('minimist')(process.argv.slice(2));
console.dir(argv);
```
```
$ node example/parse.js -a beep -b boop
{ _: [], a: 'beep', b: 'boop' }
```
```
$ node example/parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz
{ _: [ 'foo', 'bar', 'baz' ],
x: 3,
y: 4,
n: 5,
a: true,
b: true,
c: true,
beep: 'boop' }
```
# methods
``` js
var parseArgs = require('minimist')
```
## var argv = parseArgs(args, opts={})
Return an argument object `argv` populated with the array arguments from `args`.
`argv._` contains all the arguments that didn't have an option associated with
them.
Numeric-looking arguments will be returned as numbers unless `opts.string` or
`opts.boolean` is set for that argument name.
Any arguments after `'--'` will not be parsed and will end up in `argv._`.
options can be:
* `opts.string` - a string or array of strings argument names to always treat as
strings
* `opts.boolean` - a boolean, string or array of strings to always treat as
booleans. if `true` will treat all double hyphenated arguments without equal signs
as boolean (e.g. affects `--foo`, not `-f` or `--foo=bar`)
* `opts.alias` - an object mapping string names to strings or arrays of string
argument names to use as aliases
* `opts.default` - an object mapping string argument names to default values
* `opts.stopEarly` - when true, populate `argv._` with everything after the
first non-option
* `opts['--']` - when true, populate `argv._` with everything before the `--`
and `argv['--']` with everything after the `--`. Here's an example:
* `opts.unknown` - a function which is invoked with a command line parameter not
defined in the `opts` configuration object. If the function returns `false`, the
unknown option is not added to `argv`.
```
> require('./')('one two three -- four five --six'.split(' '), { '--': true })
{ _: [ 'one', 'two', 'three' ],
'--': [ 'four', 'five', '--six' ] }
```
Note that with `opts['--']` set, parsing for arguments still stops after the
`--`.
# install
With [npm](https://npmjs.org) do:
```
npm install minimist
```
# license
MIT

View File

@@ -0,0 +1,32 @@
var parse = require('../');
var test = require('tape');
test('flag boolean true (default all --args to boolean)', function (t) {
var argv = parse(['moo', '--honk', 'cow'], {
boolean: true
});
t.deepEqual(argv, {
honk: true,
_: ['moo', 'cow']
});
t.deepEqual(typeof argv.honk, 'boolean');
t.end();
});
test('flag boolean true only affects double hyphen arguments without equals signs', function (t) {
var argv = parse(['moo', '--honk', 'cow', '-p', '55', '--tacos=good'], {
boolean: true
});
t.deepEqual(argv, {
honk: true,
tacos: 'good',
p: 55,
_: ['moo', 'cow']
});
t.deepEqual(typeof argv.honk, 'boolean');
t.end();
});

View File

@@ -0,0 +1,166 @@
var parse = require('../');
var test = require('tape');
test('flag boolean default false', function (t) {
var argv = parse(['moo'], {
boolean: ['t', 'verbose'],
default: { verbose: false, t: false }
});
t.deepEqual(argv, {
verbose: false,
t: false,
_: ['moo']
});
t.deepEqual(typeof argv.verbose, 'boolean');
t.deepEqual(typeof argv.t, 'boolean');
t.end();
});
test('boolean groups', function (t) {
var argv = parse([ '-x', '-z', 'one', 'two', 'three' ], {
boolean: ['x','y','z']
});
t.deepEqual(argv, {
x : true,
y : false,
z : true,
_ : [ 'one', 'two', 'three' ]
});
t.deepEqual(typeof argv.x, 'boolean');
t.deepEqual(typeof argv.y, 'boolean');
t.deepEqual(typeof argv.z, 'boolean');
t.end();
});
test('boolean and alias with chainable api', function (t) {
var aliased = [ '-h', 'derp' ];
var regular = [ '--herp', 'derp' ];
var opts = {
herp: { alias: 'h', boolean: true }
};
var aliasedArgv = parse(aliased, {
boolean: 'herp',
alias: { h: 'herp' }
});
var propertyArgv = parse(regular, {
boolean: 'herp',
alias: { h: 'herp' }
});
var expected = {
herp: true,
h: true,
'_': [ 'derp' ]
};
t.same(aliasedArgv, expected);
t.same(propertyArgv, expected);
t.end();
});
test('boolean and alias with options hash', function (t) {
var aliased = [ '-h', 'derp' ];
var regular = [ '--herp', 'derp' ];
var opts = {
alias: { 'h': 'herp' },
boolean: 'herp'
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
var expected = {
herp: true,
h: true,
'_': [ 'derp' ]
};
t.same(aliasedArgv, expected);
t.same(propertyArgv, expected);
t.end();
});
test('boolean and alias array with options hash', function (t) {
var aliased = [ '-h', 'derp' ];
var regular = [ '--herp', 'derp' ];
var alt = [ '--harp', 'derp' ];
var opts = {
alias: { 'h': ['herp', 'harp'] },
boolean: 'h'
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
var altPropertyArgv = parse(alt, opts);
var expected = {
harp: true,
herp: true,
h: true,
'_': [ 'derp' ]
};
t.same(aliasedArgv, expected);
t.same(propertyArgv, expected);
t.same(altPropertyArgv, expected);
t.end();
});
test('boolean and alias using explicit true', function (t) {
var aliased = [ '-h', 'true' ];
var regular = [ '--herp', 'true' ];
var opts = {
alias: { h: 'herp' },
boolean: 'h'
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
var expected = {
herp: true,
h: true,
'_': [ ]
};
t.same(aliasedArgv, expected);
t.same(propertyArgv, expected);
t.end();
});
// regression, see https://github.com/substack/node-optimist/issues/71
test('boolean and --x=true', function(t) {
var parsed = parse(['--boool', '--other=true'], {
boolean: 'boool'
});
t.same(parsed.boool, true);
t.same(parsed.other, 'true');
parsed = parse(['--boool', '--other=false'], {
boolean: 'boool'
});
t.same(parsed.boool, true);
t.same(parsed.other, 'false');
t.end();
});
test('boolean --boool=true', function (t) {
var parsed = parse(['--boool=true'], {
default: {
boool: false
},
boolean: ['boool']
});
t.same(parsed.boool, true);
t.end();
});
test('boolean --boool=false', function (t) {
var parsed = parse(['--boool=false'], {
default: {
boool: true
},
boolean: ['boool']
});
t.same(parsed.boool, false);
t.end();
});

View File

@@ -0,0 +1,31 @@
var parse = require('../');
var test = require('tape');
test('-', function (t) {
t.plan(5);
t.deepEqual(parse([ '-n', '-' ]), { n: '-', _: [] });
t.deepEqual(parse([ '-' ]), { _: [ '-' ] });
t.deepEqual(parse([ '-f-' ]), { f: '-', _: [] });
t.deepEqual(
parse([ '-b', '-' ], { boolean: 'b' }),
{ b: true, _: [ '-' ] }
);
t.deepEqual(
parse([ '-s', '-' ], { string: 's' }),
{ s: '-', _: [] }
);
});
test('-a -- b', function (t) {
t.plan(3);
t.deepEqual(parse([ '-a', '--', 'b' ]), { a: true, _: [ 'b' ] });
t.deepEqual(parse([ '--a', '--', 'b' ]), { a: true, _: [ 'b' ] });
t.deepEqual(parse([ '--a', '--', 'b' ]), { a: true, _: [ 'b' ] });
});
test('move arguments after the -- into their own `--` array', function(t) {
t.plan(1);
t.deepEqual(
parse([ '--name', 'John', 'before', '--', 'after' ], { '--': true }),
{ name: 'John', _: [ 'before' ], '--': [ 'after' ] });
});

View File

@@ -0,0 +1,35 @@
var test = require('tape');
var parse = require('../');
test('boolean default true', function (t) {
var argv = parse([], {
boolean: 'sometrue',
default: { sometrue: true }
});
t.equal(argv.sometrue, true);
t.end();
});
test('boolean default false', function (t) {
var argv = parse([], {
boolean: 'somefalse',
default: { somefalse: false }
});
t.equal(argv.somefalse, false);
t.end();
});
test('boolean default to null', function (t) {
var argv = parse([], {
boolean: 'maybe',
default: { maybe: null }
});
t.equal(argv.maybe, null);
var argv = parse(['--maybe'], {
boolean: 'maybe',
default: { maybe: null }
});
t.equal(argv.maybe, true);
t.end();
})

View File

@@ -0,0 +1,22 @@
var parse = require('../');
var test = require('tape');
test('dotted alias', function (t) {
var argv = parse(['--a.b', '22'], {default: {'a.b': 11}, alias: {'a.b': 'aa.bb'}});
t.equal(argv.a.b, 22);
t.equal(argv.aa.bb, 22);
t.end();
});
test('dotted default', function (t) {
var argv = parse('', {default: {'a.b': 11}, alias: {'a.b': 'aa.bb'}});
t.equal(argv.a.b, 11);
t.equal(argv.aa.bb, 11);
t.end();
});
test('dotted default with no alias', function (t) {
var argv = parse('', {default: {'a.b': 11}});
t.equal(argv.a.b, 11);
t.end();
});

View File

@@ -0,0 +1,16 @@
var parse = require('../');
var test = require('tape');
test('short -k=v' , function (t) {
t.plan(1);
var argv = parse([ '-b=123' ]);
t.deepEqual(argv, { b: 123, _: [] });
});
test('multi short -k=v' , function (t) {
t.plan(1);
var argv = parse([ '-a=whatever', '-b=robots' ]);
t.deepEqual(argv, { a: 'whatever', b: 'robots', _: [] });
});

View File

@@ -0,0 +1,31 @@
var test = require('tape');
var parse = require('../');
test('long opts', function (t) {
t.deepEqual(
parse([ '--bool' ]),
{ bool : true, _ : [] },
'long boolean'
);
t.deepEqual(
parse([ '--pow', 'xixxle' ]),
{ pow : 'xixxle', _ : [] },
'long capture sp'
);
t.deepEqual(
parse([ '--pow=xixxle' ]),
{ pow : 'xixxle', _ : [] },
'long capture eq'
);
t.deepEqual(
parse([ '--host', 'localhost', '--port', '555' ]),
{ host : 'localhost', port : 555, _ : [] },
'long captures sp'
);
t.deepEqual(
parse([ '--host=localhost', '--port=555' ]),
{ host : 'localhost', port : 555, _ : [] },
'long captures eq'
);
t.end();
});

View File

@@ -0,0 +1,36 @@
var parse = require('../');
var test = require('tape');
test('nums', function (t) {
var argv = parse([
'-x', '1234',
'-y', '5.67',
'-z', '1e7',
'-w', '10f',
'--hex', '0xdeadbeef',
'789'
]);
t.deepEqual(argv, {
x : 1234,
y : 5.67,
z : 1e7,
w : '10f',
hex : 0xdeadbeef,
_ : [ 789 ]
});
t.deepEqual(typeof argv.x, 'number');
t.deepEqual(typeof argv.y, 'number');
t.deepEqual(typeof argv.z, 'number');
t.deepEqual(typeof argv.w, 'string');
t.deepEqual(typeof argv.hex, 'number');
t.deepEqual(typeof argv._[0], 'number');
t.end();
});
test('already a number', function (t) {
var argv = parse([ '-x', 1234, 789 ]);
t.deepEqual(argv, { x : 1234, _ : [ 789 ] });
t.deepEqual(typeof argv.x, 'number');
t.deepEqual(typeof argv._[0], 'number');
t.end();
});

View File

@@ -0,0 +1,197 @@
var parse = require('../');
var test = require('tape');
test('parse args', function (t) {
t.deepEqual(
parse([ '--no-moo' ]),
{ moo : false, _ : [] },
'no'
);
t.deepEqual(
parse([ '-v', 'a', '-v', 'b', '-v', 'c' ]),
{ v : ['a','b','c'], _ : [] },
'multi'
);
t.end();
});
test('comprehensive', function (t) {
t.deepEqual(
parse([
'--name=meowmers', 'bare', '-cats', 'woo',
'-h', 'awesome', '--multi=quux',
'--key', 'value',
'-b', '--bool', '--no-meep', '--multi=baz',
'--', '--not-a-flag', 'eek'
]),
{
c : true,
a : true,
t : true,
s : 'woo',
h : 'awesome',
b : true,
bool : true,
key : 'value',
multi : [ 'quux', 'baz' ],
meep : false,
name : 'meowmers',
_ : [ 'bare', '--not-a-flag', 'eek' ]
}
);
t.end();
});
test('flag boolean', function (t) {
var argv = parse([ '-t', 'moo' ], { boolean: 't' });
t.deepEqual(argv, { t : true, _ : [ 'moo' ] });
t.deepEqual(typeof argv.t, 'boolean');
t.end();
});
test('flag boolean value', function (t) {
var argv = parse(['--verbose', 'false', 'moo', '-t', 'true'], {
boolean: [ 't', 'verbose' ],
default: { verbose: true }
});
t.deepEqual(argv, {
verbose: false,
t: true,
_: ['moo']
});
t.deepEqual(typeof argv.verbose, 'boolean');
t.deepEqual(typeof argv.t, 'boolean');
t.end();
});
test('newlines in params' , function (t) {
var args = parse([ '-s', "X\nX" ])
t.deepEqual(args, { _ : [], s : "X\nX" });
// reproduce in bash:
// VALUE="new
// line"
// node program.js --s="$VALUE"
args = parse([ "--s=X\nX" ])
t.deepEqual(args, { _ : [], s : "X\nX" });
t.end();
});
test('strings' , function (t) {
var s = parse([ '-s', '0001234' ], { string: 's' }).s;
t.equal(s, '0001234');
t.equal(typeof s, 'string');
var x = parse([ '-x', '56' ], { string: 'x' }).x;
t.equal(x, '56');
t.equal(typeof x, 'string');
t.end();
});
test('stringArgs', function (t) {
var s = parse([ ' ', ' ' ], { string: '_' })._;
t.same(s.length, 2);
t.same(typeof s[0], 'string');
t.same(s[0], ' ');
t.same(typeof s[1], 'string');
t.same(s[1], ' ');
t.end();
});
test('empty strings', function(t) {
var s = parse([ '-s' ], { string: 's' }).s;
t.equal(s, '');
t.equal(typeof s, 'string');
var str = parse([ '--str' ], { string: 'str' }).str;
t.equal(str, '');
t.equal(typeof str, 'string');
var letters = parse([ '-art' ], {
string: [ 'a', 't' ]
});
t.equal(letters.a, '');
t.equal(letters.r, true);
t.equal(letters.t, '');
t.end();
});
test('string and alias', function(t) {
var x = parse([ '--str', '000123' ], {
string: 's',
alias: { s: 'str' }
});
t.equal(x.str, '000123');
t.equal(typeof x.str, 'string');
t.equal(x.s, '000123');
t.equal(typeof x.s, 'string');
var y = parse([ '-s', '000123' ], {
string: 'str',
alias: { str: 's' }
});
t.equal(y.str, '000123');
t.equal(typeof y.str, 'string');
t.equal(y.s, '000123');
t.equal(typeof y.s, 'string');
t.end();
});
test('slashBreak', function (t) {
t.same(
parse([ '-I/foo/bar/baz' ]),
{ I : '/foo/bar/baz', _ : [] }
);
t.same(
parse([ '-xyz/foo/bar/baz' ]),
{ x : true, y : true, z : '/foo/bar/baz', _ : [] }
);
t.end();
});
test('alias', function (t) {
var argv = parse([ '-f', '11', '--zoom', '55' ], {
alias: { z: 'zoom' }
});
t.equal(argv.zoom, 55);
t.equal(argv.z, argv.zoom);
t.equal(argv.f, 11);
t.end();
});
test('multiAlias', function (t) {
var argv = parse([ '-f', '11', '--zoom', '55' ], {
alias: { z: [ 'zm', 'zoom' ] }
});
t.equal(argv.zoom, 55);
t.equal(argv.z, argv.zoom);
t.equal(argv.z, argv.zm);
t.equal(argv.f, 11);
t.end();
});
test('nested dotted objects', function (t) {
var argv = parse([
'--foo.bar', '3', '--foo.baz', '4',
'--foo.quux.quibble', '5', '--foo.quux.o_O',
'--beep.boop'
]);
t.same(argv.foo, {
bar : 3,
baz : 4,
quux : {
quibble : 5,
o_O : true
}
});
t.same(argv.beep, { boop : true });
t.end();
});

View File

@@ -0,0 +1,9 @@
var parse = require('../');
var test = require('tape');
test('parse with modifier functions' , function (t) {
t.plan(1);
var argv = parse([ '-b', '123' ], { boolean: 'b' });
t.deepEqual(argv, { b: true, _: [123] });
});

View File

@@ -0,0 +1,67 @@
var parse = require('../');
var test = require('tape');
test('numeric short args', function (t) {
t.plan(2);
t.deepEqual(parse([ '-n123' ]), { n: 123, _: [] });
t.deepEqual(
parse([ '-123', '456' ]),
{ 1: true, 2: true, 3: 456, _: [] }
);
});
test('short', function (t) {
t.deepEqual(
parse([ '-b' ]),
{ b : true, _ : [] },
'short boolean'
);
t.deepEqual(
parse([ 'foo', 'bar', 'baz' ]),
{ _ : [ 'foo', 'bar', 'baz' ] },
'bare'
);
t.deepEqual(
parse([ '-cats' ]),
{ c : true, a : true, t : true, s : true, _ : [] },
'group'
);
t.deepEqual(
parse([ '-cats', 'meow' ]),
{ c : true, a : true, t : true, s : 'meow', _ : [] },
'short group next'
);
t.deepEqual(
parse([ '-h', 'localhost' ]),
{ h : 'localhost', _ : [] },
'short capture'
);
t.deepEqual(
parse([ '-h', 'localhost', '-p', '555' ]),
{ h : 'localhost', p : 555, _ : [] },
'short captures'
);
t.end();
});
test('mixed short bool and capture', function (t) {
t.same(
parse([ '-h', 'localhost', '-fp', '555', 'script.js' ]),
{
f : true, p : 555, h : 'localhost',
_ : [ 'script.js' ]
}
);
t.end();
});
test('short and long', function (t) {
t.deepEqual(
parse([ '-h', 'localhost', '-fp', '555', 'script.js' ]),
{
f : true, p : 555, h : 'localhost',
_ : [ 'script.js' ]
}
);
t.end();
});

View File

@@ -0,0 +1,15 @@
var parse = require('../');
var test = require('tape');
test('stops parsing on the first non-option when stopEarly is set', function (t) {
var argv = parse(['--aaa', 'bbb', 'ccc', '--ddd'], {
stopEarly: true
});
t.deepEqual(argv, {
aaa: 'bbb',
_: ['ccc', '--ddd']
});
t.end();
});

View File

@@ -0,0 +1,102 @@
var parse = require('../');
var test = require('tape');
test('boolean and alias is not unknown', function (t) {
var unknown = [];
function unknownFn(arg) {
unknown.push(arg);
return false;
}
var aliased = [ '-h', 'true', '--derp', 'true' ];
var regular = [ '--herp', 'true', '-d', 'true' ];
var opts = {
alias: { h: 'herp' },
boolean: 'h',
unknown: unknownFn
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
t.same(unknown, ['--derp', '-d']);
t.end();
});
test('flag boolean true any double hyphen argument is not unknown', function (t) {
var unknown = [];
function unknownFn(arg) {
unknown.push(arg);
return false;
}
var argv = parse(['--honk', '--tacos=good', 'cow', '-p', '55'], {
boolean: true,
unknown: unknownFn
});
t.same(unknown, ['--tacos=good', 'cow', '-p']);
t.same(argv, {
honk: true,
_: []
});
t.end();
});
test('string and alias is not unknown', function (t) {
var unknown = [];
function unknownFn(arg) {
unknown.push(arg);
return false;
}
var aliased = [ '-h', 'hello', '--derp', 'goodbye' ];
var regular = [ '--herp', 'hello', '-d', 'moon' ];
var opts = {
alias: { h: 'herp' },
string: 'h',
unknown: unknownFn
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
t.same(unknown, ['--derp', '-d']);
t.end();
});
test('default and alias is not unknown', function (t) {
var unknown = [];
function unknownFn(arg) {
unknown.push(arg);
return false;
}
var aliased = [ '-h', 'hello' ];
var regular = [ '--herp', 'hello' ];
var opts = {
default: { 'h': 'bar' },
alias: { 'h': 'herp' },
unknown: unknownFn
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
t.same(unknown, []);
t.end();
unknownFn(); // exercise fn for 100% coverage
});
test('value following -- is not unknown', function (t) {
var unknown = [];
function unknownFn(arg) {
unknown.push(arg);
return false;
}
var aliased = [ '--bad', '--', 'good', 'arg' ];
var opts = {
'--': true,
unknown: unknownFn
};
var argv = parse(aliased, opts);
t.same(unknown, ['--bad']);
t.same(argv, {
'--': ['good', 'arg'],
'_': []
})
t.end();
});

View File

@@ -0,0 +1,8 @@
var parse = require('../');
var test = require('tape');
test('whitespace should be whitespace' , function (t) {
t.plan(1);
var x = parse([ '-x', '\t' ]).x;
t.equal(x, '\t');
});

View File

@@ -0,0 +1,86 @@
{
"_args": [
[
"sane@1.6.0",
"C:\\Users\\deranjer\\go\\src\\github.com\\deranjer\\goTorrent\\torrent-project"
]
],
"_from": "sane@1.6.0",
"_id": "sane@1.6.0",
"_inBundle": false,
"_integrity": "sha1-lhDEUjB6E10pwf3+JUcDQYDEZ3U=",
"_location": "/react-scripts/sane",
"_phantomChildren": {
"node-int64": "0.4.0"
},
"_requested": {
"type": "version",
"registry": true,
"raw": "sane@1.6.0",
"name": "sane",
"escapedName": "sane",
"rawSpec": "1.6.0",
"saveSpec": null,
"fetchSpec": "1.6.0"
},
"_requiredBy": [
"/react-scripts/jest-haste-map"
],
"_resolved": "https://registry.npmjs.org/sane/-/sane-1.6.0.tgz",
"_spec": "1.6.0",
"_where": "C:\\Users\\deranjer\\go\\src\\github.com\\deranjer\\goTorrent\\torrent-project",
"author": {
"name": "amasad"
},
"bin": {
"sane": "./src/cli.js"
},
"bugs": {
"url": "https://github.com/amasad/sane/issues"
},
"dependencies": {
"anymatch": "^1.3.0",
"exec-sh": "^0.2.0",
"fb-watchman": "^1.8.0",
"minimatch": "^3.0.2",
"minimist": "^1.1.1",
"walker": "~1.0.5",
"watch": "~0.10.0"
},
"description": "Sane aims to be fast, small, and reliable file system watcher.",
"devDependencies": {
"jshint": "^2.5.10",
"mocha": "~1.17.1",
"rimraf": "~2.2.6",
"tmp": "0.0.27"
},
"engines": {
"node": ">=0.6.0"
},
"files": [
"src",
"index.js"
],
"homepage": "https://github.com/amasad/sane",
"keywords": [
"watch",
"file",
"fswatcher",
"watchfile",
"fs",
"watching"
],
"license": "MIT",
"main": "index.js",
"name": "sane",
"repository": {
"type": "git",
"url": "git+https://github.com/amasad/sane.git"
},
"scripts": {
"prepublish": "jshint --config=.jshintrc src/ index.js && mocha --bail",
"test": "jshint --config=.jshintrc src/ index.js && mocha --bail test/test.js && mocha --bail test/utils-test.js",
"test:debug": "mocha debug --bail"
},
"version": "1.6.0"
}

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env node
'use strict';
var sane = require('../');
var argv = require('minimist')(process.argv.slice(2));
var execshell = require('exec-sh');
if(argv._.length === 0) {
var msg = 'Usage: sane <command> [...directory] [--glob=<filePattern>] ' +
'[--poll] [--watchman] [--dot] [--wait=<seconds>]';
console.error(msg);
process.exit();
}
var opts = {};
var command = argv._[0];
var dir = argv._[1] || process.cwd();
var waitTime = Number(argv.wait || argv.w);
var dot = argv.dot || argv.d;
var glob = argv.glob || argv.g;
var poll = argv.poll || argv.p;
var watchman = argv.watchman || argv.w;
if (dot) { opts.dot = true; }
if (glob) { opts.glob = glob; }
if (poll) { opts.poll = true; }
if (watchman) { opts.watchman = true; }
var wait = false;
var watcher = sane(dir, opts);
watcher.on('ready', function () {
console.log('Watching: ', dir + '/' + (opts.glob || ''));
execshell(command);
});
watcher.on('change', function (filepath) {
if (wait) { return; }
console.log('Change detected in:', filepath);
execshell(command);
if (waitTime > 0) {
wait = true;
setTimeout(function () {
wait = false;
}, waitTime * 1000);
}
});

View File

@@ -0,0 +1,67 @@
'use strict';
var anymatch = require('anymatch');
var minimatch = require('minimatch');
/**
* Constants
*/
exports.DEFAULT_DELAY = 100;
exports.CHANGE_EVENT = 'change';
exports.DELETE_EVENT = 'delete';
exports.ADD_EVENT = 'add';
exports.ALL_EVENT = 'all';
/**
* Assigns options to the watcher.
*
* @param {NodeWatcher|PollWatcher|WatchmanWatcher} watcher
* @param {?object} opts
* @return {boolean}
* @public
*/
exports.assignOptions = function(watcher, opts) {
opts = opts || {};
watcher.globs = opts.glob || [];
watcher.dot = opts.dot || false;
watcher.ignored = opts.ignored || false;
if (!Array.isArray(watcher.globs)) {
watcher.globs = [watcher.globs];
}
watcher.hasIgnore = Boolean(opts.ignored) &&
!(Array.isArray(opts) && opts.length > 0);
watcher.doIgnore = opts.ignored ? anymatch(opts.ignored) : function () {
return false;
};
return opts;
};
/**
* Checks a file relative path against the globs array.
*
* @param {array} globs
* @param {string} relativePath
* @return {boolean}
* @public
*/
exports.isFileIncluded = function(globs, dot, doIgnore, relativePath) {
var matched;
if (globs.length) {
for (var i = 0; i < globs.length; i++) {
if (minimatch(relativePath, globs[i], {dot: dot}) &&
!doIgnore(relativePath)) {
matched = true;
break;
}
}
} else {
// Make sure we honor the dot option if even we're not using globs.
matched = (dot || minimatch(relativePath, '**/*')) &&
!doIgnore(relativePath);
}
return matched;
};

View File

@@ -0,0 +1,376 @@
'use strict';
var fs = require('fs');
var path = require('path');
var walker = require('walker');
var common = require('./common');
var platform = require('os').platform();
var EventEmitter = require('events').EventEmitter;
var anymatch = require('anymatch');
/**
* Constants
*/
var DEFAULT_DELAY = common.DEFAULT_DELAY;
var CHANGE_EVENT = common.CHANGE_EVENT;
var DELETE_EVENT = common.DELETE_EVENT;
var ADD_EVENT = common.ADD_EVENT;
var ALL_EVENT = common.ALL_EVENT;
/**
* Export `NodeWatcher` class.
*/
module.exports = NodeWatcher;
/**
* Watches `dir`.
*
* @class NodeWatcher
* @param {String} dir
* @param {Object} opts
* @public
*/
function NodeWatcher(dir, opts) {
opts = common.assignOptions(this, opts);
this.watched = Object.create(null);
this.changeTimers = Object.create(null);
this.dirRegistery = Object.create(null);
this.root = path.resolve(dir);
this.watchdir = this.watchdir.bind(this);
this.register = this.register.bind(this);
this.watchdir(this.root);
recReaddir(
this.root,
this.watchdir,
this.register,
this.emit.bind(this, 'ready'),
this.ignored
);
}
NodeWatcher.prototype.__proto__ = EventEmitter.prototype;
/**
* Register files that matches our globs to know what to type of event to
* emit in the future.
*
* Registery looks like the following:
*
* dirRegister => Map {
* dirpath => Map {
* filename => true
* }
* }
*
* @param {string} filepath
* @return {boolean} whether or not we have registered the file.
* @private
*/
NodeWatcher.prototype.register = function(filepath) {
var relativePath = path.relative(this.root, filepath);
if (!common.isFileIncluded(
this.globs,
this.dot,
this.doIgnore,
relativePath)) {
return false;
}
var dir = path.dirname(filepath);
if (!this.dirRegistery[dir]) {
this.dirRegistery[dir] = Object.create(null);
}
var filename = path.basename(filepath);
this.dirRegistery[dir][filename] = true;
return true;
};
/**
* Removes a file from the registery.
*
* @param {string} filepath
* @private
*/
NodeWatcher.prototype.unregister = function(filepath) {
var dir = path.dirname(filepath);
if (this.dirRegistery[dir]) {
var filename = path.basename(filepath);
delete this.dirRegistery[dir][filename];
}
};
/**
* Removes a dir from the registery.
*
* @param {string} dirpath
* @private
*/
NodeWatcher.prototype.unregisterDir = function(dirpath) {
if (this.dirRegistery[dirpath]) {
delete this.dirRegistery[dirpath];
}
};
/**
* Checks if a file or directory exists in the registery.
*
* @param {string} fullpath
* @return {boolean}
* @private
*/
NodeWatcher.prototype.registered = function(fullpath) {
var dir = path.dirname(fullpath);
return this.dirRegistery[fullpath] ||
this.dirRegistery[dir] && this.dirRegistery[dir][path.basename(fullpath)];
};
/**
* Watch a directory.
*
* @param {string} dir
* @private
*/
NodeWatcher.prototype.watchdir = function(dir) {
if (this.watched[dir]) {
return;
}
var watcher = fs.watch(
dir,
{ persistent: true },
this.normalizeChange.bind(this, dir)
);
this.watched[dir] = watcher;
// Workaround Windows node issue #4337.
if (platform === 'win32') {
watcher.on('error', function(error) {
if (error.code !== 'EPERM') {
throw error;
}
});
}
if (this.root !== dir) {
this.register(dir);
}
};
/**
* Stop watching a directory.
*
* @param {string} dir
* @private
*/
NodeWatcher.prototype.stopWatching = function(dir) {
if (this.watched[dir]) {
this.watched[dir].close();
delete this.watched[dir];
}
};
/**
* End watching.
*
* @public
*/
NodeWatcher.prototype.close = function(callback) {
Object.keys(this.watched).forEach(this.stopWatching, this);
this.removeAllListeners();
if (typeof callback === 'function') {
setImmediate(callback.bind(null, null, true));
}
};
/**
* On some platforms, as pointed out on the fs docs (most likely just win32)
* the file argument might be missing from the fs event. Try to detect what
* change by detecting if something was deleted or the most recent file change.
*
* @param {string} dir
* @param {string} event
* @param {string} file
* @public
*/
NodeWatcher.prototype.detectChangedFile = function(dir, event, callback) {
if (!this.dirRegistery[dir]) {
return;
}
var found = false;
var closest = {mtime: 0};
var c = 0;
Object.keys(this.dirRegistery[dir]).forEach(function(file, i, arr) {
fs.lstat(path.join(dir, file), function(error, stat) {
if (found) {
return;
}
if (error) {
if (error.code === 'ENOENT' ||
(platform === 'win32' && error.code === 'EPERM')) {
found = true;
callback(file);
} else {
this.emit('error', error);
}
} else {
if (stat.mtime > closest.mtime) {
stat.file = file;
closest = stat;
}
if (arr.length === ++c) {
callback(closest.file);
}
}
}.bind(this));
}, this);
};
/**
* Normalize fs events and pass it on to be processed.
*
* @param {string} dir
* @param {string} event
* @param {string} file
* @public
*/
NodeWatcher.prototype.normalizeChange = function(dir, event, file) {
if (!file) {
this.detectChangedFile(dir, event, function(actualFile) {
if (actualFile) {
this.processChange(dir, event, actualFile);
}
}.bind(this));
} else {
this.processChange(dir, event, path.normalize(file));
}
};
/**
* Process changes.
*
* @param {string} dir
* @param {string} event
* @param {string} file
* @public
*/
NodeWatcher.prototype.processChange = function(dir, event, file) {
var fullPath = path.join(dir, file);
var relativePath = path.join(path.relative(this.root, dir), file);
fs.lstat(fullPath, function(error, stat) {
if (error && error.code !== 'ENOENT') {
this.emit('error', error);
} else if (!error && stat.isDirectory()) {
// win32 emits usless change events on dirs.
if (event !== 'change') {
this.watchdir(fullPath);
if (common.isFileIncluded(
this.globs,
this.dot,
this.doIgnore,
relativePath)) {
this.emitEvent(ADD_EVENT, relativePath, stat);
}
}
} else {
var registered = this.registered(fullPath);
if (error && error.code === 'ENOENT') {
this.unregister(fullPath);
this.stopWatching(fullPath);
this.unregisterDir(fullPath);
if (registered) {
this.emitEvent(DELETE_EVENT, relativePath);
}
} else if (registered) {
this.emitEvent(CHANGE_EVENT, relativePath, stat);
} else {
if (this.register(fullPath)) {
this.emitEvent(ADD_EVENT, relativePath, stat);
}
}
}
}.bind(this));
};
/**
* Triggers a 'change' event after debounding it to take care of duplicate
* events on os x.
*
* @private
*/
NodeWatcher.prototype.emitEvent = function(type, file, stat) {
var key = type + '-' + file;
var addKey = ADD_EVENT + '-' + file;
if (type === CHANGE_EVENT && this.changeTimers[addKey]) {
// Ignore the change event that is immediately fired after an add event.
// (This happens on Linux).
return;
}
clearTimeout(this.changeTimers[key]);
this.changeTimers[key] = setTimeout(function() {
delete this.changeTimers[key];
this.emit(type, file, this.root, stat);
this.emit(ALL_EVENT, type, file, this.root, stat);
}.bind(this), DEFAULT_DELAY);
};
/**
* Traverse a directory recursively calling `callback` on every directory.
*
* @param {string} dir
* @param {function} dirCallback
* @param {function} fileCallback
* @param {function} endCallback
* @param {*} ignored
* @private
*/
function recReaddir(dir, dirCallback, fileCallback, endCallback, ignored) {
walker(dir)
.filterDir(function(currentDir) {
return !anymatch(ignored, currentDir);
})
.on('dir', normalizeProxy(dirCallback))
.on('file', normalizeProxy(fileCallback))
.on('end', function() {
if (platform === 'win32') {
setTimeout(endCallback, 1000);
} else {
endCallback();
}
});
}
/**
* Returns a callback that when called will normalize a path and call the
* original callback
*
* @param {function} callback
* @return {function}
* @private
*/
function normalizeProxy(callback) {
return function(filepath) {
return callback(path.normalize(filepath));
};
}

View File

@@ -0,0 +1,118 @@
'use strict';
var fs = require('fs');
var path = require('path');
var watch = require('watch');
var common = require('./common');
var EventEmitter = require('events').EventEmitter;
/**
* Constants
*/
var DEFAULT_DELAY = common.DEFAULT_DELAY;
var CHANGE_EVENT = common.CHANGE_EVENT;
var DELETE_EVENT = common.DELETE_EVENT;
var ADD_EVENT = common.ADD_EVENT;
var ALL_EVENT = common.ALL_EVENT;
/**
* Export `PollWatcher` class.
*/
module.exports = PollWatcher;
/**
* Watches `dir`.
*
* @class PollWatcher
* @param String dir
* @param {Object} opts
* @public
*/
function PollWatcher(dir, opts) {
opts = common.assignOptions(this, opts);
this.watched = Object.create(null);
this.root = path.resolve(dir);
watch.createMonitor(
this.root,
{ interval: opts.interval || DEFAULT_DELAY,
filter: this.filter.bind(this)
},
this.init.bind(this)
);
}
PollWatcher.prototype.__proto__ = EventEmitter.prototype;
/**
* Given a fullpath of a file or directory check if we need to watch it.
*
* @param {string} filepath
* @param {object} stat
* @private
*/
PollWatcher.prototype.filter = function(filepath, stat) {
return stat.isDirectory() || common.isFileIncluded(
this.globs,
this.dot,
this.doIgnore,
path.relative(this.root, filepath)
);
};
/**
* Initiate the polling file watcher with the event emitter passed from
* `watch.watchTree`.
*
* @param {EventEmitter} monitor
* @public
*/
PollWatcher.prototype.init = function(monitor) {
this.watched = monitor.files;
monitor.on('changed', this.emitEvent.bind(this, CHANGE_EVENT));
monitor.on('removed', this.emitEvent.bind(this, DELETE_EVENT));
monitor.on('created', this.emitEvent.bind(this, ADD_EVENT));
// 1 second wait because mtime is second-based.
setTimeout(this.emit.bind(this, 'ready'), 1000);
};
/**
* Transform and emit an event comming from the poller.
*
* @param {EventEmitter} monitor
* @public
*/
PollWatcher.prototype.emitEvent = function(type, file, stat) {
file = path.relative(this.root, file);
if (type === DELETE_EVENT) {
// Matching the non-polling API
stat = null;
}
this.emit(type, file, this.root, stat);
this.emit(ALL_EVENT, type, file, this.root, stat);
};
/**
* End watching.
*
* @public
*/
PollWatcher.prototype.close = function(callback) {
Object.keys(this.watched).forEach(function(filepath) {
fs.unwatchFile(filepath);
});
this.removeAllListeners();
if (typeof callback === 'function') {
setImmediate(callback.bind(null, null, true));
}
};

View File

@@ -0,0 +1,47 @@
'use strict';
var RECRAWL_WARNINGS = []; // shared structure, one per process.
var REG = /Recrawled this watch (\d+) times, most recently because:\n([^:]+)/;
module.exports = RecrawlWarning;
function RecrawlWarning(root, count) {
this.root = root;
this.count = count;
}
RecrawlWarning.RECRAWL_WARNINGS = RECRAWL_WARNINGS;
RecrawlWarning.REGEXP = REG;
RecrawlWarning.findByRoot = function(root) {
for (var i = 0; i < RECRAWL_WARNINGS.length; i++) {
var warning = RECRAWL_WARNINGS[i];
if (warning.root === root) {
return warning;
}
}
};
RecrawlWarning.isRecrawlWarningDupe = function(warningMessage) {
if (typeof warningMessage !== 'string') { return false; }
var match = warningMessage.match(REG);
if (!match) { return false; }
var count = Number(match[1]);
var root = match[2];
var warning = RecrawlWarning.findByRoot(root);
if (warning) {
// only keep the highest count, assume count to either stay the same or
// increase.
if (warning.count >= count ) {
return true;
} else {
// update the existing warning to the latest (highest) count
warning.count = count;
return false;
}
} else {
RECRAWL_WARNINGS.push(new RecrawlWarning(root, count));
return false;
}
};

View File

@@ -0,0 +1,317 @@
'use strict';
var fs = require('fs');
var path = require('path');
var assert = require('assert');
var common = require('./common');
var watchman = require('fb-watchman');
var EventEmitter = require('events').EventEmitter;
var RecrawlWarning = require('./utils/recrawl-warning-dedupe');
/**
* Constants
*/
var CHANGE_EVENT = common.CHANGE_EVENT;
var DELETE_EVENT = common.DELETE_EVENT;
var ADD_EVENT = common.ADD_EVENT;
var ALL_EVENT = common.ALL_EVENT;
var SUB_NAME = 'sane-sub';
/**
* Export `WatchmanWatcher` class.
*/
module.exports = WatchmanWatcher;
/**
* Watches `dir`.
*
* @class PollWatcher
* @param String dir
* @param {Object} opts
* @public
*/
function WatchmanWatcher(dir, opts) {
opts = common.assignOptions(this, opts);
this.root = path.resolve(dir);
this.init();
}
WatchmanWatcher.prototype.__proto__ = EventEmitter.prototype;
/**
* Run the watchman `watch` command on the root and subscribe to changes.
*
* @private
*/
WatchmanWatcher.prototype.init = function() {
if (this.client) {
this.client.removeAllListeners();
}
var self = this;
this.client = new watchman.Client();
this.client.on('error', function(error) {
self.emit('error', error);
});
this.client.on('subscription', this.handleChangeEvent.bind(this));
this.client.on('end', function() {
console.warn('[sane] Warning: Lost connection to watchman, reconnecting..');
self.init();
});
this.watchProjectInfo = null;
function getWatchRoot() {
return self.watchProjectInfo ? self.watchProjectInfo.root : self.root;
}
function onCapability(error, resp) {
if (handleError(self, error)) {
// The Watchman watcher is unusable on this system, we cannot continue
return;
}
handleWarning(resp);
self.capabilities = resp.capabilities;
if (self.capabilities.relative_root) {
self.client.command(
['watch-project', getWatchRoot()], onWatchProject
);
} else {
self.client.command(['watch', getWatchRoot()], onWatch);
}
}
function onWatchProject(error, resp) {
if (handleError(self, error)) {
return;
}
handleWarning(resp);
self.watchProjectInfo = {
root: resp.watch,
relativePath: resp.relative_path ? resp.relative_path : ''
};
self.client.command(['clock', getWatchRoot()], onClock);
}
function onWatch(error, resp) {
if (handleError(self, error)) {
return;
}
handleWarning(resp);
self.client.command(['clock', getWatchRoot()], onClock);
}
function onClock(error, resp) {
if (handleError(self, error)) {
return;
}
handleWarning(resp);
var options = {
fields: ['name', 'exists', 'new'],
since: resp.clock
};
// If the server has the wildmatch capability available it supports
// the recursive **/*.foo style match and we can offload our globs
// to the watchman server. This saves both on data size to be
// communicated back to us and compute for evaluating the globs
// in our node process.
if (self.capabilities.wildmatch) {
if (self.globs.length === 0) {
if (!self.dot) {
// Make sure we honor the dot option if even we're not using globs.
options.expression = ['match', '**', 'wholename', {
includedotfiles: false
}];
}
} else {
options.expression = ['anyof'];
for (var i in self.globs) {
options.expression.push(['match', self.globs[i], 'wholename', {
includedotfiles: self.dot
}]);
}
}
}
if (self.capabilities.relative_root) {
options.relative_root = self.watchProjectInfo.relativePath;
}
self.client.command(
['subscribe', getWatchRoot(), SUB_NAME, options],
onSubscribe
);
}
function onSubscribe(error, resp) {
if (handleError(self, error)) {
return;
}
handleWarning(resp);
self.emit('ready');
}
self.client.capabilityCheck({
optional:['wildmatch', 'relative_root']
},
onCapability);
};
/**
* Handles a change event coming from the subscription.
*
* @param {Object} resp
* @private
*/
WatchmanWatcher.prototype.handleChangeEvent = function(resp) {
assert.equal(resp.subscription, SUB_NAME, 'Invalid subscription event.');
if (Array.isArray(resp.files)) {
resp.files.forEach(this.handleFileChange, this);
}
};
/**
* Handles a single change event record.
*
* @param {Object} changeDescriptor
* @private
*/
WatchmanWatcher.prototype.handleFileChange = function(changeDescriptor) {
var self = this;
var absPath;
var relativePath;
if (this.capabilities.relative_root) {
relativePath = changeDescriptor.name;
absPath = path.join(
this.watchProjectInfo.root,
this.watchProjectInfo.relativePath,
relativePath
);
} else {
absPath = path.join(this.root, changeDescriptor.name);
relativePath = changeDescriptor.name;
}
if (!(self.capabilities.wildmatch && !this.hasIgnore) &&
!common.isFileIncluded(
this.globs,
this.dot,
this.doIgnore,
relativePath)) {
return;
}
if (!changeDescriptor.exists) {
self.emitEvent(DELETE_EVENT, relativePath, self.root);
} else {
fs.lstat(absPath, function(error, stat) {
// Files can be deleted between the event and the lstat call
// the most reliable thing to do here is to ignore the event.
if (error && error.code === 'ENOENT') {
return;
}
if (handleError(self, error)) {
return;
}
var eventType = changeDescriptor.new ? ADD_EVENT : CHANGE_EVENT;
// Change event on dirs are mostly useless.
if (!(eventType === CHANGE_EVENT && stat.isDirectory())) {
self.emitEvent(eventType, relativePath, self.root, stat);
}
});
}
};
/**
* Dispatches the event.
*
* @param {string} eventType
* @param {string} filepath
* @param {string} root
* @param {fs.Stat} stat
* @private
*/
WatchmanWatcher.prototype.emitEvent = function(
eventType,
filepath,
root,
stat
) {
this.emit(eventType, filepath, root, stat);
this.emit(ALL_EVENT, eventType, filepath, root, stat);
};
/**
* Closes the watcher.
*
* @param {function} callback
* @private
*/
WatchmanWatcher.prototype.close = function(callback) {
this.client.removeAllListeners();
this.client.end();
callback && callback(null, true);
};
/**
* Handles an error and returns true if exists.
*
* @param {WatchmanWatcher} self
* @param {Error} error
* @private
*/
function handleError(self, error) {
if (error != null) {
self.emit('error', error);
return true;
} else {
return false;
}
}
/**
* Handles a warning in the watchman resp object.
*
* @param {object} resp
* @private
*/
function handleWarning(resp) {
if ('warning' in resp) {
if (RecrawlWarning.isRecrawlWarningDupe(resp.warning)) {
return true;
}
console.warn(resp.warning);
return true;
} else {
return false;
}
}