jchris-couchrest 0.8.3 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/couchdir +20 -0
- data/bin/couchview +111 -0
- data/lib/couch_rest.rb +62 -0
- data/lib/couchrest.rb +2 -61
- data/lib/database.rb +4 -1
- data/lib/file_manager.rb +396 -0
- data/lib/pager.rb +0 -11
- data/spec/couchrest_spec.rb +0 -13
- data/spec/file_manager_spec.rb +116 -0
- metadata +11 -6
- data/script/couchdir +0 -62
- data/script/couchview +0 -162
data/bin/couchdir
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
unless ARGV.length >= 2
|
4
|
+
puts "usage: couchdir path/to/directory db-name [docid]"
|
5
|
+
exit
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'rubygems'
|
9
|
+
require 'couchrest'
|
10
|
+
|
11
|
+
dirname = ARGV[0]
|
12
|
+
dbname = ARGV[1]
|
13
|
+
docid = ARGV[2]
|
14
|
+
|
15
|
+
puts "Create attachments for the files in #{dirname} in database #{dbname}."
|
16
|
+
|
17
|
+
fm = CouchRest::FileManager.new(dbname)
|
18
|
+
fm.loud = true
|
19
|
+
puts "Pushing views from directory #{dirname} to database #{fm.db}"
|
20
|
+
fm.push_directory(dirname, docid)
|
data/bin/couchview
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
commands = %w{push generate}
|
4
|
+
|
5
|
+
command = ARGV[0]
|
6
|
+
|
7
|
+
if !commands.include?(command)
|
8
|
+
puts <<-USAGE
|
9
|
+
Couchview has two modes: push and generate. Run couchview push or couchview generate for usage documentation.
|
10
|
+
USAGE
|
11
|
+
exit
|
12
|
+
end
|
13
|
+
|
14
|
+
if ARGV.length == 1
|
15
|
+
case command
|
16
|
+
when "generate"
|
17
|
+
puts <<-GEN
|
18
|
+
Usage: couchview generate directory design1 design2 design3 ...
|
19
|
+
|
20
|
+
Couchview will create directories and example views for the design documents you specify.
|
21
|
+
|
22
|
+
GEN
|
23
|
+
when "push"
|
24
|
+
puts <<-PUSH
|
25
|
+
== Pushing views with Couchview ==
|
26
|
+
|
27
|
+
Usage: couchview push directory dbname
|
28
|
+
|
29
|
+
Couchview expects a specific filesystem layout for your CouchDB views (see
|
30
|
+
example below). It also supports advanced features like inlining of library
|
31
|
+
code (so you can keep DRY) as well as avoiding unnecessary document
|
32
|
+
modification.
|
33
|
+
|
34
|
+
Couchview also solves a problem with CouchDB's view API, which only provides
|
35
|
+
access to the final reduce side of any views which have both a map and a
|
36
|
+
reduce function defined. The intermediate map results are often useful for
|
37
|
+
development and production. CouchDB is smart enough to reuse map indexes for
|
38
|
+
functions duplicated across views within the same design document.
|
39
|
+
|
40
|
+
For views with a reduce function defined, Couchview creates both a reduce view
|
41
|
+
and a map-only view, so that you can browse and query the map side as well as
|
42
|
+
the reduction, with no performance penalty.
|
43
|
+
|
44
|
+
== Example ==
|
45
|
+
|
46
|
+
couchview push foo-project/bar-views baz-database
|
47
|
+
|
48
|
+
This will push the views defined in foo-project/bar-views into a database
|
49
|
+
called baz-database. Couchview expects the views to be defined in files with
|
50
|
+
names like:
|
51
|
+
|
52
|
+
foo-project/bar-views/my-design/viewname-map.js
|
53
|
+
foo-project/bar-views/my-design/viewname-reduce.js
|
54
|
+
foo-project/bar-views/my-design/noreduce-map.js
|
55
|
+
|
56
|
+
Pushed to => http://localhost:5984/baz-database/_design/my-design
|
57
|
+
|
58
|
+
And the design document:
|
59
|
+
{
|
60
|
+
"views" : {
|
61
|
+
"viewname-map" : {
|
62
|
+
"map" : "### contents of view-name-map.js ###"
|
63
|
+
},
|
64
|
+
"viewname-reduce" : {
|
65
|
+
"map" : "### contents of view-name-map.js ###",
|
66
|
+
"reduce" : "### contents of view-name-reduce.js ###"
|
67
|
+
},
|
68
|
+
"noreduce-map" : {
|
69
|
+
"map" : "### contents of noreduce-map.js ###"
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
Couchview will create a design document for each subdirectory of the views
|
75
|
+
directory specified on the command line.
|
76
|
+
|
77
|
+
== Library Inlining ==
|
78
|
+
|
79
|
+
Couchview can optionally inline library code into your views so you only have
|
80
|
+
to maintain it in one place. It looks for any files named lib.* in your
|
81
|
+
design-doc directory (for doc specific libs) and in the parent views directory
|
82
|
+
(for project global libs). These libraries are only inserted into views which
|
83
|
+
include the text
|
84
|
+
|
85
|
+
//include-lib
|
86
|
+
|
87
|
+
or
|
88
|
+
|
89
|
+
#include-lib
|
90
|
+
|
91
|
+
Couchview is a result of scratching my own itch. I'd be happy to make it more
|
92
|
+
general, so please contact me at jchris@grabb.it if you'd like to see anything
|
93
|
+
added or changed.
|
94
|
+
PUSH
|
95
|
+
end
|
96
|
+
exit
|
97
|
+
end
|
98
|
+
|
99
|
+
require 'rubygems'
|
100
|
+
require 'couchrest'
|
101
|
+
|
102
|
+
if command == 'push'
|
103
|
+
dirname = ARGV[1]
|
104
|
+
dbname = ARGV[2]
|
105
|
+
fm = CouchRest::FileManager.new(dbname)
|
106
|
+
fm.loud = true
|
107
|
+
puts "Pushing views from directory #{dirname} to database #{fm.db}"
|
108
|
+
fm.push_views(dirname)
|
109
|
+
elsif command == 'generate'
|
110
|
+
puts "Under construction ;)"
|
111
|
+
end
|
data/lib/couch_rest.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
class CouchRest
|
2
|
+
attr_accessor :uri
|
3
|
+
def initialize server = 'http://localhost:5984'
|
4
|
+
@uri = server
|
5
|
+
end
|
6
|
+
|
7
|
+
# list all databases on the server
|
8
|
+
def databases
|
9
|
+
CouchRest.get "#{@uri}/_all_dbs"
|
10
|
+
end
|
11
|
+
|
12
|
+
def database name
|
13
|
+
CouchRest::Database.new(@uri, name)
|
14
|
+
end
|
15
|
+
|
16
|
+
# get the welcome message
|
17
|
+
def info
|
18
|
+
CouchRest.get "#{@uri}/"
|
19
|
+
end
|
20
|
+
|
21
|
+
# restart the couchdb instance
|
22
|
+
def restart!
|
23
|
+
CouchRest.post "#{@uri}/_restart"
|
24
|
+
end
|
25
|
+
|
26
|
+
# create a database
|
27
|
+
def create_db name
|
28
|
+
CouchRest.put "#{@uri}/#{name}"
|
29
|
+
database name
|
30
|
+
end
|
31
|
+
|
32
|
+
class << self
|
33
|
+
def put uri, doc = nil
|
34
|
+
payload = doc.to_json if doc
|
35
|
+
JSON.parse(RestClient.put(uri, payload))
|
36
|
+
end
|
37
|
+
|
38
|
+
def get uri
|
39
|
+
JSON.parse(RestClient.get(uri), :max_nesting => false)
|
40
|
+
end
|
41
|
+
|
42
|
+
def post uri, doc = nil
|
43
|
+
payload = doc.to_json if doc
|
44
|
+
JSON.parse(RestClient.post(uri, payload))
|
45
|
+
end
|
46
|
+
|
47
|
+
def delete uri
|
48
|
+
JSON.parse(RestClient.delete(uri))
|
49
|
+
end
|
50
|
+
|
51
|
+
def paramify_url url, params = nil
|
52
|
+
if params
|
53
|
+
query = params.collect do |k,v|
|
54
|
+
v = v.to_json if %w{key startkey endkey}.include?(k.to_s)
|
55
|
+
"#{k}=#{CGI.escape(v.to_s)}"
|
56
|
+
end.join("&")
|
57
|
+
url = "#{url}?#{query}"
|
58
|
+
end
|
59
|
+
url
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/couchrest.rb
CHANGED
@@ -2,68 +2,9 @@ require "rubygems"
|
|
2
2
|
require 'json'
|
3
3
|
require 'rest_client'
|
4
4
|
|
5
|
+
require File.dirname(__FILE__) + '/couch_rest'
|
5
6
|
require File.dirname(__FILE__) + '/database'
|
6
7
|
require File.dirname(__FILE__) + '/pager'
|
8
|
+
require File.dirname(__FILE__) + '/file_manager'
|
7
9
|
|
8
|
-
class CouchRest
|
9
|
-
attr_accessor :uri
|
10
|
-
def initialize server = 'http://localhost:5984'
|
11
|
-
@uri = server
|
12
|
-
end
|
13
|
-
|
14
|
-
# list all databases on the server
|
15
|
-
def databases
|
16
|
-
CouchRest.get "#{@uri}/_all_dbs"
|
17
|
-
end
|
18
|
-
|
19
|
-
def database name
|
20
|
-
CouchRest::Database.new(@uri, name)
|
21
|
-
end
|
22
|
-
|
23
|
-
# get the welcome message
|
24
|
-
def info
|
25
|
-
CouchRest.get "#{@uri}/"
|
26
|
-
end
|
27
10
|
|
28
|
-
# restart the couchdb instance
|
29
|
-
def restart!
|
30
|
-
CouchRest.post "#{@uri}/_restart"
|
31
|
-
end
|
32
|
-
|
33
|
-
# create a database
|
34
|
-
def create_db name
|
35
|
-
CouchRest.put "#{@uri}/#{name}"
|
36
|
-
database name
|
37
|
-
end
|
38
|
-
|
39
|
-
class << self
|
40
|
-
def put uri, doc = nil
|
41
|
-
payload = doc.to_json if doc
|
42
|
-
JSON.parse(RestClient.put(uri, payload))
|
43
|
-
end
|
44
|
-
|
45
|
-
def get uri
|
46
|
-
JSON.parse(RestClient.get(uri), :max_nesting => false)
|
47
|
-
end
|
48
|
-
|
49
|
-
def post uri, doc = nil
|
50
|
-
payload = doc.to_json if doc
|
51
|
-
JSON.parse(RestClient.post(uri, payload))
|
52
|
-
end
|
53
|
-
|
54
|
-
def delete uri
|
55
|
-
JSON.parse(RestClient.delete(uri))
|
56
|
-
end
|
57
|
-
|
58
|
-
def paramify_url url, params = nil
|
59
|
-
if params
|
60
|
-
query = params.collect do |k,v|
|
61
|
-
v = v.to_json if %w{key startkey endkey}.include?(k.to_s)
|
62
|
-
"#{k}=#{CGI.escape(v.to_s)}"
|
63
|
-
end.join("&")
|
64
|
-
url = "#{url}?#{query}"
|
65
|
-
end
|
66
|
-
url
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
data/lib/database.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'cgi'
|
2
2
|
require "base64"
|
3
|
-
require File.dirname(__FILE__) + '/couchrest'
|
4
3
|
|
5
4
|
class CouchRest
|
6
5
|
class Database
|
@@ -11,6 +10,10 @@ class CouchRest
|
|
11
10
|
@root = "#{host}/#{name}"
|
12
11
|
end
|
13
12
|
|
13
|
+
def to_s
|
14
|
+
@root
|
15
|
+
end
|
16
|
+
|
14
17
|
def documents params = nil
|
15
18
|
url = CouchRest.paramify_url "#{@root}/_all_docs", params
|
16
19
|
CouchRest.get url
|
data/lib/file_manager.rb
ADDED
@@ -0,0 +1,396 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
class CouchRest
|
4
|
+
class FileManager
|
5
|
+
attr_reader :db
|
6
|
+
attr_accessor :loud
|
7
|
+
|
8
|
+
LANGS = {"rb" => "ruby", "js" => "javascript"}
|
9
|
+
MIMES = {
|
10
|
+
"html" => "text/html",
|
11
|
+
"htm" => "text/html",
|
12
|
+
"png" => "image/png",
|
13
|
+
"css" => "text/css",
|
14
|
+
"js" => "test/javascript"
|
15
|
+
}
|
16
|
+
def initialize(dbname, host="http://localhost:5984")
|
17
|
+
@db = CouchRest.new(host).database(dbname)
|
18
|
+
end
|
19
|
+
|
20
|
+
def push_directory(push_dir, docid=nil)
|
21
|
+
docid ||= push_dir.split('/').reverse.find{|part|!part.empty?}
|
22
|
+
|
23
|
+
pushfiles = Dir["#{push_dir}/**/*.*"].collect do |f|
|
24
|
+
{f.split("#{push_dir}/").last => open(f).read}
|
25
|
+
end
|
26
|
+
|
27
|
+
return if pushfiles.empty?
|
28
|
+
|
29
|
+
@attachments = {}
|
30
|
+
@signatures = {}
|
31
|
+
pushfiles.each do |file|
|
32
|
+
name = file.keys.first
|
33
|
+
value = file.values.first
|
34
|
+
@signatures[name] = md5(value)
|
35
|
+
|
36
|
+
@attachments[name] = {
|
37
|
+
"data" => value,
|
38
|
+
"content_type" => MIMES[name.split('.').last]
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
doc = @db.get(docid) rescue nil
|
43
|
+
|
44
|
+
unless doc
|
45
|
+
say "creating #{docid}"
|
46
|
+
@db.save({"_id" => docid, "_attachments" => @attachments, "signatures" => @signatures})
|
47
|
+
return
|
48
|
+
end
|
49
|
+
|
50
|
+
# remove deleted docs
|
51
|
+
to_be_removed = doc["signatures"].keys.select do |d|
|
52
|
+
!pushfiles.collect{|p| p.keys.first}.include?(d)
|
53
|
+
end
|
54
|
+
|
55
|
+
to_be_removed.each do |p|
|
56
|
+
say "deleting #{p}"
|
57
|
+
doc["signatures"].delete(p)
|
58
|
+
doc["_attachments"].delete(p)
|
59
|
+
end
|
60
|
+
|
61
|
+
# update existing docs:
|
62
|
+
doc["signatures"].each do |path, sig|
|
63
|
+
if (@signatures[path] == sig)
|
64
|
+
say "no change to #{path}. skipping..."
|
65
|
+
else
|
66
|
+
say "replacing #{path}"
|
67
|
+
doc["signatures"][path] = md5(@attachments[path]["data"])
|
68
|
+
doc["_attachments"][path].delete("stub")
|
69
|
+
doc["_attachments"][path].delete("length")
|
70
|
+
doc["_attachments"][path]["data"] = @attachments[path]["data"]
|
71
|
+
doc["_attachments"][path].merge!({"data" => @attachments[path]["data"]} )
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# add in new files
|
77
|
+
new_files = pushfiles.select{|d| !doc["signatures"].keys.include?( d.keys.first) }
|
78
|
+
|
79
|
+
new_files.each do |f|
|
80
|
+
say "creating #{f}"
|
81
|
+
path = f.keys.first
|
82
|
+
content = f.values.first
|
83
|
+
doc["signatures"][path] = md5(content)
|
84
|
+
|
85
|
+
doc["_attachments"][path] = {
|
86
|
+
"data" => content,
|
87
|
+
"content_type" => @content_types[path.split('.').last]
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
begin
|
92
|
+
@db.save(doc)
|
93
|
+
rescue Exception => e
|
94
|
+
say e.message
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def push_views(view_dir)
|
99
|
+
designs = {}
|
100
|
+
|
101
|
+
Dir["#{view_dir}/**/*.*"].each do |design_doc|
|
102
|
+
design_doc_parts = design_doc.split('/')
|
103
|
+
next if /^lib\..*$/.match design_doc_parts.last
|
104
|
+
pre_normalized_view_name = design_doc_parts.last.split("-")
|
105
|
+
view_name = pre_normalized_view_name[0..pre_normalized_view_name.length-2].join("-")
|
106
|
+
|
107
|
+
folder = design_doc_parts[-2]
|
108
|
+
|
109
|
+
designs[folder] ||= {}
|
110
|
+
designs[folder]["views"] ||= {}
|
111
|
+
design_lang = design_doc_parts.last.split(".").last
|
112
|
+
designs[folder]["language"] ||= LANGS[design_lang]
|
113
|
+
|
114
|
+
libs = ""
|
115
|
+
Dir["#{view_dir}/lib.#{design_lang}"].collect do |global_lib|
|
116
|
+
libs << open(global_lib).read
|
117
|
+
libs << "\n"
|
118
|
+
end
|
119
|
+
Dir["#{view_dir}/#{folder}/lib.#{design_lang}"].collect do |global_lib|
|
120
|
+
libs << open(global_lib).read
|
121
|
+
libs << "\n"
|
122
|
+
end
|
123
|
+
if design_doc_parts.last =~ /-map/
|
124
|
+
designs[folder]["views"]["#{view_name}-map"] ||= {}
|
125
|
+
|
126
|
+
designs[folder]["views"]["#{view_name}-map"]["map"] = read(design_doc, libs)
|
127
|
+
|
128
|
+
designs[folder]["views"]["#{view_name}-reduce"] ||= {}
|
129
|
+
designs[folder]["views"]["#{view_name}-reduce"]["map"] = read(design_doc, libs)
|
130
|
+
end
|
131
|
+
|
132
|
+
if design_doc_parts.last =~ /-reduce/
|
133
|
+
designs[folder]["views"]["#{view_name}-reduce"] ||= {}
|
134
|
+
|
135
|
+
designs[folder]["views"]["#{view_name}-reduce"]["reduce"] = read(design_doc, libs)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# cleanup empty maps and reduces
|
140
|
+
designs.each do |name, props|
|
141
|
+
props["views"].each do |view, funcs|
|
142
|
+
next unless view.include?("reduce")
|
143
|
+
props["views"].delete(view) unless funcs.keys.include?("reduce")
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
designs.each do |k,v|
|
148
|
+
create_or_update("_design/#{k}", v)
|
149
|
+
end
|
150
|
+
|
151
|
+
designs
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def say words
|
158
|
+
puts words if @loud
|
159
|
+
end
|
160
|
+
|
161
|
+
def md5 string
|
162
|
+
Digest::MD5.hexdigest(string)
|
163
|
+
end
|
164
|
+
|
165
|
+
def read(file, libs=nil)
|
166
|
+
st = open(file).read
|
167
|
+
st.sub!(/(\/\/|#)include-lib/,libs) if libs
|
168
|
+
st
|
169
|
+
end
|
170
|
+
|
171
|
+
def create_or_update(id, fields)
|
172
|
+
existing = @db.get(id) rescue nil
|
173
|
+
|
174
|
+
if existing
|
175
|
+
updated = fields.merge({"_id" => id, "_rev" => existing["_rev"]})
|
176
|
+
if existing != updated
|
177
|
+
say "replacing #{id}"
|
178
|
+
db.save(updated)
|
179
|
+
else
|
180
|
+
say "skipping #{id}"
|
181
|
+
end
|
182
|
+
else
|
183
|
+
say "creating #{id}"
|
184
|
+
db.save(fields.merge({"_id" => id}))
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
__END__
|
192
|
+
|
193
|
+
|
194
|
+
|
195
|
+
|
196
|
+
# parse the file structure to load the public files, controllers, and views into a hash with the right shape for coucdb
|
197
|
+
couch = {}
|
198
|
+
|
199
|
+
couch["public"] = Dir["#{File.expand_path(File.dirname("."))}/public/**/*.*"].collect do |f|
|
200
|
+
{f.split("public/").last => open(f).read}
|
201
|
+
end
|
202
|
+
|
203
|
+
couch["controllers"] = {}
|
204
|
+
Dir["#{File.expand_path(File.dirname("."))}/app/controllers/**/*.*"].collect do |c|
|
205
|
+
path_parts = c.split("/")
|
206
|
+
|
207
|
+
controller_name = path_parts[path_parts.length - 2]
|
208
|
+
action_name = path_parts[path_parts.length - 1].split(".").first
|
209
|
+
|
210
|
+
couch["controllers"][controller_name] ||= {"actions" => {}}
|
211
|
+
couch["controllers"][controller_name]["actions"][action_name] = open(c).read
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
couch["designs"] = {}
|
216
|
+
Dir["#{File.expand_path(File.dirname("."))}/app/views/**/*.*"].collect do |design_doc|
|
217
|
+
design_doc_parts = design_doc.split('/')
|
218
|
+
pre_normalized_view_name = design_doc_parts.last.split("-")
|
219
|
+
view_name = pre_normalized_view_name[0..pre_normalized_view_name.length-2].join("-")
|
220
|
+
|
221
|
+
folder = design_doc.split("app/views").last.split("/")[1]
|
222
|
+
|
223
|
+
couch["designs"][folder] ||= {}
|
224
|
+
couch["designs"][folder]["views"] ||= {}
|
225
|
+
couch["designs"][folder]["language"] ||= LANGS[design_doc_parts.last.split(".").last]
|
226
|
+
|
227
|
+
if design_doc_parts.last =~ /-map/
|
228
|
+
couch["designs"][folder]["views"]["#{view_name}-map"] ||= {}
|
229
|
+
|
230
|
+
couch["designs"][folder]["views"]["#{view_name}-map"]["map"] = open(design_doc).read
|
231
|
+
|
232
|
+
couch["designs"][folder]["views"]["#{view_name}-reduce"] ||= {}
|
233
|
+
couch["designs"][folder]["views"]["#{view_name}-reduce"]["map"] = open(design_doc).read
|
234
|
+
end
|
235
|
+
|
236
|
+
if design_doc_parts.last =~ /-reduce/
|
237
|
+
couch["designs"][folder]["views"]["#{view_name}-reduce"] ||= {}
|
238
|
+
|
239
|
+
couch["designs"][folder]["views"]["#{view_name}-reduce"]["reduce"] = open(design_doc).read
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# cleanup empty maps and reduces
|
244
|
+
couch["designs"].each do |name, props|
|
245
|
+
props["views"].delete("#{name}-reduce") unless props["views"]["#{name}-reduce"].keys.include?("reduce")
|
246
|
+
end
|
247
|
+
|
248
|
+
# parsing done, begin posting
|
249
|
+
|
250
|
+
# connect to couchdb
|
251
|
+
cr = CouchRest.new("http://localhost:5984")
|
252
|
+
@db = cr.database(DBNAME)
|
253
|
+
|
254
|
+
def create_or_update(id, fields)
|
255
|
+
existing = get(id)
|
256
|
+
|
257
|
+
if existing
|
258
|
+
updated = fields.merge({"_id" => id, "_rev" => existing["_rev"]})
|
259
|
+
else
|
260
|
+
puts "saving #{id}"
|
261
|
+
save(fields.merge({"_id" => id}))
|
262
|
+
end
|
263
|
+
|
264
|
+
if existing == updated
|
265
|
+
puts "no change to #{id}. skipping..."
|
266
|
+
else
|
267
|
+
puts "replacing #{id}"
|
268
|
+
save(updated)
|
269
|
+
end
|
270
|
+
|
271
|
+
end
|
272
|
+
|
273
|
+
def get(id)
|
274
|
+
doc = handle_errors do
|
275
|
+
@db.get(id)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def save(doc)
|
280
|
+
handle_errors do
|
281
|
+
@db.save(doc)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def handle_errors(&block)
|
286
|
+
begin
|
287
|
+
yield
|
288
|
+
rescue Exception => e
|
289
|
+
# puts e.message
|
290
|
+
nil
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
|
295
|
+
if todo.include? "views"
|
296
|
+
puts "posting views into CouchDB"
|
297
|
+
couch["designs"].each do |k,v|
|
298
|
+
create_or_update("_design/#{k}", v)
|
299
|
+
end
|
300
|
+
puts
|
301
|
+
end
|
302
|
+
|
303
|
+
if todo.include? "controllers"
|
304
|
+
puts "posting controllers into CouchDB"
|
305
|
+
couch["controllers"].each do |k,v|
|
306
|
+
create_or_update("controller/#{k}", v)
|
307
|
+
end
|
308
|
+
puts
|
309
|
+
end
|
310
|
+
|
311
|
+
|
312
|
+
if todo.include? "public"
|
313
|
+
puts "posting public docs into CouchDB"
|
314
|
+
|
315
|
+
if couch["public"].empty?
|
316
|
+
puts "no docs in public"; exit
|
317
|
+
end
|
318
|
+
|
319
|
+
@content_types = {
|
320
|
+
"html" => "text/html",
|
321
|
+
"htm" => "text/html",
|
322
|
+
"png" => "image/png",
|
323
|
+
"css" => "text/css",
|
324
|
+
"js" => "test/javascript"
|
325
|
+
}
|
326
|
+
|
327
|
+
def md5 string
|
328
|
+
Digest::MD5.hexdigest(string)
|
329
|
+
end
|
330
|
+
|
331
|
+
@attachments = {}
|
332
|
+
@signatures = {}
|
333
|
+
couch["public"].each do |doc|
|
334
|
+
@signatures[doc.keys.first] = md5(doc.values.first)
|
335
|
+
|
336
|
+
@attachments[doc.keys.first] = {
|
337
|
+
"data" => doc.values.first,
|
338
|
+
"content_type" => @content_types[doc.keys.first.split('.').last]
|
339
|
+
}
|
340
|
+
end
|
341
|
+
|
342
|
+
doc = get("public")
|
343
|
+
|
344
|
+
unless doc
|
345
|
+
puts "creating public"
|
346
|
+
@db.save({"_id" => "public", "_attachments" => @attachments, "signatures" => @signatures})
|
347
|
+
exit
|
348
|
+
end
|
349
|
+
|
350
|
+
# remove deleted docs
|
351
|
+
to_be_removed = doc["signatures"].keys.select{|d| !couch["public"].collect{|p| p.keys.first}.include?(d) }
|
352
|
+
|
353
|
+
to_be_removed.each do |p|
|
354
|
+
puts "deleting #{p}"
|
355
|
+
doc["signatures"].delete(p)
|
356
|
+
doc["_attachments"].delete(p)
|
357
|
+
end
|
358
|
+
|
359
|
+
# update existing docs:
|
360
|
+
doc["signatures"].each do |path, sig|
|
361
|
+
if (@signatures[path] == sig)
|
362
|
+
puts "no change to #{path}. skipping..."
|
363
|
+
else
|
364
|
+
puts "replacing #{path}"
|
365
|
+
doc["signatures"][path] = md5(@attachments[path]["data"])
|
366
|
+
doc["_attachments"][path].delete("stub")
|
367
|
+
doc["_attachments"][path].delete("length")
|
368
|
+
doc["_attachments"][path]["data"] = @attachments[path]["data"]
|
369
|
+
doc["_attachments"][path].merge!({"data" => @attachments[path]["data"]} )
|
370
|
+
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
# add in new files
|
375
|
+
new_files = couch["public"].select{|d| !doc["signatures"].keys.include?( d.keys.first) }
|
376
|
+
|
377
|
+
new_files.each do |f|
|
378
|
+
puts "creating #{f}"
|
379
|
+
path = f.keys.first
|
380
|
+
content = f.values.first
|
381
|
+
doc["signatures"][path] = md5(content)
|
382
|
+
|
383
|
+
doc["_attachments"][path] = {
|
384
|
+
"data" => content,
|
385
|
+
"content_type" => @content_types[path.split('.').last]
|
386
|
+
}
|
387
|
+
end
|
388
|
+
|
389
|
+
begin
|
390
|
+
@db.save(doc)
|
391
|
+
rescue Exception => e
|
392
|
+
puts e.message
|
393
|
+
end
|
394
|
+
|
395
|
+
puts
|
396
|
+
end
|
data/lib/pager.rb
CHANGED
@@ -1,14 +1,3 @@
|
|
1
|
-
# paginate though 'gcharts/mp3-trk-dom-map' view and save
|
2
|
-
|
3
|
-
# get 1000 records
|
4
|
-
# truncate so that the key of the last record is not included in the page
|
5
|
-
# that key will be the first of the next page
|
6
|
-
# (if the last key equals the first key, up the page size)
|
7
|
-
# group the results by key
|
8
|
-
# yield the group
|
9
|
-
|
10
|
-
require File.dirname(__FILE__) + '/couchrest'
|
11
|
-
|
12
1
|
module Enumerable
|
13
2
|
def group_by
|
14
3
|
inject({}) do |grouped, element|
|
data/spec/couchrest_spec.rb
CHANGED
@@ -16,19 +16,6 @@ describe CouchRest do
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
describe "tested against the current CouchDB svn revision" do
|
20
|
-
it "should be up to date" do
|
21
|
-
v = @cr.info["version"]
|
22
|
-
if /incubating/.match(v)
|
23
|
-
v.should include('0.8.0')
|
24
|
-
else
|
25
|
-
vi = v.split(/a/).pop.to_i
|
26
|
-
vi.should be >= 661484 # versions older than this will likely fail many specs
|
27
|
-
vi.should be <= 663797 # versions newer than this haven't been tried
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
19
|
describe "getting info" do
|
33
20
|
it "should list databases" do
|
34
21
|
@cr.databases.should be_an_instance_of(Array)
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe CouchRest::FileManager do
|
4
|
+
before(:all) do
|
5
|
+
@cr = CouchRest.new(COUCHHOST)
|
6
|
+
@db = @cr.database(TESTDB)
|
7
|
+
@db.delete! rescue nil
|
8
|
+
@db = @cr.create_db(TESTDB) rescue nil
|
9
|
+
end
|
10
|
+
it "should initialize" do
|
11
|
+
@fm = CouchRest::FileManager.new(TESTDB)
|
12
|
+
@fm.should_not be_nil
|
13
|
+
end
|
14
|
+
it "should require a db name" do
|
15
|
+
lambda{CouchRest::FileManager.new}.should raise_error
|
16
|
+
end
|
17
|
+
it "should accept a db name" do
|
18
|
+
@fm = CouchRest::FileManager.new(TESTDB, 'http://localhost')
|
19
|
+
@fm.db.name.should == TESTDB
|
20
|
+
end
|
21
|
+
it "should default to localhost couchdb" do
|
22
|
+
@fm = CouchRest::FileManager.new(TESTDB)
|
23
|
+
@fm.db.host.should == 'http://localhost:5984'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe CouchRest::FileManager, "pushing views" do
|
28
|
+
before(:all) do
|
29
|
+
@cr = CouchRest.new(COUCHHOST)
|
30
|
+
@db = @cr.database(TESTDB)
|
31
|
+
@db.delete! rescue nil
|
32
|
+
@db = @cr.create_db(TESTDB) rescue nil
|
33
|
+
|
34
|
+
@fm = CouchRest::FileManager.new(TESTDB, COUCHHOST)
|
35
|
+
@view_dir = File.dirname(__FILE__) + '/fixtures/views'
|
36
|
+
ds = @fm.push_views(@view_dir)
|
37
|
+
@design = @db.get("_design/test_view")
|
38
|
+
end
|
39
|
+
it "should create a design document for each folder" do
|
40
|
+
@design["views"].should_not be_nil
|
41
|
+
end
|
42
|
+
it "should push a map and reduce view" do
|
43
|
+
@design["views"]["test-map"].should_not be_nil
|
44
|
+
@design["views"]["test-reduce"].should_not be_nil
|
45
|
+
end
|
46
|
+
it "should push a map only view" do
|
47
|
+
@design["views"]["only-map"].should_not be_nil
|
48
|
+
@design["views"]["only-reduce"].should be_nil
|
49
|
+
end
|
50
|
+
it "should include library files" do
|
51
|
+
@design["views"]["only-map"]["map"].should include("globalLib")
|
52
|
+
@design["views"]["only-map"]["map"].should include("justThisView")
|
53
|
+
end
|
54
|
+
it "should not create extra design docs" do
|
55
|
+
docs = @db.documents(:startkey => '_design', :endkey => '_design/ZZZZZZ')
|
56
|
+
docs['total_rows'].should == 1
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe CouchRest::FileManager, "pushing a directory with id" do
|
61
|
+
before(:all) do
|
62
|
+
@cr = CouchRest.new(COUCHHOST)
|
63
|
+
@db = @cr.database(TESTDB)
|
64
|
+
@db.delete! rescue nil
|
65
|
+
@db = @cr.create_db(TESTDB) rescue nil
|
66
|
+
|
67
|
+
@fm = CouchRest::FileManager.new(TESTDB, COUCHHOST)
|
68
|
+
@push_dir = File.dirname(__FILE__) + '/fixtures/attachments'
|
69
|
+
ds = @fm.push_directory(@push_dir, 'attached')
|
70
|
+
end
|
71
|
+
it "should create a document for the folder" do
|
72
|
+
@db.get("attached")
|
73
|
+
end
|
74
|
+
it "should make attachments" do
|
75
|
+
doc = @db.get("attached")
|
76
|
+
doc["_attachments"]["test.html"].should_not be_nil
|
77
|
+
end
|
78
|
+
it "should set the content type" do
|
79
|
+
doc = @db.get("attached")
|
80
|
+
doc["_attachments"]["test.html"]["content_type"].should == "text/html"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe CouchRest::FileManager, "pushing a directory without id" do
|
85
|
+
before(:all) do
|
86
|
+
@cr = CouchRest.new(COUCHHOST)
|
87
|
+
@db = @cr.database(TESTDB)
|
88
|
+
@db.delete! rescue nil
|
89
|
+
@db = @cr.create_db(TESTDB) rescue nil
|
90
|
+
|
91
|
+
@fm = CouchRest::FileManager.new(TESTDB, COUCHHOST)
|
92
|
+
@push_dir = File.dirname(__FILE__) + '/fixtures/attachments'
|
93
|
+
ds = @fm.push_directory(@push_dir)
|
94
|
+
end
|
95
|
+
it "should use the dirname" do
|
96
|
+
doc = @db.get("attachments")
|
97
|
+
doc["_attachments"]["test.html"].should_not be_nil
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe CouchRest::FileManager, "pushing a directory/ without id" do
|
102
|
+
before(:all) do
|
103
|
+
@cr = CouchRest.new(COUCHHOST)
|
104
|
+
@db = @cr.database(TESTDB)
|
105
|
+
@db.delete! rescue nil
|
106
|
+
@db = @cr.create_db(TESTDB) rescue nil
|
107
|
+
|
108
|
+
@fm = CouchRest::FileManager.new(TESTDB, COUCHHOST)
|
109
|
+
@push_dir = File.dirname(__FILE__) + '/fixtures/attachments/'
|
110
|
+
ds = @fm.push_directory(@push_dir)
|
111
|
+
end
|
112
|
+
it "should use the dirname" do
|
113
|
+
doc = @db.get("attachments")
|
114
|
+
doc["_attachments"]["test.html"].should_not be_nil
|
115
|
+
end
|
116
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jchris-couchrest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- J. Chris Anderson
|
8
|
+
- Greg Borenstein
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
12
|
|
12
|
-
date: 2008-
|
13
|
+
date: 2008-08-03 00:00:00 -07:00
|
13
14
|
default_executable:
|
14
15
|
dependencies:
|
15
16
|
- !ruby/object:Gem::Dependency
|
@@ -32,23 +33,27 @@ dependencies:
|
|
32
33
|
version:
|
33
34
|
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.
|
34
35
|
email: jchris@grabb.it
|
35
|
-
executables:
|
36
|
-
|
36
|
+
executables:
|
37
|
+
- couchview
|
38
|
+
- couchdir
|
37
39
|
extensions: []
|
38
40
|
|
39
41
|
extra_rdoc_files: []
|
40
42
|
|
41
43
|
files:
|
42
44
|
- lib/couchrest.rb
|
45
|
+
- lib/couch_rest.rb
|
43
46
|
- lib/database.rb
|
44
47
|
- lib/pager.rb
|
48
|
+
- lib/file_manager.rb
|
45
49
|
- Rakefile
|
46
50
|
- README
|
47
|
-
-
|
48
|
-
-
|
51
|
+
- bin/couchdir
|
52
|
+
- bin/couchview
|
49
53
|
- spec/couchrest_spec.rb
|
50
54
|
- spec/database_spec.rb
|
51
55
|
- spec/pager_spec.rb
|
56
|
+
- spec/file_manager_spec.rb
|
52
57
|
- spec/spec_helper.rb
|
53
58
|
has_rdoc: false
|
54
59
|
homepage: http://github.com/jchris/couchrest
|
data/script/couchdir
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
unless ARGV.length >= 2
|
4
|
-
puts "usage: couchdir path/to/directory db-name"
|
5
|
-
exit
|
6
|
-
end
|
7
|
-
|
8
|
-
dirname = ARGV[0].sub(/\/$/,'')
|
9
|
-
dbname = ARGV[1]
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
puts "Shoving #{dirname} into #{dbname}."
|
14
|
-
|
15
|
-
require File.expand_path(File.dirname(__FILE__)) + '/../couchrest'
|
16
|
-
require 'fileutils'
|
17
|
-
|
18
|
-
cr = CouchRest.new("http://localhost:5984")
|
19
|
-
@db = cr.database(dbname)
|
20
|
-
|
21
|
-
@content_types = {
|
22
|
-
"html" => "text/html",
|
23
|
-
"htm" => "text/html",
|
24
|
-
"png" => "image/png",
|
25
|
-
"css" => "text/css"
|
26
|
-
}
|
27
|
-
|
28
|
-
files = Dir.glob(File.join(dirname,"**","*"))
|
29
|
-
attachments = {}
|
30
|
-
files.each do |filename|
|
31
|
-
content = open(filename).read
|
32
|
-
aname = filename.split('/')
|
33
|
-
aname.shift
|
34
|
-
aname = aname.join('/')
|
35
|
-
attachments[aname] = {
|
36
|
-
"data" => content,
|
37
|
-
"content_type" => @content_types[aname.split('.').last]
|
38
|
-
}
|
39
|
-
end
|
40
|
-
|
41
|
-
puts attachments.keys.inspect
|
42
|
-
|
43
|
-
begin
|
44
|
-
doc = @db.get(dirname)
|
45
|
-
rescue RestClient::Request::RequestFailed
|
46
|
-
doc = nil
|
47
|
-
end
|
48
|
-
|
49
|
-
# puts "get: #{doc.inspect}"
|
50
|
-
|
51
|
-
if doc
|
52
|
-
doc["_attachments"] = attachments
|
53
|
-
else
|
54
|
-
doc = {
|
55
|
-
"_id" => dirname,
|
56
|
-
"_attachments" => attachments
|
57
|
-
}
|
58
|
-
end
|
59
|
-
|
60
|
-
# puts "saving: #{doc.inspect}"
|
61
|
-
@db.save(doc)
|
62
|
-
puts "saved"
|
data/script/couchview
DELETED
@@ -1,162 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
commands = %w{pull push}
|
4
|
-
|
5
|
-
command = ARGV[0]
|
6
|
-
|
7
|
-
if !commands.include?(command)
|
8
|
-
puts <<-USAGE
|
9
|
-
Usage: couchview (pull|push) my-database-name
|
10
|
-
For help on pull and push run script/views (pull|push) without a database name.
|
11
|
-
USAGE
|
12
|
-
exit
|
13
|
-
end
|
14
|
-
|
15
|
-
if ARGV.length == 1
|
16
|
-
case command
|
17
|
-
when "pull"
|
18
|
-
puts <<-PULL
|
19
|
-
couchview pull my-database-name
|
20
|
-
|
21
|
-
I will automagically create a "views" directory in your current working directory if none exists.
|
22
|
-
Then I copy the design documents and views into a directory structure like:
|
23
|
-
|
24
|
-
./views/my-design-doc/view-name-map.js
|
25
|
-
./views/my-design-doc/view-name-reduce.js
|
26
|
-
|
27
|
-
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.
|
28
|
-
|
29
|
-
PULL
|
30
|
-
when "push"
|
31
|
-
puts <<-PUSH
|
32
|
-
couchview push my-database-name
|
33
|
-
|
34
|
-
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.
|
35
|
-
|
36
|
-
./views/my-design-doc/view-name-map.js
|
37
|
-
./views/my-design-doc/view-name-reduce.js
|
38
|
-
|
39
|
-
Pushed to =>
|
40
|
-
|
41
|
-
http://localhost:5984/my-database-name/_design/my-design-doc
|
42
|
-
{
|
43
|
-
"views" : {
|
44
|
-
"view-name-map" : {
|
45
|
-
"map" : "### contents of view-name-map.js ###"
|
46
|
-
},
|
47
|
-
"view-name-reduce" : {
|
48
|
-
"map" : "### contents of view-name-map.js ###",
|
49
|
-
"reduce" : "### contents of view-name-reduce.js ###"
|
50
|
-
},
|
51
|
-
}
|
52
|
-
}
|
53
|
-
PUSH
|
54
|
-
end
|
55
|
-
exit
|
56
|
-
end
|
57
|
-
|
58
|
-
dbname = ARGV[1]
|
59
|
-
dirname = ARGV[2] || "views"
|
60
|
-
|
61
|
-
puts "Running #{command} on #{dbname} from directory #{dirname}."
|
62
|
-
|
63
|
-
require File.expand_path(File.dirname(__FILE__)) + '/../couchrest'
|
64
|
-
require 'fileutils'
|
65
|
-
|
66
|
-
module Enumerable
|
67
|
-
def group_by
|
68
|
-
inject({}) do |groups, element|
|
69
|
-
(groups[yield(element)] ||= []) << element
|
70
|
-
groups
|
71
|
-
end
|
72
|
-
end if RUBY_VERSION < '1.9'
|
73
|
-
end
|
74
|
-
|
75
|
-
# connect to couchdb
|
76
|
-
cr = CouchRest.new("http://localhost:5984")
|
77
|
-
db = cr.database(dbname)
|
78
|
-
|
79
|
-
def readjs(file, libs=nil)
|
80
|
-
st = open(file).read
|
81
|
-
st.sub!(/\/\/include-lib/,libs) if libs
|
82
|
-
st
|
83
|
-
end
|
84
|
-
|
85
|
-
case command
|
86
|
-
when "push" # files to views
|
87
|
-
views = {}
|
88
|
-
viewfiles = Dir.glob(File.join(dirname,"**","*.js")) # todo support non-js views
|
89
|
-
libfiles = viewfiles.select{|f|/lib\.js/.match(f)}
|
90
|
-
libs = open(libfiles[0]).read if libfiles[0]
|
91
|
-
all = (viewfiles-libfiles).collect do |file|
|
92
|
-
fileparts = file.split('/')
|
93
|
-
filename = /(\w.*)-(\w.*)\.js/.match file.split('/').pop
|
94
|
-
design = fileparts[1]
|
95
|
-
view = filename[1]
|
96
|
-
func = filename[2]
|
97
|
-
path = file
|
98
|
-
[design,view,func,path]
|
99
|
-
end
|
100
|
-
designs = all.group_by{|f|f[0]}
|
101
|
-
designs.each do |design,parts|
|
102
|
-
# puts "replace _design/#{design}? (enter to proceed, 'n' to skip)"
|
103
|
-
# rep = $stdin.gets.chomp
|
104
|
-
# next if rep == 'n'
|
105
|
-
dviews = {}
|
106
|
-
parts.group_by{|p|p[1]}.each do |view,fs|
|
107
|
-
fs.each do |f|
|
108
|
-
dviews["#{view}-reduce"] ||= {}
|
109
|
-
dviews["#{view}-reduce"][f[2]] = readjs(f.last,libs)
|
110
|
-
end
|
111
|
-
dviews["#{view}-map"] = {'map' => dviews["#{view}-reduce"]['map']}
|
112
|
-
dviews.delete("#{view}-reduce") unless dviews["#{view}-reduce"]["reduce"]
|
113
|
-
end
|
114
|
-
# save them to the db
|
115
|
-
begin
|
116
|
-
view = db.get("_design/#{design}")
|
117
|
-
rescue RestClient::Request::RequestFailed
|
118
|
-
view = nil
|
119
|
-
end
|
120
|
-
if (view && view['views'] == dviews)
|
121
|
-
puts "no change to _design/#{design}. skipping..."
|
122
|
-
else
|
123
|
-
puts "replacing _design/#{design}"
|
124
|
-
db.delete(view) rescue nil
|
125
|
-
db.save({
|
126
|
-
"_id" => "_design/#{design}",
|
127
|
-
:views => dviews
|
128
|
-
})
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
when "pull" # views to files
|
133
|
-
ds = db.documents(:startkey => '_design/', :endkey => '_design/ZZZZZZZZZ')
|
134
|
-
ds['rows'].collect{|r|r['id']}.each do |id|
|
135
|
-
puts directory = id.split('/').last
|
136
|
-
FileUtils.mkdir_p(File.join("views",directory))
|
137
|
-
views = db.get(id)['views']
|
138
|
-
|
139
|
-
vgroups = views.keys.group_by{|k|k.sub(/\-(map|reduce)$/,'')}
|
140
|
-
vgroups.each do|g,vs|
|
141
|
-
mapname = vs.find {|v|views[v]["map"]}
|
142
|
-
if mapname
|
143
|
-
# save map
|
144
|
-
mapfunc = views[mapname]["map"]
|
145
|
-
mapfile = File.join(dirname,directory,"#{g}-map.js") # todo support non-js views
|
146
|
-
File.open(mapfile,'w') do |f|
|
147
|
-
f.write mapfunc
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
reducename = vs.find {|v|views[v]["reduce"]}
|
152
|
-
if reducename
|
153
|
-
# save reduce
|
154
|
-
reducefunc = views[reducename]["reduce"]
|
155
|
-
reducefile = File.join(dirname,directory,"#{g}-reduce.js") # todo support non-js views
|
156
|
-
File.open(reducefile,'w') do |f|
|
157
|
-
f.write reducefunc
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|