summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Lehnardt <jan@apache.org>2013-09-21 15:42:47 +0200
committerJan Lehnardt <jan@apache.org>2013-10-03 17:21:29 +0200
commitdc375701d02db659b6364f1d2aab4d6631c1e285 (patch)
tree852c6bd61d842d703dbc92a782e07c0a1b147c8e
parent3e19db216ce44ae1c6729ed68d1316b12b5d1875 (diff)
downloadcouchdb-dc375701d02db659b6364f1d2aab4d6631c1e285.tar.gz
add couchjs-node from Jason
-rw-r--r--src/couchjs-node/LICENSE202
-rw-r--r--src/couchjs-node/README.md54
-rw-r--r--src/couchjs-node/checkout.sh36
-rwxr-xr-xsrc/couchjs-node/cli.js85
-rw-r--r--src/couchjs-node/console.js65
-rwxr-xr-xsrc/couchjs-node/couchdb.js29
-rw-r--r--src/couchjs-node/couchjs.js116
-rwxr-xr-xsrc/couchjs-node/extra.js346
-rwxr-xr-xsrc/couchjs-node/inspector.js92
-rw-r--r--src/couchjs-node/package.json30
-rw-r--r--src/couchjs-node/stream.js107
-rw-r--r--src/couchjs-node/test/experiment.js105
-rw-r--r--src/couchjs-node/xml.js23
13 files changed, 1290 insertions, 0 deletions
diff --git a/src/couchjs-node/LICENSE b/src/couchjs-node/LICENSE
new file mode 100644
index 000000000..d64569567
--- /dev/null
+++ b/src/couchjs-node/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/src/couchjs-node/README.md b/src/couchjs-node/README.md
new file mode 100644
index 000000000..c686e8089
--- /dev/null
+++ b/src/couchjs-node/README.md
@@ -0,0 +1,54 @@
+# CouchJS
+
+## Drop-in replacement JavaScript engine for Apache CouchDB
+
+CouchJS is a command-line Node.js program. It is 100% compatible with Apache CouchDB's built-in JavaScript system.
+
+By using CouchJS, you will get 100% CouchDB compatibility (the test suite completely passes) but your JavaScript environment is V8, or Node.js.
+
+CouchJS is available as an npm module.
+
+ $ npm install -g couchjs
+
+## Usage
+
+Install CouchDB. Install this package with npm. Confirm your `couchjs` install location.
+
+ $ which couchjs # Note, your path will be different from mine.
+ /home/jhs/node/bin/couchjs
+
+Look at the CouchDB config for the JavaScript query server.
+
+ $ curl http://localhost:5984/_config/query_servers/javascript
+ "/home/jhs/couchdb/bin/couchjs /home/jhs/couchdb/share/couchdb/server/main.js"
+
+Change that to this `couchjs`. **Leave the second argument the same.**
+
+ $ curl -X PUT http://localhost:5984/_config/query_servers/javascript \
+ -H content-type:application/json \
+ -d "\"`which couchjs` /home/jhs/couchdb/share/couchdb/server/main.js\""
+
+Done!
+
+## Idea
+
+JavaScript is decoupled from the CouchDB core. To do JavaScript stuff, CouchDB runs a normal Unix subprocess, `couchjs`. This subprocess is just a read-eval-print loop on standard i/o. CouchDB passes `couchjs` a file name, and *that file* contains the view server implementation.
+
+This tool duplicates the "REPL" look and feel of `couchjs` and supports the exact same view server implementation.
+
+## Security
+
+I have no idea. I would not trust it for production use.
+
+## Log
+
+If you create a file, `/tmp/couchjs.log` then *couchjs* will output debugging messages there.
+
+## License
+
+Apache 2.0
+
+See the [Apache 2.0 license](named/blob/master/LICENSE).
+
+[tap]: https://github.com/isaacs/node-tap
+[def]: https://github.com/iriscouch/defaultable
diff --git a/src/couchjs-node/checkout.sh b/src/couchjs-node/checkout.sh
new file mode 100644
index 000000000..0076b5161
--- /dev/null
+++ b/src/couchjs-node/checkout.sh
@@ -0,0 +1,36 @@
+# Copyright 2011 Iris Couch
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+repo="$1"
+commit="$2"
+
+if [ -z "$repo" -o ! -d "$repo" ]; then
+ echo "Not a valid repo: $repo" >&2
+ exit 1
+fi
+if [ -z "$commit" ]; then
+ echo "Not a valid commit: $commit" >&2
+ exit 1
+fi
+
+echo "Clone $repo:$commit to $(pwd)"
+
+set -e
+
+git clone "$repo" .
+git checkout "$commit"
+
+if [ -z "$skip_npm_install" ]; then
+ npm install --production
+fi
diff --git a/src/couchjs-node/cli.js b/src/couchjs-node/cli.js
new file mode 100755
index 000000000..14a6d97be
--- /dev/null
+++ b/src/couchjs-node/cli.js
@@ -0,0 +1,85 @@
+#!/usr/bin/env node
+//
+// couchjs replacement
+//
+// Copyright 2011 Iris Couch
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+var fs = require('fs')
+var util = require('util')
+var Fiber = require('fibers')
+var optimist = require('optimist')
+var child_process = require('child_process')
+
+var couchjs = require('./couchjs')
+var package_json = require('./package.json')
+var couchdb_extra = require('./extra')
+var LineStream = require('./stream')
+var inspector = require('./inspector')
+var log = require('./console').log
+
+
+var opts = optimist.boolean(['h', 'V', 'H'])
+ .describe({ 'h': 'display a short help message and exit'
+ , 'V': 'display version information and exit'
+ , 'H': 'enable couchjs cURL bindings (not implemented)'
+ })
+ .boolean('extra')
+ .describe('extra', 'Extra features for CouchDB, for os_daemons')
+ .usage('$0 <path to main.js>')
+
+
+function main() {
+ if(opts.argv.extra)
+ return couchdb_extra()
+
+ var main_js = opts.argv._[0]
+ if(!main_js)
+ return console.error(opts.help())
+
+ log('couchjs/%s %s: %s', package_json.version, process.pid, main_js)
+ if(process.env.COUCHJS_DEBUG_PORT)
+ inspector(+process.env.COUCHJS_DEBUG_PORT)
+
+ fs.readFile(main_js, 'utf8', function(er, body) {
+ if(er)
+ throw er
+
+ var stdin = new LineStream.v2
+ stdin.on('readable', function() {
+ var buf = stdin.read()
+ if(buf)
+ couchjs.stdin(buf)
+ })
+ stdin.on('end', function() {
+ log('Terminate; connection to parent closed')
+ process.exit(0)
+ })
+
+ process.stdin.setEncoding('utf8')
+ process.stdin.pipe(stdin)
+
+ var main_func = Function(['print', 'readline', 'evalcx', 'gc', 'quit'], body)
+
+ log('Call main')
+ Fiber(function() { main_func(couchjs.print, couchjs.readline, couchjs.evalcx, couchjs.gc) }).run()
+ })
+
+ process.on('uncaughtException', function(er) {
+ log('Error:\n%s', er.stack)
+ })
+}
+
+if(require.main === module)
+ main()
diff --git a/src/couchjs-node/console.js b/src/couchjs-node/console.js
new file mode 100644
index 000000000..2a0a17a1c
--- /dev/null
+++ b/src/couchjs-node/console.js
@@ -0,0 +1,65 @@
+// Copyright 2011 Iris Couch
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+var fs = require('fs')
+var util = require('util')
+
+
+module.exports = {}
+module.exports.log = noop
+module.exports.debug = noop
+module.exports.info = noop
+module.exports.warn = noop
+module.exports.error = noop
+
+var LOG_PATH = '/tmp/couchjs.log'
+ , stat = null
+ , LOG = null
+
+try {
+ stat = fs.statSync(LOG_PATH)
+} catch(er) {}
+
+if(stat) {
+ LOG = fs.createWriteStream(LOG_PATH, {'flags':'a'})
+
+ module.exports.log = log
+ module.exports.debug = log
+ module.exports.info = log
+ module.exports.warn = log
+ module.exports.error = log
+
+ process.on('exit', function() {
+ module.exports.log('Exit %d', process.pid)
+ })
+
+ process.on('uncaughtException', on_err)
+}
+
+function log() {
+ var str = util.format.apply(this, arguments)
+ LOG.write(str + '\n')
+}
+
+function on_err(er) {
+ module.exports.error('Uncaught error:\n%s', er.stack || er.message || JSON.stringify(er))
+
+ if(er.stack)
+ er = ['fatal', 'unknown_error', er.stack]
+
+ process.stdout.write(JSON.stringify(er) + '\n')
+ process.exit(1)
+}
+
+function noop() {}
diff --git a/src/couchjs-node/couchdb.js b/src/couchjs-node/couchdb.js
new file mode 100755
index 000000000..d297112c3
--- /dev/null
+++ b/src/couchjs-node/couchdb.js
@@ -0,0 +1,29 @@
+// Copyright 2011 Iris Couch
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+module.exports = handler
+
+var http = require('http')
+
+function handler(req, res) {
+ res.writeHead(200)
+ res.end('Hello: ' + req.url + '\n')
+}
+
+if(require.main === module) {
+ var http = require('http')
+ var server = http.createServer(handler)
+ server.listen(3000)
+ console.log('Listening on :3000')
+}
diff --git a/src/couchjs-node/couchjs.js b/src/couchjs-node/couchjs.js
new file mode 100644
index 000000000..d95341dab
--- /dev/null
+++ b/src/couchjs-node/couchjs.js
@@ -0,0 +1,116 @@
+// Copyright 2011 Iris Couch
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+module.exports = { 'print' : print
+ , 'readline': readline
+ , 'stdin' : stdin
+ , 'evalcx' : evalcx
+ , 'quit' : quit
+ , 'gc' : gc
+ }
+
+
+var vm = require('vm')
+var Fiber = require('fibers')
+
+var XML = require('./xml')
+var log = require('./console').log
+
+var INPUT = {'queue':[], 'waiting':null}
+
+Error.prototype.toSource = Error.prototype.toSource || toSource
+Error.prototype.toString = Error.prototype.toString || toSource
+Function.prototype.toSource = Function.prototype.toSource || toSource
+Function.prototype.toString = Function.prototype.toString || toSource
+
+
+function print(line) {
+ log('STDOUT %s: %s', process.pid, line)
+ process.stdout.write(line + '\n')
+
+ try {
+ line = JSON.parse(line)
+ } catch(er) { return }
+
+ if(line[0] == 'log')
+ log('LOG: %s', line[1])
+}
+
+function stdin(line) {
+ log('STDIN %s: %s', process.pid, line.trim())
+ if(INPUT.waiting)
+ INPUT.waiting.run(line)
+ else
+ INPUT.queue.push(line)
+}
+
+function readline() {
+ var line = INPUT.queue.shift()
+ if(line)
+ return line
+
+ INPUT.waiting = Fiber.current
+ line = Fiber.yield()
+ INPUT.waiting = null
+
+ return line
+}
+
+
+function evalcx(source, sandbox) {
+ sandbox = sandbox || {}
+ //log('evalcx in %j: %j', Object.keys(sandbox), source)
+
+ if(source == '')
+ return sandbox
+
+ // source might be "function(doc) { emit(doc._id, 1) }"
+ source = source.replace(/;+$/, '')
+
+ sandbox.XML = sandbox.XML || XML
+ source = '(' + source + ')'
+
+ try {
+ var id = Math.floor(Math.random() * 1000*1000)
+ var filename = '_couchdb:' + id + '.js'
+ var script = vm.createScript(source, filename)
+ var func = script.runInNewContext(sandbox)
+ } catch (er) {
+ log('Error making code: %s', er.stack)
+ return sandbox
+ }
+
+ return func
+}
+
+function quit(code) {
+ code = code || 1
+ if(code < 0)
+ code = -code
+
+ process.exit(code)
+}
+
+function gc() { }
+
+
+function toSource() {
+ if(typeof this == 'function')
+ return '' + this
+
+ if(this instanceof Error)
+ return this.stack
+
+ return util.inspect(this)
+}
diff --git a/src/couchjs-node/extra.js b/src/couchjs-node/extra.js
new file mode 100755
index 000000000..2df0168d6
--- /dev/null
+++ b/src/couchjs-node/extra.js
@@ -0,0 +1,346 @@
+#!/usr/bin/env node
+// Copyright 2011 Iris Couch
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+module.exports = main
+
+var os = require('os')
+var fs = require('fs')
+var URL = require('url')
+var util = require('util')
+var http = require('http')
+var async = require('async')
+var mkdirp = require('mkdirp')
+var request = require('request')
+var optimist = require('optimist')
+var pushover = require('pushover')
+var child_process = require('child_process')
+
+var console = require('./console')
+var VER = require('./package.json').version
+
+var couch = { 'log': mk_couch_log('info')
+ , 'warn' : mk_couch_log('warn')
+ , 'error': mk_couch_log('error')
+ , 'debug': mk_couch_log('debug')
+ }
+
+
+var opts = optimist.usage('$0')
+
+var COUCH = null
+var GIT_DIR = null
+var COUCH_PASSWORD = null
+var GIT_PORT = null
+var APPLICATION = null
+
+function main() {
+ if(opts.argv.help)
+ return console.log(opts.help())
+
+ console.log('Extra CouchDB daemon: %s', process.pid)
+ couch.debug('CouchDB daemon %s: %s', VER, process.pid)
+
+ var env = {}
+ for (var k in process.env) {
+ var match = k.match(/^_couchdb_app_(.*)$/)
+ if(match)
+ env[match[1]] = process.env[k]
+ }
+
+ for (k in env)
+ couch.debug(' %s = %s', k, env[k])
+
+ if(env.port && env.password && env.couch && env.dir)
+ return git(env)
+
+ setInterval(function() {
+ console.log('Still here')
+ couch.log('Still in couch')
+ }, 60000)
+}
+
+function git(env) {
+ GIT_PORT = +env.port
+ COUCH = env.couch
+ COUCH_DIR = util.format('%s/couchjs-%s', env.dir, VER)
+ COUCH_PASSWORD = env.password
+
+ //var couch_url = util.format('http://_nodejs:%s@127.0.0.1:%d', password, couch_port)
+ //couch.log('couch url %j', couch_url)
+
+ auth('_nodejs', COUCH_PASSWORD, function(er, userCtx) {
+ if(er)
+ throw er
+
+ var roles = userCtx.roles || []
+ if(userCtx.name != '_nodejs' || !~roles.indexOf('_admin'))
+ throw new Error('Not admin: ' + JSON.stringify(res.body.userCtx))
+
+ var repos = pushover(COUCH_DIR)
+ repos.on('push', function(push) {
+ couch.log('Push %s/%s: %s', push.repo, push.commit, push.branch)
+ push.accept()
+ //couch.log('Response: %j', Object.keys(push.response))
+ push.response.on('finish', function() {
+ //couch.log('Finished!')
+ publish(push)
+ })
+ })
+
+ repos.on('fetch', function(fetch) {
+ couch.log('fetch %j', fetch.commit)
+ fetch.accept()
+ })
+
+ var server = http.createServer(function(req, res) {
+ if(! req.url.match(/^\/_nodejs\/_git(\/|$)/))
+ return handle_http(req, res)
+
+ req.pause()
+ auth_req(req, function(er, userCtx) {
+ if(er && er.statusCode) {
+ res.writeHead(er.statusCode, er.headers)
+ return res.end(er.body)
+ }
+
+ if(er) {
+ couch.log('Bad req %s: %s', req.url, er.message)
+ return res.end()
+ }
+
+ var roles = userCtx.roles || []
+ if(!~ roles.indexOf('_admin')) {
+ couch.log('Not admin: %s, %j', req.url, userCtx)
+ res.writeHead(401, 'Unauthorized', {'content-type':'application/json'})
+ return res.end('{"error":"not_authorized"}\n')
+ }
+
+ //couch.log('Handle Git: %j', req.url)
+ repos.handle(req, res)
+ req.resume()
+ })
+ })
+
+ server.listen(GIT_PORT)
+ })
+}
+
+function handle_http(req, res) {
+ if(! APPLICATION) {
+ var headers = { 'content-type': 'application/json'
+ , 'server': 'NodeJS-CouchDB/'+VER
+ }
+ res.writeHead(200, 'OK', headers)
+ var body = {'ok':true}
+ return res.end(JSON.stringify(body) + '\n')
+ }
+
+ // Clean up the vhost changes.
+ var vhost_path = req.headers['x-couchdb-vhost-path']
+ if(vhost_path) {
+ req.url = vhost_path
+ delete req.headers['x-couchdb-vhost-path']
+ }
+
+ APPLICATION(req, res)
+}
+
+function auth(user, pass, callback) {
+ if(!COUCH)
+ return process.nextTick(function() { callback(new Error('No _couchdb_port')) })
+
+ var url = COUCH + '/_session'
+ if(user || pass) {
+ url = URL.parse(url)
+ url.auth = util.format('%s:%s', user || '', pass || '')
+ url = URL.format(url)
+ }
+
+ //couch.log('auth: %j', url)
+ request({'url':url, 'json':true}, function(er, res) {
+ //couch.log('auth result: %j', res.body)
+ if(er)
+ return callback(er)
+
+ if(res.statusCode != 200) {
+ er = new Error('Bad status '+res.statusCode+' for auth: ' + res.body)
+ er.statusCode = res.statusCode
+ er.body = JSON.stringify(res.body) + '\n'
+ er.headers = res.headers
+ return callback(er)
+ }
+
+ return callback(null, res.body.userCtx)
+ })
+}
+
+function auth_req(req, callback) {
+ var headers = req.headers || {}
+ var auth_str = req.headers.authorization || ''
+
+ var match = auth_str.match(/^Basic (.+)$/)
+ if(!match)
+ return auth(null, null, callback)
+
+ try {
+ auth_str = new Buffer(match[1], 'base64').toString()
+ match = auth_str.match(/^([^:]+):(.+)$/)
+ } catch (er) {
+ return callback(er)
+ }
+
+ if(!match)
+ return callback(new Error('Bad auth string: ' + auth_str))
+
+ auth(match[1], match[2], callback)
+}
+
+
+function publish(push) {
+ var script = __dirname + '/checkout.sh'
+ var repo = COUCH_DIR + '/' + push.repo
+
+ var id = Math.floor(Math.random() * 1000 * 1000)
+ var work = util.format('%s/%s/%s', COUCH_DIR, push.commit, id)
+
+ mkdirp(work, function(er) {
+ if(er) {
+ couch.error('Failed to make working dir: %s', work)
+ throw er
+ }
+
+ checkout()
+ })
+
+ function checkout() {
+ var args = [script, repo, push.commit]
+ var opts = { 'cwd':work, 'stdio':'pipe' }
+
+ var child = child_process.spawn('bash', args, opts)
+
+ var output = []
+
+ child.stdout.on('data', function(x) {
+ var msg = util.format('OUT %s', x.toString().trim())
+ couch.debug(msg)
+ output.push(msg)
+ })
+ child.stderr.on('data', function(x) {
+ var msg = util.format('ERR %s', x.toString().trim())
+ couch.debug(msg)
+ output.push(msg)
+ })
+
+ child.on('exit', function(code) {
+ if(code !== 0) {
+ couch.error('Bad checkout: %d', code)
+ output.forEach(function(line) {
+ couch.error(line)
+ })
+
+ throw new Error('Bad checkout')
+ }
+
+ couch.log('Checked out push: %s', work)
+ fs.readFile(work+'/package.json', 'utf8', function(er, body) {
+ if(er)
+ throw er
+
+ body = JSON.parse(body)
+ if(!body.couchdb)
+ return couch.warn('No "couchdb" value in pushed package.json')
+
+ run_app(work, body)
+ })
+ })
+ }
+}
+
+
+function run_app(work_dir, pkg) {
+ var vhosts = []
+ , main = null
+
+ if(typeof pkg.couchdb == 'string')
+ main = pkg.couchdb
+ else {
+ vhosts = pkg.couchdb.vhosts || []
+ main = pkg.couchdb.main
+ }
+
+ couch.log('Run app %s: %j', main, vhosts)
+
+ var mod_path = util.format('%s/%s', work_dir, main)
+ try {
+ var ok = require.resolve(mod_path)
+ } catch (er) {
+ return couch.error('Bad module path: %s', mod_path)
+ }
+
+ couch_mod = require(mod_path)
+ APPLICATION = couch_mod
+ couch.log('Installed CouchDB application')
+
+ return async.forEach(vhosts, set_vhost, vhosts_set)
+
+ function set_vhost(vhost, to_async) {
+ var couch_url = URL.parse(COUCH)
+ couch_url.auth = '_nodejs:' + COUCH_PASSWORD
+ couch_url = URL.format(couch_url)
+ couch.log('couch_url: %j', couch_url)
+
+ var url = couch_url + '_config/vhosts/' + vhost
+ var body = '/_nodejs'
+ request.put({'url':url, 'json':body}, function(er, res) {
+ if(er)
+ return to_async(er)
+ if(res.statusCode != 200)
+ return to_async(new Error('Bad response '+res.statusCode+' to vhost: ' + vhost))
+
+ couch.log('Set vhost: %s', vhost)
+ return to_async()
+ })
+ }
+
+ function vhosts_set(er) {
+ if(er)
+ throw er
+
+ couch.log('Set %d vhosts for CouchDB application', vhosts.length)
+ }
+}
+
+
+//
+// Utilities
+//
+
+function mk_couch_log(level) {
+ if(level == 'warn')
+ level = 'error'
+
+ return logger
+
+ function logger() {
+ var str = util.format.apply(util, arguments)
+ var msg = ['log', str, {'level':level}]
+ msg = JSON.stringify(msg)
+ process.stdout.write(msg + '\n')
+ }
+}
+
+
+if(require.main === module)
+ main()
diff --git a/src/couchjs-node/inspector.js b/src/couchjs-node/inspector.js
new file mode 100755
index 000000000..efa98566f
--- /dev/null
+++ b/src/couchjs-node/inspector.js
@@ -0,0 +1,92 @@
+#!/usr/bin/env node
+//
+// couchjs replacement
+//
+// Copyright 2011 Iris Couch
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+module.exports = start
+
+if(require.main === module)
+ main()
+
+
+var fs = require('fs')
+var util = require('util')
+var child_process = require('child_process')
+
+var log = require('./console').log
+
+function start(debugPort) {
+ if(!debugPort || typeof debugPort != 'number')
+ throw new Error('Need a listen debugPort')
+ var webPort = debugPort + 1
+
+ var cmd = __filename
+ var args = [debugPort, webPort]
+ var opts =
+ { 'cwd': __dirname
+ , 'stdio': 'pipe'
+ , 'detached': false
+ }
+
+ log('Start inspector: %s %j %j', cmd, args, opts)
+ var inspector = child_process.spawn(cmd, args, opts)
+ watch_inspector(inspector)
+
+ log('Enable remote debug pid=%d port=%d', process.pid, debugPort)
+ process.debugPort = debugPort
+ process.kill(process.pid, 'SIGUSR1')
+}
+
+function watch_inspector(child) {
+ child.stderr.on('data', function(body) {
+ log('Inspector STDERR: %s', body)
+ })
+ child.stdout.on('data', function(body) {
+ log('Inspector STDOUT: %s', body)
+ })
+
+ child.on('exit', function(code, signal) {
+ log('Inspector exited %d signal=%j', code, signal)
+ process.exit(code)
+ })
+
+ process.on('exit', function() {
+ log('Kill inspector upon exit: %d', child.pid)
+ process.kill(child.pid, 'SIGTERM')
+ })
+}
+
+
+function main() {
+ var debugPort = +process.argv[2]
+ var webPort = +process.argv[3]
+
+ if(!debugPort || !webPort)
+ throw new Error('Bad arguments: need debugPort and webPort')
+
+ console.log('Start inspector debugPort=%j webPort=%j', debugPort, webPort)
+ var DebugServer = require('node-inspector/lib/debug-server')
+ var server = new DebugServer
+ server.on('close', function() {
+ console.log('Server closed')
+ process.exit(0)
+ })
+
+ server.start({'webPort':webPort, 'debugPort':debugPort})
+ process.on('uncaughtException', function(er) {
+ console.log('Error:\n%s', er.stack)
+ })
+}
diff --git a/src/couchjs-node/package.json b/src/couchjs-node/package.json
new file mode 100644
index 000000000..665888cae
--- /dev/null
+++ b/src/couchjs-node/package.json
@@ -0,0 +1,30 @@
+{ "name": "couchjs"
+, "description": "Drop-in replacement for CouchDB JavaScript view server"
+, "keywords": [ "couchdb", "couchjs" ]
+, "version": "0.3.2"
+, "author": "Jason Smith <jhs@iriscouch.com> (http://www.iriscouch.com)"
+, "repository": { "type":"git", "url":"https://github.com/iriscouch/couchjs" }
+
+, "engines": { "node": ">= 0.8" }
+, "main": "./couchjs.js"
+, "bin": {"couchjs-node":"./cli.js", "couchjs-extra":"./extra.js"}
+
+, "couchdb": { "main":"./couchdb.js"
+ , "vhosts": ["127.0.0.1.xip.io"]
+ }
+
+, "bundledDependencies": ["node-inspector"]
+
+, "dependencies": { "optimist": "~0.3.4"
+ , "async" : "~0.2.5"
+ , "mkdirp" : "~0.3.4"
+ , "fibers" : "~1.0.0"
+ , "request" : "~2.9.203"
+ , "pushover": "~1.2.1"
+ , "defaultable": "~0.7.2"
+ , "node-inspector": "git://github.com/iriscouch/node-inspector#couchjs"
+ }
+
+, "devDependencies": { "tap": "~0.2.5"
+ }
+}
diff --git a/src/couchjs-node/stream.js b/src/couchjs-node/stream.js
new file mode 100644
index 000000000..90deda17e
--- /dev/null
+++ b/src/couchjs-node/stream.js
@@ -0,0 +1,107 @@
+// Text line stream
+//
+// Copyright 2011 Iris Couch
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+module.exports = LineStream
+module.exports.v2 = LineStream2
+
+var stream = require('stream')
+var util = require('util')
+
+
+util.inherits(LineStream2, stream.Transform)
+function LineStream2 () {
+ if(! (this instanceof LineStream2))
+ return new LineStream2
+
+ stream.Transform.call(this)
+ this.setEncoding('utf8')
+}
+
+LineStream2.prototype._transform = function(message, encoding, done) {
+ var self = this
+
+ message = message.toString(encoding)
+ var lines = message.split(/\n/)
+
+ // If the data ends in "\n" this will be ""; otherwise the final partial line.
+ var remainder = lines.pop()
+ if(remainder)
+ this.unshift(remainder)
+
+ lines.forEach(function(line) {
+ self.push(line)
+ })
+
+ done()
+}
+
+util.inherits(LineStream, stream)
+function LineStream () {
+ var self = this
+ stream.call(self)
+
+ self.readable = true
+ self.writable = true
+
+ self.buffer = ''
+ self.downstream = null
+
+ self.on('pipe', function(upstream) {
+ upstream.on('end', function(data, encoding) {
+ self.emit('end', data, encoding)
+ })
+ })
+}
+
+
+LineStream.prototype.write = function(data, encoding) {
+ var self = this
+
+ data = data || ''
+ if(typeof data != 'string')
+ return self.error(new Error('Data was not a string: ' + util.inspect(data)))
+
+ self.buffer += data
+ var lines = self.buffer.split(/\n/)
+ self.buffer = lines.pop() // If the data ended in "\n" this will be ""; otherwise the final partial line.
+
+ lines.forEach(function(line) {
+ self.emit('data', line)
+ })
+}
+
+
+LineStream.prototype.end = function(data, encoding) {
+ var self = this
+
+ self.is_ending = true
+ self.writable = false
+
+ // Always call write, even with no data, so it can fire the "end" event.
+ self.write(data)
+}
+
+
+LineStream.prototype.error = function(er) {
+ var self = this
+
+ self.readable = false
+ self.writable = false
+ self.emit('error', er)
+
+ // The write() method sometimes returns this value, so if there was an error, make write() return false.
+ return false
+}
diff --git a/src/couchjs-node/test/experiment.js b/src/couchjs-node/test/experiment.js
new file mode 100644
index 000000000..54a5ab4ff
--- /dev/null
+++ b/src/couchjs-node/test/experiment.js
@@ -0,0 +1,105 @@
+var vm = require('vm')
+var util = require('util')
+
+var STATE = 'wait'
+ , v = 'vm'
+
+function main() {
+ process.debugPort = 5859
+ process.kill(process.pid, 'SIGUSR1')
+
+ setTimeout(function() { stuff(0) }, 1000)
+}
+
+function stuff(count) {
+ console.log('Doing stuff: %d', count)
+ //debugger
+ STATE = 'vm'
+ console.log('More stuff: %d', count)
+ if(STATE == 'done')
+ console.log('Done')
+ else if(STATE == 'code')
+ setTimeout(code, 1000)
+ else if(STATE == 'eval')
+ test_eval()
+ else if(STATE == 'vm')
+ test_vm()
+ else if(STATE == 'wait')
+ setTimeout(function() { stuff(count+1) }, 1000)
+ else
+ throw new Error('Unknown state: ' + STATE)
+}
+
+function code() {
+ var code =
+ [ 'var foo = "in the code"'
+ , 'console.log("This is some code")'
+ , 'debugger'
+ , 'console.log("foo = " + foo)'
+ ].join('\n')
+
+ var runner = Function([], code)
+ console.log('Run runner in 1s')
+ setTimeout(run_runner, 1000)
+
+ function run_runner() {
+ console.log('About to run runner')
+ debugger
+ runner()
+ console.log('Runner done')
+ }
+}
+
+function test_eval() {
+ console.log('Test eval in 1s')
+ setTimeout(run_eval, 1000)
+
+ var code =
+ [ 'var foo = "in eval"'
+ , 'console.log("This is eval")'
+ , 'debugger'
+ , 'console.log("foo = " + foo)'
+ ].join('\n')
+
+ function run_eval() {
+ console.log('Run eval now')
+ debugger
+ eval(code)
+ }
+}
+
+function test_vm() {
+ console.log('Test vm')
+
+ var code =
+ [ 'var i = 10'
+ , 'setTimeout(hello, 1000)'
+ , ''
+ , 'function hello() {'
+ , ' debugger'
+ , ' console.log("Hello: " + i)'
+ , ' if(--i)'
+ , ' setTimeout(hello, 1000)'
+ , '}'
+ ].join('\n')
+
+ console.log('Run vm now')
+ var filename = '_couchdb:code.js'
+
+ var sandbox = {}
+ , ok = ['console', 'setTimeout']
+
+ ok.forEach(function(key) {
+ sandbox[key] = global[key]
+ })
+
+ var ctx = vm.createContext(sandbox)
+ var script = vm.createScript(code, filename)
+
+ var r = script.runInNewContext(sandbox)
+ console.log('Result:\n%s', util.inspect(r, false, 10))
+ return r
+}
+
+if(require.main === module)
+ main()
diff --git a/src/couchjs-node/xml.js b/src/couchjs-node/xml.js
new file mode 100644
index 000000000..c81525b9a
--- /dev/null
+++ b/src/couchjs-node/xml.js
@@ -0,0 +1,23 @@
+// Copyright 2011 Iris Couch
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+module.exports = XML
+
+function XML () {
+ this.foo = 'bar'
+}
+
+XML.prototype.toXMLString = function() {
+ return '<xml>\n <title>test</title>\n</xml>'
+}