aldebaran 1.0.1

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