couch-quilt 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{couch-quilt}
8
- s.version = "0.4.1"
8
+ s.version = "0.5.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Johannes J\303\266rg Schmidt"]
12
- s.date = %q{2010-03-23}
12
+ s.date = %q{2010-04-02}
13
13
  s.default_executable = %q{couchquilt}
14
14
  s.description = %q{Access CouchDB JSON documents from filesystem.}
15
15
  s.email = %q{schmidt@netzmerk.com}
@@ -26,11 +26,15 @@ Gem::Specification.new do |s|
26
26
  "bin/couchquilt",
27
27
  "couch-quilt.gemspec",
28
28
  "lib/couchquilt.rb",
29
+ "lib/couchquilt/core_ext/array.rb",
30
+ "lib/couchquilt/core_ext/hash.rb",
29
31
  "lib/couchquilt/couch_client.rb",
32
+ "lib/couchquilt/database.rb",
30
33
  "lib/couchquilt/debugged_fs.rb",
31
34
  "lib/couchquilt/fs.rb",
32
35
  "lib/couchquilt/mapper.rb",
33
36
  "lib/couchquilt/version.rb",
37
+ "spec/couchquilt/core_ext_spec.rb",
34
38
  "spec/couchquilt/fs_spec.rb",
35
39
  "spec/couchquilt/mapper_spec.rb",
36
40
  "spec/spec_helper.rb"
@@ -44,6 +48,7 @@ Gem::Specification.new do |s|
44
48
  s.test_files = [
45
49
  "spec/spec_helper.rb",
46
50
  "spec/couchquilt/fs_spec.rb",
51
+ "spec/couchquilt/core_ext_spec.rb",
47
52
  "spec/couchquilt/mapper_spec.rb"
48
53
  ]
49
54
 
@@ -29,7 +29,10 @@ $:.unshift File.dirname(__FILE__) unless
29
29
  $:.include?(File.expand_path(File.dirname(__FILE__)))
30
30
 
31
31
  require 'couchquilt/mapper'
32
+ require 'couchquilt/core_ext/array'
33
+ require 'couchquilt/core_ext/hash'
32
34
  require 'couchquilt/couch_client'
35
+ require 'couchquilt/database'
33
36
  require 'couchquilt/fs'
34
37
 
35
38
 
