couch 0.1.2 → 0.2.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.
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