sinatra-acd 1.4.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +5 -0
  3. data/AUTHORS +61 -0
  4. data/CHANGES +1293 -0
  5. data/Gemfile +76 -0
  6. data/LICENSE +23 -0
  7. data/README.de.md +2864 -0
  8. data/README.es.md +2786 -0
  9. data/README.fr.md +2924 -0
  10. data/README.hu.md +694 -0
  11. data/README.ja.md +2726 -0
  12. data/README.ko.md +2832 -0
  13. data/README.md +2980 -0
  14. data/README.pt-br.md +965 -0
  15. data/README.pt-pt.md +791 -0
  16. data/README.ru.md +2799 -0
  17. data/README.zh.md +2158 -0
  18. data/Rakefile +199 -0
  19. data/examples/chat.rb +61 -0
  20. data/examples/simple.rb +3 -0
  21. data/examples/stream.ru +26 -0
  22. data/lib/sinatra.rb +4 -0
  23. data/lib/sinatra/base.rb +2044 -0
  24. data/lib/sinatra/images/404.png +0 -0
  25. data/lib/sinatra/images/500.png +0 -0
  26. data/lib/sinatra/main.rb +34 -0
  27. data/lib/sinatra/show_exceptions.rb +345 -0
  28. data/lib/sinatra/version.rb +3 -0
  29. data/sinatra.gemspec +19 -0
  30. data/test/asciidoctor_test.rb +72 -0
  31. data/test/base_test.rb +171 -0
  32. data/test/builder_test.rb +91 -0
  33. data/test/coffee_test.rb +90 -0
  34. data/test/compile_test.rb +183 -0
  35. data/test/contest.rb +100 -0
  36. data/test/creole_test.rb +65 -0
  37. data/test/delegator_test.rb +160 -0
  38. data/test/encoding_test.rb +20 -0
  39. data/test/erb_test.rb +116 -0
  40. data/test/extensions_test.rb +98 -0
  41. data/test/filter_test.rb +487 -0
  42. data/test/haml_test.rb +109 -0
  43. data/test/helper.rb +131 -0
  44. data/test/helpers_test.rb +1917 -0
  45. data/test/integration/app.rb +79 -0
  46. data/test/integration_helper.rb +236 -0
  47. data/test/integration_test.rb +104 -0
  48. data/test/less_test.rb +69 -0
  49. data/test/liquid_test.rb +77 -0
  50. data/test/mapped_error_test.rb +285 -0
  51. data/test/markaby_test.rb +80 -0
  52. data/test/markdown_test.rb +82 -0
  53. data/test/mediawiki_test.rb +68 -0
  54. data/test/middleware_test.rb +68 -0
  55. data/test/nokogiri_test.rb +67 -0
  56. data/test/public/favicon.ico +0 -0
  57. data/test/rabl_test.rb +89 -0
  58. data/test/rack_test.rb +45 -0
  59. data/test/radius_test.rb +59 -0
  60. data/test/rdoc_test.rb +66 -0
  61. data/test/readme_test.rb +130 -0
  62. data/test/request_test.rb +97 -0
  63. data/test/response_test.rb +63 -0
  64. data/test/result_test.rb +76 -0
  65. data/test/route_added_hook_test.rb +59 -0
  66. data/test/routing_test.rb +1412 -0
  67. data/test/sass_test.rb +115 -0
  68. data/test/scss_test.rb +88 -0
  69. data/test/server_test.rb +48 -0
  70. data/test/settings_test.rb +582 -0
  71. data/test/sinatra_test.rb +12 -0
  72. data/test/slim_test.rb +102 -0
  73. data/test/static_test.rb +236 -0
  74. data/test/streaming_test.rb +149 -0
  75. data/test/stylus_test.rb +90 -0
  76. data/test/templates_test.rb +382 -0
  77. data/test/textile_test.rb +65 -0
  78. data/test/views/a/in_a.str +1 -0
  79. data/test/views/ascii.erb +2 -0
  80. data/test/views/b/in_b.str +1 -0
  81. data/test/views/calc.html.erb +1 -0
  82. data/test/views/error.builder +3 -0
  83. data/test/views/error.erb +3 -0
  84. data/test/views/error.haml +3 -0
  85. data/test/views/error.sass +2 -0
  86. data/test/views/explicitly_nested.str +1 -0
  87. data/test/views/foo/hello.test +1 -0
  88. data/test/views/hello.asciidoc +1 -0
  89. data/test/views/hello.builder +1 -0
  90. data/test/views/hello.coffee +1 -0
  91. data/test/views/hello.creole +1 -0
  92. data/test/views/hello.erb +1 -0
  93. data/test/views/hello.haml +1 -0
  94. data/test/views/hello.less +5 -0
  95. data/test/views/hello.liquid +1 -0
  96. data/test/views/hello.mab +1 -0
  97. data/test/views/hello.md +1 -0
  98. data/test/views/hello.mediawiki +1 -0
  99. data/test/views/hello.nokogiri +1 -0
  100. data/test/views/hello.rabl +2 -0
  101. data/test/views/hello.radius +1 -0
  102. data/test/views/hello.rdoc +1 -0
  103. data/test/views/hello.sass +2 -0
  104. data/test/views/hello.scss +3 -0
  105. data/test/views/hello.slim +1 -0
  106. data/test/views/hello.str +1 -0
  107. data/test/views/hello.styl +2 -0
  108. data/test/views/hello.test +1 -0
  109. data/test/views/hello.textile +1 -0
  110. data/test/views/hello.wlang +1 -0
  111. data/test/views/hello.yajl +1 -0
  112. data/test/views/layout2.builder +3 -0
  113. data/test/views/layout2.erb +2 -0
  114. data/test/views/layout2.haml +2 -0
  115. data/test/views/layout2.liquid +2 -0
  116. data/test/views/layout2.mab +2 -0
  117. data/test/views/layout2.nokogiri +3 -0
  118. data/test/views/layout2.rabl +3 -0
  119. data/test/views/layout2.radius +2 -0
  120. data/test/views/layout2.slim +3 -0
  121. data/test/views/layout2.str +2 -0
  122. data/test/views/layout2.test +1 -0
  123. data/test/views/layout2.wlang +2 -0
  124. data/test/views/nested.str +1 -0
  125. data/test/views/utf8.erb +2 -0
  126. data/test/wlang_test.rb +87 -0
  127. data/test/yajl_test.rb +86 -0
  128. metadata +280 -0
