couch 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -36,7 +36,7 @@ begin
36
36
  gem.add_dependency "rest-client", ">= 1.4.1"
37
37
  gem.add_dependency "json_pure", ">= 1.2.2"
38
38
  gem.add_dependency "activesupport", ">= 3.0.0.beta"
39
- # gem.add_development_dependency "rspec", ">= 1.2.9"
39
+ gem.add_development_dependency "rspec", ">= 1.2.9"
40
40
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
41
41
  end
42
42
  Jeweler::GemcutterTasks.new
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{couch}
8
- s.version = "0.1.2"
8
+ s.version = "0.2.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. Schmidt"]
@@ -44,7 +44,7 @@ Gem::Specification.new do |s|
44
44
  "lib/couch/generators/application/templates/README",
45
45
  "lib/couch/generators/application/templates/_attachments/index.html",
46
46
  "lib/couch/generators/application/templates/_attachments/stylesheets/application.css",
47
- "lib/couch/generators/application/templates/_id.js",
47
+ "lib/couch/generators/application/templates/_id",
48
48
  "lib/couch/generators/application/templates/couchrc",
49
49
  "lib/couch/generators/application/templates/gitignore",
50
50
  "lib/couch/generators/application/templates/lib/mustache.js",
@@ -65,7 +65,11 @@ Gem::Specification.new do |s|
65
65
  "lib/couch/generators/view/USAGE",
66
66
  "lib/couch/generators/view/templates/map.js",
67
67
  "lib/couch/generators/view/view_generator.rb",
68
- "lib/couch/version.rb"
68
+ "lib/couch/version.rb",
69
+ "spec/couch/design_document_spec.rb",
70
+ "spec/couch_spec.rb",
71
+ "spec/spec.opts",
72
+ "spec/spec_helper.rb"
69
73
  ]
