devcenter 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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