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 +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
|