omgf 0.0.0.GIT

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,6 @@
1
+ # :stopdoc:
2
+ # Copyright (C) 2008-2012, Eric Wong <normalperson@yhbt.net>
3
+ # License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
4
+ # :startdoc:
5
+ module OMGF
6
+ end
@@ -0,0 +1,336 @@
1
+ # -*- encoding: binary -*-
2
+ # :stopdoc:
3
+ # Copyright (C) 2008-2012, Eric Wong <normalperson@yhbt.net>
4
+ # License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
5
+ # :startdoc:
6
+ require "omgf/pool"
7
+ require "time"
8
+ require "json"
9
+ require "mogilefs"
10
+ require "rack"
11
+ require "rack/utils"
12
+ require "rack/mime"
13
+
14
+ # Basic REST interface. This is bug-for-bug compatible with an old app
15
+ # that's been deployed for years in a private LAN.
16
+ #
17
+ # This started out as a WORM (write-once, read-many) system, but
18
+ # eventually gained the ability to handle deletes.
19
+ #
20
+ # This was meant to behave like the MogileFS protocol somewhat (but is
21
+ # pure-HTTP), so it redirects to the storage nodes and bypasses Ruby
22
+ # for bulk I/O when retrieving files. PUTs (writes) still go through
23
+ # Ruby, however.
24
+ class OMGF::HystericalRaisins
25
+ include OMGF::Pool
26
+
27
+ def initialize(opts)
28
+ @default_class_cb = opts[:default_class_cb] || {}
29
+ mg_opts = {
30
+ domain: "any",
31
+ hosts: opts[:hosts],
32
+ fail_timeout: opts[:fail_timeout] || 0.5,
33
+
34
+ # high defaults because of slow seeks on storage nodes (for size verify)
35
+ timeout: opts[:timeout] || 30,
36
+ get_file_data_timeout: opts[:get_file_data_timeout] || 30,
37
+ }
38
+ @reproxy_header = opts[:reproxy_header] || "HTTP_X_OMGF_REPROXY"
39
+ @reproxy_path = opts[:reproxy_path]
40
+ @get_paths_opts = {
41
+ noverify: opts[:noverify],
42
+ pathcount: opts[:pathcount] || 0x7fffffff,
43
+ }
44
+ @put_overwrite_header = opts[:put_overwrite_header] || "HTTP_X_OMGF_FORCE"
45
+ pool_init(mg_opts)
46
+ end
47
+
48
+ # The entry point for Rack
49
+ def call(env)
50
+ case env["REQUEST_METHOD"]
51
+ when "GET"
52
+ get(env)
53
+ when "HEAD"
54
+ head(env)
55
+ when "PUT"
56
+ put(env)
57
+ when "DELETE"
58
+ delete(env)
59
+ else
60
+ r(405)
61
+ end
62
+ rescue MogileFS::Backend::UnknownKeyError,
63
+ MogileFS::Backend::DomainNotFoundError
64
+ r(404, "")
65
+ rescue => e
66
+ logger = env["rack.logger"]
67
+ logger.error "#{e.message} (#{e.class})"
68
+ e.backtrace.each { |line| logger.error(line) }
69
+ r(500, "")
70
+ end
71
+
72
+ # GET /$DOMAIN?prefix=foo - list keys
73
+ # GET /$DOMAIN/$KEY - redirects to FIDs on storage nodes
74
+ def get(env)
75
+ case env["PATH_INFO"].squeeze("/")
76
+ when %r{\A/([^/]+)/(.+)\z} # /$DOMAIN/$KEY
77
+ redirect_key(env, $1, $2)
78
+ when %r{\A/([^/]+)/?\z} # /$DOMAIN
79
+ get_keys(env, $1)
80
+ when "/"
81
+ r(200, "")
82
+ else
83
+ r(404, "")
84
+ end
85
+ end
86
+
87
+ # returns metadata for a given domain/key
88
+ def head(env)
89
+ case env["PATH_INFO"].squeeze("/")
90
+ when %r{\A/([^/]+)/(.+)\z} # HEAD /$DOMAIN/$KEY
91
+ stat_key(env, $1, $2)
92
+ else
93
+ # pass on headers from listing results
94
+ status, headers, _ = get(env)
95
+ [ status, headers, [] ]
96
+ end
97
+ end
98
+
99
+ # HEAD /$DOMAIN/$KEY
100
+ def stat_key(env, domain, key)
101
+ size, paths = mg_size_and_paths(domain, key, @get_paths_opts)
102
+ return r(503, "") unless paths && location = paths[0]
103
+ h = { "Content-Length" => size.to_s }
104
+ fn = filename(h, query(env)) || key
105
+ h["Content-Type"] = key_mime_type(fn)
106
+ unless reproxy?(env, key, h, location)
107
+ paths.each_with_index { |path,i| h["X-URL-#{i}"] = path }
108
+ end
109
+ [ 200, h, [] ]
110
+ end
111
+
112
+ # GET /$DOMAIN/$KEY
113
+ def redirect_key(env, domain, key)
114
+ uris = mg_get_uris(domain, key, @get_paths_opts)
115
+
116
+ return r(503, "") unless uris && dest = uris.shift
117
+
118
+ location = dest.to_s
119
+ h = {
120
+ 'Content-Length' => '0',
121
+ 'Location' => location,
122
+ 'Content-Type' => 'text/html'
123
+ }
124
+
125
+ unless reproxy?(env, key, h, location)
126
+ uris -= [ dest ]
127
+ uris.each_with_index { |uri,i| h["X-Alt-Location-#{i}"] = uri.to_s }
128
+ end
129
+ [ 302, h, [] ]
130
+ end
131
+
132
+ # GET /$DOMAIN/
133
+ def get_keys(env, domain) # :nodoc:
134
+ params = query(env)
135
+ prefix = params["prefix"] || ""
136
+ after = params["after"]
137
+ limit = (params["limit"] || 1000).to_i
138
+
139
+ case env["HTTP_ACCEPT"]
140
+ when "application/json"
141
+ tmp = []
142
+ h = { "Content-Type" => "application/json" }
143
+ mg_list_keys(domain, prefix, after, limit) { |*x| tmp << x }
144
+ tmp = tmp.to_json
145
+ else
146
+ tmp = ""
147
+ h = { "Content-Type" => "text/plain" }
148
+ mg_list_keys(domain, prefix, after, limit) do |dkey,length,devcount|
149
+ tmp << "#{dkey}|#{length}|#{devcount}\n"
150
+ end
151
+ end
152
+ h["Content-Length"] = tmp.size.to_s
153
+
154
+ [ 200, h, [ tmp ] ]
155
+ rescue MogileFS::Backend::UnregDomainError,
156
+ MogileFS::Backend::DomainNotFoundError
157
+ r(404, "Domain not found")
158
+ end
159
+
160
+ # returns a plain-text HTTP response
161
+ def r(code, msg = nil, env = nil) # :nodoc:
162
+ if env && logger = env["rack.logger"]
163
+ logger.warn("#{env['REQUEST_METHOD']} #{env['PATH_INFO']} " \
164
+ "#{code} #{msg.inspect}")
165
+ end
166
+
167
+ if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(code)
168
+ [ code, {}, [] ]
169
+ else
170
+ msg ||= Rack::Utils::HTTP_STATUS_CODES[code] || ""
171
+
172
+ if msg.size > 0
173
+ # using += to not modify original string (owned by Rack)
174
+ msg += "\n"
175
+ end
176
+
177
+ [ code,
178
+ { 'Content-Type' => 'text/plain', 'Content-Length' => msg.size.to_s },
179
+ [ msg ] ]
180
+ end
181
+ end
182
+
183
+ # returns the mime type based on key name
184
+ def key_mime_type(key) # :nodoc:
185
+ /\.([^.]+)\z/ =~ key
186
+ Rack::Mime.mime_type($1) # nil => 'application/octet-stream'
187
+ end
188
+
189
+ # sets Content-Disposition response header if requested by the user
190
+ # in a query string (via "inline" or "attachment" param)
191
+ # Returns requested filename, or nil if invalid
192
+ def filename(h, params) # :nodoc:
193
+ if fn = params['inline'] and Rack::Utils.escape(fn) == fn
194
+ h['Content-Disposition'] = "inline; filename=#{fn}"
195
+ return fn
196
+ elsif fn = (params['attachment'] || params['filename']) and
197
+ Rack::Utils.escape(fn) == fn
198
+ h['Content-Disposition'] = "attachment; filename=#{fn}"
199
+ return fn
200
+ end
201
+ end
202
+
203
+ # This relies on nginx to reproxy paths for us
204
+ def reproxy?(env, key, h, location) # :nodoc:
205
+ return false unless @reproxy_path && env[@reproxy_header].to_i != 0
206
+ %r{/0*(\d+)\.fid\z} =~ location
207
+ fid = $1
208
+ h['ETag'] = %("#{fid}")
209
+ fn = filename(h, query(env)) || key
210
+
211
+ case env["REQUEST_METHOD"]
212
+ when "GET"
213
+ # Fake a Last-Modified time (enough to bust caches)
214
+ h['X-Redirect-Last-Modified'] = Time.at(fid.to_i).httpdate
215
+
216
+ # yes we violate Rack::Lint here, no Content-Type or Content-Length
217
+ h.delete("Content-Length")
218
+ h.delete("Content-Type")
219
+ h['X-Redirect-Content-Type'] = key_mime_type(fn)
220
+ h['X-Accel-Redirect'] = @reproxy_path
221
+ when "HEAD"
222
+ # Fake a Last-Modified time (enough to bust caches)
223
+ h['Last-Modified'] = Time.at(fid.to_i).httpdate
224
+ else
225
+ raise "BUG: bad request method #{env["REQUEST_METHOD"]}"
226
+ end
227
+
228
+ true
229
+ end
230
+
231
+ # PUT /domain/key
232
+ def put(env)
233
+ case env["PATH_INFO"]
234
+ when %r{\A/([^/]+)/(.+)\z} # /$DOMAIN/$KEY
235
+ put_key(env, $1, $2)
236
+ else
237
+ r(404, "")
238
+ end
239
+ end
240
+
241
+ # we're very picky about keys, we only allow keys which won't cause
242
+ # ambiguity when passed through URI escapers/normalizers.
243
+ def bad_key?(key) # :nodoc:
244
+ %r{\A[\w./-]+\z} !~ key ||
245
+ %r{//} =~ key ||
246
+ %r{[./]} =~ "#{key[0]}#{key[-1]}"
247
+ end
248
+
249
+ # PUT /$DOMAIN/$KEY
250
+ def put_key(env, domain, key)
251
+ return r(403, "") if env[@reproxy_header].to_i != 0
252
+ return r(406, "key `#{key}' is not URI-friendly") if bad_key?(key)
253
+ return r(406, "key is too long") if key.size > 128
254
+
255
+ # this was written before MogileFS supported empty files,
256
+ # but empty files waste DB space so we don't support them
257
+ # Not bothering with Transfer-Encoding: chunked, though...
258
+ return r(403, "empty files forbidden") if env["CONTENT_LENGTH"] == "0"
259
+
260
+ params = query(env)
261
+ input = env["rack.input"]
262
+ paths = nil
263
+ retried = false
264
+
265
+ begin
266
+ pool_use(domain) do |mg|
267
+ begin
268
+ # TOCTOU issue, but probably not worth worrying about
269
+ # Nothing we can do about it without explicit MogileFS support
270
+ # or a 3rd-party locking daemon
271
+ paths = mg.get_paths(key)
272
+ if paths && paths[0]
273
+
274
+ # overwriting existing files is not permitted by default
275
+ if env[@put_overwrite_header] != "true"
276
+ # show the existing paths in response
277
+ return r(403, paths.join("\n"))
278
+ end
279
+ end
280
+ rescue MogileFS::Backend::UnknownKeyError
281
+ # good, not clobbering anything
282
+ end
283
+
284
+ # the original deployment of this created a class for every
285
+ # domain with the same class having the same name as the domain
286
+ klass = params['class'] || @default_class_cb[domain] || "default"
287
+ mg.store_file(key, klass, input)
288
+ end # pool_use
289
+
290
+ # should always return 201 if ! found, but we keep 200 for legacy
291
+ # compat if they're not logged in (via REMOTE_USER)
292
+ status = paths ? 204 : (env["REMOTE_USER"] ? 201 : 200)
293
+ r(status, "")
294
+ rescue MogileFS::Backend::UnregDomainError,
295
+ MogileFS::Backend::DomainNotFoundError
296
+ r(406, "Invalid domain: #{domain}")
297
+ rescue => e
298
+ if retried == false && input.respond_to?(:rewind)
299
+ begin
300
+ retried = true
301
+ input.rewind # may raise on future perfectly compliant Rack servers
302
+ env["rack.logger"].warn("#{e.message} (#{e.class})")
303
+ retry
304
+ rescue
305
+ end
306
+ end
307
+
308
+ raise
309
+ end
310
+ end
311
+
312
+ # DELETE /domain/key
313
+ def delete(env)
314
+ case env["PATH_INFO"]
315
+ when %r{\A/([^/]+)/(.+)\z} # /$DOMAIN/$KEY
316
+ delete_key(env, $1, $2)
317
+ else
318
+ r(404, "")
319
+ end
320
+ end
321
+
322
+ # DELETE /domain/key
323
+ def delete_key(env, domain, key)
324
+ pool_use(domain) { |mg| mg.delete(key) }
325
+ r(204, "")
326
+ rescue MogileFS::Backend::UnregDomainError,
327
+ MogileFS::Backend::DomainNotFoundError
328
+ r(406, "Invalid domain: #{domain}")
329
+ rescue MogileFS::Backend::UnknownKeyError
330
+ r(404, "")
331
+ end
332
+
333
+ def query(env)
334
+ Rack::Utils.parse_query(env["QUERY_STRING"])
335
+ end
336
+ end
@@ -0,0 +1,48 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2008-2012, Eric Wong <normalperson@yhbt.net>
3
+ # License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
4
+ require "thread"
5
+ require "omgf"
6
+
7
+ module OMGF::Pool
8
+ # TODO: backport these improvements to MogileFS::Pool (but break compat?)
9
+ def pool_init(mg_opts)
10
+ @mg_opts = mg_opts
11
+ @pool = []
12
+ @lock = Mutex.new
13
+ end
14
+
15
+ def pool_use(domain)
16
+ # Array#pop is faster than Queue#pop since Queue#pop shifts off
17
+ # the array internally, and taking the last element off the end is faster.
18
+ mg = @lock.synchronize { @pool.pop || MogileFS::MogileFS.new(@mg_opts) }
19
+ mg.domain = domain
20
+ rv = yield mg
21
+ @lock.synchronize do
22
+ if @pool.size < 3
23
+ @pool << mg
24
+ mg = nil # prevent shutdown
25
+ end
26
+ end
27
+ rv
28
+ ensure
29
+ # shutdown if we didn't return to the pool
30
+ if mg
31
+ mg.backend.shutdown rescue nil
32
+ end
33
+ end
34
+
35
+ def mg_list_keys(domain, prefix, after, limit, &block) # :nodoc:
36
+ pool_use(domain) { |mg| mg.list_keys(prefix, after, limit, &block) }
37
+ end
38
+
39
+ def mg_get_uris(domain, key, opts) # :nodoc:
40
+ pool_use(domain) { |mg| mg.get_uris(key, opts) }
41
+ end
42
+
43
+ def mg_size_and_paths(domain, key, opts) # :nodoc:
44
+ pool_use(domain) do |mg|
45
+ [ mg.size(key), mg.get_paths(key, opts) ]
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: binary -*-
2
+ ENV["VERSION"] or abort "VERSION= must be specified"
3
+ manifest = File.readlines('.manifest').map! { |x| x.chomp! }
4
+ require 'wrongdoc'
5
+ extend Wrongdoc::Gemspec
6
+ name, summary, title = readme_metadata
7
+
8
+ Gem::Specification.new do |s|
9
+ s.name = %q{omgf}
10
+ s.version = ENV["VERSION"].dup
11
+
12
+ s.authors = ["OMGF hackers"]
13
+ s.date = Time.now.utc.strftime('%Y-%m-%d')
14
+ s.description = readme_description
15
+ s.email = %q{omgf@librelist.org}
16
+ s.extra_rdoc_files = extra_rdoc_files(manifest)
17
+ s.files = manifest
18
+ s.homepage = Wrongdoc.config[:rdoc_url]
19
+ s.summary = summary
20
+ s.rdoc_options = rdoc_options
21
+ s.test_files = Dir['test/test_*.rb']
22
+ s.add_dependency('rack', ['~> 1.3'])
23
+ s.add_dependency('mogilefs-client', ['~> 3.1'])
24
+
25
+ s.licenses = %w(AGPLv3+)
26
+ end
data/pkg.mk ADDED
@@ -0,0 +1,175 @@
1
+ RUBY = ruby
2
+ RAKE = rake
3
+ RSYNC = rsync
4
+ WRONGDOC = wrongdoc
5
+
6
+ GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
7
+ @./GIT-VERSION-GEN
8
+ -include GIT-VERSION-FILE
9
+ -include local.mk
10
+ DLEXT := $(shell $(RUBY) -rrbconfig -e 'puts RbConfig::CONFIG["DLEXT"]')
11
+ RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION')
12
+ RUBY_ENGINE := $(shell $(RUBY) -e 'puts((RUBY_ENGINE rescue "ruby"))')
13
+ lib := lib
14
+
15
+ ifeq ($(shell test -f script/isolate_for_tests && echo t),t)
16
+ isolate_libs := tmp/isolate/$(RUBY_ENGINE)-$(RUBY_VERSION)/isolate.mk
17
+ $(isolate_libs): script/isolate_for_tests
18
+ @$(RUBY) script/isolate_for_tests
19
+ -include $(isolate_libs)
20
+ lib := $(lib):$(ISOLATE_LIBS)
21
+ endif
22
+
23
+ ext := $(firstword $(wildcard ext/*))
24
+ ifneq ($(ext),)
25
+ ext_pfx := tmp/ext/$(RUBY_ENGINE)-$(RUBY_VERSION)
26
+ ext_h := $(wildcard $(ext)/*/*.h $(ext)/*.h)
27
+ ext_src := $(wildcard $(ext)/*.c $(ext_h))
28
+ ext_pfx_src := $(addprefix $(ext_pfx)/,$(ext_src))
29
+ ext_d := $(ext_pfx)/$(ext)/.d
30
+ $(ext)/extconf.rb: $(wildcard $(ext)/*.h)
31
+ @>> $@
32
+ $(ext_d):
33
+ @mkdir -p $(@D)
34
+ @> $@
35
+ $(ext_pfx)/$(ext)/%: $(ext)/% $(ext_d)
36
+ install -m 644 $< $@
37
+ $(ext_pfx)/$(ext)/Makefile: $(ext)/extconf.rb $(ext_d) $(ext_h)
38
+ $(RM) -f $(@D)/*.o
39
+ cd $(@D) && $(RUBY) $(CURDIR)/$(ext)/extconf.rb
40
+ ext_sfx := _ext.$(DLEXT)
41
+ ext_dl := $(ext_pfx)/$(ext)/$(notdir $(ext)_ext.$(DLEXT))
42
+ $(ext_dl): $(ext_src) $(ext_pfx_src) $(ext_pfx)/$(ext)/Makefile
43
+ @echo $^ == $@
44
+ $(MAKE) -C $(@D)
45
+ lib := $(lib):$(ext_pfx)/$(ext)
46
+ build: $(ext_dl)
47
+ else
48
+ build:
49
+ endif
50
+
51
+ pkg_extra += GIT-VERSION-FILE NEWS ChangeLog LATEST
52
+ ChangeLog: GIT-VERSION-FILE .wrongdoc.yml
53
+ $(WRONGDOC) prepare
54
+ NEWS LATEST: ChangeLog
55
+
56
+ manifest:
57
+ $(RM) .manifest
58
+ $(MAKE) .manifest
59
+
60
+ .manifest: $(pkg_extra)
61
+ (git ls-files && for i in $@ $(pkg_extra); do echo $$i; done) | \
62
+ LC_ALL=C sort > $@+
63
+ cmp $@+ $@ || mv $@+ $@
64
+ $(RM) $@+
65
+
66
+ doc:: .document .wrongdoc.yml $(pkg_extra)
67
+ -find lib -type f -name '*.rbc' -exec rm -f '{}' ';'
68
+ -find ext -type f -name '*.rbc' -exec rm -f '{}' ';'
69
+ $(RM) -r doc
70
+ $(WRONGDOC) all
71
+ install -m644 COPYING doc/COPYING
72
+ install -m644 $(shell LC_ALL=C grep '^[A-Z]' .document) doc/
73
+
74
+ ifneq ($(VERSION),)
75
+ pkggem := pkg/$(rfpackage)-$(VERSION).gem
76
+ pkgtgz := pkg/$(rfpackage)-$(VERSION).tgz
77
+ release_notes := release_notes-$(VERSION)
78
+ release_changes := release_changes-$(VERSION)
79
+
80
+ release-notes: $(release_notes)
81
+ release-changes: $(release_changes)
82
+ $(release_changes):
83
+ $(WRONGDOC) release_changes > $@+
84
+ $(VISUAL) $@+ && test -s $@+ && mv $@+ $@
85
+ $(release_notes):
86
+ $(WRONGDOC) release_notes > $@+
87
+ $(VISUAL) $@+ && test -s $@+ && mv $@+ $@
88
+
89
+ # ensures we're actually on the tagged $(VERSION), only used for release
90
+ verify:
91
+ test x"$(shell umask)" = x0022
92
+ git rev-parse --verify refs/tags/v$(VERSION)^{}
93
+ git diff-index --quiet HEAD^0
94
+ test $$(git rev-parse --verify HEAD^0) = \
95
+ $$(git rev-parse --verify refs/tags/v$(VERSION)^{})
96
+
97
+ fix-perms:
98
+ -git ls-tree -r HEAD | awk '/^100644 / {print $$NF}' | xargs chmod 644
99
+ -git ls-tree -r HEAD | awk '/^100755 / {print $$NF}' | xargs chmod 755
100
+
101
+ gem: $(pkggem)
102
+
103
+ install-gem: $(pkggem)
104
+ gem install $(CURDIR)/$<
105
+
106
+ $(pkggem): manifest fix-perms
107
+ gem build $(rfpackage).gemspec
108
+ mkdir -p pkg
109
+ mv $(@F) $@
110
+
111
+ $(pkgtgz): distdir = $(basename $@)
112
+ $(pkgtgz): HEAD = v$(VERSION)
113
+ $(pkgtgz): manifest fix-perms
114
+ @test -n "$(distdir)"
115
+ $(RM) -r $(distdir)
116
+ mkdir -p $(distdir)
117
+ tar cf - $$(cat .manifest) | (cd $(distdir) && tar xf -)
118
+ cd pkg && tar cf - $(basename $(@F)) | gzip -9 > $(@F)+
119
+ mv $@+ $@
120
+
121
+ package: $(pkgtgz) $(pkggem)
122
+
123
+ test-release:: verify package $(release_notes) $(release_changes)
124
+ # make tgz release on RubyForge
125
+ @echo rubyforge add_release -f \
126
+ -n $(release_notes) -a $(release_changes) \
127
+ $(rfproject) $(rfpackage) $(VERSION) $(pkgtgz)
128
+ @echo gem push $(pkggem)
129
+ @echo rubyforge add_file \
130
+ $(rfproject) $(rfpackage) $(VERSION) $(pkggem)
131
+ release:: verify package $(release_notes) $(release_changes)
132
+ # make tgz release on RubyForge
133
+ rubyforge add_release -f -n $(release_notes) -a $(release_changes) \
134
+ $(rfproject) $(rfpackage) $(VERSION) $(pkgtgz)
135
+ # push gem to RubyGems.org
136
+ gem push $(pkggem)
137
+ # in case of gem downloads from RubyForge releases page
138
+ rubyforge add_file \
139
+ $(rfproject) $(rfpackage) $(VERSION) $(pkggem)
140
+ else
141
+ gem install-gem: GIT-VERSION-FILE
142
+ $(MAKE) $@ VERSION=$(GIT_VERSION)
143
+ endif
144
+
145
+ all:: test
146
+ test_units := $(wildcard test/test_*.rb)
147
+ test: test-unit
148
+ test-unit: $(test_units)
149
+ $(test_units): build
150
+ $(RUBY) -I $(lib) $@ $(RUBY_TEST_OPTS)
151
+
152
+ # this requires GNU coreutils variants
153
+ ifneq ($(RSYNC_DEST),)
154
+ publish_doc:
155
+ -git set-file-times
156
+ $(MAKE) doc
157
+ find doc/images -type f | \
158
+ TZ=UTC xargs touch -d '1970-01-01 00:00:06' doc/rdoc.css
159
+ $(MAKE) doc_gz
160
+ $(RSYNC) -av doc/ $(RSYNC_DEST)/
161
+ git ls-files | xargs touch
162
+ endif
163
+
164
+ # Create gzip variants of the same timestamp as the original so nginx
165
+ # "gzip_static on" can serve the gzipped versions directly.
166
+ doc_gz: docs = $(shell find doc -type f ! -regex '^.*\.\(gif\|jpg\|png\|gz\)$$')
167
+ doc_gz:
168
+ for i in $(docs); do \
169
+ gzip --rsyncable -9 < $$i > $$i.gz; touch -r $$i $$i.gz; done
170
+ check-warnings:
171
+ @(for i in $$(git ls-files '*.rb'| grep -v '^setup\.rb$$'); \
172
+ do $(RUBY) -d -W2 -c $$i; done) | grep -v '^Syntax OK$$' || :
173
+
174
+ .PHONY: all .FORCE-GIT-VERSION-FILE doc test $(test_units) manifest
175
+ .PHONY: check-warnings