diff --git a/README.md b/README.md index 385aa7e..19ee506 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,16 @@ First, generate the API by calling: * `factory_options` - an optional hash. See [factory options](#factory_options) reference for more. * `callback` - a callback function of the form `callback(error, couchdb_api)`. +You can optionally call `generate_couche_api` from the commandline: + + generate_couch_api [-f PATH] [-d DB_URL] [-a USERNAME:PASSWORD] + + * `PATH` - the location of the file into which to write the couchdb api. If not specified, it will be placed in path/to/YACA/lib/couchdb.js, and will be imported when you `require('YACA')`. + * `DB_URL` - the url to your CouchDB instance. It defaults to `http://127.0.0.1:5984`. If privileges are needed to view design documents or the root instance, provide username and password as: `http://username:password@host_name` + * `USERNAME:PASSWORD` - basic auth credentials where `USERNAME` is an admin username and `PASSWORD` is the admin's password, if you wish to be able to access admin-restricted content through the api. *The username and password will be stored in the generated cache.* See [options reference](#options) for more. + +The command line function is useful for generating a cache that your production code can then access and create an api without ever having to introspect the database. + ### 2. Use the API ### There are four primary API methods, corresponding to the http methods: * `get` diff --git a/bin/generate.js b/bin/generate.js new file mode 100755 index 0000000..e1e87f9 --- /dev/null +++ b/bin/generate.js @@ -0,0 +1,30 @@ +#!/usr/bin/env node + +couchdb_factory = require('YACA'); +cli = require('cli'); +path = require('path'); + +cli.parse( { 'file' : [ 'f' + , 'filename for output code' + , 'path' + , path.join(__dirname,'../lib/couchdb.js') + ] + , 'admin': [ 'a' + , 'admin basic auth in the form of: username:password' + , 'string' + ] + , 'db' : [ 'd' + , 'db root url, include basic-auth admin privileges if needed to view db.' + , 'url' + , 'http://127.0.0.1:5984' + ] + } ) + +cli.main(function(args, options) { + couchdb_factory( {file:options.file, admin:options.admin, db_url:options.db} + , function(error, couchdb) { + if (error) {console.log(error)} + else {console.log('complete')} + } + ); +}) diff --git a/bin/generate.js~ b/bin/generate.js~ index 8a5e943..c8498cc 100755 --- a/bin/generate.js~ +++ b/bin/generate.js~ @@ -1,256 +1,33 @@ #!/usr/bin/env node -request = require('request'); -querystring = require('querystring') -fs = require('fs') -url = require('url') -async = require('async') -cli = require('cli') -path_rx = /{{path}}/g -database_rx = /{{db}}/g -ddoc_rx = /{{ddoc}}/g -handler_rx = /{{handler}}/g -method_rx = /{{method}}/g +couchdb_factory = require('YACA'); -database_code = "\n\ -//** DB: {{db}} **\n\ -couchdb['{{db}}'] = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', null, callback)\n\ -}\n\ -couchdb['{{db}}'].get = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', null, callback)\n\ -}\n\ -couchdb['{{db}}'].del = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', 'del', callback)\n\ -}\n\ -couchdb['{{db}}'].post = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', 'post', callback)\n\ -}\n\ -couchdb['{{db}}'].put = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', 'put', callback)\n\ -}" -ddoc_code = "\n\ -//** DB: {{db}} DDOC: {{ddoc}}**\n\ -couchdb['{{db}}']['{{ddoc}}'] = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', null, callback)\n\ -}\n\ -couchdb['{{db}}']['{{ddoc}}'].get = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', null, callback)\n\ -}\n\ -couchdb['{{db}}']['{{ddoc}}'].del = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', 'del', callback)\n\ -}\n\ -couchdb['{{db}}']['{{ddoc}}'].post = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', 'post', callback)\n\ -}\n\ -couchdb['{{db}}']['{{ddoc}}'].put = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', 'put', callback)\n\ -}" -handler_code = "\n\ -couchdb['{{db}}']['{{ddoc}}'].{{handler}} = {}" -ddoc_api = - { views: { path: '_view/' - , code: "\n\ -couchdb['{{db}}']['{{ddoc}}'].views.{{method}} = function(query, callback) {\n\ - return couchdb._request_generator({query:query}, '{{path}}', null, callback)\n\ -}\n\ -couchdb['{{db}}']['{{ddoc}}'].views.{{method}}.get = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', null, callback)\n\ -}\n\ -couchdb['{{db}}']['{{ddoc}}'].views.{{method}}.post = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', post, callback)\n\ -}" - } - , shows: { path: '_show/' - , code: "\n\ -couchdb['{{db}}']['{{ddoc}}'].shows.{{method}} = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', null, callback)\n\ -}\n\ -couchdb['{{db}}']['{{ddoc}}'].shows.{{method}}.get = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', null, callback)\n\ -}\n\ -couchdb['{{db}}']['{{ddoc}}'].shows.{{method}}.post = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', 'post', callback)\n\ -}" - } - , lists: { path: '_list/' - , code: "\n\ -couchdb['{{db}}']['{{ddoc}}'].lists.{{method}} = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', null, callback)\n\ -}\n\ -couchdb['{{db}}']['{{ddoc}}'].lists.{{method}}.get = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', null, callback)\n\ -}\n\ -couchdb['{{db}}']['{{ddoc}}'].lists.{{method}}.post = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', 'post', callback)\n\ -}" - } - , updates: { path: '_update/' - , code: "\n\ -couchdb['{{db}}']['{{ddoc}}'].updates.{{method}} = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', 'put', callback)\n\ -}\n\ -couchdb['{{db}}']['{{ddoc}}'].updates.{{method}}.put = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', 'put', callback)\n\ -}\n\ -couchdb['{{db}}']['{{ddoc}}'].updates.{{method}}.post = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', 'post', callback)\n\ -}" - } - , rewrites: { path: '_rewrite/' - , code: "\n\ -couchdb['{{db}}']['{{ddoc}}'].rewrites.{{method}} = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', null, callback)\n\ -}\n\ -couchdb['{{db}}']['{{ddoc}}'].rewrites.{{method}}.get = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', null, callback)\n\ -}\n\ -couchdb['{{db}}']['{{ddoc}}'].rewrites.{{method}}.put = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', 'put', callback)\n\ -}\n\ -couchdb['{{db}}']['{{ddoc}}'].rewrites.{{method}}.post = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', 'post', callback)\n\ -}\n\ -couchdb['{{db}}']['{{ddoc}}'].rewrites.{{method}}.del = function(options, callback) {\n\ - return couchdb._request_generator(options, '{{path}}', 'del', callback)\n\ -}" - } - } - -function gen_interface(uri, filename, admin) { - host = uri || 'http://127.0.0.1:5984/' - host += (host.slice(-1) == '/') ? '' : '/' // ensure ends in slash - uri = url.parse(host) - filename = filename || '../lib/couchdb.js' - - // the results of parsing our db - parsed_db = {_path:''}; - - console.log('using host:', host) - console.log('writing to file:', filename) - if (admin) { - console.log('making admin credentials available:', admin) - } - else { - console.log('warning: no admin credentials provided (provide as third argument: "username:password")') - } - - var source = fs.readFileSync('../lib/template.js', 'utf8'); - - function write_line(line) { - source += line + '\n' - } - - - // generate and append root uris - var ROOT = '"' - ROOT += uri.protocol || 'http:' - ROOT += '//' - ROOT += uri.hostname - ROOT += (uri.port) ? ':'+uri.port : '' - ROOT += '/"' - - write_line('couchdb.ROOT = ' + ROOT) - - if (admin) { - var ADMIN_ROOT ='"' - ADMIN_ROOT += uri.protocol || 'http:' - ADMIN_ROOT += '//' - ADMIN_ROOT += admin+'@' - ADMIN_ROOT += uri.hostname - ADMIN_ROOT += (uri.port) ? ':'+uri.port : '' - ADMIN_ROOT += '/"' - write_line('couchdb.ADMIN_ROOT = ' + ADMIN_ROOT) - } - - return request(host+'_all_dbs', handle_dbs); - - function handle_dbs(error, response, body) { - body = JSON.parse(body); - return async.forEach(body, parse_db, handle_db_complete) - } - - function parse_db(db, db_callback) { - // generate database api - db_name = db - if (['get', 'post', 'put', 'del', '_has_error', '_request_generator'].indexOf(db) >= 0) { - db_name = '__'+db - console.log(db, 'is a protected word. remapped to:', db_name) - } - - db_path = db + '/'; - write_line(database_code.replace(database_rx, db_name).replace(path_rx, db_path)) - - // get the design docs for this db - query = querystring.stringify({startkey:'"_design/"',endkey:'"_design0"',include_docs:true}) - return request(host+db+'/_all_docs?'+query, handle_ddocs) - - function handle_ddocs(error, response, body) { - db_name = response.request.path.split('/')[1] - db_name = (['get', 'post', 'put', 'del', '_has_error', '_request_generator'].indexOf(db) >= 0) ? '__'+db_name : db_name - body = JSON.parse(body); - for (i in body.rows) { - // generate ddoc api - ddoc = body.rows[i].doc - ddoc_name = ddoc._id.split('/')[1]; - if (['get', 'post', 'put', 'del', '_path'].indexOf(ddoc_name) >= 0) { - console.log(ddoc_name, 'is a protected word. remapped to:', '__'+ddoc_name) - ddoc_name = '__'+ddoc_name; - } - - ddoc_path = db_path + ddoc._id + '/' - write_line(ddoc_code.replace(ddoc_rx, ddoc_name).replace(database_rx, db_name).replace(path_rx, ddoc_path)) - - // generate handler (eg views, updates) apis contained in ddoc - for (handler_name in ddoc) { - if (handler_name in ddoc_api && JSON.stringify(ddoc[handler_name]) != JSON.stringify({})) { - handler = ddoc[handler_name] - handler_path = ddoc_path + ddoc_api[handler_name].path; - write_line(handler_code.replace(handler_rx, handler_name) - .replace(ddoc_rx, ddoc_name) - .replace(database_rx, db_name) - .replace(path_rx, handler_path) ) - - // put any method calls (eg _view/_by_id) in target handler - for (method_name in handler) { - method_path = handler_path + method_name - write_line(ddoc_api[handler_name].code - .replace(method_rx, method_name) - .replace(ddoc_rx, ddoc_name) - .replace(database_rx, db_name) - .replace(path_rx, method_path) ) - } - } - } - } - return db_callback(); - } - } - - function handle_db_complete(error) { - fs.writeFileSync(filename, source) - console.log('Done') - } -} - - - -cli.parse( { 'file': ['f', 'filename for output code', 'path', '../lib/couchdb.js'] - , 'admin': ['a', 'admin basic auth in the form of: username:password', 'string'] - , 'db': ['d', 'db root url, include basic-auth admin privileges if needed to modify db.', 'url', 'http://127.0.0.1:5984'] +cli.parse( { 'file' : [ 'f' + , 'filename for output code' + , 'path' + , path.join(__dirname,'../lib/couchdb.js') + ] + , 'admin': [ 'a' + , 'admin basic auth in the form of: username:password' + , 'string' + ] + , 'db' : [ 'd' + , 'db root url, include basic-auth admin privileges if needed to view db.' + , 'url' + , 'http://127.0.0.1:5984' + ] } ) - -cli.main(function(args, options) { - gen_interface(options.db, options.file, options.admin); - }) -//if (!module.parent) { -//console.log(process) -// gen_interface(process.argv[2], process.argv[3], process.argv[4]) -//} - +cli.main(function(args, options) { + couchdb_factory( {file:options.db, admin:options.admin, db_url:options.db} + , function(error, couchdb) { + if (error) {console.log(error)} + else {console.log(complete)} + } + ); +}) diff --git a/lib/api_factory.js b/lib/api_factory.js index 43df5f5..7715300 100644 --- a/lib/api_factory.js +++ b/lib/api_factory.js @@ -16,6 +16,29 @@ handlers = {views:'_view',shows:'_show',lists:'_list',updates:'_update',rewrites // ** CouchDB Root ** var proto_couchdb = proto_helper(['get', 'put', 'post', 'del'], 'get'); +proto_couchdb['_uuids'] = function(count, callback) { + if(!callback) { + callback = count; + options = '_uuids'; + } + else { + options = {uri:'_uuids', query:{count:count}}; + } + return this.get(options, helper); + + function helper(e,r,b) { + if (e) {return callback(e);} + return callback(null, b['uuids']); + } +} +/* +proto_couchdb[' get'] = function(options, callback) { + console.log(this); + return this._request(options, null, callback) + +} +*/ + // options: // file: name of cache file false if no cache - defaults to ./couchdb.json // db_url: url of couchdb - defaults to http://127.0.0.1:5984 @@ -50,6 +73,7 @@ proto_couchdb.constructor = function(options, callback){ else { // parse file and update with new options, if any; cache = JSON.parse(file); + constructor_helper.apply(that, [cache]); var changed = false; if (options.update) { changed = true; @@ -88,11 +112,13 @@ proto_couchdb.constructor = function(options, callback){ if (options.admin) { cache.ADMIN_PATH = uri.protocol || 'http:' cache.ADMIN_PATH += '//' - cache.ADMIN_PATH += admin+'@' + cache.ADMIN_PATH += options.admin+'@' cache.ADMIN_PATH += uri.hostname cache.ADMIN_PATH += (uri.port) ? ':'+uri.port : '' cache.ADMIN_PATH += '/' } + else {console.log('warning: no admin privileges provided.');} + // add helper functions constructor_helper.apply(that, [cache]); that.get('_all_dbs', handle_dbs); @@ -110,7 +136,6 @@ proto_couchdb.constructor = function(options, callback){ db_name = '__'+db_name console.log(db, 'is a protected word. remapped to:', db_name) } - console.log(db_name); cache.children[db_name] = {children:{}}; var db_cache = cache.children[db_name]; add_paths(db_cache, cache, db); @@ -199,21 +224,6 @@ proto_couchdb.constructor = function(options, callback){ } -proto_couchdb._uuids = function(count, callback) { - if(!callback) { - callback = count; - options = ''; - } - else { - options = {query:{count:count}}; - } - return couchdb._request(options, '_uuids', null, helper); - - function helper(e,r,b) { - if (e) {return callback(e);} - return callback(null, b['uuids']); - } -} var couchdb_factory = factory_factory(proto_couchdb) module.exports = couchdb_factory; diff --git a/lib/factory.js b/lib/factory.js index f103a42..3cc5ad5 100644 --- a/lib/factory.js +++ b/lib/factory.js @@ -32,7 +32,6 @@ var proto_factory_factory = function (proto_hash) { proto.constructor.apply(f, arguments); function handle_constructor_complete(error) { - console.log('finished, returning instance'); if (error) return callback(error); return callback(null, f); } @@ -55,6 +54,7 @@ var proto_factory_factory_sync = function (proto_hash) { return f; } } + function init_helper(names) { names.forEach(function(i) {that[i] = methods[i]}); } @@ -81,7 +81,7 @@ function proto_helper(names, call) { names.forEach(function(i) {proto[i] = methods[i]}); if (call) {proto.call = methods[call];} - proto.constructor = constructor_helper; + proto.constructor = constructor_helper; return proto; } @@ -117,7 +117,7 @@ function constructor_helper(cache) { } this._request = function(options, method, callback) { - console.log('generating request', options, PATH, method) +// console.log('generating request', options, PATH, method) // convert options to a standardized form. // if someone just gives a callback, the callback will be in the options // variable, and callback will be empty. make call to db with empty path @@ -158,7 +158,7 @@ function constructor_helper(cache) { options.uri = uri; var request = (!method) ? Request : Request[method]; - console.log('request options:', options) +// console.log('request options:', options) return request(options, handle_response); // return any couchdb errors as errors. handle body parsing unless specifically declined (options.parse=false) diff --git a/package.json b/package.json index 1700d4c..a31c558 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ , "directories": { "lib": "./lib" , "bin": "./bin" } +, "bin": {"generate_couch_api": "./bin/generate.js"} , "main": "index.js" , "author": "David Greisen " , "repository": {"type":"git", "url":"http://github.com/dgreisen/YACA.git"}