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 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