@@ -0,0 +1,37 @@
1
+ class Array
2
+ include Couchquilt::Mapper
3
+
4
+ def at_path(path)
5
+ head, *parts = to_parts(path)
6
+ return self if head.nil?
7
+ head = head.to_i
8
+ part = self[head]
9
+ return part if parts.empty? || !part.respond_to?(:at_path)
10
+ part.at_path(parts)
11
+ end
12
+
13
+ def update_at_path(path, value)
14
+ head, *parts = to_parts(path)
15
+ return if head.nil?
16
+ head = head.to_i
17
+ return self[head] = value if parts.empty?
18
+ self[head] ||= []
19
+ self[head].update_at_path(parts, value)
20
+ end
21
+
22
+ def delete_at_path(path)
23
+ head, *parts = to_parts(path)
24
+ return if head.nil?
25
+ head = head.to_i
26
+ return self.delete_at(head) if parts.empty?
27
+ return unless self[head]
28
+ self[head].delete_at_path(parts)
29
+ end
30
+
31
+ def to_fs(named_path = true)
32
+ # using zip for backwards compatibily:
33
+ # in Ruby 1.9 (and 1.8.7) we could simply use
34
+ # each_with_index.map { |k,i| ... }
35
+ named_path ? zip((0..size).to_a).map { |v,i| name_for(i, v) } : self
36
+ end
37
+ end
@@ -0,0 +1,43 @@
1
+ class Hash
2
+ include Couchquilt::Mapper
3
+
4
+ def at_path(path)
5
+ head, *parts = to_parts(path)
6
+ return self if head.nil?
7
+ part = self[head]
8
+ return part if parts.empty? || !part.respond_to?(:at_path)
9
+ part.at_path(parts)
10
+ end
11
+
12
+ def update_at_path(path, value)
13
+ head, *parts = to_parts(path)
14
+ return if head.nil?
15
+ return self[head] = value if parts.empty?
16
+ self[head] ||= {}
17
+ self[head].update_at_path(parts, value)
18
+ end
19
+
20
+ def delete_at_path(path)
21
+ head, *parts = to_parts(path)
22
+ return if head.nil?
23
+ return self.delete(head) if parts.empty?
24
+ return unless self[head]
25
+ self[head].delete_at_path(parts)
26
+ end
27
+
28
+ def to_fs(named_path = true)
29
+ named_path ? keys.map { |k| name_for(k, self[k]) }.sort : keys.sort
30
+ end
31
+
32
+ def map_arrays!
33
+ each do |key, value|
34
+ next unless value.is_a?(Hash)
35
+ if value.keys.all? { |k| k =~ /^\d+$/ }
36
+ self[key] = value.keys.sort.map { |k| value[k] }
37
+ else
38
+ value.map_arrays!
39
+ end
40
+ end
41
+ self
42
+ end
43
+ end
@@ -0,0 +1,68 @@
1
+ module Couchquilt
2
+ module Database
3
+ ## database queries
4
+
5
+ # does a database, document or design_document exists?
6
+ def exists?(path)
7
+ @couch.head key_for(path)
8
+ end
9
+
10
+ # query for all dbs
11
+ def all_dbs
12
+ @couch.get("_all_dbs")
13
+ end
14
+
15
+ # query for database info
16
+ def database_info(database, parts = [])
17
+ @couch.get(database).at_path(parts)
18
+ end
19
+
20
+ # query for all docs ids
21
+ def all_doc_ids(database, query_string = nil)
22
+ path = "#{database}/_all_docs"
23
+ path << "?#{URI.encode query_string}" if query_string
24
+ @couch.get(path)["rows"].map { |r| r["id"] }
25
+ end
26
+
27
+ # query for document
28
+ def document(database, id, parts = [])
29
+ @couch.get("#{database}/#{id}").at_path(parts)
30
+ end
31
+
32
+ # query for view
33
+ def view(database, id, parts)
34
+ a, view_function_name, *rest = parts
35
+ query = [id.sub("_design/", ""), "_view", view_function_name].join("/")
36
+ doc = @couch.get("#{database}/_design/#{query}").at_path(rest)
37
+ end
38
+
39
+ # query a function
40
+ def function_result(database, id, parts)
41
+ @couch.get key_for(File.join(database, id, *parts))
42
+ end
43
+
44
+ ## document manipulation
45
+
46
+ # updating documents
47
+ def update(database, id, doc)
48
+ doc.map_arrays!
49
+ @couch.put("#{database}/#{id}", doc)
50
+ end
51
+
52
+ # create database or document
53
+ def create(database, id = nil)
54
+ @couch.put("#{database}/#{id}")
55
+ end
56
+
57
+ # delete a database
58
+ def delete_database(database)
59
+ @couch.delete(database)
60
+ end
61
+
62
+ # delete a document
63
+ def delete_document(database, id)
64
+ doc = document(database, id)
65
+ @couch.delete("#{database}/#{id}?rev=#{doc["_rev"]}")
66
+ end
67
+ end
68
+ end
@@ -4,6 +4,7 @@ require 'uri'
4
4
  module Couchquilt
5
5
  class FS
6
6
  include Mapper
7
+ include Database
7
8
 
8
9
  # initializes Quilt FS with the database server name
9
10
  def initialize(server_name)
@@ -16,39 +17,35 @@ module Couchquilt
16
17
 
17
18
  list case named_path(path)
18
19
  when :root
19
- ["_uuids"] + @couch.get("_all_dbs")
20
+ ["_uuids"] + all_dbs
20
21
  when :uuids
21
22
  ["0i.js"]
22
23
  when :database
23
24
  ["_design"] +
24
25
  # database meta data
25
- map_json(@couch.get(database)) +
26
+ database_info(database, id).to_fs +
26
27
  # all documents but design documents
