aldebaran 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/.gitignore +13 -0
  2. data/.travis.yml +16 -0
  3. data/.yardopts +4 -0
  4. data/AUTHORS +4 -0
  5. data/Gemfile +77 -0
  6. data/KNOWN_ISSUES +5 -0
  7. data/LICENSE +22 -0
  8. data/README.rdoc +1900 -0
  9. data/Rakefile +175 -0
  10. data/aldebaran.gemspec +19 -0
  11. data/lib/aldebaran.rb +7 -0
  12. data/lib/aldebaran/base.rb +1600 -0
  13. data/lib/aldebaran/images/404.png +0 -0
  14. data/lib/aldebaran/images/500.png +0 -0
  15. data/lib/aldebaran/main.rb +28 -0
  16. data/lib/aldebaran/showexceptions.rb +340 -0
  17. data/lib/aldebaran/version.rb +3 -0
  18. data/test/aldebaran_test.rb +17 -0
  19. data/test/base_test.rb +160 -0
  20. data/test/builder_test.rb +95 -0
  21. data/test/coffee_test.rb +92 -0
  22. data/test/contest.rb +98 -0
  23. data/test/creole_test.rb +65 -0
  24. data/test/delegator_test.rb +162 -0
  25. data/test/encoding_test.rb +20 -0
  26. data/test/erb_test.rb +104 -0
  27. data/test/extensions_test.rb +100 -0
  28. data/test/filter_test.rb +397 -0
  29. data/test/haml_test.rb +101 -0
  30. data/test/helper.rb +115 -0
  31. data/test/helpers_test.rb +1192 -0
  32. data/test/less_test.rb +67 -0
  33. data/test/liquid_test.rb +59 -0
  34. data/test/mapped_error_test.rb +259 -0
  35. data/test/markaby_test.rb +80 -0
  36. data/test/markdown_test.rb +81 -0
  37. data/test/middleware_test.rb +68 -0
  38. data/test/nokogiri_test.rb +69 -0
  39. data/test/public/favicon.ico +0 -0
  40. data/test/radius_test.rb +59 -0
  41. data/test/rdoc_test.rb +65 -0
  42. data/test/readme_test.rb +136 -0
  43. data/test/request_test.rb +45 -0
  44. data/test/response_test.rb +61 -0
  45. data/test/result_test.rb +98 -0
  46. data/test/route_added_hook_test.rb +59 -0
  47. data/test/routing_test.rb +1096 -0
  48. data/test/sass_test.rb +115 -0
  49. data/test/scss_test.rb +88 -0
  50. data/test/server_test.rb +48 -0
  51. data/test/settings_test.rb +493 -0
  52. data/test/slim_test.rb +98 -0
  53. data/test/static_test.rb +178 -0
  54. data/test/streaming_test.rb +100 -0
  55. data/test/templates_test.rb +298 -0
  56. data/test/textile_test.rb +65 -0
  57. data/test/views/a/in_a.str +1 -0
  58. data/test/views/ascii.erb +2 -0
  59. data/test/views/b/in_b.str +1 -0
  60. data/test/views/calc.html.erb +1 -0
  61. data/test/views/error.builder +3 -0
  62. data/test/views/error.erb +3 -0
  63. data/test/views/error.haml +3 -0
  64. data/test/views/error.sass +2 -0
  65. data/test/views/explicitly_nested.str +1 -0
  66. data/test/views/foo/hello.test +1 -0
  67. data/test/views/hello.builder +1 -0
  68. data/test/views/hello.coffee +1 -0
  69. data/test/views/hello.creole +1 -0
  70. data/test/views/hello.erb +1 -0
  71. data/test/views/hello.haml +1 -0
  72. data/test/views/hello.less +5 -0
  73. data/test/views/hello.liquid +1 -0
  74. data/test/views/hello.mab +1 -0
  75. data/test/views/hello.md +1 -0
  76. data/test/views/hello.nokogiri +1 -0
  77. data/test/views/hello.radius +1 -0
  78. data/test/views/hello.rdoc +1 -0
  79. data/test/views/hello.sass +2 -0
  80. data/test/views/hello.scss +3 -0
  81. data/test/views/hello.slim +1 -0
  82. data/test/views/hello.str +1 -0
  83. data/test/views/hello.test +1 -0
  84. data/test/views/hello.textile +1 -0
  85. data/test/views/layout2.builder +3 -0
  86. data/test/views/layout2.erb +2 -0
  87. data/test/views/layout2.haml +2 -0
  88. data/test/views/layout2.liquid +2 -0
  89. data/test/views/layout2.mab +2 -0
  90. data/test/views/layout2.nokogiri +3 -0
  91. data/test/views/layout2.radius +2 -0
  92. data/test/views/layout2.slim +3 -0
  93. data/test/views/layout2.str +2 -0
  94. data/test/views/layout2.test +1 -0
  95. data/test/views/nested.str +1 -0
  96. data/test/views/utf8.erb +2 -0
  97. metadata +231 -0
