couch-quilt 0.4.1 → 0.5.0

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.
@@ -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?