working on couchview
This commit is contained in:
parent
541a3cac74
commit
9aeb34912e
|
@ -1,6 +1,6 @@
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = "couchrest"
|
s.name = "couchrest"
|
||||||
s.version = "0.8.3"
|
s.version = "0.8.4"
|
||||||
s.date = "2008-06-20"
|
s.date = "2008-06-20"
|
||||||
s.summary = "Lean and RESTful interface to CouchDB."
|
s.summary = "Lean and RESTful interface to CouchDB."
|
||||||
s.email = "jchris@grabb.it"
|
s.email = "jchris@grabb.it"
|
||||||
|
@ -8,7 +8,13 @@ Gem::Specification.new do |s|
|
||||||
s.description = "CouchRest provides a simple interface on top of CouchDB's RESTful HTTP API, as well as including some utility scripts for managing views and attachments."
|
s.description = "CouchRest provides a simple interface on top of CouchDB's RESTful HTTP API, as well as including some utility scripts for managing views and attachments."
|
||||||
s.has_rdoc = false
|
s.has_rdoc = false
|
||||||
s.authors = ["J. Chris Anderson"]
|
s.authors = ["J. Chris Anderson"]
|
||||||
s.files = %w{lib/couchrest.rb lib/database.rb lib/pager.rb Rakefile README script/couchdir script/couchview spec/couchrest_spec.rb spec/database_spec.rb spec/pager_spec.rb spec/spec_helper.rb}
|
s.files = %w{
|
||||||
|
lib/couchrest.rb lib/database.rb lib/pager.rb lib/file_manager.rb
|
||||||
|
Rakefile README
|
||||||
|
script/couchdir script/couchview
|
||||||
|
spec/couchrest_spec.rb spec/database_spec.rb spec/pager_spec.rb spec/file_manager_spec.rb
|
||||||
|
spec/spec_helper.rb
|
||||||
|
}
|
||||||
s.require_path = "lib"
|
s.require_path = "lib"
|
||||||
s.add_dependency("json", [">= 1.1.2"])
|
s.add_dependency("json", [">= 1.1.2"])
|
||||||
s.add_dependency("rest-client", [">= 0.5"])
|
s.add_dependency("rest-client", [">= 0.5"])
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
require "rubygems"
|
||||||
|
require 'json'
|
||||||
|
require 'rest_client'
|
||||||
|
|
||||||
require File.dirname(__FILE__) + '/lib/couchrest'
|
require File.dirname(__FILE__) + '/lib/couchrest'
|
||||||
require File.dirname(__FILE__) + '/lib/database'
|
require File.dirname(__FILE__) + '/lib/database'
|
||||||
require File.dirname(__FILE__) + '/lib/pager'
|
require File.dirname(__FILE__) + '/lib/pager'
|
||||||
|
|
|
@ -1,10 +1,3 @@
|
||||||
require "rubygems"
|
|
||||||
require 'json'
|
|
||||||
require 'rest_client'
|
|
||||||
|
|
||||||
require File.dirname(__FILE__) + '/database'
|
|
||||||
require File.dirname(__FILE__) + '/pager'
|
|
||||||
|
|
||||||
class CouchRest
|
class CouchRest
|
||||||
attr_accessor :uri
|
attr_accessor :uri
|
||||||
def initialize server = 'http://localhost:5984'
|
def initialize server = 'http://localhost:5984'
|
||||||
|
|
|
@ -1,14 +1,5 @@
|
||||||
require 'rubygems'
|
|
||||||
require 'couchrest'
|
|
||||||
require 'digest/md5'
|
require 'digest/md5'
|
||||||
|
|
||||||
# todo = ARGV
|
|
||||||
# todo = ["views", "public", "controllers"] if ARGV.include? "all"
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# PROJECT_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(PROJECT_ROOT)
|
|
||||||
# DBNAME = JSON.load( open("#{PROJECT_ROOT}/config.json").read )["db"]
|
|
||||||
|
|
||||||
|
|
||||||
class CouchRest
|
class CouchRest
|
||||||
class FileManager
|
class FileManager
|
||||||
|
@ -16,11 +7,11 @@ class CouchRest
|
||||||
|
|
||||||
LANGS = {"rb" => "ruby", "js" => "javascript"}
|
LANGS = {"rb" => "ruby", "js" => "javascript"}
|
||||||
MIMES = {
|
MIMES = {
|
||||||
"html" => "text/html",
|
"html" => "text/html",
|
||||||
"htm" => "text/html",
|
"htm" => "text/html",
|
||||||
"png" => "image/png",
|
"png" => "image/png",
|
||||||
"css" => "text/css",
|
"css" => "text/css",
|
||||||
"js" => "test/javascript"
|
"js" => "test/javascript"
|
||||||
}
|
}
|
||||||
def initialize(dbname, host="http://localhost:5984")
|
def initialize(dbname, host="http://localhost:5984")
|
||||||
@db = CouchRest.new(host).database(dbname)
|
@db = CouchRest.new(host).database(dbname)
|
||||||
|
@ -166,7 +157,7 @@ class CouchRest
|
||||||
|
|
||||||
def read(file, libs=nil)
|
def read(file, libs=nil)
|
||||||
st = open(file).read
|
st = open(file).read
|
||||||
st.sub!(/\/\/include-lib/,libs) if libs
|
st.sub!(/(\/\/|#)include-lib/,libs) if libs
|
||||||
st
|
st
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
168
script/couchview
168
script/couchview
|
@ -1,129 +1,131 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
commands = %w{pull push}
|
commands = %w{push generate}
|
||||||
|
|
||||||
command = ARGV[0]
|
command = ARGV[0]
|
||||||
|
|
||||||
if !commands.include?(command)
|
if !commands.include?(command)
|
||||||
puts <<-USAGE
|
puts <<-USAGE
|
||||||
Usage: couchview (pull|push) my-database-name
|
Couchview has two modes: push and generate. Run couchview push or couchview generate for usage documentation.
|
||||||
For help on pull and push run script/views (pull|push) without a database name.
|
|
||||||
USAGE
|
USAGE
|
||||||
exit
|
exit
|
||||||
end
|
end
|
||||||
|
|
||||||
if ARGV.length == 1
|
if ARGV.length == 1
|
||||||
case command
|
case command
|
||||||
when "pull"
|
when "generate"
|
||||||
puts <<-PULL
|
puts <<-GEN
|
||||||
couchview pull my-database-name
|
|
||||||
|
|
||||||
I will automagically create a "views" directory in your current working directory if none exists.
|
|
||||||
Then I copy the design documents and views into a directory structure like:
|
|
||||||
|
|
||||||
./views/my-design-doc/view-name-map.js
|
|
||||||
./views/my-design-doc/view-name-reduce.js
|
|
||||||
|
|
||||||
If your view names don't end in "map" or "reduce" I'll add those suffixes as a pull. On push I'll put them in new locations corresponding to these new names (overwriting the old design documents). I'm opinionated, but if these conventions don't work for you, the source code is right here.
|
|
||||||
|
|
||||||
PULL
|
GEN
|
||||||
when "push"
|
when "push"
|
||||||
puts <<-PUSH
|
puts <<-PUSH
|
||||||
couchview push my-database-name
|
== Pushing views with Couchview ==
|
||||||
|
|
||||||
I'll push all the files in your views directory to the specified database. Because CouchDB caches the results of view calculation by function content, there's no performance penalty for duplicating the map function twice, which I'll do if you have a reduce function. This makes it possible to browse the results of just the map, which can be useful for both queries and debugging.
|
Usage: couchview push directory dbname
|
||||||
|
|
||||||
./views/my-design-doc/view-name-map.js
|
Couchview expects a specific filesystem layout for your CouchDB views (see
|
||||||
./views/my-design-doc/view-name-reduce.js
|
example below). It also supports advanced features like inlining of library
|
||||||
|
code (so you can keep DRY) as well as avoiding unnecessary document
|
||||||
|
modification.
|
||||||
|
|
||||||
Pushed to =>
|
Couchview also solves a problem with CouchDB's view API, which only provides
|
||||||
|
access to the final reduce side of any views which have both a map and a
|
||||||
|
reduce function defined. The intermediate map results are often useful for
|
||||||
|
development and production. CouchDB is smart enough to reuse map indexes for
|
||||||
|
functions duplicated across views within the same design document.
|
||||||
|
|
||||||
http://localhost:5984/my-database-name/_design/my-design-doc
|
For views with a reduce function defined, Couchview creates both a reduce view
|
||||||
|
and a map-only view, so that you can browse and query the map side as well as
|
||||||
|
the reduction, with no performance penalty.
|
||||||
|
|
||||||
|
== Example ==
|
||||||
|
|
||||||
|
couchview push foo-project/bar-views baz-database
|
||||||
|
|
||||||
|
This will push the views defined in foo-project/bar-views into a database
|
||||||
|
called baz-database. Couchview expects the views to be defined in files with
|
||||||
|
names like:
|
||||||
|
|
||||||
|
foo-project/bar-views/my-design/viewname-map.js
|
||||||
|
foo-project/bar-views/my-design/viewname-reduce.js
|
||||||
|
foo-project/bar-views/my-design/noreduce-map.js
|
||||||
|
|
||||||
|
Pushed to => http://localhost:5984/baz-database/_design/my-design
|
||||||
|
|
||||||
|
And the design document:
|
||||||
{
|
{
|
||||||
"views" : {
|
"views" : {
|
||||||
"view-name-map" : {
|
"viewname-map" : {
|
||||||
"map" : "### contents of view-name-map.js ###"
|
"map" : "### contents of view-name-map.js ###"
|
||||||
},
|
},
|
||||||
"view-name-reduce" : {
|
"viewname-reduce" : {
|
||||||
"map" : "### contents of view-name-map.js ###",
|
"map" : "### contents of view-name-map.js ###",
|
||||||
"reduce" : "### contents of view-name-reduce.js ###"
|
"reduce" : "### contents of view-name-reduce.js ###"
|
||||||
},
|
},
|
||||||
|
"noreduce-map" : {
|
||||||
|
"map" : "### contents of noreduce-map.js ###"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Couchview will create a design document for each subdirectory of the views
|
||||||
|
directory specified on the command line.
|
||||||
|
|
||||||
|
== Library Inlining ==
|
||||||
|
|
||||||
|
Couchview can optionally inline library code into your views so you only have
|
||||||
|
to maintain it in one place. It looks for any files named lib.* in your
|
||||||
|
design-doc directory (for doc specific libs) and in the parent views directory
|
||||||
|
(for project global libs). These libraries are only inserted into views which
|
||||||
|
include the text
|
||||||
|
|
||||||
|
//include-lib
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
#include-lib
|
||||||
|
|
||||||
|
Couchview is a result of scratching my own itch. I'd be happy to make it more
|
||||||
|
general, so please contact me at jchris@grabb.it if you'd like to see anything
|
||||||
|
added or changed.
|
||||||
PUSH
|
PUSH
|
||||||
end
|
end
|
||||||
exit
|
exit
|
||||||
end
|
end
|
||||||
|
|
||||||
|
require 'rubygems'
|
||||||
|
require 'couchrest'
|
||||||
|
|
||||||
|
if command == 'push'
|
||||||
|
dirname = ARGV[1]
|
||||||
|
dbname = ARGV[2]
|
||||||
|
puts "Pushing views from directory #{dirname} to database #{dbname}"
|
||||||
|
fm = CouchRest::FileManager.new(dbname)
|
||||||
|
# fm.loud = true
|
||||||
|
fm.push_views(dirname)
|
||||||
|
elsif command == 'pull'
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
__END__
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
dbname = ARGV[1]
|
dbname = ARGV[1]
|
||||||
dirname = ARGV[2] || "views"
|
dirname = ARGV[2] || "views"
|
||||||
|
|
||||||
puts "Running #{command} on #{dbname} from directory #{dirname}."
|
puts "Running #{command} on #{dbname} from directory #{dirname}."
|
||||||
|
|
||||||
require File.expand_path(File.dirname(__FILE__)) + '/../couchrest'
|
require 'rubygems'
|
||||||
require 'fileutils'
|
require 'couchrest'
|
||||||
|
|
||||||
module Enumerable
|
|
||||||
def group_by
|
|
||||||
inject({}) do |groups, element|
|
|
||||||
(groups[yield(element)] ||= []) << element
|
|
||||||
groups
|
|
||||||
end
|
|
||||||
end if RUBY_VERSION < '1.9'
|
|
||||||
end
|
|
||||||
|
|
||||||
# connect to couchdb
|
|
||||||
cr = CouchRest.new("http://localhost:5984")
|
|
||||||
db = cr.database(dbname)
|
|
||||||
|
|
||||||
def readjs(file, libs=nil)
|
|
||||||
st = open(file).read
|
|
||||||
st.sub!(/\/\/include-lib/,libs) if libs
|
|
||||||
st
|
|
||||||
end
|
|
||||||
|
|
||||||
case command
|
case command
|
||||||
when "push" # files to views
|
when "push" # files to views
|
||||||
views = {}
|
|
||||||
viewfiles = Dir.glob(File.join(dirname,"**","*.js")) # todo support non-js views
|
|
||||||
libfiles = viewfiles.select{|f|/lib\.js/.match(f)}
|
|
||||||
libs = open(libfiles[0]).read if libfiles[0]
|
|
||||||
all = (viewfiles-libfiles).collect do |file|
|
|
||||||
fileparts = file.split('/')
|
|
||||||
filename = /(\w.*)-(\w.*)\.js/.match file.split('/').pop
|
|
||||||
design = fileparts[1]
|
|
||||||
view = filename[1]
|
|
||||||
func = filename[2]
|
|
||||||
path = file
|
|
||||||
[design,view,func,path]
|
|
||||||
end
|
|
||||||
designs = all.group_by{|f|f[0]}
|
|
||||||
designs.each do |design,parts|
|
|
||||||
# puts "replace _design/#{design}? (enter to proceed, 'n' to skip)"
|
|
||||||
# rep = $stdin.gets.chomp
|
|
||||||
# next if rep == 'n'
|
|
||||||
dviews = {}
|
|
||||||
parts.group_by{|p|p[1]}.each do |view,fs|
|
|
||||||
fs.each do |f|
|
|
||||||
dviews["#{view}-reduce"] ||= {}
|
|
||||||
dviews["#{view}-reduce"][f[2]] = readjs(f.last,libs)
|
|
||||||
end
|
|
||||||
dviews["#{view}-map"] = {'map' => dviews["#{view}-reduce"]['map']}
|
|
||||||
dviews.delete("#{view}-reduce") unless dviews["#{view}-reduce"]["reduce"]
|
|
||||||
end
|
|
||||||
# save them to the db
|
|
||||||
view = db.get("_design/#{design}") rescue nil
|
|
||||||
if (view && view['views'] == dviews)
|
|
||||||
puts "no change to _design/#{design}. skipping..."
|
|
||||||
else
|
|
||||||
puts "replacing _design/#{design}"
|
|
||||||
db.delete(view) rescue nil
|
|
||||||
db.save({
|
|
||||||
"_id" => "_design/#{design}",
|
|
||||||
:views => dviews
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
when "pull" # views to files
|
when "pull" # views to files
|
||||||
ds = db.documents(:startkey => '_design/', :endkey => '_design/ZZZZZZZZZ')
|
ds = db.documents(:startkey => '_design/', :endkey => '_design/ZZZZZZZZZ')
|
||||||
|
|
Loading…
Reference in a new issue