27
- # Note: we can not use ?startkey="_design/"&endkey="_design0" here,
28
- # because that would return no results for databases without design documents
29
- @couch.get("#{database}/_all_docs")["rows"].map { |r| r["id"] }.select { |id| id !~ /^_design\// }
28
+ all_doc_ids(database).select { |id| id !~ /^_design\// }
30
29
  when :_design
31
- query = URI.encode('startkey="_design"&endkey="_design0"')
32
30
  # all design documents
33
- @couch.get("#{database}/_all_docs?#{query}")["rows"].map { |r| r["id"].sub("_design/", "") }
31
+ all_doc_ids(database, 'startkey="_design"&endkey="_design0"').map { |id| id.sub("_design/", "") }
34
32
  when :_show
35
- (@couch.get("#{database}/#{id}")["shows"] || {}).keys
33
+ document(database, id, "shows").to_fs(false)
36
34
  when :_list
37
- (@couch.get("#{database}/#{id}")["lists"] || {}).keys
35
+ document(database, id, "lists").to_fs(false)
38
36
  when :_view
39
- (@couch.get("#{database}/#{id}")["views"] || {}).keys
37
+ document(database, id, "views").to_fs(false)
40
38
  when :list_function
41
- (@couch.get("#{database}/#{id}")["views"] || {}).keys.map { |name| "#{name}.html" }
39
+ document(database, id, "views").to_fs(false).map { |name| "#{name}.html" }
42
40
  when :show_function
43
- query = URI.encode('startkey="_design0"')
44
- @couch.get("#{database}/_all_docs?#{query}")["rows"].map { |r| "#{r["id"]}.html" }
41
+ all_doc_ids(database, 'startkey="_design0"').map { |id| "#{id}.html" }
45
42
  when :view_function, :view_function_result
46
- map_json(get_view_result_part(database, id, parts))
43
+ view(database, id, parts).to_fs
47
44
  when :design_document
48
45
  ["_list", "_show", "_view"] +
49
- map_json(get_document_part(database, id))
46
+ document(database, id).to_fs
50
47
  else
51
- map_json(get_document_part(database, id, parts))
48
+ document(database, id, parts).to_fs
52
49
  end
53
50
  end
54
51
 
@@ -58,21 +55,21 @@ module Couchquilt
58
55
 
59
56
  case named_path(path)
60
57
  when :database, :document, :design_document
61
- @couch.head(path)
58
+ exists?(path)
62
59
  when :view_function_result
63
- doc = get_view_result_part(database, id, parts)
60
+ doc = view(database, id, parts)
64
61
  # arrays and hashes are mapped into directories
65
62
  doc.is_a?(Hash) || doc.is_a?(Array)
66
63
  when :database_info, :show_function_result, :list_function_result, :uuid
67
64
  false
68
- when nil
65
+ when :document_part
69
66
  # look into document
70
- doc = get_document_part(database, id, parts)
67
+ doc = document(database, id, parts)
71
68
  # arrays and hashes are mapped into directories
72
69
  doc.is_a?(Hash) || doc.is_a?(Array)
73
70
  else
74
71
  # all other special paths are directories by now
75
- # TODO: thats not so good.
72
+ # TODO: thats not very good.
76
73
  true
77
74
  end
78
75
  end
@@ -85,11 +82,11 @@ module Couchquilt
85
82
  when :database_info, :show_function_result, :list_function_result, :uuid
86
83
  true
87
84
  when :view_function_result
88
- # Every javascript or HTML is file, based on extension
85
+ # Every javascript or HTML is a file, based on extension
89
86
  [".js", ".html"].include?(File.extname(path))
90
- when nil
87
+ when :document_part
91
88
  # look into document
92
- doc = get_document_part(database, id, parts)
89
+ doc = document(database, id, parts)
93
90
  # only arrays and hashes are mapped into directories
94
91
  !doc.nil? && !(doc.is_a?(Hash) || doc.is_a?(Array))
95
92
  else
@@ -103,19 +100,15 @@ module Couchquilt
103
100
 
104
101
  content case named_path(path)
105
102
  when :database_info
106
- @couch.get(database)[key_for(id)]
107
- when :show_function_result
108
- parts.shift
109
- @couch.get(key_for(File.join(database, id, "_show", *parts)))
110
- when :list_function_result
111
- parts.shift
112
- @couch.get(key_for(File.join(database, id, "_list", *parts)))
103
+ database_info(database, id)
104
+ when :show_function_result, :list_function_result
105
+ function_result(database, id, parts)
113
106
  when :view_function_result
114
- get_view_result_part(database, id, parts)
107
+ view(database, id, parts)
115
108
  when :uuid
116
- get_document_part(database, id, ["uuids"])
109
+ document(database, id, "uuids")
117
110
  else
118
- get_document_part(database, id, parts)
111
+ document(database, id, parts)
119
112
  end
120
113
  end
121
114
 
@@ -139,11 +132,11 @@ module Couchquilt
139
132
  str.strip!
140
133
  database, id, *parts = extract_parts(path)
141
134
  # fetch document
142
- doc = @couch.get("#{database}/#{id}")
135
+ doc = document(database, id)
143
136
  # update the value that the file at path holds
144
- map_fs(doc, parts, str)
137
+ doc.update_at_path(parts, str)
145
138
  # save document
146
- @couch.put("#{database}/#{id}", doc)
139
+ update(database, id, doc)
147
140
  end
148
141
 
149
142
  # can I delete path?
@@ -164,11 +157,11 @@ module Couchquilt
164
157
  database, id, *parts = extract_parts(path)
165
158
 
166
159
  # fetch document
167
- doc = @couch.get("#{database}/#{id}")
160
+ doc = document(database, id)
168
161
  # remove object
169
- map_fs(doc, parts, nil)
162
+ doc.delete_at_path(parts)
170
163
  # save document
171
- @couch.put("#{database}/#{id}", doc)
164
+ update(database, id, doc)
172
165
  end
173
166
 
174
167
  # can I make a directory at path?
@@ -180,9 +173,9 @@ module Couchquilt
180
173
  false
181
174
  when :database, :document, :design_document
182
175
  # can create database or document unless exists
183
- !@couch.head(path)
176
+ !exists?(path)
184
177
  else
185
- !get_document_part(database, id, parts)
178
+ !document(database, id, parts)
186
179
  end
187
180
  end
188
181
 
@@ -193,16 +186,16 @@ module Couchquilt
193
186
 
194
187
  case named_path(path)
195
188
  when :database
196
- @couch.put(database)
189
+ create(database)
197
190
  when :document, :design_document
198
- @couch.put("#{database}/#{id}")
191
+ create(database, id)
199
192
  else
200
193
  # fetch document
201
- doc = @couch.get("#{database}/#{id}")
194
+ doc = document(database, id)
202
195
  # insert empty object
203
- map_fs(doc, parts)
196
+ doc.update_at_path(parts, {})
204
197
  # save document
205
- @couch.put("#{database}/#{id}", doc)
198
+ update(database, id, doc)
206
199
  end
207
200
  end
208
201
 
@@ -214,7 +207,7 @@ module Couchquilt
214
207
  when :root, :_design, :_list, :list_function, :_show, :show_function, :_view, :view_function, :view_function_result, :database, :document, :design_document
215
208
  false
216
209
  else
217
- get_document_part(database, id, parts).empty? rescue nil
210
+ document(database, id, parts).empty? rescue false
218
211
  end
219
212
  end
220
213
 
@@ -224,11 +217,11 @@ module Couchquilt
224
217
  database, id, *parts = extract_parts(path)
225
218
 
226
219
  # fetch document
227
- doc = @couch.get("#{database}/#{id}")
220
+ doc = document(database, id)
228
221
  # remove object
229
- map_fs(doc, parts, nil)
222
+ doc.delete_at_path(parts)
230
223
  # save document
231
- @couch.put("#{database}/#{id}", doc)
224
+ update(database, id, doc)
232
225
  end
233
226
 
234
227
  # switch to delete a database or document
@@ -237,10 +230,9 @@ module Couchquilt
237
230
 
238
231
  case named_path(path)
239
232
  when :switch_delete_database
240
- @couch.delete(database)
233
+ delete_database(database)
241
234
  when :switch_delete_document
242
- doc = @couch.get("#{database}/#{id}")
243
- @couch.delete("#{database}/#{id}?rev=#{doc["_rev"]}")
235
+ delete_document(database, id)
244
236
  end
245
237
  end
246
238
 
@@ -315,37 +307,13 @@ module Couchquilt
315
307
  # /database_id/document_id/object
316
308
  # /database_id/_design/design_document_id
317
309
  # /database_id/_design/design_document_id/object
318
- nil
310
+ :document_part
319
311
  end
320
312
  end
321
-
322
- # fetch part of document
323
- def get_document_part(database, id, parts = [])
324
- doc = @couch.get("#{database}/#{id}")
325
- parts.map! { |p| key_for p }
326
- get_part(doc, parts)
327
- end
328
-
329
- # get view result, or a part of that document.
330
- def get_view_result_part(database, id, parts = [])
331
- a, view_function_name, *rest = parts
332
- view = [id.sub("_design/", ""), "_view", view_function_name].join("/")
333
- doc = @couch.get("#{database}/_design/#{view}")
334
- rest.map! { |p| key_for p }
335
- get_part(doc, rest)
336
- end
337
-
338
- # get a part of the document
339
- # eg: get_part({ :a => { :b => :c}}, [:a, :b]) #=> :c
340
- def get_part(doc, keys = [])
341
- return if doc.nil?
342
- doc = doc.dup
343
- keys.each do |key|
344
- doc = doc[key]
345
- end
346
- doc
347
- end
348
-
313
+
314
+
315
+ ## list and content helper
316
+
349
317
  # escapes the value for using as filename
350
318
  def list(array)
351
319
  return [] if array.nil?