tatami 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Rakefile +9 -0
- data/couches/cars/cars.coffee +39 -0
- data/couches/cars/erlang_design.erl +9 -0
- data/couches/flot/flot.coffee +30 -0
- data/lib/tatami.rb +5 -5
- data/lib/tatami/app.rb +20 -0
- data/lib/tatami/couch.rb +322 -0
- data/lib/tatami/flot.rb +20 -0
- data/lib/tatami/tatami.rb +5 -0
- data/lib/tatami/version.rb +1 -1
- data/test/couch_test.rb +213 -0
- data/test/fixtures/d3/css/app.css +1 -0
- data/test/fixtures/d3/index.html +12 -0
- data/test/fixtures/d3/js/app.js +6 -0
- data/test/fixtures/flot.yml +23 -0
- data/test/flot_test.rb +45 -0
- data/test/flot_views.rb +28 -0
- data/test/view_test.rb +42 -0
- metadata +25 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a2dbb07adb70e19071bbd27c4fed18682cddd1e
|
4
|
+
data.tar.gz: 575192e6a8d4dbb989085f9ccabf23cd3ef670d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4abd3c393ca07b7fb0acaea1c7c81cadb9a07306e3f3150a24f40a131cb9ab8c68776fee6f36700fc34ef936d3476642885d94280f75fec25e16be17639bec9
|
7
|
+
data.tar.gz: 32bb2cdf1e33aff92f588e1caf8c619ebdf62a77f9888e5743c641c3378b368807fb3fdc679cc7e1c3a66f66ac529650d49515c6c12a2fc1ac765013d5b1f1eb
|
data/Rakefile
CHANGED
@@ -0,0 +1,39 @@
|
|
1
|
+
# views simple map
|
2
|
+
(doc) ->
|
3
|
+
emit "a", doc.a
|
4
|
+
emit "b", doc.a
|
5
|
+
|
6
|
+
# views simple reduce
|
7
|
+
# keys = [[key1,docid1],..] when rereduce,else null
|
8
|
+
(keys, values, rereduce) ->
|
9
|
+
sum(values)
|
10
|
+
|
11
|
+
# views count_sum map
|
12
|
+
(doc) ->
|
13
|
+
emit doc.date, [1,doc.count]
|
14
|
+
|
15
|
+
# views count_sum reduce
|
16
|
+
(keys, values, rereduce) ->
|
17
|
+
log values
|
18
|
+
s1 = 0
|
19
|
+
s2 = 0
|
20
|
+
for v in values
|
21
|
+
s1 = s1 + v[0]
|
22
|
+
s2 = s2 + v[1]
|
23
|
+
[s1,s2]
|
24
|
+
|
25
|
+
# views first_of_group map
|
26
|
+
(doc) ->
|
27
|
+
emit doc.date, doc.count
|
28
|
+
|
29
|
+
# views first_of_group reduce
|
30
|
+
(keys, values, rereduce) ->
|
31
|
+
values[0]
|
32
|
+
|
33
|
+
# views perf map
|
34
|
+
(doc) ->
|
35
|
+
emit null, 1
|
36
|
+
|
37
|
+
# views perf reduce
|
38
|
+
(keys, values, rereduce) ->
|
39
|
+
sum(values)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# views count map
|
2
|
+
(doc) ->
|
3
|
+
time = doc.time
|
4
|
+
typ = doc.typ
|
5
|
+
for v1, atts of doc
|
6
|
+
if v1[0] != "_" and !(v1 in ['typ','time'])
|
7
|
+
for v2, value of atts
|
8
|
+
emit "#{typ}/#{v1}/#{v2}", 1
|
9
|
+
|
10
|
+
# views count reduce
|
11
|
+
(keys, values, rereduce) ->
|
12
|
+
sum(values)
|
13
|
+
|
14
|
+
# views typ12 map
|
15
|
+
(doc) ->
|
16
|
+
time = doc.time
|
17
|
+
typ = doc.typ
|
18
|
+
for v1, atts of doc
|
19
|
+
if v1[0] != "_" and !(v1 in ['typ','time'])
|
20
|
+
for v2, value of atts
|
21
|
+
emit [typ,v1,v2], [time,value]
|
22
|
+
|
23
|
+
# lists iter
|
24
|
+
(head, req) ->
|
25
|
+
start
|
26
|
+
"headers":
|
27
|
+
"Content-Type": "text/html"
|
28
|
+
loop
|
29
|
+
return unless r = getRow()
|
30
|
+
send ",[#{r.value}]"
|
data/lib/tatami.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module Tatami
|
4
|
-
# Your code goes here...
|
5
|
-
end
|
1
|
+
require_relative "tatami/version"
|
2
|
+
require_relative 'tatami/couch'
|
3
|
+
module Tatami
|
4
|
+
# Your code goes here...
|
5
|
+
end
|
data/lib/tatami/app.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
class Tatami::Couch::App
|
2
|
+
|
3
|
+
def attachments file_path
|
4
|
+
Dir.chdir file_path do
|
5
|
+
Dir["**/*.*"].each_with_object({}) do |path,o|
|
6
|
+
ext = File.extname(path)[1..-1]
|
7
|
+
base64 = [IO.read(path)].pack('m')
|
8
|
+
o[path] = {
|
9
|
+
"content_type" => content_type(ext),
|
10
|
+
"data" => base64
|
11
|
+
}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def content_type ext
|
17
|
+
subtype = ext.sub(/^js$/, 'javascript')
|
18
|
+
"text/#{subtype}"
|
19
|
+
end
|
20
|
+
end
|
data/lib/tatami/couch.rb
ADDED
@@ -0,0 +1,322 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'json/ext'
|
3
|
+
require 'map'
|
4
|
+
module Tatami
|
5
|
+
class Couch
|
6
|
+
def conn
|
7
|
+
@conn ||= Faraday.new(:url => @url) do |faraday|
|
8
|
+
faraday.request :url_encoded # form-encode POST params
|
9
|
+
faraday.response :logger if @opts[:log]
|
10
|
+
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
|
11
|
+
end
|
12
|
+
end
|
13
|
+
# delete and recreate the database
|
14
|
+
def blank!
|
15
|
+
conn.delete "/#{db_env}"
|
16
|
+
conn.put "/#{db_env}"
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :db
|
21
|
+
def db_env()
|
22
|
+
@env ? "#{@db}_#{@env}" : @db
|
23
|
+
end
|
24
|
+
|
25
|
+
# @option opts [Boolean] :log
|
26
|
+
# @option opts [String] :db
|
27
|
+
def initialize db, url='http://localhost:5984', opts={}
|
28
|
+
@env = opts.delete(:env)
|
29
|
+
@db, @url, @opts = db, url, opts
|
30
|
+
end
|
31
|
+
|
32
|
+
def get path, opts={}
|
33
|
+
puts "GET #{path}" if opts[:log]
|
34
|
+
data = conn.get(path).body
|
35
|
+
cast_response data, opts[:format]
|
36
|
+
end
|
37
|
+
|
38
|
+
def cast_response data, format
|
39
|
+
case format
|
40
|
+
when :string
|
41
|
+
data
|
42
|
+
when :object
|
43
|
+
JSON.parse(data)
|
44
|
+
else
|
45
|
+
Map.new JSON.parse(data)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_db_string path
|
50
|
+
conn.get("/#{db_env}#{path}").body
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_db path, opts={}
|
54
|
+
get "/#{db_env}#{path}", opts
|
55
|
+
end
|
56
|
+
|
57
|
+
def verb method, url, body, opts={}
|
58
|
+
puts "#{method.upcase} #{url} #{body.inspect}" if opts[:log]
|
59
|
+
resp = conn.send method, url, JSON.generate(body), 'Content-Type' => 'application/json'
|
60
|
+
cast_response resp.body, opts[:format]
|
61
|
+
end
|
62
|
+
|
63
|
+
def post_db url, body, opts={}
|
64
|
+
verb :post, "/#{db_env}#{url}", body, opts
|
65
|
+
end
|
66
|
+
|
67
|
+
def put_db(url, body) verb :put, "/#{db_env}#{url}", body end
|
68
|
+
|
69
|
+
def post_db_string url, body
|
70
|
+
conn.post("/#{db_env}#{url}", JSON.generate(body), 'Content-Type' => 'application/json').body
|
71
|
+
end
|
72
|
+
|
73
|
+
def version() get('/')[:version] end
|
74
|
+
def db_info() get_db "" end
|
75
|
+
#db_name: cars
|
76
|
+
#doc_count: 0
|
77
|
+
#doc_del_count: 0
|
78
|
+
#update_seq: 0
|
79
|
+
#purge_seq: 0
|
80
|
+
#compact_running: false
|
81
|
+
#disk_size: 79
|
82
|
+
#data_size: 0
|
83
|
+
#instance_start_time: '1364126614255010'
|
84
|
+
#disk_format_version: 6
|
85
|
+
#committed_update_seq: 0
|
86
|
+
|
87
|
+
def with_fixtures! names, *docs
|
88
|
+
blank!
|
89
|
+
load_design
|
90
|
+
documents = docs.map do |values|
|
91
|
+
Hash[ names.zip(values) ]
|
92
|
+
end
|
93
|
+
doc_new_bulk documents
|
94
|
+
end
|
95
|
+
|
96
|
+
def with_documents! *docs
|
97
|
+
blank!
|
98
|
+
load_design
|
99
|
+
doc_new_bulk docs
|
100
|
+
end
|
101
|
+
|
102
|
+
# PUT /db/doc
|
103
|
+
# @return [Doc]
|
104
|
+
# ok true
|
105
|
+
# id 0c8e32db863b87c9f6233d572854d6db
|
106
|
+
# rev 1-08c111cb2ccfbf117ed931bef444f730
|
107
|
+
def doc_new id, attributes
|
108
|
+
id ||= next_id
|
109
|
+
put_db "/#{id}", attributes
|
110
|
+
end
|
111
|
+
|
112
|
+
# @param [String] file_root
|
113
|
+
def doc_attach_app id, file_root
|
114
|
+
# curl -X PUT -v http://127.0.0.1:5984/cars/flot/index.html --data-binary "@test/fixtures/index.html" --header "Accept: text/html" --header "Transfer-Encoding: chunked" --header "Expect:"
|
115
|
+
require 'tatami/app'
|
116
|
+
put_db "/#{id}", "_attachments" => App.new.attachments(file_root)
|
117
|
+
end
|
118
|
+
|
119
|
+
def doc_create_or_update id, attributes
|
120
|
+
if old = doc(id)
|
121
|
+
doc_update old, attributes
|
122
|
+
else
|
123
|
+
doc_new id, attributes
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# @return [String] autogenerated available id (caches 10)
|
128
|
+
def next_id
|
129
|
+
@next_ids ||= []
|
130
|
+
if @next_ids.empty?
|
131
|
+
@next_ids = get("_uuids?count=10")[:uuids]
|
132
|
+
end
|
133
|
+
@next_ids.pop
|
134
|
+
end
|
135
|
+
|
136
|
+
# _id 0c8e32db863b87c9f6233d5728545922
|
137
|
+
# _rev 1-08c111cb2ccfbf117ed931bef444f730
|
138
|
+
# error: not_found
|
139
|
+
# reason: missing
|
140
|
+
# name Frank
|
141
|
+
# GET /db/doc
|
142
|
+
def doc id
|
143
|
+
r = get_db "/#{id}"
|
144
|
+
r[:error] == "not_found" ? nil : r
|
145
|
+
end
|
146
|
+
|
147
|
+
# GET /db/_design/design-doc/_show/show-name
|
148
|
+
# GET /db/_design/design-doc/_show/show-name/doc
|
149
|
+
def show name, doc=nil, design=db_env
|
150
|
+
d = doc ? "/#{doc}" : ""
|
151
|
+
get_db_string "/_design/#{design}/_show/#{name}#{d}"
|
152
|
+
end
|
153
|
+
|
154
|
+
# @param [Hash] old
|
155
|
+
# @param [Hash] new
|
156
|
+
def doc_update old, new
|
157
|
+
attributes = new.merge(:_rev => old[:_rev])
|
158
|
+
doc_new old[:_id], attributes
|
159
|
+
end
|
160
|
+
|
161
|
+
def doc_merge_cached id, attributes
|
162
|
+
@cache ||= Map.new
|
163
|
+
@cache[id] ||= begin
|
164
|
+
doc(id) || Map.new
|
165
|
+
end
|
166
|
+
@cache.combine! id => attributes
|
167
|
+
r = doc_new id, @cache[id]
|
168
|
+
@cache[id][:_rev] = r[:rev]
|
169
|
+
end
|
170
|
+
|
171
|
+
# creates new type in _design/[db-name] document
|
172
|
+
# @param [Symbol] type :view, :list, :show
|
173
|
+
def design_new type, name, content
|
174
|
+
doc_merge_cached "_design/#{db_env}", :language => 'coffeescript',
|
175
|
+
"#{type}s" =>
|
176
|
+
{ name => content }
|
177
|
+
end
|
178
|
+
|
179
|
+
# views name map
|
180
|
+
# views name reduce
|
181
|
+
# lists name
|
182
|
+
def load_design pattern="*"
|
183
|
+
[ %w[erlang %% erl],
|
184
|
+
%w[coffeescript # coffee]
|
185
|
+
].each do |language, comment, ext |
|
186
|
+
Dir.glob("couches/#{@db}/#{pattern}.#{ext}").each do |path|
|
187
|
+
source = IO.read(path)
|
188
|
+
name = File.basename(path)[/[^\.]+/]
|
189
|
+
map = Map.new(language: language)
|
190
|
+
source.split(/^(?=#{comment} (?:views|lists) \S+)/m).each do |element|
|
191
|
+
element.split("\n").first =~ /#{comment} (\S+) (\S+) ?(map|reduce)?/
|
192
|
+
values = [$1,$2,$3].compact << element
|
193
|
+
map.set *values
|
194
|
+
end
|
195
|
+
doc_create_or_update "_design/#{name}", map
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def doc_merge id, attributes
|
201
|
+
old = doc id
|
202
|
+
doc_new old[:_id], old.combine(attributes)
|
203
|
+
end
|
204
|
+
|
205
|
+
def doc_new_bulk documents
|
206
|
+
post_db "/_bulk_docs", {:docs => documents}
|
207
|
+
end
|
208
|
+
|
209
|
+
def bulk_import limit=100, &b
|
210
|
+
BulkImport.new self, limit, &b
|
211
|
+
end
|
212
|
+
|
213
|
+
class BulkImport
|
214
|
+
def initialize couch, limit
|
215
|
+
@couch, @limit, @cache = couch, limit, []
|
216
|
+
yield self
|
217
|
+
docs_new
|
218
|
+
end
|
219
|
+
|
220
|
+
def doc_new attributes
|
221
|
+
docs_new if @cache.size > @limit
|
222
|
+
@cache << attributes
|
223
|
+
end
|
224
|
+
|
225
|
+
def docs_new
|
226
|
+
@couch.doc_new_bulk @cache
|
227
|
+
@cache = []
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# GET /db/_design/design-doc/_view/view-name
|
232
|
+
def view name
|
233
|
+
@views ||= Map.new
|
234
|
+
@views[name] ||= View.new self, name.to_s
|
235
|
+
end
|
236
|
+
|
237
|
+
class View
|
238
|
+
def initialize couch, name
|
239
|
+
@couch = couch
|
240
|
+
name.prepend "#{@couch.db}/" unless name['/']
|
241
|
+
@design, @view = name.split('/')
|
242
|
+
@base = "/_design/#{@design}/_view/#{@view}"
|
243
|
+
end
|
244
|
+
|
245
|
+
# @param [] value
|
246
|
+
# @return [String] json
|
247
|
+
def options_to_param opts
|
248
|
+
if single_key=opts.delete(:singlekey)
|
249
|
+
opts[:startkey] = single_key
|
250
|
+
opts[:endkey] = (single_key + [{}] )
|
251
|
+
end
|
252
|
+
opts.map{|option,value| "#{option}=#{ JSON.generate([value])[1..-2] }"}.join('&')
|
253
|
+
end
|
254
|
+
|
255
|
+
# {total_rows: 1
|
256
|
+
# offset: 0
|
257
|
+
# rows: [{id: 45d73462c193b4bd5b5e643b33010de0
|
258
|
+
# key: the_key
|
259
|
+
# value: the_value}, ...
|
260
|
+
# ]
|
261
|
+
|
262
|
+
# @option opts [Boolean] :format :string, :object, (:map)
|
263
|
+
# @option opts [Boolean] :log puts url
|
264
|
+
# @option opts [Array] :keys make a post request
|
265
|
+
# @option opts [Array] :single_key calculates startkey and endkey
|
266
|
+
# @return [Map]
|
267
|
+
def data opts={}
|
268
|
+
opts_add = {log: opts.delete(:log), format: opts.delete(:format) }
|
269
|
+
list = opts[:list] ? "_list/#{opts.delete(:list)}" : "_view"
|
270
|
+
if opts[:keys]
|
271
|
+
@couch.post_db "/_design/#{@design}/#{list}/#{@view}", opts, opts_add
|
272
|
+
else
|
273
|
+
@couch.get_db "/_design/#{@design}/#{list}/#{@view}?#{options_to_param opts}", opts_add
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def rows opts={}
|
278
|
+
data(opts)[:rows]
|
279
|
+
end
|
280
|
+
|
281
|
+
# @param [String] map
|
282
|
+
# @p
|
283
|
+
# @return [View]
|
284
|
+
def create map, reduce=nil
|
285
|
+
v = {:map => map}
|
286
|
+
v[:reduce] = reduce if reduce
|
287
|
+
@couch.design_new :view, @view, v
|
288
|
+
self
|
289
|
+
end
|
290
|
+
|
291
|
+
def key_values opts={}
|
292
|
+
rows(opts).map{|r| [ r[:key],r[:value] ]}
|
293
|
+
end
|
294
|
+
|
295
|
+
def values opts={}
|
296
|
+
rows(opts).map{|d| d[:value]}
|
297
|
+
end
|
298
|
+
|
299
|
+
def keys opts={}
|
300
|
+
rows(opts).map{|d| d[:key]}
|
301
|
+
end
|
302
|
+
|
303
|
+
# @return value of the first row
|
304
|
+
def reduced opts={}
|
305
|
+
data(opts).get :rows, 0, :value
|
306
|
+
end
|
307
|
+
|
308
|
+
# @return [Map] rows as key => value
|
309
|
+
def reduce opts={}
|
310
|
+
rows(opts).each_with_object(Map.new)do |row,o|
|
311
|
+
o[row[:key]] = row[:value]
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# special function for arrays as keys
|
316
|
+
def reduced_value *values
|
317
|
+
reduced :group_level => values.size, :startkey => values, :endkey => values.dup << {}
|
318
|
+
end
|
319
|
+
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|