Commit 4843a79c authored by Zeke Sikelianos's avatar Zeke Sikelianos

Merge pull request #71 from heroku/diet

Ship the Diet Buildpack!
parents c05e23bc 27641610
## v7 (2012-03-27)
* remove build asset caching
* prevent installation of devDependencies
* add warning if no nodejs version specified
## Testing buildpack changes using Anvil
[Anvil](https://github.com/ddollar/anvil) is a generic build server for Heroku.
```
gem install anvil-cli
```
The [heroku-anvil CLI plugin](https://github.com/ddollar/heroku-anvil) is a wrapper for anvil.
```
heroku plugins:install https://github.com/ddollar/heroku-anvil
```
The [ddollar/test buildpack](https://github.com/ddollar/buildpack-test) is for testing things: it runs `bin/test` on your app.
```
heroku build -b ddollar/test # -b can also point to a local directory
```
## Compiling new versions of node and npm using Vulcan
Install [vulcan](https://github.com/heroku/vulcan) and create your own build server. Use any
app name you want and vulcan will remember it in a `~/.vulcan` config file.
```
gem install vulcan
vulcan create builder-bob
```
Store your S3 credentials in `~/.aws/`
```
mkdir -p ~/.aws
echo 'YOUR_AWS_KEY' > ~/.aws/key-nodejs.access
echo 'YOUR_AWS_SECRET' > ~/.aws/key-nodejs.secret
```
Add a credentials exporter to your `.bash_profile` or `.bashrc`
```
setup_nodejs_env () {
export AWS_ID=$(cat ~/.aws/key-nodejs.access)
export AWS_SECRET=$(cat ~/.aws/key-nodejs.secret)
export S3_BUCKET="heroku-buildpack-nodejs"
}
```
Build:
```
setup_nodejs_env
support/package_nodejs <node-version>
support/package_npm <npm-version>
```
## Publishing buildpack updates
```
heroku plugins:install https://github.com/heroku/heroku-buildpacks
cd heroku-buildpack-nodejs
git checkout master
heroku buildpacks:publish heroku/nodejs
```
- Email [dos@heroku.com](mailto:dos@heroku.com) if changes are significant.
- Add a [changelog item](https://devcenter.heroku.com/admin/changelog_items/new).
- Update [Node Devcenter articles](https://devcenter.heroku.com/admin/articles/owned) as necessary.
## Keeping up with the Nodeses
- Run `npm info npm version` to find out the latest available version of npm.
- Follow [@nodejs](https://twitter.com/nodejs) and [@npmjs](https://twitter.com/npmjs) on Twitter.
\ No newline at end of file
MIT License:
Copyright (C) 2012 Heroku, Inc.
Copyright (C) 2012-2013 Heroku, Inc.
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:
......
Heroku Buildpack for Node.js
============================
This is a [Heroku buildpack](http://devcenter.heroku.com/articles/buildpacks) for Node.js apps.
This is the official [Heroku buildpack](http://devcenter.heroku.com/articles/buildpacks) for Node.js apps. If you fork this repository, please **update this README** to explain what your fork does and why it's special.
The buildpack will detect your app as Node.js if it has a `package.json` file in the root. It will use npm to install your dependencies, and vendors a version of the Node.js runtime into your slug.
Example Usage
-------------
How it Works
------------
$ ls
Procfile package.json web.js
$ heroku create
$ git push heroku master
...
-----> Heroku receiving push
-----> Fetching custom buildpack
-----> Node.js app detected
-----> Vendoring node 0.4.7
-----> Installing dependencies with npm 1.0.8
express@2.1.0 ./node_modules/express
├── mime@1.2.2
├── qs@0.3.1
└── connect@1.6.2
Dependencies installed
Node.js and npm versions
------------------------
You can specify the versions of Node.js and npm your application requires using `package.json`
```json
{
"name": "myapp",
"version": "0.0.1",
"engines": {
"node": "~0.10.13",
"npm": "~1.3.2"
}
}
```
Here's an overview of what this buildpack does:
To list the available versions of Node.js and npm, see these manifests:
- Uses the [semver.io](http://semver.io) webservice to find the latest version of node that satisfies the [engines.node semver range](https://npmjs.org/doc/json.html#engines) in your package.json.
- Allows any recent version of node to be used, including [pre-release versions](http://semver.io/node.json).
- Uses an [S3 caching proxy](https://github.com/heroku/s3pository#readme) of nodejs.org for faster downloads of the node binary.
- Discourages use of dangerous semver ranges like `*` and `>0.10`.
- Uses the version of `npm` that comes bundled with `node`.
- Puts `node` and `npm` on the `PATH` so they can be executed with [heroku run](https://devcenter.heroku.com/articles/one-off-dynos#an-example-one-off-dyno).
- Caches the `node_modules` directory across builds for fast deploys.
- Doesn't use the cache if `node_modules` is checked into version control.
- Runs `npm rebuild` if `node_modules` is checked into version control.
- Always runs `npm install` to ensure [npm script hooks](https://npmjs.org/doc/misc/npm-scripts.html) are executed.
- Always runs `npm prune` after restoring cached modules to ensure cleanup of unused dependencies.
For more technical details, see the [heavily-commented compile script](https://github.com/heroku/heroku-buildpack-nodejs/blob/master/bin/compile).
- [heroku-buildpack-nodejs.s3.amazonaws.com/manifest.nodejs](http://heroku-buildpack-nodejs.s3.amazonaws.com/manifest.nodejs)
- [heroku-buildpack-nodejs.s3.amazonaws.com/manifest.npm](http://heroku-buildpack-nodejs.s3.amazonaws.com/manifest.npm)
Documentation
-------------
For more information about buildpacks and Node.js, see these Dev Center articles:
For more information about using Node.js and buildpacks on Heroku, see these Dev Center articles:
- [Getting Started with Node.js on Heroku](https://devcenter.heroku.com/articles/nodejs)
- [Heroku Node.js Support](https://devcenter.heroku.com/articles/nodejs-support)
- [Getting Started with Node.js on Heroku](https://devcenter.heroku.com/articles/nodejs)
- [Buildpacks](https://devcenter.heroku.com/articles/buildpacks)
- [Buildpack API](https://devcenter.heroku.com/articles/buildpack-api)
Legacy Compatibility
--------------------
For most Node.js apps this buildpack should work just fine. If, however, you're unable to deploy using this new version of the buildpack, you can get your app working again by using the legacy branch:
```
heroku config:set BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-nodejs#legacy -a my-app
git commit -am "empty" --allow-empty # force a git commit
git push heroku master
```
Then please open a support ticket at [help.heroku.com](https://help.heroku.com/) so we can diagnose and get your app running on the default buildpack.
Hacking
-------
To make changes to this buildpack, fork it on Github. Push up changes to your fork, then create a new Heroku app to test it, or configure an existing app to use your buildpack:
```sh
```
# Create a new Heroku app that uses your buildpack
heroku create --buildpack <your-github-url>
......@@ -73,4 +64,26 @@ heroku config:set BUILDPACK_URL=<your-github-url>
heroku config:set BUILDPACK_URL=<your-github-url>#your-branch
```
For more detailed information about testing buildpacks, see [CONTRIBUTING.md](CONTRIBUTING.md)
\ No newline at end of file
For more detailed information about testing buildpacks, see [CONTRIBUTING.md](CONTRIBUTING.md)
Testing
-------
[Anvil](https://github.com/ddollar/anvil) is a generic build server for Heroku.
```
gem install anvil-cli
```
The [heroku-anvil CLI plugin](https://github.com/ddollar/heroku-anvil) is a wrapper for anvil.
```
heroku plugins:install https://github.com/ddollar/heroku-anvil
```
The [ddollar/test](https://github.com/ddollar/buildpack-test) buildpack runs `bin/test` on your app/buildpack.
```
heroku build -b ddollar/test # -b can also point to a local directory
```
error() {
echo " ! $*" >&2
exit 1
}
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
indent() {
c='s/^/ /'
case $(uname) in
Darwin) sed -l "$c";; # mac/bsd sed: -l buffers on line boundaries
*) sed -u "$c";; # unix/gnu sed: -u unbuffered (arbitrary) chunks of data
esac
}
cat_npm_debug_log() {
test -f $build_dir/npm-debug.log && cat $build_dir/npm-debug.log
}
#!/usr/bin/env bash
# bin/compile <build-dir> <cache-dir>
# fail fast
set -e
# debug
# set -x
# clean up leaking environment
unset GIT_DIR
# config
SCONS_VERSION="1.2.0"
S3_BUCKET="heroku-buildpack-nodejs"
# parse and derive params
BUILD_DIR=$1
CACHE_DIR=$2
LP_DIR=`cd $(dirname $0); cd ..; pwd`
function error() {
echo " ! $*" >&2
exit 1
}
function mktmpdir() {
dir=$(mktemp -t node-$1-XXXX)
rm -rf $dir
mkdir -p $dir
echo $dir
}
function indent() {
c='s/^/ /'
case $(uname) in
Darwin) sed -l "$c";;
*) sed -u "$c";;
esac
}
function run_npm() {
command="$1"
cd $BUILD_DIR
HOME="$BUILD_DIR" $VENDORED_NODE/bin/node $VENDORED_NPM/cli.js $command 2>&1 | indent
if [ "${PIPESTATUS[*]}" != "0 0" ]; then
echo " ! Failed to $command dependencies with npm"
exit 1
fi
}
function manifest_versions() {
curl "http://${S3_BUCKET}.s3.amazonaws.com/manifest.${1}" -s -o - | tr -s '\n' ' '
}
function resolve_version() {
available_versions="$1"
requested_version="$2"
default_version="$3"
args=""
for version in $available_versions; do args="${args} -v \"${version}\""; done
if [ "$2" == "" ]; then
args="${args} -r \"${default_version}\"";
else
args="${args} -r \"${requested_version}\"";
fi
evaluated_versions=$(eval $bootstrap_node/bin/node $LP_DIR/vendor/node-semver/bin/semver ${args} || echo "")
echo "$evaluated_versions" | tail -n 1
}
function package_engine_version() {
version=$(cat $BUILD_DIR/package.json | $bootstrap_node/bin/node $LP_DIR/vendor/json/json engines.$1 2>/dev/null)
if [ $? == 0 ]; then
echo $version | sed -e 's/\([<>=]\) /\1/g'
fi
}
function package_resolve_version() {
engine="$1"
resolved_version=$(resolve_version "${engine_versions[$engine]}" "${engine_requests[$engine]}" "${engine_defaults[$engine]}")
if [ "${resolved_version}" == "" ]; then
error "Requested engine $engine version ${engine_requests[$engine]} does not match available versions: ${engine_versions[$engine]}"
else
echo $resolved_version
fi
}
function package_download() {
engine="$1"
version="$2"
location="$3"
mkdir -p $location
package="http://${S3_BUCKET}.s3.amazonaws.com/$engine-$version.tgz"
curl $package -s -o - | tar xzf - -C $location
}
function cat_npm_debug_log() {
if [ -f $BUILD_DIR/npm-debug.log ]; then
cat $BUILD_DIR/npm-debug.log
fi
}
trap cat_npm_debug_log EXIT
bootstrap_node=$(mktmpdir bootstrap_node)
package_download "nodejs" "0.4.7" $bootstrap_node
# make some associative arrays
declare -A engine_versions
declare -A engine_defaults
declare -A engine_requests
engine_defaults["node"]="0.10.x"
engine_defaults["npm"]="1.3.x"
engine_versions["node"]=$(manifest_versions "nodejs")
engine_requests["node"]=$(package_engine_version "node")
engine_versions["npm"]=$(manifest_versions "npm")
engine_requests["npm"]=$(package_engine_version "npm")
echo "-----> Resolving engine versions"
# add a warning if no version of node specified
if [ "${engine_requests["node"]}" == "" ]; then
echo
echo "WARNING: No version of Node.js specified in package.json, see:" | indent
echo "https://devcenter.heroku.com/articles/nodejs-support#versions" | indent
echo
set -e # fail fast
set -o pipefail # don't ignore exit codes when piping output
# set -x # enable debugging
# Configure directories
build_dir=$1
cache_dir=$2
bp_dir=$(cd $(dirname $0); cd ..; pwd)
# Load some convenience functions like status(), echo(), and indent()
source $bp_dir/bin/common.sh
# Output npm debug info on error
trap cat_npm_debug_log ERR
# 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
node_version=$(curl --silent --get --data-urlencode "range=${semver_range}" http://semver.io/node/resolve)
# 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
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
# If node_modules directory is checked into source control then
# rebuild any native deps. Otherwise, restore from the build cache.
if test -d $build_dir/node_modules; then
status "Using existing node_modules directory"
status "Rebuilding any native dependencies"
npm rebuild 2>&1 | indent
elif test -d $cache_dir/node_modules; then
status "Restoring node_modules from cache"
cp -r $cache_dir/node_modules $build_dir/
fi
NODE_VERSION=$(package_resolve_version "node")
echo "Using Node.js version: ${NODE_VERSION}" | indent
NPM_VERSION=$(package_resolve_version "npm")
echo "Using npm version: ${NPM_VERSION}" | indent
# cache directories
CACHE_STORE_DIR="$CACHE_DIR/node_modules/$NODE_VERSION/$NPM_VERSION"
CACHE_TARGET_DIR="$BUILD_DIR/node_modules"
# s3 packages
NODE_PACKAGE="http://${S3_BUCKET}.s3.amazonaws.com/nodejs-${NODE_VERSION}.tgz"
NPM_PACKAGE="http://${S3_BUCKET}.s3.amazonaws.com/npm-${NPM_VERSION}.tgz"
SCONS_PACKAGE="http://${S3_BUCKET}.s3.amazonaws.com/scons-${SCONS_VERSION}.tgz"
# vendor directories
VENDORED_NODE="$(mktmpdir node)"
VENDORED_NPM="$(mktmpdir npm)"
VENDORED_SCONS="$(mktmpdir scons)"
# download and unpack packages
echo "-----> Fetching Node.js binaries"
package_download "nodejs" "${NODE_VERSION}" "${VENDORED_NODE}"
package_download "npm" "${NPM_VERSION}" "${VENDORED_NPM}"
package_download "scons" "${SCONS_VERSION}" "${VENDORED_SCONS}"
# vendor node into the slug
PATH="$BUILD_DIR/bin:$PATH"
echo "-----> Vendoring node into slug"
mkdir -p "$BUILD_DIR/bin"
cp "$VENDORED_NODE/bin/node" "$BUILD_DIR/bin/node"
# setting up paths for building
PATH="$VENDORED_SCONS:$VENDORED_NODE/bin:$PATH"
INCLUDE_PATH="$VENDORED_NODE/include"
export CPATH="$INCLUDE_PATH:$CPATH"
export CPPPATH="$INCLUDE_PATH:$CPPPATH"
# install dependencies with npm
echo "-----> Installing dependencies with npm"
run_npm "install --production"
run_npm "rebuild"
echo "Dependencies installed" | indent
echo "-----> Building runtime environment"
mkdir -p $BUILD_DIR/.profile.d
echo "export PATH=\"\$HOME/bin:\$HOME/node_modules/.bin:\$PATH\"" > $BUILD_DIR/.profile.d/nodejs.sh
# Make npm output to STDOUT instead of its default STDERR
status "Installing dependencies"
npm install --production 2>&1 | indent
status "Pruning unused dependencies"
npm prune 2>&1 | indent
status "Caching node_modules directory for future builds"
rm -rf $cache_dir
mkdir -p $cache_dir
test -d $build_dir/node_modules && cp -r $build_dir/node_modules $cache_dir/
status "Cleaning up node-gyp and npm artifacts"
rm -rf "$build_dir/.node-gyp"
rm -rf "$build_dir/.npm"
# 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
# Post package.json to nomnom service
# Use a subshell so failures won't break the build.
(
curl \
--data @$build_dir/package.json \
--fail \
--silent \
--request POST \
--header "content-type: application/json" \
https://nomnom.heroku.com/?request_id=$REQUEST_ID
) &
#!/usr/bin/env bash
# bin/release <build-dir>
cat <<EOF
---
config_vars:
PATH: bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin
EOF
#!/usr/bin/env bash
#
# Create a Heroku app with the following buildpack:
# https://github.com/ddollar/buildpack-test
#
# Push this Node.js buildpack to that Heroku app to
# run the tests
#
# See CONTRIBUTING.md for info on running these tests.
testDetectWithPackageJson() {
detect "package-json-version"
detect "stable-node"
assertCaptured "Node.js"
assertCapturedSuccess
}
......@@ -19,48 +12,113 @@ testDetectWithoutPackageJson() {
assertCapturedError 1 ""
}
testPackageJsonWithVersion() {
compile "package-json-version"
assertNotCaptured "WARNING: No version of Node.js specified"
assertCaptured "Using Node.js version: 0.6.11"
assertCaptured "Using npm version: 1.1.9"
testNoVersion() {
compile "no-version"
assertCaptured "PRO TIP: Specify a node version in package.json"
assertCaptured "Defaulting to latest stable node"
assertCapturedSuccess
}
testPackageJsonWithoutVersion() {
compile "package-json-noversion"
assertCaptured "WARNING: No version of Node.js specified"
assertCaptured "Using Node.js version: 0.10"
assertCaptured "Using npm version: 1.3"
testDangerousRangeStar() {
compile "dangerous-range-star"
assertCaptured "PRO TIP: Avoid using semver ranges like '*'"
assertCaptured "Requested node range: *"
assertCaptured "Resolved node version: 0.10"
assertCapturedSuccess
}
testPackageJsonWithInvalidVersion() {
compile "package-json-invalidversion"
assertCapturedError 1 "Requested engine npm version 1.1.5 does not"
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
}
testNothingCached() {
cache=$(mktmpdir)
compile "package-json-version" $cache
testRangeWithSpace() {
compile "range-with-space"
assertCaptured "Requested node range: >= 0.8.x"
assertCaptured "Resolved node version: 0.10."
assertCapturedSuccess
}
testStableVersion() {
compile "stable-node"
assertNotCaptured "PRO TIP: Avoid using semver"
assertNotCaptured "PRO TIP: Specify"
assertCaptured "Resolved node version"
assertCapturedSuccess
}
testUnstableVersion() {
compile "unstable-version"
assertCaptured "Requested node range: >0.11.0"
assertCaptured "Resolved node version: 0.11."
assertCapturedSuccess
assertEquals "0" "$(ls -1 $cache | wc -l)"
}
testProfileCreated() {
compile "package-json-version"
compile "stable-node"
assertCaptured "Building runtime environment"
assertFile "export PATH=\"\$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
}
testInvalidDependency() {
compile "invalid-dependency"
assertCaptured "not in the npm registry"
assertCapturedError 1 ""
}
testNodeModulesCached() {
cache=$(mktmpdir)
compile "caching" $cache
assertCaptured "Caching node"
assertEquals "1" "$(ls -1 $cache/ | wc -l)"
}
testModulesCheckedIn() {
compile "modules-checked-in"
assertCaptured "Using existing node_modules directory"
assertCaptured "Rebuilding any native dependencies"
assertCapturedSuccess
}
## utils ########################################
# Pending Tests
# testNodeBinariesAddedToPath() {
# }
# testNodeModulesRestoredFromCache() {
# }
# TODO: Figure out how to test stuff like script hooks
# when restoring node_modules from cache
# testScriptHooks() {
# compile "script-hooks"
# assertCaptured "trigger script hooks"
# assertCaptured "preinstall hook message"
# assertCapturedSuccess
# }
# testWithoutScriptHooks() {
# compile "no-script-hooks"
# assertNotCaptured "trigger script hooks"
# assertCapturedSuccess
# }
# testInvalidVersion() {
# compile "invalid-node-version"
# assertCapturedError 1 "not found among available versions"
# }
# Utils
pushd $(dirname 0) >/dev/null
BASE=$(pwd)
bp_dir=$(pwd)
popd >/dev/null
source ${BASE}/vendor/test-utils/test-utils
source ${bp_dir}/vendor/test-utils/test-utils
mktmpdir() {
dir=$(mktemp -t testXXXXX)
......@@ -70,19 +128,19 @@ mktmpdir() {
}
detect() {
capture ${BASE}/bin/detect ${BASE}/test/$1
capture ${bp_dir}/bin/detect ${bp_dir}/test/$1
}
COMPILE_DIR=""
compile_dir=""
compile() {
COMPILE_DIR=$(mktmpdir)
cp -r ${BASE}/test/$1/* ${COMPILE_DIR}/
capture ${BASE}/bin/compile ${COMPILE_DIR} $2
compile_dir=$(mktmpdir)
cp -r ${bp_dir}/test/$1/* ${compile_dir}/
capture ${bp_dir}/bin/compile ${compile_dir} ${2:-$(mktmpdir)}
}
assertFile() {
assertEquals "$1" "$(cat ${COMPILE_DIR}/$2)"
assertEquals "$1" "$(cat ${compile_dir}/$2)"
}
source ${BASE}/vendor/shunit2/shunit2
source ${bp_dir}/vendor/shunit2/shunit2
#!/bin/bash
# Implement HMAC functionality on top of the OpenSSL digest functions.
# licensed under the terms of the GNU GPL v2
# Copyright 2007 Victor Lowther <victor.lowther@gmail.com>
die() {
echo $*
exit 1
}
check_deps() {
local res=0
while [ $# -ne 0 ]; do
which "${1}" >& /dev/null || { res=1; echo "${1} not found."; }
shift
done
(( res == 0 )) || die "aborting."
}
# write a byte (passed as hex) to stdout
write_byte() {
# $1 = byte to write
printf "\\x$(printf "%x" ${1})"
}
# make an hmac pad out of a key.
# this is not the most secure way of doing it, but it is
# the most expedient.
make_hmac_pad() {
# using key in file $1 and byte in $2, create the appropriate hmac pad
# Pad keys out to $3 bytes
# if key is longer than $3, use hash $4 to hash the key first.
local x y a size remainder oifs
(( remainder = ${3} ))
# in case someone else was messing with IFS.
for x in $(echo -n "${1}" | od -v -t u1 | cut -b 9-);
do
write_byte $((${x} ^ ${2}))
(( remainder -= 1 ))
done
for ((y=0; remainder - y ;y++)); do
write_byte $((0 ^ ${2}))
done
}
# utility functions for making hmac pads
hmac_ipad() {
make_hmac_pad "${1}" 0x36 ${2} "${3}"
}
hmac_opad() {
make_hmac_pad "${1}" 0x5c ${2} "${3}"
}
# hmac something
do_hmac() {
# $1 = algo to use. Must be one that openssl knows about
# $2 = keyfile to use
# $3 = file to hash. uses stdin if none is given.
# accepts input on stdin, leaves it on stdout.
# Output is binary, if you want something else pipe it accordingly.
local blocklen keysize x
case "${1}" in
sha) blocklen=64 ;;
sha1) blocklen=64 ;;
md5) blocklen=64 ;;
md4) blocklen=64 ;;
sha256) blocklen=64 ;;
sha512) blocklen=128 ;;
*) die "Unknown hash ${1} passed to hmac!" ;;
esac
cat <(hmac_ipad ${2} ${blocklen} "${1}") "${3:--}" | openssl dgst "-${1}" -binary | \
cat <(hmac_opad ${2} ${blocklen} "${1}") - | openssl dgst "-${1}" -binary
}
[[ ${1} ]] || die "Must pass the name of the hash function to use to ${0}".
check_deps od openssl
do_hmac "${@}"
#!/bin/bash
# basic amazon s3 operations
# Licensed under the terms of the GNU GPL v2
# Copyright 2007 Victor Lowther <victor.lowther@gmail.com>
set -e
basedir="$( cd -P "$( dirname "$0" )" && pwd )"
PATH="$basedir:$PATH"
# print a message and bail
die() {
echo $*
exit 1
}
# check to see if the variable name passed exists and holds a value.
# Die if it does not.
check_or_die() {
[[ ${!1} ]] || die "Environment variable ${1} is not set."
}
# check to see if we have all the needed S3 variables defined.
# Bail if we do not.
check_s3() {
local sak x
for x in AWS_ID AWS_SECRET; do
check_or_die ${x};
done
sak="$(echo -n $AWS_SECRET | wc -c)"
(( ${sak%%[!0-9 ]*} == 40 )) || \
die "S3 Secret Access Key is not exactly 40 bytes long. Please fix it."
}
# check to see if our external dependencies exist
check_dep() {
local res=0
while [[ $# -ne 0 ]]; do
which "${1}" >& /dev/null || { res=1; echo "${1} not found."; }
shift
done
(( res == 0 )) || die "aborting."
}
check_deps() {
check_dep openssl date hmac cat grep curl
check_s3
}
urlenc() {
# $1 = string to url encode
# output is on stdout
# we don't urlencode everything, just enough stuff.
echo -n "${1}" |
sed 's/%/%25/g
s/ /%20/g
s/#/%23/g
s/\$/%24/g
s/\&/%26/g
s/+/%2b/g
s/,/%2c/g
s/:/%3a/g
s/;/%3b/g
s/?/%3f/g
s/@/%40/g
s/ /%09/g'
}
xmldec() {
# no parameters.
# accept input on stdin, put it on stdout.
# patches accepted to get more stuff
sed 's/\&quot;/\"/g
s/\&amp;/\&/g
s/\&lt;/</g
s/\&gt;/>/g'
}
## basic S3 functionality. x-amz-header functionality is not implemented.
# make an S3 signature string, which will be output on stdout.
s3_signature_string() {
# $1 = HTTP verb
# $2 = date string, must be in UTC
# $3 = bucket name, if any
# $4 = resource path, if any
# $5 = content md5, if any
# $6 = content MIME type, if any
# $7 = canonicalized headers, if any
# signature string will be output on stdout
local verr="Must pass a verb to s3_signature_string!"
local verb="${1:?verr}"
local bucket="${3}"
local resource="${4}"
local derr="Must pass a date to s3_signature_string!"
local date="${2:?derr}"
local mime="${6}"
local md5="${5}"
local headers="${7}"
printf "%s\n%s\n%s\n%s\n%s\n%s%s" \
"${verb}" "${md5}" "${mime}" "${date}" \
"${headers}" "${bucket}" "${resource}" | \
hmac sha1 "${AWS_SECRET}" | openssl base64 -e -a
}
# cheesy, but it is the best way to have multiple headers.
curl_headers() {
# each arg passed will be output on its own line
local parms=$#
for ((;$#;)); do
echo "header = \"${1}\""
shift
done
}
s3_curl() {
# invoke curl to do all the heavy HTTP lifting
# $1 = method (one of GET, PUT, or DELETE. HEAD is not handled yet.)
# $2 = remote bucket.
# $3 = remote name
# $4 = local name.
# $5 = mime type
local bucket remote date sig md5 arg inout headers
# header handling is kinda fugly, but it works.
bucket="${2:+/${2}}/" # slashify the bucket
remote="$(urlenc "${3}")" # if you don't, strange things may happen.
stdopts="--connect-timeout 10 --fail --silent"
mime="${5}"
[[ $CURL_S3_DEBUG == true ]] && stdopts="${stdopts} --show-error --fail"
case "${1}" in
GET) arg="-o" inout="${4:--}" # stdout if no $4
headers[${#headers[@]}]="x-amz-acl: public-read"
;;
PUT) [[ ${2} ]] || die "PUT can has bucket?"
if [[ ! ${3} ]]; then
arg="-X PUT"
headers[${#headers[@]}]="Content-Length: 0"
elif [[ -f ${4} ]]; then
md5="$(openssl dgst -md5 -binary "${4}"|openssl base64 -e -a)"
arg="-T" inout="${4}"
headers[${#headers[@]}]="x-amz-acl: public-read"
headers[${#headers[@]}]="Expect: 100-continue"
if [ "$mime" != "" ]; then
headers[${#headers[@]}]="Content-Type: $mime"
fi
else
die "Cannot write non-existing file ${4}"
fi
;;
DELETE) arg="-X DELETE"
;;
HEAD) arg="-I" ;;
*) die "Unknown verb ${1}. It probably would not have worked anyways." ;;
esac
date="$(TZ=UTC date '+%a, %e %b %Y %H:%M:%S %z')"
sig=$(s3_signature_string ${1} "${date}" "${bucket}" "${remote}" "${md5}" "${mime}" "x-amz-acl:public-read")
headers[${#headers[@]}]="Authorization: AWS ${AWS_ID}:${sig}"
headers[${#headers[@]}]="Date: ${date}"
[[ ${md5} ]] && headers[${#headers[@]}]="Content-MD5: ${md5}"
curl ${arg} "${inout}" ${stdopts} -o - -K <(curl_headers "${headers[@]}") \
"http://s3.amazonaws.com${bucket}${remote}"
return $?
}
s3_put() {
# $1 = remote bucket to put it into
# $2 = remote name to put
# $3 = file to put. This must be present if $2 is.
# $4 = mime type
s3_curl PUT "${1}" "${2}" "${3:-${2}}" "${4}"
return $?
}
s3_get() {
# $1 = bucket to get file from
# $2 = remote file to get
# $3 = local file to get into. Will be overwritten if it exists.
# If this contains a path, that path must exist before calling this.
s3_curl GET "${1}" "${2}" "${3:-${2}}"
return $?
}
s3_test() {
# same args as s3_get, but uses the HEAD verb instead of the GET verb.
s3_curl HEAD "${1}" "${2}" >/dev/null
return $?
}
# Hideously ugly, but it works well enough.
s3_buckets() {
s3_get |grep -o '<Name>[^>]*</Name>' |sed 's/<[^>]*>//g' |xmldec
return $?
}
# this will only return the first thousand entries, alas
# Mabye some kind soul can fix this without writing an XML parser in bash?
# Also need to add xml entity handling.
s3_list() {
# $1 = bucket to list
[ "x${1}" == "x" ] && return 1
s3_get "${1}" |grep -o '<Key>[^>]*</Key>' |sed 's/<[^>]*>//g'| xmldec
return $?
}
s3_delete() {
# $1 = bucket to delete from
# $2 = item to delete
s3_curl DELETE "${1}" "${2}"
return $?
}
# because this uses s3_list, it suffers from the same flaws.
s3_rmrf() {
# $1 = bucket to delete everything from
s3_list "${1}" | while read f; do
s3_delete "${1}" "${f}";
done
}
check_deps
case $1 in
put) shift; s3_put "$@" ;;
get) shift; s3_get "$@" ;;
rm) shift; s3_delete "$@" ;;
ls) shift; s3_list "$@" ;;
test) shift; s3_test "$@" ;;
buckets) s3_buckets ;;
rmrf) shift; s3_rmrf "$@" ;;
*) die "Unknown command ${1}."
;;
esac
#!/bin/sh
set -e
manifest_type="$1"
if [ "$manifest_type" == "" ]; then
echo "usage: $0 <nodejs|npm>"
exit 1
fi
if [ "$AWS_ID" == "" ]; then
echo "must set AWS_ID"
exit 1
fi
if [ "$AWS_SECRET" == "" ]; then
echo "must set AWS_SECRET"
exit 1
fi
if [ "$S3_BUCKET" == "" ]; then
echo "must set S3_BUCKET"
exit 1
fi
basedir="$( cd -P "$( dirname "$0" )" && pwd )"
# make a temp directory
tempdir="$( mktemp -t node_XXXX )"
rm -rf $tempdir
mkdir -p $tempdir
pushd $tempdir
# generate manifest
$basedir/aws/s3 ls $S3_BUCKET \
| grep "^${manifest_type}" \
| sed -e "s/${manifest_type}-\([0-9.]*\)\\.tgz/\\1/" \
| awk 'BEGIN {FS="."} {printf("%03d.%03d.%03d %s\n",$1,$2,$3,$0)}' | sort -r | cut -d" " -f2 \
> manifest.${manifest_type}
# upload manifest to s3
$basedir/aws/s3 put $S3_BUCKET \
manifest.${manifest_type} "" "text/plain"
#!/bin/sh
set -e
node_version="$1"
if [ "$node_version" == "" ]; then
echo "usage: $0 VERSION"
exit 1
fi
if [ "$AWS_ID" == "" ]; then
echo "must set AWS_ID"
exit 1
fi
if [ "$AWS_SECRET" == "" ]; then
echo "must set AWS_SECRET"
exit 1
fi
if [ "$S3_BUCKET" == "" ]; then
echo "must set S3_BUCKET"
exit 1
fi
basedir="$( cd -P "$( dirname "$0" )" && pwd )"
# make a temp directory
tempdir="$( mktemp -t node_XXXX )"
rm -rf $tempdir
mkdir -p $tempdir
cd $tempdir
# build and package nodejs for heroku
vulcan build -v \
-n node \
-c "cd node-v${node_version} && ./configure --prefix=/app/vendor/node && make install" \
-p /app/vendor/node \
-s http://nodejs.org/dist/v${node_version}/node-v${node_version}.tar.gz \
-o $tempdir/node-${node_version}.tgz
# upload nodejs to s3
$basedir/aws/s3 put $S3_BUCKET \
nodejs-${node_version}.tgz $tempdir/node-${node_version}.tgz
# generate manifest
$basedir/manifest nodejs
#!/bin/sh
set -e
npm_version="$1"
if [ "$npm_version" == "" ]; then
echo "usage: $0 VERSION"
exit 1
fi
if [ "$AWS_ID" == "" ]; then
echo "must set AWS_ID"
exit 1
fi
if [ "$AWS_SECRET" == "" ]; then
echo "must set AWS_SECRET"
exit 1
fi
if [ "$S3_BUCKET" == "" ]; then
echo "must set S3_BUCKET"
exit 1
fi
basedir="$( cd -P "$( dirname "$0" )" && pwd )"
# make a temp directory
tempdir="$( mktemp -t node_XXXX )"
rm -rf $tempdir
mkdir -p $tempdir
cd $tempdir
# download npm
git clone https://github.com/isaacs/npm.git
# go into npm dir
pushd npm
# grab the right version
git checkout v${npm_version}
git submodule update --init --recursive
# package it up
tar czvf $tempdir/npm-${npm_version}.tgz *
# upload npm to s3
$basedir/aws/s3 put $S3_BUCKET \
npm-${npm_version}.tgz $tempdir/npm-${npm_version}.tgz
# generate manifest
$basedir/manifest npm
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.18"
},
"dependencies": {
"express": "latest"
}
}
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.4"
}
}
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": "*"
}
}
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.20"
},
"dependencies": {
"some-crazy-dep-that-doesnt-exist": "*"
}
}
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": ">2.0"
}
}
A fake README, to keep npm from polluting stderr.
\ No newline at end of file
Hashish
=======
Hashish is a node.js library for manipulating hash data structures.
It is distilled from the finest that ruby, perl, and haskell have to offer by
way of hash/map interfaces.
Hashish provides a chaining interface, where you can do:
var Hash = require('hashish');
Hash({ a : 1, b : 2, c : 3, d : 4 })
.map(function (x) { return x * 10 })
.filter(function (x) { return x < 30 })
.forEach(function (x, key) {
console.log(key + ' => ' + x);
})
;
Output:
a => 10
b => 20
Some functions and attributes in the chaining interface are terminal, like
`.items` or `.detect()`. They return values of their own instead of the chain
context.
Each function in the chainable interface is also attached to `Hash` in chainless
form:
var Hash = require('hashish');
var obj = { a : 1, b : 2, c : 3, d : 4 };
var mapped = Hash.map(obj, function (x) {
return x * 10
});
console.dir(mapped);
Output:
{ a: 10, b: 20, c: 30, d: 40 }
In either case, the 'this' context of the function calls is the same object that
the chained functions return, so you can make nested chains.
Methods
=======
forEach(cb)
-----------
For each key/value in the hash, calls `cb(value, key)`.
map(cb)
-------
For each key/value in the hash, calls `cb(value, key)`.
The return value of `cb` is the new value at `key` in the resulting hash.
filter(cb)
----------
For each key/value in the hash, calls `cb(value, key)`.
The resulting hash omits key/value pairs where `cb` returned a falsy value.
detect(cb)
----------
Returns the first value in the hash for which `cb(value, key)` is non-falsy.
Order of hashes is not well-defined so watch out for that.
reduce(cb)
----------
Returns the accumulated value of a left-fold over the key/value pairs.
some(cb)
--------
Returns a boolean: whether or not `cb(value, key)` ever returned a non-falsy
value.
update(obj1, [obj2, obj3, ...])
-----------
Mutate the context hash, merging the key/value pairs from the passed objects
and overwriting keys from the context hash if the current `obj` has keys of
the same name. Falsy arguments are silently ignored.
updateAll([ obj1, obj2, ... ])
------------------------------
Like multi-argument `update()` but operate on an array directly.
merge(obj1, [obj2, obj3, ...])
----------
Merge the key/value pairs from the passed objects into the resultant hash
without modifying the context hash. Falsy arguments are silently ignored.
mergeAll([ obj1, obj2, ... ])
------------------------------
Like multi-argument `merge()` but operate on an array directly.
has(key)
--------
Return whether the hash has a key, `key`.
valuesAt(keys)
--------------
Return an Array with the values at the keys from `keys`.
tap(cb)
-------
Call `cb` with the present raw hash.
This function is chainable.
extract(keys)
-------------
Filter by including only those keys in `keys` in the resulting hash.
exclude(keys)
-------------
Filter by excluding those keys in `keys` in the resulting hash.
Attributes
==========
These are attributes in the chaining interface and functions in the `Hash.xxx`
interface.
keys
----
Return all the enumerable attribute keys in the hash.
values
------
Return all the enumerable attribute values in the hash.
compact
-------
Filter out values which are `=== undefined`.
clone
-----
Make a deep copy of the hash.
copy
----
Make a shallow copy of the hash.
length
------
Return the number of key/value pairs in the hash.
Note: use `Hash.size()` for non-chain mode.
size
----
Alias for `length` since `Hash.length` is masked by `Function.prototype`.
See Also
========
See also [creationix's pattern/hash](http://github.com/creationix/pattern),
which does a similar thing except with hash inputs and array outputs.
Installation
============
To install with [npm](http://github.com/isaacs/npm):
npm install hashish
To run the tests with [expresso](http://github.com/visionmedia/expresso):
expresso
var Hash = require('hashish');
Hash({ a : 1, b : 2, c : 3, d : 4 })
.map(function (x) { return x * 10 })
.filter(function (x) { return x < 30 })
.forEach(function (x, key) {
console.log(key + ' => ' + x);
})
;
var Hash = require('hashish');
var obj = { a : 1, b : 2, c : 3, d : 4 };
var mapped = Hash.map(obj, function (x) {
return x * 10
});
console.dir(mapped);
module.exports = Hash;
var Traverse = require('traverse');
function Hash (hash, xs) {
if (Array.isArray(hash) && Array.isArray(xs)) {
var to = Math.min(hash.length, xs.length);
var acc = {};
for (var i = 0; i < to; i++) {
acc[hash[i]] = xs[i];
}
return Hash(acc);
}
if (hash === undefined) return Hash({});
var self = {
map : function (f) {
var acc = { __proto__ : hash.__proto__ };
Object.keys(hash).forEach(function (key) {
acc[key] = f.call(self, hash[key], key);
});
return Hash(acc);
},
forEach : function (f) {
Object.keys(hash).forEach(function (key) {
f.call(self, hash[key], key);
});
return self;
},
filter : function (f) {
var acc = { __proto__ : hash.__proto__ };
Object.keys(hash).forEach(function (key) {
if (f.call(self, hash[key], key)) {
acc[key] = hash[key];
}
});
return Hash(acc);
},
detect : function (f) {
for (var key in hash) {
if (f.call(self, hash[key], key)) {
return hash[key];
}
}
return undefined;
},
reduce : function (f, acc) {
var keys = Object.keys(hash);
if (acc === undefined) acc = keys.shift();
keys.forEach(function (key) {
acc = f.call(self, acc, hash[key], key);
});
return acc;
},
some : function (f) {
for (var key in hash) {
if (f.call(self, hash[key], key)) return true;
}
return false;
},
update : function (obj) {
if (arguments.length > 1) {
self.updateAll([].slice.call(arguments));
}
else {
Object.keys(obj).forEach(function (key) {
hash[key] = obj[key];
});
}
return self;
},
updateAll : function (xs) {
xs.filter(Boolean).forEach(function (x) {
self.update(x);
});
return self;
},
merge : function (obj) {
if (arguments.length > 1) {
return self.copy.updateAll([].slice.call(arguments));
}
else {
return self.copy.update(obj);
}
},
mergeAll : function (xs) {
return self.copy.updateAll(xs);
},
has : function (key) { // only operates on enumerables
return Array.isArray(key)
? key.every(function (k) { return self.has(k) })
: self.keys.indexOf(key.toString()) >= 0;
},
valuesAt : function (keys) {
return Array.isArray(keys)
? keys.map(function (key) { return hash[key] })
: hash[keys]
;
},
tap : function (f) {
f.call(self, hash);
return self;
},
extract : function (keys) {
var acc = {};
keys.forEach(function (key) {
acc[key] = hash[key];
});
return Hash(acc);
},
exclude : function (keys) {
return self.filter(function (_, key) {
return keys.indexOf(key) < 0
});
},
end : hash,
items : hash
};
var props = {
keys : function () { return Object.keys(hash) },
values : function () {
return Object.keys(hash).map(function (key) { return hash[key] });
},
compact : function () {
return self.filter(function (x) { return x !== undefined });
},
clone : function () { return Hash(Hash.clone(hash)) },
copy : function () { return Hash(Hash.copy(hash)) },
length : function () { return Object.keys(hash).length },
size : function () { return self.length }
};
if (Object.defineProperty) {
// es5-shim has an Object.defineProperty but it throws for getters
try {
for (var key in props) {
Object.defineProperty(self, key, { get : props[key] });
}
}
catch (err) {
for (var key in props) {
if (key !== 'clone' && key !== 'copy' && key !== 'compact') {
// ^ those keys use Hash() so can't call them without
// a stack overflow
self[key] = props[key]();
}
}
}
}
else if (self.__defineGetter__) {
for (var key in props) {
self.__defineGetter__(key, props[key]);
}
}
else {
// non-lazy version for browsers that suck >_<
for (var key in props) {
self[key] = props[key]();
}
}
return self;
};
// deep copy
Hash.clone = function (ref) {
return Traverse.clone(ref);
};
// shallow copy
Hash.copy = function (ref) {
var hash = { __proto__ : ref.__proto__ };
Object.keys(ref).forEach(function (key) {
hash[key] = ref[key];
});
return hash;
};
Hash.map = function (ref, f) {
return Hash(ref).map(f).items;
};
Hash.forEach = function (ref, f) {
Hash(ref).forEach(f);
};
Hash.filter = function (ref, f) {
return Hash(ref).filter(f).items;
};
Hash.detect = function (ref, f) {
return Hash(ref).detect(f);
};
Hash.reduce = function (ref, f, acc) {
return Hash(ref).reduce(f, acc);
};
Hash.some = function (ref, f) {
return Hash(ref).some(f);
};
Hash.update = function (a /*, b, c, ... */) {
var args = Array.prototype.slice.call(arguments, 1);
var hash = Hash(a);
return hash.update.apply(hash, args).items;
};
Hash.merge = function (a /*, b, c, ... */) {
var args = Array.prototype.slice.call(arguments, 1);
var hash = Hash(a);
return hash.merge.apply(hash, args).items;
};
Hash.has = function (ref, key) {
return Hash(ref).has(key);
};
Hash.valuesAt = function (ref, keys) {
return Hash(ref).valuesAt(keys);
};
Hash.tap = function (ref, f) {
return Hash(ref).tap(f).items;
};
Hash.extract = function (ref, keys) {
return Hash(ref).extract(keys).items;
};
Hash.exclude = function (ref, keys) {
return Hash(ref).exclude(keys).items;
};
Hash.concat = function (xs) {
var hash = Hash({});
xs.forEach(function (x) { hash.update(x) });
return hash.items;
};
Hash.zip = function (xs, ys) {
return Hash(xs, ys).items;
};
// .length is already defined for function prototypes
Hash.size = function (ref) {
return Hash(ref).size;
};
Hash.compact = function (ref) {
return Hash(ref).compact.items;
};
Copyright 2010 James Halliday (mail@substack.net)
This project is free software released under the MIT/X11 license:
http://www.opensource.org/licenses/mit-license.php
Copyright 2010 James Halliday (mail@substack.net)
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.
var traverse = require('traverse');
var id = 54;
var callbacks = {};
var obj = { moo : function () {}, foo : [2,3,4, function () {}] };
var scrubbed = traverse(obj).map(function (x) {
if (typeof x === 'function') {
callbacks[id] = { id : id, f : x, path : this.path };
this.update('[Function]');
id++;
}
});
console.dir(scrubbed);
console.dir(callbacks);
var traverse = require('traverse');
var obj = {
a : [1,2,3],
b : 4,
c : [5,6],
d : { e : [7,8], f : 9 },
};
var leaves = traverse(obj).reduce(function (acc, x) {
if (this.isLeaf) acc.push(x);
return acc;
}, []);
console.dir(leaves);
var traverse = require('traverse');
var obj = [ 5, 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
traverse(obj).forEach(function (x) {
if (x < 0) this.update(x + 128);
});
console.dir(obj);
// scrub out circular references
var traverse = require('traverse');
var obj = { a : 1, b : 2, c : [ 3, 4 ] };
obj.c.push(obj);
var scrubbed = traverse(obj).map(function (x) {
if (this.circular) this.remove()
});
console.dir(scrubbed);
#!/usr/bin/env node
var traverse = require('traverse');
var obj = [ 'five', 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
var s = '';
traverse(obj).forEach(function to_s (node) {
if (Array.isArray(node)) {
this.before(function () { s += '[' });
this.post(function (child) {
if (!child.isLast) s += ',';
});
this.after(function () { s += ']' });
}
else if (typeof node == 'object') {
this.before(function () { s += '{' });
this.pre(function (x, key) {
to_s(key);
s += ':';
});
this.post(function (child) {
if (!child.isLast) s += ',';
});
this.after(function () { s += '}' });
}
else if (typeof node == 'string') {
s += '"' + node.toString().replace(/"/g, '\\"') + '"';
}
else if (typeof node == 'function') {
s += 'null';
}
else {
s += node.toString();
}
});
console.log('JSON.stringify: ' + JSON.stringify(obj));
console.log('this stringify: ' + s);
var traverse = module.exports = function (obj) {
return new Traverse(obj);
};
function Traverse (obj) {
this.value = obj;
}
Traverse.prototype.get = function (ps) {
var node = this.value;
for (var i = 0; i < ps.length; i ++) {
var key = ps[i];
if (!node || !hasOwnProperty.call(node, key)) {
node = undefined;
break;
}
node = node[key];
}
return node;
};
Traverse.prototype.has = function (ps) {
var node = this.value;
for (var i = 0; i < ps.length; i ++) {
var key = ps[i];
if (!node || !hasOwnProperty.call(node, key)) {
return false;
}
node = node[key];
}
return true;
};
Traverse.prototype.set = function (ps, value) {
var node = this.value;
for (var i = 0; i < ps.length - 1; i ++) {
var key = ps[i];
if (!hasOwnProperty.call(node, key)) node[key] = {};
node = node[key];
}
node[ps[i]] = value;
return value;
};
Traverse.prototype.map = function (cb) {
return walk(this.value, cb, true);
};
Traverse.prototype.forEach = function (cb) {
this.value = walk(this.value, cb, false);
return this.value;
};
Traverse.prototype.reduce = function (cb, init) {
var skip = arguments.length === 1;
var acc = skip ? this.value : init;
this.forEach(function (x) {
if (!this.isRoot || !skip) {
acc = cb.call(this, acc, x);
}
});
return acc;
};
Traverse.prototype.paths = function () {
var acc = [];
this.forEach(function (x) {
acc.push(this.path);
});
return acc;
};
Traverse.prototype.nodes = function () {
var acc = [];
this.forEach(function (x) {
acc.push(this.node);
});
return acc;
};
Traverse.prototype.clone = function () {
var parents = [], nodes = [];
return (function clone (src) {
for (var i = 0; i < parents.length; i++) {
if (parents[i] === src) {
return nodes[i];
}
}
if (typeof src === 'object' && src !== null) {
var dst = copy(src);
parents.push(src);
nodes.push(dst);
forEach(objectKeys(src), function (key) {
dst[key] = clone(src[key]);
});
parents.pop();
nodes.pop();
return dst;
}
else {
return src;
}
})(this.value);
};
function walk (root, cb, immutable) {
var path = [];
var parents = [];
var alive = true;
return (function walker (node_) {
var node = immutable ? copy(node_) : node_;
var modifiers = {};
var keepGoing = true;
var state = {
node : node,
node_ : node_,
path : [].concat(path),
parent : parents[parents.length - 1],
parents : parents,
key : path.slice(-1)[0],
isRoot : path.length === 0,
level : path.length,
circular : null,
update : function (x, stopHere) {
if (!state.isRoot) {
state.parent.node[state.key] = x;
}
state.node = x;
if (stopHere) keepGoing = false;
},
'delete' : function (stopHere) {
delete state.parent.node[state.key];
if (stopHere) keepGoing = false;
},
remove : function (stopHere) {
if (isArray(state.parent.node)) {
state.parent.node.splice(state.key, 1);
}
else {
delete state.parent.node[state.key];
}
if (stopHere) keepGoing = false;
},
keys : null,
before : function (f) { modifiers.before = f },
after : function (f) { modifiers.after = f },
pre : function (f) { modifiers.pre = f },
post : function (f) { modifiers.post = f },
stop : function () { alive = false },
block : function () { keepGoing = false }
};
if (!alive) return state;
function updateState() {
if (typeof state.node === 'object' && state.node !== null) {
if (!state.keys || state.node_ !== state.node) {
state.keys = objectKeys(state.node)
}
state.isLeaf = state.keys.length == 0;
for (var i = 0; i < parents.length; i++) {
if (parents[i].node_ === node_) {
state.circular = parents[i];
break;
}
}
}
else {
state.isLeaf = true;
state.keys = null;
}
state.notLeaf = !state.isLeaf;
state.notRoot = !state.isRoot;
}
updateState();
// use return values to update if defined
var ret = cb.call(state, state.node);
if (ret !== undefined && state.update) state.update(ret);
if (modifiers.before) modifiers.before.call(state, state.node);
if (!keepGoing) return state;
if (typeof state.node == 'object'
&& state.node !== null && !state.circular) {
parents.push(state);
updateState();
forEach(state.keys, function (key, i) {
path.push(key);
if (modifiers.pre) modifiers.pre.call(state, state.node[key], key);
var child = walker(state.node[key]);
if (immutable && hasOwnProperty.call(state.node, key)) {
state.node[key] = child.node;
}
child.isLast = i == state.keys.length - 1;
child.isFirst = i == 0;
if (modifiers.post) modifiers.post.call(state, child);
path.pop();
});
parents.pop();
}
if (modifiers.after) modifiers.after.call(state, state.node);
return state;
})(root).node;
}
function copy (src) {
if (typeof src === 'object' && src !== null) {
var dst;
if (isArray(src)) {
dst = [];
}
else if (isDate(src)) {
dst = new Date(src.getTime ? src.getTime() : src);
}
else if (isRegExp(src)) {
dst = new RegExp(src);
}
else if (isError(src)) {
dst = { message: src.message };
}
else if (isBoolean(src)) {
dst = new Boolean(src);
}
else if (isNumber(src)) {
dst = new Number(src);
}
else if (isString(src)) {
dst = new String(src);
}
else if (Object.create && Object.getPrototypeOf) {
dst = Object.create(Object.getPrototypeOf(src));
}
else if (src.constructor === Object) {
dst = {};
}
else {
var proto =
(src.constructor && src.constructor.prototype)
|| src.__proto__
|| {}
;
var T = function () {};
T.prototype = proto;
dst = new T;
}
forEach(objectKeys(src), function (key) {
dst[key] = src[key];
});
return dst;
}
else return src;
}
var objectKeys = Object.keys || function keys (obj) {
var res = [];
for (var key in obj) res.push(key)
return res;
};
function toS (obj) { return Object.prototype.toString.call(obj) }
function isDate (obj) { return toS(obj) === '[object Date]' }
function isRegExp (obj) { return toS(obj) === '[object RegExp]' }
function isError (obj) { return toS(obj) === '[object Error]' }
function isBoolean (obj) { return toS(obj) === '[object Boolean]' }
function isNumber (obj) { return toS(obj) === '[object Number]' }
function isString (obj) { return toS(obj) === '[object String]' }
var isArray = Array.isArray || function isArray (xs) {
return Object.prototype.toString.call(xs) === '[object Array]';
};
var forEach = function (xs, fn) {
if (xs.forEach) return xs.forEach(fn)
else for (var i = 0; i < xs.length; i++) {
fn(xs[i], i, xs);
}
};
forEach(objectKeys(Traverse.prototype), function (key) {
traverse[key] = function (obj) {
var args = [].slice.call(arguments, 1);
var t = new Traverse(obj);
return t[key].apply(t, args);
};
});
var hasOwnProperty = Object.hasOwnProperty || function (obj, key) {
return key in obj;
};
{
"name": "traverse",
"version": "0.6.6",
"description": "traverse and transform objects by visiting every node on a recursive walk",
"main": "index.js",
"directories": {
"example": "example",
"test": "test"
},
"devDependencies": {
"tape": "~1.0.4"
},
"scripts": {
"test": "tape test/*.js"
},
"testling": {
"files": "test/*.js",
"browsers": {
"iexplore": [
"6.0",
"7.0",
"8.0",
"9.0"
],
"chrome": [
"10.0",
"20.0"
],
"firefox": [
"10.0",
"15.0"
],
"safari": [
"5.1"
],
"opera": [
"12.0"
]
}
},
"repository": {
"type": "git",
"url": "git://github.com/substack/js-traverse.git"
},
"homepage": "https://github.com/substack/js-traverse",
"keywords": [
"traverse",
"walk",
"recursive",
"map",
"forEach",
"deep",
"clone"
],
"author": {
"name": "James Halliday",
"email": "mail@substack.net",
"url": "http://substack.net"
},
"license": "MIT",
"readme": "# traverse\n\nTraverse and transform objects by visiting every node on a recursive walk.\n\n[![browser support](http://ci.testling.com/substack/js-traverse.png)](http://ci.testling.com/substack/js-traverse)\n\n[![build status](https://secure.travis-ci.org/substack/js-traverse.png)](http://travis-ci.org/substack/js-traverse)\n\n# examples\n\n## transform negative numbers in-place\n\nnegative.js\n\n````javascript\nvar traverse = require('traverse');\nvar obj = [ 5, 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];\n\ntraverse(obj).forEach(function (x) {\n if (x < 0) this.update(x + 128);\n});\n\nconsole.dir(obj);\n````\n\nOutput:\n\n [ 5, 6, 125, [ 7, 8, 126, 1 ], { f: 10, g: 115 } ]\n\n## collect leaf nodes\n\nleaves.js\n\n````javascript\nvar traverse = require('traverse');\n\nvar obj = {\n a : [1,2,3],\n b : 4,\n c : [5,6],\n d : { e : [7,8], f : 9 },\n};\n\nvar leaves = traverse(obj).reduce(function (acc, x) {\n if (this.isLeaf) acc.push(x);\n return acc;\n}, []);\n\nconsole.dir(leaves);\n````\n\nOutput:\n\n [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]\n\n## scrub circular references\n\nscrub.js:\n\n````javascript\nvar traverse = require('traverse');\n\nvar obj = { a : 1, b : 2, c : [ 3, 4 ] };\nobj.c.push(obj);\n\nvar scrubbed = traverse(obj).map(function (x) {\n if (this.circular) this.remove()\n});\nconsole.dir(scrubbed);\n````\n\noutput:\n\n { a: 1, b: 2, c: [ 3, 4 ] }\n\n# methods\n\nEach method that takes an `fn` uses the context documented below in the context\nsection.\n\n## .map(fn)\n\nExecute `fn` for each node in the object and return a new object with the\nresults of the walk. To update nodes in the result use `this.update(value)`.\n\n## .forEach(fn)\n\nExecute `fn` for each node in the object but unlike `.map()`, when\n`this.update()` is called it updates the object in-place.\n\n## .reduce(fn, acc)\n\nFor each node in the object, perform a\n[left-fold](http://en.wikipedia.org/wiki/Fold_(higher-order_function))\nwith the return value of `fn(acc, node)`.\n\nIf `acc` isn't specified, `acc` is set to the root object for the first step\nand the root element is skipped.\n\n## .paths()\n\nReturn an `Array` of every possible non-cyclic path in the object.\nPaths are `Array`s of string keys.\n\n## .nodes()\n\nReturn an `Array` of every node in the object.\n\n## .clone()\n\nCreate a deep clone of the object.\n\n## .get(path)\n\nGet the element at the array `path`.\n\n## .set(path, value)\n\nSet the element at the array `path` to `value`.\n\n## .has(path)\n\nReturn whether the element at the array `path` exists.\n\n# context\n\nEach method that takes a callback has a context (its `this` object) with these\nattributes:\n\n## this.node\n\nThe present node on the recursive walk\n\n## this.path\n\nAn array of string keys from the root to the present node\n\n## this.parent\n\nThe context of the node's parent.\nThis is `undefined` for the root node.\n\n## this.key\n\nThe name of the key of the present node in its parent.\nThis is `undefined` for the root node.\n\n## this.isRoot, this.notRoot\n\nWhether the present node is the root node\n\n## this.isLeaf, this.notLeaf\n\nWhether or not the present node is a leaf node (has no children)\n\n## this.level\n\nDepth of the node within the traversal\n\n## this.circular\n\nIf the node equals one of its parents, the `circular` attribute is set to the\ncontext of that parent and the traversal progresses no deeper.\n\n## this.update(value, stopHere=false)\n\nSet a new value for the present node.\n\nAll the elements in `value` will be recursively traversed unless `stopHere` is\ntrue.\n\n## this.remove(stopHere=false)\n\nRemove the current element from the output. If the node is in an Array it will\nbe spliced off. Otherwise it will be deleted from its parent.\n\n## this.delete(stopHere=false)\n\nDelete the current element from its parent in the output. Calls `delete` even on\nArrays.\n\n## this.before(fn)\n\nCall this function before any of the children are traversed.\n\nYou can assign into `this.keys` here to traverse in a custom order.\n\n## this.after(fn)\n\nCall this function after any of the children are traversed.\n\n## this.pre(fn)\n\nCall this function before each of the children are traversed.\n\n## this.post(fn)\n\nCall this function after each of the children are traversed.\n\n\n# install\n\nUsing [npm](http://npmjs.org) do:\n\n $ npm install traverse\n\n# license\n\nMIT\n",
"readmeFilename": "readme.markdown",
"bugs": {
"url": "https://github.com/substack/js-traverse/issues"
},
"_id": "traverse@0.6.6",
"dist": {
"shasum": "887acce42e0d9aa00e0e7e4c00c29529d7df90f2"
},
"_from": "traverse@>=0.2.4",
"_resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz"
}
# traverse
Traverse and transform objects by visiting every node on a recursive walk.
[![browser support](http://ci.testling.com/substack/js-traverse.png)](http://ci.testling.com/substack/js-traverse)
[![build status](https://secure.travis-ci.org/substack/js-traverse.png)](http://travis-ci.org/substack/js-traverse)
# examples
## transform negative numbers in-place
negative.js
````javascript
var traverse = require('traverse');
var obj = [ 5, 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
traverse(obj).forEach(function (x) {
if (x < 0) this.update(x + 128);
});
console.dir(obj);
````
Output:
[ 5, 6, 125, [ 7, 8, 126, 1 ], { f: 10, g: 115 } ]
## collect leaf nodes
leaves.js
````javascript
var traverse = require('traverse');
var obj = {
a : [1,2,3],
b : 4,
c : [5,6],
d : { e : [7,8], f : 9 },
};
var leaves = traverse(obj).reduce(function (acc, x) {
if (this.isLeaf) acc.push(x);
return acc;
}, []);
console.dir(leaves);
````
Output:
[ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
## scrub circular references
scrub.js:
````javascript
var traverse = require('traverse');
var obj = { a : 1, b : 2, c : [ 3, 4 ] };
obj.c.push(obj);
var scrubbed = traverse(obj).map(function (x) {
if (this.circular) this.remove()
});
console.dir(scrubbed);
````
output:
{ a: 1, b: 2, c: [ 3, 4 ] }
# methods
Each method that takes an `fn` uses the context documented below in the context
section.
## .map(fn)
Execute `fn` for each node in the object and return a new object with the
results of the walk. To update nodes in the result use `this.update(value)`.
## .forEach(fn)
Execute `fn` for each node in the object but unlike `.map()`, when
`this.update()` is called it updates the object in-place.
## .reduce(fn, acc)
For each node in the object, perform a
[left-fold](http://en.wikipedia.org/wiki/Fold_(higher-order_function))
with the return value of `fn(acc, node)`.
If `acc` isn't specified, `acc` is set to the root object for the first step
and the root element is skipped.
## .paths()
Return an `Array` of every possible non-cyclic path in the object.
Paths are `Array`s of string keys.
## .nodes()
Return an `Array` of every node in the object.
## .clone()
Create a deep clone of the object.
## .get(path)
Get the element at the array `path`.
## .set(path, value)
Set the element at the array `path` to `value`.
## .has(path)
Return whether the element at the array `path` exists.
# context
Each method that takes a callback has a context (its `this` object) with these
attributes:
## this.node
The present node on the recursive walk
## this.path
An array of string keys from the root to the present node
## this.parent
The context of the node's parent.
This is `undefined` for the root node.
## this.key
The name of the key of the present node in its parent.
This is `undefined` for the root node.
## this.isRoot, this.notRoot
Whether the present node is the root node
## this.isLeaf, this.notLeaf
Whether or not the present node is a leaf node (has no children)
## this.level
Depth of the node within the traversal
## this.circular
If the node equals one of its parents, the `circular` attribute is set to the
context of that parent and the traversal progresses no deeper.
## this.update(value, stopHere=false)
Set a new value for the present node.
All the elements in `value` will be recursively traversed unless `stopHere` is
true.
## this.remove(stopHere=false)
Remove the current element from the output. If the node is in an Array it will
be spliced off. Otherwise it will be deleted from its parent.
## this.delete(stopHere=false)
Delete the current element from its parent in the output. Calls `delete` even on
Arrays.
## this.before(fn)
Call this function before any of the children are traversed.
You can assign into `this.keys` here to traverse in a custom order.
## this.after(fn)
Call this function after any of the children are traversed.
## this.pre(fn)
Call this function before each of the children are traversed.
## this.post(fn)
Call this function after each of the children are traversed.
# install
Using [npm](http://npmjs.org) do:
$ npm install traverse
# license
MIT
var test = require('tape');
var traverse = require('../');
var deepEqual = require('./lib/deep_equal');
var util = require('util');
test('circular', function (t) {
t.plan(1);
var obj = { x : 3 };
obj.y = obj;
traverse(obj).forEach(function (x) {
if (this.path.join('') == 'y') {
t.equal(
util.inspect(this.circular.node),
util.inspect(obj)
);
}
});
});
test('deepCirc', function (t) {
t.plan(2);
var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
obj.y[2] = obj;
var times = 0;
traverse(obj).forEach(function (x) {
if (this.circular) {
t.same(this.circular.path, []);
t.same(this.path, [ 'y', 2 ]);
}
});
});
test('doubleCirc', function (t) {
var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
obj.y[2] = obj;
obj.x.push(obj.y);
var circs = [];
traverse(obj).forEach(function (x) {
if (this.circular) {
circs.push({ circ : this.circular, self : this, node : x });
}
});
t.same(circs[0].self.path, [ 'x', 3, 2 ]);
t.same(circs[0].circ.path, []);
t.same(circs[1].self.path, [ 'y', 2 ]);
t.same(circs[1].circ.path, []);
t.same(circs.length, 2);
t.end();
});
test('circDubForEach', function (t) {
var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
obj.y[2] = obj;
obj.x.push(obj.y);
traverse(obj).forEach(function (x) {
if (this.circular) this.update('...');
});
t.same(obj, { x : [ 1, 2, 3, [ 4, 5, '...' ] ], y : [ 4, 5, '...' ] });
t.end();
});
test('circDubMap', function (t) {
var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
obj.y[2] = obj;
obj.x.push(obj.y);
var c = traverse(obj).map(function (x) {
if (this.circular) {
this.update('...');
}
});
t.same(c, { x : [ 1, 2, 3, [ 4, 5, '...' ] ], y : [ 4, 5, '...' ] });
t.end();
});
test('circClone', function (t) {
var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
obj.y[2] = obj;
obj.x.push(obj.y);
var clone = traverse.clone(obj);
t.ok(obj !== clone);
t.ok(clone.y[2] === clone);
t.ok(clone.y[2] !== obj);
t.ok(clone.x[3][2] === clone);
t.ok(clone.x[3][2] !== obj);
t.same(clone.x.slice(0,3), [1,2,3]);
t.same(clone.y.slice(0,2), [4,5]);
t.end();
});
test('circMapScrub', function (t) {
var obj = { a : 1, b : 2 };
obj.c = obj;
var scrubbed = traverse(obj).map(function (node) {
if (this.circular) this.remove();
});
t.same(
Object.keys(scrubbed).sort(),
[ 'a', 'b' ]
);
t.ok(deepEqual(scrubbed, { a : 1, b : 2 }));
t.equal(obj.c, obj);
t.end();
});
var test = require('tape');
var traverse = require('../');
test('dateEach', function (t) {
var obj = { x : new Date, y : 10, z : 5 };
var counts = {};
traverse(obj).forEach(function (node) {
var t = (node instanceof Date && 'Date') || typeof node;
counts[t] = (counts[t] || 0) + 1;
});
t.same(counts, {
object : 1,
Date : 1,
number : 2,
});
t.end();
});
test('dateMap', function (t) {
var obj = { x : new Date, y : 10, z : 5 };
var res = traverse(obj).map(function (node) {
if (typeof node === 'number') this.update(node + 100);
});
t.ok(obj.x !== res.x);
t.same(res, {
x : obj.x,
y : 110,
z : 105,
});
t.end();
});
var test = require('tape');
var traverse = require('../');
var deepEqual = require('./lib/deep_equal');
test('deepDates', function (t) {
t.plan(2);
t.ok(
deepEqual(
{ d : new Date, x : [ 1, 2, 3 ] },
{ d : new Date, x : [ 1, 2, 3 ] }
),
'dates should be equal'
);
var d0 = new Date;
setTimeout(function () {
t.ok(
!deepEqual(
{ d : d0, x : [ 1, 2, 3 ], },
{ d : new Date, x : [ 1, 2, 3 ] }
),
'microseconds should count in date equality'
);
}, 5);
});
test('deepCircular', function (t) {
var a = [1];
a.push(a); // a = [ 1, *a ]
var b = [1];
b.push(a); // b = [ 1, [ 1, *a ] ]
t.ok(
!deepEqual(a, b),
'circular ref mount points count towards equality'
);
var c = [1];
c.push(c); // c = [ 1, *c ]
t.ok(
deepEqual(a, c),
'circular refs are structurally the same here'
);
var d = [1];
d.push(a); // c = [ 1, [ 1, *d ] ]
t.ok(
deepEqual(b, d),
'non-root circular ref structural comparison'
);
t.end();
});
test('deepInstances', function (t) {
t.ok(
!deepEqual([ new Boolean(false) ], [ false ]),
'boolean instances are not real booleans'
);
t.ok(
!deepEqual([ new String('x') ], [ 'x' ]),
'string instances are not real strings'
);
t.ok(
!deepEqual([ new Number(4) ], [ 4 ]),
'number instances are not real numbers'
);
t.ok(
deepEqual([ new RegExp('x') ], [ /x/ ]),
'regexp instances are real regexps'
);
t.ok(
!deepEqual([ new RegExp(/./) ], [ /../ ]),
'these regexps aren\'t the same'
);
t.ok(
!deepEqual(
[ function (x) { return x * 2 } ],
[ function (x) { return x * 2 } ]
),
'functions with the same .toString() aren\'t necessarily the same'
);
var f = function (x) { return x * 2 };
t.ok(
deepEqual([ f ], [ f ]),
'these functions are actually equal'
);
t.end();
});
test('deepEqual', function (t) {
t.ok(
!deepEqual([ 1, 2, 3 ], { 0 : 1, 1 : 2, 2 : 3 }),
'arrays are not objects'
);
t.end();
});
test('falsy', function (t) {
t.ok(
!deepEqual([ undefined ], [ null ]),
'null is not undefined!'
);
t.ok(
!deepEqual([ null ], [ undefined ]),
'undefined is not null!'
);
t.ok(
!deepEqual(
{ a : 1, b : 2, c : [ 3, undefined, 5 ] },
{ a : 1, b : 2, c : [ 3, null, 5 ] }
),
'undefined is not null, however deeply!'
);
t.ok(
!deepEqual(
{ a : 1, b : 2, c : [ 3, undefined, 5 ] },
{ a : 1, b : 2, c : [ 3, null, 5 ] }
),
'null is not undefined, however deeply!'
);
t.ok(
!deepEqual(
{ a : 1, b : 2, c : [ 3, undefined, 5 ] },
{ a : 1, b : 2, c : [ 3, null, 5 ] }
),
'null is not undefined, however deeply!'
);
t.end();
});
test('deletedArrayEqual', function (t) {
var xs = [ 1, 2, 3, 4 ];
delete xs[2];
var ys = Object.create(Array.prototype);
ys[0] = 1;
ys[1] = 2;
ys[3] = 4;
t.ok(
deepEqual(xs, ys),
'arrays with deleted elements are only equal to'
+ ' arrays with similarly deleted elements'
);
t.ok(
!deepEqual(xs, [ 1, 2, undefined, 4 ]),
'deleted array elements cannot be undefined'
);
t.ok(
!deepEqual(xs, [ 1, 2, null, 4 ]),
'deleted array elements cannot be null'
);
t.end();
});
test('deletedObjectEqual', function (t) {
var obj = { a : 1, b : 2, c : 3 };
delete obj.c;
t.ok(
deepEqual(obj, { a : 1, b : 2 }),
'deleted object elements should not show up'
);
t.ok(
!deepEqual(obj, { a : 1, b : 2, c : undefined }),
'deleted object elements are not undefined'
);
t.ok(
!deepEqual(obj, { a : 1, b : 2, c : null }),
'deleted object elements are not null'
);
t.end();
});
test('emptyKeyEqual', function (t) {
t.ok(!deepEqual(
{ a : 1 }, { a : 1, '' : 55 }
));
t.end();
});
test('deepArguments', function (t) {
t.ok(
!deepEqual(
[ 4, 5, 6 ],
(function () { return arguments })(4, 5, 6)
),
'arguments are not arrays'
);
t.ok(
deepEqual(
(function () { return arguments })(4, 5, 6),
(function () { return arguments })(4, 5, 6)
),
'arguments should equal'
);
t.end();
});
test('deepUn', function (t) {
t.ok(!deepEqual({ a : 1, b : 2 }, undefined));
t.ok(!deepEqual({ a : 1, b : 2 }, {}));
t.ok(!deepEqual(undefined, { a : 1, b : 2 }));
t.ok(!deepEqual({}, { a : 1, b : 2 }));
t.ok(deepEqual(undefined, undefined));
t.ok(deepEqual(null, null));
t.ok(!deepEqual(undefined, null));
t.end();
});
test('deepLevels', function (t) {
var xs = [ 1, 2, [ 3, 4, [ 5, 6 ] ] ];
t.ok(!deepEqual(xs, []));
t.end();
});
var test = require('tape');
var traverse = require('../');
test('traverse an Error', function (t) {
var obj = new Error("test");
var results = traverse(obj).map(function (node) {});
t.same(results, { message: 'test' });
t.end();
});
var test = require('tape');
var traverse = require('../');
test('has', function (t) {
var obj = { a : 2, b : [ 4, 5, { c : 6 } ] };
t.equal(traverse(obj).has([ 'b', 2, 'c' ]), true)
t.equal(traverse(obj).has([ 'b', 2, 'c', 0 ]), false)
t.equal(traverse(obj).has([ 'b', 2, 'd' ]), false)
t.equal(traverse(obj).has([]), true)
t.equal(traverse(obj).has([ 'a' ]), true)
t.equal(traverse(obj).has([ 'a', 2 ]), false)
t.end();
});
var test = require('tape');
var traverse = require('../');
var EventEmitter = require('events').EventEmitter;
test('check instanceof on node elems', function (t) {
var counts = { emitter : 0 };
traverse([ new EventEmitter, 3, 4, { ev : new EventEmitter }])
.forEach(function (node) {
if (node instanceof EventEmitter) counts.emitter ++;
})
;
t.equal(counts.emitter, 2);
t.end();
});
var test = require('tape');
var traverse = require('../');
test('interface map', function (t) {
var obj = { a : [ 5,6,7 ], b : { c : [8] } };
t.same(
traverse.paths(obj)
.sort()
.map(function (path) { return path.join('/') })
.slice(1)
.join(' ')
,
'a a/0 a/1 a/2 b b/c b/c/0'
);
t.same(
traverse.nodes(obj),
[
{ a: [ 5, 6, 7 ], b: { c: [ 8 ] } },
[ 5, 6, 7 ], 5, 6, 7,
{ c: [ 8 ] }, [ 8 ], 8
]
);
t.same(
traverse.map(obj, function (node) {
if (typeof node == 'number') {
return node + 1000;
}
else if (Array.isArray(node)) {
return node.join(' ');
}
}),
{ a: '5 6 7', b: { c: '8' } }
);
var nodes = 0;
traverse.forEach(obj, function (node) { nodes ++ });
t.same(nodes, 8);
t.end();
});
var test = require('tape');
var traverse = require('../');
test('json test', function (t) {
var id = 54;
var callbacks = {};
var obj = { moo : function () {}, foo : [2,3,4, function () {}] };
var scrubbed = traverse(obj).map(function (x) {
if (typeof x === 'function') {
callbacks[id] = { id : id, f : x, path : this.path };
this.update('[Function]');
id++;
}
});
t.equal(
scrubbed.moo, '[Function]',
'obj.moo replaced with "[Function]"'
);
t.equal(
scrubbed.foo[3], '[Function]',
'obj.foo[3] replaced with "[Function]"'
);
t.same(scrubbed, {
moo : '[Function]',
foo : [ 2, 3, 4, "[Function]" ]
}, 'Full JSON string matches');
t.same(
typeof obj.moo, 'function',
'Original obj.moo still a function'
);
t.same(
typeof obj.foo[3], 'function',
'Original obj.foo[3] still a function'
);
t.same(callbacks, {
54: { id: 54, f : obj.moo, path: [ 'moo' ] },
55: { id: 55, f : obj.foo[3], path: [ 'foo', '3' ] },
}, 'Check the generated callbacks list');
t.end();
});
var test = require('tape');
var traverse = require('../');
test('sort test', function (t) {
var acc = [];
traverse({
a: 30,
b: 22,
id: 9
}).forEach(function (node) {
if ((! Array.isArray(node)) && typeof node === 'object') {
this.before(function(node) {
this.keys = Object.keys(node);
this.keys.sort(function(a, b) {
a = [a === "id" ? 0 : 1, a];
b = [b === "id" ? 0 : 1, b];
return a < b ? -1 : a > b ? 1 : 0;
});
});
}
if (this.isLeaf) acc.push(node);
});
t.equal(
acc.join(' '),
'9 30 22',
'Traversal in a custom order'
);
t.end();
});
var test = require('tape');
var traverse = require('../');
test('leaves test', function (t) {
var acc = [];
traverse({
a : [1,2,3],
b : 4,
c : [5,6],
d : { e : [7,8], f : 9 }
}).forEach(function (x) {
if (this.isLeaf) acc.push(x);
});
t.equal(
acc.join(' '),
'1 2 3 4 5 6 7 8 9',
'Traversal in the right(?) order'
);
t.end();
});
var traverse = require('../../');
module.exports = function (a, b) {
if (arguments.length !== 2) {
throw new Error(
'deepEqual requires exactly two objects to compare against'
);
}
var equal = true;
var node = b;
traverse(a).forEach(function (y) {
var notEqual = (function () {
equal = false;
//this.stop();
return undefined;
}).bind(this);
//if (node === undefined || node === null) return notEqual();
if (!this.isRoot) {
/*
if (!Object.hasOwnProperty.call(node, this.key)) {
return notEqual();
}
*/
if (typeof node !== 'object') return notEqual();
node = node[this.key];
}
var x = node;
this.post(function () {
node = x;
});
var toS = function (o) {
return Object.prototype.toString.call(o);
};
if (this.circular) {
if (traverse(b).get(this.circular.path) !== x) notEqual();
}
else if (typeof x !== typeof y) {
notEqual();
}
else if (x === null || y === null || x === undefined || y === undefined) {
if (x !== y) notEqual();
}
else if (x.__proto__ !== y.__proto__) {
notEqual();
}
else if (x === y) {
// nop
}
else if (typeof x === 'function') {
if (x instanceof RegExp) {
// both regexps on account of the __proto__ check
if (x.toString() != y.toString()) notEqual();
}
else if (x !== y) notEqual();
}
else if (typeof x === 'object') {
if (toS(y) === '[object Arguments]'
|| toS(x) === '[object Arguments]') {
if (toS(x) !== toS(y)) {
notEqual();
}
}
else if (toS(y) === '[object RegExp]'
|| toS(x) === '[object RegExp]') {
if (!x || !y || x.toString() !== y.toString()) notEqual();
}
else if (x instanceof Date || y instanceof Date) {
if (!(x instanceof Date) || !(y instanceof Date)
|| x.getTime() !== y.getTime()) {
notEqual();
}
}
else {
var kx = Object.keys(x);
var ky = Object.keys(y);
if (kx.length !== ky.length) return notEqual();
for (var i = 0; i < kx.length; i++) {
var k = kx[i];
if (!Object.hasOwnProperty.call(y, k)) {
notEqual();
}
}
}
}
});
return equal;
};
var test = require('tape');
var traverse = require('../');
var deepEqual = require('./lib/deep_equal');
test('mutate', function (t) {
var obj = { a : 1, b : 2, c : [ 3, 4 ] };
var res = traverse(obj).forEach(function (x) {
if (typeof x === 'number' && x % 2 === 0) {
this.update(x * 10);
}
});
t.same(obj, res);
t.same(obj, { a : 1, b : 20, c : [ 3, 40 ] });
t.end();
});
test('mutateT', function (t) {
var obj = { a : 1, b : 2, c : [ 3, 4 ] };
var res = traverse.forEach(obj, function (x) {
if (typeof x === 'number' && x % 2 === 0) {
this.update(x * 10);
}
});
t.same(obj, res);
t.same(obj, { a : 1, b : 20, c : [ 3, 40 ] });
t.end();
});
test('map', function (t) {
var obj = { a : 1, b : 2, c : [ 3, 4 ] };
var res = traverse(obj).map(function (x) {
if (typeof x === 'number' && x % 2 === 0) {
this.update(x * 10);
}
});
t.same(obj, { a : 1, b : 2, c : [ 3, 4 ] });
t.same(res, { a : 1, b : 20, c : [ 3, 40 ] });
t.end();
});
test('mapT', function (t) {
var obj = { a : 1, b : 2, c : [ 3, 4 ] };
var res = traverse.map(obj, function (x) {
if (typeof x === 'number' && x % 2 === 0) {
this.update(x * 10);
}
});
t.same(obj, { a : 1, b : 2, c : [ 3, 4 ] });
t.same(res, { a : 1, b : 20, c : [ 3, 40 ] });
t.end();
});
test('clone', function (t) {
var obj = { a : 1, b : 2, c : [ 3, 4 ] };
var res = traverse(obj).clone();
t.same(obj, res);
t.ok(obj !== res);
obj.a ++;
t.same(res.a, 1);
obj.c.push(5);
t.same(res.c, [ 3, 4 ]);
t.end();
});
test('cloneT', function (t) {
var obj = { a : 1, b : 2, c : [ 3, 4 ] };
var res = traverse.clone(obj);
t.same(obj, res);
t.ok(obj !== res);
obj.a ++;
t.same(res.a, 1);
obj.c.push(5);
t.same(res.c, [ 3, 4 ]);
t.end();
});
test('reduce', function (t) {
var obj = { a : 1, b : 2, c : [ 3, 4 ] };
var res = traverse(obj).reduce(function (acc, x) {
if (this.isLeaf) acc.push(x);
return acc;
}, []);
t.same(obj, { a : 1, b : 2, c : [ 3, 4 ] });
t.same(res, [ 1, 2, 3, 4 ]);
t.end();
});
test('reduceInit', function (t) {
var obj = { a : 1, b : 2, c : [ 3, 4 ] };
var res = traverse(obj).reduce(function (acc, x) {
if (this.isRoot) assert.fail('got root');
return acc;
});
t.same(obj, { a : 1, b : 2, c : [ 3, 4 ] });
t.same(res, obj);
t.end();
});
test('remove', function (t) {
var obj = { a : 1, b : 2, c : [ 3, 4 ] };
traverse(obj).forEach(function (x) {
if (this.isLeaf && x % 2 == 0) this.remove();
});
t.same(obj, { a : 1, c : [ 3 ] });
t.end();
});
exports.removeNoStop = function() {
var obj = { a : 1, b : 2, c : { d: 3, e: 4 }, f: 5 };
var keys = [];
traverse(obj).forEach(function (x) {
keys.push(this.key)
if (this.key == 'c') this.remove();
});
t.same(keys, [undefined, 'a', 'b', 'c', 'd', 'e', 'f'])
t.end();
}
exports.removeStop = function() {
var obj = { a : 1, b : 2, c : { d: 3, e: 4 }, f: 5 };
var keys = [];
traverse(obj).forEach(function (x) {
keys.push(this.key)
if (this.key == 'c') this.remove(true);
});
t.same(keys, [undefined, 'a', 'b', 'c', 'f'])
t.end();
}
test('removeMap', function (t) {
var obj = { a : 1, b : 2, c : [ 3, 4 ] };
var res = traverse(obj).map(function (x) {
if (this.isLeaf && x % 2 == 0) this.remove();
});
t.same(obj, { a : 1, b : 2, c : [ 3, 4 ] });
t.same(res, { a : 1, c : [ 3 ] });
t.end();
});
test('delete', function (t) {
var obj = { a : 1, b : 2, c : [ 3, 4 ] };
traverse(obj).forEach(function (x) {
if (this.isLeaf && x % 2 == 0) this.delete();
});
t.ok(!deepEqual(
obj, { a : 1, c : [ 3, undefined ] }
));
t.ok(deepEqual(
obj, { a : 1, c : [ 3 ] }
));
t.ok(!deepEqual(
obj, { a : 1, c : [ 3, null ] }
));
t.end();
});
test('deleteNoStop', function (t) {
var obj = { a : 1, b : 2, c : { d: 3, e: 4 } };
var keys = [];
traverse(obj).forEach(function (x) {
keys.push(this.key)
if (this.key == 'c') this.delete();
});
t.same(keys, [undefined, 'a', 'b', 'c', 'd', 'e'])
t.end();
});
test('deleteStop', function (t) {
var obj = { a : 1, b : 2, c : { d: 3, e: 4 } };
var keys = [];
traverse(obj).forEach(function (x) {
keys.push(this.key)
if (this.key == 'c') this.delete(true);
});
t.same(keys, [undefined, 'a', 'b', 'c'])
t.end();
});
test('deleteRedux', function (t) {
var obj = { a : 1, b : 2, c : [ 3, 4, 5 ] };
traverse(obj).forEach(function (x) {
if (this.isLeaf && x % 2 == 0) this.delete();
});
t.ok(!deepEqual(
obj, { a : 1, c : [ 3, undefined, 5 ] }
));
t.ok(deepEqual(
obj, { a : 1, c : [ 3 ,, 5 ] }
));
t.ok(!deepEqual(
obj, { a : 1, c : [ 3, null, 5 ] }
));
t.ok(!deepEqual(
obj, { a : 1, c : [ 3, 5 ] }
));
t.end();
});
test('deleteMap', function (t) {
var obj = { a : 1, b : 2, c : [ 3, 4 ] };
var res = traverse(obj).map(function (x) {
if (this.isLeaf && x % 2 == 0) this.delete();
});
t.ok(deepEqual(
obj,
{ a : 1, b : 2, c : [ 3, 4 ] }
));
var xs = [ 3, 4 ];
delete xs[1];
t.ok(deepEqual(
res, { a : 1, c : xs }
));
t.ok(deepEqual(
res, { a : 1, c : [ 3, ] }
));
t.ok(deepEqual(
res, { a : 1, c : [ 3 ] }
));
t.end();
});
test('deleteMapRedux', function (t) {
var obj = { a : 1, b : 2, c : [ 3, 4, 5 ] };
var res = traverse(obj).map(function (x) {
if (this.isLeaf && x % 2 == 0) this.delete();
});
t.ok(deepEqual(
obj,
{ a : 1, b : 2, c : [ 3, 4, 5 ] }
));
var xs = [ 3, 4, 5 ];
delete xs[1];
t.ok(deepEqual(
res, { a : 1, c : xs }
));
t.ok(!deepEqual(
res, { a : 1, c : [ 3, 5 ] }
));
t.ok(deepEqual(
res, { a : 1, c : [ 3 ,, 5 ] }
));
t.end();
});
test('objectToString', function (t) {
var obj = { a : 1, b : 2, c : [ 3, 4 ] };
var res = traverse(obj).forEach(function (x) {
if (typeof x === 'object' && !this.isRoot) {
this.update(JSON.stringify(x));
}
});
t.same(obj, res);
t.same(obj, { a : 1, b : 2, c : "[3,4]" });
t.end();
});
test('stringToObject', function (t) {
var obj = { a : 1, b : 2, c : "[3,4]" };
var res = traverse(obj).forEach(function (x) {
if (typeof x === 'string') {
this.update(JSON.parse(x));
}
else if (typeof x === 'number' && x % 2 === 0) {
this.update(x * 10);
}
});
t.deepEqual(obj, res);
t.deepEqual(obj, { a : 1, b : 20, c : [ 3, 40 ] });
t.end();
});
var traverse = require('../');
var test = require('tape');
test('negative update test', function (t) {
var obj = [ 5, 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
var fixed = traverse.map(obj, function (x) {
if (x < 0) this.update(x + 128);
});
t.same(fixed,
[ 5, 6, 125, [ 7, 8, 126, 1 ], { f: 10, g: 115 } ],
'Negative values += 128'
);
t.same(obj,
[ 5, 6, -3, [ 7, 8, -2, 1 ], { f: 10, g: -13 } ],
'Original references not modified'
);
t.end();
});
var test = require('tape');
var traverse = require('../');
test('traverse an object with nested functions', function (t) {
t.plan(1);
function Cons (x) {
t.equal(x, 10)
};
traverse(new Cons(10));
});
var test = require('tape');
var traverse = require('../');
test('siblings', function (t) {
var obj = { a : 1, b : 2, c : [ 4, 5, 6 ] };
var res = traverse(obj).reduce(function (acc, x) {
var p = '/' + this.path.join('/');
if (this.parent) {
acc[p] = {
siblings : this.parent.keys,
key : this.key,
index : this.parent.keys.indexOf(this.key)
};
}
else {
acc[p] = {
siblings : [],
key : this.key,
index : -1
}
}
return acc;
}, {});
t.same(res, {
'/' : { siblings : [], key : undefined, index : -1 },
'/a' : { siblings : [ 'a', 'b', 'c' ], key : 'a', index : 0 },
'/b' : { siblings : [ 'a', 'b', 'c' ], key : 'b', index : 1 },
'/c' : { siblings : [ 'a', 'b', 'c' ], key : 'c', index : 2 },
'/c/0' : { siblings : [ '0', '1', '2' ], key : '0', index : 0 },
'/c/1' : { siblings : [ '0', '1', '2' ], key : '1', index : 1 },
'/c/2' : { siblings : [ '0', '1', '2' ], key : '2', index : 2 }
});
t.end();
});
var test = require('tape');
var traverse = require('../');
test('stop', function (t) {
var visits = 0;
traverse('abcdefghij'.split('')).forEach(function (node) {
if (typeof node === 'string') {
visits ++;
if (node === 'e') this.stop()
}
});
t.equal(visits, 5);
t.end();
});
test('stopMap', function (t) {
var s = traverse('abcdefghij'.split('')).map(function (node) {
if (typeof node === 'string') {
if (node === 'e') this.stop()
return node.toUpperCase();
}
}).join('');
t.equal(s, 'ABCDEfghij');
t.end();
});
test('stopReduce', function (t) {
var obj = {
a : [ 4, 5 ],
b : [ 6, [ 7, 8, 9 ] ]
};
var xs = traverse(obj).reduce(function (acc, node) {
if (this.isLeaf) {
if (node === 7) this.stop();
else acc.push(node)
}
return acc;
}, []);
t.same(xs, [ 4, 5, 6 ]);
t.end();
});
var test = require('tape');
var traverse = require('../');
test('stringify', function (t) {
var obj = [ 5, 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
var s = '';
traverse(obj).forEach(function (node) {
if (Array.isArray(node)) {
this.before(function () { s += '[' });
this.post(function (child) {
if (!child.isLast) s += ',';
});
this.after(function () { s += ']' });
}
else if (typeof node == 'object') {
this.before(function () { s += '{' });
this.pre(function (x, key) {
s += '"' + key + '"' + ':';
});
this.post(function (child) {
if (!child.isLast) s += ',';
});
this.after(function () { s += '}' });
}
else if (typeof node == 'function') {
s += 'null';
}
else {
s += node.toString();
}
});
t.equal(s, JSON.stringify(obj));
t.end();
});
var traverse = require('../');
var test = require('tape');
test('subexpr', function (t) {
var obj = [ 'a', 4, 'b', 5, 'c', 6 ];
var r = traverse(obj).map(function (x) {
if (typeof x === 'number') {
this.update([ x - 0.1, x, x + 0.1 ], true);
}
});
t.same(obj, [ 'a', 4, 'b', 5, 'c', 6 ]);
t.same(r, [
'a', [ 3.9, 4, 4.1 ],
'b', [ 4.9, 5, 5.1 ],
'c', [ 5.9, 6, 6.1 ],
]);
t.end();
});
test('block', function (t) {
var obj = [ [ 1 ], [ 2 ], [ 3 ] ];
var r = traverse(obj).map(function (x) {
if (Array.isArray(x) && !this.isRoot) {
if (x[0] === 5) this.block()
else this.update([ [ x[0] + 1 ] ])
}
});
t.same(r, [
[ [ [ [ [ 5 ] ] ] ] ],
[ [ [ [ 5 ] ] ] ],
[ [ [ 5 ] ] ],
]);
t.end();
});
var test = require('tape');
var traverse = require('../');
var deepEqual = require('./lib/deep_equal');
test('super_deep', function (t) {
var util = require('util');
var a0 = make();
var a1 = make();
t.ok(deepEqual(a0, a1));
a0.c.d.moo = true;
t.ok(!deepEqual(a0, a1));
a1.c.d.moo = true;
t.ok(deepEqual(a0, a1));
// TODO: this one
//a0.c.a = a1;
//t.ok(!deepEqual(a0, a1));
t.end();
});
function make () {
var a = { self : 'a' };
var b = { self : 'b' };
var c = { self : 'c' };
var d = { self : 'd' };
var e = { self : 'e' };
a.a = a;
a.b = b;
a.c = c;
b.a = a;
b.b = b;
b.c = c;
c.a = a;
c.b = b;
c.c = c;
c.d = d;
d.a = a;
d.b = b;
d.c = c;
d.d = d;
d.e = e;
e.a = a;
e.b = b;
e.c = c;
e.d = d;
e.e = e;
return a;
}
var traverse = require('./');
var test = require('testling');
test('leaves', function (t) {
var obj = {
a : [1,2,3],
b : 4,
c : [5,6],
d : { e : [7,8], f : 9 }
};
var acc = [];
traverse(obj).forEach(function (x) {
if (this.isLeaf) acc.push(x);
});
t.deepEqual(
acc, [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ],
'traversal in the proper order'
);
t.end();
});
{
"name": "hashish",
"version": "0.0.4",
"description": "Hash data structure manipulation functions",
"main": "./index.js",
"repository": {
"type": "git",
"url": "http://github.com/substack/node-hashish.git"
},
"keywords": [
"hash",
"object",
"convenience",
"manipulation",
"data structure"
],
"author": {
"name": "James Halliday",
"email": "mail@substack.net",
"url": "http://substack.net"
},
"dependencies": {
"traverse": ">=0.2.4"
},
"devDependencies": {
"expresso": ">=0.6.0"
},
"scripts": {
"test": "expresso"
},
"license": "MIT/X11",
"engine": [
"node >=0.2.0"
],
"readme": "Hashish\n=======\n\nHashish is a node.js library for manipulating hash data structures.\nIt is distilled from the finest that ruby, perl, and haskell have to offer by\nway of hash/map interfaces.\n\nHashish provides a chaining interface, where you can do:\n\n var Hash = require('hashish');\n \n Hash({ a : 1, b : 2, c : 3, d : 4 })\n .map(function (x) { return x * 10 })\n .filter(function (x) { return x < 30 })\n .forEach(function (x, key) {\n console.log(key + ' => ' + x);\n })\n ;\n \nOutput:\n\n a => 10\n b => 20\n\nSome functions and attributes in the chaining interface are terminal, like\n`.items` or `.detect()`. They return values of their own instead of the chain\ncontext.\n\nEach function in the chainable interface is also attached to `Hash` in chainless\nform:\n\n var Hash = require('hashish');\n var obj = { a : 1, b : 2, c : 3, d : 4 };\n \n var mapped = Hash.map(obj, function (x) {\n return x * 10\n });\n \n console.dir(mapped);\n\nOutput:\n\n { a: 10, b: 20, c: 30, d: 40 }\n\nIn either case, the 'this' context of the function calls is the same object that\nthe chained functions return, so you can make nested chains.\n\nMethods\n=======\n\nforEach(cb)\n-----------\n\nFor each key/value in the hash, calls `cb(value, key)`.\n\nmap(cb)\n-------\n\nFor each key/value in the hash, calls `cb(value, key)`.\nThe return value of `cb` is the new value at `key` in the resulting hash.\n\nfilter(cb)\n----------\n\nFor each key/value in the hash, calls `cb(value, key)`.\nThe resulting hash omits key/value pairs where `cb` returned a falsy value.\n\ndetect(cb)\n----------\n\nReturns the first value in the hash for which `cb(value, key)` is non-falsy.\nOrder of hashes is not well-defined so watch out for that.\n\nreduce(cb)\n----------\n\nReturns the accumulated value of a left-fold over the key/value pairs.\n\nsome(cb)\n--------\n\nReturns a boolean: whether or not `cb(value, key)` ever returned a non-falsy\nvalue.\n\nupdate(obj1, [obj2, obj3, ...])\n-----------\n\nMutate the context hash, merging the key/value pairs from the passed objects\nand overwriting keys from the context hash if the current `obj` has keys of\nthe same name. Falsy arguments are silently ignored.\n\nupdateAll([ obj1, obj2, ... ])\n------------------------------\n\nLike multi-argument `update()` but operate on an array directly.\n\nmerge(obj1, [obj2, obj3, ...])\n----------\n\nMerge the key/value pairs from the passed objects into the resultant hash\nwithout modifying the context hash. Falsy arguments are silently ignored.\n\nmergeAll([ obj1, obj2, ... ])\n------------------------------\n\nLike multi-argument `merge()` but operate on an array directly.\n\nhas(key)\n--------\n\nReturn whether the hash has a key, `key`.\n\nvaluesAt(keys)\n--------------\n\nReturn an Array with the values at the keys from `keys`.\n\ntap(cb)\n-------\n\nCall `cb` with the present raw hash.\nThis function is chainable.\n\nextract(keys)\n-------------\n\nFilter by including only those keys in `keys` in the resulting hash.\n\nexclude(keys)\n-------------\n\nFilter by excluding those keys in `keys` in the resulting hash.\n\nAttributes\n==========\n\nThese are attributes in the chaining interface and functions in the `Hash.xxx`\ninterface.\n\nkeys\n----\n\nReturn all the enumerable attribute keys in the hash.\n\nvalues\n------\n\nReturn all the enumerable attribute values in the hash.\n\ncompact\n-------\n\nFilter out values which are `=== undefined`.\n\nclone\n-----\n\nMake a deep copy of the hash.\n\ncopy\n----\n\nMake a shallow copy of the hash.\n\nlength\n------\n\nReturn the number of key/value pairs in the hash.\nNote: use `Hash.size()` for non-chain mode.\n\nsize\n----\n\nAlias for `length` since `Hash.length` is masked by `Function.prototype`.\n\nSee Also\n========\n\nSee also [creationix's pattern/hash](http://github.com/creationix/pattern),\nwhich does a similar thing except with hash inputs and array outputs.\n\nInstallation\n============\n\nTo install with [npm](http://github.com/isaacs/npm):\n \n npm install hashish\n\nTo run the tests with [expresso](http://github.com/visionmedia/expresso):\n\n expresso\n",
"readmeFilename": "README.markdown",
"bugs": {
"url": "https://github.com/substack/node-hashish/issues"
},
"_id": "hashish@0.0.4",
"dist": {
"shasum": "ef5af4bf63fee968e4dc040692c558a8fb4429f6"
},
"_from": "hashish@*",
"_resolved": "https://registry.npmjs.org/hashish/-/hashish-0.0.4.tgz"
}
var Hash = require('hashish');
var assert = require('assert');
exports.map = function () {
var ref = { a : 1, b : 2 };
var items = Hash(ref).map(function (v) { return v + 1 }).items;
var hash = Hash.map(ref, function (v) { return v + 1 });
assert.deepEqual(ref, { a : 1, b : 2 });
assert.deepEqual(items, { a : 2, b : 3 });
assert.deepEqual(hash, { a : 2, b : 3 });
};
exports['cloned map'] = function () {
var ref = { foo : [1,2], bar : [4,5] };
var hash = Hash(ref).clone.map(
function (v) { v.unshift(v[0] - 1); return v }
).items;
assert.deepEqual(ref.foo, [1,2]);
assert.deepEqual(ref.bar, [4,5]);
assert.deepEqual(hash.foo, [0,1,2]);
assert.deepEqual(hash.bar, [3,4,5]);
};
exports.forEach = function () {
var ref = { a : 5, b : 2, c : 7, 1337 : 'leet' };
var xs = [];
Hash(ref).forEach(function (x, i) {
xs.push([ i, x ]);
});
assert.eql(
xs.map(function (x) { return x[0] }).sort(),
[ '1337', 'a', 'b', 'c' ]
);
assert.eql(
xs.map(function (x) { return x[1] }).sort(),
[ 2, 5, 7, 'leet' ]
);
var ys = [];
Hash.forEach(ref, function (x, i) {
ys.push([ i, x ]);
});
assert.eql(xs.sort(), ys.sort());
};
exports.filter_items = function () {
var ref = { a : 5, b : 2, c : 7, 1337 : 'leet' };
var items = Hash(ref).filter(function (v, k) {
return v > 5 || k > 5
}).items;
var hash = Hash.filter(ref, function (v, k) { return v > 5 || k > 5 });
assert.deepEqual(items, { 1337 : 'leet', c : 7 });
assert.deepEqual(hash, { 1337 : 'leet', c : 7 });
assert.deepEqual(ref, { a : 5, b : 2, c : 7, 1337 : 'leet' });
assert.equal(Hash(ref).length, 4);
};
exports.detect = function () {
var h = { a : 5, b : 6, c : 7, d : 8 };
var hh = Hash(h);
var gt6hh = hh.detect(function (x) { return x > 6 });
assert.ok(gt6hh == 7 || gt6hh == 8);
var gt6h = Hash.detect(h, function (x) { return x > 6 });
assert.ok(gt6h == 7 || gt6h == 8);
assert.equal(hh.detect(function (x) { return x > 100 }), undefined);
};
exports.reduce = function () {
var ref = { foo : [1,2], bar : [4,5] };
var sum1 = Hash(ref).reduce(function (acc, v) {
return acc + v.length
}, 0);
assert.equal(sum1, 4);
var sum2 = Hash.reduce(ref, function (acc, v) {
return acc + v.length
}, 0);
assert.equal(sum2, 4);
};
exports.some = function () {
var h = { a : 5, b : 6, c : 7, d : 8 };
var hh = Hash(h);
assert.ok(Hash.some(h, function (x) { return x > 7 }));
assert.ok(Hash.some(h, function (x) { return x < 6 }));
assert.ok(!Hash.some(h, function (x) { return x > 10 }));
assert.ok(!Hash.some(h, function (x) { return x < 0 }));
assert.ok(hh.some(function (x) { return x > 7 }));
assert.ok(hh.some(function (x) { return x < 6 }));
assert.ok(!hh.some(function (x) { return x > 10 }));
assert.ok(!hh.some(function (x) { return x < 0 }));
};
exports.update = function () {
var ref = { a : 1, b : 2 };
var items = Hash(ref).clone.update({ c : 3, a : 0 }).items;
assert.deepEqual(ref, { a : 1, b : 2 });
assert.deepEqual(items, { a : 0, b : 2, c : 3 });
var hash = Hash.update(ref, { c : 3, a : 0 });
assert.deepEqual(ref, hash);
assert.deepEqual(hash, { a : 0, b : 2, c : 3 });
var ref2 = {a: 1};
var hash2 = Hash.update(ref2, { b: 2, c: 3 }, undefined, { d: 4 });
assert.deepEqual(ref2, { a: 1, b: 2, c: 3, d: 4 });
};
exports.merge = function () {
var ref = { a : 1, b : 2 };
var items = Hash(ref).merge({ b : 3, c : 3.14 }).items;
var hash = Hash.merge(ref, { b : 3, c : 3.14 });
assert.deepEqual(ref, { a : 1, b : 2 });
assert.deepEqual(items, { a : 1, b : 3, c : 3.14 });
assert.deepEqual(hash, { a : 1, b : 3, c : 3.14 });
var ref2 = { a : 1 };
var hash2 = Hash.merge(ref, { b: 2, c: 3 }, undefined, { d: 4 });
assert.deepEqual(hash2, { a: 1, b: 2, c: 3, d: 4 });
};
exports.has = function () {
var h = { a : 4, b : 5 };
var hh = Hash(h);
assert.ok(hh.has('a'));
assert.equal(hh.has('c'), false);
assert.ok(hh.has(['a','b']));
assert.equal(hh.has(['a','b','c']), false);
assert.ok(Hash.has(h, 'a'));
assert.equal(Hash.has(h, 'c'), false);
assert.ok(Hash.has(h, ['a','b']));
assert.equal(Hash.has(h, ['a','b','c']), false);
};
exports.valuesAt = function () {
var h = { a : 4, b : 5, c : 6 };
assert.equal(Hash(h).valuesAt('a'), 4);
assert.equal(Hash(h).valuesAt(['a'])[0], 4);
assert.deepEqual(Hash(h).valuesAt(['a','b']), [4,5]);
assert.equal(Hash.valuesAt(h, 'a'), 4);
assert.deepEqual(Hash.valuesAt(h, ['a']), [4]);
assert.deepEqual(Hash.valuesAt(h, ['a','b']), [4,5]);
};
exports.tap = function () {
var h = { a : 4, b : 5, c : 6 };
var hh = Hash(h);
hh.tap(function (x) {
assert.ok(this === hh)
assert.eql(x, h);
});
Hash.tap(h, function (x) {
assert.eql(
Object.keys(this).sort(),
Object.keys(hh).sort()
);
assert.eql(x, h);
});
};
exports.extract = function () {
var hash = Hash({ a : 1, b : 2, c : 3 }).clone;
var extracted = hash.extract(['a','b']);
assert.equal(extracted.length, 2);
assert.deepEqual(extracted.items, { a : 1, b : 2 });
};
exports.exclude = function () {
var hash = Hash({ a : 1, b : 2, c : 3 }).clone;
var extracted = hash.exclude(['a','b']);
assert.equal(extracted.length, 1);
assert.deepEqual(extracted.items, { c : 3 });
};
exports.concat = function () {
var ref1 = { a : 1, b : 2 };
var ref2 = { foo : 100, bar : 200 };
var ref3 = { b : 3, c : 4, bar : 300 };
assert.deepEqual(
Hash.concat([ ref1, ref2 ]),
{ a : 1, b : 2, foo : 100, bar : 200 }
);
assert.deepEqual(
Hash.concat([ ref1, ref2, ref3 ]),
{ a : 1, b : 3, c : 4, foo : 100, bar : 300 }
);
};
exports.zip = function () {
var xs = ['a','b','c'];
var ys = [1,2,3,4];
var h = Hash(xs,ys);
assert.equal(h.length, 3);
assert.deepEqual(h.items, { a : 1, b : 2, c : 3 });
var zipped = Hash.zip(xs,ys);
assert.deepEqual(zipped, { a : 1, b : 2, c : 3 });
};
exports.length = function () {
assert.equal(Hash({ a : 1, b : [2,3], c : 4 }).length, 3);
assert.equal(Hash({ a : 1, b : [2,3], c : 4 }).size, 3);
assert.equal(Hash.size({ a : 1, b : [2,3], c : 4 }), 3);
};
exports.compact = function () {
var hash = {
a : 1,
b : undefined,
c : false,
d : 4,
e : [ undefined, 4 ],
f : null
};
var compacted = Hash(hash).compact;
assert.deepEqual(
{
a : 1,
b : undefined,
c : false,
d : 4,
e : [ undefined, 4 ],
f : null
},
hash, 'compact modified the hash'
);
assert.deepEqual(
compacted.items,
{
a : 1,
c : false,
d : 4,
e : [ undefined, 4 ],
f : null
}
);
var h = Hash.compact(hash);
assert.deepEqual(h, compacted.items);
};
var Hash = require('hashish');
var assert = require('assert');
var vm = require('vm');
var fs = require('fs');
var src = fs.readFileSync(__dirname + '/../index.js', 'utf8');
exports.defineGetter = function () {
var context = {
module : { exports : {} },
Object : {
keys : Object.keys,
defineProperty : undefined,
},
require : require,
};
context.exports = context.module.exports;
vm.runInNewContext('(function () {' + src + '})()', context);
var Hash_ = context.module.exports;
var times = 0;
Hash_.__proto__.__proto__.__defineGetter__ = function () {
times ++;
return Object.__defineGetter__.apply(this, arguments);
};
assert.equal(vm.runInNewContext('Object.defineProperty', context), null);
assert.deepEqual(
Hash_({ a : 1, b : 2, c : 3 }).values,
[ 1, 2, 3 ]
);
assert.ok(times > 5);
};
exports.defineProperty = function () {
var times = 0;
var context = {
module : { exports : {} },
Object : {
keys : Object.keys,
defineProperty : function (prop) {
times ++;
if (prop.get) throw new TypeError('engine does not support')
assert.fail('should have asserted by now');
},
},
require : require
};
context.exports = context.module.exports;
vm.runInNewContext('(function () {' + src + '})()', context);
var Hash_ = context.module.exports;
Hash_.__proto__.__proto__.__defineGetter__ = function () {
assert.fail('getter called when a perfectly good'
+ ' defineProperty was available'
);
};
assert.deepEqual(
Hash_({ a : 1, b : 2, c : 3 }).values,
[ 1, 2, 3 ]
);
assert.equal(times, 1);
};
{
"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"
},
"dependencies": {
"hashish": "*"
},
"engines": {
"node": "~0.10.0"
}
}
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
{
"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"
}
}
{
"name": "myapp",
"version": "0.0.1",
"engines": {
"node": "0.6.11",
"npm": "1.1.5"
}
}
{
"name": "myapp",
"version": "0.0.1"
}
{
"name": "myapp",
"version": "0.0.1",
"engines": {
"node": "0.6.11",
"npm": "1.1.9"
}
}
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.8.x"
}
}
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"
},
"scripts" : {
"preinstall" : "echo preinstall hook message"
}
}
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"
},
"dependencies": {
"hashish": "*"
},
"engines": {
"node": "~0.10.0"
}
}
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.11.0"
}
}
File added
This diff is collapsed.
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"))
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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