devcenter 0.0.1

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