Commit 5bc02d22 authored by Zeke Sikelianos's avatar Zeke Sikelianos

Merge pull request #53 from heroku/use-semver-service

Use semver.io
parents 2c88fc0b 8a80b869
#!/usr/bin/env bash
# fail fast
set -e
# Uncomment the line below to enable debugging
# set -x
download_and_install_node() {
version="$1"
node_url="http://s3pository.heroku.com/node/v$version/node-v$version-linux-x64.tar.gz"
curl $node_url -s -o - | tar xzf - -C $build_dir
mkdir -p $build_dir/vendor
# Remove node in case we're overwriting a previously-downloaded version
rm -rf $build_dir/vendor/node
mv $build_dir/node-v$version-linux-x64 $build_dir/vendor/node
chmod +x $build_dir/vendor/node/bin/*
PATH=$PATH:$build_dir/vendor/node/bin
}
query_stable_version() {
curl -s http://nodejs.org/dist/ \
| egrep -o '[0-9]+\.[0-9]*[02468]\.[0-9]+' \
| sort -u -k 1,1n -k 2,2n -k 3,3n -t . \
| tail -n1
}
query_latest_version() {
curl -s http://nodejs.org/dist/ \
| egrep -o '[0-9]+\.[0-9]+\.[0-9]+' \
| sort -u -k 1,1n -k 2,2n -k 3,3n -t . \
| tail -n1
}
query_all_versions() {
curl -s http://nodejs.org/dist/ \
| egrep -o '[0-9]+\.[0-9]+\.[0-9]+' \
| sort -u -k 1,1n -k 2,2n -k 3,3n -t .
}
error() {
echo " ! $*" >&2
exit 1
......@@ -49,6 +7,13 @@ status() {
echo "-----> $*"
}
protip() {
echo
echo "PRO TIP: $*" | indent
echo "See https://devcenter.heroku.com/articles/nodejs-support" | indent
echo
}
# sed -l basically makes sed replace and buffer through stdin to stdout
# so you get updates while the command runs and dont wait for the end
# e.g. npm install | indent
......@@ -60,6 +25,6 @@ indent() {
esac
}
function cat_npm_debug_log() {
cat_npm_debug_log() {
test -f $build_dir/npm-debug.log && cat $build_dir/npm-debug.log
}
#!/usr/bin/env bash
# fail fast
set -e
# enable debugging
# set -x
# Configure directories
build_dir=$1
cache_basedir=$2
bp_dir=$(cd $(dirname $0); cd ..; pwd)
stable_version="0.10.20"
# Load some convenience functions like status() echo(), indent
source $bp_dir/bin/common.sh
# Output npm debug info on error
trap cat_npm_debug_log ERR
# Bootstrap the build process with latest stable version of node
# We'll use it to parse package.json and do semver detection
status "Bootstrapping node"
download_and_install_node $stable_version
# Is a node version specified in package.json?
# https://github.com/trentm/json
requested_version=$(cat $build_dir/package.json | $bp_dir/vendor/json engines.node)
# Give a warning if engines.node is unspecified
if ! test $requested_version; then
node_version=$stable_version
echo
echo "WARNING: No node version specified in package.json, see:" | indent
echo "https://devcenter.heroku.com/articles/nodejs-support#versions" | indent
echo
status "Defaulting to latest stable node, v$stable_version"
# Look in package.json's engines.node field for a semver range
semver_range=$(cat $build_dir/package.json | $bp_dir/vendor/jq -r .engines.node)
# Resolve node version using semver.io
semver_url=http://semver.io/node/$semver_range
node_version=$(curl --silent $semver_url)
# Recommend using semver ranges in a safe manner
if [ "$semver_range" == "null" ]; then
protip "Specify a node version in package.json"
semver_range=""
elif [ "$semver_range" == "*" ]; then
protip "Avoid using semver ranges like '*' in engines.node"
elif [ ${semver_range:0:1} == ">" ]; then
protip "Avoid using semver ranges starting with '>' in engines.node"
fi
# Output info about requested range and resolved node version
if [ "$semver_range" == "" ]; then
status "Defaulting to latest stable node: $node_version"
else
# Does the already-downloaded stable version of node satisfy the requested version?
default_satisfies=$($bp_dir/vendor/semver/bin/semver -v "$stable_version" -r "$requested_version" || echo "")
if test $default_satisfies; then
status "Latest stable node v$stable_version satisfies engines.node: $requested_version"
node_version=$stable_version
else
# Fetch all versions of node from nodejs.org/dist and format them into
# a string that the semver binary will appreciate.
# e.g. semver -v "0.10.0" -v "0.10.1" -v "0.10.2" -r ">0.8"
# See https://github.com/isaacs/node-semver/blob/master/bin/semver
args=""
for version in $(query_all_versions); do args="${args} -v \"${version}\""; done
args="${args} -r \"${requested_version}\""
# Find all versions that satisfy.
satisfying_versions=$(eval $bp_dir/vendor/semver/bin/semver ${args} || echo "")
# Use the latest one.
node_version=$(echo "$satisfying_versions" | tail -n 1)
# Bail if no matching version was found
if ! test $node_version; then
error "node ${requested_version} not found among available versions on nodejs.org/dist"
fi
fi
status "Requested node range: $semver_range"
status "Resolved node version: $node_version"
fi
# Download node from Heroku's S3 mirror of nodejs.org/dist
status "Downloading and installing node"
node_url="http://s3pository.heroku.com/node/v$node_version/node-v$node_version-linux-x64.tar.gz"
curl $node_url -s -o - | tar xzf - -C $build_dir
# Move node into ./vendor and make it executable
mkdir -p $build_dir/vendor
mv $build_dir/node-v$node_version-linux-x64 $build_dir/vendor/node
chmod +x $build_dir/vendor/node/bin/*
PATH=$PATH:$build_dir/vendor/node/bin
# Run subsequent node/npm commands from the build path
cd $build_dir
# Configure cache directory
package_checksum=$(cat $build_dir/package.json | md5sum | awk '{print $1}')
cache_dir="$cache_basedir/$node_version/$package_checksum"
cache_dir="$cache_basedir/$package_checksum"
# Restore from cache if node and package.json haven't changed
# Restore from cache if package.json hasn't changed
if test -d $cache_dir; then
status "package.json and node version unchanged since last build"
status "Restoring node v$node_version and node_modules from cache"
status "package.json unchanged since last build"
status "Restoring node_modules from cache"
test -d $cache_dir/node_modules && cp -r $cache_dir/node_modules $build_dir/
cp -r $cache_dir/vendor/node $build_dir/vendor/
# cp -r $cache_dir/vendor/node $build_dir/vendor/
else
if [ $stable_version != $node_version ]; then
status "Downloading and installing node v$node_version"
download_and_install_node $node_version
fi
status "Installing dependencies"
npm install --production | indent
status "Rebuilding dependencies"
npm rebuild | indent
status "Installing dependencies"
npm install --production | indent
status "Caching node and node_modules for future builds"
status "Caching node_modules for future builds"
rm -rf $cache_dir
mkdir -p $cache_dir
mkdir -p $cache_dir/vendor
# mkdir -p $cache_dir/vendor
test -d $build_dir/node_modules && cp -r $build_dir/node_modules $cache_dir/
cp -r $build_dir/vendor/node $cache_dir/vendor/
# cp -r $build_dir/vendor/node $cache_dir/vendor/
fi
# Update the PATH
status "Building runtime environment"
mkdir -p $build_dir/.profile.d
echo "export PATH=\"\$HOME/vendor/node/bin:$HOME/bin:\$HOME/node_modules/.bin:\$PATH\"" > $build_dir/.profile.d/nodejs.sh
echo "export PATH=\"\$HOME/vendor/node/bin:\$HOME/bin:\$HOME/node_modules/.bin:\$PATH\"" > $build_dir/.profile.d/nodejs.sh
# Post to nomnom
curl \
......
......@@ -2,7 +2,7 @@
# See CONTRIBUTING.md for info on running these tests.
testDetectWithPackageJson() {
detect "package-json-stable-version"
detect "stable-node"
assertCaptured "Node.js"
assertCapturedSuccess
}
......@@ -12,43 +12,65 @@ testDetectWithoutPackageJson() {
assertCapturedError 1 ""
}
testPackageJsonWithoutVersion() {
compile "package-json-noversion"
assertCaptured "WARNING: No node version specified"
testNoVersion() {
compile "no-version"
assertCaptured "PRO TIP: Specify a node version in package.json"
assertCaptured "Defaulting to latest stable node"
assertCapturedSuccess
}
testPackageJsonWithStableVersion() {
compile "package-json-stable-version"
assertNotCaptured "WARNING: No node version specified"
assertCaptured "satisfies engines.node"
testDangerousRangeStar() {
compile "dangerous-range-star"
assertCaptured "PRO TIP: Avoid using semver ranges like '*'"
assertCaptured "Requested node range: *"
assertCaptured "Resolved node version: 0.10"
assertCapturedSuccess
}
testPackageJsonWithUnstableVersion() {
compile "package-json-unstable-version"
assertCaptured "Downloading and installing node v0.11"
testDangerousRangeGreaterThan() {
compile "dangerous-range-greater-than"
assertCaptured "PRO TIP: Avoid using semver ranges starting with '>'"
assertCaptured "Requested node range: >"
assertCaptured "Resolved node version: 0.10."
assertCapturedSuccess
}
testPackageJsonWithInvalidVersion() {
compile "package-json-invalidversion"
assertCapturedError 1 "not found among available versions"
testStableVersion() {
compile "stable-node"
assertNotCaptured "PRO TIP"
assertCaptured "Resolved node version"
assertCapturedSuccess
}
testUnstableVersion() {
compile "unstable-version"
assertCaptured "Requested node range: >0.11.0"
assertCaptured "Resolved node version: 0.11."
assertCapturedSuccess
}
# testInvalidVersion() {
# compile "invalid-node-version"
# assertCapturedError 1 "not found among available versions"
# }
# testInvalidVersion() {
# compile "invalid-dependency"
# assertCapturedError 1 "not in the npm registry"
# }
testProfileCreated() {
compile "package-json-stable-version"
compile "stable-node"
assertCaptured "Building runtime environment"
assertFile "export PATH=\"\$HOME/vendor/node/bin:$HOME/bin:\$HOME/node_modules/.bin:\$PATH\"" ".profile.d/nodejs.sh"
assertFile "export PATH=\"\$HOME/vendor/node/bin:\$HOME/bin:\$HOME/node_modules/.bin:\$PATH\"" ".profile.d/nodejs.sh"
assertCapturedSuccess
}
testNodeModulesCached() {
cache=$(mktmpdir)
compile "node-modules-caching" $cache
compile "caching" $cache
assertCaptured "Caching node"
assertEquals "1" "$(ls -1 $cache/0.10.18 | wc -l)"
assertEquals "1" "$(ls -1 $cache/ | wc -l)"
}
# Pending
......
......@@ -7,6 +7,6 @@
"url" : "http://github.com/example/example.git"
},
"engines": {
"node": ">0.10.0"
"node": ">0.4"
}
}
{
"name": "node-buildpack-test-app",
"version": "0.0.1",
"description": "node buildpack integration test app",
"repository" : {
"type" : "git",
"url" : "http://github.com/example/example.git"
},
"engines": {
"node": "*"
}
}
{
"name": "node-buildpack-test-app",
"version": "0.0.1",
"description": "node buildpack integration test app",
"repository" : {
"type" : "git",
"url" : "http://github.com/example/example.git"
},
"engines": {
"node": "~0.10.20"
},
"dependencies": {
"some-crazy-dep-that-doesnt-exist": "*"
}
}
A fake README, to keep npm from polluting stderr.
\ No newline at end of file
A fake README, to keep npm from polluting stderr.
\ No newline at end of file
{
"name": "node-buildpack-test-app",
"version": "0.0.1",
"description": "node buildpack integration test app",
"repository" : {
"type" : "git",
"url" : "http://github.com/example/example.git"
},
"engines": {
"node": "~0.10.0"
}
}
A fake README, to keep npm from polluting stderr.
\ No newline at end of file
File added
#!/usr/bin/env node
//
// json -- a 'json' command for massaging JSON on the command line
//
// See <https://github.com/trentm/json>.
//
var VERSION = "6.0.1";
var p = console.warn;
var util = require('util');
var assert = require('assert');
var path = require('path');
var vm = require('vm');
var fs = require('fs');
var warn = console.warn;
var EventEmitter = require('events').EventEmitter;
//--- exports for module usage
exports.main = main;
exports.getVersion = getVersion;
exports.parseLookup = parseLookup;
// As an exported API, these are still experimental:
exports.lookupDatum = lookupDatum;
exports.printDatum = printDatum; // DEPRECATED
//---- globals and constants
// Output modes.
var OM_JSONY = 1;
var OM_JSON = 2;
var OM_INSPECT = 3;
var OM_COMPACT = 4;
var OM_FROM_NAME = {
"jsony": OM_JSONY,
"json": OM_JSON,
"inspect": OM_INSPECT,
"compact": OM_COMPACT
}
//---- support functions
function getVersion() {
return VERSION;
}
/**
* Return a *shallow* copy of the given object.
*
* Only support objects that you get out of JSON, i.e. no functions.
*/
function objCopy(obj) {
var copy;
if (Array.isArray(obj)) {
copy = obj.slice();
} else if (typeof(obj) === 'object') {
copy = {};
Object.keys(obj).forEach(function (k) {
copy[k] = obj[k];
});
} else {
copy = obj; // immutable type
}
return copy;
}
if (util.format) {
format = util.format;
} else {
// From <https://github.com/joyent/node/blob/master/lib/util.js#L22>:
var formatRegExp = /%[sdj%]/g;
function format(f) {
if (typeof f !== 'string') {
var objects = [];
for (var i = 0; i < arguments.length; i++) {
objects.push(util.inspect(arguments[i]));
}
return objects.join(' ');
}
var i = 1;
var args = arguments;
var len = args.length;
var str = String(f).replace(formatRegExp, function(x) {
if (i >= len) return x;
switch (x) {
case '%s': return String(args[i++]);
case '%d': return Number(args[i++]);
case '%j': return JSON.stringify(args[i++]);
case '%%': return '%';
default:
return x;
}
});
for (var x = args[i]; i < len; x = args[++i]) {
if (x === null || typeof x !== 'object') {
str += ' ' + x;
} else {
str += ' ' + util.inspect(x);
}
}
return str;
};
}
/**
* Parse the given string into a JS string. Basically: handle escapes.
*/
function _parseString(s) {
var quoted = '"' + s.replace(/\\"/, '"').replace('"', '\\"') + '"';
return eval(quoted);
}
// json_parse.js (<https://github.com/douglascrockford/JSON-js>)
// START json_parse
var json_parse=function(){"use strict";var a,b,c={'"':'"',"\\":"\\","/":"/",b:"\b",f:"\f",n:"\n",r:"\r",t:"\t"},d,e=function(b){throw{name:"SyntaxError",message:b,at:a,text:d}},f=function(c){return c&&c!==b&&e("Expected '"+c+"' instead of '"+b+"'"),b=d.charAt(a),a+=1,b},g=function(){var a,c="";b==="-"&&(c="-",f("-"));while(b>="0"&&b<="9")c+=b,f();if(b==="."){c+=".";while(f()&&b>="0"&&b<="9")c+=b}if(b==="e"||b==="E"){c+=b,f();if(b==="-"||b==="+")c+=b,f();while(b>="0"&&b<="9")c+=b,f()}a=+c;if(!isFinite(a))e("Bad number");else return a},h=function(){var a,d,g="",h;if(b==='"')while(f()){if(b==='"')return f(),g;if(b==="\\"){f();if(b==="u"){h=0;for(d=0;d<4;d+=1){a=parseInt(f(),16);if(!isFinite(a))break;h=h*16+a}g+=String.fromCharCode(h)}else if(typeof c[b]=="string")g+=c[b];else break}else g+=b}e("Bad string")},i=function(){while(b&&b<=" ")f()},j=function(){switch(b){case"t":return f("t"),f("r"),f("u"),f("e"),!0;case"f":return f("f"),f("a"),f("l"),f("s"),f("e"),!1;case"n":return f("n"),f("u"),f("l"),f("l"),null}e("Unexpected '"+b+"'")},k,l=function(){var a=[];if(b==="["){f("["),i();if(b==="]")return f("]"),a;while(b){a.push(k()),i();if(b==="]")return f("]"),a;f(","),i()}}e("Bad array")},m=function(){var a,c={};if(b==="{"){f("{"),i();if(b==="}")return f("}"),c;while(b){a=h(),i(),f(":"),Object.hasOwnProperty.call(c,a)&&e('Duplicate key "'+a+'"'),c[a]=k(),i();if(b==="}")return f("}"),c;f(","),i()}}e("Bad object")};return k=function(){i();switch(b){case"{":return m();case"[":return l();case'"':return h();case"-":return g();default:return b>="0"&&b<="9"?g():j()}},function(c,f){var g;return d=c,a=0,b=" ",g=k(),i(),b&&e("Syntax error"),typeof f=="function"?function h(a,b){var c,d,e=a[b];if(e&&typeof e=="object")for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=h(e,c),d!==undefined?e[c]=d:delete e[c]);return f.call(a,b,e)}({"":g},""):g}}();
// END json_parse
function printHelp() {
util.puts("Usage:");
util.puts(" <something generating JSON on stdout> | json [OPTIONS] [LOOKUPS...]");
util.puts(" json -f FILE [OPTIONS] [LOOKUPS...]");
util.puts("");
util.puts("Pipe in your JSON for pretty-printing, JSON validation, filtering, ");
util.puts("and modification. Supply one or more `LOOKUPS` to extract a ");
util.puts("subset of the JSON. HTTP header blocks are skipped by default.");
util.puts("Roughly in order of processing, features are:");
util.puts("");
util.puts("Grouping:");
util.puts(" Use '-g' or '--group' to group adjacent objects, separated by");
util.puts(" by no space or a by a newline, or adjacent arrays, separate by");
util.puts(" by a newline. This can be helpful for, e.g.: ");
util.puts(" $ cat *.json | json -g ... ");
util.puts(" and similar.");
util.puts("");
util.puts("Execution:");
util.puts(" Use the '-e CODE' option to execute JavaScript code on the input JSON.");
util.puts(" $ echo '{\"name\":\"trent\",\"age\":38}' | json -e 'age++'");
util.puts(" {");
util.puts(" \"name\": \"trent\",");
util.puts(" \"age\": 39");
util.puts(" }");
util.puts(" If input is an array, this will automatically process each");
util.puts(" item separately.");
util.puts("");
util.puts("Conditional filtering:");
util.puts(" Use the '-c CODE' option to filter the input JSON.");
util.puts(" $ echo '[{\"age\":38},{\"age\":4}]' | json -c 'age>21'");
util.puts(" [{\"age\":38}]");
util.puts(" If input is an array, this will automatically process each");
util.puts(" item separately. Note: 'CODE' is JavaScript code.");
util.puts("");
util.puts("Lookups:");
util.puts(" Use lookup arguments to extract particular values:");
util.puts(" $ echo '{\"name\":\"trent\",\"age\":38}' | json name");
util.puts(" trent");
util.puts("");
util.puts(" Use '-a' for *array processing* of lookups and *tabular output*:");
util.puts(" $ echo '{\"name\":\"trent\",\"age\":38}' | json name age");
util.puts(" trent");
util.puts(" 38");
util.puts(" $ echo '[{\"name\":\"trent\",\"age\":38},");
util.puts(" {\"name\":\"ewan\",\"age\":4}]' | json -a name age");
util.puts(" trent 38");
util.puts(" ewan 4");
util.puts("");
util.puts("In-place editing:");
util.puts(" Use '-I, --in-place' to edit a file in place:");
util.puts(" $ json -I -f config.json # reformat");
util.puts(" $ json -I -f config.json -c 'this.logLevel=\"debug\"' # add field");
util.puts("");
util.puts("Pretty-printing:");
util.puts(" Output is 'jsony' by default: 2-space indented JSON, except a");
util.puts(" single string value is printed without quotes.");
util.puts(" $ echo '{\"name\": \"trent\", \"age\": 38}' | json");
util.puts(" {");
util.puts(" \"name\": \"trent\",");
util.puts(" \"age\": 38");
util.puts(" }");
util.puts(" $ echo '{\"name\": \"trent\", \"age\": 38}' | json name");
util.puts(" trent");
util.puts("");
util.puts(" Use '-j' or '-o json' for explicit JSON, '-o json-N' for N-space indent:");
util.puts(" $ echo '{\"name\": \"trent\", \"age\": 38}' | json -o json-0");
util.puts(" {\"name\":\"trent\",\"age\":38}");
util.puts("");
util.puts("Options:");
util.puts(" -h, --help Print this help info and exit.");
util.puts(" --version Print version of this command and exit.");
util.puts(" -q, --quiet Don't warn if input isn't valid JSON.");
util.puts("");
util.puts(" -f FILE Path to a file to process. If not given, then");
util.puts(" stdin is used.");
util.puts(" -I, --in-place In-place edit of the file given with '-f'.");
util.puts(" Lookups are not allow with in-place editing");
util.puts(" because it makes it too easy to lose content.");
util.puts("");
util.puts(" -H Drop any HTTP header block (as from `curl -i ...`).");
util.puts(" -g, --group Group adjacent objects or arrays into an array.");
util.puts(" --merge Merge adjacent objects into one. Keys in last ");
util.puts(" object win.");
util.puts(" --deep-merge Same as '--merge', but will recurse into objects ");
util.puts(" under the same key in both.")
util.puts(" -a, --array Process input as an array of separate inputs");
util.puts(" and output in tabular form.");
util.puts(" -A Process input as a single object, i.e. stop");
util.puts(" '-e' and '-c' automatically processing each");
util.puts(" item of an input array.");
util.puts(" -d DELIM Delimiter char for tabular output (default is ' ').");
util.puts(" -D DELIM Delimiter char between lookups (default is '.'). E.g.:");
util.puts(" $ echo '{\"a.b\": {\"b\": 1}}' | json -D / a.b/b");
util.puts("");
util.puts(" -e CODE Execute the given JavaScript code on the input. If input");
util.puts(" is an array, then each item of the array is processed");
util.puts(" separately (use '-A' to override).");
util.puts(" -c CODE Filter the input with JavaScript `CODE`. If `CODE`");
util.puts(" returns false-y, then the item is filtered out. If");
util.puts(" input is an array, then each item of the array is ");
util.puts(" processed separately (use '-A' to override).");
util.puts("");
util.puts(" -k, --keys Output the input object's keys.");
util.puts(" -n, --validate Just validate the input (no processing or output).");
util.puts(" Use with '-q' for silent validation (exit status).");
util.puts("");
util.puts(" -o, --output MODE Specify an output mode. One of");
util.puts(" jsony (default): JSON with string quotes elided");
util.puts(" json: JSON output, 2-space indent");
util.puts(" json-N: JSON output, N-space indent, e.g. 'json-4'");
util.puts(" inspect: node.js `util.inspect` output");
util.puts(" -i shortcut for `-o inspect`");
util.puts(" -j shortcut for `-o json`");
util.puts("");
util.puts("See <http://trentm.com/json> for more docs and ");
util.puts("<https://github.com/trentm/json> for project details.");
}
/**
* Parse the command-line options and arguments into an object.
*
* {
* 'args': [...] // arguments
* 'help': true, // true if '-h' option given
* // etc.
* }
*
* @return {Object} The parsed options. `.args` is the argument list.
* @throws {Error} If there is an error parsing argv.
*/
function parseArgv(argv) {
var parsed = {
args: [],
help: false,
quiet: false,
dropHeaders: false,
exeSnippets: [],
condSnippets: [],
outputMode: OM_JSONY,
jsonIndent: 2,
array: null,
delim: ' ',
lookupDelim: '.',
outputKeys: false,
group: false,
merge: null, // --merge -> "shallow", --deep-merge -> "deep"
inputFiles: [],
validate: false,
inPlace: false
};
// Turn '-iH' into '-i -H', except for argument-accepting options.
var args = argv.slice(2); // drop ['node', 'scriptname']
var newArgs = [];
var optTakesArg = {'d': true, 'o': true, 'D': true};
for (var i = 0; i < args.length; i++) {
if (args[i] === '--') {
newArgs = newArgs.concat(args.slice(i));
break;
}
if (args[i].charAt(0) === "-" && args[i].charAt(1) !== '-' && args[i].length > 2) {
var splitOpts = args[i].slice(1).split("");
for (var j = 0; j < splitOpts.length; j++) {
newArgs.push('-' + splitOpts[j])
if (optTakesArg[splitOpts[j]]) {
var optArg = splitOpts.slice(j+1).join("");
if (optArg.length) {
newArgs.push(optArg);
}
break;
}
}
} else {
newArgs.push(args[i]);
}
}
args = newArgs;
endOfOptions = false;
while (args.length > 0) {
var arg = args.shift();
switch(arg) {
case "--":
endOfOptions = true;
break;
case "-h": // display help and exit
case "--help":
parsed.help = true;
break;
case "--version":
parsed.version = true;
break;
case "-q":
case "--quiet":
parsed.quiet = true;
break;
case "-H": // drop any headers
parsed.dropHeaders = true;
break;
case "-o":
case "--output":
var name = args.shift();
if (!name) {
throw new Error("no argument given for '-o|--output' option");
}
var idx = name.lastIndexOf('-');
if (idx !== -1) {
var indent = Number(name.slice(idx+1));
if (! isNaN(indent)) {
parsed.jsonIndent = indent;
name = name.slice(0, idx);
}
}
parsed.outputMode = OM_FROM_NAME[name];
if (parsed.outputMode === undefined) {
throw new Error("unknown output mode: '"+name+"'");
}
break;
case "-I":
case "--in-place":
parsed.inPlace = true;
break;
case "-i": // output with util.inspect
parsed.outputMode = OM_INSPECT;
break;
case "-j": // output with JSON.stringify
parsed.outputMode = OM_JSON;
break;
case "-a":
case "--array":
parsed.array = true;
break;
case "-A":
parsed.array = false;
break;
case "-d":
parsed.delim = _parseString(args.shift());
break;
case "-D":
parsed.lookupDelim = args.shift();
if (parsed.lookupDelim.length !== 1) {
throw new Error(format(
"invalid lookup delim '%s' (must be a single char)",
parsed.lookupDelim));
}
break;
case "-e":
parsed.exeSnippets.push(args.shift());
break;
case "-c":
parsed.condSnippets.push(args.shift());
break;
case "-k":
case "--keys":
parsed.outputKeys = true;
break;
case "-g":
case "--group":
parsed.group = true;
break;
case "--merge":
parsed.merge = "shallow";
break;
case "--deep-merge":
parsed.merge = "deep";
break;
case "-f":
parsed.inputFiles.push(args.shift());
break;
case "-n":
case "--validate":
parsed.validate = true;
break;
default: // arguments
if (!endOfOptions && arg.length > 0 && arg[0] === '-') {
throw new Error("unknown option '"+arg+"'");
}
parsed.args.push(arg);
break;
}
}
if (parsed.group && parsed.merge) {
throw new Error("cannot use -g|--group and --merge options together");
}
if (parsed.outputKeys && parsed.args.length > 0) {
throw new Error("cannot use -k|--keys option and lookup arguments together");
}
if (parsed.inPlace && parsed.inputFiles.length !== 1) {
throw new Error("must specify exactly one file with '-f FILE' to "
+ "use -I/--in-place");
}
if (parsed.inPlace && parsed.args.length > 0) {
throw new Error("lookups cannot be specified with in-place editing "
+ "(-I/--in-place), too easy to lose content");
}
return parsed;
}
/**
* Streams chunks from given file paths or stdin.
*
* @param opts {Object} Parsed options.
* @returns {Object} An emitter that emits 'chunk', 'error', and 'end'.
* - `emit('chunk', chunk, [obj])` where chunk is a complete block of JSON
* ready to parse. If `obj` is provided, it is the already parsed
* JSON.
* - `emit('error', error)` when an underlying stream emits an error
* - `emit('end')` when all streams are done
*/
function chunkEmitter(opts) {
var emitter = new EventEmitter();
var streaming = true;
var chunks = [];
var leftover = '';
var finishedHeaders = false;
function stripHeaders(s) {
// Take off a leading HTTP header if any and pass it through.
while (true) {
if (s.slice(0,5) === "HTTP/") {
var index = s.indexOf('\r\n\r\n');
var sepLen = 4;
if (index == -1) {
index = s.indexOf('\n\n');
sepLen = 2;
}
if (index != -1) {
if (! opts.dropHeaders) {
emit(s.slice(0, index+sepLen));
}
var is100Continue = (s.slice(0, 21) === "HTTP/1.1 100 Continue");
s = s.slice(index+sepLen);
if (is100Continue) {
continue;
}
finishedHeaders = true;
}
} else {
finishedHeaders = true;
}
break;
}
//console.warn("stripHeaders done, finishedHeaders=%s", finishedHeaders)
return s;
}
function emitChunks(block, emitter) {
//console.warn("emitChunks start: block='%s'", block)
var splitter = /(})(\s*\n\s*)?({\s*")/;
var leftTrimmedBlock = block.trimLeft();
if (leftTrimmedBlock && leftTrimmedBlock[0] !== '{') {
// Currently (at least), only support streaming consecutive *objects*.
streaming = false;
chunks.push(block);
return '';
}
/* Example:
* > '{"a":"b"}\n{"a":"b"}\n{"a":"b"}'.split(/(})(\s*\n\s*)?({\s*")/)
* [ '{"a":"b"',
* '}',
* '\n',
* '{"',
* 'a":"b"',
* '}',
* '\n',
* '{"',
* 'a":"b"}' ]
*/
var bits = block.split(splitter);
//console.warn("emitChunks: bits (length %d): %j", bits.length, bits);
if (bits.length === 1) {
/*
* An unwanted side-effect of using a regex to find newline-separated
* objects *with a regex*, is that we are looking for the end of one
* object leading into the start of a another. That means that we
* can end up buffering a complete object until a subsequent one
* comes in. If the input stream has large delays between objects, then
* this is unwanted buffering.
*
* One solution would be full stream parsing of objects a la
* <https://github.com/creationix/jsonparse>. This would nicely also
* remove the artibrary requirement that the input stream be newline
* separated. jsonparse apparently has some issues tho, so I don't
* want to use it right now. It also isn't *small* so not sure I
* want to inline it (`json` doesn't have external deps).
*
* An alternative: The block we have so far one of:
* 1. some JSON that we don't support grouping (e.g. a stream of
* non-objects),
* 2. a JSON object fragment, or
* 3. a complete JSON object (with a possible trailing '{')
*
* If #3, then we can just emit this as a chunk right now.
*
* TODO(PERF): Try out avoiding the first more complete regex split
* for a presumed common case of single-line newline-separated JSON
* objects (e.g. a bunyan log).
*/
// An object must end with '}'. This is an early out to avoid
// `JSON.parse` which I *presuming* is slower.
var trimmed = block.split(/\s*\r?\n/)[0];
//console.warn("XXX trimmed: '%s'", trimmed);
if (trimmed[trimmed.length - 1] === '}') {
var obj;
try {
obj = JSON.parse(block);
} catch (e) {
/* pass through */
}
if (obj !== undefined) {
// Emit the parsed `obj` to avoid re-parsing it later.
emitter.emit('chunk', block, obj);
block = '';
}
}
return block;
} else {
var n = bits.length - 2;
emitter.emit('chunk', bits[0] + bits[1]);
for (var i = 3; i < n; i += 4) {
emitter.emit('chunk', bits[i] + bits[i+1] + bits[i+2]);
}
return bits[n] + bits[n+1];
}
}
function addDataListener(stream) {
stream.on('data', function (chunk) {
var s = leftover + chunk;
if (!finishedHeaders) {
s = stripHeaders(s);
}
if (!finishedHeaders) {
leftover = s;
} else {
if (!streaming) {
chunks.push(chunk);
return;
}
leftover = emitChunks(s, emitter);
//console.warn("XXX leftover: '%s'", leftover)
}
});
}
if (opts.inputFiles.length > 0) {
// Stream each file in order.
var i = 0;
function addErrorListener(file) {
file.on('error', function (err) {
emitter.emit(
'error',
format('could not read "%s": %s', opts.inputFiles[i], e)
);
});
}
function addEndListener(file) {
file.on('end', function () {
if (i < opts.inputFiles.length) {
var next = opts.inputFiles[i++];
var nextFile = fs.createReadStream(next, {encoding: 'utf8'});
addErrorListener(nextFile);
addEndListener(nextFile);
addDataListener(nextFile);
} else {
if (!streaming) {
emitter.emit('chunk', chunks.join(''));
} else if (leftover) {
leftover = emitChunks(leftover, emitter);
emitter.emit('chunk', leftover);
}
emitter.emit('end');
}
});
}
var first = fs.createReadStream(opts.inputFiles[i++], {encoding: 'utf8'});
addErrorListener(first);
addEndListener(first);
addDataListener(first);
} else {
// Streaming from stdin.
var stdin = process.openStdin();
stdin.setEncoding('utf8');
addDataListener(stdin);
stdin.on('end', function () {
if (!streaming) {
emitter.emit('chunk', chunks.join(''));
} else if (leftover) {
leftover = emitChunks(leftover, emitter);
emitter.emit('chunk', leftover);
}
emitter.emit('end');
});
}
return emitter;
}
/**
* Get input from either given file paths or stdin. If `opts.inPlace` then
* this calls the callback once for each `opts.inputFiles`.
*
* @param opts {Object} Parsed options.
* @param callback {Function} `function (err, content, filename)` where err
* is an error string if there was a problem, `content` is the read
* content and `filename` is the associated file name from which content
* was loaded if applicable. `filename` is only included when
* `opts.inPlace`.
*/
function getInput(opts, callback) {
if (opts.inputFiles.length === 0) {
// Read from stdin.
var chunks = [];
var stdin = process.openStdin();
stdin.setEncoding('utf8');
stdin.on('data', function (chunk) {
chunks.push(chunk);
});
stdin.on('end', function () {
callback(null, chunks.join(''));
});
} else if (opts.inPlace) {
for (var i = 0; i < opts.inputFiles.length; i++) {
var file = opts.inputFiles[i];
var content;
try {
content = fs.readFileSync(file, 'utf8');
} catch (e) {
callback(e, null, file);
}
if (content) {
callback(null, content, file);
}
}
} else {
// Read input files.
var i = 0;
var chunks = [];
try {
for (; i < opts.inputFiles.length; i++) {
chunks.push(fs.readFileSync(opts.inputFiles[i], 'utf8'));
}
} catch (e) {
return callback(
format('could not read "%s": %s', opts.inputFiles[i], e));
}
callback(null, chunks.join(''));
}
}
function isInteger(s) {
return (s.search(/^-?[0-9]+$/) == 0);
}
// Parse a lookup string into a list of lookup bits. E.g.:
// "a.b.c" -> ["a","b","c"]
// "b['a']" -> ["b","['a']"]
// Optionally receives an alternative lookup delimiter (other than '.')
function parseLookup(lookup, lookupDelim) {
//var debug = console.warn;
var debug = function () {};
var bits = [];
debug("\n*** "+lookup+" ***")
bits = [];
lookupDelim = lookupDelim || ".";
var bit = "";
var states = [null];
var escaped = false;
var ch = null;
for (var i=0; i < lookup.length; ++i) {
var escaped = (!escaped && ch === '\\');
var ch = lookup[i];
debug("-- i="+i+", ch="+JSON.stringify(ch)+" escaped="+JSON.stringify(escaped))
debug("states: "+JSON.stringify(states))
if (escaped) {
bit += ch;
continue;
}
switch (states[states.length-1]) {
case null:
switch (ch) {
case '"':
case "'":
states.push(ch);
bit += ch;
break;
case '[':
states.push(ch);
if (bit !== "") {
bits.push(bit);
bit = ""
}
bit += ch;
break;
case lookupDelim:
if (bit !== "") {
bits.push(bit);
bit = ""
}
break;
default:
bit += ch;
break;
}
break;
case '[':
bit += ch;
switch (ch) {
case '"':
case "'":
case '[':
states.push(ch);
break;
case ']':
states.pop();
if (states[states.length-1] === null) {
bits.push(bit);
bit = ""
}
break;
}
break;
case '"':
bit += ch;
switch (ch) {
case '"':
states.pop();
if (states[states.length-1] === null) {
bits.push(bit);
bit = ""
}
break;
}
break;
case "'":
bit += ch;
switch (ch) {
case "'":
states.pop();
if (states[states.length-1] === null) {
bits.push(bit);
bit = ""
}
break;
}
break;
}
debug("bit: "+JSON.stringify(bit))
debug("bits: "+JSON.stringify(bits))
}
if (bit !== "") {
bits.push(bit);
bit = ""
}
debug(JSON.stringify(lookup)+" -> "+JSON.stringify(bits))
return bits
}
/**
* Parse the given stdin input into:
* {
* "error": ... error object if there was an error ...,
* "datum": ... parsed object if content was JSON ...
* }
*
* @param buffer {String} The text to parse as JSON.
* @param obj {Object} Optional. Some streaming code paths will provide
* this, an already parsed JSON object. Use this to avoid reparsing.
* @param group {Boolean} Default false. If true, then non-JSON input
* will be attempted to be "arrayified" (see inline comment).
* @param merge {Boolean} Default null. Can be "shallow" or "deep". An
* attempt will be made to interpret the input as adjacent objects to
* be merged, last key wins. See inline comment for limitations.
*/
function parseInput(buffer, obj, group, merge) {
if (obj) {
return {datum: obj};
} else if (group) {
/**
* Special case: Grouping (previously called auto-arrayification)
* of unjoined list of objects:
* {"one": 1}{"two": 2}
* and auto-concatenation of unjoined list of arrays:
* ["a", "b"]["c", "d"]
*
* This can be nice to process a stream of JSON objects generated from
* multiple calls to another tool or `cat *.json | json`.
*
* Rules:
* - Only JS objects and arrays. Don't see strong need for basic
* JS types right now and this limitation simplifies.
* - The break between JS objects has to include a newline:
* {"one": 1}
* {"two": 2}
* or no spaces at all:
* {"one": 1}{"two": 2}
* I.e., not this:
* {"one": 1} {"two": 2}
* This condition should be fine for typical use cases and ensures
* no false matches inside JS strings.
* - The break between JS *arrays* has to include a newline:
* ["one", "two"]
* ["three"]
* The "no spaces" case is NOT supported for JS arrays as of v6.0.0
* because <https://github.com/trentm/json/issues/55> shows that that
* is not safe.
*/
var newBuffer = buffer;
[/(})\s*\n\s*({)/g, /(})({")/g].forEach(function (pat) {
newBuffer = newBuffer.replace(pat, "$1,\n$2");
});
[/(\])\s*\n\s*(\[)/g].forEach(function (pat) {
newBuffer = newBuffer.replace(pat, ",\n");
});
newBuffer = newBuffer.trim();
if (newBuffer[0] !== '[') {
newBuffer = '[\n' + newBuffer;
}
if (newBuffer.slice(-1) !== ']') {
newBuffer = newBuffer + '\n]\n';
}
try {
return {datum: JSON.parse(newBuffer)};
} catch (e2) {
return {error: e2};
}
} else if (merge) {
// See the "Rules" above for limitations on boundaries for "adjacent"
// objects: KISS.
var newBuffer = buffer;
[/(})\s*\n\s*({)/g, /(})({")/g].forEach(function (pat) {
newBuffer = newBuffer.replace(pat, "$1,\n$2");
});
newBuffer = '[\n' + newBuffer + '\n]\n';
var objs;
try {
objs = JSON.parse(newBuffer);
} catch(e) {
return {error: e};
}
var merged = objs[0];
if (merge === "shallow") {
for (var i = 1; i < objs.length; i++) {
var obj = objs[i];
Object.keys(obj).forEach(function (k) {
merged[k] = obj[k];
});
}
} else if (merge === "deep") {
function deepExtend(a, b) {
Object.keys(b).forEach(function (k) {
if (a[k] && b[k] && toString.call(a[k]) === '[object Object]'
&& toString.call(b[k]) === '[object Object]') {
deepExtend(a[k], b[k])
} else {
a[k] = b[k];
}
});
}
for (var i = 1; i < objs.length; i++) {
deepExtend(merged, objs[i]);
}
} else {
throw new Error(format('unknown value for "merge": "%s"', merge));
}
return {datum: merged};
} else {
try {
return {datum: JSON.parse(buffer)};
} catch(e) {
return {error: e};
}
}
}
/**
* Apply a lookup to the given datum.
*
* @argument datum {Object}
* @argument lookup {Array} The parsed lookup (from
* `parseLookup(<string>, <string>)`). Might be empty.
* @returns {Object} The result of the lookup.
*/
function lookupDatum(datum, lookup) {
// Put it back together with some convenience transformations.
var lookupCode = "";
var isJSIdentifier = /^[$A-Za-z_][0-9A-Za-z_]*$/;
var isNegArrayIndex = /^-\d+$/;
for (var i=0; i < lookup.length; i++) {
var bit = lookup[i];
if (bit[0] === '[') {
lookupCode += bit;
// Support Python-style negative array indexing.
} else if (bit === '-1') {
lookupCode += '.slice(-1)[0]';
} else if (isNegArrayIndex.test(bit)) {
lookupCode += format('.slice(%s, %d)[0]', bit, Number(bit) + 1);
} else if (! isJSIdentifier.test(bit)) {
// Allow a non-JS-indentifier token, e.g. `json foo-bar`. This also
// works for array index lookups: `json 0` becomes a `["0"]` lookup.
lookupCode += '["' + bit.replace(/"/g, '\\"') + '"]';
} else {
lookupCode += '.' + bit;
}
}
try {
return vm.runInNewContext("(" + JSON.stringify(datum) + ")" + lookupCode);
} catch (e) {
if (e.name === 'TypeError') {
// Skip the following for a lookup 'foo.bar' where 'foo' is undefined:
// TypeError: Cannot read property 'bar' of undefined
// TODO: Are there other potential TypeError's in here to avoid?
return undefined;
}
throw e;
}
}
/**
* Output the given datasets.
*
* @param datasets {Array} Array of data sets to print, in the form:
* `[ [<datum>, <sep>, <alwaysPrintSep>], ... ]`
* @param filename {String} The filename to which to write the output. If
* not set, then emit to stdout.
* @param headers {String} The HTTP header block string, if any, to emit
* first.
* @param opts {Object} Parsed tool options.
*/
function printDatasets(datasets, filename, headers, opts) {
var isTTY = (filename ? false : process.stdout.isTTY)
var write = emit;
if (filename) {
var tmpPath = path.resolve(path.dirname(filename),
format('.%s-json-%s-%s.tmp', path.basename(filename), process.pid,
Date.now()));
var stats = fs.statSync(filename);
var f = fs.createWriteStream(tmpPath,
{encoding: 'utf8', mode: stats.mode});
write = f.write.bind(f);
}
if (headers && headers.length > 0) {
write(headers)
}
for (var i = 0; i < datasets.length; i++) {
var dataset = datasets[i];
var output = stringifyDatum(dataset[0], opts, isTTY);
var sep = dataset[1];
if (output && output.length) {
write(output);
write(sep);
} else if (dataset[2]) {
write(sep);
}
}
if (filename) {
f.end();
fs.renameSync(tmpPath, filename);
if (! opts.quiet) {
warn('json: updated "%s" in-place', filename);
}
}
}
/**
* Stringify the given datum according to the given output options.
*/
function stringifyDatum(datum, opts, isTTY) {
var output = null;
switch (opts.outputMode) {
case OM_INSPECT:
output = util.inspect(datum, false, Infinity, isTTY);
break;
case OM_JSON:
if (typeof datum !== 'undefined') {
output = JSON.stringify(datum, null, opts.jsonIndent);
}
break;
case OM_COMPACT:
// Dev Note: A still relatively experimental attempt at a more
// compact ouput somewhat a la Python's repr of a dict. I.e. try to
// fit elements on one line as much as reasonable.
if (datum === undefined) {
// pass
} else if (Array.isArray(datum)) {
var bits = ['[\n'];
datum.forEach(function (d) {
bits.push(' ')
bits.push(JSON.stringify(d, null, 0).replace(/,"(?![,:])/g, ', "'));
bits.push(',\n');
});
bits.push(bits.pop().slice(0, -2) + '\n') // drop last comma
bits.push(']');
output = bits.join('');
} else {
output = JSON.stringify(datum, null, 0);
}
break;
case OM_JSONY:
if (typeof datum === 'string') {
output = datum;
} else if (typeof datum !== 'undefined') {
output = JSON.stringify(datum, null, opts.jsonIndent);
}
break;
default:
throw new Error("unknown output mode: "+opts.outputMode);
}
return output;
}
/**
* Print out a single result, considering input options.
*
* @deprecated
*/
function printDatum(datum, opts, sep, alwaysPrintSep) {
var output = stringifyDatum(datum, opts);
if (output && output.length) {
emit(output);
emit(sep);
} else if (alwaysPrintSep) {
emit(sep);
}
}
var stdoutFlushed = true;
function emit(s) {
// TODO:PERF If this is try/catch is too slow (too granular): move up to
// mainline and be sure to only catch this particular error.
try {
stdoutFlushed = process.stdout.write(s);
} catch (e) {
// Handle any exceptions in stdout writing in the "error" event above.
}
}
process.stdout.on("error", function (err) {
if (err.code === "EPIPE") {
// See <https://github.com/trentm/json/issues/9>.
drainStdoutAndExit(0);
} else {
warn(err)
drainStdoutAndExit(1);
}
});
/**
* A hacked up version of "process.exit" that will first drain stdout
* before exiting. *WARNING: This doesn't stop event processing.* IOW,
* callers have to be careful that code following this call isn't
* accidentally executed.
*
* In node v0.6 "process.stdout and process.stderr are blocking when they
* refer to regular files or TTY file descriptors." However, this hack might
* still be necessary in a shell pipeline.
*/
function drainStdoutAndExit(code) {
process.stdout.on('drain', function () {
process.exit(code);
});
if (stdoutFlushed) {
process.exit(code);
}
}
//---- mainline
function main(argv) {
var opts;
try {
opts = parseArgv(argv);
} catch (e) {
warn("json: error: %s", e.message)
return drainStdoutAndExit(1);
}
//warn(opts);
if (opts.help) {
printHelp();
return;
}
if (opts.version) {
util.puts("json " + getVersion());
return;
}
var lookupStrs = opts.args;
if (opts.group && opts.array && opts.outputMode !== OM_JSON) {
// streaming
var chunker = chunkEmitter(opts);
chunker.on('error', function(error) {
warn("json: error: %s", err);
return drainStdoutAndExit(1);
});
chunker.on('chunk', parseChunk);
} else if (opts.inPlace) {
assert.equal(opts.inputFiles.length, 1,
'cannot handle more than one file with -I');
getInput(opts, function (err, content, filename) {
if (err) {
warn("json: error: %s", err)
return drainStdoutAndExit(1);
}
// Take off a leading HTTP header if any and pass it through.
var headers = [];
while (true) {
if (content.slice(0,5) === "HTTP/") {
var index = content.indexOf('\r\n\r\n');
var sepLen = 4;
if (index == -1) {
index = content.indexOf('\n\n');
sepLen = 2;
}
if (index != -1) {
if (! opts.dropHeaders) {
headers.push(content.slice(0, index+sepLen));
}
var is100Continue = (content.slice(0, 21) === "HTTP/1.1 100 Continue");
content = content.slice(index+sepLen);
if (is100Continue) {
continue;
}
}
}
break;
}
parseChunk(content, undefined, filename, headers.join(''));
});
} else {
// not streaming
getInput(opts, function (err, buffer) {
if (err) {
warn("json: error: %s", err)
return drainStdoutAndExit(1);
}
// Take off a leading HTTP header if any and pass it through.
while (true) {
if (buffer.slice(0,5) === "HTTP/") {
var index = buffer.indexOf('\r\n\r\n');
var sepLen = 4;
if (index == -1) {
index = buffer.indexOf('\n\n');
sepLen = 2;
}
if (index != -1) {
if (! opts.dropHeaders) {
emit(buffer.slice(0, index+sepLen));
}
var is100Continue = (buffer.slice(0, 21) === "HTTP/1.1 100 Continue");
buffer = buffer.slice(index+sepLen);
if (is100Continue) {
continue;
}
}
}
break;
}
parseChunk(buffer);
});
}
/**
* Parse a single chunk of JSON. This may be called more than once
* (when streaming or when operating on multiple files).
*
* @param chunk {String} The JSON-encoded string.
* @param obj {Object} Optional. For some code paths while streaming `obj`
* will be provided. This is an already parsed JSON object.
* @param filename {String} Optional. The filename from which this content
* came, if relevant. This is only set if `opts.inPlace`.
* @param headers {String} Optional. Leading HTTP headers, if any to emit.
*/
function parseChunk(chunk, obj, filename, headers) {
// Expect the chunk to be JSON.
if (! chunk.length) {
return;
}
// parseInput() -> {datum: <input object>, error: <error object>}
var input = parseInput(chunk, obj, opts.group, opts.merge);
if (input.error) {
// Doesn't look like JSON. Just print it out and move on.
if (! opts.quiet) {
// Use JSON-js' "json_parse" parser to get more detail on the
// syntax error.
var details = "";
var normBuffer = chunk.replace(/\r\n|\n|\r/, '\n');
try {
json_parse(normBuffer);
details = input.error;
} catch(err) {
// err.at has the position. Get line/column from that.
var at = err.at - 1; // `err.at` looks to be 1-based.
var lines = chunk.split('\n');
var line, col, pos = 0;
for (line = 0; line < lines.length; line++) {
pos += lines[line].length + 1;
if (pos > at) {
col = at - (pos - lines[line].length - 1);
break;
}
}
var spaces = '';
for (var i=0; i<col; i++) {
spaces += '.';
}
details = err.message+" at line "+(line+1)+", column "+(col+1)
+ ":\n "+lines[line]+"\n "+spaces+"^";
}
warn("json: error: input is not JSON: %s", details);
}
if (!opts.validate) {
emit(chunk);
if (chunk.length && chunk[chunk.length-1] !== "\n") {
emit('\n');
}
}
return drainStdoutAndExit(1);
}
if (opts.validate) {
return drainStdoutAndExit(0);
}
var data = input.datum;
// Process: executable (-e).
var i, j;
var exeScripts = [];
for (i = 0; i < opts.exeSnippets.length; i++) {
exeScripts[i] = vm.createScript(opts.exeSnippets[i]);
}
if (!exeScripts.length) {
/* pass */
} else if (opts.array || (opts.array === null && Array.isArray(data))) {
var arrayified = false;
if (!Array.isArray(data)) {
arrayified = true;
data = [data];
}
for (i = 0; i < data.length; i++) {
var datum = data[i];
for (j = 0; j < exeScripts.length; j++) {
exeScripts[j].runInNewContext(datum);
}
}
if (arrayified) {
data = data[0];
}
} else {
for (j = 0; j < exeScripts.length; j++) {
exeScripts[j].runInNewContext(data);
}
}
// Process: conditionals (-c).
var condScripts = [];
for (i = 0; i < opts.condSnippets.length; i++) {
condScripts[i] = vm.createScript(opts.condSnippets[i]);
}
if (!condScripts.length) {
/* pass */
} else if (opts.array || (opts.array === null && Array.isArray(data))) {
var arrayified = false;
if (!Array.isArray(data)) {
arrayified = true;
data = [data];
}
var filtered = [];
for (i = 0; i < data.length; i++) {
var datum = data[i];
var datumCopy = objCopy(datum);
var keep = true;
for (j = 0; j < condScripts.length; j++) {
if (! condScripts[j].runInNewContext(datumCopy)) {
keep = false;
break;
}
}
if (keep) {
filtered.push(datum);
}
}
if (arrayified) {
data = (filtered.length ? filtered[0] : []);
} else {
data = filtered;
}
} else {
var keep = true;
var dataCopy = objCopy(data);
for (j = 0; j < condScripts.length; j++) {
if (! condScripts[j].runInNewContext(dataCopy)) {
keep = false;
break;
}
}
if (!keep) {
data = undefined;
}
}
// Process: lookups
var lookupsAreIndeces = false;
var lookups = lookupStrs.map(function(lookup) {
return parseLookup(lookup, opts.lookupDelim);
});
if (lookups.length) {
if (opts.array) {
if (!Array.isArray(data)) data = [data];
var table = [];
for (j=0; j < data.length; j++) {
var datum = data[j];
var row = {};
for (i=0; i < lookups.length; i++) {
var lookup = lookups[i];
var value = lookupDatum(datum, lookup);
if (value !== undefined) {
row[lookup.join('.')] = value;
}
}
table.push(row);
}
data = table;
} else {
// Special case handling: Note if the "lookups" are indeces into an
// array. This may be used below to change the output representation.
if (Array.isArray(data)) {
lookupsAreIndeces = true;
for (i = 0; i < lookups.length; i++) {
if (lookups[i].length !== 1 || isNaN(Number(lookups[i]))) {
lookupsAreIndeces = false;
break;
}
}
}
var row = {};
for (i = 0; i < lookups.length; i++) {
var lookup = lookups[i];
var value = lookupDatum(data, lookup);
if (value !== undefined) {
row[lookup.join('.')] = value;
}
}
data = row;
}
}
// --keys
if (opts.outputKeys) {
var data = Object.keys(data);
}
// Output
var datasets = [];
if (opts.outputMode === OM_JSON) {
if (lookups.length === 1 && !opts.array) {
// Special case: For JSON output of a *single* lookup, *don't* use
// the full table structure, else there is no way to get string
// quoting for a single value:
// $ echo '{"a": [], "b": "[]"}' | json -j a
// []
// $ echo '{"a": [], "b": "[]"}' | json -j b
// "[]"
// See <https://github.com/trentm/json/issues/35> for why.
data = data[lookups[0].join('.')];
} else if (lookupsAreIndeces) {
// Special case: Lookups that are all indeces into an input array
// are more likely to be wanted as an array of selected items rather
// than a "JSON table" thing that we use otherwise.
var flattened = [];
for (i = 0; i < lookups.length; i++) {
var lookupStr = lookups[i].join('.');
if (data.hasOwnProperty(lookupStr)) {
flattened.push(data[lookupStr])
}
}
data = flattened;
}
// If JSON output mode, then always just output full set of data to
// ensure valid JSON output.
datasets.push([data, '\n', false]);
} else if (lookups.length) {
if (opts.array) {
// Output `data` as a "table" of lookup results.
for (j = 0; j < data.length; j++) {
var row = data[j];
for (i = 0; i < lookups.length-1; i++) {
datasets.push([row[lookups[i].join('.')], opts.delim, true]);
}
datasets.push([row[lookups[i].join('.')], '\n', true]);
}
} else {
for (i = 0; i < lookups.length; i++) {
datasets.push([data[lookups[i].join('.')], '\n', false]);
}
}
} else if (opts.array) {
if (!Array.isArray(data)) data = [data];
for (j = 0; j < data.length; j++) {
datasets.push([data[j], '\n', false]);
}
} else {
// Output `data` as is.
datasets.push([data, '\n', false]);
}
printDatasets(datasets, filename, headers, opts);
}
}
if (require.main === module) {
// HACK guard for <https://github.com/trentm/json/issues/24>.
// We override the `process.stdout.end` guard that core node.js puts in
// place. The real fix is that `.end()` shouldn't be called on stdout
// in node core. Hopefully node v0.6.9 will fix that. Only guard
// for v0.6.0..v0.6.8.
var nodeVer = process.versions.node.split('.').map(Number);
if ([0,6,0] <= nodeVer && nodeVer <= [0,6,8]) {
var stdout = process.stdout;
stdout.end = stdout.destroy = stdout.destroySoon = function() {
/* pass */
};
}
main(process.argv);
}
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.
semver(1) -- The semantic versioner for npm
===========================================
## Usage
$ npm install semver
semver.valid('1.2.3') // true
semver.valid('a.b.c') // false
semver.clean(' =v1.2.3 ') // '1.2.3'
semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true
semver.gt('1.2.3', '9.8.7') // false
semver.lt('1.2.3', '9.8.7') // true
As a command-line utility:
$ semver -h
Usage: semver -v <version> [-r <range>]
Test if version(s) satisfy the supplied range(s),
and sort them.
Multiple versions or ranges may be supplied.
Program exits successfully if any valid version satisfies
all supplied ranges, and prints all satisfying versions.
If no versions are valid, or ranges are not satisfied,
then exits failure.
Versions are printed in ascending order, so supplying
multiple versions to the utility will just sort them.
## Versions
A version is the following things, in this order:
* a number (Major)
* a period
* a number (minor)
* a period
* a number (patch)
* OPTIONAL: a hyphen, followed by a number (build)
* OPTIONAL: a collection of pretty much any non-whitespace characters
(tag)
A leading `"="` or `"v"` character is stripped off and ignored.
## Comparisons
The ordering of versions is done using the following algorithm, given
two versions and asked to find the greater of the two:
* If the majors are numerically different, then take the one
with a bigger major number. `2.3.4 > 1.3.4`
* If the minors are numerically different, then take the one
with the bigger minor number. `2.3.4 > 2.2.4`
* If the patches are numerically different, then take the one with the
bigger patch number. `2.3.4 > 2.3.3`
* If only one of them has a build number, then take the one with the
build number. `2.3.4-0 > 2.3.4`
* If they both have build numbers, and the build numbers are numerically
different, then take the one with the bigger build number.
`2.3.4-10 > 2.3.4-9`
* If only one of them has a tag, then take the one without the tag.
`2.3.4 > 2.3.4-beta`
* If they both have tags, then take the one with the lexicographically
larger tag. `2.3.4-beta > 2.3.4-alpha`
* At this point, they're equal.
## Ranges
The following range styles are supported:
* `>1.2.3` Greater than a specific version.
* `<1.2.3` Less than
* `1.2.3 - 2.3.4` := `>=1.2.3 <=2.3.4`
* `~1.2.3` := `>=1.2.3 <1.3.0`
* `~1.2` := `>=1.2.0 <2.0.0`
* `~1` := `>=1.0.0 <2.0.0`
* `1.2.x` := `>=1.2.0 <1.3.0`
* `1.x` := `>=1.0.0 <2.0.0`
Ranges can be joined with either a space (which implies "and") or a
`||` (which implies "or").
## Functions
* valid(v): Return the parsed version, or null if it's not valid.
* inc(v, release): Return the version incremented by the release type
(major, minor, patch, or build), or null if it's not valid.
### Comparison
* gt(v1, v2): `v1 > v2`
* gte(v1, v2): `v1 >= v2`
* lt(v1, v2): `v1 < v2`
* lte(v1, v2): `v1 <= v2`
* eq(v1, v2): `v1 == v2` This is true if they're logically equivalent,
even if they're not the exact same string. You already know how to
compare strings.
* neq(v1, v2): `v1 != v2` The opposite of eq.
* cmp(v1, comparator, v2): Pass in a comparison string, and it'll call
the corresponding function above. `"==="` and `"!=="` do simple
string comparison, but are included for completeness. Throws if an
invalid comparison string is provided.
* compare(v1, v2): Return 0 if v1 == v2, or 1 if v1 is greater, or -1 if
v2 is greater. Sorts in ascending order if passed to Array.sort().
* rcompare(v1, v2): The reverse of compare. Sorts an array of versions
in descending order when passed to Array.sort().
### Ranges
* validRange(range): Return the valid range or null if it's not valid
* satisfies(version, range): Return true if the version satisfies the
range.
* maxSatisfying(versions, range): Return the highest version in the list
that satisfies the range, or null if none of them do.
#!/usr/bin/env node
// Standalone semver comparison program.
// Exits successfully and prints matching version(s) if
// any supplied version is valid and passes all tests.
var argv = process.argv.slice(2)
, versions = []
, range = []
, gt = []
, lt = []
, eq = []
, semver = require("../semver")
main()
function main () {
if (!argv.length) return help()
while (argv.length) {
var a
switch (a = argv.shift()) {
case "-v": case "--version":
versions.push(argv.shift())
break
case "-r": case "--range":
range.push(argv.shift())
break
case "-h": case "--help": case "-?":
return help()
default:
versions.push(a)
break
}
}
versions = versions.filter(semver.valid)
for (var i = 0, l = range.length; i < l ; i ++) {
versions = versions.filter(function (v) {
return semver.satisfies(v, range[i])
})
if (!versions.length) return fail()
}
return success(versions)
}
function fail () { process.exit(1) }
function success () {
versions.sort(semver.compare)
.map(semver.clean)
.forEach(function (v,i,_) { console.log(v) })
}
function help () {
console.log(["Usage: semver -v <version> [-r <range>]"
,"Test if version(s) satisfy the supplied range(s),"
,"and sort them."
,""
,"Multiple versions or ranges may be supplied."
,""
,"Program exits successfully if any valid version satisfies"
,"all supplied ranges, and prints all satisfying versions."
,""
,"If no versions are valid, or ranges are not satisfied,"
,"then exits failure."
,""
,"Versions are printed in ascending order, so supplying"
,"multiple versions to the utility will just sort them."
].join("\n"))
}
{ "name" : "semver"
, "version" : "1.0.12"
, "description" : "The semantic version parser used by npm."
, "main" : "semver.js"
, "scripts" : { "test" : "tap test.js" }
, "devDependencies": { "tap" : "0.x >=0.0.4" }
, "license" :
{ "type" : "MIT"
, "url" : "https://github.com/isaacs/semver/raw/master/LICENSE" }
, "repository" : "git://github.com/isaacs/node-semver.git"
, "bin" : { "semver" : "./bin/semver" } }
;(function (exports) { // nothing in here is node-specific.
// See http://semver.org/
// This implementation is a *hair* less strict in that it allows
// v1.2.3 things, and also tags that don't begin with a char.
var semver = "\\s*[v=]*\\s*([0-9]+)" // major
+ "\\.([0-9]+)" // minor
+ "\\.([0-9]+)" // patch
+ "(-[0-9]+-?)?" // build
+ "([a-zA-Z-][a-zA-Z0-9-\.:]*)?" // tag
, exprComparator = "^((<|>)?=?)\s*("+semver+")$|^$"
, xRangePlain = "[v=]*([0-9]+|x|X|\\*)"
+ "(?:\\.([0-9]+|x|X|\\*)"
+ "(?:\\.([0-9]+|x|X|\\*)"
+ "([a-zA-Z-][a-zA-Z0-9-\.:]*)?)?)?"
, xRange = "((?:<|>)?=?)?\\s*" + xRangePlain
, exprSpermy = "(?:~>?)"+xRange
, expressions = exports.expressions =
{ parse : new RegExp("^\\s*"+semver+"\\s*$")
, parsePackage : new RegExp("^\\s*([^\/]+)[-@](" +semver+")\\s*$")
, parseRange : new RegExp(
"^\\s*(" + semver + ")\\s+-\\s+(" + semver + ")\\s*$")
, validComparator : new RegExp("^"+exprComparator+"$")
, parseXRange : new RegExp("^"+xRange+"$")
, parseSpermy : new RegExp("^"+exprSpermy+"$")
}
Object.getOwnPropertyNames(expressions).forEach(function (i) {
exports[i] = function (str) {
return ("" + (str || "")).match(expressions[i])
}
})
exports.rangeReplace = ">=$1 <=$7"
exports.clean = clean
exports.compare = compare
exports.satisfies = satisfies
exports.gt = gt
exports.gte = gte
exports.lt = lt
exports.lte = lte
exports.eq = eq
exports.neq = neq
exports.cmp = cmp
exports.inc = inc
exports.valid = valid
exports.validPackage = validPackage
exports.validRange = validRange
exports.maxSatisfying = maxSatisfying
exports.replaceStars = replaceStars
exports.toComparators = toComparators
function stringify (version) {
var v = version
return [v[1]||'', v[2]||'', v[3]||''].join(".") + (v[4]||'') + (v[5]||'')
}
function clean (version) {
version = exports.parse(version)
if (!version) return version
return stringify(version)
}
function valid (version) {
if (typeof version !== "string") return null
return exports.parse(version) && version.trim().replace(/^[v=]+/, '')
}
function validPackage (version) {
if (typeof version !== "string") return null
return version.match(expressions.parsePackage) && version.trim()
}
// range can be one of:
// "1.0.3 - 2.0.0" range, inclusive, like ">=1.0.3 <=2.0.0"
// ">1.0.2" like 1.0.3 - 9999.9999.9999
// ">=1.0.2" like 1.0.2 - 9999.9999.9999
// "<2.0.0" like 0.0.0 - 1.9999.9999
// ">1.0.2 <2.0.0" like 1.0.3 - 1.9999.9999
var starExpression = /(<|>)?=?\s*\*/g
, starReplace = ""
, compTrimExpression = new RegExp("((<|>)?=?)\\s*("
+semver+"|"+xRangePlain+")", "g")
, compTrimReplace = "$1$3"
function toComparators (range) {
var ret = (range || "").trim()
.replace(expressions.parseRange, exports.rangeReplace)
.replace(compTrimExpression, compTrimReplace)
.split(/\s+/)
.join(" ")
.split("||")
.map(function (orchunk) {
return orchunk
.split(" ")
.map(replaceXRanges)
.map(replaceSpermies)
.map(replaceStars)
.join(" ").trim()
})
.map(function (orchunk) {
return orchunk
.trim()
.split(/\s+/)
.filter(function (c) { return c.match(expressions.validComparator) })
})
.filter(function (c) { return c.length })
return ret
}
function replaceStars (stars) {
return stars.trim().replace(starExpression, starReplace)
}
// "2.x","2.x.x" --> ">=2.0.0- <2.1.0-"
// "2.3.x" --> ">=2.3.0- <2.4.0-"
function replaceXRanges (ranges) {
return ranges.split(/\s+/)
.map(replaceXRange)
.join(" ")
}
function replaceXRange (version) {
return version.trim().replace(expressions.parseXRange,
function (v, gtlt, M, m, p, t) {
var anyX = !M || M.toLowerCase() === "x" || M === "*"
|| !m || m.toLowerCase() === "x" || m === "*"
|| !p || p.toLowerCase() === "x" || p === "*"
, ret = v
if (gtlt && anyX) {
// just replace x'es with zeroes
;(!M || M === "*" || M.toLowerCase() === "x") && (M = 0)
;(!m || m === "*" || m.toLowerCase() === "x") && (m = 0)
;(!p || p === "*" || p.toLowerCase() === "x") && (p = 0)
ret = gtlt + M+"."+m+"."+p+"-"
} else if (!M || M === "*" || M.toLowerCase() === "x") {
ret = "*" // allow any
} else if (!m || m === "*" || m.toLowerCase() === "x") {
// append "-" onto the version, otherwise
// "1.x.x" matches "2.0.0beta", since the tag
// *lowers* the version value
ret = ">="+M+".0.0- <"+(+M+1)+".0.0-"
} else if (!p || p === "*" || p.toLowerCase() === "x") {
ret = ">="+M+"."+m+".0- <"+M+"."+(+m+1)+".0-"
}
//console.error("parseXRange", [].slice.call(arguments), ret)
return ret
})
}
// ~, ~> --> * (any, kinda silly)
// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0
// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0
// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0
// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0
function replaceSpermies (version) {
return version.trim().replace(expressions.parseSpermy,
function (v, gtlt, M, m, p, t) {
if (gtlt) throw new Error(
"Using '"+gtlt+"' with ~ makes no sense. Don't do it.")
if (!M || M.toLowerCase() === "x") {
return ""
}
// ~1 == >=1.0.0- <2.0.0-
if (!m || m.toLowerCase() === "x") {
return ">="+M+".0.0- <"+(+M+1)+".0.0-"
}
// ~1.2 == >=1.2.0- <1.3.0-
if (!p || p.toLowerCase() === "x") {
return ">="+M+"."+m+".0- <"+M+"."+(+m+1)+".0-"
}
// ~1.2.3 == >=1.2.3- <1.3.0-
t = t || "-"
return ">="+M+"."+m+"."+p+t+" <"+M+"."+(+m+1)+".0-"
})
}
function validRange (range) {
range = replaceStars(range)
var c = toComparators(range)
return (c.length === 0)
? null
: c.map(function (c) { return c.join(" ") }).join("||")
}
// returns the highest satisfying version in the list, or undefined
function maxSatisfying (versions, range) {
return versions
.filter(function (v) { return satisfies(v, range) })
.sort(compare)
.pop()
}
function satisfies (version, range) {
version = valid(version)
if (!version) return false
range = toComparators(range)
for (var i = 0, l = range.length ; i < l ; i ++) {
var ok = false
for (var j = 0, ll = range[i].length ; j < ll ; j ++) {
var r = range[i][j]
, gtlt = r.charAt(0) === ">" ? gt
: r.charAt(0) === "<" ? lt
: false
, eq = r.charAt(!!gtlt) === "="
, sub = (!!eq) + (!!gtlt)
if (!gtlt) eq = true
r = r.substr(sub)
r = (r === "") ? r : valid(r)
ok = (r === "") || (eq && r === version) || (gtlt && gtlt(version, r))
if (!ok) break
}
if (ok) return true
}
return false
}
// return v1 > v2 ? 1 : -1
function compare (v1, v2) {
var g = gt(v1, v2)
return g === null ? 0 : g ? 1 : -1
}
function rcompare (v1, v2) {
return compare(v2, v1)
}
function lt (v1, v2) { return gt(v2, v1) }
function gte (v1, v2) { return !lt(v1, v2) }
function lte (v1, v2) { return !gt(v1, v2) }
function eq (v1, v2) { return gt(v1, v2) === null }
function neq (v1, v2) { return gt(v1, v2) !== null }
function cmp (v1, c, v2) {
switch (c) {
case ">": return gt(v1, v2)
case "<": return lt(v1, v2)
case ">=": return gte(v1, v2)
case "<=": return lte(v1, v2)
case "==": return eq(v1, v2)
case "!=": return neq(v1, v2)
case "===": return v1 === v2
case "!==": return v1 !== v2
default: throw new Error("Y U NO USE VALID COMPARATOR!? "+c)
}
}
// return v1 > v2
function num (v) {
return v === undefined ? -1 : parseInt((v||"0").replace(/[^0-9]+/g, ''), 10)
}
function gt (v1, v2) {
v1 = exports.parse(v1)
v2 = exports.parse(v2)
if (!v1 || !v2) return false
for (var i = 1; i < 5; i ++) {
v1[i] = num(v1[i])
v2[i] = num(v2[i])
if (v1[i] > v2[i]) return true
else if (v1[i] !== v2[i]) return false
}
// no tag is > than any tag, or use lexicographical order.
var tag1 = v1[5] || ""
, tag2 = v2[5] || ""
// kludge: null means they were equal. falsey, and detectable.
// embarrassingly overclever, though, I know.
return tag1 === tag2 ? null
: !tag1 ? true
: !tag2 ? false
: tag1 > tag2
}
function inc (version, release) {
version = exports.parse(version)
if (!version) return null
var parsedIndexLookup =
{ 'major': 1
, 'minor': 2
, 'patch': 3
, 'build': 4 }
var incIndex = parsedIndexLookup[release]
if (incIndex === undefined) return null
var current = num(version[incIndex])
version[incIndex] = current === -1 ? 1 : current + 1
for (var i = incIndex + 1; i < 5; i ++) {
if (num(version[i]) !== -1) version[i] = "0"
}
if (version[4]) version[4] = "-" + version[4]
version[5] = ""
return stringify(version)
}
})(typeof exports === "object" ? exports : semver = {})
var tap = require("tap")
, test = tap.test
, semver = require("./semver.js")
, eq = semver.eq
, gt = semver.gt
, lt = semver.lt
, neq = semver.neq
, cmp = semver.cmp
, gte = semver.gte
, lte = semver.lte
, satisfies = semver.satisfies
, validRange = semver.validRange
, inc = semver.inc
, replaceStars = semver.replaceStars
, toComparators = semver.toComparators
tap.plan(8)
test("\ncomparison tests", function (t) {
; [ ["0.0.0", "0.0.0foo"]
, ["0.0.1", "0.0.0"]
, ["1.0.0", "0.9.9"]
, ["0.10.0", "0.9.0"]
, ["0.99.0", "0.10.0"]
, ["2.0.0", "1.2.3"]
, ["v0.0.0", "0.0.0foo"]
, ["v0.0.1", "0.0.0"]
, ["v1.0.0", "0.9.9"]
, ["v0.10.0", "0.9.0"]
, ["v0.99.0", "0.10.0"]
, ["v2.0.0", "1.2.3"]
, ["0.0.0", "v0.0.0foo"]
, ["0.0.1", "v0.0.0"]
, ["1.0.0", "v0.9.9"]
, ["0.10.0", "v0.9.0"]
, ["0.99.0", "v0.10.0"]
, ["2.0.0", "v1.2.3"]
, ["1.2.3", "1.2.3-asdf"]
, ["1.2.3-4", "1.2.3"]
, ["1.2.3-4-foo", "1.2.3"]
, ["1.2.3-5", "1.2.3-5-foo"]
, ["1.2.3-5", "1.2.3-4"]
, ["1.2.3-5-foo", "1.2.3-5-Foo"]
].forEach(function (v) {
var v0 = v[0]
, v1 = v[1]
t.ok(gt(v0, v1), "gt('"+v0+"', '"+v1+"')")
t.ok(lt(v1, v0), "lt('"+v1+"', '"+v0+"')")
t.ok(!gt(v1, v0), "!gt('"+v1+"', '"+v0+"')")
t.ok(!lt(v0, v1), "!lt('"+v0+"', '"+v1+"')")
t.ok(eq(v0, v0), "eq('"+v0+"', '"+v0+"')")
t.ok(eq(v1, v1), "eq('"+v1+"', '"+v1+"')")
t.ok(neq(v0, v1), "neq('"+v0+"', '"+v1+"')")
t.ok(cmp(v1, "==", v1), "cmp('"+v1+"' == '"+v1+"')")
t.ok(cmp(v0, ">=", v1), "cmp('"+v0+"' >= '"+v1+"')")
t.ok(cmp(v1, "<=", v0), "cmp('"+v1+"' <= '"+v0+"')")
t.ok(cmp(v0, "!=", v1), "cmp('"+v0+"' != '"+v1+"')")
})
t.end()
})
test("\nequality tests", function (t) {
; [ ["1.2.3", "v1.2.3"]
, ["1.2.3", "=1.2.3"]
, ["1.2.3", "v 1.2.3"]
, ["1.2.3", "= 1.2.3"]
, ["1.2.3", " v1.2.3"]
, ["1.2.3", " =1.2.3"]
, ["1.2.3", " v 1.2.3"]
, ["1.2.3", " = 1.2.3"]
, ["1.2.3-0", "v1.2.3-0"]
, ["1.2.3-0", "=1.2.3-0"]
, ["1.2.3-0", "v 1.2.3-0"]
, ["1.2.3-0", "= 1.2.3-0"]
, ["1.2.3-0", " v1.2.3-0"]
, ["1.2.3-0", " =1.2.3-0"]
, ["1.2.3-0", " v 1.2.3-0"]
, ["1.2.3-0", " = 1.2.3-0"]
, ["1.2.3-01", "v1.2.3-1"]
, ["1.2.3-01", "=1.2.3-1"]
, ["1.2.3-01", "v 1.2.3-1"]
, ["1.2.3-01", "= 1.2.3-1"]
, ["1.2.3-01", " v1.2.3-1"]
, ["1.2.3-01", " =1.2.3-1"]
, ["1.2.3-01", " v 1.2.3-1"]
, ["1.2.3-01", " = 1.2.3-1"]
, ["1.2.3beta", "v1.2.3beta"]
, ["1.2.3beta", "=1.2.3beta"]
, ["1.2.3beta", "v 1.2.3beta"]
, ["1.2.3beta", "= 1.2.3beta"]
, ["1.2.3beta", " v1.2.3beta"]
, ["1.2.3beta", " =1.2.3beta"]
, ["1.2.3beta", " v 1.2.3beta"]
, ["1.2.3beta", " = 1.2.3beta"]
].forEach(function (v) {
var v0 = v[0]
, v1 = v[1]
t.ok(eq(v0, v1), "eq('"+v0+"', '"+v1+"')")
t.ok(!neq(v0, v1), "!neq('"+v0+"', '"+v1+"')")
t.ok(cmp(v0, "==", v1), "cmp("+v0+"=="+v1+")")
t.ok(!cmp(v0, "!=", v1), "!cmp("+v0+"!="+v1+")")
t.ok(!cmp(v0, "===", v1), "!cmp("+v0+"==="+v1+")")
t.ok(cmp(v0, "!==", v1), "cmp("+v0+"!=="+v1+")")
t.ok(!gt(v0, v1), "!gt('"+v0+"', '"+v1+"')")
t.ok(gte(v0, v1), "gte('"+v0+"', '"+v1+"')")
t.ok(!lt(v0, v1), "!lt('"+v0+"', '"+v1+"')")
t.ok(lte(v0, v1), "lte('"+v0+"', '"+v1+"')")
})
t.end()
})
test("\nrange tests", function (t) {
; [ ["1.0.0 - 2.0.0", "1.2.3"]
, ["1.0.0", "1.0.0"]
, [">=*", "0.2.4"]
, ["", "1.0.0"]
, ["*", "1.2.3"]
, ["*", "v1.2.3-foo"]
, [">=1.0.0", "1.0.0"]
, [">=1.0.0", "1.0.1"]
, [">=1.0.0", "1.1.0"]
, [">1.0.0", "1.0.1"]
, [">1.0.0", "1.1.0"]
, ["<=2.0.0", "2.0.0"]
, ["<=2.0.0", "1.9999.9999"]
, ["<=2.0.0", "0.2.9"]
, ["<2.0.0", "1.9999.9999"]
, ["<2.0.0", "0.2.9"]
, [">= 1.0.0", "1.0.0"]
, [">= 1.0.0", "1.0.1"]
, [">= 1.0.0", "1.1.0"]
, ["> 1.0.0", "1.0.1"]
, ["> 1.0.0", "1.1.0"]
, ["<= 2.0.0", "2.0.0"]
, ["<= 2.0.0", "1.9999.9999"]
, ["<= 2.0.0", "0.2.9"]
, ["< 2.0.0", "1.9999.9999"]
, ["<\t2.0.0", "0.2.9"]
, [">=0.1.97", "v0.1.97"]
, [">=0.1.97", "0.1.97"]
, ["0.1.20 || 1.2.4", "1.2.4"]
, [">=0.2.3 || <0.0.1", "0.0.0"]
, [">=0.2.3 || <0.0.1", "0.2.3"]
, [">=0.2.3 || <0.0.1", "0.2.4"]
, ["||", "1.3.4"]
, ["2.x.x", "2.1.3"]
, ["1.2.x", "1.2.3"]
, ["1.2.x || 2.x", "2.1.3"]
, ["1.2.x || 2.x", "1.2.3"]
, ["x", "1.2.3"]
, ["2.*.*", "2.1.3"]
, ["1.2.*", "1.2.3"]
, ["1.2.* || 2.*", "2.1.3"]
, ["1.2.* || 2.*", "1.2.3"]
, ["*", "1.2.3"]
, ["2", "2.1.2"]
, ["2.3", "2.3.1"]
, ["~2.4", "2.4.0"] // >=2.4.0 <2.5.0
, ["~2.4", "2.4.5"]
, ["~>3.2.1", "3.2.2"] // >=3.2.1 <3.3.0
, ["~1", "1.2.3"] // >=1.0.0 <2.0.0
, ["~>1", "1.2.3"]
, ["~> 1", "1.2.3"]
, ["~1.0", "1.0.2"] // >=1.0.0 <1.1.0
, ["~ 1.0", "1.0.2"]
, [">=1", "1.0.0"]
, [">= 1", "1.0.0"]
, ["<1.2", "1.1.1"]
, ["< 1.2", "1.1.1"]
, ["1", "1.0.0beta"]
, ["~v0.5.4-pre", "0.5.5"]
, ["~v0.5.4-pre", "0.5.4"]
].forEach(function (v) {
t.ok(satisfies(v[1], v[0]), v[0]+" satisfied by "+v[1])
})
t.end()
})
test("\nnegative range tests", function (t) {
; [ ["1.0.0 - 2.0.0", "2.2.3"]
, ["1.0.0", "1.0.1"]
, [">=1.0.0", "0.0.0"]
, [">=1.0.0", "0.0.1"]
, [">=1.0.0", "0.1.0"]
, [">1.0.0", "0.0.1"]
, [">1.0.0", "0.1.0"]
, ["<=2.0.0", "3.0.0"]
, ["<=2.0.0", "2.9999.9999"]
, ["<=2.0.0", "2.2.9"]
, ["<2.0.0", "2.9999.9999"]
, ["<2.0.0", "2.2.9"]
, [">=0.1.97", "v0.1.93"]
, [">=0.1.97", "0.1.93"]
, ["0.1.20 || 1.2.4", "1.2.3"]
, [">=0.2.3 || <0.0.1", "0.0.3"]
, [">=0.2.3 || <0.0.1", "0.2.2"]
, ["2.x.x", "1.1.3"]
, ["2.x.x", "3.1.3"]
, ["1.2.x", "1.3.3"]
, ["1.2.x || 2.x", "3.1.3"]
, ["1.2.x || 2.x", "1.1.3"]
, ["2.*.*", "1.1.3"]
, ["2.*.*", "3.1.3"]
, ["1.2.*", "1.3.3"]
, ["1.2.* || 2.*", "3.1.3"]
, ["1.2.* || 2.*", "1.1.3"]
, ["2", "1.1.2"]
, ["2.3", "2.4.1"]
, ["~2.4", "2.5.0"] // >=2.4.0 <2.5.0
, ["~2.4", "2.3.9"]
, ["~>3.2.1", "3.3.2"] // >=3.2.1 <3.3.0
, ["~>3.2.1", "3.2.0"] // >=3.2.1 <3.3.0
, ["~1", "0.2.3"] // >=1.0.0 <2.0.0
, ["~>1", "2.2.3"]
, ["~1.0", "1.1.0"] // >=1.0.0 <1.1.0
, ["<1", "1.0.0"]
, [">=1.2", "1.1.1"]
, ["1", "2.0.0beta"]
, ["~v0.5.4-beta", "0.5.4-alpha"]
, ["<1", "1.0.0beta"]
, ["< 1", "1.0.0beta"]
].forEach(function (v) {
t.ok(!satisfies(v[1], v[0]), v[0]+" not satisfied by "+v[1])
})
t.end()
})
test("\nincrement versions test", function (t) {
; [ [ "1.2.3", "major", "2.0.0" ]
, [ "1.2.3", "minor", "1.3.0" ]
, [ "1.2.3", "patch", "1.2.4" ]
, [ "1.2.3", "build", "1.2.3-1" ]
, [ "1.2.3-4", "build", "1.2.3-5" ]
, [ "1.2.3tag", "major", "2.0.0" ]
, [ "1.2.3-tag", "major", "2.0.0" ]
, [ "1.2.3tag", "build", "1.2.3-1" ]
, [ "1.2.3-tag", "build", "1.2.3-1" ]
, [ "1.2.3-4-tag", "build", "1.2.3-5" ]
, [ "1.2.3-4tag", "build", "1.2.3-5" ]
, [ "1.2.3", "fake", null ]
, [ "fake", "major", null ]
].forEach(function (v) {
t.equal(inc(v[0], v[1]), v[2], "inc("+v[0]+", "+v[1]+") === "+v[2])
})
t.end()
})
test("\nreplace stars test", function (t) {
; [ [ "", "" ]
, [ "*", "" ]
, [ "> *", "" ]
, [ "<*", "" ]
, [ " >= *", "" ]
, [ "* || 1.2.3", " || 1.2.3" ]
].forEach(function (v) {
t.equal(replaceStars(v[0]), v[1], "replaceStars("+v[0]+") === "+v[1])
})
t.end()
})
test("\nvalid range test", function (t) {
; [ ["1.0.0 - 2.0.0", ">=1.0.0 <=2.0.0"]
, ["1.0.0", "1.0.0"]
, [">=*", ""]
, ["", ""]
, ["*", ""]
, ["*", ""]
, [">=1.0.0", ">=1.0.0"]
, [">1.0.0", ">1.0.0"]
, ["<=2.0.0", "<=2.0.0"]
, ["1", ">=1.0.0- <2.0.0-"]
, ["<=2.0.0", "<=2.0.0"]
, ["<=2.0.0", "<=2.0.0"]
, ["<2.0.0", "<2.0.0"]
, ["<2.0.0", "<2.0.0"]
, [">= 1.0.0", ">=1.0.0"]
, [">= 1.0.0", ">=1.0.0"]
, [">= 1.0.0", ">=1.0.0"]
, ["> 1.0.0", ">1.0.0"]
, ["> 1.0.0", ">1.0.0"]
, ["<= 2.0.0", "<=2.0.0"]
, ["<= 2.0.0", "<=2.0.0"]
, ["<= 2.0.0", "<=2.0.0"]
, ["< 2.0.0", "<2.0.0"]
, ["< 2.0.0", "<2.0.0"]
, [">=0.1.97", ">=0.1.97"]
, [">=0.1.97", ">=0.1.97"]
, ["0.1.20 || 1.2.4", "0.1.20||1.2.4"]
, [">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"]
, [">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"]
, [">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"]
, ["||", "||"]
, ["2.x.x", ">=2.0.0- <3.0.0-"]
, ["1.2.x", ">=1.2.0- <1.3.0-"]
, ["1.2.x || 2.x", ">=1.2.0- <1.3.0-||>=2.0.0- <3.0.0-"]
, ["1.2.x || 2.x", ">=1.2.0- <1.3.0-||>=2.0.0- <3.0.0-"]
, ["x", ""]
, ["2.*.*", null]
, ["1.2.*", null]
, ["1.2.* || 2.*", null]
, ["1.2.* || 2.*", null]
, ["*", ""]
, ["2", ">=2.0.0- <3.0.0-"]
, ["2.3", ">=2.3.0- <2.4.0-"]
, ["~2.4", ">=2.4.0- <2.5.0-"]
, ["~2.4", ">=2.4.0- <2.5.0-"]
, ["~>3.2.1", ">=3.2.1- <3.3.0-"]
, ["~1", ">=1.0.0- <2.0.0-"]
, ["~>1", ">=1.0.0- <2.0.0-"]
, ["~> 1", ">=1.0.0- <2.0.0-"]
, ["~1.0", ">=1.0.0- <1.1.0-"]
, ["~ 1.0", ">=1.0.0- <1.1.0-"]
, ["<1", "<1.0.0-"]
, ["< 1", "<1.0.0-"]
, [">=1", ">=1.0.0-"]
, [">= 1", ">=1.0.0-"]
, ["<1.2", "<1.2.0-"]
, ["< 1.2", "<1.2.0-"]
, ["1", ">=1.0.0- <2.0.0-"]
].forEach(function (v) {
t.equal(validRange(v[0]), v[1], "validRange("+v[0]+") === "+v[1])
})
t.end()
})
test("\ncomparators test", function (t) {
; [ ["1.0.0 - 2.0.0", [[">=1.0.0", "<=2.0.0"]] ]
, ["1.0.0", [["1.0.0"]] ]
, [">=*", [[">=0.0.0-"]] ]
, ["", [[""]]]
, ["*", [[""]] ]
, ["*", [[""]] ]
, [">=1.0.0", [[">=1.0.0"]] ]
, [">=1.0.0", [[">=1.0.0"]] ]
, [">=1.0.0", [[">=1.0.0"]] ]
, [">1.0.0", [[">1.0.0"]] ]
, [">1.0.0", [[">1.0.0"]] ]
, ["<=2.0.0", [["<=2.0.0"]] ]
, ["1", [[">=1.0.0-", "<2.0.0-"]] ]
, ["<=2.0.0", [["<=2.0.0"]] ]
, ["<=2.0.0", [["<=2.0.0"]] ]
, ["<2.0.0", [["<2.0.0"]] ]
, ["<2.0.0", [["<2.0.0"]] ]
, [">= 1.0.0", [[">=1.0.0"]] ]
, [">= 1.0.0", [[">=1.0.0"]] ]
, [">= 1.0.0", [[">=1.0.0"]] ]
, ["> 1.0.0", [[">1.0.0"]] ]
, ["> 1.0.0", [[">1.0.0"]] ]
, ["<= 2.0.0", [["<=2.0.0"]] ]
, ["<= 2.0.0", [["<=2.0.0"]] ]
, ["<= 2.0.0", [["<=2.0.0"]] ]
, ["< 2.0.0", [["<2.0.0"]] ]
, ["<\t2.0.0", [["<2.0.0"]] ]
, [">=0.1.97", [[">=0.1.97"]] ]
, [">=0.1.97", [[">=0.1.97"]] ]
, ["0.1.20 || 1.2.4", [["0.1.20"], ["1.2.4"]] ]
, [">=0.2.3 || <0.0.1", [[">=0.2.3"], ["<0.0.1"]] ]
, [">=0.2.3 || <0.0.1", [[">=0.2.3"], ["<0.0.1"]] ]
, [">=0.2.3 || <0.0.1", [[">=0.2.3"], ["<0.0.1"]] ]
, ["||", [[""], [""]] ]
, ["2.x.x", [[">=2.0.0-", "<3.0.0-"]] ]
, ["1.2.x", [[">=1.2.0-", "<1.3.0-"]] ]
, ["1.2.x || 2.x", [[">=1.2.0-", "<1.3.0-"], [">=2.0.0-", "<3.0.0-"]] ]
, ["1.2.x || 2.x", [[">=1.2.0-", "<1.3.0-"], [">=2.0.0-", "<3.0.0-"]] ]
, ["x", [[""]] ]
, ["2.*.*", [[">=2.0.0-", "<3.0.0-"]] ]
, ["1.2.*", [[">=1.2.0-", "<1.3.0-"]] ]
, ["1.2.* || 2.*", [[">=1.2.0-", "<1.3.0-"], [">=2.0.0-", "<3.0.0-"]] ]
, ["1.2.* || 2.*", [[">=1.2.0-", "<1.3.0-"], [">=2.0.0-", "<3.0.0-"]] ]
, ["*", [[""]] ]
, ["2", [[">=2.0.0-", "<3.0.0-"]] ]
, ["2.3", [[">=2.3.0-", "<2.4.0-"]] ]
, ["~2.4", [[">=2.4.0-", "<2.5.0-"]] ]
, ["~2.4", [[">=2.4.0-", "<2.5.0-"]] ]
, ["~>3.2.1", [[">=3.2.1-", "<3.3.0-"]] ]
, ["~1", [[">=1.0.0-", "<2.0.0-"]] ]
, ["~>1", [[">=1.0.0-", "<2.0.0-"]] ]
, ["~> 1", [[">=1.0.0-", "<2.0.0-"]] ]
, ["~1.0", [[">=1.0.0-", "<1.1.0-"]] ]
, ["~ 1.0", [[">=1.0.0-", "<1.1.0-"]] ]
, ["<1", [["<1.0.0-"]] ]
, ["< 1", [["<1.0.0-"]] ]
, [">=1", [[">=1.0.0-"]] ]
, [">= 1", [[">=1.0.0-"]] ]
, ["<1.2", [["<1.2.0-"]] ]
, ["< 1.2", [["<1.2.0-"]] ]
, ["1", [[">=1.0.0-", "<2.0.0-"]] ]
].forEach(function (v) {
t.equivalent(toComparators(v[0]), v[1], "toComparators("+v[0]+") === "+JSON.stringify(v[1]))
})
t.end()
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment