initial commit

This commit is contained in:
tmshv committed 2014-02-12 12:36:59 +04:00
commit d3fcd7f10c
3159 files changed
+349702

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
{
"strict": true,
"undef": true,
"unused": true,
"node": true,
"globals": {
"describe": true,
"it": true,
"beforeEach": true,
"afterEach": true
}
}
+12
View File
@@ -0,0 +1,12 @@
.DS_Store
*.log
node_modules
build
*.node
components
coverage
*.orig
.idea
sandbox
test/out-fixtures/*
test/watch-*.txt
+6
View File
@@ -0,0 +1,6 @@
language: node_js
node_js:
- "0.9"
- "0.10"
after_script:
- npm run coveralls
+128
View File
@@ -0,0 +1,128 @@
# gulp changelog
# 3.5.2
- add -V for version on CLI (unix standard)
- -v is deprecated, use -V
- add -T as an alias for --tasks
- documentation
# 3.5
- added `gulp.watch(globs, tasksArray)` sugar
- remove gulp.taskQueue
- deprecate gulp.run
- deprecate gulp.env
- add engineStrict to prevent people with node < 0.9 from installing
# 3.4
- added `--tasks` that prints out the tree of tasks + deps
- global cli + local install mismatch is no longer fatal
- remove tests for fs stuff
- switch core src, dest, and watch to vinyl-fs
- internal cleaning
## 3.3.4
- `--base` is now `--cwd`
## 3.3.3
- support for `--base` CLI arg to change where the search for gulpfile/`--require`s starts
- support for `--gulpfile` CLI arg to point to a gulpfile specifically
## 3.3.0
- file.contents streams are no longer paused coming out of src
- dest now passes files through before they are empty to fix passing to multiple dests
## 3.2.4
- Bug fix - we didn't have any CLI tests
## 3.2.3
- Update dependencies for bug fixes
- autocomplete stuff in the completion folder
## 3.2
- File object is now [vinyl](https://github.com/wearefractal/vinyl)
- .watch() is now [glob-watcher](https://github.com/wearefractal/glob-watcher)
- Fix CLI -v when no gulpfile found
- gulp-util updated
- Logging moved to CLI bin file
- Will cause double logging if you update global CLI to 3.2 but not local
- Will cause no logging if you update local to 3.1 but not global CLI
- Drop support for < 0.9
## 3.1.3
- Move isStream and isBuffer to gulp-util
## 3.1
- Move file class to gulp-util
## 3.0
- Ability to pass multiple globs and glob negations to glob-stream
- Breaking change to the way glob-stream works
- File object is now a class
- file.shortened changed to file.relative
- file.cwd added
- Break out getStats to avoid nesting
- Major code reorganization
## 2.7
- Breaking change to the way options are passed to glob-stream
- Introduce new File object to ease pain of computing shortened names (now a getter)
## 2.4 - 2.6
- Moved stuff to gulp-util
- Quit exposing createGlobStream (just use the glob-stream module)
- More logging
- Prettier time durations
- Tons of documentation changes
- gulp.trigger(tasks...) as a through stream
## 1.2-2.4 (11/12/13)
- src buffer=false fixed for 0.8 and 0.9 (remember to .resume() on these versions before consuming)
- CLI completely rewritten
- Colorful logging
- Uses local version of gulp to run tasks
- Uses findup to locate gulpfile (so you can run it anywhere in your project)
- chdir to gulpfile directory before loading it
- Correct exit codes on errors
- silent flag added to gulp to disable logging
- Fixes to task orchestration (3rd party)
- Better support for globbed directories (thanks @robrich)
## 1.2 (10/28/13)
- Can specify buffer=false on src streams to make file.content a stream
- Can specify read=false on src streams to disable file.content
## 1.1 (10/21/13)
- Can specify run callback
- Can specify task dependencies
- Tasks can accept callback or return promise
- `gulp.verbose` exposes run-time internals
## 1.0 (9/26/13)
- Specify dependency versions
- Updated docs
## 0.2 (8/6/13)
- Rename .files() to .src() and .folder() to .dest()
## 0.1 (7/18/13)
- Initial Release
Generated Vendored Executable
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2014 Fractal <contact@wearefractal.com>
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.
+77
View File
@@ -0,0 +1,77 @@
<p align="center">
<a href="http://gulpjs.com">
<img height="194" width="98" src="https://raw.github.com/gulpjs/artwork/master/gulp.png"/>
</a>
<br/>
<a href="http://gulpjs.com/">Visit our website!</a>
</p>
# gulp [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coveralls Status][coveralls-image]][coveralls-url] [![Dependency Status][daviddm-url]][daviddm-image]
> The streaming build system
## Like what we do?
[Support us via gittip](https://www.gittip.com/Contra/)
## Documentation
For a Getting started guide, API docs, recipes, making a plugin, etc. see the [documentation page](/docs/README.md)!
## Sample gulpfile
This file is just a quick sample to give you a taste of what gulp does.
```javascript
var gulp = require('gulp');
var coffee = require('gulp-coffee');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var imagemin = require('gulp-imagemin');
var paths = {
scripts: ['client/js/**/*.coffee', '!client/external/**/*.coffee'],
images: 'client/img/**/*'
};
gulp.task('scripts', function() {
// Minify and copy all JavaScript (except vendor scripts)
return gulp.src(paths.scripts)
.pipe(coffee())
.pipe(uglify())
.pipe(concat('all.min.js'))
.pipe(gulp.dest('build/js'));
});
// Copy all static images
gulp.task('images', function() {
return gulp.src(paths.images)
// Pass in options to the task
.pipe(imagemin({optimizationLevel: 5}))
.pipe(gulp.dest('build/img'));
});
// Rerun the task when a file changes
gulp.task('watch', function () {
gulp.watch(paths.scripts, ['scripts']);
gulp.watch(paths.images, ['images']);
});
// The default task (called when you run `gulp` from cli)
gulp.task('default', ['scripts', 'images', 'watch']);
```
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/wearefractal/gulp/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
[npm-url]: https://npmjs.org/package/gulp
[npm-image]: https://badge.fury.io/js/gulp.png
[travis-url]: https://travis-ci.org/gulpjs/gulp
[travis-image]: https://travis-ci.org/gulpjs/gulp.png?branch=master
[coveralls-url]: https://coveralls.io/r/gulpjs/gulp
[coveralls-image]: https://coveralls.io/repos/gulpjs/gulp/badge.png
[depstat-url]: https://david-dm.org/gulpjs/gulp
[depstat-image]: https://david-dm.org/gulpjs/gulp.png
[daviddm-url]: https://david-dm.org/gulpjs/gulp.png?theme=shields.io
[daviddm-image]: https://david-dm.org/gulpjs/gulp
Generated Vendored Executable
+202
View File
@@ -0,0 +1,202 @@
#!/usr/bin/env node
'use strict';
var path = require('path');
var resolve = require('resolve');
var findup = require('findup-sync');
var gutil = require('gulp-util');
var prettyTime = require('pretty-hrtime');
var minimist = require('minimist');
var semver = require('semver');
var archy = require('archy');
var completion = require('../lib/completion');
var taskTree = require('../lib/taskTree');
var cliPkg = require('../package.json');
// parse what we want off the CLI
var argv = minimist(process.argv.slice(2));
if (argv.completion) {
return completion(argv.completion);
}
var tasks = argv._;
var tasksFlag = argv.T || argv.tasks;
// TODO: drop argv.v in the next breaking release
var versionFlag = argv.v || argv.V || argv.version;
var cwdFlag = argv.cwd;
var fileFlag = argv.gulpfile;
var cwd;
if (cwdFlag) {
cwd = path.resolve(cwdFlag);
} else {
cwd = process.cwd();
}
loadRequires(argv.require, cwd);
var gulpFile;
if (fileFlag) {
gulpFile = path.join(cwd, fileFlag);
} else {
gulpFile = getGulpFile(cwd);
}
// find the local gulp
var localGulp = findLocalGulp(gulpFile);
var localPkg = findLocalGulpPackage(gulpFile);
// print some versions and shit
if (versionFlag) {
gutil.log('CLI version', cliPkg.version);
if (localGulp) {
gutil.log('Local version', localPkg.version);
}
process.exit(0);
}
if (!localGulp) {
gutil.log(gutil.colors.red('No local gulp install found in'), gutil.colors.magenta(getLocalBase(gulpFile)));
gutil.log(gutil.colors.red('Try running: npm install gulp'));
process.exit(1);
}
// check for semver difference in running gulp vs locally installed and warn if running gulp > locally installed
if (semver.gt(cliPkg.version, localPkg.version)) {
gutil.log(gutil.colors.red('gulp version mismatch:'));
gutil.log(gutil.colors.red('Running gulp is', cliPkg.version));
gutil.log(gutil.colors.red('Local gulp (installed in gulpfile dir) is', localPkg.version));
}
if (!gulpFile) {
gutil.log(gutil.colors.red('No gulpfile found'));
process.exit(1);
}
// Wire up logging for tasks
// on local gulp singleton
logEvents(localGulp);
// Load their gulpfile and run it
gutil.log('Using file', gutil.colors.magenta(gulpFile));
loadGulpFile(localGulp, gulpFile, tasks);
function loadRequires(requires, baseDir) {
if (typeof requires === 'undefined') requires = [];
if (!Array.isArray(requires)) requires = [requires];
return requires.map(function(modName){
gutil.log('Requiring external module', gutil.colors.magenta(modName));
var mod = findLocalModule(modName, baseDir);
if (typeof mod === 'undefined') {
gutil.log('Failed to load external module', gutil.colors.magenta(modName));
}
});
}
function getLocalBase(gulpFile) {
return path.resolve(path.dirname(gulpFile));
}
function findLocalGulp(gulpFile){
var baseDir = getLocalBase(gulpFile);
return findLocalModule('gulp', baseDir);
}
function findLocalModule(modName, baseDir){
try {
return require(resolve.sync(modName, {basedir: baseDir}));
} catch(e) {}
return;
}
function findLocalGulpPackage(gulpFile){
var baseDir = getLocalBase(gulpFile);
var packageBase;
try {
packageBase = path.dirname(resolve.sync('gulp', {basedir: baseDir}));
return require(path.join(packageBase, 'package.json'));
} catch(e) {}
return;
}
function loadGulpFile(localGulp, gulpFile, tasks){
var gulpFileCwd = path.dirname(gulpFile);
process.chdir(gulpFileCwd);
gutil.log('Working directory changed to', gutil.colors.magenta(gulpFileCwd));
var theGulpfile = require(gulpFile);
// just for good measure
process.nextTick(function(){
if (tasksFlag) {
return logTasks(gulpFile, localGulp);
}
startGulp(localGulp, tasks);
});
return theGulpfile;
}
function startGulp(localGulp, tasks) {
// impose our opinion of "default" tasks onto orchestrator
var toRun = tasks.length ? tasks : ['default'];
return localGulp.start.apply(localGulp, toRun);
}
function logTasks(gulpFile, localGulp) {
var tree = taskTree(localGulp.tasks);
tree.label = 'Tasks for '+gutil.colors.magenta(gulpFile);
archy(tree).split('\n').forEach(function(v){
if (v.trim().length === 0) return;
gutil.log(v);
});
}
function getGulpFile() {
var extensions;
if (require.extensions) {
extensions = Object.keys(require.extensions).join(',');
} else {
extensions = ['.js'];
}
var gulpFile = findup('gulpfile{'+extensions+'}', {cwd: cwd, nocase: true});
return gulpFile;
}
// format orchestrator errors
function formatError (e) {
if (!e.err) return e.message;
if (e.err.message) return e.err.message;
return JSON.stringify(e.err);
}
// wire up logging events
function logEvents(gulp) {
gulp.on('task_start', function(e){
gutil.log('Running', "'"+gutil.colors.cyan(e.task)+"'...");
});
gulp.on('task_stop', function(e){
var time = prettyTime(e.hrDuration);
gutil.log('Finished', "'"+gutil.colors.cyan(e.task)+"'", 'in', gutil.colors.magenta(time));
});
gulp.on('task_err', function(e){
var msg = formatError(e);
var time = prettyTime(e.hrDuration);
gutil.log('Errored', "'"+gutil.colors.cyan(e.task)+"'", 'in', gutil.colors.magenta(time), gutil.colors.red(msg));
});
gulp.on('task_not_found', function(err){
gutil.log(gutil.colors.red("Task '"+err.task+"' was not defined in your gulpfile but you tried to run it."));
gutil.log('Please check the documentation for proper gulpfile formatting.');
process.exit(1);
});
}
+12
View File
@@ -0,0 +1,12 @@
# Completion for gulp
> Thanks to grunt team and Tyler Kellen
To enable tasks auto-completion in shell you should add `eval "$(gulp --completion=shell)"` in your `.shellrc` file.
## Bash
Add `eval "$(gulp --completion=bash)"` to `~/.bashrc`.
## Zsh
Add `eval "$(gulp --completion=zsh)"` to `~/.zshrc`.
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash
# Borrowed from grunt-cli
# http://gruntjs.com/
#
# Copyright (c) 2012 Tyler Kellen, contributors
# Licensed under the MIT license.
# https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
# Usage:
#
# To enable bash <tab> completion for grunt, add the following line (minus the
# leading #, which is the bash comment character) to your ~/.bashrc file:
#
# eval "$(gulp --completion=bash)"
# Enable bash autocompletion.
function _gulp_completions() {
# The currently-being-completed word.
local cur="${COMP_WORDS[COMP_CWORD]}"
#Grab tasks
local compls=$(node -e "try { var gulp = require('gulp'); require('./gulpfile'); console.log(Object.keys(gulp.tasks).join(' ')); } catch (e) {}")
# Trim whitespace.
compls=$(echo "$compls" | sed -e 's/^ *//g' -e 's/ *$//g')
# Tell complete what stuff to show.
COMPREPLY=($(compgen -W "$compls" -- "$cur"))
}
complete -o default -F _gulp_completions gulp
+27
View File
@@ -0,0 +1,27 @@
#!/bin/zsh
# Borrowed from grunt-cli
# http://gruntjs.com/
#
# Copyright (c) 2012 Tyler Kellen, contributors
# Licensed under the MIT license.
# https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
# Usage:
#
# To enable zsh <tab> completion for grunt, add the following line (minus the
# leading #, which is the zsh comment character) to your ~/.zshrc file:
#
# eval "$(gulp --completion=zsh)"
# Enable zsh autocompletion.
function _gulp_completion() {
# Grap tasks
compls=$(node -e "try { var gulp = require('gulp'); require('./gulpfile'); console.log(Object.keys(gulp.tasks).join(' ')); } catch (e) {}")
# Trim whitespace.
compls=$(echo "$compls" | sed -e 's/^ *//g' -e 's/ *$//g')
completions=(${=compls})
compadd -- $completions
}
compdef _gulp_completion gulp
+232
View File
@@ -0,0 +1,232 @@
## gulp API docs
### gulp.src(globs[, options])
Takes a glob and represents a file structure. Can be piped to plugins.
```javascript
gulp.src('./client/templates/*.jade')
.pipe(jade())
.pipe(minify())
.pipe(gulp.dest('./build/minified_templates'));
```
#### globs
Type: `String` or `Array`
Glob or globs to read.
#### options
Type: `Object`
Options to pass to [node-glob] through [glob-stream].
gulp adds two additional options in addition to the [options supported by node-glob][node-glob documentation]:
#### options.buffer
Type: `Boolean`
Default: `true`
Setting this to `false` will return `file.contents` as a stream and not buffer files. This is useful when working with large files. **Note:** Plugins may not implement support for streams.
#### options.read
Type: `Boolean`
Default: `true`
Setting this to `false` will return `file.contents` as null and not read the file at all.
### gulp.dest(path)
Can be piped to and it will write files. Re-emits all data passed to it so you can pipe to multiple folders.
```javascript
gulp.src('./client/templates/*.jade')
.pipe(jade())
.pipe(gulp.dest('./build/templates'))
.pipe(minify())
.pipe(gulp.dest('./build/minified_templates'));
```
#### path
Type: `String`
The path (folder) to write files to.
### gulp.task(name[, deps], fn)
Define a task using [Orchestrator].
```javascript
gulp.task('somename', function() {
// Do stuff
});
```
#### name
The name of the task. Tasks that you want to run from the command line should not have spaces in them.
#### deps
Type: `Array`
An array of tasks to be executed and completed before your task will run.
```javascript
gulp.task('mytask', ['array', 'of', 'task', 'names'], function() {
// Do stuff
});
```
**Note:** Are your tasks running before the dependencies are complete? Make sure your dependency tasks
are correctly using the async run hints: take in a callback or return a promise or event stream.
#### fn
The function that performs the task's operations. Generally this takes the form of `gulp.src().pipe(someplugin())`.
#### Async task support
Tasks can be made asynchronous if its `fn` does one of the following:
##### Accept a callback
```javascript
gulp.task('somename', function(cb) {
// Do stuff
cb(err);
});
```
##### Return a stream
```javascript
gulp.task('somename', function() {
var stream = gulp.src('./client/**/*.js')
.pipe(minify())
.pipe(gulp.dest('/build'));
return stream;
});
```
##### Return a promise
```javascript
var Q = require('q');
gulp.task('somename', function() {
var deferred = Q.defer();
// Do async stuff
setTimeout(function() {
deferred.resolve();
}, 1);
return deferred.promise;
});
```
**Note:** By default, tasks run with maximum concurrency -- e.g. it launches all the tasks at once and waits for nothing.
If you want to create a series where tasks run in a particular order, you need to do two things:
- give it a hint to tell it when the task is done,
- and give it a hint that a task depends on completion of another.
For these examples, let's presume you have two tasks, "one" and "two" that you specifically want to run in this order:
1. In task "one" you add a hint to tell it when the task is done. Either take in a callback and call it when you're
done or return a promise or stream that the engine should wait to resolve or end respectively.
2. In task "two" you add a hint telling the engine that it depends on completion of the first task.
So this example would look like this:
```javascript
var gulp = require('gulp');
// takes in a callback so the engine knows when it'll be done
gulp.task('one', function (cb) {
// do stuff -- async or otherwise
cb(err); // if err is not null and not undefined, the run will stop, and note that it failed
});
// identifies a dependent task must be complete before this one begins
gulp.task('two', ['one'], function () {
// task 'one' is done now
});
gulp.task('default', ['one', 'two']);
```
### gulp.watch(glob [, opts], tasks) or gulp.watch(glob [, opts, cb])
Watch files and do something when a file changes. This always returns an EventEmitter that emits `changed` events.
### gulp.watch(glob[, opts], tasks)
#### glob
Type: `String` or `Array`
A single glob or array of globs that indicate which files to watch for changes.
#### opts
Type: `Object`
Options, that are passed to [`gaze`](https://github.com/shama/gaze).
#### tasks
Type: `Array`
Names of task(s) to run when a file changes, added with `gulp.task()`
```javascript
var watcher = gulp.watch('js/**/*.js', ['uglify','reload']);
watcher.on('changed', function(event){
console.log('File '+event.path+' was '+event.type+', running tasks...');
});
```
### gulp.watch(glob[, opts, cb])
#### glob
Type: `String` or `Array`
A single glob or array of globs that indicate which files to watch for changes.
#### opts
Type: `Object`
Options, that are passed to [`gaze`](https://github.com/shama/gaze).
#### cb(event)
Type: `Function`
Callback to be called on each change.
```javascript
gulp.watch('js/**/*.js', function(event) {
console.log('File '+event.path+' was '+event.type+', running tasks...');
});
```
The callback will be passed an object, `event`, that describes the change:
##### event.type
Type: `String`
The type of change that occurred, either `added`, `changed` or `deleted`.
##### event.path
Type: `String`
The path to the file that triggered the event.
[node-optimist]: https://github.com/substack/node-optimist
[node-glob documentation]: https://github.com/isaacs/node-glob#options
[node-glob]: https://github.com/isaacs/node-glob
[glob-stream]: https://github.com/wearefractal/glob-stream
[gulp-if]: https://github.com/robrich/gulp-if
[Orchestrator]: https://github.com/robrich/orchestrator
+25
View File
@@ -0,0 +1,25 @@
## gulp CLI docs
### Flags
gulp has very few flags to know about. All other flags are for tasks to use if needed.
- `-V` or `--version` will display the global and local gulp versions
- `--require <module path>` will require a module before running the gulpfile. This is useful for transpilers but also has other applications. You can use multiple `--require` flags
- `--gulpfile <gulpfile path>` manually set path of gulpfile. Useful if you have multiple gulpfiles. This will set the CWD to the gulpfile directory as well.
- `--cwd <dir path>` manually set the CWD. The search for the gulpfile, as well as the relativity of all requires will be from here.
- `-T` or `--tasks` will display the task dependency tree for the loaded gulpfile
### Tasks
Tasks can be executed by running `gulp <task> <othertask>`. Just running `gulp` will execute the task you registered called `default`. If there is no `default` task gulp will error.
### Compilers
You can use any language you want for your gulpfile. You will have to specify the language module name so the CLI can load it (and its associated extensions) before attempting to find your gulpfile. Make sure you have this module installed accessible by the folder you are running the CLI in.
Example:
```
gulp dosomething --require coffee-script/register
```
+50
View File
@@ -0,0 +1,50 @@
# FAQ
## Why gulp? Why not ____?
See the [gulp introduction slideshow] for a rundown on how gulp came to be.
## Is it "gulp" or "Gulp"?
gulp is always lowercase.
## Where can I find a list of gulp plugins?
gulp plugins always include the `gulpplugin` keyword. [Search gulp plugins][search-gulp-plugins] or [view all plugins][npm plugin search].
## I want to write a gulp plugin, how do I get started?
See the [Writing a gulp plugin] wiki page for guidelines and an example to get you started.
## My plugin does ____, is it doing too much?
Probably. Ask yourself:
1. Is my plugin doing something that other plugins may need to do?
- If so, that piece of functionality should be a separate plugin. [Check if it already exists on npm][npm plugin search].
1. Is my plugin doing two, completely different things based on a configuration option?
- If so, it may serve the community better to release it as two separate plugins
- If the two tasks are different, but very closely related, it's probably OK
## How should newlines be represented in plugin output?
Always use gulp-util.newline (which is \n) to prevent diff issues between operating systems.
## Where can I get updates on gulp?
gulp updates can be found on the following twitters:
- [@wearefractal](https://twitter.com/wearefractal)
- [@eschoff](https://twitter.com/eschoff)
- [@funkytek](https://twitter.com/funkytek)
- [@gulpjs](https://twitter.com/gulpjs)
## Does gulp have an IRC channel?
Yes, come chat with us in #gulpjs on [Freenode].
[Writing a gulp plugin]: writing-a-plugin/README.md
[gulp introduction slideshow]: http://slid.es/contra/gulp
[Freenode]: http://freenode.net/
[search-gulp-plugins]: http://gratimax.github.io/search-gulp-plugins/
[npm plugin search]: https://npmjs.org/browse/keyword/gulpplugin
+30
View File
@@ -0,0 +1,30 @@
# gulp documentation
* [Getting Started](getting-started.md) - How to get going with gulp
* [API documentation](API.md) - Learn the ins and outs of using gulp
* [CLI documentation](CLI.md) - Learn how to call tasks and use compilers
* [Writing a Plugin](writing-a-plugin/README.md) - So you're writing a gulp plugin? Go here for the essential dos and don'ts.
## FAQ
See [the FAQ](FAQ.md) for the answers to commonly asked questions.
## Recipes
The community has compiled guides on how to use gulp for common use cases. Check out the [recipes folder](recipes) for a full list.
## Articles
* [Introduction to node.js streams](https://github.com/substack/stream-handbook)
* [Video introduction to node.js streams](http://www.youtube.com/watch?v=QgEuZ52OZtU)
* [Getting started with gulp (by @markgdyr)](http://markgoodyear.com/2014/01/getting-started-with-gulp/)
* [Why you shouldn’t create a gulp plugin (or, how to stop worrying and learn to love existing node packages)](http://blog.overzealous.com/post/74121048393/why-you-shouldnt-create-a-gulp-plugin-or-how-to-stop)
* [Inspiration (slides) about why gulp was made](http://slid.es/contra/gulp)
## License
All the documentation is covered by the CC0 license *(do whatever you want with it - public domain)*.
[![CC0](http://i.creativecommons.org/p/zero/1.0/88x31.png)](http://creativecommons.org/publicdomain/zero/1.0/)
To the extent possible under law, [Fractal](http://wearefractal.com) has waived all copyright and related or neighboring rights to this work.
+46
View File
@@ -0,0 +1,46 @@
## Getting Started
### 1. Install gulp globally:
```
npm install -g gulp
```
### 2. Install gulp and gulp-util in your project devDependencies:
```
npm install --save-dev gulp gulp-util
```
### 3. Create a `gulpfile.js` at the root of your project:
```javascript
var gulp = require('gulp');
var gutil = require('gulp-util');
gulp.task('default', function(){
// place code for your default task here
});
```
### 4. Run gulp
```
gulp
```
The default task will run and do nothing.
To run individual tasks, use `gulp <task> <othertask>`
## Where do I go now?
You have an empty gulpfile and everything is installed. How do you REALLY get started? Check out the [recipes and articles section](README.md#articles-and-recipes) for more information
## .src, .watch, .dest, CLI args - How do I use these things?
For API specific documentation you can check out the [documentation for that](API.md)
## Available Plugins
The gulp community is growing, with new plugins being added daily. See the [main website](http://gulpjs.com/) for a complete list.
+38
View File
@@ -0,0 +1,38 @@
# Combining streams to handle errors #
By default, emitting an error on a stream will cause it to be thrown
unless it already has a listener attached to the `error` event. This
gets a bit tricky when you're working with longer pipelines of streams.
By using [gulp-util](https://github.com/gulpjs/gulp-util)'s `combine`
method you can turn a series of streams into a single stream, meaning you
only need to listen to the `error` event in one place in your code.
Here's an example of using it in a gulpfile:
``` javascript
var combine = require('gulp-util').combine
var uglify = require('gulp-uglify')
var gulp = require('gulp')
gulp.task('test', function() {
var combined = combine(
gulp.src('bootstrap/js/*.js'),
uglify(),
gulp.dest('public/bootstrap')
)
// any errors in the above streams
// will get caught by this listener,
// instead of being thrown:
combined.on('error', function(err) {
console.warn(err.message)
})
return combined
})
```
You can use this technique in your gulp plugins and node modules too using
the [multipipe](http://npmjs.org/package/multipipe) module, and is a generally useful means of ensuring that errors aren't thrown from inaccessible streams
somewhere deep inside `./node_modules`.
+69
View File
@@ -0,0 +1,69 @@
# Mocha test-runner with gulp
### Passing shared module in all tests
```js
var gulp = require('gulp');
var mocha = require('gulp-mocha');
gulp.task('tests', function() {
return gulp.src(['test/test-*.js'], { read: false })
.pipe(mocha({
reporter: 'spec',
globals: {
should: require('should')
}
}));
});
```
### Running mocha tests when files change
With bundled `gulp.watch` and [`gulp-batch`](https://github.com/floatdrop/gulp-batch) (see readme of gulp-batch for reasons):
```js
// npm install gulp gulp-watch gulp-mocha gulp-batch
var gulp = require('gulp');
var mocha = require('gulp-mocha');
var batch = require('gulp-batch');
var gutil = require('gulp-util');
gulp.task('mocha', function () {
return gulp.src(['test/*.js'], { read: false })
.pipe(mocha({ reporter: 'list' }))
.on('error', gutil.log);
});
gulp.watch(['lib/**', 'test/**'], batch(function(events, cb) {
gulp.run('mocha', cb);
}));
```
With [`gulp-watch`](https://github.com/floatdrop/gulp-watch) plugin:
```js
// npm i gulp gulp-watch gulp-mocha
var gulp = require('gulp');
var mocha = require('gulp-mocha');
var watch = require('gulp-watch');
var gutil = require('gulp-util')
gulp.task('mocha', function () {
return gulp.src(['test/*.js'], { read: false })
.pipe(mocha({ reporter: 'list' }))
.on('error', gutil.log);
});
gulp.task('watch', function() {
return gulp.src(['lib/**', 'test/**'], { read: false })
.pipe(watch(function(events, cb) {
gulp.run('mocha', cb);
}));
});
gulp.task('default', ['mocha', 'watch']);
// run `gulp watch` or just `gulp` for watching and rerunning tests
```
+27
View File
@@ -0,0 +1,27 @@
# Only pass through changed files
Files are passed through the whole pipe chain on every run by default. By using [gulp-changed](https://github.com/sindresorhus/gulp-changed) only changed files will be passed through. This can speed up consecutive runs considerably.
```js
// run `npm install gulp gulp-changed gulp-jscs gulp-uglify`
var gulp = require('gulp');
var changed = require('gulp-changed');
var jscs = require('gulp-jscs');
var uglify = require('gulp-uglify');
// we define some constants here so they can be reused
var SRC = 'src/*.js';
var DEST = 'dist';
gulp.task('default', function () {
return gulp.src(SRC)
// the `changed` task needs to know the destination directory
// upfront to be able to figure out which files changed
.pipe(changed(DEST))
// only files that has changed will pass through here
.pipe(jscs())
.pipe(uglify())
.pipe(gulp.dest(DEST));
});
```
+28
View File
@@ -0,0 +1,28 @@
# Pass parameters from the command line
## bonus: keeping those tasks DRY
---
`gulpfile.js`
```js
// npm install gulp gulp-util gulp-if gulp-uglify
var gulp = require('gulp');
var gutil = require('gulp-util');
var gulpif = require('gulp-if');
var uglify = require('gulp-uglify');
var isProduction = gutil.env.type === 'production';
gulp.task('scripts', function () {
return gulp.src('**/*.js')
.pipe(gulpif(isProduction, uglify())) // only minify if production
.pipe(gulp.dest('dist'));
});
```
---
`cli`
`gulp scripts --type production`
+16
View File
@@ -0,0 +1,16 @@
# Rebuild only files that change
With [`gulp-watch`](https://github.com/floatdrop/gulp-watch):
```js
var gulp = require('gulp');
var sass = require('gulp-sass');
var watch = require('gulp-watch');
gulp.task('default', function() {
return gulp.src('./sass/*.scss')
.pipe(watch())
.pipe(sass())
.pipe(gulp.dest('./dist/'));
});
```
+34
View File
@@ -0,0 +1,34 @@
# Running tasks in series
By default, tasks run with maximum concurrency -- e.g. it launches all the tasks at once and waits for nothing.
If you want to create a series where tasks run in a particular order, you need to do two things:
- give it a hint to tell it when the task is done,
- and give it a hint that a task depends on completion of another.
For these examples, let's presume you have two tasks, "one" and "two" that you specifically want to run in this order:
1. In task "one" you add a hint to tell it when the task is done. Either take in a callback and call it when you're
done or return a promise or stream that the engine should wait to resolve or end respectively.
2. In task "two" you add a hint telling the engine that it depends on completion of the first task.
So this example would look like this:
```javascript
var gulp = require('gulp');
// takes in a callback so the engine knows when it'll be done
gulp.task('one', function (cb) {
// do stuff -- async or otherwise
cb(err); // if err is not null and not undefined, the orchestration will stop, and 'two' will not run
});
// identifies a dependent task must be complete before this one begins
gulp.task('two', ['one'], function () {
// task 'one' is done now
});
gulp.task('default', ['one', 'two']);
// alternatively: gulp.task('default', ['two']);
```
+37
View File
@@ -0,0 +1,37 @@
# Specifying a new cwd (current working directory)
This is helpful for projects using a nested directory structure, such as:
```
/project
/layer1
/layer2
```
You can use the gulp CLI option `--cwd`
From the `project/` directory
```bash
gulp --cwd ./layer1/
```
Another option is to use `process.chdir` which is just vanilla node.
`gulpfile.js`
```js
var gulp = require('gulp');
try {
process.chdir(gulp.env.cwd);
} catch (err) {
console.error('Unable to chdir to %s', gulp.env.cwd);
}
```
If you only need to specify a cwd for a certain glob, you can use the `cwd` option on a [glob-stream](https://github.com/wearefractal/glob-stream)
```js
gulp.src('./some/dir/**/*.js', { cwd: './public' });
```
+23
View File
@@ -0,0 +1,23 @@
# Using coffee-script for gulpfile
As discussed in [issue #103](https://github.com/gulpjs/gulp/issues/103), there are 2 ways to do this.
1. Use `gulp --require coffee-script/register` at the command line
2. Require in `gulpfile.coffee` after requiring `coffee-script` in `gulpfile.js`
`gulpfile.js`
```js
require('coffee-script/register');
require('./gulpfile.coffee');
```
`gulpfile.coffee`
```coffeescript
gulp = require 'gulp'
gulp.task 'default', ->
console.log('default task called')
```
+49
View File
@@ -0,0 +1,49 @@
# Using external config file
## bonus: keeping those tasks DRY
## bonus2: config.json can be used by another task runner, like `Grunt`
---
`config.json`
```json
{
"desktop" : {
"src" : [
"dev/desktop/js/**/*.js",
"!dev/desktop/js/vendor/**"
],
"dest" : "build/desktop/js"
},
"mobile" : {
"src" : [
"dev/mobile/js/**/*.js",
"!dev/mobile/js/vendor/**"
],
"dest" : "build/mobile/js"
}
}
```
---
`gulpfile.js`
```js
// npm install gulp gulp-uglify
var gulp = require('gulp');
var uglify = require('gulp-uglify');
var config = require('./config.json');
function doStuff(cfg) {
return gulp.src(cfg.src)
.pipe(uglify())
.pipe(gulp.dest(cfg.dest));
}
gulp.task('dry', function () {
doStuff(config.desktop);
doStuff(config.mobile);
});
```
+17
View File
@@ -0,0 +1,17 @@
# Using multiple sources in one task
```js
// npm install gulp event-stream
var gulp = require('gulp');
var es = require('event-stream');
gulp.task('test', function(cb) {
return es.concat(
gulp.src('bootstrap/js/*.js')
.pipe(gulp.dest('public/bootstrap')),
gulp.src('jquery.cookie/jquery.cookie.js')
.pipe(gulp.dest('public/jquery'))
);
});
```
+53
View File
@@ -0,0 +1,53 @@
# Writing a plugin
If you plan to create your own Gulp plugin, you will save time by reading the full documentation.
* [Guidelines](guidelines.md) (a MUST read)
* [Using buffers](using-buffers.md)
* [Dealing with streams](dealing-with-streams.md)
* [Testing](testing.md)
* [README conventions](readme-conventions.md)
## What it does
### Streaming file objects
A gulp plugin always returns a stream in [object mode](http://nodejs.org/api/stream.html#stream_object_mode) that does the following:
1. Takes in [vinyl File objects](http://github.com/wearefractal/vinyl)
2. Outputs [vinyl File objects](http://github.com/wearefractal/vinyl)
These are known as [transform streams](http://nodejs.org/api/stream.html#stream_class_stream_transform_1) (also sometimes called through streams). Transform streams are streams that are readable and writable which manipulate objects as they're being passed through.
### Modifying file content
Vinyl files can have 3 possible forms for the contents attribute:
- [Streams](dealing-with-streams.md)
- [Buffers](using-buffers.md)
- Empty (null) - Useful for things like rimraf, clean, where contents is not neeeded.
## Useful resources
* [File object](https://github.com/wearefractal/gulp-util/#new-fileobj)
* [PluginError](https://github.com/gulpjs/gulp-util#new-pluginerrorpluginname-message-options)
* [event-stream](https://github.com/dominictarr/event-stream)
* [BufferStream](https://github.com/nfroidure/BufferStream)
* [gulp-util](https://github.com/wearefractal/gulp-util)
## Sample plugins
* [sindresorhus' gulp plugins](https://github.com/search?q=%40sindresorhus+gulp-)
* [Fractal's gulp plugins](https://github.com/search?q=%40wearefractal+gulp-)
* [gulp-replace](https://github.com/lazd/gulp-replace)
## About streams
If you're unfamiliar with streams, you will need to read up on them:
* https://github.com/substack/stream-handbook (a MUST read)
* http://nodejs.org/api/stream.html
Other libraries that are not file manipulating through streams but are made for use with gulp are tagged with the [gulpfriendly](https://npmjs.org/browse/keyword/gulpfriendly) keyword on npm.
+83
View File
@@ -0,0 +1,83 @@
# Dealing with streams
> It is highly recommended to write plugins supporting streams. Here is some information on creating a gulp plugin that supports streams.
> Make sure to follow the best practice regarging error handling and add the line that make the gulp plugin re-emit the first error catched during the transformation of the content
[Writing a Plugin](README.md) > Writing stream based plugins
## Dealing with streams
Let's implement a plugin prepending some text to files. This plugin supports all possible forms of file.contents.
```js
var through = require('through2');
var gutil = require('gulp-util');
var PluginError = gutil.PluginError;
// Consts
const PLUGIN_NAME = 'gulp-prefixer';
function prefixStream(prefixText) {
var stream = through();
stream.write(prefixText);
return stream;
}
// Plugin level function (dealing with files)
function gulpPrefixer(prefixText) {
if (!prefixText) {
throw PluginError(PLUGIN_NAME, "Missing prefix text!");
}
prefixText = new Buffer(prefixText); // allocate ahead of time
// Creating a stream through which each file will pass
var stream = through.obj(function (file, enc, callback) {
if (file.isNull()) {
this.push(file); // Do nothing if no contents
return callback();
}
if (file.isBuffer()) {
this.emit('error', new PluginError(PLUGIN_NAME, 'Buffers not supported!'));
return callback();
}
if (file.isStream()) {
// define the streamer that will transform the content
var streamer = prefixStream(prefixText);
// catch errors from the streamer and emit a gulp plugin error
streamer.on('error', this.emit.bind(this, 'error'));
// start the transformation
file.contents = file.contents.pipe(streamer);
// make sure the file goes through the next gulp plugin
this.push(file);
// tell the stream engine that we are done with this file
return callback();
}
});
// returning the file stream
return stream;
};
// Exporting the plugin main function
module.exports = gulpPrefixer;
```
The above plugin can be used like this:
```js
var gulp = require('gulp');
var gulpPrefixer = require('gulp-prefixer');
gulp.src('files/**/*.js')
.pipe(gulpPrefixer('prepended string'))
.pipe(gulp.dest('/modified-files/'));
```
## Some plugins using streams
* [gulp-svgicons2svgfont](https://github.com/nfroidure/gulp-svgiconstosvgfont)
* gulp-browserify (Soon)
+97
View File
@@ -0,0 +1,97 @@
# Guidelines
> While these guidelines are totally optional, we **HIGHLY** recommend that everyone follows them. Nobody wants to use a bad plugin. These guidelines will actually help make your life easier by giving you assurance that your plugin fits well within gulp.
[Writing a Plugin](README.md) > Guidelines
1. Your plugin should not do something that can be done easily with an existing node module
- Wrapping every possible thing just for the sake of wrapping it will pollute the ecosystem with low quality plugins that don't make sense
1. Your plugin should only do **one thing**, and do it well.
- Avoid config options that make your plugin do completely different tasks
1. Your plugin shouldn't do things that other plugins are responsible for
- It should not concat, [gulp-concat](https://github.com/wearefractal/gulp-concat) does that
- It should not add headers, [gulp-header](https://github.com/godaddy/gulp-header) does that
- It should not add footers, [gulp-footer](https://github.com/godaddy/gulp-footer) does that
- If it's a common but optional use case, document that your plugin is often used with another plugin
- If it's an internal requirement, make use of existing plugins by piping your plugin's output to them
1. Your plugin **must be tested**
- Testing a gulp plugin is easy, you don't even need gulp to test it
- Look at other plugins for examples
1. Add `gulpplugin` as a keyword in your `package.json` so you show up on our search
1. Do not throw errors inside a stream
- Instead, you should emit it as an **error** event.
- If you encounter an error **outside** the stream, such as invalid configuration, you may throw it.
1. Prefix any errors with the name of your plugin
- For example: `gulp-replace: Cannot do regexp replace on a stream`
- Use gulp-util's [PluginError](https://github.com/gulpjs/gulp-util#new-pluginerrorpluginname-message-options) class to make this easy
1. The type of `file.contents` should always be the same going out as it was when it came in
- If file.contents is null (non-read) just ignore the file and pass it along
- If file.contents is a Stream and you don't support that just emit an error
- If you choose to support file.contents as a Stream, use [BufferStream](https://github.com/nfroidure/BufferStream) to make this easy
1. Do not pass the `file` object downstream until you are done with it
1. Make use of the [gulp-util](https://github.com/gulpjs/gulp-util) library
- It provides templating, CLI colors, logging, changing file extensions
- Try looking for common things there first and add it if it doesn't exist
1. Do NOT require `gulp` as a dependency or peerDependency
- There is no reason you should have to do this and it will cause problems if you do
## Why are these guidelines so strict?
gulp aims to be simple for users. By providing strict guidelines we are able to provide a consistent and high-quality ecosystem for everyone. While this does add a little more work and thought for plugin authors, it removes a lot of problems later down the road.
### What happens if I don't follow them?
npm is open for everyone, and you are free to make whatever you want but these guidelines were prescribed for a reason. There are acceptances tests coming soon that will be integrated into the plugin search. If you fail to adhere to the plugin guidelines it will be publicly visible/sortable via a scoring system. People will always prefer to use plugins that match "the gulp way".
### What does a good plugin look like?
```js
// through2 is a thin wrapper around node transform streams
var through = require('through2');
var gutil = require('gulp-util');
var PluginError = gutil.PluginError;
// Consts
const PLUGIN_NAME = 'gulp-prefixer';
function prefixStream(prefixText) {
var stream = through();
stream.write(prefixText);
return stream;
}
// Plugin level function (dealing with files)
function gulpPrefixer(prefixText) {
if (!prefixText) {
throw PluginError(PLUGIN_NAME, "Missing prefix text!");
}
prefixText = new Buffer(prefixText); // allocate ahead of time
// Creating a stream through which each file will pass
var stream = through.obj(function (file, enc, callback) {
if (file.isNull()) {
this.push(file); // Do nothing if no contents
return callback();
}
if (file.isBuffer()) {
file.contents = Buffer.concat([prefixText, file.contents]);
this.push(file);
return callback();
}
if (file.isStream()) {
file.contents = file.contents.pipe(prefixStream(prefixText));
this.push(file);
return callback();
}
});
// returning the file stream
return stream;
};
// Exporting the plugin main function
module.exports = gulpPrefixer;
```
+218
View File
@@ -0,0 +1,218 @@
# README conventions
> Follow README conventions to increase your plugin readability and usability.
[Writing a Gulp Plugin](README.md) > README conventions
A plugin README should have the following parts at a minimum:
1. **Heading**: Give a short introduction to your plugin
* Include the version of gulp your plugin was designed for
* Include any badges on the same line as the heading
1. **Usage**: Include a simple usage example
* Show the plugin used on the context of a `gulpfile.js`
* Cover the most common or simplest use case
1. **API**: Document your plugin's API
* For individual parameters, include:
1. Parameter name
1. Type
1. Default value
1. Description
1. Optional example for complex options
* If a parameter accepts an Object, do the above for each property
* If your plugin accepts overloaded arguments, document each style individually
# Example: Options object
The following is an example of a plugin that takes a single options object (from [gulp-handlebars](https://github.com/lazd/gulp-handlebars)).
***
# gulp-handlebars [![NPM version][handlebars-npm-image]][handlebars-npm-url] [![Build status][handlebars-travis-image]][handlebars-travis-url]
> Handlebars plugin for gulp 3
## Usage
First, install `gulp-handlebars` as a development dependency:
```shell
npm install --save-dev gulp-handlebars
```
Then, add it to your `gulpfile.js`:
```js
var handlebars = require('gulp-handlebars');
gulp.task('templates', function(){
gulp.src(['client/templates/*.hbs'])
.pipe(handlebars({
namespace: 'MyApp.templates',
outputType: 'hybrid'
}))
.pipe(concat('templates.js'))
.pipe(gulp.dest('build/js/'));
});
```
## API
### handlebars(options)
#### options.namespace
Type: `String`
Default: `templates`
The namespace in which the precompiled templates will be assigned. Use dot notation (e.g. `App.Templates`) for nested namespaces or false to declare templates in the global namespace.
#### options.outputType
Type: `String`
Default: `browser`
The desired output type. One of the following:
* `browser` - Produce plain JavaScript files for the browser
* `hybrid` - Produce Node modules that can optionally be used on the frontend
* `node` - Produce Node modules
* `amd` - Produce AMD modules
* `commonjs` - Produce CommonJS modules
* `bare` - Return an unmolested function definition
#### options.declareNamespace
Type: `Boolean`
Default: `true`
If true, non-destructively declare all parts of the namespace. This option is only necessary when `options.type` is `browser` or `hybrid`.
For example, if the namespace is `MyApp.Templates` and a template is named `App.Header.hbs`, the following declaration will be present in the output file for that template:
```js
this["MyApp"] = this["MyApp"] || {};
this["MyApp"]["templates"] = this["MyApp"]["templates"] || {};
this["MyApp"]["templates"]["App"] = this["MyApp"]["templates"]["App"] || {};
this["MyApp"]["templates"]["App"]["Header"] = function () {};
```
When processing multiple templates under a given namespace, this will result in duplicate declarations. That is, the non-destructive declaration of the namespace will be repeated for each template compiled.
#### options.processName
Type: `Function`
Default: Strip file extension
This option accepts a function which takes one argument (the template filepath) and returns a string which will be used as the key for the precompiled template object. By default, the filename minus the extension is used.
If this function returns a string containing periods (not including the file extension), they will be represented as a sub-namespace. See `options.declareNamespace` for an example of the effect.
#### options.compilerOptions
Type: `Object`
Compiler options to pass to `Handlebars.precompile()`.
[handlebars-travis-url]: http://travis-ci.org/lazd/gulp-handlebars
[handlebars-travis-image]: https://secure.travis-ci.org/lazd/gulp-handlebars.png?branch=master
[handlebars-npm-url]: https://npmjs.org/package/gulp-handlebars
[handlebars-npm-image]: https://badge.fury.io/js/gulp-handlebars.png
***
# Example: Overloaded API
The following is an example of a plugin that has an overloaded API (from [gulp-csslint](https://github.com/lazd/gulp-csslint)).
***
# gulp-csslint [![NPM version][csslint-npm-image]][csslint-npm-url] [![Build status][csslint-travis-image]][csslint-travis-url]
> CSSLint plugin for gulp 3
## Usage
First, install `gulp-csslint` as a development dependency:
```shell
npm install --save-dev gulp-csslint
```
Then, add it to your `gulpfile.js`:
```javascript
var csslint = require('gulp-csslint');
gulp.task('css', function() {
gulp.src('./client/css/*.css')
.pipe(csslint())
.pipe(csslint.reporter());
});
```
## API
### csslint(ruleConfiguration)
#### ruleConfiguration
Type: `Object`
You can pass rule configuration as an object. See the [list of rules by ID on the CSSLint wiki](https://github.com/stubbornella/csslint/wiki/Rules-by-ID) for valid rule IDs.
```javascript
gulp.src('./client/css/*.css')
.pipe(csslint({
'shorthand': false
}))
.pipe(csslint.reporter());
```
### csslint(csslintrc)
#### csslintrc
Type: `String`
You can also pass the path to your csslintrc file instead of a rule configuration object.
```javascript
gulp.src('./client/css/*.css')
.pipe(csslint('csslintrc.json'))
.pipe(csslint.reporter());
```
## Results
Adds the following properties to the file object:
```javascript
file.csslint.success = true; // or false
file.csslint.errorCount = 0; // number of errors returned by CSSLint
file.csslint.results = []; // CSSLint errors
file.csslint.opt = {}; // The options you passed to CSSLint
```
## Custom Reporters
Custom reporter functions can be passed as `cssline.reporter(reporterFunc)`. The reporter function will be called for each linted file and passed the file object as described above.
```javascript
var csslint = require('gulp-csslint');
var gutil = require('gulp-util');
var customReporter = function(file) {
gutil.log(gutil.colors.cyan(file.csslint.errorCount)+' errors in '+gutil.colors.magenta(file.path));
file.csslint.results.forEach(function(result) {
gutil.log(result.error.message+' on line '+result.error.line);
});
};
gulp.task('lint', function() {
gulp.files('./lib/*.js')
.pipe(csslint())
.pipe(csslint.reporter(customReporter));
});
```
[csslint-travis-url]: http://travis-ci.org/lazd/gulp-csslint
[csslint-travis-image]: https://secure.travis-ci.org/lazd/gulp-csslint.png?branch=master
[csslint-npm-url]: https://npmjs.org/package/gulp-csslint
[csslint-npm-image]: https://badge.fury.io/js/gulp-csslint.png
***
+95
View File
@@ -0,0 +1,95 @@
# Testing
> Testing your plugin is the only way to ensure quality. It brings confidence to your users and makes your life easier.
[Writing a Plugin](README.md) > Testing
## Tooling
Most plugins use [mocha](https://github.com/visionmedia/mocha) and [event-stream](https://github.com/dominictarr/event-stream) to help them test. The following examples will use these tools.
## Testing plugins for streaming mode
```js
var assert = require('assert');
var es = require('event-stream');
var gutil = require('gulp-util');
var prefixer = require('../index');
describe('gulp-prefixer', function () {
describe('in streaming mode', function () {
it('should prepend text', function (done) {
// create the fake file
var fakeFile = new gutil.File({
contents: es.readArray(['stream', 'with', 'those', 'contents'])
});
// Create a prefixer plugin stream
var myPrefixer = prefixer('prependthis');
// write the fake file to it
myPrefixer.write(fakeFile);
// wait for the file to come back out
myPrefixer.once('data', function (file) {
// make sure it came out the same way it went in
assert(file.isStream());
// buffer the contents to make sure it got prepended to
file.contents.pipe(es.wait(function (err, data) {
// check the contents
assert.equal(data, 'prependthistostreamwiththosecontents');
done();
}));
});
});
});
});
```
## Testing plugins for buffer mode
```js
var assert = require('assert');
var es = require('event-stream');
var gutil = require('gulp-util');
var prefixer = require('../index');
describe('gulp-prefixer', function () {
describe('in buffer mode', function () {
it('should prepend text', function (done) {
// create the fake file
var fakeFile = new gutil.File({
contents: new Buffer('abufferwiththiscontent')
});
// Create a prefixer plugin stream
var myPrefixer = prefixer('prependthis');
// write the fake file to it
myPrefixer.write(fakeFile);
// wait for the file to come back out
myPrefixer.once('data', function (file) {
// make sure it came out the same way it went in
assert(file.isBuffer());
// check the contents
assert.equal(file.contents.toString('utf8'), 'prependthisabufferwiththiscontent');
done();
});
});
});
});
```
## Sample tests
* [gulp-cat](https://github.com/ben-eb/gulp-cat/blob/master/test.js)
+72
View File
@@ -0,0 +1,72 @@
# Using buffers
> Here is some information on creating gulp plugin that manipulates buffers.
[Writing a Plugin](README.md) > Using buffers
## Using buffers
If your plugin is relying on a buffer based library, you will probably choose to base your plugin around file.contents as a buffer. Let's implement a plugin prepending some text to files:
```js
var through = require('through2');
var gutil = require('gulp-util');
var PluginError = gutil.PluginError;
// Consts
const PLUGIN_NAME = 'gulp-prefixer';
// Plugin level function (dealing with files)
function gulpPrefixer(prefixText) {
if (!prefixText) {
throw new PluginError(PLUGIN_NAME, "Missing prefix text!");
}
prefixText = new Buffer(prefixText); // allocate ahead of time
// Creating a stream through which each file will pass
var stream = through.obj(function (file, enc, callback) {
if (file.isNull()) {
this.push(file); // Do nothing if no contents
return callback();
}
if (file.isBuffer()) {
file.contents = Buffer.concat([prefixText, file.contents]);
this.push(file);
return callback();
}
if (file.isStream()) {
this.emit('error', new PluginError(PLUGIN_NAME, 'Streams are not supported!'));
return callback();
}
});
// returning the file stream
return stream;
};
// Exporting the plugin main function
module.exports = gulpPrefixer;
```
The above plugin can be used like this:
```js
var gulp = require('gulp');
var gulpPrefixer = require('gulp-prefixer');
gulp.src('files/**/*.js')
.pipe(gulpPrefixer('prepended string'))
.pipe(gulp.dest('/modified-files/'));
```
## Handling streams
Unfortunately, the above plugin will error when using gulp.src in non-buffered (streaming) mode. You should support streams too if possible. See [Dealing with streams](dealing-with-streams.md) for more information.
## Some plugins based on buffers
* [gulp-coffee](https://github.com/wearefractal/gulp-coffee)
* [gulp-svgmin](https://github.com/ben-eb/gulp-svgmin)
* [gulp-marked](https://github.com/lmtm/gulp-marked)
* [gulp-svg2ttf](https://github.com/nfroidure/gulp-svg2ttf)
+27
View File
@@ -0,0 +1,27 @@
'use strict';
var gulp = require('./');
var env = require('gulp-util').env;
var log = require('gulp-util').log;
var jshint = require('gulp-jshint');
var codeFiles = ['**/*.js', '!node_modules/**'];
gulp.task('lint', function(){
log('Linting Files');
return gulp.src(codeFiles)
.pipe(jshint('.jshintrc'))
.pipe(jshint.reporter());
});
gulp.task('watch', function(){
log('Watching Files');
gulp.watch(codeFiles, ['lint']);
});
gulp.task('default', ['lint', 'watch']);
var taskToRun = env.dev ? 'default' : 'lint';
gulp.start(taskToRun);
Generated Vendored
+51
View File
@@ -0,0 +1,51 @@
'use strict';
var util = require('util');
var Orchestrator = require('orchestrator');
var gutil = require('gulp-util');
var deprecated = require('deprecated');
var vfs = require('vinyl-fs');
function Gulp(){
Orchestrator.call(this);
}
util.inherits(Gulp, Orchestrator);
Gulp.prototype.task = Gulp.prototype.add;
Gulp.prototype.run = function(){
// run() is deprecated as of 3.5 and will be removed in 4.0
// use task dependencies instead
// impose our opinion of "default" tasks onto orchestrator
var tasks = arguments.length ? arguments : ['default'];
this.start.apply(this, tasks);
};
Gulp.prototype.src = vfs.src;
Gulp.prototype.dest = vfs.dest;
Gulp.prototype.watch = function (glob, opt, fn) {
if (!fn) {
fn = opt;
opt = null;
}
// array of tasks given
if (Array.isArray(fn)) {
return vfs.watch(glob, opt, function(){
this.start.apply(this, fn);
}.bind(this));
}
return vfs.watch(glob, opt, fn);
};
// let people use this class from our instance
Gulp.prototype.Gulp = Gulp;
// deprecations
deprecated.field('gulp.env has been deprecated. Use gulp-util.env or your own CLI parser instead.', console.log, Gulp.prototype, 'env', gutil.env);
Gulp.prototype.run = deprecated.method('gulp.run() has been deprecated. Use task dependencies or gulp.watch task triggering instead.', console.log, Gulp.prototype.run);
var inst = new Gulp();
module.exports = inst;
+16
View File
@@ -0,0 +1,16 @@
'use strict';
var fs = require('fs');
var path = require('path');
module.exports = function(name) {
if (typeof name !== 'string') throw new Error('Missing completion type');
var file = path.join(__dirname, '../completion', name);
try {
console.log(fs.readFileSync(file, 'utf8'));
process.exit(0);
} catch (err) {
console.log('echo "Specified gulp shell auto-completion rules for \''+name+'\' not found"');
process.exit(5);
}
};
+11
View File
@@ -0,0 +1,11 @@
'use strict';
module.exports = function(tasks) {
return Object.keys(tasks).reduce(function(prev, task) {
prev.nodes.push({
label: task,
nodes: tasks[task].dep
});
return prev;
}, {nodes: []});
};
+1
View File
@@ -0,0 +1 @@
../semver/bin/semver
+4
View File
@@ -0,0 +1,4 @@
language: node_js
node_js:
- 0.4
- 0.6
+92
View File
@@ -0,0 +1,92 @@
archy
=====
Render nested hierarchies `npm ls` style with unicode pipes.
[![build status](https://secure.travis-ci.org/substack/node-archy.png)](http://travis-ci.org/substack/node-archy)
example
=======
``` js
var archy = require('archy');
var s = archy({
label : 'beep',
nodes : [
'ity',
{
label : 'boop',
nodes : [
{
label : 'o_O',
nodes : [
{
label : 'oh',
nodes : [ 'hello', 'puny' ]
},
'human'
]
},
'party\ntime!'
]
}
]
});
console.log(s);
```
output
```
beep
├── ity
└─┬ boop
├─┬ o_O
│ ├─┬ oh
│ │ ├── hello
│ │ └── puny
│ └── human
└── party
time!
```
methods
=======
var archy = require('archy')
archy(obj, prefix='', opts={})
------------------------------
Return a string representation of `obj` with unicode pipe characters like how
`npm ls` looks.
`obj` should be a tree of nested objects with `'label'` and `'nodes'` fields.
`'label'` is a string of text to display at a node level and `'nodes'` is an
array of the descendents of the current node.
If a node is a string, that string will be used as the `'label'` and an empty
array of `'nodes'` will be used.
`prefix` gets prepended to all the lines and is used by the algorithm to
recursively update.
If `'label'` has newlines they will be indented at the present indentation level
with the current prefix.
To disable unicode results in favor of all-ansi output set `opts.unicode` to
`false`.
install
=======
With [npm](http://npmjs.org) do:
```
npm install archy
```
license
=======
MIT/X11
+24
View File
@@ -0,0 +1,24 @@
var archy = require('../');
var s = archy({
label : 'beep',
nodes : [
'ity',
{
label : 'boop',
nodes : [
{
label : 'o_O',
nodes : [
{
label : 'oh',
nodes : [ 'hello', 'puny' ]
},
'human'
]
},
'party\ntime!'
]
}
]
});
console.log(s);
+25
View File
@@ -0,0 +1,25 @@
var archy = require('../');
var s = archy({
label : 'beep\none\ntwo',
nodes : [
'ity',
{
label : 'boop',
nodes : [
{
label : 'o_O\nwheee',
nodes : [
{
label : 'oh',
nodes : [ 'hello', 'puny\nmeat' ]
},
'creature'
]
},
'party\ntime!'
]
}
]
});
console.log(s);
+35
View File
@@ -0,0 +1,35 @@
module.exports = function archy (obj, prefix, opts) {
if (prefix === undefined) prefix = '';
if (!opts) opts = {};
var chr = function (s) {
var chars = {
'│' : '|',
'└' : '`',
'├' : '+',
'─' : '-',
'┬' : '-'
};
return opts.unicode === false ? chars[s] : s;
};
if (typeof obj === 'string') obj = { label : obj };
var nodes = obj.nodes || [];
var lines = (obj.label || '').split('\n');
var splitter = '\n' + prefix + (nodes.length ? chr('│') : ' ') + ' ';
return prefix
+ lines.join(splitter) + '\n'
+ nodes.map(function (node, ix) {
var last = ix === nodes.length - 1;
var more = node.nodes && node.nodes.length;
var prefix_ = prefix + (last ? ' ' : chr('│')) + ' ';
return prefix
+ (last ? chr('└') : chr('├')) + chr('─')
+ (more ? chr('┬') : chr('─')) + ' '
+ archy(node, prefix_, opts).slice(prefix.length + 2)
;
}).join('')
;
};
+49
View File
@@ -0,0 +1,49 @@
{
"name": "archy",
"version": "0.0.2",
"description": "render nested hierarchies `npm ls` style with unicode pipes",
"main": "index.js",
"directories": {
"lib": ".",
"example": "example",
"test": "test"
},
"devDependencies": {
"tap": "~0.2.3"
},
"scripts": {
"test": "tap test"
},
"repository": {
"type": "git",
"url": "http://github.com/substack/node-archy.git"
},
"keywords": [
"hierarchy",
"npm ls",
"unicode",
"pretty",
"print"
],
"author": {
"name": "James Halliday",
"email": "mail@substack.net",
"url": "http://substack.net"
},
"license": "MIT/X11",
"engine": {
"node": ">=0.4"
},
"readme": "archy\n=====\n\nRender nested hierarchies `npm ls` style with unicode pipes.\n\n[![build status](https://secure.travis-ci.org/substack/node-archy.png)](http://travis-ci.org/substack/node-archy)\n\nexample\n=======\n\n``` js\nvar archy = require('archy');\nvar s = archy({\n label : 'beep',\n nodes : [\n 'ity',\n {\n label : 'boop',\n nodes : [\n {\n label : 'o_O',\n nodes : [\n {\n label : 'oh',\n nodes : [ 'hello', 'puny' ]\n },\n 'human'\n ]\n },\n 'party\\ntime!'\n ]\n }\n ]\n});\nconsole.log(s);\n```\n\noutput\n\n```\nbeep\n├── ity\n└─┬ boop\n ├─┬ o_O\n │ ├─┬ oh\n │ │ ├── hello\n │ │ └── puny\n │ └── human\n └── party\n time!\n```\n\nmethods\n=======\n\nvar archy = require('archy')\n\narchy(obj, prefix='', opts={})\n------------------------------\n\nReturn a string representation of `obj` with unicode pipe characters like how\n`npm ls` looks.\n\n`obj` should be a tree of nested objects with `'label'` and `'nodes'` fields.\n`'label'` is a string of text to display at a node level and `'nodes'` is an\narray of the descendents of the current node.\n\nIf a node is a string, that string will be used as the `'label'` and an empty\narray of `'nodes'` will be used.\n\n`prefix` gets prepended to all the lines and is used by the algorithm to\nrecursively update.\n\nIf `'label'` has newlines they will be indented at the present indentation level\nwith the current prefix.\n\nTo disable unicode results in favor of all-ansi output set `opts.unicode` to\n`false`.\n\ninstall\n=======\n\nWith [npm](http://npmjs.org) do:\n\n```\nnpm install archy\n```\n\nlicense\n=======\n\nMIT/X11\n",
"readmeFilename": "README.markdown",
"bugs": {
"url": "https://github.com/substack/node-archy/issues"
},
"homepage": "https://github.com/substack/node-archy",
"_id": "archy@0.0.2",
"dist": {
"shasum": "309338c61a5ef6d8249e772a14bfea549b8382dc"
},
"_from": "archy@~0.0.2",
"_resolved": "https://registry.npmjs.org/archy/-/archy-0.0.2.tgz"
}
+40
View File
@@ -0,0 +1,40 @@
var test = require('tap').test;
var archy = require('../');
test('beep', function (t) {
var s = archy({
label : 'beep',
nodes : [
'ity',
{
label : 'boop',
nodes : [
{
label : 'o_O',
nodes : [
{
label : 'oh',
nodes : [ 'hello', 'puny' ]
},
'human'
]
},
'party!'
]
}
]
});
t.equal(s, [
'beep',
'├── ity',
'└─┬ boop',
' ├─┬ o_O',
' │ ├─┬ oh',
' │ │ ├── hello',
' │ │ └── puny',
' │ └── human',
' └── party!',
''
].join('\n'));
t.end();
});
+45
View File
@@ -0,0 +1,45 @@
var test = require('tap').test;
var archy = require('../');
test('multi-line', function (t) {
var s = archy({
label : 'beep\none\ntwo',
nodes : [
'ity',
{
label : 'boop',
nodes : [
{
label : 'o_O\nwheee',
nodes : [
{
label : 'oh',
nodes : [ 'hello', 'puny\nmeat' ]
},
'creature'
]
},
'party\ntime!'
]
}
]
});
t.equal(s, [
'beep',
'│ one',
'│ two',
'├── ity',
'└─┬ boop',
' ├─┬ o_O',
' │ │ wheee',
' │ ├─┬ oh',
' │ │ ├── hello',
' │ │ └── puny',
' │ │ meat',
' │ └── creature',
' └── party',
' time!',
''
].join('\n'));
t.end();
});
+40
View File
@@ -0,0 +1,40 @@
var test = require('tap').test;
var archy = require('../');
test('beep', function (t) {
var s = archy({
label : 'beep',
nodes : [
'ity',
{
label : 'boop',
nodes : [
{
label : 'o_O',
nodes : [
{
label : 'oh',
nodes : [ 'hello', 'puny' ]
},
'human'
]
},
'party!'
]
}
]
}, '', { unicode : false });
t.equal(s, [
'beep',
'+-- ity',
'`-- boop',
' +-- o_O',
' | +-- oh',
' | | +-- hello',
' | | `-- puny',
' | `-- human',
' `-- party!',
''
].join('\n'));
t.end();
});
+6
View File
@@ -0,0 +1,6 @@
.DS_Store
*.log
node_modules
build
*.node
components
+6
View File
@@ -0,0 +1,6 @@
language: node_js
node_js:
- "0.9"
- "0.10"
after_script:
- npm run coveralls
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2014 Fractal <contact@wearefractal.com>
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.
+51
View File
@@ -0,0 +1,51 @@
# deprecated [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coveralls Status][coveralls-image]][coveralls-url] [![Dependency Status][david-image]][david-url]
## Information
<table>
<tr>
<td>Package</td><td>deprecated</td>
</tr>
<tr>
<td>Description</td>
<td>Tool for deprecating things</td>
</tr>
<tr>
<td>Node Version</td>
<td>>= 0.9</td>
</tr>
</table>
## Usage
```javascript
var oldfn = function(a,b) {
return a+b;
};
// returns a new wrapper function that logs the deprecated function once
var somefn = deprecated('dont use this anymore', console.log, oldfn);
var someobj = {};
// set up a getter/set for field that logs deprecated message once
deprecated('dont use this anymore', console.log, someobj, 'a', 123);
console.log(someobj.a); // 123
```
[npm-url]: https://npmjs.org/package/deprecated
[npm-image]: https://badge.fury.io/js/deprecated.png
[travis-url]: https://travis-ci.org/wearefractal/deprecated
[travis-image]: https://travis-ci.org/wearefractal/deprecated.png?branch=master
[coveralls-url]: https://coveralls.io/r/wearefractal/deprecated
[coveralls-image]: https://coveralls.io/repos/wearefractal/deprecated/badge.png
[depstat-url]: https://david-dm.org/wearefractal/deprecated
[depstat-image]: https://david-dm.org/wearefractal/deprecated.png
[david-url]: https://david-dm.org/wearefractal/deprecated
[david-image]: https://david-dm.org/wearefractal/deprecated.png?theme=shields.io
+39
View File
@@ -0,0 +1,39 @@
var deprecated = {
method: function(msg, log, fn) {
var called = false;
return function(){
if (!called) {
called = true;
log(msg);
}
return fn.apply(this, arguments);
};
},
field: function(msg, log, parent, field, val) {
var called = false;
var getter = function(){
if (!called) {
called = true;
log(msg);
}
return val;
};
var setter = function(v) {
if (!called) {
called = true;
log(msg);
}
val = v;
return v;
};
Object.defineProperty(parent, field, {
get: getter,
set: setter,
enumerable: true
});
return;
}
};
module.exports = deprecated;
+50
View File
@@ -0,0 +1,50 @@
{
"name": "deprecated",
"description": "Tool for deprecating things",
"version": "0.0.1",
"homepage": "http://github.com/wearefractal/deprecated",
"repository": {
"type": "git",
"url": "git://github.com/wearefractal/deprecated.git"
},
"author": {
"name": "Fractal",
"email": "contact@wearefractal.com",
"url": "http://wearefractal.com/"
},
"main": "./index.js",
"dependencies": {},
"devDependencies": {
"mocha": "~1.17.0",
"should": "~3.1.0",
"mocha-lcov-reporter": "~0.0.1",
"coveralls": "~2.6.1",
"istanbul": "~0.2.3",
"rimraf": "~2.2.5",
"jshint": "~2.4.1"
},
"scripts": {
"test": "mocha --reporter spec && jshint",
"coveralls": "istanbul cover _mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage"
},
"engines": {
"node": ">= 0.9"
},
"licenses": [
{
"type": "MIT",
"url": "http://github.com/wearefractal/deprecated/raw/master/LICENSE"
}
],
"readme": "# deprecated [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coveralls Status][coveralls-image]][coveralls-url] [![Dependency Status][david-image]][david-url]\n\n\n## Information\n\n<table>\n<tr> \n<td>Package</td><td>deprecated</td>\n</tr>\n<tr>\n<td>Description</td>\n<td>Tool for deprecating things</td>\n</tr>\n<tr>\n<td>Node Version</td>\n<td>>= 0.9</td>\n</tr>\n</table>\n\n## Usage\n\n```javascript\nvar oldfn = function(a,b) {\n return a+b;\n};\n\n// returns a new wrapper function that logs the deprecated function once\nvar somefn = deprecated('dont use this anymore', console.log, oldfn);\n\nvar someobj = {};\n\n// set up a getter/set for field that logs deprecated message once\ndeprecated('dont use this anymore', console.log, someobj, 'a', 123);\n\nconsole.log(someobj.a); // 123\n```\n\n[npm-url]: https://npmjs.org/package/deprecated\n[npm-image]: https://badge.fury.io/js/deprecated.png\n\n[travis-url]: https://travis-ci.org/wearefractal/deprecated\n[travis-image]: https://travis-ci.org/wearefractal/deprecated.png?branch=master\n\n[coveralls-url]: https://coveralls.io/r/wearefractal/deprecated\n[coveralls-image]: https://coveralls.io/repos/wearefractal/deprecated/badge.png\n\n[depstat-url]: https://david-dm.org/wearefractal/deprecated\n[depstat-image]: https://david-dm.org/wearefractal/deprecated.png\n\n[david-url]: https://david-dm.org/wearefractal/deprecated\n[david-image]: https://david-dm.org/wearefractal/deprecated.png?theme=shields.io",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/wearefractal/deprecated/issues"
},
"_id": "deprecated@0.0.1",
"dist": {
"shasum": "f9c9af5464afa1e7a971458a8bdef2aa94d5bb19"
},
"_from": "deprecated@~0.0.1",
"_resolved": "https://registry.npmjs.org/deprecated/-/deprecated-0.0.1.tgz"
}
+44
View File
@@ -0,0 +1,44 @@
var deprecated = require('../');
var should = require('should');
require('mocha');
describe('field()', function() {
it('should return a wrapped function that logs once on get', function(done) {
var message = 'testing';
var scope = {
a: 1
};
var obj = {};
var logged = false;
var log = function(msg){
msg.should.equal(message);
logged.should.equal(false);
logged = true;
};
deprecated.field(message, log, obj, 'a', 123);
obj.a.should.equal(123);
obj.a = 1234;
obj.a.should.equal(1234);
logged.should.equal(true);
done();
});
it('should return a wrapped function that logs once on set', function(done) {
var message = 'testing';
var scope = {
a: 1
};
var obj = {};
var logged = false;
var log = function(msg){
msg.should.equal(message);
logged.should.equal(false);
logged = true;
};
deprecated.field(message, log, obj, 'a', 123);
obj.a = 1234;
logged.should.equal(true);
done();
});
});
+32
View File
@@ -0,0 +1,32 @@
var deprecated = require('../');
var should = require('should');
require('mocha');
describe('method()', function() {
it('should return a wrapped function that logs once', function(done) {
var message = 'testing';
var scope = {
a: 1
};
var logged = false;
var log = function(msg){
msg.should.equal(message);
logged.should.equal(false);
logged = true;
};
var fn = deprecated.method(message, log, function(one, two){
this.should.equal(scope);
one.should.equal(1);
two.should.equal(2);
return one+two;
});
fn.bind(scope)(1,2).should.equal(3);
fn.bind(scope)(1,2).should.equal(3);
fn.bind(scope)(1,2).should.equal(3);
fn.bind(scope)(1,2).should.equal(3);
logged.should.equal(true);
done();
});
});
+16
View File
@@ -0,0 +1,16 @@
{
"loopfunc": true,
"curly": true,
"eqeqeq": true,
"immed": true,
"latedef": true,
"newcap": true,
"noarg": true,
"sub": true,
"undef": true,
"unused": true,
"boss": true,
"eqnull": true,
"node": true,
"es5": true
}
View File
Whitespace-only changes.
+25
View File
@@ -0,0 +1,25 @@
'use strict';
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
nodeunit: {
files: ['test/**/*_test.js'],
},
jshint: {
options: {
jshintrc: '.jshintrc'
},
all: ['Gruntfile.js', 'lib/**/*.js', 'test/**/*.js']
}
});
// Load plugins.
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-nodeunit');
// Default task.
grunt.registerTask('default', ['jshint', 'nodeunit']);
};
+22
View File
@@ -0,0 +1,22 @@
Copyright (c) 2013 "Cowboy" Ben Alman
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.
+44
View File
@@ -0,0 +1,44 @@
# findup-sync
Find the first file matching a given pattern in the current directory or the nearest ancestor directory.
## Getting Started
Install the module with: `npm install findup-sync`
```js
var findup = require('findup-sync');
// Start looking in the CWD.
var filepath1 = findup('{a,b}*.txt');
// Start looking somewhere else, and ignore case (probably a good idea).
var filepath2 = findup('{a,b}*.txt', {cwd: '/some/path', nocase: true});
```
## Usage
```js
findup(patternOrPatterns [, minimatchOptions])
```
### patternOrPatterns
Type: `String` or `Array`
Default: none
One or more wildcard glob patterns. Or just filenames.
### minimatchOptions
Type: `Object`
Default: `{}`
Options to be passed to [minimatch](https://github.com/isaacs/minimatch).
Note that if you want to start in a different directory than the current working directory, specify a `cwd` property here.
## Contributing
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).
## Release History
2013-03-08 - v0.1.2 - Updated dependencies. Fixed a Node 0.9.x bug. Updated unit tests to work cross-platform.
2012-11-15 - v0.1.1 - Now works without an options object.
2012-11-01 - v0.1.0 - Initial release.
+46
View File
@@ -0,0 +1,46 @@
/*
* findup-sync
* https://github.com/cowboy/node-findup-sync
*
* Copyright (c) 2013 "Cowboy" Ben Alman
* Licensed under the MIT license.
*/
'use strict';
// Nodejs libs.
var path = require('path');
// External libs.
var glob = require('glob');
var _ = require('lodash');
// Search for a filename in the given directory or all parent directories.
module.exports = function(patterns, options) {
// Normalize patterns to an array.
if (!Array.isArray(patterns)) { patterns = [patterns]; }
// Create globOptions so that it can be modified without mutating the
// original object.
var globOptions = Object.create(options || {});
globOptions.maxDepth = 1;
globOptions.cwd = path.resolve(globOptions.cwd || '.');
var files, lastpath;
do {
// Search for files matching patterns.
files = _(patterns).map(function(pattern) {
return glob.sync(pattern, globOptions);
}).flatten().uniq().value();
// Return file if found.
if (files.length > 0) {
return path.resolve(path.join(globOptions.cwd, files[0]));
}
// Go up a directory.
lastpath = globOptions.cwd;
globOptions.cwd = path.resolve(globOptions.cwd, '..');
// If parentpath is the same as basedir, we can't go any higher.
} while (globOptions.cwd !== lastpath);
// No files were found!
return null;
};
@@ -0,0 +1,2 @@
.*.swp
test/a/
@@ -0,0 +1,3 @@
language: node_js
node_js:
- 0.8
+27
View File
@@ -0,0 +1,27 @@
Copyright (c) Isaac Z. Schlueter ("Author")
All rights reserved.
The BSD License
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+233
View File
@@ -0,0 +1,233 @@
# Glob
This is a glob implementation in JavaScript. It uses the `minimatch`
library to do its matching.
## Attention: node-glob users!
The API has changed dramatically between 2.x and 3.x. This library is
now 100% JavaScript, and the integer flags have been replaced with an
options object.
Also, there's an event emitter class, proper tests, and all the other
things you've come to expect from node modules.
And best of all, no compilation!
## Usage
```javascript
var glob = require("glob")
// options is optional
glob("**/*.js", options, function (er, files) {
// files is an array of filenames.
// If the `nonull` option is set, and nothing
// was found, then files is ["**/*.js"]
// er is an error object or null.
})
```
## Features
Please see the [minimatch
documentation](https://github.com/isaacs/minimatch) for more details.
Supports these glob features:
* Brace Expansion
* Extended glob matching
* "Globstar" `**` matching
See:
* `man sh`
* `man bash`
* `man 3 fnmatch`
* `man 5 gitignore`
* [minimatch documentation](https://github.com/isaacs/minimatch)
## glob(pattern, [options], cb)
* `pattern` {String} Pattern to be matched
* `options` {Object}
* `cb` {Function}
* `err` {Error | null}
* `matches` {Array<String>} filenames found matching the pattern
Perform an asynchronous glob search.
## glob.sync(pattern, [options]
* `pattern` {String} Pattern to be matched
* `options` {Object}
* return: {Array<String>} filenames found matching the pattern
Perform a synchronous glob search.
## Class: glob.Glob
Create a Glob object by instanting the `glob.Glob` class.
```javascript
var Glob = require("glob").Glob
var mg = new Glob(pattern, options, cb)
```
It's an EventEmitter, and starts walking the filesystem to find matches
immediately.
### new glob.Glob(pattern, [options], [cb])
* `pattern` {String} pattern to search for
* `options` {Object}
* `cb` {Function} Called when an error occurs, or matches are found
* `err` {Error | null}
* `matches` {Array<String>} filenames found matching the pattern
Note that if the `sync` flag is set in the options, then matches will
be immediately available on the `g.found` member.
### Properties
* `minimatch` The minimatch object that the glob uses.
* `options` The options object passed in.
* `error` The error encountered. When an error is encountered, the
glob object is in an undefined state, and should be discarded.
* `aborted` Boolean which is set to true when calling `abort()`. There
is no way at this time to continue a glob search after aborting, but
you can re-use the statCache to avoid having to duplicate syscalls.
### Events
* `end` When the matching is finished, this is emitted with all the
matches found. If the `nonull` option is set, and no match was found,
then the `matches` list contains the original pattern. The matches
are sorted, unless the `nosort` flag is set.
* `match` Every time a match is found, this is emitted with the matched.
* `error` Emitted when an unexpected error is encountered, or whenever
any fs error occurs if `options.strict` is set.
* `abort` When `abort()` is called, this event is raised.
### Methods
* `abort` Stop the search.
### Options
All the options that can be passed to Minimatch can also be passed to
Glob to change pattern matching behavior. Also, some have been added,
or have glob-specific ramifications.
All options are false by default, unless otherwise noted.
All options are added to the glob object, as well.
* `cwd` The current working directory in which to search. Defaults
to `process.cwd()`.
* `root` The place where patterns starting with `/` will be mounted
onto. Defaults to `path.resolve(options.cwd, "/")` (`/` on Unix
systems, and `C:\` or some such on Windows.)
* `nomount` By default, a pattern starting with a forward-slash will be
"mounted" onto the root setting, so that a valid filesystem path is
returned. Set this flag to disable that behavior.
* `mark` Add a `/` character to directory matches. Note that this
requires additional stat calls.
* `nosort` Don't sort the results.
* `stat` Set to true to stat *all* results. This reduces performance
somewhat, and is completely unnecessary, unless `readdir` is presumed
to be an untrustworthy indicator of file existence. It will cause
ELOOP to be triggered one level sooner in the case of cyclical
symbolic links.
* `silent` When an unusual error is encountered
when attempting to read a directory, a warning will be printed to
stderr. Set the `silent` option to true to suppress these warnings.
* `strict` When an unusual error is encountered
when attempting to read a directory, the process will just continue on
in search of other matches. Set the `strict` option to raise an error
in these cases.
* `statCache` A cache of results of filesystem information, to prevent
unnecessary stat calls. While it should not normally be necessary to
set this, you may pass the statCache from one glob() call to the
options object of another, if you know that the filesystem will not
change between calls. (See "Race Conditions" below.)
* `sync` Perform a synchronous glob search.
* `nounique` In some cases, brace-expanded patterns can result in the
same file showing up multiple times in the result set. By default,
this implementation prevents duplicates in the result set.
Set this flag to disable that behavior.
* `nonull` Set to never return an empty set, instead returning a set
containing the pattern itself. This is the default in glob(3).
* `nocase` Perform a case-insensitive match. Note that case-insensitive
filesystems will sometimes result in glob returning results that are
case-insensitively matched anyway, since readdir and stat will not
raise an error.
* `debug` Set to enable debug logging in minimatch and glob.
* `globDebug` Set to enable debug logging in glob, but not minimatch.
## Comparisons to other fnmatch/glob implementations
While strict compliance with the existing standards is a worthwhile
goal, some discrepancies exist between node-glob and other
implementations, and are intentional.
If the pattern starts with a `!` character, then it is negated. Set the
`nonegate` flag to suppress this behavior, and treat leading `!`
characters normally. This is perhaps relevant if you wish to start the
pattern with a negative extglob pattern like `!(a|B)`. Multiple `!`
characters at the start of a pattern will negate the pattern multiple
times.
If a pattern starts with `#`, then it is treated as a comment, and
will not match anything. Use `\#` to match a literal `#` at the
start of a line, or set the `nocomment` flag to suppress this behavior.
The double-star character `**` is supported by default, unless the
`noglobstar` flag is set. This is supported in the manner of bsdglob
and bash 4.1, where `**` only has special significance if it is the only
thing in a path part. That is, `a/**/b` will match `a/x/y/b`, but
`a/**b` will not. **Note that this is different from the way that `**` is
handled by ruby's `Dir` class.**
If an escaped pattern has no matches, and the `nonull` flag is set,
then glob returns the pattern as-provided, rather than
interpreting the character escapes. For example,
`glob.match([], "\\*a\\?")` will return `"\\*a\\?"` rather than
`"*a?"`. This is akin to setting the `nullglob` option in bash, except
that it does not resolve escaped pattern characters.
If brace expansion is not disabled, then it is performed before any
other interpretation of the glob pattern. Thus, a pattern like
`+(a|{b),c)}`, which would not be valid in bash or zsh, is expanded
**first** into the set of `+(a|b)` and `+(a|c)`, and those patterns are
checked for validity. Since those two are valid, matching proceeds.
## Windows
**Please only use forward-slashes in glob expressions.**
Though windows uses either `/` or `\` as its path separator, only `/`
characters are used by this glob implementation. You must use
forward-slashes **only** in glob expressions. Back-slashes will always
be interpreted as escape characters, not path separators.
Results from absolute patterns such as `/foo/*` are mounted onto the
root setting using `path.join`. On windows, this will by default result
in `/foo/*` matching `C:\foo\bar.txt`.
## Race Conditions
Glob searching, by its very nature, is susceptible to race conditions,
since it relies on directory walking and such.
As a result, it is possible that a file that exists when glob looks for
it may have been deleted or modified by the time it returns the result.
As part of its internal implementation, this program caches all stat
and readdir calls that it makes, in order to cut down on system
overhead. However, this also makes it even more susceptible to races,
especially if the statCache object is reused between glob calls.
Users are thus advised not to use a glob result as a
guarantee of filesystem state in the face of rapid changes.
For the vast majority of operations, this is never a problem.
@@ -0,0 +1,9 @@
var Glob = require("../").Glob
var pattern = "test/a/**/[cg]/../[cg]"
console.log(pattern)
var mg = new Glob(pattern, {mark: true, sync:true}, function (er, matches) {
console.log("matches", matches)
})
console.log("after")
@@ -0,0 +1,9 @@
var Glob = require("../").Glob
var pattern = "{./*/*,/*,/usr/local/*}"
console.log(pattern)
var mg = new Glob(pattern, {mark: true}, function (er, matches) {
console.log("matches", matches)
})
console.log("after")
+643
View File
@@ -0,0 +1,643 @@
// Approach:
//
// 1. Get the minimatch set
// 2. For each pattern in the set, PROCESS(pattern)
// 3. Store matches per-set, then uniq them
//
// PROCESS(pattern)
// Get the first [n] items from pattern that are all strings
// Join these together. This is PREFIX.
// If there is no more remaining, then stat(PREFIX) and
// add to matches if it succeeds. END.
// readdir(PREFIX) as ENTRIES
// If fails, END
// If pattern[n] is GLOBSTAR
// // handle the case where the globstar match is empty
// // by pruning it out, and testing the resulting pattern
// PROCESS(pattern[0..n] + pattern[n+1 .. $])
// // handle other cases.
// for ENTRY in ENTRIES (not dotfiles)
// // attach globstar + tail onto the entry
// PROCESS(pattern[0..n] + ENTRY + pattern[n .. $])
//
// else // not globstar
// for ENTRY in ENTRIES (not dotfiles, unless pattern[n] is dot)
// Test ENTRY against pattern[n]
// If fails, continue
// If passes, PROCESS(pattern[0..n] + item + pattern[n+1 .. $])
//
// Caveat:
// Cache all stats and readdirs results to minimize syscall. Since all
// we ever care about is existence and directory-ness, we can just keep
// `true` for files, and [children,...] for directories, or `false` for
// things that don't exist.
module.exports = glob
var fs = require("graceful-fs")
, minimatch = require("minimatch")
, Minimatch = minimatch.Minimatch
, inherits = require("inherits")
, EE = require("events").EventEmitter
, path = require("path")
, isDir = {}
, assert = require("assert").ok
function glob (pattern, options, cb) {
if (typeof options === "function") cb = options, options = {}
if (!options) options = {}
if (typeof options === "number") {
deprecated()
return
}
var g = new Glob(pattern, options, cb)
return g.sync ? g.found : g
}
glob.fnmatch = deprecated
function deprecated () {
throw new Error("glob's interface has changed. Please see the docs.")
}
glob.sync = globSync
function globSync (pattern, options) {
if (typeof options === "number") {
deprecated()
return
}
options = options || {}
options.sync = true
return glob(pattern, options)
}
glob.Glob = Glob
inherits(Glob, EE)
function Glob (pattern, options, cb) {
if (!(this instanceof Glob)) {
return new Glob(pattern, options, cb)
}
if (typeof cb === "function") {
this.on("error", cb)
this.on("end", function (matches) {
cb(null, matches)
})
}
options = options || {}
this.EOF = {}
this._emitQueue = []
this.maxDepth = options.maxDepth || 1000
this.maxLength = options.maxLength || Infinity
this.statCache = options.statCache || {}
this.changedCwd = false
var cwd = process.cwd()
if (!options.hasOwnProperty("cwd")) this.cwd = cwd
else {
this.cwd = options.cwd
this.changedCwd = path.resolve(options.cwd) !== cwd
}
this.root = options.root || path.resolve(this.cwd, "/")
this.root = path.resolve(this.root)
if (process.platform === "win32")
this.root = this.root.replace(/\\/g, "/")
this.nomount = !!options.nomount
if (!pattern) {
throw new Error("must provide pattern")
}
// base-matching: just use globstar for that.
if (options.matchBase && -1 === pattern.indexOf("/")) {
if (options.noglobstar) {
throw new Error("base matching requires globstar")
}
pattern = "**/" + pattern
}
this.strict = options.strict !== false
this.dot = !!options.dot
this.mark = !!options.mark
this.sync = !!options.sync
this.nounique = !!options.nounique
this.nonull = !!options.nonull
this.nosort = !!options.nosort
this.nocase = !!options.nocase
this.stat = !!options.stat
this.debug = !!options.debug || !!options.globDebug
if (this.debug)
this.log = console.error
this.silent = !!options.silent
var mm = this.minimatch = new Minimatch(pattern, options)
this.options = mm.options
pattern = this.pattern = mm.pattern
this.error = null
this.aborted = false
EE.call(this)
// process each pattern in the minimatch set
var n = this.minimatch.set.length
// The matches are stored as {<filename>: true,...} so that
// duplicates are automagically pruned.
// Later, we do an Object.keys() on these.
// Keep them as a list so we can fill in when nonull is set.
this.matches = new Array(n)
this.minimatch.set.forEach(iterator.bind(this))
function iterator (pattern, i, set) {
this._process(pattern, 0, i, function (er) {
if (er) this.emit("error", er)
if (-- n <= 0) this._finish()
})
}
}
Glob.prototype.log = function () {}
Glob.prototype._finish = function () {
assert(this instanceof Glob)
var nou = this.nounique
, all = nou ? [] : {}
for (var i = 0, l = this.matches.length; i < l; i ++) {
var matches = this.matches[i]
this.log("matches[%d] =", i, matches)
// do like the shell, and spit out the literal glob
if (!matches) {
if (this.nonull) {
var literal = this.minimatch.globSet[i]
if (nou) all.push(literal)
else all[literal] = true
}
} else {
// had matches
var m = Object.keys(matches)
if (nou) all.push.apply(all, m)
else m.forEach(function (m) {
all[m] = true
})
}
}
if (!nou) all = Object.keys(all)
if (!this.nosort) {
all = all.sort(this.nocase ? alphasorti : alphasort)
}
if (this.mark) {
// at *some* point we statted all of these
all = all.map(function (m) {
var sc = this.statCache[m]
if (!sc)
return m
var isDir = (Array.isArray(sc) || sc === 2)
if (isDir && m.slice(-1) !== "/") {
return m + "/"
}
if (!isDir && m.slice(-1) === "/") {
return m.replace(/\/+$/, "")
}
return m
}, this)
}
this.log("emitting end", all)
this.EOF = this.found = all
this.emitMatch(this.EOF)
}
function alphasorti (a, b) {
a = a.toLowerCase()
b = b.toLowerCase()
return alphasort(a, b)
}
function alphasort (a, b) {
return a > b ? 1 : a < b ? -1 : 0
}
Glob.prototype.abort = function () {
this.aborted = true
this.emit("abort")
}
Glob.prototype.pause = function () {
if (this.paused) return
if (this.sync)
this.emit("error", new Error("Can't pause/resume sync glob"))
this.paused = true
this.emit("pause")
}
Glob.prototype.resume = function () {
if (!this.paused) return
if (this.sync)
this.emit("error", new Error("Can't pause/resume sync glob"))
this.paused = false
this.emit("resume")
this._processEmitQueue()
//process.nextTick(this.emit.bind(this, "resume"))
}
Glob.prototype.emitMatch = function (m) {
this._emitQueue.push(m)
this._processEmitQueue()
}
Glob.prototype._processEmitQueue = function (m) {
while (!this._processingEmitQueue &&
!this.paused) {
this._processingEmitQueue = true
var m = this._emitQueue.shift()
if (!m) {
this._processingEmitQueue = false
break
}
this.log('emit!', m === this.EOF ? "end" : "match")
this.emit(m === this.EOF ? "end" : "match", m)
this._processingEmitQueue = false
}
}
Glob.prototype._process = function (pattern, depth, index, cb_) {
assert(this instanceof Glob)
var cb = function cb (er, res) {
assert(this instanceof Glob)
if (this.paused) {
if (!this._processQueue) {
this._processQueue = []
this.once("resume", function () {
var q = this._processQueue
this._processQueue = null
q.forEach(function (cb) { cb() })
})
}
this._processQueue.push(cb_.bind(this, er, res))
} else {
cb_.call(this, er, res)
}
}.bind(this)
if (this.aborted) return cb()
if (depth > this.maxDepth) return cb()
// Get the first [n] parts of pattern that are all strings.
var n = 0
while (typeof pattern[n] === "string") {
n ++
}
// now n is the index of the first one that is *not* a string.
// see if there's anything else
var prefix
switch (n) {
// if not, then this is rather simple
case pattern.length:
prefix = pattern.join("/")
this._stat(prefix, function (exists, isDir) {
// either it's there, or it isn't.
// nothing more to do, either way.
if (exists) {
if (prefix && isAbsolute(prefix) && !this.nomount) {
if (prefix.charAt(0) === "/") {
prefix = path.join(this.root, prefix)
} else {
prefix = path.resolve(this.root, prefix)
}
}
if (process.platform === "win32")
prefix = prefix.replace(/\\/g, "/")
this.matches[index] = this.matches[index] || {}
this.matches[index][prefix] = true
this.emitMatch(prefix)
}
return cb()
})
return
case 0:
// pattern *starts* with some non-trivial item.
// going to readdir(cwd), but not include the prefix in matches.
prefix = null
break
default:
// pattern has some string bits in the front.
// whatever it starts with, whether that's "absolute" like /foo/bar,
// or "relative" like "../baz"
prefix = pattern.slice(0, n)
prefix = prefix.join("/")
break
}
// get the list of entries.
var read
if (prefix === null) read = "."
else if (isAbsolute(prefix) || isAbsolute(pattern.join("/"))) {
if (!prefix || !isAbsolute(prefix)) {
prefix = path.join("/", prefix)
}
read = prefix = path.resolve(prefix)
// if (process.platform === "win32")
// read = prefix = prefix.replace(/^[a-zA-Z]:|\\/g, "/")
this.log('absolute: ', prefix, this.root, pattern, read)
} else {
read = prefix
}
this.log('readdir(%j)', read, this.cwd, this.root)
return this._readdir(read, function (er, entries) {
if (er) {
// not a directory!
// this means that, whatever else comes after this, it can never match
return cb()
}
// globstar is special
if (pattern[n] === minimatch.GLOBSTAR) {
// test without the globstar, and with every child both below
// and replacing the globstar.
var s = [ pattern.slice(0, n).concat(pattern.slice(n + 1)) ]
entries.forEach(function (e) {
if (e.charAt(0) === "." && !this.dot) return
// instead of the globstar
s.push(pattern.slice(0, n).concat(e).concat(pattern.slice(n + 1)))
// below the globstar
s.push(pattern.slice(0, n).concat(e).concat(pattern.slice(n)))
}, this)
// now asyncForEach over this
var l = s.length
, errState = null
s.forEach(function (gsPattern) {
this._process(gsPattern, depth + 1, index, function (er) {
if (errState) return
if (er) return cb(errState = er)
if (--l <= 0) return cb()
})
}, this)
return
}
// not a globstar
// It will only match dot entries if it starts with a dot, or if
// dot is set. Stuff like @(.foo|.bar) isn't allowed.
var pn = pattern[n]
if (typeof pn === "string") {
var found = entries.indexOf(pn) !== -1
entries = found ? entries[pn] : []
} else {
var rawGlob = pattern[n]._glob
, dotOk = this.dot || rawGlob.charAt(0) === "."
entries = entries.filter(function (e) {
return (e.charAt(0) !== "." || dotOk) &&
(typeof pattern[n] === "string" && e === pattern[n] ||
e.match(pattern[n]))
})
}
// If n === pattern.length - 1, then there's no need for the extra stat
// *unless* the user has specified "mark" or "stat" explicitly.
// We know that they exist, since the readdir returned them.
if (n === pattern.length - 1 &&
!this.mark &&
!this.stat) {
entries.forEach(function (e) {
if (prefix) {
if (prefix !== "/") e = prefix + "/" + e
else e = prefix + e
}
if (e.charAt(0) === "/" && !this.nomount) {
e = path.join(this.root, e)
}
if (process.platform === "win32")
e = e.replace(/\\/g, "/")
this.matches[index] = this.matches[index] || {}
this.matches[index][e] = true
this.emitMatch(e)
}, this)
return cb.call(this)
}
// now test all the remaining entries as stand-ins for that part
// of the pattern.
var l = entries.length
, errState = null
if (l === 0) return cb() // no matches possible
entries.forEach(function (e) {
var p = pattern.slice(0, n).concat(e).concat(pattern.slice(n + 1))
this._process(p, depth + 1, index, function (er) {
if (errState) return
if (er) return cb(errState = er)
if (--l === 0) return cb.call(this)
})
}, this)
})
}
Glob.prototype._stat = function (f, cb) {
assert(this instanceof Glob)
var abs = f
if (f.charAt(0) === "/") {
abs = path.join(this.root, f)
} else if (this.changedCwd) {
abs = path.resolve(this.cwd, f)
}
this.log('stat', [this.cwd, f, '=', abs])
if (f.length > this.maxLength) {
var er = new Error("Path name too long")
er.code = "ENAMETOOLONG"
er.path = f
return this._afterStat(f, abs, cb, er)
}
if (this.statCache.hasOwnProperty(f)) {
var exists = this.statCache[f]
, isDir = exists && (Array.isArray(exists) || exists === 2)
if (this.sync) return cb.call(this, !!exists, isDir)
return process.nextTick(cb.bind(this, !!exists, isDir))
}
if (this.sync) {
var er, stat
try {
stat = fs.statSync(abs)
} catch (e) {
er = e
}
this._afterStat(f, abs, cb, er, stat)
} else {
fs.stat(abs, this._afterStat.bind(this, f, abs, cb))
}
}
Glob.prototype._afterStat = function (f, abs, cb, er, stat) {
var exists
assert(this instanceof Glob)
if (abs.slice(-1) === "/" && stat && !stat.isDirectory()) {
this.log("should be ENOTDIR, fake it")
er = new Error("ENOTDIR, not a directory '" + abs + "'")
er.path = abs
er.code = "ENOTDIR"
stat = null
}
if (er || !stat) {
exists = false
} else {
exists = stat.isDirectory() ? 2 : 1
}
this.statCache[f] = this.statCache[f] || exists
cb.call(this, !!exists, exists === 2)
}
Glob.prototype._readdir = function (f, cb) {
assert(this instanceof Glob)
var abs = f
if (f.charAt(0) === "/") {
abs = path.join(this.root, f)
} else if (isAbsolute(f)) {
abs = f
} else if (this.changedCwd) {
abs = path.resolve(this.cwd, f)
}
this.log('readdir', [this.cwd, f, abs])
if (f.length > this.maxLength) {
var er = new Error("Path name too long")
er.code = "ENAMETOOLONG"
er.path = f
return this._afterReaddir(f, abs, cb, er)
}
if (this.statCache.hasOwnProperty(f)) {
var c = this.statCache[f]
if (Array.isArray(c)) {
if (this.sync) return cb.call(this, null, c)
return process.nextTick(cb.bind(this, null, c))
}
if (!c || c === 1) {
// either ENOENT or ENOTDIR
var code = c ? "ENOTDIR" : "ENOENT"
, er = new Error((c ? "Not a directory" : "Not found") + ": " + f)
er.path = f
er.code = code
this.log(f, er)
if (this.sync) return cb.call(this, er)
return process.nextTick(cb.bind(this, er))
}
// at this point, c === 2, meaning it's a dir, but we haven't
// had to read it yet, or c === true, meaning it's *something*
// but we don't have any idea what. Need to read it, either way.
}
if (this.sync) {
var er, entries
try {
entries = fs.readdirSync(abs)
} catch (e) {
er = e
}
return this._afterReaddir(f, abs, cb, er, entries)
}
fs.readdir(abs, this._afterReaddir.bind(this, f, abs, cb))
}
Glob.prototype._afterReaddir = function (f, abs, cb, er, entries) {
assert(this instanceof Glob)
if (entries && !er) {
this.statCache[f] = entries
// if we haven't asked to stat everything for suresies, then just
// assume that everything in there exists, so we can avoid
// having to stat it a second time. This also gets us one step
// further into ELOOP territory.
if (!this.mark && !this.stat) {
entries.forEach(function (e) {
if (f === "/") e = f + e
else e = f + "/" + e
this.statCache[e] = true
}, this)
}
return cb.call(this, er, entries)
}
// now handle errors, and cache the information
if (er) switch (er.code) {
case "ENOTDIR": // totally normal. means it *does* exist.
this.statCache[f] = 1
return cb.call(this, er)
case "ENOENT": // not terribly unusual
case "ELOOP":
case "ENAMETOOLONG":
case "UNKNOWN":
this.statCache[f] = false
return cb.call(this, er)
default: // some unusual error. Treat as failure.
this.statCache[f] = false
if (this.strict) this.emit("error", er)
if (!this.silent) console.error("glob error", er)
return cb.call(this, er)
}
}
var isAbsolute = process.platform === "win32" ? absWin : absUnix
function absWin (p) {
if (absUnix(p)) return true
// pull off the device/UNC bit from a windows path.
// from node's lib/path.js
var splitDeviceRe =
/^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/
, result = splitDeviceRe.exec(p)
, device = result[1] || ''
, isUnc = device && device.charAt(1) !== ':'
, isAbsolute = !!result[2] || isUnc // UNC paths are always absolute
return isAbsolute
}
function absUnix (p) {
return p.charAt(0) === "/" || p === ""
}
@@ -0,0 +1 @@
node_modules/
@@ -0,0 +1,27 @@
Copyright (c) Isaac Z. Schlueter ("Author")
All rights reserved.
The BSD License
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,33 @@
# graceful-fs
graceful-fs functions as a drop-in replacement for the fs module,
making various improvements.
The improvements are meant to normalize behavior across different
platforms and environments, and to make filesystem access more
resilient to errors.
## Improvements over fs module
graceful-fs:
* keeps track of how many file descriptors are open, and by default
limits this to 1024. Any further requests to open a file are put in a
queue until new slots become available. If 1024 turns out to be too
much, it decreases the limit further.
* fixes `lchmod` for Node versions prior to 0.6.2.
* implements `fs.lutimes` if possible. Otherwise it becomes a noop.
* ignores `EINVAL` and `EPERM` errors in `chown`, `fchown` or
`lchown` if the user isn't root.
* makes `lchmod` and `lchown` become noops, if not available.
* retries reading a file if `read` results in EAGAIN error.
On Windows, it retries renaming a file for up to one second if `EACCESS`
or `EPERM` error occurs, likely because antivirus software has locked
the directory.
## Configuration
The maximum number of open file descriptors that graceful-fs manages may
be adjusted by setting `fs.MAX_OPEN` to a different number. The default
is 1024.
@@ -0,0 +1,442 @@
// this keeps a queue of opened file descriptors, and will make
// fs operations wait until some have closed before trying to open more.
var fs = exports = module.exports = {}
fs._originalFs = require("fs")
Object.getOwnPropertyNames(fs._originalFs).forEach(function(prop) {
var desc = Object.getOwnPropertyDescriptor(fs._originalFs, prop)
Object.defineProperty(fs, prop, desc)
})
var queue = []
, constants = require("constants")
fs._curOpen = 0
fs.MIN_MAX_OPEN = 64
fs.MAX_OPEN = 1024
// prevent EMFILE errors
function OpenReq (path, flags, mode, cb) {
this.path = path
this.flags = flags
this.mode = mode
this.cb = cb
}
function noop () {}
fs.open = gracefulOpen
function gracefulOpen (path, flags, mode, cb) {
if (typeof mode === "function") cb = mode, mode = null
if (typeof cb !== "function") cb = noop
if (fs._curOpen >= fs.MAX_OPEN) {
queue.push(new OpenReq(path, flags, mode, cb))
setTimeout(flush)
return
}
open(path, flags, mode, function (er, fd) {
if (er && er.code === "EMFILE" && fs._curOpen > fs.MIN_MAX_OPEN) {
// that was too many. reduce max, get back in queue.
// this should only happen once in a great while, and only
// if the ulimit -n is set lower than 1024.
fs.MAX_OPEN = fs._curOpen - 1
return fs.open(path, flags, mode, cb)
}
cb(er, fd)
})
}
function open (path, flags, mode, cb) {
cb = cb || noop
fs._curOpen ++
fs._originalFs.open.call(fs, path, flags, mode, function (er, fd) {
if (er) onclose()
cb(er, fd)
})
}
fs.openSync = function (path, flags, mode) {
var ret
ret = fs._originalFs.openSync.call(fs, path, flags, mode)
fs._curOpen ++
return ret
}
function onclose () {
fs._curOpen --
flush()
}
function flush () {
while (fs._curOpen < fs.MAX_OPEN) {
var req = queue.shift()
if (!req) return
switch (req.constructor.name) {
case 'OpenReq':
open(req.path, req.flags || "r", req.mode || 0777, req.cb)
break
case 'ReaddirReq':
readdir(req.path, req.cb)
break
case 'ReadFileReq':
readFile(req.path, req.options, req.cb)
break
case 'WriteFileReq':
writeFile(req.path, req.data, req.options, req.cb)
break
default:
throw new Error('Unknown req type: ' + req.constructor.name)
}
}
}
fs.close = function (fd, cb) {
cb = cb || noop
fs._originalFs.close.call(fs, fd, function (er) {
onclose()
cb(er)
})
}
fs.closeSync = function (fd) {
try {
return fs._originalFs.closeSync.call(fs, fd)
} finally {
onclose()
}
}
// readdir takes a fd as well.
// however, the sync version closes it right away, so
// there's no need to wrap.
// It would be nice to catch when it throws an EMFILE,
// but that's relatively rare anyway.
fs.readdir = gracefulReaddir
function gracefulReaddir (path, cb) {
if (fs._curOpen >= fs.MAX_OPEN) {
queue.push(new ReaddirReq(path, cb))
setTimeout(flush)
return
}
readdir(path, function (er, files) {
if (er && er.code === "EMFILE" && fs._curOpen > fs.MIN_MAX_OPEN) {
fs.MAX_OPEN = fs._curOpen - 1
return fs.readdir(path, cb)
}
cb(er, files)
})
}
function readdir (path, cb) {
cb = cb || noop
fs._curOpen ++
fs._originalFs.readdir.call(fs, path, function (er, files) {
onclose()
cb(er, files)
})
}
function ReaddirReq (path, cb) {
this.path = path
this.cb = cb
}
fs.readFile = gracefulReadFile
function gracefulReadFile(path, options, cb) {
if (typeof options === "function") cb = options, options = null
if (typeof cb !== "function") cb = noop
if (fs._curOpen >= fs.MAX_OPEN) {
queue.push(new ReadFileReq(path, options, cb))
setTimeout(flush)
return
}
readFile(path, options, function (er, data) {
if (er && er.code === "EMFILE" && fs._curOpen > fs.MIN_MAX_OPEN) {
fs.MAX_OPEN = fs._curOpen - 1
return fs.readFile(path, options, cb)
}
cb(er, data)
})
}
function readFile (path, options, cb) {
cb = cb || noop
fs._curOpen ++
fs._originalFs.readFile.call(fs, path, options, function (er, data) {
onclose()
cb(er, data)
})
}
function ReadFileReq (path, options, cb) {
this.path = path
this.options = options
this.cb = cb
}
fs.writeFile = gracefulWriteFile
function gracefulWriteFile(path, data, options, cb) {
if (typeof options === "function") cb = options, options = null
if (typeof cb !== "function") cb = noop
if (fs._curOpen >= fs.MAX_OPEN) {
queue.push(new WriteFileReq(path, data, options, cb))
setTimeout(flush)
return
}
writeFile(path, data, options, function (er) {
if (er && er.code === "EMFILE" && fs._curOpen > fs.MIN_MAX_OPEN) {
fs.MAX_OPEN = fs._curOpen - 1
return fs.writeFile(path, data, options, cb)
}
cb(er)
})
}
function writeFile (path, data, options, cb) {
cb = cb || noop
fs._curOpen ++
fs._originalFs.writeFile.call(fs, path, data, options, function (er) {
onclose()
cb(er)
})
}
function WriteFileReq (path, data, options, cb) {
this.path = path
this.data = data
this.options = options
this.cb = cb
}
// (re-)implement some things that are known busted or missing.
var constants = require("constants")
// lchmod, broken prior to 0.6.2
// back-port the fix here.
if (constants.hasOwnProperty('O_SYMLINK') &&
process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
fs.lchmod = function (path, mode, callback) {
callback = callback || noop
fs.open( path
, constants.O_WRONLY | constants.O_SYMLINK
, mode
, function (err, fd) {
if (err) {
callback(err)
return
}
// prefer to return the chmod error, if one occurs,
// but still try to close, and report closing errors if they occur.
fs.fchmod(fd, mode, function (err) {
fs.close(fd, function(err2) {
callback(err || err2)
})
})
})
}
fs.lchmodSync = function (path, mode) {
var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode)
// prefer to return the chmod error, if one occurs,
// but still try to close, and report closing errors if they occur.
var err, err2
try {
var ret = fs.fchmodSync(fd, mode)
} catch (er) {
err = er
}
try {
fs.closeSync(fd)
} catch (er) {
err2 = er
}
if (err || err2) throw (err || err2)
return ret
}
}
// lutimes implementation, or no-op
if (!fs.lutimes) {
if (constants.hasOwnProperty("O_SYMLINK")) {
fs.lutimes = function (path, at, mt, cb) {
fs.open(path, constants.O_SYMLINK, function (er, fd) {
cb = cb || noop
if (er) return cb(er)
fs.futimes(fd, at, mt, function (er) {
fs.close(fd, function (er2) {
return cb(er || er2)
})
})
})
}
fs.lutimesSync = function (path, at, mt) {
var fd = fs.openSync(path, constants.O_SYMLINK)
, err
, err2
, ret
try {
var ret = fs.futimesSync(fd, at, mt)
} catch (er) {
err = er
}
try {
fs.closeSync(fd)
} catch (er) {
err2 = er
}
if (err || err2) throw (err || err2)
return ret
}
} else if (fs.utimensat && constants.hasOwnProperty("AT_SYMLINK_NOFOLLOW")) {
// maybe utimensat will be bound soonish?
fs.lutimes = function (path, at, mt, cb) {
fs.utimensat(path, at, mt, constants.AT_SYMLINK_NOFOLLOW, cb)
}
fs.lutimesSync = function (path, at, mt) {
return fs.utimensatSync(path, at, mt, constants.AT_SYMLINK_NOFOLLOW)
}
} else {
fs.lutimes = function (_a, _b, _c, cb) { process.nextTick(cb) }
fs.lutimesSync = function () {}
}
}
// https://github.com/isaacs/node-graceful-fs/issues/4
// Chown should not fail on einval or eperm if non-root.
fs.chown = chownFix(fs.chown)
fs.fchown = chownFix(fs.fchown)
fs.lchown = chownFix(fs.lchown)
fs.chownSync = chownFixSync(fs.chownSync)
fs.fchownSync = chownFixSync(fs.fchownSync)
fs.lchownSync = chownFixSync(fs.lchownSync)
function chownFix (orig) {
if (!orig) return orig
return function (target, uid, gid, cb) {
return orig.call(fs, target, uid, gid, function (er, res) {
if (chownErOk(er)) er = null
cb(er, res)
})
}
}
function chownFixSync (orig) {
if (!orig) return orig
return function (target, uid, gid) {
try {
return orig.call(fs, target, uid, gid)
} catch (er) {
if (!chownErOk(er)) throw er
}
}
}
function chownErOk (er) {
// if there's no getuid, or if getuid() is something other than 0,
// and the error is EINVAL or EPERM, then just ignore it.
// This specific case is a silent failure in cp, install, tar,
// and most other unix tools that manage permissions.
// When running as root, or if other types of errors are encountered,
// then it's strict.
if (!er || (!process.getuid || process.getuid() !== 0)
&& (er.code === "EINVAL" || er.code === "EPERM")) return true
}
// if lchmod/lchown do not exist, then make them no-ops
if (!fs.lchmod) {
fs.lchmod = function (path, mode, cb) {
process.nextTick(cb)
}
fs.lchmodSync = function () {}
}
if (!fs.lchown) {
fs.lchown = function (path, uid, gid, cb) {
process.nextTick(cb)
}
fs.lchownSync = function () {}
}
// on Windows, A/V software can lock the directory, causing this
// to fail with an EACCES or EPERM if the directory contains newly
// created files. Try again on failure, for up to 1 second.
if (process.platform === "win32") {
var rename_ = fs.rename
fs.rename = function rename (from, to, cb) {
var start = Date.now()
rename_(from, to, function CB (er) {
if (er
&& (er.code === "EACCES" || er.code === "EPERM")
&& Date.now() - start < 1000) {
return rename_(from, to, CB)
}
cb(er)
})
}
}
// if read() returns EAGAIN, then just try it again.
var read = fs.read
fs.read = function (fd, buffer, offset, length, position, callback_) {
var callback
if (callback_ && typeof callback_ === 'function') {
var eagCounter = 0
callback = function (er, _, __) {
if (er && er.code === 'EAGAIN' && eagCounter < 10) {
eagCounter ++
return read.call(fs, fd, buffer, offset, length, position, callback)
}
callback_.apply(this, arguments)
}
}
return read.call(fs, fd, buffer, offset, length, position, callback)
}
var readSync = fs.readSync
fs.readSync = function (fd, buffer, offset, length, position) {
var eagCounter = 0
while (true) {
try {
return readSync.call(fs, fd, buffer, offset, length, position)
} catch (er) {
if (er.code === 'EAGAIN' && eagCounter < 10) {
eagCounter ++
continue
}
throw er
}
}
}
@@ -0,0 +1,49 @@
{
"author": {
"name": "Isaac Z. Schlueter",
"email": "i@izs.me",
"url": "http://blog.izs.me"
},
"name": "graceful-fs",
"description": "A drop-in replacement for fs, making various improvements.",
"version": "1.2.3",
"repository": {
"type": "git",
"url": "git://github.com/isaacs/node-graceful-fs.git"
},
"main": "graceful-fs.js",
"engines": {
"node": ">=0.4.0"
},
"directories": {
"test": "test"
},
"scripts": {
"test": "tap test/*.js"
},
"keywords": [
"fs",
"module",
"reading",
"retry",
"retries",
"queue",
"error",
"errors",
"handling",
"EMFILE",
"EAGAIN",
"EINVAL",
"EPERM",
"EACCESS"
],
"license": "BSD",
"readme": "# graceful-fs\n\ngraceful-fs functions as a drop-in replacement for the fs module,\nmaking various improvements.\n\nThe improvements are meant to normalize behavior across different\nplatforms and environments, and to make filesystem access more\nresilient to errors.\n\n## Improvements over fs module\n\ngraceful-fs:\n\n* keeps track of how many file descriptors are open, and by default\n limits this to 1024. Any further requests to open a file are put in a\n queue until new slots become available. If 1024 turns out to be too\n much, it decreases the limit further.\n* fixes `lchmod` for Node versions prior to 0.6.2.\n* implements `fs.lutimes` if possible. Otherwise it becomes a noop.\n* ignores `EINVAL` and `EPERM` errors in `chown`, `fchown` or\n `lchown` if the user isn't root.\n* makes `lchmod` and `lchown` become noops, if not available.\n* retries reading a file if `read` results in EAGAIN error.\n\nOn Windows, it retries renaming a file for up to one second if `EACCESS`\nor `EPERM` error occurs, likely because antivirus software has locked\nthe directory.\n\n## Configuration\n\nThe maximum number of open file descriptors that graceful-fs manages may\nbe adjusted by setting `fs.MAX_OPEN` to a different number. The default\nis 1024.\n",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/isaacs/node-graceful-fs/issues"
},
"homepage": "https://github.com/isaacs/node-graceful-fs",
"_id": "graceful-fs@1.2.3",
"_from": "graceful-fs@~1.2.0"
}
@@ -0,0 +1,46 @@
var test = require('tap').test
var fs = require('../graceful-fs.js')
test('graceful fs is not fs', function (t) {
t.notEqual(fs, require('fs'))
t.end()
})
test('open an existing file works', function (t) {
var start = fs._curOpen
var fd = fs.openSync(__filename, 'r')
t.equal(fs._curOpen, start + 1)
fs.closeSync(fd)
t.equal(fs._curOpen, start)
fs.open(__filename, 'r', function (er, fd) {
if (er) throw er
t.equal(fs._curOpen, start + 1)
fs.close(fd, function (er) {
if (er) throw er
t.equal(fs._curOpen, start)
t.end()
})
})
})
test('open a non-existing file throws', function (t) {
var start = fs._curOpen
var er
try {
var fd = fs.openSync('this file does not exist', 'r')
} catch (x) {
er = x
}
t.ok(er, 'should throw')
t.notOk(fd, 'should not get an fd')
t.equal(er.code, 'ENOENT')
t.equal(fs._curOpen, start)
fs.open('neither does this file', 'r', function (er, fd) {
t.ok(er, 'should throw')
t.notOk(fd, 'should not get an fd')
t.equal(er.code, 'ENOENT')
t.equal(fs._curOpen, start)
t.end()
})
})
@@ -0,0 +1,158 @@
var test = require('tap').test
// simulated ulimit
// this is like graceful-fs, but in reverse
var fs_ = require('fs')
var fs = require('../graceful-fs.js')
var files = fs.readdirSync(__dirname)
// Ok, no more actual file reading!
var fds = 0
var nextFd = 60
var limit = 8
fs_.open = function (path, flags, mode, cb) {
process.nextTick(function() {
++fds
if (fds >= limit) {
--fds
var er = new Error('EMFILE Curses!')
er.code = 'EMFILE'
er.path = path
return cb(er)
} else {
cb(null, nextFd++)
}
})
}
fs_.openSync = function (path, flags, mode) {
if (fds >= limit) {
var er = new Error('EMFILE Curses!')
er.code = 'EMFILE'
er.path = path
throw er
} else {
++fds
return nextFd++
}
}
fs_.close = function (fd, cb) {
process.nextTick(function () {
--fds
cb()
})
}
fs_.closeSync = function (fd) {
--fds
}
fs_.readdir = function (path, cb) {
process.nextTick(function() {
if (fds >= limit) {
var er = new Error('EMFILE Curses!')
er.code = 'EMFILE'
er.path = path
return cb(er)
} else {
++fds
process.nextTick(function () {
--fds
cb(null, [__filename, "some-other-file.js"])
})
}
})
}
fs_.readdirSync = function (path) {
if (fds >= limit) {
var er = new Error('EMFILE Curses!')
er.code = 'EMFILE'
er.path = path
throw er
} else {
return [__filename, "some-other-file.js"]
}
}
test('open emfile autoreduce', function (t) {
fs.MIN_MAX_OPEN = 4
t.equal(fs.MAX_OPEN, 1024)
var max = 12
for (var i = 0; i < max; i++) {
fs.open(__filename, 'r', next(i))
}
var phase = 0
var expect =
[ [ 0, 60, null, 1024, 4, 12, 1 ],
[ 1, 61, null, 1024, 4, 12, 2 ],
[ 2, 62, null, 1024, 4, 12, 3 ],
[ 3, 63, null, 1024, 4, 12, 4 ],
[ 4, 64, null, 1024, 4, 12, 5 ],
[ 5, 65, null, 1024, 4, 12, 6 ],
[ 6, 66, null, 1024, 4, 12, 7 ],
[ 7, 67, null, 6, 4, 5, 1 ],
[ 8, 68, null, 6, 4, 5, 2 ],
[ 9, 69, null, 6, 4, 5, 3 ],
[ 10, 70, null, 6, 4, 5, 4 ],
[ 11, 71, null, 6, 4, 5, 5 ] ]
var actual = []
function next (i) { return function (er, fd) {
if (er)
throw er
actual.push([i, fd, er, fs.MAX_OPEN, fs.MIN_MAX_OPEN, fs._curOpen, fds])
if (i === max - 1) {
t.same(actual, expect)
t.ok(fs.MAX_OPEN < limit)
t.end()
}
fs.close(fd)
} }
})
test('readdir emfile autoreduce', function (t) {
fs.MAX_OPEN = 1024
var max = 12
for (var i = 0; i < max; i ++) {
fs.readdir(__dirname, next(i))
}
var expect =
[ [0,[__filename,"some-other-file.js"],null,7,4,7,7],
[1,[__filename,"some-other-file.js"],null,7,4,7,6],
[2,[__filename,"some-other-file.js"],null,7,4,7,5],
[3,[__filename,"some-other-file.js"],null,7,4,7,4],
[4,[__filename,"some-other-file.js"],null,7,4,7,3],
[5,[__filename,"some-other-file.js"],null,7,4,6,2],
[6,[__filename,"some-other-file.js"],null,7,4,5,1],
[7,[__filename,"some-other-file.js"],null,7,4,4,0],
[8,[__filename,"some-other-file.js"],null,7,4,3,3],
[9,[__filename,"some-other-file.js"],null,7,4,2,2],
[10,[__filename,"some-other-file.js"],null,7,4,1,1],
[11,[__filename,"some-other-file.js"],null,7,4,0,0] ]
var actual = []
function next (i) { return function (er, files) {
if (er)
throw er
var line = [i, files, er, fs.MAX_OPEN, fs.MIN_MAX_OPEN, fs._curOpen, fds ]
actual.push(line)
if (i === max - 1) {
t.ok(fs.MAX_OPEN < limit)
t.same(actual, expect)
t.end()
}
} }
})
@@ -0,0 +1,51 @@
A dead simple way to do inheritance in JS.
var inherits = require("inherits")
function Animal () {
this.alive = true
}
Animal.prototype.say = function (what) {
console.log(what)
}
inherits(Dog, Animal)
function Dog () {
Dog.super.apply(this)
}
Dog.prototype.sniff = function () {
this.say("sniff sniff")
}
Dog.prototype.bark = function () {
this.say("woof woof")
}
inherits(Chihuahua, Dog)
function Chihuahua () {
Chihuahua.super.apply(this)
}
Chihuahua.prototype.bark = function () {
this.say("yip yip")
}
// also works
function Cat () {
Cat.super.apply(this)
}
Cat.prototype.hiss = function () {
this.say("CHSKKSS!!")
}
inherits(Cat, Animal, {
meow: function () { this.say("miao miao") }
})
Cat.prototype.purr = function () {
this.say("purr purr")
}
var c = new Chihuahua
assert(c instanceof Chihuahua)
assert(c instanceof Dog)
assert(c instanceof Animal)
The actual function is laughably small. 10-lines small.
@@ -0,0 +1,29 @@
module.exports = inherits
function inherits (c, p, proto) {
proto = proto || {}
var e = {}
;[c.prototype, proto].forEach(function (s) {
Object.getOwnPropertyNames(s).forEach(function (k) {
e[k] = Object.getOwnPropertyDescriptor(s, k)
})
})
c.prototype = Object.create(p.prototype, e)
c.super = p
}
//function Child () {
// Child.super.call(this)
// console.error([this
// ,this.constructor
// ,this.constructor === Child
// ,this.constructor.super === Parent
// ,Object.getPrototypeOf(this) === Child.prototype
// ,Object.getPrototypeOf(Object.getPrototypeOf(this))
// === Parent.prototype
// ,this instanceof Child
// ,this instanceof Parent])
//}
//function Parent () {}
//inherits(Child, Parent)
//new Child
@@ -0,0 +1,30 @@
{
"name": "inherits",
"description": "A tiny simple way to do classic inheritance in js",
"version": "1.0.0",
"keywords": [
"inheritance",
"class",
"klass",
"oop",
"object-oriented"
],
"main": "./inherits.js",
"repository": {
"type": "git",
"url": "https://github.com/isaacs/inherits"
},
"author": {
"name": "Isaac Z. Schlueter",
"email": "i@izs.me",
"url": "http://blog.izs.me/"
},
"readme": "A dead simple way to do inheritance in JS.\n\n var inherits = require(\"inherits\")\n\n function Animal () {\n this.alive = true\n }\n Animal.prototype.say = function (what) {\n console.log(what)\n }\n\n inherits(Dog, Animal)\n function Dog () {\n Dog.super.apply(this)\n }\n Dog.prototype.sniff = function () {\n this.say(\"sniff sniff\")\n }\n Dog.prototype.bark = function () {\n this.say(\"woof woof\")\n }\n\n inherits(Chihuahua, Dog)\n function Chihuahua () {\n Chihuahua.super.apply(this)\n }\n Chihuahua.prototype.bark = function () {\n this.say(\"yip yip\")\n }\n\n // also works\n function Cat () {\n Cat.super.apply(this)\n }\n Cat.prototype.hiss = function () {\n this.say(\"CHSKKSS!!\")\n }\n inherits(Cat, Animal, {\n meow: function () { this.say(\"miao miao\") }\n })\n Cat.prototype.purr = function () {\n this.say(\"purr purr\")\n }\n\n\n var c = new Chihuahua\n assert(c instanceof Chihuahua)\n assert(c instanceof Dog)\n assert(c instanceof Animal)\n\nThe actual function is laughably small. 10-lines small.\n",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/isaacs/inherits/issues"
},
"homepage": "https://github.com/isaacs/inherits",
"_id": "inherits@1.0.0",
"_from": "inherits@1"
}
@@ -0,0 +1 @@
node_modules
@@ -0,0 +1,23 @@
Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
All rights reserved.
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.
@@ -0,0 +1,218 @@
# minimatch
A minimal matching utility.
[![Build Status](https://secure.travis-ci.org/isaacs/minimatch.png)](http://travis-ci.org/isaacs/minimatch)
This is the matching library used internally by npm.
Eventually, it will replace the C binding in node-glob.
It works by converting glob expressions into JavaScript `RegExp`
objects.
## Usage
```javascript
var minimatch = require("minimatch")
minimatch("bar.foo", "*.foo") // true!
minimatch("bar.foo", "*.bar") // false!
minimatch("bar.foo", "*.+(bar|foo)", { debug: true }) // true, and noisy!
```
## Features
Supports these glob features:
* Brace Expansion
* Extended glob matching
* "Globstar" `**` matching
See:
* `man sh`
* `man bash`
* `man 3 fnmatch`
* `man 5 gitignore`
## Minimatch Class
Create a minimatch object by instanting the `minimatch.Minimatch` class.
```javascript
var Minimatch = require("minimatch").Minimatch
var mm = new Minimatch(pattern, options)
```
### Properties
* `pattern` The original pattern the minimatch object represents.
* `options` The options supplied to the constructor.
* `set` A 2-dimensional array of regexp or string expressions.
Each row in the
array corresponds to a brace-expanded pattern. Each item in the row
corresponds to a single path-part. For example, the pattern
`{a,b/c}/d` would expand to a set of patterns like:
[ [ a, d ]
, [ b, c, d ] ]
If a portion of the pattern doesn't have any "magic" in it
(that is, it's something like `"foo"` rather than `fo*o?`), then it
will be left as a string rather than converted to a regular
expression.
* `regexp` Created by the `makeRe` method. A single regular expression
expressing the entire pattern. This is useful in cases where you wish
to use the pattern somewhat like `fnmatch(3)` with `FNM_PATH` enabled.
* `negate` True if the pattern is negated.
* `comment` True if the pattern is a comment.
* `empty` True if the pattern is `""`.
### Methods
* `makeRe` Generate the `regexp` member if necessary, and return it.
Will return `false` if the pattern is invalid.
* `match(fname)` Return true if the filename matches the pattern, or
false otherwise.
* `matchOne(fileArray, patternArray, partial)` Take a `/`-split
filename, and match it against a single row in the `regExpSet`. This
method is mainly for internal use, but is exposed so that it can be
used by a glob-walker that needs to avoid excessive filesystem calls.
All other methods are internal, and will be called as necessary.
## Functions
The top-level exported function has a `cache` property, which is an LRU
cache set to store 100 items. So, calling these methods repeatedly
with the same pattern and options will use the same Minimatch object,
saving the cost of parsing it multiple times.
### minimatch(path, pattern, options)
Main export. Tests a path against the pattern using the options.
```javascript
var isJS = minimatch(file, "*.js", { matchBase: true })
```
### minimatch.filter(pattern, options)
Returns a function that tests its
supplied argument, suitable for use with `Array.filter`. Example:
```javascript
var javascripts = fileList.filter(minimatch.filter("*.js", {matchBase: true}))
```
### minimatch.match(list, pattern, options)
Match against the list of
files, in the style of fnmatch or glob. If nothing is matched, and
options.nonull is set, then return a list containing the pattern itself.
```javascript
var javascripts = minimatch.match(fileList, "*.js", {matchBase: true}))
```
### minimatch.makeRe(pattern, options)
Make a regular expression object from the pattern.
## Options
All options are `false` by default.
### debug
Dump a ton of stuff to stderr.
### nobrace
Do not expand `{a,b}` and `{1..3}` brace sets.
### noglobstar
Disable `**` matching against multiple folder names.
### dot
Allow patterns to match filenames starting with a period, even if
the pattern does not explicitly have a period in that spot.
Note that by default, `a/**/b` will **not** match `a/.d/b`, unless `dot`
is set.
### noext
Disable "extglob" style patterns like `+(a|b)`.
### nocase
Perform a case-insensitive match.
### nonull
When a match is not found by `minimatch.match`, return a list containing
the pattern itself. When set, an empty list is returned if there are
no matches.
### matchBase
If set, then patterns without slashes will be matched
against the basename of the path if it contains slashes. For example,
`a?b` would match the path `/xyz/123/acb`, but not `/xyz/acb/123`.
### nocomment
Suppress the behavior of treating `#` at the start of a pattern as a
comment.
### nonegate
Suppress the behavior of treating a leading `!` character as negation.
### flipNegate
Returns from negate expressions the same as if they were not negated.
(Ie, true on a hit, false on a miss.)
## Comparisons to other fnmatch/glob implementations
While strict compliance with the existing standards is a worthwhile
goal, some discrepancies exist between minimatch and other
implementations, and are intentional.
If the pattern starts with a `!` character, then it is negated. Set the
`nonegate` flag to suppress this behavior, and treat leading `!`
characters normally. This is perhaps relevant if you wish to start the
pattern with a negative extglob pattern like `!(a|B)`. Multiple `!`
characters at the start of a pattern will negate the pattern multiple
times.
If a pattern starts with `#`, then it is treated as a comment, and
will not match anything. Use `\#` to match a literal `#` at the
start of a line, or set the `nocomment` flag to suppress this behavior.
The double-star character `**` is supported by default, unless the
`noglobstar` flag is set. This is supported in the manner of bsdglob
and bash 4.1, where `**` only has special significance if it is the only
thing in a path part. That is, `a/**/b` will match `a/x/y/b`, but
`a/**b` will not.
If an escaped pattern has no matches, and the `nonull` flag is set,
then minimatch.match returns the pattern as-provided, rather than
interpreting the character escapes. For example,
`minimatch.match([], "\\*a\\?")` will return `"\\*a\\?"` rather than
`"*a?"`. This is akin to setting the `nullglob` option in bash, except
that it does not resolve escaped pattern characters.
If brace expansion is not disabled, then it is performed before any
other interpretation of the glob pattern. Thus, a pattern like
`+(a|{b),c)}`, which would not be valid in bash or zsh, is expanded
**first** into the set of `+(a|b)` and `+(a|c)`, and those patterns are
checked for validity. Since those two are valid, matching proceeds.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,14 @@
# Authors, sorted by whether or not they are me
Isaac Z. Schlueter <i@izs.me>
Brian Cottingham <spiffytech@gmail.com>
Carlos Brito Lage <carlos@carloslage.net>
Jesse Dailey <jesse.dailey@gmail.com>
Kevin O'Hara <kevinohara80@gmail.com>
Marco Rogers <marco.rogers@gmail.com>
Mark Cavage <mcavage@gmail.com>
Marko Mikulicic <marko.mikulicic@isti.cnr.it>
Nathan Rajlich <nathan@tootallnate.net>
Satheesh Natesan <snateshan@myspace-inc.com>
Trent Mick <trentm@gmail.com>
ashleybrener <ashley@starlogik.com>
n4kz <n4kz@n4kz.com>
@@ -0,0 +1,23 @@
Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
All rights reserved.
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.
@@ -0,0 +1,97 @@
# lru cache
A cache object that deletes the least-recently-used items.
## Usage:
```javascript
var LRU = require("lru-cache")
, options = { max: 500
, length: function (n) { return n * 2 }
, dispose: function (key, n) { n.close() }
, maxAge: 1000 * 60 * 60 }
, cache = LRU(options)
, otherCache = LRU(50) // sets just the max size
cache.set("key", "value")
cache.get("key") // "value"
cache.reset() // empty the cache
```
If you put more stuff in it, then items will fall out.
If you try to put an oversized thing in it, then it'll fall out right
away.
## Options
* `max` The maximum size of the cache, checked by applying the length
function to all values in the cache. Not setting this is kind of
silly, since that's the whole purpose of this lib, but it defaults
to `Infinity`.
* `maxAge` Maximum age in ms. Items are not pro-actively pruned out
as they age, but if you try to get an item that is too old, it'll
drop it and return undefined instead of giving it to you.
* `length` Function that is used to calculate the length of stored
items. If you're storing strings or buffers, then you probably want
to do something like `function(n){return n.length}`. The default is
`function(n){return 1}`, which is fine if you want to store `n`
like-sized things.
* `dispose` Function that is called on items when they are dropped
from the cache. This can be handy if you want to close file
descriptors or do other cleanup tasks when items are no longer
accessible. Called with `key, value`. It's called *before*
actually removing the item from the internal cache, so if you want
to immediately put it back in, you'll have to do that in a
`nextTick` or `setTimeout` callback or it won't do anything.
* `stale` By default, if you set a `maxAge`, it'll only actually pull
stale items out of the cache when you `get(key)`. (That is, it's
not pre-emptively doing a `setTimeout` or anything.) If you set
`stale:true`, it'll return the stale value before deleting it. If
you don't set this, then it'll return `undefined` when you try to
get a stale entry, as if it had already been deleted.
## API
* `set(key, value)`
* `get(key) => value`
Both of these will update the "recently used"-ness of the key.
They do what you think.
* `peek(key)`
Returns the key value (or `undefined` if not found) without
updating the "recently used"-ness of the key.
(If you find yourself using this a lot, you *might* be using the
wrong sort of data structure, but there are some use cases where
it's handy.)
* `del(key)`
Deletes a key out of the cache.
* `reset()`
Clear the cache entirely, throwing away all values.
* `has(key)`
Check if a key is in the cache, without updating the recent-ness
or deleting it for being stale.
* `forEach(function(value,key,cache), [thisp])`
Just like `Array.prototype.forEach`. Iterates over all the keys
in the cache, in order of recent-ness. (Ie, more recently used
items are iterated over first.)
* `keys()`
Return an array of the keys in the cache.
* `values()`
Return an array of the values in the cache.
@@ -0,0 +1,252 @@
;(function () { // closure for web browsers
if (typeof module === 'object' && module.exports) {
module.exports = LRUCache
} else {
// just set the global for non-node platforms.
this.LRUCache = LRUCache
}
function hOP (obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key)
}
function naiveLength () { return 1 }
function LRUCache (options) {
if (!(this instanceof LRUCache))
return new LRUCache(options)
if (typeof options === 'number')
options = { max: options }
if (!options)
options = {}
this._max = options.max
// Kind of weird to have a default max of Infinity, but oh well.
if (!this._max || !(typeof this._max === "number") || this._max <= 0 )
this._max = Infinity
this._lengthCalculator = options.length || naiveLength
if (typeof this._lengthCalculator !== "function")
this._lengthCalculator = naiveLength
this._allowStale = options.stale || false
this._maxAge = options.maxAge || null
this._dispose = options.dispose
this.reset()
}
// resize the cache when the max changes.
Object.defineProperty(LRUCache.prototype, "max",
{ set : function (mL) {
if (!mL || !(typeof mL === "number") || mL <= 0 ) mL = Infinity
this._max = mL
if (this._length > this._max) trim(this)
}
, get : function () { return this._max }
, enumerable : true
})
// resize the cache when the lengthCalculator changes.
Object.defineProperty(LRUCache.prototype, "lengthCalculator",
{ set : function (lC) {
if (typeof lC !== "function") {
this._lengthCalculator = naiveLength
this._length = this._itemCount
for (var key in this._cache) {
this._cache[key].length = 1
}
} else {
this._lengthCalculator = lC
this._length = 0
for (var key in this._cache) {
this._cache[key].length = this._lengthCalculator(this._cache[key].value)
this._length += this._cache[key].length
}
}
if (this._length > this._max) trim(this)
}
, get : function () { return this._lengthCalculator }
, enumerable : true
})
Object.defineProperty(LRUCache.prototype, "length",
{ get : function () { return this._length }
, enumerable : true
})
Object.defineProperty(LRUCache.prototype, "itemCount",
{ get : function () { return this._itemCount }
, enumerable : true
})
LRUCache.prototype.forEach = function (fn, thisp) {
thisp = thisp || this
var i = 0;
for (var k = this._mru - 1; k >= 0 && i < this._itemCount; k--) if (this._lruList[k]) {
i++
var hit = this._lruList[k]
if (this._maxAge && (Date.now() - hit.now > this._maxAge)) {
del(this, hit)
if (!this._allowStale) hit = undefined
}
if (hit) {
fn.call(thisp, hit.value, hit.key, this)
}
}
}
LRUCache.prototype.keys = function () {
var keys = new Array(this._itemCount)
var i = 0
for (var k = this._mru - 1; k >= 0 && i < this._itemCount; k--) if (this._lruList[k]) {
var hit = this._lruList[k]
keys[i++] = hit.key
}
return keys
}
LRUCache.prototype.values = function () {
var values = new Array(this._itemCount)
var i = 0
for (var k = this._mru - 1; k >= 0 && i < this._itemCount; k--) if (this._lruList[k]) {
var hit = this._lruList[k]
values[i++] = hit.value
}
return values
}
LRUCache.prototype.reset = function () {
if (this._dispose && this._cache) {
for (var k in this._cache) {
this._dispose(k, this._cache[k].value)
}
}
this._cache = Object.create(null) // hash of items by key
this._lruList = Object.create(null) // list of items in order of use recency
this._mru = 0 // most recently used
this._lru = 0 // least recently used
this._length = 0 // number of items in the list
this._itemCount = 0
}
// Provided for debugging/dev purposes only. No promises whatsoever that
// this API stays stable.
LRUCache.prototype.dump = function () {
return this._cache
}
LRUCache.prototype.dumpLru = function () {
return this._lruList
}
LRUCache.prototype.set = function (key, value) {
if (hOP(this._cache, key)) {
// dispose of the old one before overwriting
if (this._dispose) this._dispose(key, this._cache[key].value)
if (this._maxAge) this._cache[key].now = Date.now()
this._cache[key].value = value
this.get(key)
return true
}
var len = this._lengthCalculator(value)
var age = this._maxAge ? Date.now() : 0
var hit = new Entry(key, value, this._mru++, len, age)
// oversized objects fall out of cache automatically.
if (hit.length > this._max) {
if (this._dispose) this._dispose(key, value)
return false
}
this._length += hit.length
this._lruList[hit.lu] = this._cache[key] = hit
this._itemCount ++
if (this._length > this._max) trim(this)
return true
}
LRUCache.prototype.has = function (key) {
if (!hOP(this._cache, key)) return false
var hit = this._cache[key]
if (this._maxAge && (Date.now() - hit.now > this._maxAge)) {
return false
}
return true
}
LRUCache.prototype.get = function (key) {
return get(this, key, true)
}
LRUCache.prototype.peek = function (key) {
return get(this, key, false)
}
LRUCache.prototype.pop = function () {
var hit = this._lruList[this._lru]
del(this, hit)
return hit || null
}
LRUCache.prototype.del = function (key) {
del(this, this._cache[key])
}
function get (self, key, doUse) {
var hit = self._cache[key]
if (hit) {
if (self._maxAge && (Date.now() - hit.now > self._maxAge)) {
del(self, hit)
if (!self._allowStale) hit = undefined
} else {
if (doUse) use(self, hit)
}
if (hit) hit = hit.value
}
return hit
}
function use (self, hit) {
shiftLU(self, hit)
hit.lu = self._mru ++
self._lruList[hit.lu] = hit
}
function trim (self) {
while (self._lru < self._mru && self._length > self._max)
del(self, self._lruList[self._lru])
}
function shiftLU (self, hit) {
delete self._lruList[ hit.lu ]
while (self._lru < self._mru && !self._lruList[self._lru]) self._lru ++
}
function del (self, hit) {
if (hit) {
if (self._dispose) self._dispose(hit.key, hit.value)
self._length -= hit.length
self._itemCount --
delete self._cache[ hit.key ]
shiftLU(self, hit)
}
}
// classy, since V8 prefers predictable objects.
function Entry (key, value, lu, length, now) {
this.key = key
this.value = value
this.lu = lu
this.length = length
this.now = now
}
})()
@@ -0,0 +1,33 @@
{
"name": "lru-cache",
"description": "A cache object that deletes the least-recently-used items.",
"version": "2.5.0",
"author": {
"name": "Isaac Z. Schlueter",
"email": "i@izs.me"
},
"scripts": {
"test": "tap test --gc"
},
"main": "lib/lru-cache.js",
"repository": {
"type": "git",
"url": "git://github.com/isaacs/node-lru-cache.git"
},
"devDependencies": {
"tap": "",
"weak": ""
},
"license": {
"type": "MIT",
"url": "http://github.com/isaacs/node-lru-cache/raw/master/LICENSE"
},
"readme": "# lru cache\n\nA cache object that deletes the least-recently-used items.\n\n## Usage:\n\n```javascript\nvar LRU = require(\"lru-cache\")\n , options = { max: 500\n , length: function (n) { return n * 2 }\n , dispose: function (key, n) { n.close() }\n , maxAge: 1000 * 60 * 60 }\n , cache = LRU(options)\n , otherCache = LRU(50) // sets just the max size\n\ncache.set(\"key\", \"value\")\ncache.get(\"key\") // \"value\"\n\ncache.reset() // empty the cache\n```\n\nIf you put more stuff in it, then items will fall out.\n\nIf you try to put an oversized thing in it, then it'll fall out right\naway.\n\n## Options\n\n* `max` The maximum size of the cache, checked by applying the length\n function to all values in the cache. Not setting this is kind of\n silly, since that's the whole purpose of this lib, but it defaults\n to `Infinity`.\n* `maxAge` Maximum age in ms. Items are not pro-actively pruned out\n as they age, but if you try to get an item that is too old, it'll\n drop it and return undefined instead of giving it to you.\n* `length` Function that is used to calculate the length of stored\n items. If you're storing strings or buffers, then you probably want\n to do something like `function(n){return n.length}`. The default is\n `function(n){return 1}`, which is fine if you want to store `n`\n like-sized things.\n* `dispose` Function that is called on items when they are dropped\n from the cache. This can be handy if you want to close file\n descriptors or do other cleanup tasks when items are no longer\n accessible. Called with `key, value`. It's called *before*\n actually removing the item from the internal cache, so if you want\n to immediately put it back in, you'll have to do that in a\n `nextTick` or `setTimeout` callback or it won't do anything.\n* `stale` By default, if you set a `maxAge`, it'll only actually pull\n stale items out of the cache when you `get(key)`. (That is, it's\n not pre-emptively doing a `setTimeout` or anything.) If you set\n `stale:true`, it'll return the stale value before deleting it. If\n you don't set this, then it'll return `undefined` when you try to\n get a stale entry, as if it had already been deleted.\n\n## API\n\n* `set(key, value)`\n* `get(key) => value`\n\n Both of these will update the \"recently used\"-ness of the key.\n They do what you think.\n\n* `peek(key)`\n\n Returns the key value (or `undefined` if not found) without\n updating the \"recently used\"-ness of the key.\n\n (If you find yourself using this a lot, you *might* be using the\n wrong sort of data structure, but there are some use cases where\n it's handy.)\n\n* `del(key)`\n\n Deletes a key out of the cache.\n\n* `reset()`\n\n Clear the cache entirely, throwing away all values.\n\n* `has(key)`\n\n Check if a key is in the cache, without updating the recent-ness\n or deleting it for being stale.\n\n* `forEach(function(value,key,cache), [thisp])`\n\n Just like `Array.prototype.forEach`. Iterates over all the keys\n in the cache, in order of recent-ness. (Ie, more recently used\n items are iterated over first.)\n\n* `keys()`\n\n Return an array of the keys in the cache.\n\n* `values()`\n\n Return an array of the values in the cache.\n",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/isaacs/node-lru-cache/issues"
},
"homepage": "https://github.com/isaacs/node-lru-cache",
"_id": "lru-cache@2.5.0",
"_from": "lru-cache@2"
}
@@ -0,0 +1,369 @@
var test = require("tap").test
, LRU = require("../")
test("basic", function (t) {
var cache = new LRU({max: 10})
cache.set("key", "value")
t.equal(cache.get("key"), "value")
t.equal(cache.get("nada"), undefined)
t.equal(cache.length, 1)
t.equal(cache.max, 10)
t.end()
})
test("least recently set", function (t) {
var cache = new LRU(2)
cache.set("a", "A")
cache.set("b", "B")
cache.set("c", "C")
t.equal(cache.get("c"), "C")
t.equal(cache.get("b"), "B")
t.equal(cache.get("a"), undefined)
t.end()
})
test("lru recently gotten", function (t) {
var cache = new LRU(2)
cache.set("a", "A")
cache.set("b", "B")
cache.get("a")
cache.set("c", "C")
t.equal(cache.get("c"), "C")
t.equal(cache.get("b"), undefined)
t.equal(cache.get("a"), "A")
t.end()
})
test("del", function (t) {
var cache = new LRU(2)
cache.set("a", "A")
cache.del("a")
t.equal(cache.get("a"), undefined)
t.end()
})
test("max", function (t) {
var cache = new LRU(3)
// test changing the max, verify that the LRU items get dropped.
cache.max = 100
for (var i = 0; i < 100; i ++) cache.set(i, i)
t.equal(cache.length, 100)
for (var i = 0; i < 100; i ++) {
t.equal(cache.get(i), i)
}
cache.max = 3
t.equal(cache.length, 3)
for (var i = 0; i < 97; i ++) {
t.equal(cache.get(i), undefined)
}
for (var i = 98; i < 100; i ++) {
t.equal(cache.get(i), i)
}
// now remove the max restriction, and try again.
cache.max = "hello"
for (var i = 0; i < 100; i ++) cache.set(i, i)
t.equal(cache.length, 100)
for (var i = 0; i < 100; i ++) {
t.equal(cache.get(i), i)
}
// should trigger an immediate resize
cache.max = 3
t.equal(cache.length, 3)
for (var i = 0; i < 97; i ++) {
t.equal(cache.get(i), undefined)
}
for (var i = 98; i < 100; i ++) {
t.equal(cache.get(i), i)
}
t.end()
})
test("reset", function (t) {
var cache = new LRU(10)
cache.set("a", "A")
cache.set("b", "B")
cache.reset()
t.equal(cache.length, 0)
t.equal(cache.max, 10)
t.equal(cache.get("a"), undefined)
t.equal(cache.get("b"), undefined)
t.end()
})
// Note: `<cache>.dump()` is a debugging tool only. No guarantees are made
// about the format/layout of the response.
test("dump", function (t) {
var cache = new LRU(10)
var d = cache.dump();
t.equal(Object.keys(d).length, 0, "nothing in dump for empty cache")
cache.set("a", "A")
var d = cache.dump() // { a: { key: "a", value: "A", lu: 0 } }
t.ok(d.a)
t.equal(d.a.key, "a")
t.equal(d.a.value, "A")
t.equal(d.a.lu, 0)
cache.set("b", "B")
cache.get("b")
d = cache.dump()
t.ok(d.b)
t.equal(d.b.key, "b")
t.equal(d.b.value, "B")
t.equal(d.b.lu, 2)
t.end()
})
test("basic with weighed length", function (t) {
var cache = new LRU({
max: 100,
length: function (item) { return item.size }
})
cache.set("key", {val: "value", size: 50})
t.equal(cache.get("key").val, "value")
t.equal(cache.get("nada"), undefined)
t.equal(cache.lengthCalculator(cache.get("key")), 50)
t.equal(cache.length, 50)
t.equal(cache.max, 100)
t.end()
})
test("weighed length item too large", function (t) {
var cache = new LRU({
max: 10,
length: function (item) { return item.size }
})
t.equal(cache.max, 10)
// should fall out immediately
cache.set("key", {val: "value", size: 50})
t.equal(cache.length, 0)
t.equal(cache.get("key"), undefined)
t.end()
})
test("least recently set with weighed length", function (t) {
var cache = new LRU({
max:8,
length: function (item) { return item.length }
})
cache.set("a", "A")
cache.set("b", "BB")
cache.set("c", "CCC")
cache.set("d", "DDDD")
t.equal(cache.get("d"), "DDDD")
t.equal(cache.get("c"), "CCC")
t.equal(cache.get("b"), undefined)
t.equal(cache.get("a"), undefined)
t.end()
})
test("lru recently gotten with weighed length", function (t) {
var cache = new LRU({
max: 8,
length: function (item) { return item.length }
})
cache.set("a", "A")
cache.set("b", "BB")
cache.set("c", "CCC")
cache.get("a")
cache.get("b")
cache.set("d", "DDDD")
t.equal(cache.get("c"), undefined)
t.equal(cache.get("d"), "DDDD")
t.equal(cache.get("b"), "BB")
t.equal(cache.get("a"), "A")
t.end()
})
test("set returns proper booleans", function(t) {
var cache = new LRU({
max: 5,
length: function (item) { return item.length }
})
t.equal(cache.set("a", "A"), true)
// should return false for max exceeded
t.equal(cache.set("b", "donuts"), false)
t.equal(cache.set("b", "B"), true)
t.equal(cache.set("c", "CCCC"), true)
t.end()
})
test("drop the old items", function(t) {
var cache = new LRU({
max: 5,
maxAge: 50
})
cache.set("a", "A")
setTimeout(function () {
cache.set("b", "b")
t.equal(cache.get("a"), "A")
}, 25)
setTimeout(function () {
cache.set("c", "C")
// timed out
t.notOk(cache.get("a"))
}, 60)
setTimeout(function () {
t.notOk(cache.get("b"))
t.equal(cache.get("c"), "C")
}, 90)
setTimeout(function () {
t.notOk(cache.get("c"))
t.end()
}, 155)
})
test("disposal function", function(t) {
var disposed = false
var cache = new LRU({
max: 1,
dispose: function (k, n) {
disposed = n
}
})
cache.set(1, 1)
cache.set(2, 2)
t.equal(disposed, 1)
cache.set(3, 3)
t.equal(disposed, 2)
cache.reset()
t.equal(disposed, 3)
t.end()
})
test("disposal function on too big of item", function(t) {
var disposed = false
var cache = new LRU({
max: 1,
length: function (k) {
return k.length
},
dispose: function (k, n) {
disposed = n
}
})
var obj = [ 1, 2 ]
t.equal(disposed, false)
cache.set("obj", obj)
t.equal(disposed, obj)
t.end()
})
test("has()", function(t) {
var cache = new LRU({
max: 1,
maxAge: 10
})
cache.set('foo', 'bar')
t.equal(cache.has('foo'), true)
cache.set('blu', 'baz')
t.equal(cache.has('foo'), false)
t.equal(cache.has('blu'), true)
setTimeout(function() {
t.equal(cache.has('blu'), false)
t.end()
}, 15)
})
test("stale", function(t) {
var cache = new LRU({
maxAge: 10,
stale: true
})
cache.set('foo', 'bar')
t.equal(cache.get('foo'), 'bar')
t.equal(cache.has('foo'), true)
setTimeout(function() {
t.equal(cache.has('foo'), false)
t.equal(cache.get('foo'), 'bar')
t.equal(cache.get('foo'), undefined)
t.end()
}, 15)
})
test("lru update via set", function(t) {
var cache = LRU({ max: 2 });
cache.set('foo', 1);
cache.set('bar', 2);
cache.del('bar');
cache.set('baz', 3);
cache.set('qux', 4);
t.equal(cache.get('foo'), undefined)
t.equal(cache.get('bar'), undefined)
t.equal(cache.get('baz'), 3)
t.equal(cache.get('qux'), 4)
t.end()
})
test("least recently set w/ peek", function (t) {
var cache = new LRU(2)
cache.set("a", "A")
cache.set("b", "B")
t.equal(cache.peek("a"), "A")
cache.set("c", "C")
t.equal(cache.get("c"), "C")
t.equal(cache.get("b"), "B")
t.equal(cache.get("a"), undefined)
t.end()
})
test("pop the least used item", function (t) {
var cache = new LRU(3)
, last
cache.set("a", "A")
cache.set("b", "B")
cache.set("c", "C")
t.equal(cache.length, 3)
t.equal(cache.max, 3)
// Ensure we pop a, c, b
cache.get("b", "B")
last = cache.pop()
t.equal(last.key, "a")
t.equal(last.value, "A")
t.equal(cache.length, 2)
t.equal(cache.max, 3)
last = cache.pop()
t.equal(last.key, "c")
t.equal(last.value, "C")
t.equal(cache.length, 1)
t.equal(cache.max, 3)
last = cache.pop()
t.equal(last.key, "b")
t.equal(last.value, "B")
t.equal(cache.length, 0)
t.equal(cache.max, 3)
last = cache.pop()
t.equal(last, null)
t.equal(cache.length, 0)
t.equal(cache.max, 3)
t.end()
})
@@ -0,0 +1,52 @@
var test = require('tap').test
var LRU = require('../')
test('forEach', function (t) {
var l = new LRU(5)
for (var i = 0; i < 10; i ++) {
l.set(i.toString(), i.toString(2))
}
var i = 9
l.forEach(function (val, key, cache) {
t.equal(cache, l)
t.equal(key, i.toString())
t.equal(val, i.toString(2))
i -= 1
})
// get in order of most recently used
l.get(6)
l.get(8)
var order = [ 8, 6, 9, 7, 5 ]
var i = 0
l.forEach(function (val, key, cache) {
var j = order[i ++]
t.equal(cache, l)
t.equal(key, j.toString())
t.equal(val, j.toString(2))
})
t.end()
})
test('keys() and values()', function (t) {
var l = new LRU(5)
for (var i = 0; i < 10; i ++) {
l.set(i.toString(), i.toString(2))
}
t.similar(l.keys(), ['9', '8', '7', '6', '5'])
t.similar(l.values(), ['1001', '1000', '111', '110', '101'])
// get in order of most recently used
l.get(6)
l.get(8)
t.similar(l.keys(), ['8', '6', '9', '7', '5'])
t.similar(l.values(), ['1000', '110', '1001', '111', '101'])
t.end()
})
@@ -0,0 +1,50 @@
#!/usr/bin/env node --expose_gc
var weak = require('weak');
var test = require('tap').test
var LRU = require('../')
var l = new LRU({ max: 10 })
var refs = 0
function X() {
refs ++
weak(this, deref)
}
function deref() {
refs --
}
test('no leaks', function (t) {
// fill up the cache
for (var i = 0; i < 100; i++) {
l.set(i, new X);
// throw some gets in there, too.
if (i % 2 === 0)
l.get(i / 2)
}
gc()
var start = process.memoryUsage()
// capture the memory
var startRefs = refs
// do it again, but more
for (var i = 0; i < 10000; i++) {
l.set(i, new X);
// throw some gets in there, too.
if (i % 2 === 0)
l.get(i / 2)
}
gc()
var end = process.memoryUsage()
t.equal(refs, startRefs, 'no leaky refs')
console.error('start: %j\n' +
'end: %j', start, end);
t.pass();
t.end();
})
@@ -0,0 +1,27 @@
Copyright (c) Isaac Z. Schlueter ("Author")
All rights reserved.
The BSD License
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,53 @@
# sigmund
Quick and dirty signatures for Objects.
This is like a much faster `deepEquals` comparison, which returns a
string key suitable for caches and the like.
## Usage
```javascript
function doSomething (someObj) {
var key = sigmund(someObj, maxDepth) // max depth defaults to 10
var cached = cache.get(key)
if (cached) return cached)
var result = expensiveCalculation(someObj)
cache.set(key, result)
return result
}
```
The resulting key will be as unique and reproducible as calling
`JSON.stringify` or `util.inspect` on the object, but is much faster.
In order to achieve this speed, some differences are glossed over.
For example, the object `{0:'foo'}` will be treated identically to the
array `['foo']`.
Also, just as there is no way to summon the soul from the scribblings
of a cocain-addled psychoanalyst, there is no way to revive the object
from the signature string that sigmund gives you. In fact, it's
barely even readable.
As with `sys.inspect` and `JSON.stringify`, larger objects will
produce larger signature strings.
Because sigmund is a bit less strict than the more thorough
alternatives, the strings will be shorter, and also there is a
slightly higher chance for collisions. For example, these objects
have the same signature:
var obj1 = {a:'b',c:/def/,g:['h','i',{j:'',k:'l'}]}
var obj2 = {a:'b',c:'/def/',g:['h','i','{jkl']}
Like a good Freudian, sigmund is most effective when you already have
some understanding of what you're looking for. It can help you help
yourself, but you must be willing to do some work as well.
Cycles are handled, and cyclical objects are silently omitted (though
the key is included in the signature output.)
The second argument is the maximum depth, which defaults to 10,
because that is the maximum object traversal depth covered by most
insurance carriers.
@@ -0,0 +1,283 @@
// different ways to id objects
// use a req/res pair, since it's crazy deep and cyclical
// sparseFE10 and sigmund are usually pretty close, which is to be expected,
// since they are essentially the same algorithm, except that sigmund handles
// regular expression objects properly.
var http = require('http')
var util = require('util')
var sigmund = require('./sigmund.js')
var sreq, sres, creq, cres, test
http.createServer(function (q, s) {
sreq = q
sres = s
sres.end('ok')
this.close(function () { setTimeout(function () {
start()
}, 200) })
}).listen(1337, function () {
creq = http.get({ port: 1337 })
creq.on('response', function (s) { cres = s })
})
function start () {
test = [sreq, sres, creq, cres]
// test = sreq
// sreq.sres = sres
// sreq.creq = creq
// sreq.cres = cres
for (var i in exports.compare) {
console.log(i)
var hash = exports.compare[i]()
console.log(hash)
console.log(hash.length)
console.log('')
}
require('bench').runMain()
}
function customWs (obj, md, d) {
d = d || 0
var to = typeof obj
if (to === 'undefined' || to === 'function' || to === null) return ''
if (d > md || !obj || to !== 'object') return ('' + obj).replace(/[\n ]+/g, '')
if (Array.isArray(obj)) {
return obj.map(function (i, _, __) {
return customWs(i, md, d + 1)
}).reduce(function (a, b) { return a + b }, '')
}
var keys = Object.keys(obj)
return keys.map(function (k, _, __) {
return k + ':' + customWs(obj[k], md, d + 1)
}).reduce(function (a, b) { return a + b }, '')
}
function custom (obj, md, d) {
d = d || 0
var to = typeof obj
if (to === 'undefined' || to === 'function' || to === null) return ''
if (d > md || !obj || to !== 'object') return '' + obj
if (Array.isArray(obj)) {
return obj.map(function (i, _, __) {
return custom(i, md, d + 1)
}).reduce(function (a, b) { return a + b }, '')
}
var keys = Object.keys(obj)
return keys.map(function (k, _, __) {
return k + ':' + custom(obj[k], md, d + 1)
}).reduce(function (a, b) { return a + b }, '')
}
function sparseFE2 (obj, maxDepth) {
var seen = []
var soFar = ''
function ch (v, depth) {
if (depth > maxDepth) return
if (typeof v === 'function' || typeof v === 'undefined') return
if (typeof v !== 'object' || !v) {
soFar += v
return
}
if (seen.indexOf(v) !== -1 || depth === maxDepth) return
seen.push(v)
soFar += '{'
Object.keys(v).forEach(function (k, _, __) {
// pseudo-private values. skip those.
if (k.charAt(0) === '_') return
var to = typeof v[k]
if (to === 'function' || to === 'undefined') return
soFar += k + ':'
ch(v[k], depth + 1)
})
soFar += '}'
}
ch(obj, 0)
return soFar
}
function sparseFE (obj, maxDepth) {
var seen = []
var soFar = ''
function ch (v, depth) {
if (depth > maxDepth) return
if (typeof v === 'function' || typeof v === 'undefined') return
if (typeof v !== 'object' || !v) {
soFar += v
return
}
if (seen.indexOf(v) !== -1 || depth === maxDepth) return
seen.push(v)
soFar += '{'
Object.keys(v).forEach(function (k, _, __) {
// pseudo-private values. skip those.
if (k.charAt(0) === '_') return
var to = typeof v[k]
if (to === 'function' || to === 'undefined') return
soFar += k
ch(v[k], depth + 1)
})
}
ch(obj, 0)
return soFar
}
function sparse (obj, maxDepth) {
var seen = []
var soFar = ''
function ch (v, depth) {
if (depth > maxDepth) return
if (typeof v === 'function' || typeof v === 'undefined') return
if (typeof v !== 'object' || !v) {
soFar += v
return
}
if (seen.indexOf(v) !== -1 || depth === maxDepth) return
seen.push(v)
soFar += '{'
for (var k in v) {
// pseudo-private values. skip those.
if (k.charAt(0) === '_') continue
var to = typeof v[k]
if (to === 'function' || to === 'undefined') continue
soFar += k
ch(v[k], depth + 1)
}
}
ch(obj, 0)
return soFar
}
function noCommas (obj, maxDepth) {
var seen = []
var soFar = ''
function ch (v, depth) {
if (depth > maxDepth) return
if (typeof v === 'function' || typeof v === 'undefined') return
if (typeof v !== 'object' || !v) {
soFar += v
return
}
if (seen.indexOf(v) !== -1 || depth === maxDepth) return
seen.push(v)
soFar += '{'
for (var k in v) {
// pseudo-private values. skip those.
if (k.charAt(0) === '_') continue
var to = typeof v[k]
if (to === 'function' || to === 'undefined') continue
soFar += k + ':'
ch(v[k], depth + 1)
}
soFar += '}'
}
ch(obj, 0)
return soFar
}
function flatten (obj, maxDepth) {
var seen = []
var soFar = ''
function ch (v, depth) {
if (depth > maxDepth) return
if (typeof v === 'function' || typeof v === 'undefined') return
if (typeof v !== 'object' || !v) {
soFar += v
return
}
if (seen.indexOf(v) !== -1 || depth === maxDepth) return
seen.push(v)
soFar += '{'
for (var k in v) {
// pseudo-private values. skip those.
if (k.charAt(0) === '_') continue
var to = typeof v[k]
if (to === 'function' || to === 'undefined') continue
soFar += k + ':'
ch(v[k], depth + 1)
soFar += ','
}
soFar += '}'
}
ch(obj, 0)
return soFar
}
exports.compare =
{
// 'custom 2': function () {
// return custom(test, 2, 0)
// },
// 'customWs 2': function () {
// return customWs(test, 2, 0)
// },
'JSON.stringify (guarded)': function () {
var seen = []
return JSON.stringify(test, function (k, v) {
if (typeof v !== 'object' || !v) return v
if (seen.indexOf(v) !== -1) return undefined
seen.push(v)
return v
})
},
'flatten 10': function () {
return flatten(test, 10)
},
// 'flattenFE 10': function () {
// return flattenFE(test, 10)
// },
'noCommas 10': function () {
return noCommas(test, 10)
},
'sparse 10': function () {
return sparse(test, 10)
},
'sparseFE 10': function () {
return sparseFE(test, 10)
},
'sparseFE2 10': function () {
return sparseFE2(test, 10)
},
sigmund: function() {
return sigmund(test, 10)
},
// 'util.inspect 1': function () {
// return util.inspect(test, false, 1, false)
// },
// 'util.inspect undefined': function () {
// util.inspect(test)
// },
// 'util.inspect 2': function () {
// util.inspect(test, false, 2, false)
// },
// 'util.inspect 3': function () {
// util.inspect(test, false, 3, false)
// },
// 'util.inspect 4': function () {
// util.inspect(test, false, 4, false)
// },
// 'util.inspect Infinity': function () {
// util.inspect(test, false, Infinity, false)
// }
}
/** results
**/
@@ -0,0 +1,42 @@
{
"name": "sigmund",
"version": "1.0.0",
"description": "Quick and dirty signatures for Objects.",
"main": "sigmund.js",
"directories": {
"test": "test"
},
"dependencies": {},
"devDependencies": {
"tap": "~0.3.0"
},
"scripts": {
"test": "tap test/*.js",
"bench": "node bench.js"
},
"repository": {
"type": "git",
"url": "git://github.com/isaacs/sigmund"
},
"keywords": [
"object",
"signature",
"key",
"data",
"psychoanalysis"
],
"author": {
"name": "Isaac Z. Schlueter",
"email": "i@izs.me",
"url": "http://blog.izs.me/"
},
"license": "BSD",
"readme": "# sigmund\n\nQuick and dirty signatures for Objects.\n\nThis is like a much faster `deepEquals` comparison, which returns a\nstring key suitable for caches and the like.\n\n## Usage\n\n```javascript\nfunction doSomething (someObj) {\n var key = sigmund(someObj, maxDepth) // max depth defaults to 10\n var cached = cache.get(key)\n if (cached) return cached)\n\n var result = expensiveCalculation(someObj)\n cache.set(key, result)\n return result\n}\n```\n\nThe resulting key will be as unique and reproducible as calling\n`JSON.stringify` or `util.inspect` on the object, but is much faster.\nIn order to achieve this speed, some differences are glossed over.\nFor example, the object `{0:'foo'}` will be treated identically to the\narray `['foo']`.\n\nAlso, just as there is no way to summon the soul from the scribblings\nof a cocain-addled psychoanalyst, there is no way to revive the object\nfrom the signature string that sigmund gives you. In fact, it's\nbarely even readable.\n\nAs with `sys.inspect` and `JSON.stringify`, larger objects will\nproduce larger signature strings.\n\nBecause sigmund is a bit less strict than the more thorough\nalternatives, the strings will be shorter, and also there is a\nslightly higher chance for collisions. For example, these objects\nhave the same signature:\n\n var obj1 = {a:'b',c:/def/,g:['h','i',{j:'',k:'l'}]}\n var obj2 = {a:'b',c:'/def/',g:['h','i','{jkl']}\n\nLike a good Freudian, sigmund is most effective when you already have\nsome understanding of what you're looking for. It can help you help\nyourself, but you must be willing to do some work as well.\n\nCycles are handled, and cyclical objects are silently omitted (though\nthe key is included in the signature output.)\n\nThe second argument is the maximum depth, which defaults to 10,\nbecause that is the maximum object traversal depth covered by most\ninsurance carriers.\n",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/isaacs/sigmund/issues"
},
"homepage": "https://github.com/isaacs/sigmund",
"_id": "sigmund@1.0.0",
"_from": "sigmund@~1.0.0"
}
@@ -0,0 +1,39 @@
module.exports = sigmund
function sigmund (subject, maxSessions) {
maxSessions = maxSessions || 10;
var notes = [];
var analysis = '';
var RE = RegExp;
function psychoAnalyze (subject, session) {
if (session > maxSessions) return;
if (typeof subject === 'function' ||
typeof subject === 'undefined') {
return;
}
if (typeof subject !== 'object' || !subject ||
(subject instanceof RE)) {
analysis += subject;
return;
}
if (notes.indexOf(subject) !== -1 || session === maxSessions) return;
notes.push(subject);
analysis += '{';
Object.keys(subject).forEach(function (issue, _, __) {
// pseudo-private values. skip those.
if (issue.charAt(0) === '_') return;
var to = typeof subject[issue];
if (to === 'function' || to === 'undefined') return;
analysis += issue;
psychoAnalyze(subject[issue], session + 1);
});
}
psychoAnalyze(subject, 0);
return analysis;
}
// vim: set softtabstop=4 shiftwidth=4:
@@ -0,0 +1,24 @@
var test = require('tap').test
var sigmund = require('../sigmund.js')
// occasionally there are duplicates
// that's an acceptable edge-case. JSON.stringify and util.inspect
// have some collision potential as well, though less, and collision
// detection is expensive.
var hash = '{abc/def/g{0h1i2{jkl'
var obj1 = {a:'b',c:/def/,g:['h','i',{j:'',k:'l'}]}
var obj2 = {a:'b',c:'/def/',g:['h','i','{jkl']}
var obj3 = JSON.parse(JSON.stringify(obj1))
obj3.c = /def/
obj3.g[2].cycle = obj3
var cycleHash = '{abc/def/g{0h1i2{jklcycle'
test('basic', function (t) {
t.equal(sigmund(obj1), hash)
t.equal(sigmund(obj2), hash)
t.equal(sigmund(obj3), cycleHash)
t.end()
})
@@ -0,0 +1,40 @@
{
"author": {
"name": "Isaac Z. Schlueter",
"email": "i@izs.me",
"url": "http://blog.izs.me"
},
"name": "minimatch",
"description": "a glob matcher in javascript",
"version": "0.2.14",
"repository": {
"type": "git",
"url": "git://github.com/isaacs/minimatch.git"
},
"main": "minimatch.js",
"scripts": {
"test": "tap test/*.js"
},
"engines": {
"node": "*"
},
"dependencies": {
"lru-cache": "2",
"sigmund": "~1.0.0"
},
"devDependencies": {
"tap": ""
},
"license": {
"type": "MIT",
"url": "http://github.com/isaacs/minimatch/raw/master/LICENSE"
},
"readme": "# minimatch\n\nA minimal matching utility.\n\n[![Build Status](https://secure.travis-ci.org/isaacs/minimatch.png)](http://travis-ci.org/isaacs/minimatch)\n\n\nThis is the matching library used internally by npm.\n\nEventually, it will replace the C binding in node-glob.\n\nIt works by converting glob expressions into JavaScript `RegExp`\nobjects.\n\n## Usage\n\n```javascript\nvar minimatch = require(\"minimatch\")\n\nminimatch(\"bar.foo\", \"*.foo\") // true!\nminimatch(\"bar.foo\", \"*.bar\") // false!\nminimatch(\"bar.foo\", \"*.+(bar|foo)\", { debug: true }) // true, and noisy!\n```\n\n## Features\n\nSupports these glob features:\n\n* Brace Expansion\n* Extended glob matching\n* \"Globstar\" `**` matching\n\nSee:\n\n* `man sh`\n* `man bash`\n* `man 3 fnmatch`\n* `man 5 gitignore`\n\n## Minimatch Class\n\nCreate a minimatch object by instanting the `minimatch.Minimatch` class.\n\n```javascript\nvar Minimatch = require(\"minimatch\").Minimatch\nvar mm = new Minimatch(pattern, options)\n```\n\n### Properties\n\n* `pattern` The original pattern the minimatch object represents.\n* `options` The options supplied to the constructor.\n* `set` A 2-dimensional array of regexp or string expressions.\n Each row in the\n array corresponds to a brace-expanded pattern. Each item in the row\n corresponds to a single path-part. For example, the pattern\n `{a,b/c}/d` would expand to a set of patterns like:\n\n [ [ a, d ]\n , [ b, c, d ] ]\n\n If a portion of the pattern doesn't have any \"magic\" in it\n (that is, it's something like `\"foo\"` rather than `fo*o?`), then it\n will be left as a string rather than converted to a regular\n expression.\n\n* `regexp` Created by the `makeRe` method. A single regular expression\n expressing the entire pattern. This is useful in cases where you wish\n to use the pattern somewhat like `fnmatch(3)` with `FNM_PATH` enabled.\n* `negate` True if the pattern is negated.\n* `comment` True if the pattern is a comment.\n* `empty` True if the pattern is `\"\"`.\n\n### Methods\n\n* `makeRe` Generate the `regexp` member if necessary, and return it.\n Will return `false` if the pattern is invalid.\n* `match(fname)` Return true if the filename matches the pattern, or\n false otherwise.\n* `matchOne(fileArray, patternArray, partial)` Take a `/`-split\n filename, and match it against a single row in the `regExpSet`. This\n method is mainly for internal use, but is exposed so that it can be\n used by a glob-walker that needs to avoid excessive filesystem calls.\n\nAll other methods are internal, and will be called as necessary.\n\n## Functions\n\nThe top-level exported function has a `cache` property, which is an LRU\ncache set to store 100 items. So, calling these methods repeatedly\nwith the same pattern and options will use the same Minimatch object,\nsaving the cost of parsing it multiple times.\n\n### minimatch(path, pattern, options)\n\nMain export. Tests a path against the pattern using the options.\n\n```javascript\nvar isJS = minimatch(file, \"*.js\", { matchBase: true })\n```\n\n### minimatch.filter(pattern, options)\n\nReturns a function that tests its\nsupplied argument, suitable for use with `Array.filter`. Example:\n\n```javascript\nvar javascripts = fileList.filter(minimatch.filter(\"*.js\", {matchBase: true}))\n```\n\n### minimatch.match(list, pattern, options)\n\nMatch against the list of\nfiles, in the style of fnmatch or glob. If nothing is matched, and\noptions.nonull is set, then return a list containing the pattern itself.\n\n```javascript\nvar javascripts = minimatch.match(fileList, \"*.js\", {matchBase: true}))\n```\n\n### minimatch.makeRe(pattern, options)\n\nMake a regular expression object from the pattern.\n\n## Options\n\nAll options are `false` by default.\n\n### debug\n\nDump a ton of stuff to stderr.\n\n### nobrace\n\nDo not expand `{a,b}` and `{1..3}` brace sets.\n\n### noglobstar\n\nDisable `**` matching against multiple folder names.\n\n### dot\n\nAllow patterns to match filenames starting with a period, even if\nthe pattern does not explicitly have a period in that spot.\n\nNote that by default, `a/**/b` will **not** match `a/.d/b`, unless `dot`\nis set.\n\n### noext\n\nDisable \"extglob\" style patterns like `+(a|b)`.\n\n### nocase\n\nPerform a case-insensitive match.\n\n### nonull\n\nWhen a match is not found by `minimatch.match`, return a list containing\nthe pattern itself. When set, an empty list is returned if there are\nno matches.\n\n### matchBase\n\nIf set, then patterns without slashes will be matched\nagainst the basename of the path if it contains slashes. For example,\n`a?b` would match the path `/xyz/123/acb`, but not `/xyz/acb/123`.\n\n### nocomment\n\nSuppress the behavior of treating `#` at the start of a pattern as a\ncomment.\n\n### nonegate\n\nSuppress the behavior of treating a leading `!` character as negation.\n\n### flipNegate\n\nReturns from negate expressions the same as if tLine truncated
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/isaacs/minimatch/issues"
},
"homepage": "https://github.com/isaacs/minimatch",
"_id": "minimatch@0.2.14",
"_from": "minimatch@~0.2.11"
}
@@ -0,0 +1,399 @@
// http://www.bashcookbook.com/bashinfo/source/bash-1.14.7/tests/glob-test
//
// TODO: Some of these tests do very bad things with backslashes, and will
// most likely fail badly on windows. They should probably be skipped.
var tap = require("tap")
, globalBefore = Object.keys(global)
, mm = require("../")
, files = [ "a", "b", "c", "d", "abc"
, "abd", "abe", "bb", "bcd"
, "ca", "cb", "dd", "de"
, "bdir/", "bdir/cfile"]
, next = files.concat([ "a-b", "aXb"
, ".x", ".y" ])
var patterns =
[ "http://www.bashcookbook.com/bashinfo/source/bash-1.14.7/tests/glob-test"
, ["a*", ["a", "abc", "abd", "abe"]]
, ["X*", ["X*"], {nonull: true}]
// allow null glob expansion
, ["X*", []]
// isaacs: Slightly different than bash/sh/ksh
// \\* is not un-escaped to literal "*" in a failed match,
// but it does make it get treated as a literal star
, ["\\*", ["\\*"], {nonull: true}]
, ["\\**", ["\\**"], {nonull: true}]
, ["\\*\\*", ["\\*\\*"], {nonull: true}]
, ["b*/", ["bdir/"]]
, ["c*", ["c", "ca", "cb"]]
, ["**", files]
, ["\\.\\./*/", ["\\.\\./*/"], {nonull: true}]
, ["s/\\..*//", ["s/\\..*//"], {nonull: true}]
, "legendary larry crashes bashes"
, ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/"
, ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/"], {nonull: true}]
, ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\1/"
, ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\1/"], {nonull: true}]
, "character classes"
, ["[a-c]b*", ["abc", "abd", "abe", "bb", "cb"]]
, ["[a-y]*[^c]", ["abd", "abe", "bb", "bcd",
"bdir/", "ca", "cb", "dd", "de"]]
, ["a*[^c]", ["abd", "abe"]]
, function () { files.push("a-b", "aXb") }
, ["a[X-]b", ["a-b", "aXb"]]
, function () { files.push(".x", ".y") }
, ["[^a-c]*", ["d", "dd", "de"]]
, function () { files.push("a*b/", "a*b/ooo") }
, ["a\\*b/*", ["a*b/ooo"]]
, ["a\\*?/*", ["a*b/ooo"]]
, ["*\\\\!*", [], {null: true}, ["echo !7"]]
, ["*\\!*", ["echo !7"], null, ["echo !7"]]
, ["*.\\*", ["r.*"], null, ["r.*"]]
, ["a[b]c", ["abc"]]
, ["a[\\b]c", ["abc"]]
, ["a?c", ["abc"]]
, ["a\\*c", [], {null: true}, ["abc"]]
, ["", [""], { null: true }, [""]]
, "http://www.opensource.apple.com/source/bash/bash-23/" +
"bash/tests/glob-test"
, function () { files.push("man/", "man/man1/", "man/man1/bash.1") }
, ["*/man*/bash.*", ["man/man1/bash.1"]]
, ["man/man1/bash.1", ["man/man1/bash.1"]]
, ["a***c", ["abc"], null, ["abc"]]
, ["a*****?c", ["abc"], null, ["abc"]]
, ["?*****??", ["abc"], null, ["abc"]]
, ["*****??", ["abc"], null, ["abc"]]
, ["?*****?c", ["abc"], null, ["abc"]]
, ["?***?****c", ["abc"], null, ["abc"]]
, ["?***?****?", ["abc"], null, ["abc"]]
, ["?***?****", ["abc"], null, ["abc"]]
, ["*******c", ["abc"], null, ["abc"]]
, ["*******?", ["abc"], null, ["abc"]]
, ["a*cd**?**??k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
, ["a**?**cd**?**??k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
, ["a**?**cd**?**??k***", ["abcdecdhjk"], null, ["abcdecdhjk"]]
, ["a**?**cd**?**??***k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
, ["a**?**cd**?**??***k**", ["abcdecdhjk"], null, ["abcdecdhjk"]]
, ["a****c**?**??*****", ["abcdecdhjk"], null, ["abcdecdhjk"]]
, ["[-abc]", ["-"], null, ["-"]]
, ["[abc-]", ["-"], null, ["-"]]
, ["\\", ["\\"], null, ["\\"]]
, ["[\\\\]", ["\\"], null, ["\\"]]
, ["[[]", ["["], null, ["["]]
, ["[", ["["], null, ["["]]
, ["[*", ["[abc"], null, ["[abc"]]
, "a right bracket shall lose its special meaning and\n" +
"represent itself in a bracket expression if it occurs\n" +
"first in the list. -- POSIX.2 2.8.3.2"
, ["[]]", ["]"], null, ["]"]]
, ["[]-]", ["]"], null, ["]"]]
, ["[a-\z]", ["p"], null, ["p"]]
, ["??**********?****?", [], { null: true }, ["abc"]]
, ["??**********?****c", [], { null: true }, ["abc"]]
, ["?************c****?****", [], { null: true }, ["abc"]]
, ["*c*?**", [], { null: true }, ["abc"]]
, ["a*****c*?**", [], { null: true }, ["abc"]]
, ["a********???*******", [], { null: true }, ["abc"]]
, ["[]", [], { null: true }, ["a"]]
, ["[abc", [], { null: true }, ["["]]
, "nocase tests"
, ["XYZ", ["xYz"], { nocase: true, null: true }
, ["xYz", "ABC", "IjK"]]
, ["ab*", ["ABC"], { nocase: true, null: true }
, ["xYz", "ABC", "IjK"]]
, ["[ia]?[ck]", ["ABC", "IjK"], { nocase: true, null: true }
, ["xYz", "ABC", "IjK"]]
// [ pattern, [matches], MM opts, files, TAP opts]
, "onestar/twostar"
, ["{/*,*}", [], {null: true}, ["/asdf/asdf/asdf"]]
, ["{/?,*}", ["/a", "bb"], {null: true}
, ["/a", "/b/b", "/a/b/c", "bb"]]
, "dots should not match unless requested"
, ["**", ["a/b"], {}, ["a/b", "a/.d", ".a/.d"]]
// .. and . can only match patterns starting with .,
// even when options.dot is set.
, function () {
files = ["a/./b", "a/../b", "a/c/b", "a/.d/b"]
}
, ["a/*/b", ["a/c/b", "a/.d/b"], {dot: true}]
, ["a/.*/b", ["a/./b", "a/../b", "a/.d/b"], {dot: true}]
, ["a/*/b", ["a/c/b"], {dot:false}]
, ["a/.*/b", ["a/./b", "a/../b", "a/.d/b"], {dot: false}]
// this also tests that changing the options needs
// to change the cache key, even if the pattern is
// the same!
, ["**", ["a/b","a/.d",".a/.d"], { dot: true }
, [ ".a/.d", "a/.d", "a/b"]]
, "paren sets cannot contain slashes"
, ["*(a/b)", ["*(a/b)"], {nonull: true}, ["a/b"]]
// brace sets trump all else.
//
// invalid glob pattern. fails on bash4 and bsdglob.
// however, in this implementation, it's easier just
// to do the intuitive thing, and let brace-expansion
// actually come before parsing any extglob patterns,
// like the documentation seems to say.
//
// XXX: if anyone complains about this, either fix it
// or tell them to grow up and stop complaining.
//
// bash/bsdglob says this:
// , ["*(a|{b),c)}", ["*(a|{b),c)}"], {}, ["a", "ab", "ac", "ad"]]
// but we do this instead:
, ["*(a|{b),c)}", ["a", "ab", "ac"], {}, ["a", "ab", "ac", "ad"]]
// test partial parsing in the presence of comment/negation chars
, ["[!a*", ["[!ab"], {}, ["[!ab", "[ab"]]
, ["[#a*", ["[#ab"], {}, ["[#ab", "[ab"]]
// like: {a,b|c\\,d\\\|e} except it's unclosed, so it has to be escaped.
, ["+(a|*\\|c\\\\|d\\\\\\|e\\\\\\\\|f\\\\\\\\\\|g"
, ["+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g"]
, {}
, ["+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g", "a", "b\\c"]]
// crazy nested {,,} and *(||) tests.
, function () {
files = [ "a", "b", "c", "d"
, "ab", "ac", "ad"
, "bc", "cb"
, "bc,d", "c,db", "c,d"
, "d)", "(b|c", "*(b|c"
, "b|c", "b|cc", "cb|c"
, "x(a|b|c)", "x(a|c)"
, "(a|b|c)", "(a|c)"]
}
, ["*(a|{b,c})", ["a", "b", "c", "ab", "ac"]]
, ["{a,*(b|c,d)}", ["a","(b|c", "*(b|c", "d)"]]
// a
// *(b|c)
// *(b|d)
, ["{a,*(b|{c,d})}", ["a","b", "bc", "cb", "c", "d"]]
, ["*(a|{b|c,c})", ["a", "b", "c", "ab", "ac", "bc", "cb"]]
// test various flag settings.
, [ "*(a|{b|c,c})", ["x(a|b|c)", "x(a|c)", "(a|b|c)", "(a|c)"]
, { noext: true } ]
, ["a?b", ["x/y/acb", "acb/"], {matchBase: true}
, ["x/y/acb", "acb/", "acb/d/e", "x/y/acb/d"] ]
, ["#*", ["#a", "#b"], {nocomment: true}, ["#a", "#b", "c#d"]]
// begin channelling Boole and deMorgan...
, "negation tests"
, function () {
files = ["d", "e", "!ab", "!abc", "a!b", "\\!a"]
}
// anything that is NOT a* matches.
, ["!a*", ["\\!a", "d", "e", "!ab", "!abc"]]
// anything that IS !a* matches.
, ["!a*", ["!ab", "!abc"], {nonegate: true}]
// anything that IS a* matches
, ["!!a*", ["a!b"]]
// anything that is NOT !a* matches
, ["!\\!a*", ["a!b", "d", "e", "\\!a"]]
// negation nestled within a pattern
, function () {
files = [ "foo.js"
, "foo.bar"
// can't match this one without negative lookbehind.
, "foo.js.js"
, "blar.js"
, "foo."
, "boo.js.boo" ]
}
, ["*.!(js)", ["foo.bar", "foo.", "boo.js.boo"] ]
// https://github.com/isaacs/minimatch/issues/5
, function () {
files = [ 'a/b/.x/c'
, 'a/b/.x/c/d'
, 'a/b/.x/c/d/e'
, 'a/b/.x'
, 'a/b/.x/'
, 'a/.x/b'
, '.x'
, '.x/'
, '.x/a'
, '.x/a/b'
, 'a/.x/b/.x/c'
, '.x/.x' ]
}
, ["**/.x/**", [ '.x/'
, '.x/a'
, '.x/a/b'
, 'a/.x/b'
, 'a/b/.x/'
, 'a/b/.x/c'
, 'a/b/.x/c/d'
, 'a/b/.x/c/d/e' ] ]
]
var regexps =
[ '/^(?:(?=.)a[^/]*?)$/',
'/^(?:(?=.)X[^/]*?)$/',
'/^(?:(?=.)X[^/]*?)$/',
'/^(?:\\*)$/',
'/^(?:(?=.)\\*[^/]*?)$/',
'/^(?:\\*\\*)$/',
'/^(?:(?=.)b[^/]*?\\/)$/',
'/^(?:(?=.)c[^/]*?)$/',
'/^(?:(?:(?!(?:\\/|^)\\.).)*?)$/',
'/^(?:\\.\\.\\/(?!\\.)(?=.)[^/]*?\\/)$/',
'/^(?:s\\/(?=.)\\.\\.[^/]*?\\/)$/',
'/^(?:\\/\\^root:\\/\\{s\\/(?=.)\\^[^:][^/]*?:[^:][^/]*?:\\([^:]\\)[^/]*?\\.[^/]*?\\$\\/1\\/)$/',
'/^(?:\\/\\^root:\\/\\{s\\/(?=.)\\^[^:][^/]*?:[^:][^/]*?:\\([^:]\\)[^/]*?\\.[^/]*?\\$\\/\u0001\\/)$/',
'/^(?:(?!\\.)(?=.)[a-c]b[^/]*?)$/',
'/^(?:(?!\\.)(?=.)[a-y][^/]*?[^c])$/',
'/^(?:(?=.)a[^/]*?[^c])$/',
'/^(?:(?=.)a[X-]b)$/',
'/^(?:(?!\\.)(?=.)[^a-c][^/]*?)$/',
'/^(?:a\\*b\\/(?!\\.)(?=.)[^/]*?)$/',
'/^(?:(?=.)a\\*[^/]\\/(?!\\.)(?=.)[^/]*?)$/',
'/^(?:(?!\\.)(?=.)[^/]*?\\\\\\![^/]*?)$/',
'/^(?:(?!\\.)(?=.)[^/]*?\\![^/]*?)$/',
'/^(?:(?!\\.)(?=.)[^/]*?\\.\\*)$/',
'/^(?:(?=.)a[b]c)$/',
'/^(?:(?=.)a[b]c)$/',
'/^(?:(?=.)a[^/]c)$/',
'/^(?:a\\*c)$/',
'false',
'/^(?:(?!\\.)(?=.)[^/]*?\\/(?=.)man[^/]*?\\/(?=.)bash\\.[^/]*?)$/',
'/^(?:man\\/man1\\/bash\\.1)$/',
'/^(?:(?=.)a[^/]*?[^/]*?[^/]*?c)$/',
'/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]c)$/',
'/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/])$/',
'/^(?:(?!\\.)(?=.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/])$/',
'/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]c)$/',
'/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?c)$/',
'/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/])$/',
'/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?)$/',
'/^(?:(?!\\.)(?=.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?c)$/',
'/^(?:(?!\\.)(?=.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/])$/',
'/^(?:(?=.)a[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/]k)$/',
'/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/]k)$/',
'/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/]k[^/]*?[^/]*?[^/]*?)$/',
'/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/][^/]*?[^/]*?[^/]*?k)$/',
'/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/][^/]*?[^/]*?[^/]*?k[^/]*?[^/]*?)$/',
'/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?c[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?)$/',
'/^(?:(?!\\.)(?=.)[-abc])$/',
'/^(?:(?!\\.)(?=.)[abc-])$/',
'/^(?:\\\\)$/',
'/^(?:(?!\\.)(?=.)[\\\\])$/',
'/^(?:(?!\\.)(?=.)[\\[])$/',
'/^(?:\\[)$/',
'/^(?:(?=.)\\[(?!\\.)(?=.)[^/]*?)$/',
'/^(?:(?!\\.)(?=.)[\\]])$/',
'/^(?:(?!\\.)(?=.)[\\]-])$/',
'/^(?:(?!\\.)(?=.)[a-z])$/',
'/^(?:(?!\\.)(?=.)[^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/])$/',
'/^(?:(?!\\.)(?=.)[^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?c)$/',
'/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?c[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?)$/',
'/^(?:(?!\\.)(?=.)[^/]*?c[^/]*?[^/][^/]*?[^/]*?)$/',
'/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?c[^/]*?[^/][^/]*?[^/]*?)$/',
'/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?)$/',
'/^(?:\\[\\])$/',
'/^(?:\\[abc)$/',
'/^(?:(?=.)XYZ)$/i',
'/^(?:(?=.)ab[^/]*?)$/i',
'/^(?:(?!\\.)(?=.)[ia][^/][ck])$/i',
'/^(?:\\/(?!\\.)(?=.)[^/]*?|(?!\\.)(?=.)[^/]*?)$/',
'/^(?:\\/(?!\\.)(?=.)[^/]|(?!\\.)(?=.)[^/]*?)$/',
'/^(?:(?:(?!(?:\\/|^)\\.).)*?)$/',
'/^(?:a\\/(?!(?:^|\\/)\\.{1,2}(?:$|\\/))(?=.)[^/]*?\\/b)$/',
'/^(?:a\\/(?=.)\\.[^/]*?\\/b)$/',
'/^(?:a\\/(?!\\.)(?=.)[^/]*?\\/b)$/',
'/^(?:a\\/(?=.)\\.[^/]*?\\/b)$/',
'/^(?:(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?)$/',
'/^(?:(?!\\.)(?=.)[^/]*?\\(a\\/b\\))$/',
'/^(?:(?!\\.)(?=.)(?:a|b)*|(?!\\.)(?=.)(?:a|c)*)$/',
'/^(?:(?=.)\\[(?=.)\\!a[^/]*?)$/',
'/^(?:(?=.)\\[(?=.)#a[^/]*?)$/',
'/^(?:(?=.)\\+\\(a\\|[^/]*?\\|c\\\\\\\\\\|d\\\\\\\\\\|e\\\\\\\\\\\\\\\\\\|f\\\\\\\\\\\\\\\\\\|g)$/',
'/^(?:(?!\\.)(?=.)(?:a|b)*|(?!\\.)(?=.)(?:a|c)*)$/',
'/^(?:a|(?!\\.)(?=.)[^/]*?\\(b\\|c|d\\))$/',
'/^(?:a|(?!\\.)(?=.)(?:b|c)*|(?!\\.)(?=.)(?:b|d)*)$/',
'/^(?:(?!\\.)(?=.)(?:a|b|c)*|(?!\\.)(?=.)(?:a|c)*)$/',
'/^(?:(?!\\.)(?=.)[^/]*?\\(a\\|b\\|c\\)|(?!\\.)(?=.)[^/]*?\\(a\\|c\\))$/',
'/^(?:(?=.)a[^/]b)$/',
'/^(?:(?=.)#[^/]*?)$/',
'/^(?!^(?:(?=.)a[^/]*?)$).*$/',
'/^(?:(?=.)\\!a[^/]*?)$/',
'/^(?:(?=.)a[^/]*?)$/',
'/^(?!^(?:(?=.)\\!a[^/]*?)$).*$/',
'/^(?:(?!\\.)(?=.)[^/]*?\\.(?:(?!js)[^/]*?))$/',
'/^(?:(?:(?!(?:\\/|^)\\.).)*?\\/\\.x\\/(?:(?!(?:\\/|^)\\.).)*?)$/' ]
var re = 0;
tap.test("basic tests", function (t) {
var start = Date.now()
// [ pattern, [matches], MM opts, files, TAP opts]
patterns.forEach(function (c) {
if (typeof c === "function") return c()
if (typeof c === "string") return t.comment(c)
var pattern = c[0]
, expect = c[1].sort(alpha)
, options = c[2] || {}
, f = c[3] || files
, tapOpts = c[4] || {}
// options.debug = true
var m = new mm.Minimatch(pattern, options)
var r = m.makeRe()
var expectRe = regexps[re++]
tapOpts.re = String(r) || JSON.stringify(r)
tapOpts.files = JSON.stringify(f)
tapOpts.pattern = pattern
tapOpts.set = m.set
tapOpts.negated = m.negate
var actual = mm.match(f, pattern, options)
actual.sort(alpha)
t.equivalent( actual, expect
, JSON.stringify(pattern) + " " + JSON.stringify(expect)
, tapOpts )
t.equal(tapOpts.re, expectRe, tapOpts)
})
t.comment("time=" + (Date.now() - start) + "ms")
t.end()
})
tap.test("global leak test", function (t) {
var globalAfter = Object.keys(global)
t.equivalent(globalAfter, globalBefore, "no new globals, please")
t.end()
})
function alpha (a, b) {
return a > b ? 1 : -1
}
@@ -0,0 +1,33 @@
var tap = require("tap")
, minimatch = require("../")
tap.test("brace expansion", function (t) {
// [ pattern, [expanded] ]
; [ [ "a{b,c{d,e},{f,g}h}x{y,z}"
, [ "abxy"
, "abxz"
, "acdxy"
, "acdxz"
, "acexy"
, "acexz"
, "afhxy"
, "afhxz"
, "aghxy"
, "aghxz" ] ]
, [ "a{1..5}b"
, [ "a1b"
, "a2b"
, "a3b"
, "a4b"
, "a5b" ] ]
, [ "a{b}c", ["a{b}c"] ]
].forEach(function (tc) {
var p = tc[0]
, expect = tc[1]
t.equivalent(minimatch.braceExpand(p), expect, p)
})
console.error("ending")
t.end()
})
@@ -0,0 +1,14 @@
var Minimatch = require("../minimatch.js").Minimatch
var tap = require("tap")
tap.test("cache test", function (t) {
var mm1 = new Minimatch("a?b")
var mm2 = new Minimatch("a?b")
t.equal(mm1, mm2, "should get the same object")
// the lru should drop it after 100 entries
for (var i = 0; i < 100; i ++) {
new Minimatch("a"+i)
}
mm2 = new Minimatch("a?b")
t.notEqual(mm1, mm2, "cache should have dropped")
t.end()
})
@@ -0,0 +1,274 @@
// http://www.bashcookbook.com/bashinfo/source/bash-1.14.7/tests/glob-test
//
// TODO: Some of these tests do very bad things with backslashes, and will
// most likely fail badly on windows. They should probably be skipped.
var tap = require("tap")
, globalBefore = Object.keys(global)
, mm = require("../")
, files = [ "a", "b", "c", "d", "abc"
, "abd", "abe", "bb", "bcd"
, "ca", "cb", "dd", "de"
, "bdir/", "bdir/cfile"]
, next = files.concat([ "a-b", "aXb"
, ".x", ".y" ])
tap.test("basic tests", function (t) {
var start = Date.now()
// [ pattern, [matches], MM opts, files, TAP opts]
; [ "http://www.bashcookbook.com/bashinfo" +
"/source/bash-1.14.7/tests/glob-test"
, ["a*", ["a", "abc", "abd", "abe"]]
, ["X*", ["X*"], {nonull: true}]
// allow null glob expansion
, ["X*", []]
// isaacs: Slightly different than bash/sh/ksh
// \\* is not un-escaped to literal "*" in a failed match,
// but it does make it get treated as a literal star
, ["\\*", ["\\*"], {nonull: true}]
, ["\\**", ["\\**"], {nonull: true}]
, ["\\*\\*", ["\\*\\*"], {nonull: true}]
, ["b*/", ["bdir/"]]
, ["c*", ["c", "ca", "cb"]]
, ["**", files]
, ["\\.\\./*/", ["\\.\\./*/"], {nonull: true}]
, ["s/\\..*//", ["s/\\..*//"], {nonull: true}]
, "legendary larry crashes bashes"
, ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/"
, ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/"], {nonull: true}]
, ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\1/"
, ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\1/"], {nonull: true}]
, "character classes"
, ["[a-c]b*", ["abc", "abd", "abe", "bb", "cb"]]
, ["[a-y]*[^c]", ["abd", "abe", "bb", "bcd",
"bdir/", "ca", "cb", "dd", "de"]]
, ["a*[^c]", ["abd", "abe"]]
, function () { files.push("a-b", "aXb") }
, ["a[X-]b", ["a-b", "aXb"]]
, function () { files.push(".x", ".y") }
, ["[^a-c]*", ["d", "dd", "de"]]
, function () { files.push("a*b/", "a*b/ooo") }
, ["a\\*b/*", ["a*b/ooo"]]
, ["a\\*?/*", ["a*b/ooo"]]
, ["*\\\\!*", [], {null: true}, ["echo !7"]]
, ["*\\!*", ["echo !7"], null, ["echo !7"]]
, ["*.\\*", ["r.*"], null, ["r.*"]]
, ["a[b]c", ["abc"]]
, ["a[\\b]c", ["abc"]]
, ["a?c", ["abc"]]
, ["a\\*c", [], {null: true}, ["abc"]]
, ["", [""], { null: true }, [""]]
, "http://www.opensource.apple.com/source/bash/bash-23/" +
"bash/tests/glob-test"
, function () { files.push("man/", "man/man1/", "man/man1/bash.1") }
, ["*/man*/bash.*", ["man/man1/bash.1"]]
, ["man/man1/bash.1", ["man/man1/bash.1"]]
, ["a***c", ["abc"], null, ["abc"]]
, ["a*****?c", ["abc"], null, ["abc"]]
, ["?*****??", ["abc"], null, ["abc"]]
, ["*****??", ["abc"], null, ["abc"]]
, ["?*****?c", ["abc"], null, ["abc"]]
, ["?***?****c", ["abc"], null, ["abc"]]
, ["?***?****?", ["abc"], null, ["abc"]]
, ["?***?****", ["abc"], null, ["abc"]]
, ["*******c", ["abc"], null, ["abc"]]
, ["*******?", ["abc"], null, ["abc"]]
, ["a*cd**?**??k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
, ["a**?**cd**?**??k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
, ["a**?**cd**?**??k***", ["abcdecdhjk"], null, ["abcdecdhjk"]]
, ["a**?**cd**?**??***k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
, ["a**?**cd**?**??***k**", ["abcdecdhjk"], null, ["abcdecdhjk"]]
, ["a****c**?**??*****", ["abcdecdhjk"], null, ["abcdecdhjk"]]
, ["[-abc]", ["-"], null, ["-"]]
, ["[abc-]", ["-"], null, ["-"]]
, ["\\", ["\\"], null, ["\\"]]
, ["[\\\\]", ["\\"], null, ["\\"]]
, ["[[]", ["["], null, ["["]]
, ["[", ["["], null, ["["]]
, ["[*", ["[abc"], null, ["[abc"]]
, "a right bracket shall lose its special meaning and\n" +
"represent itself in a bracket expression if it occurs\n" +
"first in the list. -- POSIX.2 2.8.3.2"
, ["[]]", ["]"], null, ["]"]]
, ["[]-]", ["]"], null, ["]"]]
, ["[a-\z]", ["p"], null, ["p"]]
, ["??**********?****?", [], { null: true }, ["abc"]]
, ["??**********?****c", [], { null: true }, ["abc"]]
, ["?************c****?****", [], { null: true }, ["abc"]]
, ["*c*?**", [], { null: true }, ["abc"]]
, ["a*****c*?**", [], { null: true }, ["abc"]]
, ["a********???*******", [], { null: true }, ["abc"]]
, ["[]", [], { null: true }, ["a"]]
, ["[abc", [], { null: true }, ["["]]
, "nocase tests"
, ["XYZ", ["xYz"], { nocase: true, null: true }
, ["xYz", "ABC", "IjK"]]
, ["ab*", ["ABC"], { nocase: true, null: true }
, ["xYz", "ABC", "IjK"]]
, ["[ia]?[ck]", ["ABC", "IjK"], { nocase: true, null: true }
, ["xYz", "ABC", "IjK"]]
// [ pattern, [matches], MM opts, files, TAP opts]
, "onestar/twostar"
, ["{/*,*}", [], {null: true}, ["/asdf/asdf/asdf"]]
, ["{/?,*}", ["/a", "bb"], {null: true}
, ["/a", "/b/b", "/a/b/c", "bb"]]
, "dots should not match unless requested"
, ["**", ["a/b"], {}, ["a/b", "a/.d", ".a/.d"]]
// .. and . can only match patterns starting with .,
// even when options.dot is set.
, function () {
files = ["a/./b", "a/../b", "a/c/b", "a/.d/b"]
}
, ["a/*/b", ["a/c/b", "a/.d/b"], {dot: true}]
, ["a/.*/b", ["a/./b", "a/../b", "a/.d/b"], {dot: true}]
, ["a/*/b", ["a/c/b"], {dot:false}]
, ["a/.*/b", ["a/./b", "a/../b", "a/.d/b"], {dot: false}]
// this also tests that changing the options needs
// to change the cache key, even if the pattern is
// the same!
, ["**", ["a/b","a/.d",".a/.d"], { dot: true }
, [ ".a/.d", "a/.d", "a/b"]]
, "paren sets cannot contain slashes"
, ["*(a/b)", ["*(a/b)"], {nonull: true}, ["a/b"]]
// brace sets trump all else.
//
// invalid glob pattern. fails on bash4 and bsdglob.
// however, in this implementation, it's easier just
// to do the intuitive thing, and let brace-expansion
// actually come before parsing any extglob patterns,
// like the documentation seems to say.
//
// XXX: if anyone complains about this, either fix it
// or tell them to grow up and stop complaining.
//
// bash/bsdglob says this:
// , ["*(a|{b),c)}", ["*(a|{b),c)}"], {}, ["a", "ab", "ac", "ad"]]
// but we do this instead:
, ["*(a|{b),c)}", ["a", "ab", "ac"], {}, ["a", "ab", "ac", "ad"]]
// test partial parsing in the presence of comment/negation chars
, ["[!a*", ["[!ab"], {}, ["[!ab", "[ab"]]
, ["[#a*", ["[#ab"], {}, ["[#ab", "[ab"]]
// like: {a,b|c\\,d\\\|e} except it's unclosed, so it has to be escaped.
, ["+(a|*\\|c\\\\|d\\\\\\|e\\\\\\\\|f\\\\\\\\\\|g"
, ["+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g"]
, {}
, ["+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g", "a", "b\\c"]]
// crazy nested {,,} and *(||) tests.
, function () {
files = [ "a", "b", "c", "d"
, "ab", "ac", "ad"
, "bc", "cb"
, "bc,d", "c,db", "c,d"
, "d)", "(b|c", "*(b|c"
, "b|c", "b|cc", "cb|c"
, "x(a|b|c)", "x(a|c)"
, "(a|b|c)", "(a|c)"]
}
, ["*(a|{b,c})", ["a", "b", "c", "ab", "ac"]]
, ["{a,*(b|c,d)}", ["a","(b|c", "*(b|c", "d)"]]
// a
// *(b|c)
// *(b|d)
, ["{a,*(b|{c,d})}", ["a","b", "bc", "cb", "c", "d"]]
, ["*(a|{b|c,c})", ["a", "b", "c", "ab", "ac", "bc", "cb"]]
// test various flag settings.
, [ "*(a|{b|c,c})", ["x(a|b|c)", "x(a|c)", "(a|b|c)", "(a|c)"]
, { noext: true } ]
, ["a?b", ["x/y/acb", "acb/"], {matchBase: true}
, ["x/y/acb", "acb/", "acb/d/e", "x/y/acb/d"] ]
, ["#*", ["#a", "#b"], {nocomment: true}, ["#a", "#b", "c#d"]]
// begin channelling Boole and deMorgan...
, "negation tests"
, function () {
files = ["d", "e", "!ab", "!abc", "a!b", "\\!a"]
}
// anything that is NOT a* matches.
, ["!a*", ["\\!a", "d", "e", "!ab", "!abc"]]
// anything that IS !a* matches.
, ["!a*", ["!ab", "!abc"], {nonegate: true}]
// anything that IS a* matches
, ["!!a*", ["a!b"]]
// anything that is NOT !a* matches
, ["!\\!a*", ["a!b", "d", "e", "\\!a"]]
// negation nestled within a pattern
, function () {
files = [ "foo.js"
, "foo.bar"
// can't match this one without negative lookbehind.
, "foo.js.js"
, "blar.js"
, "foo."
, "boo.js.boo" ]
}
, ["*.!(js)", ["foo.bar", "foo.", "boo.js.boo"] ]
].forEach(function (c) {
if (typeof c === "function") return c()
if (typeof c === "string") return t.comment(c)
var pattern = c[0]
, expect = c[1].sort(alpha)
, options = c[2] || {}
, f = c[3] || files
, tapOpts = c[4] || {}
// options.debug = true
var Class = mm.defaults(options).Minimatch
var m = new Class(pattern, {})
var r = m.makeRe()
tapOpts.re = String(r) || JSON.stringify(r)
tapOpts.files = JSON.stringify(f)
tapOpts.pattern = pattern
tapOpts.set = m.set
tapOpts.negated = m.negate
var actual = mm.match(f, pattern, options)
actual.sort(alpha)
t.equivalent( actual, expect
, JSON.stringify(pattern) + " " + JSON.stringify(expect)
, tapOpts )
})
t.comment("time=" + (Date.now() - start) + "ms")
t.end()
})
tap.test("global leak test", function (t) {
var globalAfter = Object.keys(global)
t.equivalent(globalAfter, globalBefore, "no new globals, please")
t.end()
})
function alpha (a, b) {
return a > b ? 1 : -1
}
Loaded 100 of 3159 files, more files were not shown because too many files have changed in this diff. Show more