70
74
  s.homepage = %q{http://github.com/jo/couch}
71
75
  s.rdoc_options = ["--charset=UTF-8"]
@@ -73,6 +77,11 @@ Gem::Specification.new do |s|
73
77
  s.rubyforge_project = %q{couch}
74
78
  s.rubygems_version = %q{1.3.6}
75
79
  s.summary = %q{Standalone CouchDB Application Development Suite}
80
+ s.test_files = [
81
+ "spec/spec_helper.rb",
82
+ "spec/couch/design_document_spec.rb",
83
+ "spec/couch_spec.rb"
84
+ ]
76
85
 
77
86
  if s.respond_to? :specification_version then
78
87
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
@@ -83,17 +92,20 @@ Gem::Specification.new do |s|
83
92
  s.add_runtime_dependency(%q<rest-client>, [">= 1.4.1"])
84
93
  s.add_runtime_dependency(%q<json_pure>, [">= 1.2.2"])
85
94
  s.add_runtime_dependency(%q<activesupport>, [">= 3.0.0.beta"])
95
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
86
96
  else
87
97
  s.add_dependency(%q<thor>, [">= 0.13.4"])
88
98
  s.add_dependency(%q<rest-client>, [">= 1.4.1"])
89
99
  s.add_dependency(%q<json_pure>, [">= 1.2.2"])
90
100
  s.add_dependency(%q<activesupport>, [">= 3.0.0.beta"])
101
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
91
102
  end
92
103
  else
93
104
  s.add_dependency(%q<thor>, [">= 0.13.4"])
94
105
  s.add_dependency(%q<rest-client>, [">= 1.4.1"])
95
106
  s.add_dependency(%q<json_pure>, [">= 1.2.2"])
96
107
  s.add_dependency(%q<activesupport>, [">= 3.0.0.beta"])
108
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
97
109
  end
98
110
  end
99
111
 
@@ -10,15 +10,23 @@ module Couch
10
10
  end
11
11
 
12
12
  def self.database
13
- config["database"]
13
+ @database ||= config["database"]
14
14
  end
15
15
 
16
- def self.config
17
- @config ||= YAML.load(File.open config_file)
16
+ def self.id
17
+ @id ||= File.read(File.join(root, '_id')).strip
18
+ end
19
+
20
+ def self.rev
21
+ @rev ||= File.read(File.join(root, '_rev')).strip rescue nil
18
22
  end
19
23
 
20
24
  private
21
25
 
26
+ def self.config
27
+ @config ||= YAML.load(File.open config_file)
28
+ end
29
+
22
30
  def self.config_file
23
31
  File.join root, CONFIG_FILENAME
24
32
  end
@@ -9,10 +9,11 @@ module Couch
9
9
  add_runtime_options!
10
10
 
11
11
  def pull
12
- say "Pulling from %s" % DesignDocument.url
12
+ doc = DesignDocument.new
13
+ say "Pulling %s" % doc.url
13
14
 
14
- resp = RestClient.get DesignDocument.url(:attachments => true)
15
- doc = DesignDocument.build_from_json(resp.body)
15
+ resp = RestClient.get doc.url(:attachments => true)
16
+ doc.json = resp.body
16
17
 
17
18
  doc.write do |filename, content|
18
19
  create_file filename, content
@@ -20,7 +21,7 @@ module Couch
20
21
 
21
22
  say "Checked out %s" % doc.rev
22
23
  rescue RestClient::ResourceNotFound
23
- say "Error: Document %s does not exist!" % DesignDocument.id
24
+ say "Error: Document %s does not exist!" % doc.id
24
25
  end
25
26
  end
26
27
  end
@@ -1,31 +1,46 @@
1
1
  require 'couch/actions/base'
2
2
  require 'couch/design_document'
3
3
 
4
+ require 'pathname'
4
5
  require "rest_client"
5
6
 
6
7
  module Couch
7
8
  module Actions
8
9
  class Push < Base
10
+ def initialize(*args)
11
+ super
12
+ @doc = DesignDocument.new
13
+ end
14
+
9
15
  def create_database_unless_exists
10
- RestClient.put DesignDocument.database, nil
11
- say "Created database %s" % DesignDocument.database
16
+ RestClient.put @doc.database, nil
17
+ say "Created database %s" % @doc.database
12
18
  rescue RestClient::PreconditionFailed
13
19
  end
14
20
 
15
21
  def push
16
- doc = DesignDocument.build_from_filesystem(destination_root)
17
- say "Pushing to %s" % DesignDocument.url
22
+ root = Pathname.new(destination_root)
23
+ filenames = Dir[root.join "**/*"]
24
+ filenames.map! { |file| Pathname.new file }
25
+ filenames.map! { |path| path.relative_path_from root }
26
+ filenames.delete_if { |path| !path.file? }
27
+ filenames.map!(&:to_s)
28
+ @doc.read(filenames) do |filename|
29
+ File.read File.join(destination_root, filename)
30
+ end
31
+
32
+ say "Pushing to %s" % @doc.url
18
33
 
19
- resp = RestClient.put DesignDocument.url, doc.to_json
34
+ resp = RestClient.put @doc.url, @doc.json
20
35
  response = JSON.parse(resp.body)
21
36
 
22
37
  if response["ok"]
23
- rev = response["rev"]
24
- File.open File.join(destination_root, "_rev.js"), "w" do |file|
25
- file << "#{rev}\n"
38
+ @doc.rev = response["rev"]
39
+ File.open File.join(destination_root, "_rev"), "w" do |file|
40
+ file << @doc.rev
26
41
  end
27
42
 
28
- say "Pushed %s" % rev
43
+ say "Pushed %s" % @doc.rev
29
44
  else
30
45
  say "Error occured: %s" % response.inspect
31
46
  end
@@ -10,14 +10,14 @@ module Couch
10
10
  end
11
11
 
12
12
  say 'Lists:'
13
- Dir.glob(File.join(destination_root, "lists/*.js")).each do |list|
13
+ Dir.glob(File.join(destination_root, "lists/*")).each do |list|
14
14
  Dir.glob(File.join(destination_root, "views/*")).each do |view|
15
15
  say ' %s' % list_url(list, view)
16
16
  end
17
17
  end
18
18
 
19
19
  say 'Shows:'
20
- Dir.glob(File.join(destination_root, "shows/*.js")).each do |show|
20
+ Dir.glob(File.join(destination_root, "shows/*")).each do |show|
21
21
  say ' %s' % show_url(show)
22
22
  say ' %s' % show_url(show, '/:id')
23
23
  end
@@ -2,227 +2,313 @@ require 'uri'
2
2
 
3
3
  module Couch
4
4
  class DesignDocument
5
- EXT_MIME_MAPPING = {
5
+ # Mime type mapping from extensions
6
+ MIME_TYPE_MAPPING = {
6
7
  ".html" => "text/html",
7
8
  ".js" => "text/javascript",
8
9
  ".css" => "text/css",
9
10
  }
10
11
 
11
- # initialize with a hash
12
- def initialize(hash = {})
13
- @hash = hash
14
- end
12
+ # Files that should have a .js extension
13
+ JAVASCRIPT_FILES = %w[
14
+ validate_doc_update
15
+ lists/*
16
+ shows/*
17
+ updates/*
18
+ views/*/*
19
+ ]
15
20
 
16
- # returns the id of the design document
17
- def id
18
- @hash["_id"]
19
- end
21
+ # Files that should not be included in document
22
+ EXCLUDE_FILES = %w[
23
+ README
24
+ ]
20
25
 
21
- # returns the rev of the design document
22
- def rev
23
- @hash["_rev"]
24
- end
26
+ attr_accessor :hash
25
27
 
26
- # converts to json
27
- def to_json
28
- hash_with_injected_makros.to_json
28
+ def initialize
29
+ @hash = {}
29
30
  end
30
31
 
31
- # write to filesystem
32
- def write(&block)
33
- write_doc @hash, nil, block
34
- end
32
+ # Read document from a filesystem.
33
+ #
34
+ # Takes a filename,
35
+ # many filenames,
36
+ # or an array of filenames
37
+ # and assign the return value of a yielded block to the hash.
38
+ #
39
+ # Nested hashes
40
+ # like { "hash" => { "key" => "value" } }
41
+ # can be constructed if the filename contains a slash (/),
42
+ # eg "hash/key".
43
+ #
44
+ def read(*filenames, &block)
45
+ filenames.flatten.uniq.each do |filename|
46
+ # skip exclude files
47
+ next if EXCLUDE_FILES.include?(filename)
35
48
 
36
- class << self
37
- # returns new design document object from filesystem
38
- def build_from_filesystem(root)
39
- new map(root)
40
- end
49
+ key = filename.dup
50
+ # strip extname from javascript files
51
+ key.sub!(/\.js$/, '') if filename =~ /#{JAVASCRIPT_FILES.join('|')}/
41
52
 
42
- # returns new design document object from json string
43
- def build_from_json(json)
44
- new reject_makros(JSON.parse(json))
53
+ set_hash_at key, block.call(filename)
45
54
  end
46
55
 
47
- # returns the database used to store design document
48
- def database
49
- @database ||= Couch.database
50
- end
56
+ map_attachments!
57
+ inject_makros!
58
+ end
51
59
 
52
- # returns the id for design document
53
- def id
54
- @id ||= File.read(File.join(Couch.root, '_id.js')).strip
55
- end
60
+ # Write document to a filesystem
61
+ #
62
+ # Takes a directoy as startpoint (default is nil),
63
+ # a document hash (default is the design documents hash)
64
+ # and recursively yields all keys and values to the given block.
65
+ #
66
+ # Nested hashes
67
+ # like { "hash" => { "key" => "value" } }
68
+ # will result in the yielded filename
69
+ # "hash/key".
70
+ #
71
+ # The key "_attachments" has a special meaning:
72
+ # the value holds base64 encoded data as well as other metadata.
73
+ # This data will gets decoded and used as value for the key.
74
+ #
75
+ def write(directory = nil, doc = nil, &block)
76
+ reduce_attachments!
77
+ reject_makros!
56
78
 
57
- # returns the url for design document
58
- def url(options = {})
59
- File.join(database, id) << build_options_string(options)
79
+ doc ||= hash
80
+ doc.each do |key, value|
81
+ filename = directory ? File.join(directory, key) : key.dup
82
+ if value.is_a?(Hash)
83
+ write(filename, value, &block)
84
+ else
85
+ # append extname to javascript files
86
+ filename << '.js' if filename =~ /#{JAVASCRIPT_FILES.join('|')}/
87
+ block.call(filename, value)
88
+ end
60
89
  end
90
+ end
61
91
 
62
- private
92
+ # Returns a JSON string representation of the documents hash
93
+ #
94
+ def json
95
+ hash.to_json
96
+ end
63
97
 
64
- def reject_makros(doc)
65
- # TODO: recursive walk libs
66
- libs = doc["lib"]
67
- return doc if libs.nil? || libs.empty?
68
- # Attention: replace json makros first!
69
- doc = reject_json_makros(doc, libs)
70
- doc = reject_code_makros(doc, libs)
71
- end
98
+ # Build the documents hash from a JSON string
99
+ #
100
+ def json=(json)
101
+ self.hash = JSON.parse(json)
102
+ end
72
103
 
73
- def reject_code_makros(doc, libs)
74
- doc = doc.dup
75
- doc.each do |key, value|
76
- next if key == "lib"
77
- if value.is_a?(String)
78
- libs.each do |name, content|
79
- # only substitute strings
80
- next unless content.is_a?(String)
81
- next unless value.include?(content.strip)
82
- doc[key] = value.gsub(content.strip, "// !code #{name}.js")
83
- end
84
- elsif value.is_a?(Hash)
85
- doc[key] = reject_code_makros(value, libs)
86
- end
87
- end
88
- doc
89
- end
90
104
 
91
- def reject_json_makros(doc, libs)
92
- doc.each do |key, value|
93
- next if key == "lib"
94
- if value.is_a?(String)
95
- libs.each do |name, content|
96
- # only substitute strings
97
- next unless content.is_a?(String)
98
- json = 'var %s = %s;' % [name.sub(/\..*$/, ''), content.to_json]
99
- next unless value.include?(json)
100
- doc[key] = value.gsub(json, "// !json #{name}")
101
- end
102
- elsif value.is_a?(Hash)
103
- doc[key] = reject_json_makros(value, libs)
104
- end
105
- end
106
- doc
107
- end
105
+ # Accessor for id
106
+ def id
107
+ hash["_id"] || Couch.id
108
+ end
108
109
 
109
- def build_options_string(options)
110
- return '' if options.empty?
111
- options_array = []
112
- options.each do |key, value|
113
- options_array << URI.escape([key, value].join('='))
114
- end
115
- '?' + options_array.join("&")
116
- end
110
+ # Accessor for rev
111
+ def rev
112
+ hash["_rev"] || Couch.rev
113
+ end
117
114
 
118
- def map(dirname, hash = {})
119
- Dir.entries(dirname).each do |file|
120
- next if file =~ /^\./
121
- filename = File.join(dirname, file)
122
- if file == "_attachments"
123
- hash['_attachments'] = map_attachments(filename)
124
- elsif File.directory?(filename)
125
- hash[file] = map(filename)
126
- elsif File.extname(filename) =~ /^\.(js|html)$/
127
- # only .js and .html files are mapped
128
- # but .js is stripped off the key
129
- key = file.sub(/\.js$/, '')
130
- hash[key] = File.read(filename).strip
131
- end
132
- end
133
- hash
134
- end
115
+ # Updates rev in documents hash
116
+ def rev=(new_rev)
117
+ hash["_rev"] = new_rev
118
+ end
135
119
 
136
- def map_attachments(dirname, hash = {}, keys = [])
137
- Dir.entries(dirname).each do |file|
138
- next if file =~ /^\./
139
- filename = File.join(dirname, file)
140
- base = keys + [file]
141
- if File.directory?(filename)
142
- map_attachments filename, hash, base
143
- else
144
- hash[base.join('/')] = {
145
- "content_type" => mime_type(filename),
146
- "data" => base64(File.read(filename)),
147
- }
148
- end
149
- end
150
- hash
151
- end
152
120
 
153
- # CouchDB needs base64 encodings without spaces
154
- def base64(data)
155
- [data].pack("m").gsub(/\s/,'')
156
- end
121
+ # Accessor for couch database
122
+ def database
123
+ @database ||= Couch.database
124
+ end
157
125
 
158
- # detect mime type from filename extension
159
- def mime_type(filename)
160
- ext = File.extname(filename)
161
- EXT_MIME_MAPPING[ext] || 'text/plain'
162
- end
126
+ # Base URL for document
127
+ def base_url
128
+ @base_url ||= File.join(database, id)
129
+ end
130
+
131
+ # URL for accessing design document
132
+ #
133
+ # Takes an optional options hash
134
+ # which gets converted to url encoded options
135
+ # and appended to the documents base url
136
+ #
137
+ def url(options = {})
138
+ base_url + build_options_string(options)
163
139
  end
164
140
 
165
141
  private
166
142
 
167
- def write_doc(doc, dirname, block)
168
- doc.each do |key, value|
169
- next if key == "_attachments"
170
- filename = dirname ? File.join(dirname, key) : key.dup
171
- if value.is_a?(String)
172
- filename << ".js" unless File.extname(filename) == ".html"
173
- block.call filename, "#{value}\n"
174
- else
175
- write_doc value, filename, block
176
- end
143
+ def hash_at(path)
144
+ current_hash = hash
145
+
146
+ parts = path.split('/')
147
+ key = parts.pop
148
+
149
+ parts.each do |part|
150
+ current_hash[part] ||= {}
151
+ current_hash = current_hash[part]
177
152
  end
178
- write_attachments doc["_attachments"], dirname, block if doc["_attachments"]
153
+
154
+ current_hash[key]
179
155
  end
180
156
 
181
- def write_attachments(doc, dirname, block)
182
- dirname = dirname ? File.join(dirname, "_attachments") : "_attachments"
183
-
184
- doc.each do |key, value|
185
- next unless value["data"]
157
+ def set_hash_at(path, value)
158
+ current_hash = hash
159
+
160
+ parts = path.split('/')
161
+ key = parts.pop
162
+
163
+ parts.each do |part|
164
+ current_hash[part] ||= {}
165
+ current_hash = current_hash[part]
166
+ end
167
+
168
+ current_hash[key] = value
169
+ end
186
170
 
187
- block.call File.join(dirname, key), value["data"].unpack("m")
171
+ def build_options_string(options)
172
+ return '' if options.empty?
173
+ options_array = []
174
+ options.each do |key, value|
175
+ options_array << URI.escape([key, value].join('='))
188
176
  end
177
+ '?' + options_array.join("&")
189
178
  end
190
179
 
191
- def hash_with_injected_makros
192
- hash = inject_code_makros(@hash)
193
- inject_json_makros(hash)
180
+ def inject_makros!
181
+ self.hash = inject_code_makro(hash)
182
+ self.hash = inject_json_makro(hash)
194
183
  end
195
184
 
196
- def inject_code_makros(doc)
185
+ def inject_code_makro(doc)
197
186
  doc.each do |key, value|
198
187
  doc[key] = if value.is_a?(String)
199
188
  value.gsub(/\/\/\s*!code.*$/) do |match|
200
189
  filename = match.sub(/^.*!code\s*(\S+).*$/, '\1')
201
- File.read(File.join(Couch.root, 'lib', filename)).strip
190
+ hash_at File.join('lib', filename)
202
191
  end
203
192
  elsif value.is_a?(Hash)
204
- inject_code_makros(value)
193
+ inject_code_makro(value)
205
194
  else
206
195
  value
207
196
  end
208
197
  end
198
+
209
199
  doc
210
200
  end
211
201
 
212
- def inject_json_makros(doc)
202
+ def inject_json_makro(doc)
213
203
  doc.each do |key, value|
214
204
  doc[key] = if value.is_a?(String)
215
205
  value.gsub(/\/\/\s*!json.*$/) do |match|
216
206
  filename = match.sub(/^.*!json\s*(\S+).*$/, '\1')
217
- 'var %s = %s;' % [filename.sub(/\..*$/, ''), File.read(File.join(Couch.root, 'lib', filename)).strip.to_json]
207
+ 'var %s = %s;' % [filename.sub(/\..*$/, ''), hash_at(File.join('lib', filename)).to_json]
218
208
  end
219
209
  elsif value.is_a?(Hash)
220
- inject_json_makros(value)
210
+ inject_json_makro(value)
221
211
  else
222
212
  value
223
213
  end
224
214
  end
215
+
216
+ doc
217
+ end
218
+
219
+ def reject_makros!
220
+ # TODO: recursive walk libs
221
+ libs = hash["lib"]
222
+ return if libs.nil? || libs.empty?
223
+ # Attention: replace json makros first!
224
+ self.hash = reject_json_makro(hash, libs)
225
+ self.hash = reject_code_makro(hash, libs)
226
+ end
227
+
228
+ def reject_code_makro(doc, libs)
229
+ doc = doc.dup
230
+ doc.each do |key, value|
231
+ next if key == "lib"
232
+
233
+ if value.is_a?(String)
234
+ libs.each do |name, content|
235
+ # only try substituting strings
236
+ next unless content.is_a?(String)
237
+ next unless value.include?(content)
238
+ doc[key] = value.gsub(content, "// !code #{name}")
239
+ end
240
+ elsif value.is_a?(Hash)
241
+ doc[key] = reject_code_makro(value, libs)
242
+ end
243
+ end
244
+ doc
245
+ end
246
+
247
+ def reject_json_makro(doc, libs)
248
+ doc.each do |key, value|
249
+ next if key == "lib"
250
+ if value.is_a?(String)
251
+ libs.each do |name, content|
252
+ # only try substituting strings
253
+ next unless content.is_a?(String)
254
+ json = 'var %s = %s;' % [name.sub(/\..*$/, ''), content.to_json]
255
+ next unless value.include?(json)
256
+ doc[key] = value.gsub(json, "// !json #{name}")
257
+ end
258
+ elsif value.is_a?(Hash)
259
+ doc[key] = reject_json_makro(value, libs)
260
+ end
261
+ end
225
262
  doc
226
263
  end
264
+
265
+ def reduce_attachments!
266
+ return hash unless hash["_attachments"]
267
+ attachments = {}
268
+ hash["_attachments"].each do |key, value|
269
+ data = value["data"]
270
+ next unless data
271
+ attachments.update key => decode_attachment(data)
272
+ end
273
+ hash.update "_attachments" => attachments
274
+ end
275
+
276
+ def map_attachments!
277
+ return unless hash["_attachments"]
278
+ attachments = {}
279
+ flatten_attachements(hash["_attachments"]).each do |key, value|
280
+ attachments.update key => {
281
+ "data" => encode_attachment(value),
282
+ "content_type" => mime_type_for(key)
283
+ }
284
+ end
285
+ self.hash.update "_attachments" => attachments
286
+ end
287
+
288
+ def flatten_attachements(doc, base = nil)
289
+ result = {}
290
+ doc.each do |key, value|
291
+ new_base = base ? [base, key].join('/') : key
292
+ if value.is_a?(Hash)
293
+ result.update flatten_attachements(value, new_base)
294
+ else
295
+ result.update new_base => value
296
+ end
297
+ end
298
+ result
299
+ end
300
+
301
+ def decode_attachment(data)
302
+ data.unpack("m").first
303
+ end
304
+
305
+ def encode_attachment(data)
306
+ [data].pack("m").gsub(/\s+/,'')
307
+ end
308
+
309
+ def mime_type_for(filename)
310
+ ext = File.extname(filename)
311
+ MIME_TYPE_MAPPING[ext] || 'text/plain'
312
+ end
227
313
  end
228
314
  end
@@ -13,7 +13,7 @@ module Couch::Generators
13
13
  template "couchrc", ".couchrc"
14
14
  copy_file "README"
15
15
  copy_file "gitignore", ".gitignore" unless options[:skip_git]
16
- template "_id.js"
16
+ template "_id"
17
17
  copy_file "validate_doc_update.js"
18
18
  empty_directory "lists"
19
19
  empty_directory "shows"
@@ -1,3 +1,3 @@
1
1
  module Couch
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,313 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'couch/design_document'
3
+
4
+ describe "DesignDocument" do
5
+ before do
6
+ @doc = Couch::DesignDocument.new
7
+ end
8
+
9
+ describe "read" do
10
+ it "should assign key-value pair" do
11
+ @doc.read("key") do |filename|
12
+ "value"
13
+ end
14
+ @doc.hash.should == { "key" => "value" }
15
+ end
16
+
17
+ it "should assign two key-value pairs" do
18
+ @doc.read("key1", "key2") do |filename|
19
+ "value"
20
+ end
21
+ @doc.hash.should == { "key1" => "value", "key2" => "value" }
22
+ end
23
+
24
+ it "should assign an array of two key-value pairs" do
25
+ @doc.read(["key1", "key2"]) do |filename|
26
+ "value"
27
+ end
28
+ @doc.hash.should == { "key1" => "value", "key2" => "value" }
29
+ end
30
+
31
+ it "should assign nested hash" do
32
+ @doc.read("hash/key") do |filename|
33
+ "value"
34
+ end
35
+ @doc.hash.should == { "hash" => { "key" => "value" } }
36
+ end
37
+
38
+ it "should assign deep nested hash" do
39
+ @doc.read("hash/hash/key") do |filename|
40
+ "value"
41
+ end
42
+ @doc.hash.should == { "hash" => { "hash" => { "key" => "value" } } }
43
+ end
44
+
45
+ describe "_attachments encoding and content_type" do
46
+ it "should proper encode and add plain text content type" do
47
+ @doc.read("_attachments/key") do |filename|
48
+ "value"
49
+ end
50
+ @doc.hash.should == { "_attachments" => { "key" => { "data" => "dmFsdWU=", "content_type" => "text/plain" } } }
51
+ end
52
+
53
+ it "should proper encode and add html content type" do
54
+ @doc.read("_attachments/key.html") do |filename|
55
+ "value"
56
+ end
57
+ @doc.hash.should == { "_attachments" => { "key.html" => { "data" => "dmFsdWU=", "content_type" => "text/html" } } }
58
+ end
59
+
60
+ it "should proper encode nested keys" do
61
+ @doc.read("_attachments/hash/key") do |filename|
62
+ "value"
63
+ end
64
+ @doc.hash.should == { "_attachments" => { "hash/key" => { "data" => "dmFsdWU=", "content_type" => "text/plain" } } }
65
+ end
66
+ end
67
+
68
+ describe "exclude files should not be mapped" do
69
+ it "should not map README" do
70
+ @doc.read("README") do |filename|
71
+ "value"
72
+ end
73
+ @doc.hash.should == {}
74
+ end
75
+ end
76
+
77
+ describe "javascript files should be stripped off extension" do
78
+ it "should strip validate_doc_update extension" do
79
+ @doc.read("validate_doc_update.js") do |filename|
80
+ "value"
81
+ end
82
+ @doc.hash.should == { "validate_doc_update" => "value" }
83
+ end
84
+
85
+ it "should strip lists/my_list.js" do
86
+ @doc.read("lists/my_list.js") do |filename|
87
+ "value"
88
+ end
89
+ @doc.hash.should == { "lists" => { "my_list" => "value" } }
90
+ end
91
+
92
+ it "should strip views/my_view/map.js" do
93
+ @doc.read("views/my_view/map.js") do |filename|
94
+ "value"
95
+ end
96
+ @doc.hash.should == { "views" => { "my_view" => { "map" => "value" } } }
97
+ end
98
+ end
99
+
100
+ describe "code makro" do
101
+ it "should expand code" do
102
+ idx = 0
103
+ @doc.read("key", "lib/code.js") do |filename|
104
+ case idx += 1
105
+ when 1
106
+ "// !code code.js"
107
+ when 2
108
+ "value"
109
+ end
110
+ end
111
+ @doc.hash.should == { "key" => "value", "lib" => { "code.js" => "value" } }
112
+ end
113
+ end
114
+
115
+ describe "json makro" do
116
+ it "should expand json" do
117
+ idx = 0
118
+ @doc.read("key", "lib/json.json") do |filename|
119
+ case idx += 1
120
+ when 1
121
+ "// !json json.json"
122
+ when 2
123
+ "value"
124
+ end
125
+ end
126
+ @doc.hash.should == { "key" => "var json = \"value\";", "lib" => { "json.json" => "value" } }
127
+ end
128
+ end
129
+ end
130
+
131
+ describe "write" do
132
+ it "should return key-value pair" do
133
+ @doc.hash = { "key" => "value" }
134
+ filename, content = nil, nil
135
+ @doc.write do |key, value|
136
+ filename, content = key, value
137
+ end
138
+ filename.should == "key"
139
+ content.should == "value"
140
+ end
141
+
142
+ it "should return subdirectory for nested hash" do
143
+ @doc.hash = { "hash" => { "key" => "value" } }
144
+ filename, content = nil, nil
145
+ @doc.write do |key, value|
146
+ filename, content = key, value
147
+ end
148
+ filename.should == "hash/key"
149
+ content.should == "value"
150
+ end
151
+
152
+ it "should return subdirectory for nested hash" do
153
+ @doc.hash = { "hash" => { "hash" => { "key" => "value" } } }
154
+ filename, content = nil, nil
155
+ @doc.write do |key, value|
156
+ filename, content = key, value
157
+ end
158
+ filename.should == "hash/hash/key"
159
+ content.should == "value"
160
+ end
161
+
162
+ it "should return decoded _attachments data" do
163
+ @doc.hash = { "_attachments" => { "key" => { "data" => "dmFsdWU=" } } }
164
+ filename, content = nil, nil
165
+ @doc.write do |key, value|
166
+ filename, content = key, value
167
+ end
168
+ filename.should == "_attachments/key"
169
+ content.should == "value"
170
+ end
171
+
172
+ describe "javascript extensions" do
173
+ it "should append validate_doc_update" do
174
+ @doc.hash = { "validate_doc_update" => "value" }
175
+ filename = nil
176
+ @doc.write do |key, value|
177
+ filename = key
178
+ end
179
+ filename.should == "validate_doc_update.js"
180
+ end
181
+
182
+ it "should append lists/my_list" do
183
+ @doc.hash = { "lists" => { "my_list" => "value" } }
184
+ filename = nil
185
+ @doc.write do |key, value|
186
+ filename = key
187
+ end
188
+ filename.should == "lists/my_list.js"
189
+ end
190
+
191
+ it "should append views/my_view/map" do
192
+ @doc.hash = { "views" => { "my_view" => { "map" => "value" } } }
193
+ filename = nil
194
+ @doc.write do |key, value|
195
+ filename = key
196
+ end
197
+ filename.should == "views/my_view/map.js"
198
+ end
199
+ end
200
+
201
+ describe "code makro" do
202
+ it "should reject code" do
203
+ @doc.hash = { "key" => "value", "lib" => { "code.js" => "value" } }
204
+ content = nil
205
+ @doc.write do |key, value|
206
+ content = value
207
+ end
208
+ @doc.hash.should == { "key" => "// !code code.js", "lib" => { "code.js" => "value" } }
209
+ content.should == "// !code code.js"
210
+ end
211
+ end
212
+
213
+ describe "json makro" do
214
+ it "should reject json" do
215
+ @doc.hash = { "key" => "var json = \"value\";", "lib" => { "json.json" => "value" } }
216
+ content = nil
217
+ @doc.write do |key, value|
218
+ content = value
219
+ end
220
+ @doc.hash.should == { "key" => "// !json json.json", "lib" => { "json.json" => "value" } }
221
+ content.should == "// !json json.json"
222
+ end
223
+ end
224
+ end
225
+
226
+ describe "json" do
227
+ it "should convert key-value pair" do
228
+ @doc.hash = { "key" => "value" }
229
+ @doc.json.should == '{"key":"value"}'
230
+ end
231
+
232
+ it "should convert nested hash" do
233
+ @doc.hash = { "hash" => { "key" => "value" } }
234
+ @doc.json.should == '{"hash":{"key":"value"}}'
235
+ end
236
+ end
237
+
238
+ describe "json=" do
239
+ it "should read key-value pair" do
240
+ @doc.json = '{"key":"value"}'
241
+ @doc.hash.should == { "key" => "value" }
242
+ end
243
+
244
+ it "should read nested hash" do
245
+ @doc.json = '{"hash":{"key":"value"}}'
246
+ @doc.hash.should == { "hash" => { "key" => "value" } }
247
+ end
248
+ end
249
+
250
+ describe "id accessor" do
251
+ it "should return id from hash" do
252
+ @doc.hash = { "_id" => "my_id" }
253
+ @doc.id.should == "my_id"
254
+ end
255
+
256
+ it "should return id from couch" do
257
+ Couch.stub!(:id).and_return("my_id")
258
+ @doc.hash = {}
259
+ @doc.id.should == "my_id"
260
+ end
261
+ end
262
+
263
+ describe "rev accessor" do
264
+ it "should return rev from hash" do
265
+ @doc.hash = { "_rev" => "my_rev" }
266
+ @doc.rev.should == "my_rev"
267
+ end
268
+
269
+ it "should return rev from couch" do
270
+ Couch.stub!(:rev).and_return("my_rev")
271
+ @doc.hash = {}
272
+ @doc.rev.should == "my_rev"
273
+ end
274
+
275
+ it "should update rev in hash" do
276
+ @doc.hash = {}
277
+ @doc.rev = "my_rev"
278
+ @doc.hash.should == { "_rev" => "my_rev" }
279
+ end
280
+ end
281
+
282
+ describe "database accessor" do
283
+ it "should return database couch" do
284
+ Couch.stub!(:database).and_return("my_db")
285
+ @doc.database.should == "my_db"
286
+ end
287
+ end
288
+
289
+ describe "base url" do
290
+ it "should combine database and id" do
291
+ @doc.should_receive(:id).and_return("my_id")
292
+ @doc.should_receive(:database).and_return("my_db")
293
+ @doc.base_url.should == "my_db/my_id"
294
+ end
295
+ end
296
+
297
+ describe "url" do
298
+ it "should return base_url without options" do
299
+ @doc.should_receive(:base_url).and_return("my_base_url")
300
+ @doc.url.should == "my_base_url"
301
+ end
302
+
303
+ it "should return base_url with options string" do
304
+ @doc.should_receive(:base_url).and_return("my_base_url")
305
+ @doc.url(:key => :value).should == "my_base_url?key=value"
306
+ end
307
+
308
+ it "should return base_url with properly escaped options string" do
309
+ @doc.should_receive(:base_url).and_return("my_base_url")
310
+ @doc.url(:key => "value value").should == "my_base_url?key=value%20value"
311
+ end
312
+ end
313
+ end
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Couch" do
4
+ it "should return .couchrc as config filename" do
5
+ Couch::CONFIG_FILENAME.should == '.couchrc'
6
+ end
7
+ end
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'couch'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
8
7
  - 2
9
- version: 0.1.2
8
+ - 0
9
+ version: 0.2.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Johannes J. Schmidt
@@ -74,6 +74,20 @@ dependencies:
74
74
  version: 3.0.0.beta
75
75
  type: :runtime
76
76
  version_requirements: *id004
77
+ - !ruby/object:Gem::Dependency
78
+ name: rspec
79
+ prerelease: false
80
+ requirement: &id005 !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ segments:
85
+ - 1
86
+ - 2
87
+ - 9
88
+ version: 1.2.9
89
+ type: :development
90
+ version_requirements: *id005
77
91
  description: With Couch you can easy build a standalone CouchDB application.
78
92
  email: schmidt@netzmerk.com
79
93
  executables:
@@ -109,7 +123,7 @@ files:
109
123
  - lib/couch/generators/application/templates/README
110
124
  - lib/couch/generators/application/templates/_attachments/index.html
111
125
  - lib/couch/generators/application/templates/_attachments/stylesheets/application.css
112
- - lib/couch/generators/application/templates/_id.js
126
+ - lib/couch/generators/application/templates/_id
113
127
  - lib/couch/generators/application/templates/couchrc
114
128
  - lib/couch/generators/application/templates/gitignore
115
129
  - lib/couch/generators/application/templates/lib/mustache.js
@@ -131,6 +145,10 @@ files:
131
145
  - lib/couch/generators/view/templates/map.js
132
146
  - lib/couch/generators/view/view_generator.rb
133
147
  - lib/couch/version.rb
148
+ - spec/couch/design_document_spec.rb
149
+ - spec/couch_spec.rb
150
+ - spec/spec.opts
151
+ - spec/spec_helper.rb
134
152
  has_rdoc: true
135
153
  homepage: http://github.com/jo/couch
136
154
  licenses: []
@@ -161,5 +179,7 @@ rubygems_version: 1.3.6
161
179
  signing_key:
162
180
  specification_version: 3
163
181
  summary: Standalone CouchDB Application Development Suite
164
- test_files: []
165
-
182
+ test_files:
183
+ - spec/spec_helper.rb
184
+ - spec/couch/design_document_spec.rb
185
+ - spec/couch_spec.rb