tatami 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d7d8dc0044e3cb652e2f0bca608ff687bc7e1bf3
4
- data.tar.gz: 593e6c6c3b495d8c92e2c2d648ea5c95c2648b83
3
+ metadata.gz: 7a2dbb07adb70e19071bbd27c4fed18682cddd1e
4
+ data.tar.gz: 575192e6a8d4dbb989085f9ccabf23cd3ef670d9
5
5
  SHA512:
6
- metadata.gz: 2af59307a6af69dcf91ec8b0a3a9d923924c4ea377f409fe30bb8e4ee2dda9542273ec8061f9ef6d1713cd4d8c45ddd01989bc4640ec97a1d14c892767ddee83
7
- data.tar.gz: edea8442958000268c7cb45b70d892ce2770dfcb8565f167eb423d0a4213947da63d223ce2a34bc5f63af1106920233bd714d47738bab9932a66e1ff9ae57b22
6
+ metadata.gz: a4abd3c393ca07b7fb0acaea1c7c81cadb9a07306e3f3150a24f40a131cb9ab8c68776fee6f36700fc34ef936d3476642885d94280f75fec25e16be17639bec9
7
+ data.tar.gz: 32bb2cdf1e33aff92f588e1caf8c619ebdf62a77f9888e5743c641c3378b368807fb3fdc679cc7e1c3a66f66ac529650d49515c6c12a2fc1ac765013d5b1f1eb
data/Rakefile CHANGED
@@ -1 +1,10 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test" << 'lib'
6
+ t.test_files = FileList['test/*test.rb']
7
+ t.verbose = true
8
+ end
9
+
10
+ task default: :test
@@ -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,9 @@
1
+ %% views simple map
2
+ fun({Doc}) ->
3
+ %% <<K,_/binary>> = proplists:get_value(<<"_rev">>, Doc, null),
4
+ A = proplists:get_value(<<"a">>, Doc, null),
5
+ Emit(1, A)
6
+ end.
7
+
8
+ %% views simple reduce
9
+ fun(Keys, Values, ReReduce) -> length(Values) end.
@@ -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
- require "tatami/version"
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
@@ -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