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 +1 -1
- data/couch.gemspec +15 -3
- data/lib/couch.rb +11 -3
- data/lib/couch/actions/pull.rb +5 -4
- data/lib/couch/actions/push.rb +24 -9
- data/lib/couch/actions/routes.rb +2 -2
- data/lib/couch/design_document.rb +244 -158
- data/lib/couch/generators/application/application_generator.rb +1 -1
- data/lib/couch/generators/application/templates/{_id.js → _id} +0 -0
- data/lib/couch/version.rb +1 -1
- data/spec/couch/design_document_spec.rb +313 -0
- data/spec/couch_spec.rb +7 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- metadata +25 -5
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
|
-
|
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
|
data/couch.gemspec
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{couch}
|
8
|
-
s.version = "0.
|
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
|
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
|
|
data/lib/couch.rb
CHANGED
@@ -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.
|
17
|
-
@
|
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
|
data/lib/couch/actions/pull.rb
CHANGED
@@ -9,10 +9,11 @@ module Couch
|
|
9
9
|
add_runtime_options!
|
10
10
|
|
11
11
|
def pull
|
12
|
-
|
12
|
+
doc = DesignDocument.new
|
13
|
+
say "Pulling %s" % doc.url
|
13
14
|
|
14
|
-
resp = RestClient.get
|
15
|
-
doc =
|
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!" %
|
24
|
+
say "Error: Document %s does not exist!" % doc.id
|
24
25
|
end
|
25
26
|
end
|
26
27
|
end
|
data/lib/couch/actions/push.rb
CHANGED
@@ -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
|
11
|
-
say "Created database %s" %
|
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
|
-
|
17
|
-
|
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
|
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
|
25
|
-
file <<
|
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
|
data/lib/couch/actions/routes.rb
CHANGED
@@ -10,14 +10,14 @@ module Couch
|
|
10
10
|
end
|
11
11
|
|
12
12
|
say 'Lists:'
|
13
|
-
Dir.glob(File.join(destination_root, "lists
|
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
|
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
|
-
|
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
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
#
|
17
|
-
|
18
|
-
|
19
|
-
|
21
|
+
# Files that should not be included in document
|
22
|
+
EXCLUDE_FILES = %w[
|
23
|
+
README
|
24
|
+
]
|
20
25
|
|
21
|
-
|
22
|
-
def rev
|
23
|
-
@hash["_rev"]
|
24
|
-
end
|
26
|
+
attr_accessor :hash
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
hash_with_injected_makros.to_json
|
28
|
+
def initialize
|
29
|
+
@hash = {}
|
29
30
|
end
|
30
31
|
|
31
|
-
#
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
56
|
+
map_attachments!
|
57
|
+
inject_makros!
|
58
|
+
end
|
51
59
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
58
|
-
|
59
|
-
File.join(
|
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
|
-
|
92
|
+
# Returns a JSON string representation of the documents hash
|
93
|
+
#
|
94
|
+
def json
|
95
|
+
hash.to_json
|
96
|
+
end
|
63
97
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
121
|
+
# Accessor for couch database
|
122
|
+
def database
|
123
|
+
@database ||= Couch.database
|
124
|
+
end
|
157
125
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
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
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
-
|
153
|
+
|
154
|
+
current_hash[key]
|
179
155
|
end
|
180
156
|
|
181
|
-
def
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
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
|
-
|
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
|
192
|
-
hash =
|
193
|
-
|
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
|
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.
|
190
|
+
hash_at File.join('lib', filename)
|
202
191
|
end
|
203
192
|
elsif value.is_a?(Hash)
|
204
|
-
|
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
|
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(/\..*$/, ''),
|
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
|
-
|
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
|
16
|
+
template "_id"
|
17
17
|
copy_file "validate_doc_update.js"
|
18
18
|
empty_directory "lists"
|
19
19
|
empty_directory "shows"
|
File without changes
|
data/lib/couch/version.rb
CHANGED
@@ -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
|
data/spec/couch_spec.rb
ADDED
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
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
|
-
|
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
|
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
|