makura 2009.05.27

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.
@@ -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