makura 2009.05.27

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,56 @@
1
+ module Makura
2
+ module Plugin
3
+ module Pager
4
+ module SingletonMethods
5
+ def pager(page, limit)
6
+ Makura::Plugin::Pager::Pagination.new(self, :pager, page, limit)
7
+ end
8
+ end
9
+
10
+ class Pagination
11
+ def initialize(model, view, page, limit)
12
+ @model, @view, @page, @limit = model, view, page, limit
13
+ end
14
+
15
+ # /pager/_all_docs?count=10&group=true
16
+ # /pager/_all_docs?startkey=%224f9dca1c66121f9320a69553546db07a%22&startkey_docid=4f9dca1c66121f9320a69553546db07a&skip=1&descending=false&count=10&group=true
17
+ # /pager/_all_docs?startkey=%22_design%2FUser%22&startkey_docid=_design%2FUser&skip=1&descending=false&count=10&group=true
18
+ # /pager/_all_docs?startkey=%22d850f0801686b85035680bb6f38d5c5c%22&startkey_docid=d850f0801686b85035680bb6f38d5c5c&skip=1&descending=false&count=10&group=true
19
+
20
+ # NOTE:
21
+ # * descending should be true if you page backwards
22
+
23
+ include Enumerable
24
+
25
+ def each(start_id = nil, descending = false, &block)
26
+ opts = {
27
+ :count => @limit,
28
+ :group => true,
29
+ :descending => descending,
30
+ # :include_docs => true,
31
+ }
32
+
33
+ if start_id
34
+ opts[:skip] = 1
35
+ opts[:startkey_docid] = start_id
36
+ opts[:startkey] = start_id
37
+ end
38
+
39
+ @model.view(@view, opts).each(&block)
40
+ end
41
+
42
+ def count
43
+ end
44
+
45
+ def first_page?
46
+ end
47
+
48
+ def last_page?
49
+ end
50
+
51
+ def empty?
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,203 @@
1
+ module Makura
2
+ class Server
3
+ include HTTPMethods
4
+ attr_accessor :uri, :cache_ttl, :cache_tries
5
+
6
+ COUCHDB_URI = 'http://localhost:5984'
7
+ CACHE_TTL = 5
8
+ CACHE_TRIES = 2
9
+
10
+ # Usage:
11
+ # server = Makura::Server.new
12
+ # #<URI::HTTP:0xb778ce38 URL:http://localhost:5984>
13
+ # server.info
14
+ # {"couchdb"=>"Welcome", "version"=>"0.9.0a718650-incubating"}
15
+
16
+ def initialize(uri = COUCHDB_URI, cache_ttl = CACHE_TTL, cache_tries = CACHE_TRIES)
17
+ @uri = URI(uri.to_s)
18
+ @cache_ttl = cache_ttl
19
+ @cache_tries = cache_tries
20
+ @uuids = UUIDCache.new(self)
21
+ end
22
+
23
+ def inspect
24
+ @uri.inspect
25
+ end
26
+
27
+ # General queries
28
+
29
+ # Answers with general couchdb info, looks like:
30
+ #
31
+ # Usage:
32
+ # server.info
33
+ # # {'couchdb' => 'Welcome', 'version' => '0.9.0a718650-incubating'}
34
+ def info
35
+ get('/')
36
+ end
37
+
38
+ # Answers with configuration info.
39
+ #
40
+ # Usage:
41
+ # server.config
42
+ #
43
+ def config
44
+ get('/_config')
45
+ end
46
+
47
+ # Issue restart of the CouchDB daemon.
48
+ #
49
+ # Usage:
50
+ # server.restart
51
+ # # {'ok' => true}
52
+ def restart
53
+ post('/_restart')
54
+ end
55
+
56
+ # Array of names of databases on the server
57
+ #
58
+ # Usage:
59
+ # server.databases
60
+ # # ["another", "blog", "makura-spec"]
61
+ def databases
62
+ get('/_all_dbs')
63
+ end
64
+
65
+ # Return new database instance using this server instance.
66
+ #
67
+ # Usage:
68
+ # foo = server.database('foo')
69
+ # # #<Makura::Database 'http://localhost:5984/foo'>
70
+ # server.databases
71
+ # # ["another", "blog", "foo", "makura-spec"]
72
+
73
+ def database(name)
74
+ Database.new(self, name)
75
+ end
76
+
77
+ # Answers with an uuid from the UUIDCache.
78
+ #
79
+ # Usage:
80
+ # server.next_uuid
81
+ # # "55fdca746fa5a5b56f5270875477a2cc"
82
+
83
+ def next_uuid
84
+ @uuids.next
85
+ end
86
+
87
+ def start_cache(namespace = 'makura', *servers)
88
+ servers << 'localhost:11211' if servers.empty?
89
+ @cache = MemCache.new(servers, :namespace => namespace, :multithread => true)
90
+ end
91
+
92
+ def stop_cache
93
+ @cache = nil
94
+ end
95
+
96
+ def cached(request, ttl = cache_ttl, tries = cache_tries)
97
+ key = request[:url]
98
+
99
+ unless response = @cache.get(key)
100
+ response = execute(request)
101
+ @cache.add(key, response, ttl)
102
+ end
103
+
104
+ return response
105
+ rescue MemCache::MemCacheError => error
106
+ servers = @cache.servers.map{|s| "#{s.host}:#{s.port}"}
107
+ start_cache(@cache.namespace, *servers)
108
+ tries -= 1
109
+ retry if tries > 0
110
+ warn "[makura caching disabled] #{error.message}"
111
+ @cache = nil
112
+ execute(request)
113
+ end
114
+
115
+ # Helpers
116
+
117
+ def request(method, path, params = {})
118
+ keep_raw = params.delete(:raw)
119
+ payload = params.delete(:payload)
120
+ payload = payload.to_json if payload and not keep_raw
121
+ headers = {}
122
+
123
+ if content_type = params.delete('Content-Type')
124
+ headers['Content-Type'] = content_type
125
+ end
126
+
127
+ params.delete_if{|k,v| v.nil? }
128
+ uri = uri(path, params).to_s
129
+
130
+ request = {
131
+ :method => method,
132
+ :url => uri,
133
+ :payload => payload,
134
+ :headers => headers}
135
+
136
+ if @cache and request[:method] == :get
137
+ raw = cached(request)
138
+ else
139
+ raw = execute(request)
140
+ end
141
+
142
+ return raw if keep_raw
143
+ json = JSON.parse(raw)
144
+ rescue JSON::ParserError
145
+ return raw
146
+ rescue RestClient::RequestFailed => ex
147
+ raise appropriate_error(ex)
148
+ rescue RestClient::ResourceNotFound => ex
149
+ raise Error::ResourceNotFound, request[:url], ex.backtrace
150
+ rescue Errno::ECONNREFUSED
151
+ raise Error::ConnectionRefused, "Is CouchDB running at #{@uri}?"
152
+ end
153
+
154
+ def execute(request)
155
+ RestClient::Request.execute(request)
156
+ end
157
+
158
+ def appropriate_error(exception)
159
+ body = exception.response.body if exception.respond_to?(:response)
160
+ backtrace = exception.backtrace
161
+
162
+ raise(Error::RequestFailed, exception.message, backtrace) unless body
163
+ raise(Error::RequestFailed, exception.message, backtrace) if body.empty?
164
+
165
+ json = JSON.parse(body)
166
+ error, reason = json['error'], json['reason']
167
+
168
+ case error
169
+ when 'bad_request'
170
+ raise(Error::BadRequest, reason, backtrace)
171
+ when 'authorization'
172
+ raise(Error::Authorization, reason, backtrace)
173
+ when 'not_found'
174
+ raise(Error::NotFound, reason, backtrace)
175
+ when 'file_exists'
176
+ raise(Error::FileExists, reason, backtrace)
177
+ when 'missing_rev'
178
+ raise(Error::MissingRevision, reason, backtrace)
179
+ when 'conflict'
180
+ raise(Error::Conflict, reason, backtrace)
181
+ else
182
+ raise(Error::RequestFailed, json.inspect, backtrace)
183
+ end
184
+ end
185
+
186
+ JSON_PARAMS = %w[key startkey endkey]
187
+
188
+ def paramify(hash)
189
+ hash.map{|k,v|
190
+ k = k.to_s
191
+ v = v.to_json if JSON_PARAMS.include?(k)
192
+ "#{Makura.escape(k)}=#{Makura.escape(v)}"
193
+ }.join('&')
194
+ end
195
+
196
+ def uri(path = '/', params = {})
197
+ uri = @uri.dup
198
+ uri.path = (path[0,1] == '/' ? path : "/#{path}").squeeze('/')
199
+ uri.query = paramify(params) unless params.empty?
200
+ uri
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,23 @@
1
+ module Makura
2
+ class UUIDCache
3
+ attr_accessor :min, :max, :server, :pretty
4
+
5
+ def initialize(server, min = 500, max = 1500, pretty = true)
6
+ @server, @min, @max, @pretty = server, min, max, pretty
7
+ @uuids = []
8
+ end
9
+
10
+ def next
11
+ fetch if @uuids.size < min
12
+ @uuids.shift
13
+ end
14
+
15
+ def fetch(count = 0)
16
+ todo = max - @uuids.size
17
+ count = [min, todo, max].sort[1]
18
+ uuids = @server.get('/_uuids', :count => count)['uuids']
19
+ uuids.map!{|u| Makura.pretty_from_md5(u) } if pretty
20
+ @uuids.concat(uuids)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module Makura
2
+ VERSION = "2009.05.27"
3
+ end
data/makura.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{makura}
5
+ s.version = "2009.05.27"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Michael 'manveru' Fellinger"]
9
+ s.date = %q{2009-05-27}
10
+ s.default_executable = %q{makura}
11
+ s.email = %q{m.fellinger@gmail.com}
12
+ s.executables = ["makura"]
13
+ s.files = ["CHANGELOG", "COPYING", "MANIFEST", "README.md", "Rakefile", "bin/makura", "example/blog.rb", "example/couch/map/author_all.js", "example/couch/map/author_posts.js", "example/couch/map/post_all.js", "example/couch/map/post_comments.js", "example/couch/reduce/sum_length.js", "lib/makura.rb", "lib/makura/database.rb", "lib/makura/design.rb", "lib/makura/error.rb", "lib/makura/http_methods.rb", "lib/makura/layout.rb", "lib/makura/model.rb", "lib/makura/plugin/localize.rb", "lib/makura/plugin/pager.rb", "lib/makura/server.rb", "lib/makura/uuid_cache.rb", "lib/makura/version.rb", "makura.gemspec", "tasks/authors.rake", "tasks/bacon.rake", "tasks/changelog.rake", "tasks/copyright.rake", "tasks/gem.rake", "tasks/gem_installer.rake", "tasks/git.rake", "tasks/grancher.rake", "tasks/manifest.rake", "tasks/metric_changes.rake", "tasks/rcov.rake", "tasks/release.rake", "tasks/reversion.rake", "tasks/todo.rake", "tasks/traits.rake", "tasks/yard.rake", "tasks/ycov.rake"]
14
+ s.homepage = %q{http://github.com/manveru/makura}
15
+ s.require_paths = ["lib"]
16
+ s.rubygems_version = %q{1.3.3}
17
+ s.summary = %q{Ruby wrapper around the CouchDB REST API.}
18
+
19
+ if s.respond_to? :specification_version then
20
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
21
+ s.specification_version = 3
22
+
23
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
24
+ s.add_runtime_dependency(%q<rest-client>, [">= 0.8.1"])
25
+ else
26
+ s.add_dependency(%q<rest-client>, [">= 0.8.1"])
27
+ end
28
+ else
29
+ s.add_dependency(%q<rest-client>, [">= 0.8.1"])
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ # Once git has a fix for the glibc in handling .mailmap and another fix for
2
+ # allowing empty mail address to be mapped in .mailmap we won't have to handle
3
+ # them manually.
4
+
5
+ desc 'Update AUTHORS'
6
+ task :authors do
7
+ authors = Hash.new(0)
8
+
9
+ `git shortlog -nse`.scan(/(\d+)\s(.+)\s<(.*)>$/) do |count, name, email|
10
+ case name
11
+ when "ahoward"
12
+ name, email = "Ara T. Howard", "ara.t.howard@gmail.com"
13
+ when "Martin Hilbig blueonyx@dev-area.net"
14
+ name, email = "Martin Hilbig", "blueonyx@dev-area.net"
15
+ when "Michael Fellinger m.fellinger@gmail.com"
16
+ name, email = "Michael Fellinger", "m.fellinger@gmail.com"
17
+ end
18
+
19
+ authors[[name, email]] += count.to_i
20
+ end
21
+
22
+ File.open('AUTHORS', 'w+') do |io|
23
+ io.puts "Following persons have contributed to #{GEMSPEC.name}."
24
+ io.puts '(Sorted by number of submitted patches, then alphabetically)'
25
+ io.puts ''
26
+ authors.sort_by{|(n,e),c| [-c, n.downcase] }.each do |(name, email), count|
27
+ io.puts("%6d %s <%s>" % [count, name, email])
28
+ end
29
+ end
30
+ end
data/tasks/bacon.rake ADDED
@@ -0,0 +1,66 @@
1
+ desc 'Run all bacon specs with pretty output'
2
+ task :bacon => :install_dependencies do
3
+ require 'open3'
4
+ require 'scanf'
5
+ require 'matrix'
6
+
7
+ specs = PROJECT_SPECS
8
+
9
+ some_failed = false
10
+ specs_size = specs.size
11
+ len = specs.map{|s| s.size }.sort.last
12
+ total_tests = total_assertions = total_failures = total_errors = 0
13
+ totals = Vector[0, 0, 0, 0]
14
+
15
+ red, yellow, green = "\e[31m%s\e[0m", "\e[33m%s\e[0m", "\e[32m%s\e[0m"
16
+ left_format = "%4d/%d: %-#{len + 11}s"
17
+ spec_format = "%d specifications (%d requirements), %d failures, %d errors"
18
+
19
+ specs.each_with_index do |spec, idx|
20
+ print(left_format % [idx + 1, specs_size, spec])
21
+
22
+ Open3.popen3(RUBY, spec) do |sin, sout, serr|
23
+ out = sout.read.strip
24
+ err = serr.read.strip
25
+
26
+ # this is conventional, see spec/innate/state/fiber.rb for usage
27
+ if out =~ /^Bacon::Error: (needed .*)/
28
+ puts(yellow % ("%6s %s" % ['', $1]))
29
+ else
30
+ total = nil
31
+
32
+ out.each_line do |line|
33
+ scanned = line.scanf(spec_format)
34
+
35
+ next unless scanned.size == 4
36
+
37
+ total = Vector[*scanned]
38
+ break
39
+ end
40
+
41
+ if total
42
+ totals += total
43
+ tests, assertions, failures, errors = total_array = total.to_a
44
+
45
+ if tests > 0 && failures + errors == 0
46
+ puts((green % "%6d passed") % tests)
47
+ else
48
+ some_failed = true
49
+ puts(red % " failed")
50
+ puts out unless out.empty?
51
+ puts err unless err.empty?
52
+ end
53
+ else
54
+ some_failed = true
55
+ puts(red % " failed")
56
+ puts out unless out.empty?
57
+ puts err unless err.empty?
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ total_color = some_failed ? red : green
64
+ puts(total_color % (spec_format % totals.to_a))
65
+ exit 1 if some_failed
66
+ end
@@ -0,0 +1,18 @@
1
+ desc 'update changelog'
2
+ task :changelog do
3
+ File.open('CHANGELOG', 'w+') do |changelog|
4
+ `git log -z --abbrev-commit`.split("\0").each do |commit|
5
+ next if commit =~ /^Merge: \d*/
6
+ ref, author, time, _, title, _, message = commit.split("\n", 7)
7
+ ref = ref[/commit ([0-9a-f]+)/, 1]
8
+ author = author[/Author: (.*)/, 1].strip
9
+ time = Time.parse(time[/Date: (.*)/, 1]).utc
10
+ title.strip!
11
+
12
+ changelog.puts "[#{ref} | #{time}] #{author}"
13
+ changelog.puts '', " * #{title}"
14
+ changelog.puts '', message.rstrip if message
15
+ changelog.puts
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ desc "add copyright to all .rb files in the distribution"
2
+ task :copyright do
3
+ ignore = File.readlines('doc/LEGAL').
4
+ select{|line| line.strip!; File.exist?(line)}.
5
+ map{|file| File.expand_path(file)}
6
+
7
+ puts "adding copyright to files that don't have it currently"
8
+ puts PROJECT_COPYRIGHT
9
+ puts
10
+
11
+ Dir['{lib,test}/**/*{.rb}'].each do |file|
12
+ file = File.expand_path(file)
13
+ next if ignore.include? file
14
+ lines = File.readlines(file).map{|l| l.chomp}
15
+ unless lines.first(PROJECT_COPYRIGHT.size) == PROJECT_COPYRIGHT
16
+ puts "#{file} seems to need attention, first 4 lines:"
17
+ puts lines[0..3]
18
+ puts
19
+ end
20
+ end
21
+ end