@@ -0,0 +1,199 @@
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/sinatra/version.rb'
20
+ Sinatra::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
+ t.warning = true
45
+ end
46
+
47
+ Rake::TestTask.new(:"test:core") do |t|
48
+ core_tests = %w[base delegator encoding extensions filter
49
+ helpers mapped_error middleware radius rdoc
50
+ readme request response result route_added_hook
51
+ routing server settings sinatra static templates]
52
+ t.test_files = core_tests.map {|n| "test/#{n}_test.rb"}
53
+ t.ruby_opts = ["-rubygems"] if defined? Gem
54
+ t.ruby_opts << "-I."
55
+ t.warning = true
56
+ end
57
+
58
+ # Rcov ================================================================
59
+
60
+ namespace :test do
61
+ desc 'Measures test coverage'
62
+ task :coverage do
63
+ rm_f "coverage"
64
+ sh "rcov -Ilib test/*_test.rb"
65
+ end
66
+ end
67
+
68
+ # Website =============================================================
69
+
70
+ desc 'Generate RDoc under doc/api'
71
+ task 'doc' => ['doc:api']
72
+ task('doc:api') { sh "yardoc -o doc/api" }
73
+ CLEAN.include 'doc/api'
74
+
75
+ # README ===============================================================
76
+
77
+ task :add_template, [:name] do |t, args|
78
+ Dir.glob('README.*') do |file|
79
+ code = File.read(file)
80
+ if code =~ /^===.*#{args.name.capitalize}/
81
+ puts "Already covered in #{file}"
82
+ else
83
+ template = code[/===[^\n]*Liquid.*index\.liquid<\/tt>[^\n]*/m]
84
+ if !template
85
+ puts "Liquid not found in #{file}"
86
+ else
87
+ puts "Adding section to #{file}"
88
+ template = template.gsub(/Liquid/, args.name.capitalize).gsub(/liquid/, args.name.downcase)
89
+ code.gsub! /^(\s*===.*CoffeeScript)/, "\n" << template << "\n\\1"
90
+ File.open(file, "w") { |f| f << code }
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ # Thanks in announcement ===============================================
97
+
98
+ team = ["Ryan Tomayko", "Blake Mizerany", "Simon Rozet", "Konstantin Haase"]
99
+ desc "list of contributors"
100
+ task :thanks, [:release,:backports] do |t, a|
101
+ a.with_defaults :release => "#{prev_version}..HEAD",
102
+ :backports => "#{prev_feature}.0..#{prev_feature}.x"
103
+ included = `git log --format=format:"%aN\t%s" #{a.release}`.lines.map { |l| l.force_encoding('binary') }
104
+ excluded = `git log --format=format:"%aN\t%s" #{a.backports}`.lines.map { |l| l.force_encoding('binary') }
105
+ commits = (included - excluded).group_by { |c| c[/^[^\t]+/] }
106
+ authors = commits.keys.sort_by { |n| - commits[n].size } - team
107
+ puts authors[0..-2].join(', ') << " and " << authors.last,
108
+ "(based on commits included in #{a.release}, but not in #{a.backports})"
109
+ end
110
+
111
+ desc "list of authors"
112
+ task :authors, [:commit_range, :format, :sep] do |t, a|
113
+ a.with_defaults :format => "%s (%d)", :sep => ", ", :commit_range => '--all'
114
+ authors = Hash.new(0)
115
+ blake = "Blake Mizerany"
116
+ overall = 0
117
+ mapping = {
118
+ "blake.mizerany@gmail.com" => blake, "bmizerany" => blake,
119
+ "a_user@mac.com" => blake, "ichverstehe" => "Harry Vangberg",
120
+ "Wu Jiang (nouse)" => "Wu Jiang" }
121
+ `git shortlog -s #{a.commit_range}`.lines.map do |line|
122
+ line = line.force_encoding 'binary' if line.respond_to? :force_encoding
123
+ num, name = line.split("\t", 2).map(&:strip)
124
+ authors[mapping[name] || name] += num.to_i
125
+ overall += num.to_i
126
+ end
127
+ puts "#{overall} commits by #{authors.count} authors:"
128
+ puts authors.sort_by { |n,c| -c }.map { |e| a.format % e }.join(a.sep)
129
+ end
130
+
131
+ desc "generates TOC"
132
+ task :toc, [:readme] do |t, a|
133
+ a.with_defaults :readme => 'README.md'
134
+
135
+ def self.link(title)
136
+ title.downcase.gsub(/(?!-)\W /, '-').gsub(' ', '-').gsub(/(?!-)\W/, '')
137
+ end
138
+
139
+ puts "* [Sinatra](#sinatra)"
140
+ title = Regexp.new('(?<=\* )(.*)') # so Ruby 1.8 doesn't complain
141
+ File.binread(a.readme).scan(/^##.*/) do |line|
142
+ puts line.gsub(/#(?=#)/, ' ').gsub('#', '*').gsub(title) { "[#{$1}](##{link($1)})" }
143
+ end
144
+ end
145
+
146
+ # PACKAGING ============================================================
147
+
148
+ if defined?(Gem)
149
+ # Load the gemspec using the same limitations as github
150
+ def spec
151
+ require 'rubygems' unless defined? Gem::Specification
152
+ @spec ||= eval(File.read('sinatra.gemspec'))
153
+ end
154
+
155
+ def package(ext='')
156
+ "pkg/sinatra-#{spec.version}" + ext
157
+ end
158
+
159
+ desc 'Build packages'
160
+ task :package => %w[.gem .tar.gz].map {|e| package(e)}
161
+
162
+ desc 'Build and install as local gem'
163
+ task :install => package('.gem') do
164
+ sh "gem install #{package('.gem')}"
165
+ end
166
+
167
+ directory 'pkg/'
168
+ CLOBBER.include('pkg')
169
+
170
+ file package('.gem') => %w[pkg/ sinatra.gemspec] + spec.files do |f|
171
+ sh "gem build sinatra.gemspec"
172
+ mv File.basename(f.name), f.name
173
+ end
174
+
175
+ file package('.tar.gz') => %w[pkg/] + spec.files do |f|
176
+ sh <<-SH
177
+ git archive \
178
+ --prefix=sinatra-#{source_version}/ \
179
+ --format=tar \
180
+ HEAD | gzip > #{f.name}
181
+ SH
182
+ end
183
+
184
+ task 'release' => ['test', package('.gem')] do
185
+ if File.binread("CHANGES") =~ /= \d\.\d\.\d . not yet released$/i
186
+ fail 'please update changes first' unless %x{git symbolic-ref HEAD} == "refs/heads/prerelease\n"
187
+ end
188
+
189
+ sh <<-SH
190
+ gem install #{package('.gem')} --local &&
191
+ gem push #{package('.gem')} &&
192
+ git commit --allow-empty -a -m '#{source_version} release' &&
193
+ git tag -s v#{source_version} -m '#{source_version} release' &&
194
+ git tag -s #{source_version} -m '#{source_version} release' &&
195
+ git push && (git push sinatra || true) &&
196
+ git push --tags && (git push sinatra --tags || true)
197
+ SH
198
+ end
199
+ end
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env ruby -I ../lib -I lib
2
+ # coding: utf-8
3
+ require 'sinatra'
4
+ set :server, 'thin'
5
+ connections = []
6
+
7
+ get '/' do
8
+ halt erb(:login) unless params[:user]
9
+ erb :chat, :locals => { :user => params[:user].gsub(/\W/, '') }
10
+ end
11
+
12
+ get '/stream', :provides => 'text/event-stream' do
13
+ stream :keep_open do |out|
14
+ connections << out
15
+ out.callback { connections.delete(out) }
16
+ end
17
+ end
18
+
19
+ post '/' do
20
+ connections.each { |out| out << "data: #{params[:msg]}\n\n" }
21
+ 204 # response without entity body
22
+ end
23
+
24
+ __END__
25
+
26
+ @@ layout
27
+ <html>
28
+ <head>
29
+ <title>Super Simple Chat with Sinatra</title>
30
+ <meta charset="utf-8" />
31
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
32
+ </head>
33
+ <body><%= yield %></body>
34
+ </html>
35
+
36
+ @@ login
37
+ <form action='/'>
38
+ <label for='user'>User Name:</label>
39
+ <input name='user' value='' />
40
+ <input type='submit' value="GO!" />
41
+ </form>
42
+
43
+ @@ chat
44
+ <pre id='chat'></pre>
45
+ <form>
46
+ <input id='msg' placeholder='type message here...' />
47
+ </form>
48
+
49
+ <script>
50
+ // reading
51
+ var es = new EventSource('/stream');
52
+ es.onmessage = function(e) { $('#chat').append(e.data + "\n") };
53
+
54
+ // writing
55
+ $("form").on('submit',function(e) {
56
+ $.post('/', {msg: "<%= user %>: " + $('#msg').val()});
57
+ $('#msg').val(''); $('#msg').focus();
58
+ e.preventDefault();
59
+ });
60
+ </script>
61
+
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby -I ../lib -I lib
2
+ require 'sinatra'
3
+ get('/') { 'this is a simple app' }
@@ -0,0 +1,26 @@
1
+ # this example does *not* work properly with WEBrick
2
+ #
3
+ # run *one* of these:
4
+ #
5
+ # rackup -s mongrel stream.ru # gem install mongrel
6
+ # thin -R stream.ru start # gem install thin
7
+ # unicorn stream.ru # gem install unicorn
8
+ # puma stream.ru # gem install puma
9
+
10
+ require 'sinatra/base'
11
+
12
+ class Stream < Sinatra::Base
13
+ get '/' do
14
+ content_type :txt
15
+
16
+ stream do |out|
17
+ out << "It's gonna be legen -\n"
18
+ sleep 0.5
19
+ out << " (wait for it) \n"
20
+ sleep 1
21
+ out << "- dary!\n"
22
+ end
23
+ end
24
+ end
25
+
26
+ run Stream
@@ -0,0 +1,4 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/main'
3
+
4
+ enable :inline_templates
@@ -0,0 +1,2044 @@
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 'sinatra/show_exceptions'
13
+ require 'sinatra/version'
14
+
15
+ module Sinatra
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
+ HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/
20
+ HEADER_VALUE_WITH_PARAMS = /(?:(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*/
21
+
22
+ # Returns an array of acceptable media types for the response
23
+ def accept
24
+ @env['sinatra.accept'] ||= begin
25
+ if @env.include? 'HTTP_ACCEPT' and @env['HTTP_ACCEPT'].to_s != ''
26
+ @env['HTTP_ACCEPT'].to_s.scan(HEADER_VALUE_WITH_PARAMS).
27
+ map! { |e| AcceptEntry.new(e) }.sort
28
+ else
29
+ [AcceptEntry.new('*/*')]
30
+ end
31
+ end
32
+ end
33
+
34
+ def accept?(type)
35
+ preferred_type(type).to_s.include?(type)
36
+ end
37
+
38
+ def preferred_type(*types)
39
+ accepts = accept # just evaluate once
40
+ return accepts.first if types.empty?
41
+ types.flatten!
42
+ return types.first if accepts.empty?
43
+ accepts.detect do |pattern|
44
+ type = types.detect { |t| File.fnmatch(pattern, t) }
45
+ return type if type
46
+ end
47
+ end
48
+
49
+ alias secure? ssl?
50
+
51
+ def forwarded?
52
+ @env.include? "HTTP_X_FORWARDED_HOST"
53
+ end
54
+
55
+ def safe?
56
+ get? or head? or options? or trace?
57
+ end
58
+
59
+ def idempotent?
60
+ safe? or put? or delete? or link? or unlink?
61
+ end
62
+
63
+ def link?
64
+ request_method == "LINK"
65
+ end
66
+
67
+ def unlink?
68
+ request_method == "UNLINK"
69
+ end
70
+
71
+ private
72
+
73
+ class AcceptEntry
74
+ attr_accessor :params
75
+
76
+ def initialize(entry)
77
+ params = entry.scan(HEADER_PARAM).map! do |s|
78
+ key, value = s.strip.split('=', 2)
79
+ value = value[1..-2].gsub(/\\(.)/, '\1') if value.start_with?('"')
80
+ [key, value]
81
+ end
82
+
83
+ @entry = entry
84
+ @type = entry[/[^;]+/].delete(' ')
85
+ @params = Hash[params]
86
+ @q = @params.delete('q') { 1.0 }.to_f
87
+ end
88
+
89
+ def <=>(other)
90
+ other.priority <=> self.priority
91
+ end
92
+
93
+ def priority
94
+ # We sort in descending order; better matches should be higher.
95
+ [ @q, -@type.count('*'), @params.size ]
96
+ end
97
+
98
+ def to_str
99
+ @type
100
+ end
101
+
102
+ def to_s(full = false)
103
+ full ? entry : to_str
104
+ end
105
+
106
+ def respond_to?(*args)
107
+ super or to_str.respond_to?(*args)
108
+ end
109
+
110
+ def method_missing(*args, &block)
111
+ to_str.send(*args, &block)
112
+ end
113
+ end
114
+ end
115
+
116
+ # The response object. See Rack::Response and Rack::Response::Helpers for
117
+ # more info:
118
+ # http://rack.rubyforge.org/doc/classes/Rack/Response.html
119
+ # http://rack.rubyforge.org/doc/classes/Rack/Response/Helpers.html
120
+ class Response < Rack::Response
121
+ DROP_BODY_RESPONSES = [204, 205, 304]
122
+ def initialize(*)
123
+ super
124
+ headers['Content-Type'] ||= 'text/html'
125
+ end
126
+
127
+ def body=(value)
128
+ value = value.body while Rack::Response === value
129
+ @body = String === value ? [value.to_str] : value
130
+ end
131
+
132
+ def each
133
+ block_given? ? super : enum_for(:each)
134
+ end
135
+
136
+ def finish
137
+ result = body
138
+
139
+ if drop_content_info?
140
+ headers.delete "Content-Length"
141
+ headers.delete "Content-Type"
142
+ end
143
+
144
+ if drop_body?
145
+ close
146
+ result = []
147
+ end
148
+
149
+ if calculate_content_length?
150
+ # if some other code has already set Content-Length, don't muck with it
151
+ # currently, this would be the static file-handler
152
+ headers["Content-Length"] = body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s
153
+ end
154
+
155
+ [status.to_i, headers, result]
156
+ end
157
+
158
+ private
159
+
160
+ def calculate_content_length?
161
+ headers["Content-Type"] and not headers["Content-Length"] and Array === body
162
+ end
163
+
164
+ def drop_content_info?
165
+ status.to_i / 100 == 1 or drop_body?
166
+ end
167
+
168
+ def drop_body?
169
+ DROP_BODY_RESPONSES.include?(status.to_i)
170
+ end
171
+ end
172
+
173
+ # Some Rack handlers (Thin, Rainbows!) implement an extended body object protocol, however,
174
+ # some middleware (namely Rack::Lint) will break it by not mirroring the methods in question.
175
+ # This middleware will detect an extended body object and will make sure it reaches the
176
+ # handler directly. We do this here, so our middleware and middleware set up by the app will
177
+ # still be able to run.
178
+ class ExtendedRack < Struct.new(:app)
179
+ def call(env)
180
+ result, callback = app.call(env), env['async.callback']
181
+ return result unless callback and async?(*result)
182
+ after_response { callback.call result }
183
+ setup_close(env, *result)
184
+ throw :async
185
+ end
186
+
187
+ private
188
+
189
+ def setup_close(env, status, headers, body)
190
+ return unless body.respond_to? :close and env.include? 'async.close'
191
+ env['async.close'].callback { body.close }
192
+ env['async.close'].errback { body.close }
193
+ end
194
+
195
+ def after_response(&block)
196
+ raise NotImplementedError, "only supports EventMachine at the moment" unless defined? EventMachine
197
+ EventMachine.next_tick(&block)
198
+ end
199
+
200
+ def async?(status, headers, body)
201
+ return true if status == -1
202
+ body.respond_to? :callback and body.respond_to? :errback
203
+ end
204
+ end
205
+
206
+ # Behaves exactly like Rack::CommonLogger with the notable exception that it does nothing,
207
+ # if another CommonLogger is already in the middleware chain.
208
+ class CommonLogger < Rack::CommonLogger
209
+ def call(env)
210
+ env['sinatra.commonlogger'] ? @app.call(env) : super
211
+ end
212
+
213
+ superclass.class_eval do
214
+ alias call_without_check call unless method_defined? :call_without_check
215
+ def call(env)
216
+ env['sinatra.commonlogger'] = true
217
+ call_without_check(env)
218
+ end
219
+ end
220
+ end
221
+
222
+ class NotFound < NameError #:nodoc:
223
+ def http_status; 404 end
224
+ end
225
+
226
+ # Methods available to routes, before/after filters, and views.
227
+ module Helpers
228
+ # Set or retrieve the response status code.
229
+ def status(value = nil)
230
+ response.status = value if value
231
+ response.status
232
+ end
233
+
234
+ # Set or retrieve the response body. When a block is given,
235
+ # evaluation is deferred until the body is read with #each.
236
+ def body(value = nil, &block)
237
+ if block_given?
238
+ def block.each; yield(call) end
239
+ response.body = block
240
+ elsif value
241
+ headers.delete 'Content-Length' unless request.head? || value.is_a?(Rack::File) || value.is_a?(Stream)
242
+ response.body = value
243
+ else
244
+ response.body
245
+ end
246
+ end
247
+
248
+ # Halt processing and redirect to the URI provided.
249
+ def redirect(uri, *args)
250
+ if env['HTTP_VERSION'] == 'HTTP/1.1' and env["REQUEST_METHOD"] != 'GET'
251
+ status 303
252
+ else
253
+ status 302
254
+ end
255
+
256
+ # According to RFC 2616 section 14.30, "the field value consists of a
257
+ # single absolute URI"
258
+ response['Location'] = uri(uri.to_s, settings.absolute_redirects?, settings.prefixed_redirects?)
259
+ halt(*args)
260
+ end
261
+
262
+ # Generates the absolute URI for a given path in the app.
263
+ # Takes Rack routers and reverse proxies into account.
264
+ def uri(addr = nil, absolute = true, add_script_name = true)
265
+ return addr if addr =~ /\A[A-z][A-z0-9\+\.\-]*:/
266
+ uri = [host = ""]
267
+ if absolute
268
+ host << "http#{'s' if request.secure?}://"
269
+ if request.forwarded? or request.port != (request.secure? ? 443 : 80)
270
+ host << request.host_with_port
271
+ else
272
+ host << request.host
273
+ end
274
+ end
275
+ uri << request.script_name.to_s if add_script_name
276
+ uri << (addr ? addr : request.path_info).to_s
277
+ File.join uri
278
+ end
279
+
280
+ alias url uri
281
+ alias to uri
282
+
283
+ # Halt processing and return the error status provided.
284
+ def error(code, body = nil)
285
+ code, body = 500, code.to_str if code.respond_to? :to_str
286
+ response.body = body unless body.nil?
287
+ halt code
288
+ end
289
+
290
+ # Halt processing and return a 404 Not Found.
291
+ def not_found(body = nil)
292
+ error 404, body
293
+ end
294
+
295
+ # Set multiple response headers with Hash.
296
+ def headers(hash = nil)
297
+ response.headers.merge! hash if hash
298
+ response.headers
299
+ end
300
+
301
+ # Access the underlying Rack session.
302
+ def session
303
+ request.session
304
+ end
305
+
306
+ # Access shared logger object.
307
+ def logger
308
+ request.logger
309
+ end
310
+
311
+ # Look up a media type by file extension in Rack's mime registry.
312
+ def mime_type(type)
313
+ Base.mime_type(type)
314
+ end
315
+
316
+ # Set the Content-Type of the response body given a media type or file
317
+ # extension.
318
+ def content_type(type = nil, params = {})
319
+ return response['Content-Type'] unless type
320
+ default = params.delete :default
321
+ mime_type = mime_type(type) || default
322
+ fail "Unknown media type: %p" % type if mime_type.nil?
323
+ mime_type = mime_type.dup
324
+ unless params.include? :charset or settings.add_charset.all? { |p| not p === mime_type }
325
+ params[:charset] = params.delete('charset') || settings.default_encoding
326
+ end
327
+ params.delete :charset if mime_type.include? 'charset'
328
+ unless params.empty?
329
+ mime_type << (mime_type.include?(';') ? ', ' : ';')
330
+ mime_type << params.map do |key, val|
331
+ val = val.inspect if val =~ /[";,]/
332
+ "#{key}=#{val}"
333
+ end.join(', ')
334
+ end
335
+ response['Content-Type'] = mime_type
336
+ end
337
+
338
+ # Set the Content-Disposition to "attachment" with the specified filename,
339
+ # instructing the user agents to prompt to save.
340
+ def attachment(filename = nil, disposition = 'attachment')
341
+ response['Content-Disposition'] = disposition.to_s
342
+ if filename
343
+ params = '; filename="%s"' % File.basename(filename)
344
+ response['Content-Disposition'] << params
345
+ ext = File.extname(filename)
346
+ content_type(ext) unless response['Content-Type'] or ext.empty?
347
+ end
348
+ end
349
+
350
+ # Use the contents of the file at +path+ as the response body.
351
+ def send_file(path, opts = {})
352
+ if opts[:type] or not response['Content-Type']
353
+ content_type opts[:type] || File.extname(path), :default => 'application/octet-stream'
354
+ end
355
+
356
+ disposition = opts[:disposition]
357
+ filename = opts[:filename]
358
+ disposition = 'attachment' if disposition.nil? and filename
359
+ filename = path if filename.nil?
360
+ attachment(filename, disposition) if disposition
361
+
362
+ last_modified opts[:last_modified] if opts[:last_modified]
363
+
364
+ file = Rack::File.new nil
365
+ file.path = path
366
+ result = file.serving env
367
+ result[1].each { |k,v| headers[k] ||= v }
368
+ headers['Content-Length'] = result[1]['Content-Length']
369
+ opts[:status] &&= Integer(opts[:status])
370
+ halt opts[:status] || result[0], result[2]
371
+ rescue Errno::ENOENT
372
+ not_found
373
+ end
374
+
375
+ # Class of the response body in case you use #stream.
376
+ #
377
+ # Three things really matter: The front and back block (back being the
378
+ # block generating content, front the one sending it to the client) and
379
+ # the scheduler, integrating with whatever concurrency feature the Rack
380
+ # handler is using.
381
+ #
382
+ # Scheduler has to respond to defer and schedule.
383
+ class Stream
384
+ def self.schedule(*) yield end
385
+ def self.defer(*) yield end
386
+
387
+ def initialize(scheduler = self.class, keep_open = false, &back)
388
+ @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
389
+ @callbacks, @closed = [], false
390
+ end
391
+
392
+ def close
393
+ return if @closed
394
+ @closed = true
395
+ @scheduler.schedule { @callbacks.each { |c| c.call }}
396
+ end
397
+
398
+ def each(&front)
399
+ @front = front
400
+ @scheduler.defer do
401
+ begin
402
+ @back.call(self)
403
+ rescue Exception => e
404
+ @scheduler.schedule { raise e }
405
+ end
406
+ close unless @keep_open
407
+ end
408
+ end
409
+
410
+ def <<(data)
411
+ @scheduler.schedule { @front.call(data.to_s) }
412
+ self
413
+ end
414
+
415
+ def callback(&block)
416
+ return yield if @closed
417
+ @callbacks << block
418
+ end
419
+
420
+ alias errback callback
421
+
422
+ def closed?
423
+ @closed
424
+ end
425
+ end
426
+
427
+ # Allows to start sending data to the client even though later parts of
428
+ # the response body have not yet been generated.
429
+ #
430
+ # The close parameter specifies whether Stream#close should be called
431
+ # after the block has been executed. This is only relevant for evented
432
+ # servers like Thin or Rainbows.
433
+ def stream(keep_open = false)
434
+ scheduler = env['async.callback'] ? EventMachine : Stream
435
+ current = @params.dup
436
+ body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
437
+ end
438
+
439
+ # Specify response freshness policy for HTTP caches (Cache-Control header).
440
+ # Any number of non-value directives (:public, :private, :no_cache,
441
+ # :no_store, :must_revalidate, :proxy_revalidate) may be passed along with
442
+ # a Hash of value directives (:max_age, :min_stale, :s_max_age).
443
+ #
444
+ # cache_control :public, :must_revalidate, :max_age => 60
445
+ # => Cache-Control: public, must-revalidate, max-age=60
446
+ #
447
+ # See RFC 2616 / 14.9 for more on standard cache control directives:
448
+ # http://tools.ietf.org/html/rfc2616#section-14.9.1
449
+ def cache_control(*values)
450
+ if values.last.kind_of?(Hash)
451
+ hash = values.pop
452
+ hash.reject! { |k,v| v == false }
453
+ hash.reject! { |k,v| values << k if v == true }
454
+ else
455
+ hash = {}
456
+ end
457
+
458
+ values.map! { |value| value.to_s.tr('_','-') }
459
+ hash.each do |key, value|
460
+ key = key.to_s.tr('_', '-')
461
+ value = value.to_i if key == "max-age"
462
+ values << "#{key}=#{value}"
463
+ end
464
+
465
+ response['Cache-Control'] = values.join(', ') if values.any?
466
+ end
467
+
468
+ # Set the Expires header and Cache-Control/max-age directive. Amount
469
+ # can be an integer number of seconds in the future or a Time object
470
+ # indicating when the response should be considered "stale". The remaining
471
+ # "values" arguments are passed to the #cache_control helper:
472
+ #
473
+ # expires 500, :public, :must_revalidate
474
+ # => Cache-Control: public, must-revalidate, max-age=60
475
+ # => Expires: Mon, 08 Jun 2009 08:50:17 GMT
476
+ #
477
+ def expires(amount, *values)
478
+ values << {} unless values.last.kind_of?(Hash)
479
+
480
+ if amount.is_a? Integer
481
+ time = Time.now + amount.to_i
482
+ max_age = amount
483
+ else
484
+ time = time_for amount
485
+ max_age = time - Time.now
486
+ end
487
+
488
+ values.last.merge!(:max_age => max_age)
489
+ cache_control(*values)
490
+
491
+ response['Expires'] = time.httpdate
492
+ end
493
+
494
+ # Set the last modified time of the resource (HTTP 'Last-Modified' header)
495
+ # and halt if conditional GET matches. The +time+ argument is a Time,
496
+ # DateTime, or other object that responds to +to_time+.
497
+ #
498
+ # When the current request includes an 'If-Modified-Since' header that is
499
+ # equal or later than the time specified, execution is immediately halted
500
+ # with a '304 Not Modified' response.
501
+ def last_modified(time)
502
+ return unless time
503
+ time = time_for time
504
+ response['Last-Modified'] = time.httpdate
505
+ return if env['HTTP_IF_NONE_MATCH']
506
+
507
+ if status == 200 and env['HTTP_IF_MODIFIED_SINCE']
508
+ # compare based on seconds since epoch
509
+ since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
510
+ halt 304 if since >= time.to_i
511
+ end
512
+
513
+ if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE']
514
+ # compare based on seconds since epoch
515
+ since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
516
+ halt 412 if since < time.to_i
517
+ end
518
+ rescue ArgumentError
519
+ end
520
+
521
+ ETAG_KINDS = [:strong, :weak]
522
+ # Set the response entity tag (HTTP 'ETag' header) and halt if conditional
523
+ # GET matches. The +value+ argument is an identifier that uniquely
524
+ # identifies the current version of the resource. The +kind+ argument
525
+ # indicates whether the etag should be used as a :strong (default) or :weak
526
+ # cache validator.
527
+ #
528
+ # When the current request includes an 'If-None-Match' header with a
529
+ # matching etag, execution is immediately halted. If the request method is
530
+ # GET or HEAD, a '304 Not Modified' response is sent.
531
+ def etag(value, options = {})
532
+ # Before touching this code, please double check RFC 2616 14.24 and 14.26.
533
+ options = {:kind => options} unless Hash === options
534
+ kind = options[:kind] || :strong
535
+ new_resource = options.fetch(:new_resource) { request.post? }
536
+
537
+ unless ETAG_KINDS.include?(kind)
538
+ raise ArgumentError, ":strong or :weak expected"
539
+ end
540
+
541
+ value = '"%s"' % value
542
+ value = "W/#{value}" if kind == :weak
543
+ response['ETag'] = value
544
+
545
+ if success? or status == 304
546
+ if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
547
+ halt(request.safe? ? 304 : 412)
548
+ end
549
+
550
+ if env['HTTP_IF_MATCH']
551
+ halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
552
+ end
553
+ end
554
+ end
555
+
556
+ # Sugar for redirect (example: redirect back)
557
+ def back
558
+ request.referer
559
+ end
560
+
561
+ # whether or not the status is set to 1xx
562
+ def informational?
563
+ status.between? 100, 199
564
+ end
565
+
566
+ # whether or not the status is set to 2xx
567
+ def success?
568
+ status.between? 200, 299
569
+ end
570
+
571
+ # whether or not the status is set to 3xx
572
+ def redirect?
573
+ status.between? 300, 399
574
+ end
575
+
576
+ # whether or not the status is set to 4xx
577
+ def client_error?
578
+ status.between? 400, 499
579
+ end
580
+
581
+ # whether or not the status is set to 5xx
582
+ def server_error?
583
+ status.between? 500, 599
584
+ end
585
+
586
+ # whether or not the status is set to 404
587
+ def not_found?
588
+ status == 404
589
+ end
590
+
591
+ # Generates a Time object from the given value.
592
+ # Used by #expires and #last_modified.
593
+ def time_for(value)
594
+ if value.respond_to? :to_time
595
+ value.to_time
596
+ elsif value.is_a? Time
597
+ value
598
+ elsif value.respond_to? :new_offset
599
+ # DateTime#to_time does the same on 1.9
600
+ d = value.new_offset 0
601
+ t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction
602
+ t.getlocal
603
+ elsif value.respond_to? :mday
604
+ # Date#to_time does the same on 1.9
605
+ Time.local(value.year, value.mon, value.mday)
606
+ elsif value.is_a? Numeric
607
+ Time.at value
608
+ else
609
+ Time.parse value.to_s
610
+ end
611
+ rescue ArgumentError => boom
612
+ raise boom
613
+ rescue Exception
614
+ raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
615
+ end
616
+
617
+ private
618
+
619
+ # Helper method checking if a ETag value list includes the current ETag.
620
+ def etag_matches?(list, new_resource = request.post?)
621
+ return !new_resource if list == '*'
622
+ list.to_s.split(/\s*,\s*/).include? response['ETag']
623
+ end
624
+
625
+ def with_params(temp_params)
626
+ original, @params = @params, temp_params
627
+ yield
628
+ ensure
629
+ @params = original if original
630
+ end
631
+ end
632
+
633
+ private
634
+
635
+ # Template rendering methods. Each method takes the name of a template
636
+ # to render as a Symbol and returns a String with the rendered output,
637
+ # as well as an optional hash with additional options.
638
+ #
639
+ # `template` is either the name or path of the template as symbol
640
+ # (Use `:'subdir/myview'` for views in subdirectories), or a string
641
+ # that will be rendered.
642
+ #
643
+ # Possible options are:
644
+ # :content_type The content type to use, same arguments as content_type.
645
+ # :layout If set to something falsy, no layout is rendered, otherwise
646
+ # the specified layout is used (Ignored for `sass` and `less`)
647
+ # :layout_engine Engine to use for rendering the layout.
648
+ # :locals A hash with local variables that should be available
649
+ # in the template
650
+ # :scope If set, template is evaluate with the binding of the given
651
+ # object rather than the application instance.
652
+ # :views Views directory to use.
653
+ module Templates
654
+ module ContentTyped
655
+ attr_accessor :content_type
656
+ end
657
+
658
+ def initialize
659
+ super
660
+ @default_layout = :layout
661
+ @preferred_extension = nil
662
+ end
663
+
664
+ def erb(template, options = {}, locals = {}, &block)
665
+ render(:erb, template, options, locals, &block)
666
+ end
667
+
668
+ def erubis(template, options = {}, locals = {})
669
+ warn "Sinatra::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \
670
+ "If you have Erubis installed, it will be used automatically."
671
+ render :erubis, template, options, locals
672
+ end
673
+
674
+ def haml(template, options = {}, locals = {}, &block)
675
+ render(:haml, template, options, locals, &block)
676
+ end
677
+
678
+ def sass(template, options = {}, locals = {})
679
+ options.merge! :layout => false, :default_content_type => :css
680
+ render :sass, template, options, locals
681
+ end
682
+
683
+ def scss(template, options = {}, locals = {})
684
+ options.merge! :layout => false, :default_content_type => :css
685
+ render :scss, template, options, locals
686
+ end
687
+
688
+ def less(template, options = {}, locals = {})
689
+ options.merge! :layout => false, :default_content_type => :css
690
+ render :less, template, options, locals
691
+ end
692
+
693
+ def stylus(template, options={}, locals={})
694
+ options.merge! :layout => false, :default_content_type => :css
695
+ render :styl, template, options, locals
696
+ end
697
+
698
+ def builder(template = nil, options = {}, locals = {}, &block)
699
+ options[:default_content_type] = :xml
700
+ render_ruby(:builder, template, options, locals, &block)
701
+ end
702
+
703
+ def liquid(template, options = {}, locals = {}, &block)
704
+ render(:liquid, template, options, locals, &block)
705
+ end
706
+
707
+ def markdown(template, options = {}, locals = {})
708
+ render :markdown, template, options, locals
709
+ end
710
+
711
+ def textile(template, options = {}, locals = {})
712
+ render :textile, template, options, locals
713
+ end
714
+
715
+ def rdoc(template, options = {}, locals = {})
716
+ render :rdoc, template, options, locals
717
+ end
718
+
719
+ def asciidoc(template, options = {}, locals = {})
720
+ render :asciidoc, template, options, locals
721
+ end
722
+
723
+ def radius(template, options = {}, locals = {})
724
+ render :radius, template, options, locals
725
+ end
726
+
727
+ def markaby(template = nil, options = {}, locals = {}, &block)
728
+ render_ruby(:mab, template, options, locals, &block)
729
+ end
730
+
731
+ def coffee(template, options = {}, locals = {})
732
+ options.merge! :layout => false, :default_content_type => :js
733
+ render :coffee, template, options, locals
734
+ end
735
+
736
+ def nokogiri(template = nil, options = {}, locals = {}, &block)
737
+ options[:default_content_type] = :xml
738
+ render_ruby(:nokogiri, template, options, locals, &block)
739
+ end
740
+
741
+ def slim(template, options = {}, locals = {}, &block)
742
+ render(:slim, template, options, locals, &block)
743
+ end
744
+
745
+ def creole(template, options = {}, locals = {})
746
+ render :creole, template, options, locals
747
+ end
748
+
749
+ def mediawiki(template, options = {}, locals = {})
750
+ render :mediawiki, template, options, locals
751
+ end
752
+
753
+ def wlang(template, options = {}, locals = {}, &block)
754
+ render(:wlang, template, options, locals, &block)
755
+ end
756
+
757
+ def yajl(template, options = {}, locals = {})
758
+ options[:default_content_type] = :json
759
+ render :yajl, template, options, locals
760
+ end
761
+
762
+ def rabl(template, options = {}, locals = {})
763
+ Rabl.register!
764
+ render :rabl, template, options, locals
765
+ end
766
+
767
+ # Calls the given block for every possible template file in views,
768
+ # named name.ext, where ext is registered on engine.
769
+ def find_template(views, name, engine)
770
+ yield ::File.join(views, "#{name}.#{@preferred_extension}")
771
+ Tilt.mappings.each do |ext, engines|
772
+ next unless ext != @preferred_extension and engines.include? engine
773
+ yield ::File.join(views, "#{name}.#{ext}")
774
+ end
775
+ end
776
+
777
+ private
778
+
779
+ # logic shared between builder and nokogiri
780
+ def render_ruby(engine, template, options = {}, locals = {}, &block)
781
+ options, template = template, nil if template.is_a?(Hash)
782
+ template = Proc.new { block } if template.nil?
783
+ render engine, template, options, locals
784
+ end
785
+
786
+ def render(engine, data, options = {}, locals = {}, &block)
787
+ # merge app-level options
788
+ engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
789
+ options.merge!(engine_options) { |key, v1, v2| v1 }
790
+
791
+ # extract generic options
792
+ locals = options.delete(:locals) || locals || {}
793
+ views = options.delete(:views) || settings.views || "./views"
794
+ layout = options[:layout]
795
+ layout = false if layout.nil? && options.include?(:layout)
796
+ eat_errors = layout.nil?
797
+ layout = engine_options[:layout] if layout.nil? or (layout == true && engine_options[:layout] != false)
798
+ layout = @default_layout if layout.nil? or layout == true
799
+ layout_options = options.delete(:layout_options) || {}
800
+ content_type = options.delete(:content_type) || options.delete(:default_content_type)
801
+ layout_engine = options.delete(:layout_engine) || engine
802
+ scope = options.delete(:scope) || self
803
+ options.delete(:layout)
804
+
805
+ # set some defaults
806
+ options[:outvar] ||= '@_out_buf'
807
+ options[:default_encoding] ||= settings.default_encoding
808
+
809
+ # compile and render template
810
+ begin
811
+ layout_was = @default_layout
812
+ @default_layout = false
813
+ template = compile_template(engine, data, options, views)
814
+ output = template.render(scope, locals, &block)
815
+ ensure
816
+ @default_layout = layout_was
817
+ end
818
+
819
+ # render layout
820
+ if layout
821
+ options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope).
822
+ merge!(layout_options)
823
+ catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
824
+ end
825
+
826
+ output.extend(ContentTyped).content_type = content_type if content_type
827
+ output
828
+ end
829
+
830
+ def compile_template(engine, data, options, views)
831
+ eat_errors = options.delete :eat_errors
832
+ template_cache.fetch engine, data, options, views do
833
+ template = Tilt[engine]
834
+ raise "Template engine not found: #{engine}" if template.nil?
835
+
836
+ case data
837
+ when Symbol
838
+ body, path, line = settings.templates[data]
839
+ if body
840
+ body = body.call if body.respond_to?(:call)
841
+ template.new(path, line.to_i, options) { body }
842
+ else
843
+ found = false
844
+ @preferred_extension = engine.to_s
845
+ find_template(views, data, template) do |file|
846
+ path ||= file # keep the initial path rather than the last one
847
+ if found = File.exist?(file)
848
+ path = file
849
+ break
850
+ end
851
+ end
852
+ throw :layout_missing if eat_errors and not found
853
+ template.new(path, 1, options)
854
+ end
855
+ when Proc, String
856
+ body = data.is_a?(String) ? Proc.new { data } : data
857
+ path, line = settings.caller_locations.first
858
+ template.new(path, line.to_i, options, &body)
859
+ else
860
+ raise ArgumentError, "Sorry, don't know how to render #{data.inspect}."
861
+ end
862
+ end
863
+ end
864
+ end
865
+
866
+ # Base class for all Sinatra applications and middleware.
867
+ class Base
868
+ include Rack::Utils
869
+ include Helpers
870
+ include Templates
871
+
872
+ URI_INSTANCE = URI.const_defined?(:Parser) ? URI::Parser.new : URI
873
+
874
+ attr_accessor :app, :env, :request, :response, :params
875
+ attr_reader :template_cache
876
+
877
+ def initialize(app = nil)
878
+ super()
879
+ @app = app
880
+ @template_cache = Tilt::Cache.new
881
+ yield self if block_given?
882
+ end
883
+
884
+ # Rack call interface.
885
+ def call(env)
886
+ dup.call!(env)
887
+ end
888
+
889
+ def call!(env) # :nodoc:
890
+ @env = env
891
+ @request = Request.new(env)
892
+ @response = Response.new
893
+ @params = indifferent_params(@request.params)
894
+ template_cache.clear if settings.reload_templates
895
+ force_encoding(@params)
896
+
897
+ @response['Content-Type'] = nil
898
+ invoke { dispatch! }
899
+ invoke { error_block!(response.status) } unless @env['sinatra.error']
900
+
901
+ unless @response['Content-Type']
902
+ if Array === body and body[0].respond_to? :content_type
903
+ content_type body[0].content_type
904
+ else
905
+ content_type :html
906
+ end
907
+ end
908
+
909
+ @response.finish
910
+ end
911
+
912
+ # Access settings defined with Base.set.
913
+ def self.settings
914
+ self
915
+ end
916
+
917
+ # Access settings defined with Base.set.
918
+ def settings
919
+ self.class.settings
920
+ end
921
+
922
+ def options
923
+ warn "Sinatra::Base#options is deprecated and will be removed, " \
924
+ "use #settings instead."
925
+ settings
926
+ end
927
+
928
+ # Exit the current block, halts any further processing
929
+ # of the request, and returns the specified response.
930
+ def halt(*response)
931
+ response = response.first if response.length == 1
932
+ throw :halt, response
933
+ end
934
+
935
+ # Pass control to the next matching route.
936
+ # If there are no more matching routes, Sinatra will
937
+ # return a 404 response.
938
+ def pass(&block)
939
+ throw :pass, block
940
+ end
941
+
942
+ # Forward the request to the downstream app -- middleware only.
943
+ def forward
944
+ fail "downstream app not set" unless @app.respond_to? :call
945
+ status, headers, body = @app.call env
946
+ @response.status = status
947
+ @response.body = body
948
+ @response.headers.merge! headers
949
+ nil
950
+ end
951
+
952
+ private
953
+
954
+ # Run filters defined on the class and all superclasses.
955
+ def filter!(type, base = settings)
956
+ filter! type, base.superclass if base.superclass.respond_to?(:filters)
957
+ base.filters[type].each { |args| process_route(*args) }
958
+ end
959
+
960
+ # Run routes defined on the class and all superclasses.
961
+ def route!(base = settings, pass_block = nil)
962
+ if routes = base.routes[@request.request_method]
963
+ routes.each do |pattern, keys, conditions, block|
964
+ returned_pass_block = process_route(pattern, keys, conditions) do |*args|
965
+ env['sinatra.route'] = block.instance_variable_get(:@route_name)
966
+ route_eval { block[*args] }
967
+ end
968
+
969
+ # don't wipe out pass_block in superclass
970
+ pass_block = returned_pass_block if returned_pass_block
971
+ end
972
+ end
973
+
974
+ # Run routes defined in superclass.
975
+ if base.superclass.respond_to?(:routes)
976
+ return route!(base.superclass, pass_block)
977
+ end
978
+
979
+ route_eval(&pass_block) if pass_block
980
+ route_missing
981
+ end
982
+
983
+ # Run a route block and throw :halt with the result.
984
+ def route_eval
985
+ throw :halt, yield
986
+ end
987
+
988
+ # If the current request matches pattern and conditions, fill params
989
+ # with keys and call the given block.
990
+ # Revert params afterwards.
991
+ #
992
+ # Returns pass block.
993
+ def process_route(pattern, keys, conditions, block = nil, values = [])
994
+ route = @request.path_info
995
+ route = '/' if route.empty? and not settings.empty_path_info?
996
+ return unless match = pattern.match(route)
997
+ values += match.captures.map! { |v| force_encoding URI_INSTANCE.unescape(v) if v }
998
+
999
+ if values.any?
1000
+ original, @params = params, params.merge('splat' => [], 'captures' => values)
1001
+ keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v }
1002
+ end
1003
+
1004
+ catch(:pass) do
1005
+ conditions.each { |c| throw :pass if c.bind(self).call == false }
1006
+ block ? block[self, values] : yield(self, values)
1007
+ end
1008
+ ensure
1009
+ @params = original if original
1010
+ end
1011
+
1012
+ # No matching route was found or all routes passed. The default
1013
+ # implementation is to forward the request downstream when running
1014
+ # as middleware (@app is non-nil); when no downstream app is set, raise
1015
+ # a NotFound exception. Subclasses can override this method to perform
1016
+ # custom route miss logic.
1017
+ def route_missing
1018
+ if @app
1019
+ forward
1020
+ else
1021
+ raise NotFound
1022
+ end
1023
+ end
1024
+
1025
+ # Attempt to serve static files from public directory. Throws :halt when
1026
+ # a matching file is found, returns nil otherwise.
1027
+ def static!(options = {})
1028
+ return if (public_dir = settings.public_folder).nil?
1029
+ path = File.expand_path("#{public_dir}#{unescape(request.path_info)}" )
1030
+ return unless File.file?(path)
1031
+
1032
+ env['sinatra.static_file'] = path
1033
+ cache_control(*settings.static_cache_control) if settings.static_cache_control?
1034
+ send_file path, options.merge(:disposition => nil)
1035
+ end
1036
+
1037
+ # Enable string or symbol key access to the nested params hash.
1038
+ def indifferent_params(object)
1039
+ case object
1040
+ when Hash
1041
+ new_hash = indifferent_hash
1042
+ object.each { |key, value| new_hash[key] = indifferent_params(value) }
1043
+ new_hash
1044
+ when Array
1045
+ object.map { |item| indifferent_params(item) }
1046
+ else
1047
+ object
1048
+ end
1049
+ end
1050
+
1051
+ # Creates a Hash with indifferent access.
1052
+ def indifferent_hash
1053
+ Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
1054
+ end
1055
+
1056
+ # Run the block with 'throw :halt' support and apply result to the response.
1057
+ def invoke
1058
+ res = catch(:halt) { yield }
1059
+ res = [res] if Fixnum === res or String === res
1060
+ if Array === res and Fixnum === res.first
1061
+ res = res.dup
1062
+ status(res.shift)
1063
+ body(res.pop)
1064
+ headers(*res)
1065
+ elsif res.respond_to? :each
1066
+ body res
1067
+ end
1068
+ nil # avoid double setting the same response tuple twice
1069
+ end
1070
+
1071
+ # Dispatch a request with error handling.
1072
+ def dispatch!
1073
+ invoke do
1074
+ static! if settings.static? && (request.get? || request.head?)
1075
+ filter! :before
1076
+ route!
1077
+ end
1078
+ rescue ::Exception => boom
1079
+ invoke { handle_exception!(boom) }
1080
+ ensure
1081
+ begin
1082
+ filter! :after unless env['sinatra.static_file']
1083
+ rescue ::Exception => boom
1084
+ invoke { handle_exception!(boom) } unless @env['sinatra.error']
1085
+ end
1086
+ end
1087
+
1088
+ # Error handling during requests.
1089
+ def handle_exception!(boom)
1090
+ @env['sinatra.error'] = boom
1091
+
1092
+ if boom.respond_to? :http_status
1093
+ status(boom.http_status)
1094
+ elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
1095
+ status(boom.code)
1096
+ else
1097
+ status(500)
1098
+ end
1099
+
1100
+ status(500) unless status.between? 400, 599
1101
+
1102
+ if server_error?
1103
+ dump_errors! boom if settings.dump_errors?
1104
+ raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
1105
+ end
1106
+
1107
+ if not_found?
1108
+ headers['X-Cascade'] = 'pass' if settings.x_cascade?
1109
+ body '<h1>Not Found</h1>'
1110
+ end
1111
+
1112
+ res = error_block!(boom.class, boom) || error_block!(status, boom)
1113
+ return res if res or not server_error?
1114
+ raise boom if settings.raise_errors? or settings.show_exceptions?
1115
+ error_block! Exception, boom
1116
+ end
1117
+
1118
+ # Find an custom error block for the key(s) specified.
1119
+ def error_block!(key, *block_params)
1120
+ base = settings
1121
+ while base.respond_to?(:errors)
1122
+ next base = base.superclass unless args_array = base.errors[key]
1123
+ args_array.reverse_each do |args|
1124
+ first = args == args_array.first
1125
+ args += [block_params]
1126
+ resp = process_route(*args)
1127
+ return resp unless resp.nil? && !first
1128
+ end
1129
+ end
1130
+ return false unless key.respond_to? :superclass and key.superclass < Exception
1131
+ error_block!(key.superclass, *block_params)
1132
+ end
1133
+
1134
+ def dump_errors!(boom)
1135
+ msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
1136
+ @env['rack.errors'].puts(msg)
1137
+ end
1138
+
1139
+ class << self
1140
+ CALLERS_TO_IGNORE = [ # :nodoc:
1141
+ /\/sinatra(\/(base|main|show_exceptions))?\.rb$/, # all sinatra code
1142
+ /lib\/tilt.*\.rb$/, # all tilt code
1143
+ /^\(.*\)$/, # generated code
1144
+ /rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks
1145
+ /active_support/, # active_support require hacks
1146
+ /bundler(\/runtime)?\.rb/, # bundler require hacks
1147
+ /<internal:/, # internal in ruby >= 1.9.2
1148
+ /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files
1149
+ ]
1150
+
1151
+ # contrary to what the comment said previously, rubinius never supported this
1152
+ if defined?(RUBY_IGNORE_CALLERS)
1153
+ warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0"
1154
+ CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS)
1155
+ end
1156
+
1157
+ attr_reader :routes, :filters, :templates, :errors
1158
+
1159
+ # Removes all routes, filters, middleware and extension hooks from the
1160
+ # current class (not routes/filters/... defined by its superclass).
1161
+ def reset!
1162
+ @conditions = []
1163
+ @routes = {}
1164
+ @filters = {:before => [], :after => []}
1165
+ @errors = {}
1166
+ @middleware = []
1167
+ @prototype = nil
1168
+ @extensions = []
1169
+
1170
+ if superclass.respond_to?(:templates)
1171
+ @templates = Hash.new { |hash,key| superclass.templates[key] }
1172
+ else
1173
+ @templates = {}
1174
+ end
1175
+ end
1176
+
1177
+ # Extension modules registered on this class and all superclasses.
1178
+ def extensions
1179
+ if superclass.respond_to?(:extensions)
1180
+ (@extensions + superclass.extensions).uniq
1181
+ else
1182
+ @extensions
1183
+ end
1184
+ end
1185
+
1186
+ # Middleware used in this class and all superclasses.
1187
+ def middleware
1188
+ if superclass.respond_to?(:middleware)
1189
+ superclass.middleware + @middleware
1190
+ else
1191
+ @middleware
1192
+ end
1193
+ end
1194
+
1195
+ # Sets an option to the given value. If the value is a proc,
1196
+ # the proc will be called every time the option is accessed.
1197
+ def set(option, value = (not_set = true), ignore_setter = false, &block)
1198
+ raise ArgumentError if block and !not_set
1199
+ value, not_set = block, false if block
1200
+
1201
+ if not_set
1202
+ raise ArgumentError unless option.respond_to?(:each)
1203
+ option.each { |k,v| set(k, v) }
1204
+ return self
1205
+ end
1206
+
1207
+ if respond_to?("#{option}=") and not ignore_setter
1208
+ return __send__("#{option}=", value)
1209
+ end
1210
+
1211
+ setter = proc { |val| set option, val, true }
1212
+ getter = proc { value }
1213
+
1214
+ case value
1215
+ when Proc
1216
+ getter = value
1217
+ when Symbol, Fixnum, FalseClass, TrueClass, NilClass
1218
+ getter = value.inspect
1219
+ when Hash
1220
+ setter = proc do |val|
1221
+ val = value.merge val if Hash === val
1222
+ set option, val, true
1223
+ end
1224
+ end
1225
+
1226
+ define_singleton("#{option}=", setter) if setter
1227
+ define_singleton(option, getter) if getter
1228
+ define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?"
1229
+ self
1230
+ end
1231
+
1232
+ # Same as calling `set :option, true` for each of the given options.
1233
+ def enable(*opts)
1234
+ opts.each { |key| set(key, true) }
1235
+ end
1236
+
1237
+ # Same as calling `set :option, false` for each of the given options.
1238
+ def disable(*opts)
1239
+ opts.each { |key| set(key, false) }
1240
+ end
1241
+
1242
+ # Define a custom error handler. Optionally takes either an Exception
1243
+ # class, or an HTTP status code to specify which errors should be
1244
+ # handled.
1245
+ def error(*codes, &block)
1246
+ args = compile! "ERROR", //, block
1247
+ codes = codes.map { |c| Array(c) }.flatten
1248
+ codes << Exception if codes.empty?
1249
+ codes.each { |c| (@errors[c] ||= []) << args }
1250
+ end
1251
+
1252
+ # Sugar for `error(404) { ... }`
1253
+ def not_found(&block)
1254
+ error(404, &block)
1255
+ error(Sinatra::NotFound, &block)
1256
+ end
1257
+
1258
+ # Define a named template. The block must return the template source.
1259
+ def template(name, &block)
1260
+ filename, line = caller_locations.first
1261
+ templates[name] = [block, filename, line.to_i]
1262
+ end
1263
+
1264
+ # Define the layout template. The block must return the template source.
1265
+ def layout(name = :layout, &block)
1266
+ template name, &block
1267
+ end
1268
+
1269
+ # Load embedded templates from the file; uses the caller's __FILE__
1270
+ # when no file is specified.
1271
+ def inline_templates=(file = nil)
1272
+ file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file
1273
+
1274
+ begin
1275
+ io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
1276
+ app, data = io.gsub("\r\n", "\n").split(/^__END__$/, 2)
1277
+ rescue Errno::ENOENT
1278
+ app, data = nil
1279
+ end
1280
+
1281
+ if data
1282
+ if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
1283
+ encoding = $2
1284
+ else
1285
+ encoding = settings.default_encoding
1286
+ end
1287
+ lines = app.count("\n") + 1
1288
+ template = nil
1289
+ force_encoding data, encoding
1290
+ data.each_line do |line|
1291
+ lines += 1
1292
+ if line =~ /^@@\s*(.*\S)\s*$/
1293
+ template = force_encoding('', encoding)
1294
+ templates[$1.to_sym] = [template, file, lines]
1295
+ elsif template
1296
+ template << line
1297
+ end
1298
+ end
1299
+ end
1300
+ end
1301
+
1302
+ # Lookup or register a mime type in Rack's mime registry.
1303
+ def mime_type(type, value = nil)
1304
+ return type if type.nil?
1305
+ return type.to_s if type.to_s.include?('/')
1306
+ type = ".#{type}" unless type.to_s[0] == ?.
1307
+ return Rack::Mime.mime_type(type, nil) unless value
1308
+ Rack::Mime::MIME_TYPES[type] = value
1309
+ end
1310
+
1311
+ # provides all mime types matching type, including deprecated types:
1312
+ # mime_types :html # => ['text/html']
1313
+ # mime_types :js # => ['application/javascript', 'text/javascript']
1314
+ def mime_types(type)
1315
+ type = mime_type type
1316
+ type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type]
1317
+ end
1318
+
1319
+ # Define a before filter; runs before all requests within the same
1320
+ # context as route handlers and may access/modify the request and
1321
+ # response.
1322
+ def before(path = nil, options = {}, &block)
1323
+ add_filter(:before, path, options, &block)
1324
+ end
1325
+
1326
+ # Define an after filter; runs after all requests within the same
1327
+ # context as route handlers and may access/modify the request and
1328
+ # response.
1329
+ def after(path = nil, options = {}, &block)
1330
+ add_filter(:after, path, options, &block)
1331
+ end
1332
+
1333
+ # add a filter
1334
+ def add_filter(type, path = nil, options = {}, &block)
1335
+ path, options = //, path if path.respond_to?(:each_pair)
1336
+ filters[type] << compile!(type, path || //, block, options)
1337
+ end
1338
+
1339
+ # Add a route condition. The route is considered non-matching when the
1340
+ # block returns false.
1341
+ def condition(name = "#{caller.first[/`.*'/]} condition", &block)
1342
+ @conditions << generate_method(name, &block)
1343
+ end
1344
+
1345
+ def public=(value)
1346
+ warn ":public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead"
1347
+ set(:public_folder, value)
1348
+ end
1349
+
1350
+ def public_dir=(value)
1351
+ self.public_folder = value
1352
+ end
1353
+
1354
+ def public_dir
1355
+ public_folder
1356
+ end
1357
+
1358
+ # Defining a `GET` handler also automatically defines
1359
+ # a `HEAD` handler.
1360
+ def get(path, opts = {}, &block)
1361
+ conditions = @conditions.dup
1362
+ route('GET', path, opts, &block)
1363
+
1364
+ @conditions = conditions
1365
+ route('HEAD', path, opts, &block)
1366
+ end
1367
+
1368
+ def put(path, opts = {}, &bk) route 'PUT', path, opts, &bk end
1369
+ def post(path, opts = {}, &bk) route 'POST', path, opts, &bk end
1370
+ def delete(path, opts = {}, &bk) route 'DELETE', path, opts, &bk end
1371
+ def head(path, opts = {}, &bk) route 'HEAD', path, opts, &bk end
1372
+ def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end
1373
+ def patch(path, opts = {}, &bk) route 'PATCH', path, opts, &bk end
1374
+ def link(path, opts = {}, &bk) route 'LINK', path, opts, &bk end
1375
+ def unlink(path, opts = {}, &bk) route 'UNLINK', path, opts, &bk end
1376
+
1377
+ # Makes the methods defined in the block and in the Modules given
1378
+ # in `extensions` available to the handlers and templates
1379
+ def helpers(*extensions, &block)
1380
+ class_eval(&block) if block_given?
1381
+ include(*extensions) if extensions.any?
1382
+ end
1383
+
1384
+ # Register an extension. Alternatively take a block from which an
1385
+ # extension will be created and registered on the fly.
1386
+ def register(*extensions, &block)
1387
+ extensions << Module.new(&block) if block_given?
1388
+ @extensions += extensions
1389
+ extensions.each do |extension|
1390
+ extend extension
1391
+ extension.registered(self) if extension.respond_to?(:registered)
1392
+ end
1393
+ end
1394
+
1395
+ def development?; environment == :development end
1396
+ def production?; environment == :production end
1397
+ def test?; environment == :test end
1398
+
1399
+ # Set configuration options for Sinatra and/or the app.
1400
+ # Allows scoping of settings for certain environments.
1401
+ def configure(*envs)
1402
+ yield self if envs.empty? || envs.include?(environment.to_sym)
1403
+ end
1404
+
1405
+ # Use the specified Rack middleware
1406
+ def use(middleware, *args, &block)
1407
+ @prototype = nil
1408
+ @middleware << [middleware, args, block]
1409
+ end
1410
+
1411
+ # Stop the self-hosted server if running.
1412
+ def quit!
1413
+ return unless running?
1414
+ # Use Thin's hard #stop! if available, otherwise just #stop.
1415
+ running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
1416
+ $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
1417
+ set :running_server, nil
1418
+ set :handler_name, nil
1419
+ end
1420
+
1421
+ alias_method :stop!, :quit!
1422
+
1423
+ # Run the Sinatra app as a self-hosted server using
1424
+ # Thin, Puma, Mongrel, or WEBrick (in that order). If given a block, will call
1425
+ # with the constructed handler once we have taken the stage.
1426
+ def run!(options = {}, &block)
1427
+ return if running?
1428
+ set options
1429
+ handler = detect_rack_handler
1430
+ handler_name = handler.name.gsub(/.*::/, '')
1431
+ server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
1432
+ server_settings.merge!(:Port => port, :Host => bind)
1433
+
1434
+ begin
1435
+ start_server(handler, server_settings, handler_name, &block)
1436
+ rescue Errno::EADDRINUSE
1437
+ $stderr.puts "== Someone is already performing on port #{port}!"
1438
+ raise
1439
+ ensure
1440
+ quit!
1441
+ end
1442
+ end
1443
+
1444
+ alias_method :start!, :run!
1445
+
1446
+ # Check whether the self-hosted server is running or not.
1447
+ def running?
1448
+ running_server?
1449
+ end
1450
+
1451
+ # The prototype instance used to process requests.
1452
+ def prototype
1453
+ @prototype ||= new
1454
+ end
1455
+
1456
+ # Create a new instance without middleware in front of it.
1457
+ alias new! new unless method_defined? :new!
1458
+
1459
+ # Create a new instance of the class fronted by its middleware
1460
+ # pipeline. The object is guaranteed to respond to #call but may not be
1461
+ # an instance of the class new was called on.
1462
+ def new(*args, &bk)
1463
+ instance = new!(*args, &bk)
1464
+ Wrapper.new(build(instance).to_app, instance)
1465
+ end
1466
+
1467
+ # Creates a Rack::Builder instance with all the middleware set up and
1468
+ # the given +app+ as end point.
1469
+ def build(app)
1470
+ builder = Rack::Builder.new
1471
+ setup_default_middleware builder
1472
+ setup_middleware builder
1473
+ builder.run app
1474
+ builder
1475
+ end
1476
+
1477
+ def call(env)
1478
+ synchronize { prototype.call(env) }
1479
+ end
1480
+
1481
+ # Like Kernel#caller but excluding certain magic entries and without
1482
+ # line / method information; the resulting array contains filenames only.
1483
+ def caller_files
1484
+ cleaned_caller(1).flatten
1485
+ end
1486
+
1487
+ # Like caller_files, but containing Arrays rather than strings with the
1488
+ # first element being the file, and the second being the line.
1489
+ def caller_locations
1490
+ cleaned_caller 2
1491
+ end
1492
+
1493
+ private
1494
+
1495
+ # Starts the server by running the Rack Handler.
1496
+ def start_server(handler, server_settings, handler_name)
1497
+ handler.run(self, server_settings) do |server|
1498
+ unless handler_name =~ /cgi/i
1499
+ $stderr.puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
1500
+ "on #{port} for #{environment} with backup from #{handler_name}"
1501
+ end
1502
+
1503
+ setup_traps
1504
+ set :running_server, server
1505
+ set :handler_name, handler_name
1506
+ server.threaded = settings.threaded if server.respond_to? :threaded=
1507
+
1508
+ yield server if block_given?
1509
+ end
1510
+ end
1511
+
1512
+ def setup_traps
1513
+ if traps?
1514
+ at_exit { quit! }
1515
+
1516
+ [:INT, :TERM].each do |signal|
1517
+ old_handler = trap(signal) do
1518
+ quit!
1519
+ old_handler.call if old_handler.respond_to?(:call)
1520
+ end
1521
+ end
1522
+
1523
+ set :traps, false
1524
+ end
1525
+ end
1526
+
1527
+ # Dynamically defines a method on settings.
1528
+ def define_singleton(name, content = Proc.new)
1529
+ # replace with call to singleton_class once we're 1.9 only
1530
+ (class << self; self; end).class_eval do
1531
+ undef_method(name) if method_defined? name
1532
+ String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content)
1533
+ end
1534
+ end
1535
+
1536
+ # Condition for matching host name. Parameter might be String or Regexp.
1537
+ def host_name(pattern)
1538
+ condition { pattern === request.host }
1539
+ end
1540
+
1541
+ # Condition for matching user agent. Parameter should be Regexp.
1542
+ # Will set params[:agent].
1543
+ def user_agent(pattern)
1544
+ condition do
1545
+ if request.user_agent.to_s =~ pattern
1546
+ @params[:agent] = $~[1..-1]
1547
+ true
1548
+ else
1549
+ false
1550
+ end
1551
+ end
1552
+ end
1553
+ alias_method :agent, :user_agent
1554
+
1555
+ # Condition for matching mimetypes. Accepts file extensions.
1556
+ def provides(*types)
1557
+ types.map! { |t| mime_types(t) }
1558
+ types.flatten!
1559
+ condition do
1560
+ if type = response['Content-Type']
1561
+ types.include? type or types.include? type[/^[^;]+/]
1562
+ elsif type = request.preferred_type(types)
1563
+ params = (type.respond_to?(:params) ? type.params : {})
1564
+ content_type(type, params)
1565
+ true
1566
+ else
1567
+ false
1568
+ end
1569
+ end
1570
+ end
1571
+
1572
+ def route(verb, path, options = {}, &block)
1573
+ # Because of self.options.host
1574
+ host_name(options.delete(:host)) if options.key?(:host)
1575
+ enable :empty_path_info if path == "" and empty_path_info.nil?
1576
+ signature = compile!(verb, path, block, options)
1577
+ (@routes[verb] ||= []) << signature
1578
+ invoke_hook(:route_added, verb, path, block)
1579
+ signature
1580
+ end
1581
+
1582
+ def invoke_hook(name, *args)
1583
+ extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
1584
+ end
1585
+
1586
+ def generate_method(method_name, &block)
1587
+ method_name = method_name.to_sym
1588
+ define_method(method_name, &block)
1589
+ method = instance_method method_name
1590
+ remove_method method_name
1591
+ method
1592
+ end
1593
+
1594
+ def compile!(verb, path, block, options = {})
1595
+ options.each_pair { |option, args| send(option, *args) }
1596
+ method_name = "#{verb} #{path}"
1597
+ unbound_method = generate_method(method_name, &block)
1598
+ pattern, keys = compile path
1599
+ conditions, @conditions = @conditions, []
1600
+
1601
+ wrapper = block.arity != 0 ?
1602
+ proc { |a,p| unbound_method.bind(a).call(*p) } :
1603
+ proc { |a,p| unbound_method.bind(a).call }
1604
+ wrapper.instance_variable_set(:@route_name, method_name)
1605
+
1606
+ [ pattern, keys, conditions, wrapper ]
1607
+ end
1608
+
1609
+ def compile(path)
1610
+ if path.respond_to? :to_str
1611
+ keys = []
1612
+
1613
+ # We append a / at the end if there was one.
1614
+ # Reason: Splitting does not split off an empty
1615
+ # string at the end if the split separator
1616
+ # is at the end.
1617
+ #
1618
+ postfix = '/' if path =~ /\/\z/
1619
+
1620
+ # Split the path into pieces in between forward slashes.
1621
+ #
1622
+ segments = path.split('/').map! do |segment|
1623
+ ignore = []
1624
+
1625
+ # Special character handling.
1626
+ #
1627
+ pattern = segment.to_str.gsub(/[^\?\%\\\/\:\*\w]/) do |c|
1628
+ ignore << escaped(c).join if c.match(/[\.@]/)
1629
+ patt = encoded(c)
1630
+ patt.gsub(/%[\da-fA-F]{2}/) do |match|
1631
+ match.split(//).map! {|char| char =~ /[A-Z]/ ? "[#{char}#{char.tr('A-Z', 'a-z')}]" : char}.join
1632
+ end
1633
+ end
1634
+
1635
+ ignore = ignore.uniq.join
1636
+
1637
+ # Key handling.
1638
+ #
1639
+ pattern.gsub(/((:\w+)|\*)/) do |match|
1640
+ if match == "*"
1641
+ keys << 'splat'
1642
+ "(.*?)"
1643
+ else
1644
+ keys << $2[1..-1]
1645
+ ignore_pattern = safe_ignore(ignore)
1646
+
1647
+ ignore_pattern
1648
+ end
1649
+ end
1650
+ end
1651
+
1652
+ # Special case handling.
1653
+ #
1654
+ if segment = segments.pop
1655
+ if segment.match(/\[\^\\\./)
1656
+ parts = segment.rpartition(/\[\^\\\./)
1657
+ parts[1] = '[^'
1658
+ segments << parts.join
1659
+ else
1660
+ segments << segment
1661
+ end
1662
+ end
1663
+ [/\A#{segments.join('/')}#{postfix}\z/, keys]
1664
+ elsif path.respond_to?(:keys) && path.respond_to?(:match)
1665
+ [path, path.keys]
1666
+ elsif path.respond_to?(:names) && path.respond_to?(:match)
1667
+ [path, path.names]
1668
+ elsif path.respond_to? :match
1669
+ [path, []]
1670
+ else
1671
+ raise TypeError, path
1672
+ end
1673
+ end
1674
+
1675
+ def encoded(char)
1676
+ enc = URI_INSTANCE.escape(char)
1677
+ enc = "(?:#{escaped(char, enc).join('|')})" if enc == char
1678
+ enc = "(?:#{enc}|#{encoded('+')})" if char == " "
1679
+ enc
1680
+ end
1681
+
1682
+ def escaped(char, enc = URI_INSTANCE.escape(char))
1683
+ [Regexp.escape(enc), URI_INSTANCE.escape(char, /./)]
1684
+ end
1685
+
1686
+ def safe_ignore(ignore)
1687
+ unsafe_ignore = []
1688
+ ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex|
1689
+ unsafe_ignore << hex[1..2]
1690
+ ''
1691
+ end
1692
+ unsafe_patterns = unsafe_ignore.map! do |unsafe|
1693
+ chars = unsafe.split(//).map! do |char|
1694
+ if char =~ /[A-Z]/
1695
+ char <<= char.tr('A-Z', 'a-z')
1696
+ end
1697
+ char
1698
+ end
1699
+
1700
+ "|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])"
1701
+ end
1702
+ if unsafe_patterns.length > 0
1703
+ "((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)"
1704
+ else
1705
+ "([^#{ignore}/?#]+)"
1706
+ end
1707
+ end
1708
+
1709
+ def setup_default_middleware(builder)
1710
+ builder.use ExtendedRack
1711
+ builder.use ShowExceptions if show_exceptions?
1712
+ builder.use Rack::MethodOverride if method_override?
1713
+ builder.use Rack::Head
1714
+ setup_logging builder
1715
+ setup_sessions builder
1716
+ setup_protection builder
1717
+ end
1718
+
1719
+ def setup_middleware(builder)
1720
+ middleware.each { |c,a,b| builder.use(c, *a, &b) }
1721
+ end
1722
+
1723
+ def setup_logging(builder)
1724
+ if logging?
1725
+ setup_common_logger(builder)
1726
+ setup_custom_logger(builder)
1727
+ elsif logging == false
1728
+ setup_null_logger(builder)
1729
+ end
1730
+ end
1731
+
1732
+ def setup_null_logger(builder)
1733
+ builder.use Rack::NullLogger
1734
+ end
1735
+
1736
+ def setup_common_logger(builder)
1737
+ builder.use Sinatra::CommonLogger
1738
+ end
1739
+
1740
+ def setup_custom_logger(builder)
1741
+ if logging.respond_to? :to_int
1742
+ builder.use Rack::Logger, logging
1743
+ else
1744
+ builder.use Rack::Logger
1745
+ end
1746
+ end
1747
+
1748
+ def setup_protection(builder)
1749
+ return unless protection?
1750
+ options = Hash === protection ? protection.dup : {}
1751
+ protect_session = options.fetch(:session) { sessions? }
1752
+ options[:except] = Array options[:except]
1753
+ options[:except] += [:session_hijacking, :remote_token] unless protect_session
1754
+ options[:reaction] ||= :drop_session
1755
+ builder.use Rack::Protection, options
1756
+ end
1757
+
1758
+ def setup_sessions(builder)
1759
+ return unless sessions?
1760
+ options = {}
1761
+ options[:secret] = session_secret if session_secret?
1762
+ options.merge! sessions.to_hash if sessions.respond_to? :to_hash
1763
+ builder.use Rack::Session::Cookie, options
1764
+ end
1765
+
1766
+ def detect_rack_handler
1767
+ servers = Array(server)
1768
+ servers.each do |server_name|
1769
+ begin
1770
+ return Rack::Handler.get(server_name.to_s)
1771
+ rescue LoadError, NameError
1772
+ end
1773
+ end
1774
+ fail "Server handler (#{servers.join(',')}) not found."
1775
+ end
1776
+
1777
+ def inherited(subclass)
1778
+ subclass.reset!
1779
+ subclass.set :app_file, caller_files.first unless subclass.app_file?
1780
+ super
1781
+ end
1782
+
1783
+ @@mutex = Mutex.new
1784
+ def synchronize(&block)
1785
+ if lock?
1786
+ @@mutex.synchronize(&block)
1787
+ else
1788
+ yield
1789
+ end
1790
+ end
1791
+
1792
+ # used for deprecation warnings
1793
+ def warn(message)
1794
+ super message + "\n\tfrom #{cleaned_caller.first.join(':')}"
1795
+ end
1796
+
1797
+ # Like Kernel#caller but excluding certain magic entries
1798
+ def cleaned_caller(keep = 3)
1799
+ caller(1).
1800
+ map! { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }.
1801
+ reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
1802
+ end
1803
+ end
1804
+
1805
+ # Fixes encoding issues by
1806
+ # * defaulting to UTF-8
1807
+ # * casting params to Encoding.default_external
1808
+ #
1809
+ # The latter might not be necessary if Rack handles it one day.
1810
+ # Keep an eye on Rack's LH #100.
1811
+ def force_encoding(*args) settings.force_encoding(*args) end
1812
+ if defined? Encoding
1813
+ def self.force_encoding(data, encoding = default_encoding)
1814
+ return if data == settings || data.is_a?(Tempfile)
1815
+ if data.respond_to? :force_encoding
1816
+ data.force_encoding(encoding).encode!
1817
+ elsif data.respond_to? :each_value
1818
+ data.each_value { |v| force_encoding(v, encoding) }
1819
+ elsif data.respond_to? :each
1820
+ data.each { |v| force_encoding(v, encoding) }
1821
+ end
1822
+ data
1823
+ end
1824
+ else
1825
+ def self.force_encoding(data, *) data end
1826
+ end
1827
+
1828
+ reset!
1829
+
1830
+ set :environment, (ENV['RACK_ENV'] || :development).to_sym
1831
+ set :raise_errors, Proc.new { test? }
1832
+ set :dump_errors, Proc.new { !test? }
1833
+ set :show_exceptions, Proc.new { development? }
1834
+ set :sessions, false
1835
+ set :logging, false
1836
+ set :protection, true
1837
+ set :method_override, false
1838
+ set :use_code, false
1839
+ set :default_encoding, "utf-8"
1840
+ set :x_cascade, true
1841
+ set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" }
1842
+ settings.add_charset << /^text\//
1843
+
1844
+ # explicitly generating a session secret eagerly to play nice with preforking
1845
+ begin
1846
+ require 'securerandom'
1847
+ set :session_secret, SecureRandom.hex(64)
1848
+ rescue LoadError, NotImplementedError
1849
+ # SecureRandom raises a NotImplementedError if no random device is available
1850
+ set :session_secret, "%064x" % Kernel.rand(2**256-1)
1851
+ end
1852
+
1853
+ class << self
1854
+ alias_method :methodoverride?, :method_override?
1855
+ alias_method :methodoverride=, :method_override=
1856
+ end
1857
+
1858
+ set :run, false # start server via at-exit hook?
1859
+ set :running_server, nil
1860
+ set :handler_name, nil
1861
+ set :traps, true
1862
+ set :server, %w[HTTP webrick]
1863
+ set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' }
1864
+ set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
1865
+
1866
+ ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
1867
+
1868
+ if ruby_engine == 'macruby'
1869
+ server.unshift 'control_tower'
1870
+ else
1871
+ server.unshift 'reel'
1872
+ server.unshift 'mongrel' if ruby_engine.nil?
1873
+ server.unshift 'puma' if ruby_engine != 'rbx'
1874
+ server.unshift 'thin' if ruby_engine != 'jruby'
1875
+ server.unshift 'puma' if ruby_engine == 'rbx'
1876
+ server.unshift 'trinidad' if ruby_engine == 'jruby'
1877
+ end
1878
+
1879
+ set :absolute_redirects, true
1880
+ set :prefixed_redirects, false
1881
+ set :empty_path_info, nil
1882
+
1883
+ set :app_file, nil
1884
+ set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
1885
+ set :views, Proc.new { root && File.join(root, 'views') }
1886
+ set :reload_templates, Proc.new { development? }
1887
+ set :lock, false
1888
+ set :threaded, true
1889
+
1890
+ set :public_folder, Proc.new { root && File.join(root, 'public') }
1891
+ set :static, Proc.new { public_folder && File.exist?(public_folder) }
1892
+ set :static_cache_control, false
1893
+
1894
+ error ::Exception do
1895
+ response.status = 500
1896
+ content_type 'text/html'
1897
+ '<h1>Internal Server Error</h1>'
1898
+ end
1899
+
1900
+ configure :development do
1901
+ get '/__sinatra__/:image.png' do
1902
+ filename = File.dirname(__FILE__) + "/images/#{params[:image]}.png"
1903
+ content_type :png
1904
+ send_file filename
1905
+ end
1906
+
1907
+ error NotFound do
1908
+ content_type 'text/html'
1909
+
1910
+ if self.class == Sinatra::Application
1911
+ code = <<-RUBY.gsub(/^ {12}/, '')
1912
+ #{request.request_method.downcase} '#{request.path_info}' do
1913
+ "Hello World"
1914
+ end
1915
+ RUBY
1916
+ else
1917
+ code = <<-RUBY.gsub(/^ {12}/, '')
1918
+ class #{self.class}
1919
+ #{request.request_method.downcase} '#{request.path_info}' do
1920
+ "Hello World"
1921
+ end
1922
+ end
1923
+ RUBY
1924
+
1925
+ file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(/^\//, '')
1926
+ code = "# in #{file}\n#{code}" unless file.empty?
1927
+ end
1928
+
1929
+ (<<-HTML).gsub(/^ {10}/, '')
1930
+ <!DOCTYPE html>
1931
+ <html>
1932
+ <head>
1933
+ <style type="text/css">
1934
+ body { text-align:center;font-family:helvetica,arial;font-size:22px;
1935
+ color:#888;margin:20px}
1936
+ #c {margin:0 auto;width:500px;text-align:left}
1937
+ </style>
1938
+ </head>
1939
+ <body>
1940
+ <h2>Sinatra doesn&rsquo;t know this ditty.</h2>
1941
+ <img src='#{uri "/__sinatra__/404.png"}'>
1942
+ <div id="c">
1943
+ Try this:
1944
+ <pre>#{code}</pre>
1945
+ </div>
1946
+ </body>
1947
+ </html>
1948
+ HTML
1949
+ end
1950
+ end
1951
+ end
1952
+
1953
+ # Execution context for classic style (top-level) applications. All
1954
+ # DSL methods executed on main are delegated to this class.
1955
+ #
1956
+ # The Application class should not be subclassed, unless you want to
1957
+ # inherit all settings, routes, handlers, and error pages from the
1958
+ # top-level. Subclassing Sinatra::Base is highly recommended for
1959
+ # modular applications.
1960
+ class Application < Base
1961
+ set :logging, Proc.new { ! test? }
1962
+ set :method_override, true
1963
+ set :run, Proc.new { ! test? }
1964
+ set :session_secret, Proc.new { super() unless development? }
1965
+ set :app_file, nil
1966
+
1967
+ def self.register(*extensions, &block) #:nodoc:
1968
+ added_methods = extensions.map {|m| m.public_instance_methods }.flatten
1969
+ Delegator.delegate(*added_methods)
1970
+ super(*extensions, &block)
1971
+ end
1972
+ end
1973
+
1974
+ # Sinatra delegation mixin. Mixing this module into an object causes all
1975
+ # methods to be delegated to the Sinatra::Application class. Used primarily
1976
+ # at the top-level.
1977
+ module Delegator #:nodoc:
1978
+ def self.delegate(*methods)
1979
+ methods.each do |method_name|
1980
+ define_method(method_name) do |*args, &block|
1981
+ return super(*args, &block) if respond_to? method_name
1982
+ Delegator.target.send(method_name, *args, &block)
1983
+ end
1984
+ private method_name
1985
+ end
1986
+ end
1987
+
1988
+ delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink,
1989
+ :template, :layout, :before, :after, :error, :not_found, :configure,
1990
+ :set, :mime_type, :enable, :disable, :use, :development?, :test?,
1991
+ :production?, :helpers, :settings, :register
1992
+
1993
+ class << self
1994
+ attr_accessor :target
1995
+ end
1996
+
1997
+ self.target = Application
1998
+ end
1999
+
2000
+ class Wrapper
2001
+ def initialize(stack, instance)
2002
+ @stack, @instance = stack, instance
2003
+ end
2004
+
2005
+ def settings
2006
+ @instance.settings
2007
+ end
2008
+
2009
+ def helpers
2010
+ @instance
2011
+ end
2012
+
2013
+ def call(env)
2014
+ @stack.call(env)
2015
+ end
2016
+
2017
+ def inspect
2018
+ "#<#{@instance.class} app_file=#{settings.app_file.inspect}>"
2019
+ end
2020
+ end
2021
+
2022
+ # Create a new Sinatra application. The block is evaluated in the new app's
2023
+ # class scope.
2024
+ def self.new(base = Base, &block)
2025
+ base = Class.new(base)
2026
+ base.class_eval(&block) if block_given?
2027
+ base
2028
+ end
2029
+
2030
+ # Extend the top-level DSL with the modules provided.
2031
+ def self.register(*extensions, &block)
2032
+ Delegator.target.register(*extensions, &block)
2033
+ end
2034
+
2035
+ # Include the helper modules provided in Sinatra's request context.
2036
+ def self.helpers(*extensions, &block)
2037
+ Delegator.target.helpers(*extensions, &block)
2038
+ end
2039
+
2040
+ # Use the middleware for classic applications.
2041
+ def self.use(*args, &block)
2042
+ Delegator.target.use(*args, &block)
2043
+ end
2044
+ end