@@ -0,0 +1,175 @@
1
+ require 'rake/clean'
2
+ require 'rake/testtask'
3
+ require 'fileutils'
4
+ require 'date'
5
+
6
+ # CI Reporter is only needed for the CI
7
+ begin
8
+ require 'ci/reporter/rake/test_unit'
9
+ rescue LoadError
10
+ end
11
+
12
+ task :default => :test
13
+ task :spec => :test
14
+
15
+ CLEAN.include "**/*.rbc"
16
+
17
+ def source_version
18
+ @source_version ||= begin
19
+ load './lib/aldebaran/version.rb'
20
+ Aldebaran::VERSION
21
+ end
22
+ end
23
+
24
+ def prev_feature
25
+ source_version.gsub(/^(\d\.)(\d+)\..*$/) { $1 + ($2.to_i - 1).to_s }
26
+ end
27
+
28
+ def prev_version
29
+ return prev_feature + '.0' if source_version.end_with? '.0'
30
+ source_version.gsub(/\d+$/) { |s| s.to_i - 1 }
31
+ end
32
+
33
+ # SPECS ===============================================================
34
+
35
+ task :test do
36
+ ENV['LANG'] = 'C'
37
+ ENV.delete 'LC_CTYPE'
38
+ end
39
+
40
+ Rake::TestTask.new(:test) do |t|
41
+ t.test_files = FileList['test/*_test.rb']
42
+ t.ruby_opts = ['-rubygems'] if defined? Gem
43
+ t.ruby_opts << '-I.'
44
+ end
45
+
46
+ Rake::TestTask.new(:"test:core") do |t|
47
+ core_tests = %w[base delegator encoding extensions filter
48
+ helpers mapped_error middleware radius rdoc
49
+ readme request response result route_added_hook
50
+ routing server settings aldebaran static templates]
51
+ t.test_files = core_tests.map {|n| "test/#{n}_test.rb"}
52
+ t.ruby_opts = ["-rubygems"] if defined? Gem
53
+ t.ruby_opts << "-I."
54
+ end
55
+
56
+ # Rcov ================================================================
57
+
58
+ namespace :test do
59
+ desc 'Measures test coverage'
60
+ task :coverage do
61
+ rm_f "coverage"
62
+ sh "rcov -Ilib test/*_test.rb"
63
+ end
64
+ end
65
+
66
+ # Website =============================================================
67
+
68
+ desc 'Generate RDoc under doc/api'
69
+ task 'doc' => ['doc:api']
70
+ task('doc:api') { sh "yardoc -o doc/api" }
71
+ CLEAN.include 'doc/api'
72
+
73
+ # README ===============================================================
74
+
75
+ task :add_template, [:name] do |t, args|
76
+ Dir.glob('README.*') do |file|
77
+ code = File.read(file)
78
+ if code =~ /^===.*#{args.name.capitalize}/
79
+ puts "Already covered in #{file}"
80
+ else
81
+ template = code[/===[^\n]*Liquid.*index\.liquid<\/tt>[^\n]*/m]
82
+ if !template
83
+ puts "Liquid not found in #{file}"
84
+ else
85
+ puts "Adding section to #{file}"
86
+ template = template.gsub(/Liquid/, args.name.capitalize).gsub(/liquid/, args.name.downcase)
87
+ code.gsub! /^(\s*===.*CoffeeScript)/, "\n" << template << "\n\\1"
88
+ File.open(file, "w") { |f| f << code }
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ # Thanks in announcement ===============================================
95
+
96
+ team = ["Mahmut Bulut"]
97
+ desc "list of contributors"
98
+ task :thanks, [:release,:backports] do |t, a|
99
+ a.with_defaults :release => "#{prev_version}..HEAD",
100
+ :backports => "#{prev_feature}.0..#{prev_feature}.x"
101
+ included = `git log --format=format:"%aN\t%s" #{a.release}`.lines.to_a
102
+ excluded = `git log --format=format:"%aN\t%s" #{a.backports}`.lines.to_a
103
+ commits = (included - excluded).group_by { |c| c[/^[^\t]+/] }
104
+ authors = commits.keys.sort_by { |n| - commits[n].size } - team
105
+ puts authors[0..-2].join(', ') << " and " << authors.last,
106
+ "(based on commits included in #{a.release}, but not in #{a.backports})"
107
+ end
108
+
109
+ desc "list of authors"
110
+ task :authors, [:commit_range, :format, :sep] do |t, a|
111
+ a.with_defaults :format => "%s (%d)", :sep => ", ", :commit_range => '--all'
112
+ authors = Hash.new { |h,k| h[k] = 0 }
113
+ mahmut = "Mahmut Bulut"
114
+ overall = 0
115
+ mapping = {
116
+ "mahmutbulut0@gmail.com" => mahmut, "regularlambda" => mahmut}
117
+ `git shortlog -s #{a.commit_range}`.lines.map do |line|
118
+ num, name = line.split("\t", 2).map(&:strip)
119
+ authors[mapping[name] || name] += num.to_i
120
+ overall += num.to_i
121
+ end
122
+ puts "#{overall} commits by #{authors.count} authors:"
123
+ puts authors.sort_by { |n,c| -c }.map { |e| a.format % e }.join(a.sep)
124
+ end
125
+
126
+ # PACKAGING ============================================================
127
+
128
+ if defined?(Gem)
129
+ # Load the gemspec using the same limitations as github
130
+ def spec
131
+ require 'rubygems' unless defined? Gem::Specification
132
+ @spec ||= eval(File.read('aldebaran.gemspec'))
133
+ end
134
+
135
+ def package(ext='')
136
+ "pkg/aldebaran-#{spec.version}" + ext
137
+ end
138
+
139
+ desc 'Build packages'
140
+ task :package => %w[.gem .tar.gz].map {|e| package(e)}
141
+
142
+ desc 'Build and install as local gem'
143
+ task :install => package('.gem') do
144
+ sh "gem install #{package('.gem')}"
145
+ end
146
+
147
+ directory 'pkg/'
148
+ CLOBBER.include('pkg')
149
+
150
+ file package('.gem') => %w[pkg/ aldebaran.gemspec] + spec.files do |f|
151
+ sh "gem build aldebaran.gemspec"
152
+ mv File.basename(f.name), f.name
153
+ end
154
+
155
+ file package('.tar.gz') => %w[pkg/] + spec.files do |f|
156
+ sh <<-SH
157
+ git archive \
158
+ --prefix=aldebaran-#{source_version}/ \
159
+ --format=tar \
160
+ HEAD | gzip > #{f.name}
161
+ SH
162
+ end
163
+
164
+ task 'release' => ['test', package('.gem')] do
165
+ sh <<-SH
166
+ gem install #{package('.gem')} --local &&
167
+ gem push #{package('.gem')} &&
168
+ git commit --allow-empty -a -m '#{source_version} release' &&
169
+ git tag -s v#{source_version} -m '#{source_version} release' &&
170
+ git tag -s #{source_version} -m '#{source_version} release' &&
171
+ git push && (git push aldebaran || true) &&
172
+ git push --tags && (git push aldebaran --tags || true)
173
+ SH
174
+ end
175
+ end
@@ -0,0 +1,19 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+ require 'aldebaran/version'
3
+
4
+ Gem::Specification.new 'aldebaran', Aldebaran::VERSION do |s|
5
+ s.description = "Aldebaran is Web-development DSL micro-framework written with Mathematical Constructions"
6
+ s.summary = "DSL micro-framework with mathematical constructions"
7
+ s.authors = ["Mahmut Bulut", "Eren Kaplan"]
8
+ s.homepage = "http://www.resettek.com/aldebaran"
9
+ s.files = `git ls-files`.split("\n")
10
+ s.test_files = s.files.select { |p| p =~ /^test\/.*_test.rb/ }
11
+ s.extra_rdoc_files = s.files.select { |p| p =~ /^README/ } << 'LICENSE'
12
+ s.rdoc_options = %w[--line-numbers --inline-source --title aldebaran --main README.rdoc]
13
+
14
+ s.add_dependency 'rack', '~> 1.3'
15
+ s.add_dependency 'tilt', '~> 1.3'
16
+ end
17
+
18
+ #Gökte yılduz ellidur da
19
+ #Ellisuda bellidur
@@ -0,0 +1,7 @@
1
+ libdir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
3
+
4
+ require 'aldebaran/base'
5
+ require 'aldebaran/main'
6
+
7
+ enable :inline_templates
@@ -0,0 +1,1600 @@
1
+ # external dependencies
2
+ require 'rack'
3
+ require 'tilt'
4
+ require "rack/protection"
5
+
6
+ # stdlib dependencies
7
+ require 'thread'
8
+ require 'time'
9
+ require 'uri'
10
+
11
+ # other files we need
12
+ require 'aldebaran/showexceptions'
13
+ require 'aldebaran/version'
14
+
15
+ module Aldebaran
16
+ # The request object. See Rack::Request for more info:
17
+ # http://rack.rubyforge.org/doc/classes/Rack/Request.html
18
+ class Request < Rack::Request
19
+ # Returns an array of acceptable media types for the response
20
+ def accept
21
+ @env['aldebaran.accept'] ||= begin
22
+ entries = @env['HTTP_ACCEPT'].to_s.split(',')
23
+ entries.map { |e| accept_entry(e) }.sort_by(&:last).map(&:first)
24
+ end
25
+ end
26
+
27
+ def preferred_type(*types)
28
+ return accept.first if types.empty?
29
+ types.flatten!
30
+ accept.detect do |pattern|
31
+ type = types.detect { |t| File.fnmatch(pattern, t) }
32
+ return type if type
33
+ end
34
+ end
35
+
36
+ alias accept? preferred_type
37
+ alias secure? ssl?
38
+
39
+ def forwarded?
40
+ @env.include? "HTTP_X_FORWARDED_HOST"
41
+ end
42
+
43
+ def safe?
44
+ get? or head? or options? or trace?
45
+ end
46
+
47
+ def idempotent?
48
+ safe? or put? or delete?
49
+ end
50
+
51
+ private
52
+
53
+ def accept_entry(entry)
54
+ type, *options = entry.gsub(/\s/, '').split(';')
55
+ quality = 0 # we sort smalles first
56
+ options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' }
57
+ [type, [quality, type.count('*'), 1 - options.size]]
58
+ end
59
+ end
60
+
61
+ # The response object. See Rack::Response and Rack::ResponseHelpers for
62
+ # more info:
63
+ # http://rack.rubyforge.org/doc/classes/Rack/Response.html
64
+ # http://rack.rubyforge.org/doc/classes/Rack/Response/Helpers.html
65
+ class Response < Rack::Response
66
+ def body=(value)
67
+ value = value.body while Rack::Response === value
68
+ @body = String === value ? [value.to_str] : value
69
+ end
70
+
71
+ def each
72
+ block_given? ? super : enum_for(:each)
73
+ end
74
+
75
+ def finish
76
+ if status.to_i / 100 == 1
77
+ headers.delete "Content-Length"
78
+ headers.delete "Content-Type"
79
+ elsif Array === body and not [204, 304].include?(status.to_i)
80
+ headers["Content-Length"] = body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s
81
+ end
82
+
83
+ # Rack::Response#finish sometimes returns self as response body. We don't want that.
84
+ status, headers, result = super
85
+ result = body if result == self
86
+ [status, headers, result]
87
+ end
88
+ end
89
+
90
+ class NotFound < NameError #:nodoc:
91
+ def code ; 404 ; end
92
+ end
93
+
94
+ # Methods available to routes, before/after filters, and views.
95
+ module Helpers
96
+ # Set or retrieve the response status code.
97
+ def status(value=nil)
98
+ response.status = value if value
99
+ response.status
100
+ end
101
+
102
+ # Set or retrieve the response body. When a block is given,
103
+ # evaluation is deferred until the body is read with #each.
104
+ def body(value=nil, &block)
105
+ if block_given?
106
+ def block.each; yield(call) end
107
+ response.body = block
108
+ elsif value
109
+ response.body = value
110
+ else
111
+ response.body
112
+ end
113
+ end
114
+
115
+ # Halt processing and redirect to the URI provided.
116
+ def redirect(uri, *args)
117
+ if env['HTTP_VERSION'] == 'HTTP/1.1' and env["REQUEST_METHOD"] != 'GET'
118
+ status 303
119
+ else
120
+ status 302
121
+ end
122
+
123
+ # According to RFC 2616 section 14.30, "the field value consists of a
124
+ # single absolute URI"
125
+ response['Location'] = uri(uri, settings.absolute_redirects?, settings.prefixed_redirects?)
126
+ halt(*args)
127
+ end
128
+
129
+ # Generates the absolute URI for a given path in the app.
130
+ # Takes Rack routers and reverse proxies into account.
131
+ def uri(addr = nil, absolute = true, add_script_name = true)
132
+ return addr if addr =~ /\A[A-z][A-z0-9\+\.\-]*:/
133
+ uri = [host = ""]
134
+ if absolute
135
+ host << "http#{'s' if request.secure?}://"
136
+ if request.forwarded? or request.port != (request.secure? ? 443 : 80)
137
+ host << request.host_with_port
138
+ else
139
+ host << request.host
140
+ end
141
+ end
142
+ uri << request.script_name.to_s if add_script_name
143
+ uri << (addr ? addr : request.path_info).to_s
144
+ File.join uri
145
+ end
146
+
147
+ alias url uri
148
+ alias to uri
149
+
150
+ # Halt processing and return the error status provided.
151
+ def error(code, body=nil)
152
+ code, body = 500, code.to_str if code.respond_to? :to_str
153
+ response.body = body unless body.nil?
154
+ halt code
155
+ end
156
+
157
+ # Halt processing and return a 404 Not Found.
158
+ def not_found(body=nil)
159
+ error 404, body
160
+ end
161
+
162
+ # Set multiple response headers with Hash.
163
+ def headers(hash=nil)
164
+ response.headers.merge! hash if hash
165
+ response.headers
166
+ end
167
+
168
+ # Access the underlying Rack session.
169
+ def session
170
+ request.session
171
+ end
172
+
173
+ # Access shared logger object.
174
+ def logger
175
+ request.logger
176
+ end
177
+
178
+ # Look up a media type by file extension in Rack's mime registry.
179
+ def mime_type(type)
180
+ Base.mime_type(type)
181
+ end
182
+
183
+ # Set the Content-Type of the response body given a media type or file
184
+ # extension.
185
+ def content_type(type = nil, params={})
186
+ return response['Content-Type'] unless type
187
+ default = params.delete :default
188
+ mime_type = mime_type(type) || default
189
+ fail "Unknown media type: %p" % type if mime_type.nil?
190
+ mime_type = mime_type.dup
191
+ unless params.include? :charset or settings.add_charset.all? { |p| not p === mime_type }
192
+ params[:charset] = params.delete('charset') || settings.default_encoding
193
+ end
194
+ params.delete :charset if mime_type.include? 'charset'
195
+ unless params.empty?
196
+ mime_type << (mime_type.include?(';') ? ', ' : ';')
197
+ mime_type << params.map { |kv| kv.join('=') }.join(', ')
198
+ end
199
+ response['Content-Type'] = mime_type
200
+ end
201
+
202
+ # Set the Content-Disposition to "attachment" with the specified filename,
203
+ # instructing the user agents to prompt to save.
204
+ def attachment(filename=nil)
205
+ response['Content-Disposition'] = 'attachment'
206
+ if filename
207
+ params = '; filename="%s"' % File.basename(filename)
208
+ response['Content-Disposition'] << params
209
+ ext = File.extname(filename)
210
+ content_type(ext) unless response['Content-Type'] or ext.empty?
211
+ end
212
+ end
213
+
214
+ # Use the contents of the file at +path+ as the response body.
215
+ def send_file(path, opts={})
216
+ if opts[:type] or not response['Content-Type']
217
+ content_type opts[:type] || File.extname(path), :default => 'application/octet-stream'
218
+ end
219
+
220
+ if opts[:disposition] == 'attachment' || opts[:filename]
221
+ attachment opts[:filename] || path
222
+ elsif opts[:disposition] == 'inline'
223
+ response['Content-Disposition'] = 'inline'
224
+ end
225
+
226
+ last_modified opts[:last_modified] if opts[:last_modified]
227
+
228
+ file = Rack::File.new nil
229
+ file.path = path
230
+ result = file.serving env
231
+ result[1].each { |k,v| headers[k] ||= v }
232
+ halt result[0], result[2]
233
+ rescue Errno::ENOENT
234
+ not_found
235
+ end
236
+
237
+ # Class of the response body in case you use #stream.
238
+ #
239
+ # Three things really matter: The front and back block (back being the
240
+ # blog generating content, front the one sending it to the client) and
241
+ # the scheduler, integrating with whatever concurrency feature the Rack
242
+ # handler is using.
243
+ #
244
+ # Scheduler has to respond to defer and schedule.
245
+ class Stream
246
+ def self.schedule(*) yield end
247
+ def self.defer(*) yield end
248
+
249
+ def initialize(scheduler = self.class, close = true, &back)
250
+ @back, @scheduler, @callback, @close = back.to_proc, scheduler, nil, close
251
+ end
252
+
253
+ def close
254
+ @scheduler.schedule { @callback.call if @callback }
255
+ end
256
+
257
+ def each(&front)
258
+ @front = front
259
+ @scheduler.defer do
260
+ begin
261
+ @back.call(self)
262
+ rescue Exception => e
263
+ @scheduler.schedule { raise e }
264
+ end
265
+ close if @close
266
+ end
267
+ end
268
+
269
+ def <<(data)
270
+ @scheduler.schedule { @front.call(data.to_s) }
271
+ self
272
+ end
273
+
274
+ def callback(&block)
275
+ @callback = block
276
+ end
277
+
278
+ alias errback callback
279
+ end
280
+
281
+ # Allows to start sending data to the client even though later parts of
282
+ # the response body have not yet been generated.
283
+ #
284
+ # The close parameter specifies whether Stream#close should be called
285
+ # after the block has been executed. This is only relevant for evented
286
+ # servers like Thin or Rainbows.
287
+ def stream(close = true, &block)
288
+ scheduler = env['async.callback'] ? EventMachine : Stream
289
+ body Stream.new(scheduler, close, &block)
290
+ end
291
+
292
+ # Specify response freshness policy for HTTP caches (Cache-Control header).
293
+ # Any number of non-value directives (:public, :private, :no_cache,
294
+ # :no_store, :must_revalidate, :proxy_revalidate) may be passed along with
295
+ # a Hash of value directives (:max_age, :min_stale, :s_max_age).
296
+ #
297
+ # cache_control :public, :must_revalidate, :max_age => 60
298
+ # => Cache-Control: public, must-revalidate, max-age=60
299
+ #
300
+ # See RFC 2616 / 14.9 for more on standard cache control directives:
301
+ # http://tools.ietf.org/html/rfc2616#section-14.9.1
302
+ def cache_control(*values)
303
+ if values.last.kind_of?(Hash)
304
+ hash = values.pop
305
+ hash.reject! { |k,v| v == false }
306
+ hash.reject! { |k,v| values << k if v == true }
307
+ else
308
+ hash = {}
309
+ end
310
+
311
+ values = values.map { |value| value.to_s.tr('_','-') }
312
+ hash.each do |key, value|
313
+ key = key.to_s.tr('_', '-')
314
+ value = value.to_i if key == "max-age"
315
+ values << [key, value].join('=')
316
+ end
317
+
318
+ response['Cache-Control'] = values.join(', ') if values.any?
319
+ end
320
+
321
+ # Set the Expires header and Cache-Control/max-age directive. Amount
322
+ # can be an integer number of seconds in the future or a Time object
323
+ # indicating when the response should be considered "stale". The remaining
324
+ # "values" arguments are passed to the #cache_control helper:
325
+ #
326
+ # expires 500, :public, :must_revalidate
327
+ # => Cache-Control: public, must-revalidate, max-age=60
328
+ # => Expires: Mon, 08 Jun 2009 08:50:17 GMT
329
+ #
330
+ def expires(amount, *values)
331
+ values << {} unless values.last.kind_of?(Hash)
332
+
333
+ if amount.is_a? Integer
334
+ time = Time.now + amount.to_i
335
+ max_age = amount
336
+ else
337
+ time = time_for amount
338
+ max_age = time - Time.now
339
+ end
340
+
341
+ values.last.merge!(:max_age => max_age)
342
+ cache_control(*values)
343
+
344
+ response['Expires'] = time.httpdate
345
+ end
346
+
347
+ # Set the last modified time of the resource (HTTP 'Last-Modified' header)
348
+ # and halt if conditional GET matches. The +time+ argument is a Time,
349
+ # DateTime, or other object that responds to +to_time+.
350
+ #
351
+ # When the current request includes an 'If-Modified-Since' header that is
352
+ # equal or later than the time specified, execution is immediately halted
353
+ # with a '304 Not Modified' response.
354
+ def last_modified(time)
355
+ return unless time
356
+ time = time_for time
357
+ response['Last-Modified'] = time.httpdate
358
+ # compare based on seconds since epoch
359
+ halt 304 if Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i >= time.to_i
360
+ rescue ArgumentError
361
+ end
362
+
363
+ # Set the response entity tag (HTTP 'ETag' header) and halt if conditional
364
+ # GET matches. The +value+ argument is an identifier that uniquely
365
+ # identifies the current version of the resource. The +kind+ argument
366
+ # indicates whether the etag should be used as a :strong (default) or :weak
367
+ # cache validator.
368
+ #
369
+ # When the current request includes an 'If-None-Match' header with a
370
+ # matching etag, execution is immediately halted. If the request method is
371
+ # GET or HEAD, a '304 Not Modified' response is sent.
372
+ def etag(value, kind = :strong)
373
+ raise ArgumentError, ":strong or :weak expected" unless [:strong,:weak].include?(kind)
374
+ value = '"%s"' % value
375
+ value = 'W/' + value if kind == :weak
376
+ response['ETag'] = value
377
+
378
+ if etags = env['HTTP_IF_NONE_MATCH']
379
+ etags = etags.split(/\s*,\s*/)
380
+ if etags.include?(value) or etags.include?('*')
381
+ halt 304 if request.safe?
382
+ else
383
+ halt 412 unless request.safe?
384
+ end
385
+ end
386
+ end
387
+
388
+ # Sugar for redirect (example: redirect back)
389
+ def back
390
+ request.referer
391
+ end
392
+
393
+ # whether or not the status is set to 1xx
394
+ def informational?
395
+ status.between? 100, 199
396
+ end
397
+
398
+ # whether or not the status is set to 2xx
399
+ def success?
400
+ status.between? 200, 299
401
+ end
402
+
403
+ # whether or not the status is set to 3xx
404
+ def redirect?
405
+ status.between? 300, 399
406
+ end
407
+
408
+ # whether or not the status is set to 4xx
409
+ def client_error?
410
+ status.between? 400, 499
411
+ end
412
+
413
+ # whether or not the status is set to 5xx
414
+ def server_error?
415
+ status.between? 500, 599
416
+ end
417
+
418
+ # whether or not the status is set to 404
419
+ def not_found?
420
+ status == 404
421
+ end
422
+
423
+ # Generates a Time object from the given value.
424
+ # Used by #expires and #last_modified.
425
+ def time_for(value)
426
+ if value.respond_to? :to_time
427
+ value.to_time
428
+ elsif value.is_a? Time
429
+ value
430
+ elsif value.respond_to? :new_offset
431
+ # DateTime#to_time does the same on 1.9
432
+ d = value.new_offset 0
433
+ t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction
434
+ t.getlocal
435
+ elsif value.respond_to? :mday
436
+ # Date#to_time does the same on 1.9
437
+ Time.local(value.year, value.mon, value.mday)
438
+ elsif value.is_a? Numeric
439
+ Time.at value
440
+ else
441
+ Time.parse value.to_s
442
+ end
443
+ rescue ArgumentError => boom
444
+ raise boom
445
+ rescue Exception
446
+ raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
447
+ end
448
+ end
449
+
450
+ private
451
+
452
+ # Template rendering methods. Each method takes the name of a template
453
+ # to render as a Symbol and returns a String with the rendered output,
454
+ # as well as an optional hash with additional options.
455
+ #
456
+ # `template` is either the name or path of the template as symbol
457
+ # (Use `:'subdir/myview'` for views in subdirectories), or a string
458
+ # that will be rendered.
459
+ #
460
+ # Possible options are:
461
+ # :content_type The content type to use, same arguments as content_type.
462
+ # :layout If set to false, no layout is rendered, otherwise
463
+ # the specified layout is used (Ignored for `sass` and `less`)
464
+ # :layout_engine Engine to use for rendering the layout.
465
+ # :locals A hash with local variables that should be available
466
+ # in the template
467
+ # :scope If set, template is evaluate with the binding of the given
468
+ # object rather than the application instance.
469
+ # :views Views directory to use.
470
+ module Templates
471
+ module ContentTyped
472
+ attr_accessor :content_type
473
+ end
474
+
475
+ def initialize
476
+ super
477
+ @default_layout = :layout
478
+ end
479
+
480
+ def erb(template, options={}, locals={})
481
+ render :erb, template, options, locals
482
+ end
483
+
484
+ def erubis(template, options={}, locals={})
485
+ warn "Aldebaran::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \
486
+ "If you have Erubis installed, it will be used automatically."
487
+ render :erubis, template, options, locals
488
+ end
489
+
490
+ def haml(template, options={}, locals={})
491
+ render :haml, template, options, locals
492
+ end
493
+
494
+ def sass(template, options={}, locals={})
495
+ options.merge! :layout => false, :default_content_type => :css
496
+ render :sass, template, options, locals
497
+ end
498
+
499
+ def scss(template, options={}, locals={})
500
+ options.merge! :layout => false, :default_content_type => :css
501
+ render :scss, template, options, locals
502
+ end
503
+
504
+ def less(template, options={}, locals={})
505
+ options.merge! :layout => false, :default_content_type => :css
506
+ render :less, template, options, locals
507
+ end
508
+
509
+ def builder(template=nil, options={}, locals={}, &block)
510
+ options[:default_content_type] = :xml
511
+ render_ruby(:builder, template, options, locals, &block)
512
+ end
513
+
514
+ def liquid(template, options={}, locals={})
515
+ render :liquid, template, options, locals
516
+ end
517
+
518
+ def markdown(template, options={}, locals={})
519
+ render :markdown, template, options, locals
520
+ end
521
+
522
+ def textile(template, options={}, locals={})
523
+ render :textile, template, options, locals
524
+ end
525
+
526
+ def rdoc(template, options={}, locals={})
527
+ render :rdoc, template, options, locals
528
+ end
529
+
530
+ def radius(template, options={}, locals={})
531
+ render :radius, template, options, locals
532
+ end
533
+
534
+ def markaby(template=nil, options={}, locals={}, &block)
535
+ render_ruby(:mab, template, options, locals, &block)
536
+ end
537
+
538
+ def coffee(template, options={}, locals={})
539
+ options.merge! :layout => false, :default_content_type => :js
540
+ render :coffee, template, options, locals
541
+ end
542
+
543
+ def nokogiri(template=nil, options={}, locals={}, &block)
544
+ options[:default_content_type] = :xml
545
+ render_ruby(:nokogiri, template, options, locals, &block)
546
+ end
547
+
548
+ def slim(template, options={}, locals={})
549
+ render :slim, template, options, locals
550
+ end
551
+
552
+ def creole(template, options={}, locals={})
553
+ render :creole, template, options, locals
554
+ end
555
+
556
+ # Calls the given block for every possible template file in views,
557
+ # named name.ext, where ext is registered on engine.
558
+ def find_template(views, name, engine)
559
+ yield ::File.join(views, "#{name}.#{@preferred_extension}")
560
+ Tilt.mappings.each do |ext, engines|
561
+ next unless ext != @preferred_extension and engines.include? engine
562
+ yield ::File.join(views, "#{name}.#{ext}")
563
+ end
564
+ end
565
+
566
+ private
567
+ # logic shared between builder and nokogiri
568
+ def render_ruby(engine, template, options={}, locals={}, &block)
569
+ options, template = template, nil if template.is_a?(Hash)
570
+ template = Proc.new { block } if template.nil?
571
+ render engine, template, options, locals
572
+ end
573
+
574
+ def render(engine, data, options={}, locals={}, &block)
575
+ # merge app-level options
576
+ options = settings.send(engine).merge(options) if settings.respond_to?(engine)
577
+ options[:outvar] ||= '@_out_buf'
578
+ options[:default_encoding] ||= settings.default_encoding
579
+
580
+ # extract generic options
581
+ locals = options.delete(:locals) || locals || {}
582
+ views = options.delete(:views) || settings.views || "./views"
583
+ layout = options.delete(:layout)
584
+ eat_errors = layout.nil?
585
+ layout = @default_layout if layout.nil? or layout == true
586
+ content_type = options.delete(:content_type) || options.delete(:default_content_type)
587
+ layout_engine = options.delete(:layout_engine) || engine
588
+ scope = options.delete(:scope) || self
589
+
590
+ # compile and render template
591
+ layout_was = @default_layout
592
+ @default_layout = false
593
+ template = compile_template(engine, data, options, views)
594
+ output = template.render(scope, locals, &block)
595
+ @default_layout = layout_was
596
+
597
+ # render layout
598
+ if layout
599
+ options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope)
600
+ catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
601
+ end
602
+
603
+ output.extend(ContentTyped).content_type = content_type if content_type
604
+ output
605
+ end
606
+
607
+ def compile_template(engine, data, options, views)
608
+ eat_errors = options.delete :eat_errors
609
+ template_cache.fetch engine, data, options do
610
+ template = Tilt[engine]
611
+ raise "Template engine not found: #{engine}" if template.nil?
612
+
613
+ case data
614
+ when Symbol
615
+ body, path, line = settings.templates[data]
616
+ if body
617
+ body = body.call if body.respond_to?(:call)
618
+ template.new(path, line.to_i, options) { body }
619
+ else
620
+ found = false
621
+ @preferred_extension = engine.to_s
622
+ find_template(views, data, template) do |file|
623
+ path ||= file # keep the initial path rather than the last one
624
+ if found = File.exists?(file)
625
+ path = file
626
+ break
627
+ end
628
+ end
629
+ throw :layout_missing if eat_errors and not found
630
+ template.new(path, 1, options)
631
+ end
632
+ when Proc, String
633
+ body = data.is_a?(String) ? Proc.new { data } : data
634
+ path, line = settings.caller_locations.first
635
+ template.new(path, line.to_i, options, &body)
636
+ else
637
+ raise ArgumentError
638
+ end
639
+ end
640
+ end
641
+ end
642
+
643
+ # Base class for all aldebaran applications and middleware.
644
+ class Base
645
+ include Rack::Utils
646
+ include Helpers
647
+ include Templates
648
+
649
+ attr_accessor :app
650
+ attr_reader :template_cache
651
+
652
+ def initialize(app=nil)
653
+ super()
654
+ @app = app
655
+ @template_cache = Tilt::Cache.new
656
+ yield self if block_given?
657
+ end
658
+
659
+ # Rack call interface.
660
+ def call(env)
661
+ dup.call!(env)
662
+ end
663
+
664
+ attr_accessor :env, :request, :response, :params
665
+
666
+ def call!(env) # :nodoc:
667
+ @env = env
668
+ @request = Request.new(env)
669
+ @response = Response.new
670
+ @params = indifferent_params(@request.params)
671
+ template_cache.clear if settings.reload_templates
672
+ force_encoding(@params)
673
+
674
+ @response['Content-Type'] = nil
675
+ invoke { dispatch! }
676
+ invoke { error_block!(response.status) }
677
+
678
+ unless @response['Content-Type']
679
+ if Array === body and body[0].respond_to? :content_type
680
+ content_type body[0].content_type
681
+ else
682
+ content_type :html
683
+ end
684
+ end
685
+
686
+ @response.finish
687
+ end
688
+
689
+ # Access settings defined with Base.set.
690
+ def self.settings
691
+ self
692
+ end
693
+
694
+ # Access settings defined with Base.set.
695
+ def settings
696
+ self.class.settings
697
+ end
698
+
699
+ def options
700
+ warn "Aldebaran::Base#options is deprecated and will be removed, " \
701
+ "use #settings instead."
702
+ settings
703
+ end
704
+
705
+ # Exit the current block, halts any further processing
706
+ # of the request, and returns the specified response.
707
+ def halt(*response)
708
+ response = response.first if response.length == 1
709
+ throw :halt, response
710
+ end
711
+
712
+ # Pass control to the next matching route.
713
+ # If there are no more matching routes, aldebaran will
714
+ # return a 404 response.
715
+ def pass(&block)
716
+ throw :pass, block
717
+ end
718
+
719
+ # Forward the request to the downstream app -- middleware only.
720
+ def forward
721
+ fail "downstream app not set" unless @app.respond_to? :call
722
+ status, headers, body = @app.call env
723
+ @response.status = status
724
+ @response.body = body
725
+ @response.headers.merge! headers
726
+ nil
727
+ end
728
+
729
+ private
730
+ # Run filters defined on the class and all superclasses.
731
+ def filter!(type, base = settings)
732
+ filter! type, base.superclass if base.superclass.respond_to?(:filters)
733
+ base.filters[type].each { |args| process_route(*args) }
734
+ end
735
+
736
+ # Run routes defined on the class and all superclasses.
737
+ def route!(base = settings, pass_block=nil)
738
+ if routes = base.routes[@request.request_method]
739
+ routes.each do |pattern, keys, conditions, block|
740
+ pass_block = process_route(pattern, keys, conditions) do |*args|
741
+ route_eval { block[*args] }
742
+ end
743
+ end
744
+ end
745
+
746
+ # Run routes defined in superclass.
747
+ if base.superclass.respond_to?(:routes)
748
+ return route!(base.superclass, pass_block)
749
+ end
750
+
751
+ route_eval(&pass_block) if pass_block
752
+ route_missing
753
+ end
754
+
755
+ # Run a route block and throw :halt with the result.
756
+ def route_eval
757
+ throw :halt, yield
758
+ end
759
+
760
+ # If the current request matches pattern and conditions, fill params
761
+ # with keys and call the given block.
762
+ # Revert params afterwards.
763
+ #
764
+ # Returns pass block.
765
+ def process_route(pattern, keys, conditions, block = nil, values = [])
766
+ @original_params ||= @params
767
+ route = @request.path_info
768
+ route = '/' if route.empty? and not settings.empty_path_info?
769
+ if match = pattern.match(route)
770
+ values += match.captures.to_a.map { |v| force_encoding URI.decode(v) if v }
771
+ params =
772
+ if keys.any?
773
+ keys.zip(values).inject({}) do |hash,(k,v)|
774
+ if k == 'splat'
775
+ (hash[k] ||= []) << v
776
+ else
777
+ hash[k] = v
778
+ end
779
+ hash
780
+ end
781
+ elsif values.any?
782
+ {'captures' => values}
783
+ else
784
+ {}
785
+ end
786
+ @params = @original_params.merge(params)
787
+ @block_params = values
788
+ catch(:pass) do
789
+ conditions.each { |c| throw :pass if c.bind(self).call == false }
790
+ block ? block[self, @block_params] : yield(self, @block_params)
791
+ end
792
+ end
793
+ ensure
794
+ @params = @original_params
795
+ end
796
+
797
+ # No matching route was found or all routes passed. The default
798
+ # implementation is to forward the request downstream when running
799
+ # as middleware (@app is non-nil); when no downstream app is set, raise
800
+ # a NotFound exception. Subclasses can override this method to perform
801
+ # custom route miss logic.
802
+ def route_missing
803
+ if @app
804
+ forward
805
+ else
806
+ raise NotFound
807
+ end
808
+ end
809
+
810
+ # Attempt to serve static files from public directory. Throws :halt when
811
+ # a matching file is found, returns nil otherwise.
812
+ def static!
813
+ return if (public_dir = settings.public_folder).nil?
814
+ public_dir = File.expand_path(public_dir)
815
+
816
+ path = File.expand_path(public_dir + unescape(request.path_info))
817
+ return unless path.start_with?(public_dir) and File.file?(path)
818
+
819
+ env['aldebaran.static_file'] = path
820
+ cache_control *settings.static_cache_control if settings.static_cache_control?
821
+ send_file path, :disposition => nil
822
+ end
823
+
824
+ # Enable string or symbol key access to the nested params hash.
825
+ def indifferent_params(params)
826
+ params = indifferent_hash.merge(params)
827
+ params.each do |key, value|
828
+ next unless value.is_a?(Hash)
829
+ params[key] = indifferent_params(value)
830
+ end
831
+ end
832
+
833
+ # Creates a Hash with indifferent access.
834
+ def indifferent_hash
835
+ Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
836
+ end
837
+
838
+ # Run the block with 'throw :halt' support and apply result to the response.
839
+ def invoke
840
+ res = catch(:halt) { yield }
841
+ res = [res] if Fixnum === res or String === res
842
+ if Array === res and Fixnum === res.first
843
+ status(res.shift)
844
+ body(res.pop)
845
+ headers(*res)
846
+ elsif res.respond_to? :each
847
+ body res
848
+ end
849
+ end
850
+
851
+ # Dispatch a request with error handling.
852
+ def dispatch!
853
+ static! if settings.static? && (request.get? || request.head?)
854
+ filter! :before
855
+ route!
856
+ rescue ::Exception => boom
857
+ handle_exception!(boom)
858
+ ensure
859
+ filter! :after unless env['aldebaran.static_file']
860
+ end
861
+
862
+ # Error handling during requests.
863
+ def handle_exception!(boom)
864
+ @env['aldebaran.error'] = boom
865
+ status boom.respond_to?(:code) ? Integer(boom.code) : 500
866
+
867
+ if server_error?
868
+ dump_errors! boom if settings.dump_errors?
869
+ raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
870
+ end
871
+
872
+ if not_found?
873
+ headers['X-Cascade'] = 'pass'
874
+ body '<h1>Not Found</h1>'
875
+ end
876
+
877
+ res = error_block!(boom.class, boom) || error_block!(status, boom)
878
+ return res if res or not server_error?
879
+ raise boom if settings.raise_errors? or settings.show_exceptions?
880
+ error_block! Exception, boom
881
+ end
882
+
883
+ # Find an custom error block for the key(s) specified.
884
+ def error_block!(key, *block_params)
885
+ base = settings
886
+ while base.respond_to?(:errors)
887
+ next base = base.superclass unless args = base.errors[key]
888
+ args += [block_params]
889
+ return process_route(*args)
890
+ end
891
+ return false unless key.respond_to? :superclass and key.superclass < Exception
892
+ error_block!(key.superclass, *block_params)
893
+ end
894
+
895
+ def dump_errors!(boom)
896
+ msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
897
+ @env['rack.errors'].puts(msg)
898
+ end
899
+
900
+ class << self
901
+ attr_reader :routes, :filters, :templates, :errors
902
+
903
+ # Removes all routes, filters, middleware and extension hooks from the
904
+ # current class (not routes/filters/... defined by its superclass).
905
+ def reset!
906
+ @conditions = []
907
+ @routes = {}
908
+ @filters = {:before => [], :after => []}
909
+ @errors = {}
910
+ @middleware = []
911
+ @prototype = nil
912
+ @extensions = []
913
+
914
+ if superclass.respond_to?(:templates)
915
+ @templates = Hash.new { |hash,key| superclass.templates[key] }
916
+ else
917
+ @templates = {}
918
+ end
919
+ end
920
+
921
+ # Extension modules registered on this class and all superclasses.
922
+ def extensions
923
+ if superclass.respond_to?(:extensions)
924
+ (@extensions + superclass.extensions).uniq
925
+ else
926
+ @extensions
927
+ end
928
+ end
929
+
930
+ # Middleware used in this class and all superclasses.
931
+ def middleware
932
+ if superclass.respond_to?(:middleware)
933
+ superclass.middleware + @middleware
934
+ else
935
+ @middleware
936
+ end
937
+ end
938
+
939
+ # Sets an option to the given value. If the value is a proc,
940
+ # the proc will be called every time the option is accessed.
941
+ def set(option, value = (not_set = true), ignore_setter = false, &block)
942
+ raise ArgumentError if block and !not_set
943
+ value, not_set = block, false if block
944
+
945
+ if not_set
946
+ raise ArgumentError unless option.respond_to?(:each)
947
+ option.each { |k,v| set(k, v) }
948
+ return self
949
+ end
950
+
951
+ if respond_to?("#{option}=") and not ignore_setter
952
+ return __send__("#{option}=", value)
953
+ end
954
+
955
+ setter = proc { |val| set option, val, true }
956
+ getter = proc { value }
957
+
958
+ case value
959
+ when Proc
960
+ getter = value
961
+ when Symbol, Fixnum, FalseClass, TrueClass, NilClass
962
+ # we have a lot of enable and disable calls, let's optimize those
963
+ class_eval "def self.#{option}() #{value.inspect} end"
964
+ getter = nil
965
+ when Hash
966
+ setter = proc do |val|
967
+ val = value.merge val if Hash === val
968
+ set option, val, true
969
+ end
970
+ end
971
+
972
+ (class << self; self; end).class_eval do
973
+ define_method("#{option}=", &setter) if setter
974
+ define_method(option, &getter) if getter
975
+ unless method_defined? "#{option}?"
976
+ class_eval "def #{option}?() !!#{option} end"
977
+ end
978
+ end
979
+ self
980
+ end
981
+
982
+ # Same as calling `set :option, true` for each of the given options.
983
+ def enable(*opts)
984
+ opts.each { |key| set(key, true) }
985
+ end
986
+
987
+ # Same as calling `set :option, false` for each of the given options.
988
+ def disable(*opts)
989
+ opts.each { |key| set(key, false) }
990
+ end
991
+
992
+ # Define a custom error handler. Optionally takes either an Exception
993
+ # class, or an HTTP status code to specify which errors should be
994
+ # handled.
995
+ def error(*codes, &block)
996
+ args = compile! "ERROR", //, block
997
+ codes = codes.map { |c| Array(c) }.flatten
998
+ codes << Exception if codes.empty?
999
+ codes.each { |c| @errors[c] = args }
1000
+ end
1001
+
1002
+ # Sugar for `error(404) { ... }`
1003
+ def not_found(&block)
1004
+ error 404, &block
1005
+ end
1006
+
1007
+ # Define a named template. The block must return the template source.
1008
+ def template(name, &block)
1009
+ filename, line = caller_locations.first
1010
+ templates[name] = [block, filename, line.to_i]
1011
+ end
1012
+
1013
+ # Define the layout template. The block must return the template source.
1014
+ def layout(name=:layout, &block)
1015
+ template name, &block
1016
+ end
1017
+
1018
+ # Load embeded templates from the file; uses the caller's __FILE__
1019
+ # when no file is specified.
1020
+ def inline_templates=(file=nil)
1021
+ file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file
1022
+
1023
+ begin
1024
+ io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
1025
+ app, data = io.gsub("\r\n", "\n").split(/^__END__$/, 2)
1026
+ rescue Errno::ENOENT
1027
+ app, data = nil
1028
+ end
1029
+
1030
+ if data
1031
+ if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
1032
+ encoding = $2
1033
+ else
1034
+ encoding = settings.default_encoding
1035
+ end
1036
+ lines = app.count("\n") + 1
1037
+ template = nil
1038
+ force_encoding data, encoding
1039
+ data.each_line do |line|
1040
+ lines += 1
1041
+ if line =~ /^@@\s*(.*\S)\s*$/
1042
+ template = force_encoding('', encoding)
1043
+ templates[$1.to_sym] = [template, file, lines]
1044
+ elsif template
1045
+ template << line
1046
+ end
1047
+ end
1048
+ end
1049
+ end
1050
+
1051
+ # Lookup or register a mime type in Rack's mime registry.
1052
+ def mime_type(type, value=nil)
1053
+ return type if type.nil? || type.to_s.include?('/')
1054
+ type = ".#{type}" unless type.to_s[0] == ?.
1055
+ return Rack::Mime.mime_type(type, nil) unless value
1056
+ Rack::Mime::MIME_TYPES[type] = value
1057
+ end
1058
+
1059
+ # provides all mime types matching type, including deprecated types:
1060
+ # mime_types :html # => ['text/html']
1061
+ # mime_types :js # => ['application/javascript', 'text/javascript']
1062
+ def mime_types(type)
1063
+ type = mime_type type
1064
+ type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type]
1065
+ end
1066
+
1067
+ # Define a before filter; runs before all requests within the same
1068
+ # context as route handlers and may access/modify the request and
1069
+ # response.
1070
+ def before(path = nil, options = {}, &block)
1071
+ add_filter(:before, path, options, &block)
1072
+ end
1073
+
1074
+ # Define an after filter; runs after all requests within the same
1075
+ # context as route handlers and may access/modify the request and
1076
+ # response.
1077
+ def after(path = nil, options = {}, &block)
1078
+ add_filter(:after, path, options, &block)
1079
+ end
1080
+
1081
+ # add a filter
1082
+ def add_filter(type, path = nil, options = {}, &block)
1083
+ path, options = //, path if path.respond_to?(:each_pair)
1084
+ filters[type] << compile!(type, path || //, block, options)
1085
+ end
1086
+
1087
+ # Add a route condition. The route is considered non-matching when the
1088
+ # block returns false.
1089
+ def condition(name = "#{caller.first[/`.*'/]} condition", &block)
1090
+ @conditions << generate_method(name, &block)
1091
+ end
1092
+
1093
+ def public=(value)
1094
+ warn ":public is no longer used to avoid overloading Module#public, use :public_folder instead"
1095
+ set(:public_folder, value)
1096
+ end
1097
+
1098
+ private
1099
+ # Condition for matching host name. Parameter might be String or Regexp.
1100
+ def host_name(pattern)
1101
+ condition { pattern === request.host }
1102
+ end
1103
+
1104
+ # Condition for matching user agent. Parameter should be Regexp.
1105
+ # Will set params[:agent].
1106
+ def user_agent(pattern)
1107
+ condition do
1108
+ if request.user_agent.to_s =~ pattern
1109
+ @params[:agent] = $~[1..-1]
1110
+ true
1111
+ else
1112
+ false
1113
+ end
1114
+ end
1115
+ end
1116
+ alias_method :agent, :user_agent
1117
+
1118
+ # Condition for matching mimetypes. Accepts file extensions.
1119
+ def provides(*types)
1120
+ types.map! { |t| mime_types(t) }
1121
+ types.flatten!
1122
+ condition do
1123
+ if type = request.preferred_type(types)
1124
+ content_type(type)
1125
+ true
1126
+ else
1127
+ false
1128
+ end
1129
+ end
1130
+ end
1131
+
1132
+ public
1133
+ # Defining a `GET` handler also automatically defines
1134
+ # a `HEAD` handler.
1135
+ def get(path, opts={}, &block)
1136
+ conditions = @conditions.dup
1137
+ route('GET', path, opts, &block)
1138
+
1139
+ @conditions = conditions
1140
+ route('HEAD', path, opts, &block)
1141
+ end
1142
+
1143
+ def put(path, opts={}, &bk) route 'PUT', path, opts, &bk end
1144
+ def post(path, opts={}, &bk) route 'POST', path, opts, &bk end
1145
+ def delete(path, opts={}, &bk) route 'DELETE', path, opts, &bk end
1146
+ def head(path, opts={}, &bk) route 'HEAD', path, opts, &bk end
1147
+ def options(path, opts={}, &bk) route 'OPTIONS', path, opts, &bk end
1148
+ def patch(path, opts={}, &bk) route 'PATCH', path, opts, &bk end
1149
+
1150
+ private
1151
+ def route(verb, path, options={}, &block)
1152
+ # Because of self.options.host
1153
+ host_name(options.delete(:host)) if options.key?(:host)
1154
+ enable :empty_path_info if path == "" and empty_path_info.nil?
1155
+ signature = compile!(verb, path, block, options)
1156
+ (@routes[verb] ||= []) << signature
1157
+ invoke_hook(:route_added, verb, path, block)
1158
+ signature
1159
+ end
1160
+
1161
+ def invoke_hook(name, *args)
1162
+ extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
1163
+ end
1164
+
1165
+ def generate_method(method_name, &block)
1166
+ define_method(method_name, &block)
1167
+ method = instance_method method_name
1168
+ remove_method method_name
1169
+ method
1170
+ end
1171
+
1172
+ def compile!(verb, path, block, options = {})
1173
+ options.each_pair { |option, args| send(option, *args) }
1174
+ method_name = "#{verb} #{path}"
1175
+ unbound_method = generate_method(method_name, &block)
1176
+ pattern, keys = compile path
1177
+ conditions, @conditions = @conditions, []
1178
+
1179
+ [ pattern, keys, conditions, block.arity != 0 ?
1180
+ proc { |a,p| unbound_method.bind(a).call(*p) } :
1181
+ proc { |a,p| unbound_method.bind(a).call } ]
1182
+ end
1183
+
1184
+ def compile(path)
1185
+ keys = []
1186
+ if path.respond_to? :to_str
1187
+ special_chars = %w{. + ( ) $}
1188
+ pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) { |c| encoded(c) }
1189
+ pattern.gsub! /((:\w+)|\*)/ do |match|
1190
+ if match == "*"
1191
+ keys << 'splat'
1192
+ "(.*?)"
1193
+ else
1194
+ keys << $2[1..-1]
1195
+ "([^/?#]+)"
1196
+ end
1197
+ end
1198
+ [/^#{pattern}$/, keys]
1199
+ elsif path.respond_to?(:keys) && path.respond_to?(:match)
1200
+ [path, path.keys]
1201
+ elsif path.respond_to?(:names) && path.respond_to?(:match)
1202
+ [path, path.names]
1203
+ elsif path.respond_to? :match
1204
+ [path, keys]
1205
+ else
1206
+ raise TypeError, path
1207
+ end
1208
+ end
1209
+
1210
+ def encoded(char)
1211
+ enc = URI.encode(char)
1212
+ enc = "(?:#{Regexp.escape enc}|#{URI.encode char, /./})" if enc == char
1213
+ enc = "(?:#{enc}|#{encoded('+')})" if char == " "
1214
+ enc
1215
+ end
1216
+
1217
+ public
1218
+ # Makes the methods defined in the block and in the Modules given
1219
+ # in `extensions` available to the handlers and templates
1220
+ def helpers(*extensions, &block)
1221
+ class_eval(&block) if block_given?
1222
+ include(*extensions) if extensions.any?
1223
+ end
1224
+
1225
+ # Register an extension. Alternatively take a block from which an
1226
+ # extension will be created and registered on the fly.
1227
+ def register(*extensions, &block)
1228
+ extensions << Module.new(&block) if block_given?
1229
+ @extensions += extensions
1230
+ extensions.each do |extension|
1231
+ extend extension
1232
+ extension.registered(self) if extension.respond_to?(:registered)
1233
+ end
1234
+ end
1235
+
1236
+ def development?; environment == :development end
1237
+ def production?; environment == :production end
1238
+ def test?; environment == :test end
1239
+
1240
+ # Set configuration options for aldebaran and/or the app.
1241
+ # Allows scoping of settings for certain environments.
1242
+ def configure(*envs, &block)
1243
+ yield self if envs.empty? || envs.include?(environment.to_sym)
1244
+ end
1245
+
1246
+ # Use the specified Rack middleware
1247
+ def use(middleware, *args, &block)
1248
+ @prototype = nil
1249
+ @middleware << [middleware, args, block]
1250
+ end
1251
+
1252
+ def quit!(server, handler_name)
1253
+ # Use Thin's hard #stop! if available, otherwise just #stop.
1254
+ server.respond_to?(:stop!) ? server.stop! : server.stop
1255
+ $stderr.puts "\n== aldebaran has ended his set (crowd applauds)" unless handler_name =~/cgi/i
1256
+ end
1257
+
1258
+ # Run the aldebaran app as a self-hosted server using
1259
+ # Thin, Mongrel or WEBrick (in that order). If given a block, will call
1260
+ # with the constructed handler once we have taken the stage.
1261
+ def run!(options={})
1262
+ set options
1263
+ handler = detect_rack_handler
1264
+ handler_name = handler.name.gsub(/.*::/, '')
1265
+ handler.run self, :Host => bind, :Port => port do |server|
1266
+ unless handler_name =~ /cgi/i
1267
+ $stderr.puts "== aldebaran/#{Aldebaran::VERSION} has taken the stage " +
1268
+ "on #{port} for #{environment} with backup from #{handler_name}"
1269
+ end
1270
+ [:INT, :TERM].each { |sig| trap(sig) { quit!(server, handler_name) } }
1271
+ server.threaded = settings.threaded if server.respond_to? :threaded=
1272
+ set :running, true
1273
+ yield server if block_given?
1274
+ end
1275
+ rescue Errno::EADDRINUSE => e
1276
+ $stderr.puts "== Someone is already performing on port #{port}!"
1277
+ end
1278
+
1279
+ # The prototype instance used to process requests.
1280
+ def prototype
1281
+ @prototype ||= new
1282
+ end
1283
+
1284
+ # Create a new instance without middleware in front of it.
1285
+ alias new! new unless method_defined? :new!
1286
+
1287
+ # Create a new instance of the class fronted by its middleware
1288
+ # pipeline. The object is guaranteed to respond to #call but may not be
1289
+ # an instance of the class new was called on.
1290
+ def new(*args, &bk)
1291
+ build(Rack::Builder.new, *args, &bk).to_app
1292
+ end
1293
+
1294
+ # Creates a Rack::Builder instance with all the middleware set up and
1295
+ # an instance of this class as end point.
1296
+ def build(builder, *args, &bk)
1297
+ setup_default_middleware builder
1298
+ setup_middleware builder
1299
+ builder.run new!(*args, &bk)
1300
+ builder
1301
+ end
1302
+
1303
+ def call(env)
1304
+ synchronize { prototype.call(env) }
1305
+ end
1306
+
1307
+ private
1308
+ def setup_default_middleware(builder)
1309
+ builder.use ShowExceptions if show_exceptions?
1310
+ builder.use Rack::MethodOverride if method_override?
1311
+ builder.use Rack::Head
1312
+ setup_logging builder
1313
+ setup_sessions builder
1314
+ setup_protection builder
1315
+ end
1316
+
1317
+ def setup_protection(builder)
1318
+ return unless protection?
1319
+ options = Hash === protection ? protection.dup : {}
1320
+ options[:except] = Array options[:except]
1321
+ options[:except] += [:session_hijacking, :remote_token] unless sessions?
1322
+ builder.use Rack::Protection, options
1323
+ end
1324
+
1325
+ def setup_middleware(builder)
1326
+ middleware.each { |c,a,b| builder.use(c, *a, &b) }
1327
+ end
1328
+
1329
+ def setup_logging(builder)
1330
+ if logging?
1331
+ builder.use Rack::CommonLogger
1332
+ if logging.respond_to? :to_int
1333
+ builder.use Rack::Logger, logging
1334
+ else
1335
+ builder.use Rack::Logger
1336
+ end
1337
+ else
1338
+ builder.use Rack::NullLogger
1339
+ end
1340
+ end
1341
+
1342
+ def setup_sessions(builder)
1343
+ return unless sessions?
1344
+ options = {}
1345
+ options[:secret] = session_secret if session_secret?
1346
+ options.merge! sessions.to_hash if sessions.respond_to? :to_hash
1347
+ builder.use Rack::Session::Cookie, options
1348
+ end
1349
+
1350
+ def detect_rack_handler
1351
+ servers = Array(server)
1352
+ servers.each do |server_name|
1353
+ begin
1354
+ return Rack::Handler.get(server_name.to_s)
1355
+ rescue LoadError
1356
+ rescue NameError
1357
+ end
1358
+ end
1359
+ fail "Server handler (#{servers.join(',')}) not found."
1360
+ end
1361
+
1362
+ def inherited(subclass)
1363
+ subclass.reset!
1364
+ subclass.set :app_file, caller_files.first unless subclass.app_file?
1365
+ super
1366
+ end
1367
+
1368
+ @@mutex = Mutex.new
1369
+ def synchronize(&block)
1370
+ if lock?
1371
+ @@mutex.synchronize(&block)
1372
+ else
1373
+ yield
1374
+ end
1375
+ end
1376
+
1377
+ public
1378
+ CALLERS_TO_IGNORE = [ # :nodoc:
1379
+ /\/aldebaran(\/(base|main|showexceptions))?\.rb$/, # all aldebaran code
1380
+ /lib\/tilt.*\.rb$/, # all tilt code
1381
+ /^\(.*\)$/, # generated code
1382
+ /rubygems\/custom_require\.rb$/, # rubygems require hacks
1383
+ /active_support/, # active_support require hacks
1384
+ /bundler(\/runtime)?\.rb/, # bundler require hacks
1385
+ /<internal:/, # internal in ruby >= 1.9.2
1386
+ /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files
1387
+ ]
1388
+
1389
+ # add rubinius (and hopefully other VM impls) ignore patterns ...
1390
+ CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) if defined?(RUBY_IGNORE_CALLERS)
1391
+
1392
+ # Like Kernel#caller but excluding certain magic entries and without
1393
+ # line / method information; the resulting array contains filenames only.
1394
+ def caller_files
1395
+ cleaned_caller(1).flatten
1396
+ end
1397
+
1398
+ # Like caller_files, but containing Arrays rather than strings with the
1399
+ # first element being the file, and the second being the line.
1400
+ def caller_locations
1401
+ cleaned_caller 2
1402
+ end
1403
+
1404
+ private
1405
+ # used for deprecation warnings
1406
+ def warn(message)
1407
+ super message + "\n\tfrom #{cleaned_caller.first.join(':')}"
1408
+ end
1409
+
1410
+ # Like Kernel#caller but excluding certain magic entries
1411
+ def cleaned_caller(keep = 3)
1412
+ caller(1).
1413
+ map { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }.
1414
+ reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
1415
+ end
1416
+ end
1417
+
1418
+ # Fixes encoding issues by
1419
+ # * defaulting to UTF-8
1420
+ # * casting params to Encoding.default_external
1421
+ #
1422
+ # The latter might not be necessary if Rack handles it one day.
1423
+ # Keep an eye on Rack's LH #100.
1424
+ def force_encoding(*args) settings.force_encoding(*args) end
1425
+ if defined? Encoding
1426
+ def self.force_encoding(data, encoding = default_encoding)
1427
+ return if data == settings || data.is_a?(Tempfile)
1428
+ if data.respond_to? :force_encoding
1429
+ data.force_encoding(encoding).encode!
1430
+ elsif data.respond_to? :each_value
1431
+ data.each_value { |v| force_encoding(v, encoding) }
1432
+ elsif data.respond_to? :each
1433
+ data.each { |v| force_encoding(v, encoding) }
1434
+ end
1435
+ data
1436
+ end
1437
+ else
1438
+ def self.force_encoding(data, *) data end
1439
+ end
1440
+
1441
+ reset!
1442
+
1443
+ set :environment, (ENV['RACK_ENV'] || :development).to_sym
1444
+ set :raise_errors, Proc.new { test? }
1445
+ set :dump_errors, Proc.new { !test? }
1446
+ set :show_exceptions, Proc.new { development? }
1447
+ set :sessions, false
1448
+ set :logging, false
1449
+ set :protection, true
1450
+ set :method_override, false
1451
+ set :default_encoding, "utf-8"
1452
+ set :add_charset, %w[javascript xml xhtml+xml json].map { |t| "application/#{t}" }
1453
+ settings.add_charset << /^text\//
1454
+
1455
+ # explicitly generating a session secret eagerly to play nice with preforking
1456
+ begin
1457
+ require 'securerandom'
1458
+ set :session_secret, SecureRandom.hex(64)
1459
+ rescue LoadError, NotImplementedError
1460
+ # SecureRandom raises a NotImplementedError if no random device is available
1461
+ set :session_secret, "%064x" % Kernel.rand(2**256-1)
1462
+ end
1463
+
1464
+ class << self
1465
+ alias_method :methodoverride?, :method_override?
1466
+ alias_method :methodoverride=, :method_override=
1467
+ end
1468
+
1469
+ set :run, false # start server via at-exit hook?
1470
+ set :running, false # is the built-in server running now?
1471
+ set :server, %w[thin mongrel webrick]
1472
+ set :bind, '0.0.0.0'
1473
+ #TODO : port'u değiştir, parse olmayan bir port ile
1474
+ set :port, 4567
1475
+
1476
+ set :absolute_redirects, true
1477
+ set :prefixed_redirects, false
1478
+ set :empty_path_info, nil
1479
+
1480
+ set :app_file, nil
1481
+ set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
1482
+ set :views, Proc.new { root && File.join(root, 'views') }
1483
+ set :reload_templates, Proc.new { development? }
1484
+ set :lock, false
1485
+ set :threaded, true
1486
+
1487
+ set :public_folder, Proc.new { root && File.join(root, 'public') }
1488
+ set :static, Proc.new { public_folder && File.exist?(public_folder) }
1489
+ set :static_cache_control, false
1490
+
1491
+ error ::Exception do
1492
+ response.status = 500
1493
+ content_type 'text/html'
1494
+ '<h1>Internal Server Error</h1>'
1495
+ end
1496
+
1497
+ configure :development do
1498
+ get '/__aldebaran__/:image.png' do
1499
+ filename = File.dirname(__FILE__) + "/images/#{params[:image]}.png"
1500
+ content_type :png
1501
+ send_file filename
1502
+ end
1503
+
1504
+ error NotFound do
1505
+ content_type 'text/html'
1506
+
1507
+ (<<-HTML).gsub(/^ {8}/, '')
1508
+ <!DOCTYPE html>
1509
+ <html>
1510
+ <head>
1511
+ <style type="text/css">
1512
+ body { text-align:center;font-family:helvetica,arial;font-size:22px;
1513
+ color:#888;margin:20px}
1514
+ #c {margin:0 auto;width:500px;text-align:left}
1515
+ </style>
1516
+ </head>
1517
+ <body>
1518
+ <h2>aldebaran doesn&rsquo;t know this amplifier.</h2>
1519
+ <img src='#{uri "/__aldebaran__/404.png"}'>
1520
+ <div id="c">
1521
+ Try this:
1522
+ <pre>#{request.request_method.downcase} '#{request.path_info}' do\n "Hello World"\nend</pre>
1523
+ </div>
1524
+ </body>
1525
+ </html>
1526
+ HTML
1527
+ end
1528
+ end
1529
+ end
1530
+
1531
+ # Execution context for classic style (top-level) applications. All
1532
+ # DSL methods executed on main are delegated to this class.
1533
+ #
1534
+ # The Application class should not be subclassed, unless you want to
1535
+ # inherit all settings, routes, handlers, and error pages from the
1536
+ # top-level. Subclassing aldebaran::Base is highly recommended for
1537
+ # modular applications.
1538
+ class Application < Base
1539
+ set :logging, Proc.new { ! test? }
1540
+ set :method_override, true
1541
+ set :run, Proc.new { ! test? }
1542
+ set :session_secret, Proc.new { super() unless development? }
1543
+ set :app_file, nil
1544
+
1545
+ def self.register(*extensions, &block) #:nodoc:
1546
+ added_methods = extensions.map {|m| m.public_instance_methods }.flatten
1547
+ Delegator.delegate(*added_methods)
1548
+ super(*extensions, &block)
1549
+ end
1550
+ end
1551
+
1552
+ # aldebaran delegation mixin. Mixing this module into an object causes all
1553
+ # methods to be delegated to the aldebaran::Application class. Used primarily
1554
+ # at the top-level.
1555
+ module Delegator #:nodoc:
1556
+ def self.delegate(*methods)
1557
+ methods.each do |method_name|
1558
+ define_method(method_name) do |*args, &block|
1559
+ return super(*args, &block) if respond_to? method_name
1560
+ Delegator.target.send(method_name, *args, &block)
1561
+ end
1562
+ private method_name
1563
+ end
1564
+ end
1565
+
1566
+ delegate :get, :patch, :put, :post, :delete, :head, :options, :template, :layout,
1567
+ :before, :after, :error, :not_found, :configure, :set, :mime_type,
1568
+ :enable, :disable, :use, :development?, :test?, :production?,
1569
+ :helpers, :settings
1570
+
1571
+ class << self
1572
+ attr_accessor :target
1573
+ end
1574
+
1575
+ self.target = Application
1576
+ end
1577
+
1578
+ # Create a new aldebaran application. The block is evaluated in the new app's
1579
+ # class scope.
1580
+ def self.new(base=Base, options={}, &block)
1581
+ base = Class.new(base)
1582
+ base.class_eval(&block) if block_given?
1583
+ base
1584
+ end
1585
+
1586
+ # Extend the top-level DSL with the modules provided.
1587
+ def self.register(*extensions, &block)
1588
+ Delegator.target.register(*extensions, &block)
1589
+ end
1590
+
1591
+ # Include the helper modules provided in aldebaran's request context.
1592
+ def self.helpers(*extensions, &block)
1593
+ Delegator.target.helpers(*extensions, &block)
1594
+ end
1595
+
1596
+ # Use the middleware for classic applications.
1597
+ def self.use(*args, &block)
1598
+ Delegator.target.use(*args, &block)
1599
+ end
1600
+ end