roger 0.0.1 → 0.10.0

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 (89) hide show
  1. checksums.yaml +8 -8
  2. data/.gitignore +2 -0
  3. data/.travis.yml +12 -0
  4. data/CHANGELOG.md +102 -0
  5. data/Gemfile +5 -0
  6. data/MIT_LICENSE +20 -0
  7. data/README.md +10 -10
  8. data/Rakefile +9 -0
  9. data/bin/roger +5 -0
  10. data/doc/cli.md +46 -0
  11. data/doc/mockupfile.md +3 -0
  12. data/doc/templating.md +88 -0
  13. data/examples/default_template/.gitignore +2 -0
  14. data/examples/default_template/CHANGELOG +0 -0
  15. data/examples/default_template/Gemfile +3 -0
  16. data/examples/default_template/Mockupfile +1 -0
  17. data/examples/default_template/html/.empty_directory +0 -0
  18. data/examples/default_template/partials/.empty_directory +0 -0
  19. data/lib/roger/cli/command.rb +23 -0
  20. data/lib/roger/cli/generate.rb +5 -0
  21. data/lib/roger/cli/release.rb +10 -0
  22. data/lib/roger/cli/serve.rb +29 -0
  23. data/lib/roger/cli.rb +123 -0
  24. data/lib/roger/extractor.rb +95 -0
  25. data/lib/roger/generators/generator.rb +23 -0
  26. data/lib/roger/generators/new.rb +67 -0
  27. data/lib/roger/generators/templates/generator.tt +13 -0
  28. data/lib/roger/generators.rb +23 -0
  29. data/lib/roger/mockupfile.rb +63 -0
  30. data/lib/roger/project.rb +92 -0
  31. data/lib/roger/rack/html_validator.rb +26 -0
  32. data/lib/roger/rack/roger.rb +52 -0
  33. data/lib/roger/rack/sleep.rb +21 -0
  34. data/lib/roger/release/cleaner.rb +47 -0
  35. data/lib/roger/release/finalizers/dir.rb +29 -0
  36. data/lib/roger/release/finalizers/git_branch.rb +92 -0
  37. data/lib/roger/release/finalizers/rsync.rb +77 -0
  38. data/lib/roger/release/finalizers/zip.rb +42 -0
  39. data/lib/roger/release/finalizers.rb +19 -0
  40. data/lib/roger/release/injector.rb +99 -0
  41. data/lib/roger/release/processors/mockup.rb +93 -0
  42. data/lib/roger/release/processors/url_relativizer.rb +45 -0
  43. data/lib/roger/release/processors.rb +17 -0
  44. data/lib/roger/release/scm/git.rb +101 -0
  45. data/lib/roger/release/scm.rb +32 -0
  46. data/lib/roger/release.rb +363 -0
  47. data/lib/roger/resolver.rb +119 -0
  48. data/lib/roger/server.rb +117 -0
  49. data/lib/roger/template.rb +206 -0
  50. data/lib/roger/w3c_validator.rb +129 -0
  51. data/roger.gemspec +35 -0
  52. data/test/Mockupfile-syntax.rb +85 -0
  53. data/test/project/.rvmrc +1 -0
  54. data/test/project/Gemfile +7 -0
  55. data/test/project/Gemfile.lock +38 -0
  56. data/test/project/Mockupfile +13 -0
  57. data/test/project/html/formats/erb.html.erb +5 -0
  58. data/test/project/html/formats/index.html +1 -0
  59. data/test/project/html/formats/json.json.erb +0 -0
  60. data/test/project/html/formats/markdown.md +3 -0
  61. data/test/project/html/formats/mockup.html +5 -0
  62. data/test/project/html/front_matter/erb.html.erb +16 -0
  63. data/test/project/html/front_matter/markdown.md +7 -0
  64. data/test/project/html/layouts/content-for.html.erb +17 -0
  65. data/test/project/html/layouts/erb.html.erb +19 -0
  66. data/test/project/html/mockup/encoding.html +3 -0
  67. data/test/project/html/partials/erb.html.erb +10 -0
  68. data/test/project/html/partials/load_path.html.erb +3 -0
  69. data/test/project/html/partials/mockup.html +13 -0
  70. data/test/project/html/static/non-relative.html.erb +9 -0
  71. data/test/project/html/static/relative.html.erb +9 -0
  72. data/test/project/layouts/test.html.erb +34 -0
  73. data/test/project/layouts/yield.html.erb +1 -0
  74. data/test/project/lib/generators/test.rb +9 -0
  75. data/test/project/partials/formats/erb.html.erb +1 -0
  76. data/test/project/partials/partials-test.html.erb +1 -0
  77. data/test/project/partials/test/erb.html.erb +1 -0
  78. data/test/project/partials/test/front_matter.html.erb +1 -0
  79. data/test/project/partials/test/json.json.erb +1 -0
  80. data/test/project/partials/test/markdown.md +1 -0
  81. data/test/project/partials/test/mockup.part.html +1 -0
  82. data/test/project/partials/test/simple.html.erb +1 -0
  83. data/test/project/partials2/partials2-test.html.erb +1 -0
  84. data/test/unit/cli_test.rb +12 -0
  85. data/test/unit/generators_test.rb +75 -0
  86. data/test/unit/release/cleaner_test.rb +47 -0
  87. data/test/unit/resolver_test.rb +92 -0
  88. data/test/unit/template_test.rb +127 -0
  89. metadata +202 -8
@@ -0,0 +1,363 @@
1
+ require File.dirname(__FILE__) + "/cli"
2
+
3
+ module Roger
4
+ class Release
5
+
6
+ attr_reader :config, :project
7
+
8
+ attr_reader :finalizers, :injections, :stack, :cleanups
9
+
10
+ class << self
11
+
12
+ def default_stack
13
+ []
14
+ end
15
+
16
+ def default_finalizers
17
+ [[self.get_callable(:dir, Roger::Release::Finalizers), {}]]
18
+ end
19
+
20
+ # Makes callable into a object that responds to call.
21
+ #
22
+ # @param [#call, Symbol, Class] callable If callable already responds to #call will just return callable, a Symbol will be searched for in the scope parameter, a class will be instantiated (and checked if it will respond to #call)
23
+ # @param [Module] scope The scope in which to search callable in if it's a Symbol
24
+ def get_callable(callable, scope)
25
+ return callable if callable.respond_to?(:call)
26
+
27
+ if callable.kind_of?(Symbol)
28
+ callable = camel_case(callable.to_s).to_sym
29
+ if scope.constants.include?(callable)
30
+ c = scope.const_get(callable)
31
+ callable = c if c.is_a?(Class)
32
+ end
33
+ end
34
+
35
+ if callable.kind_of?(Class)
36
+ callable = callable.new
37
+ end
38
+
39
+ if callable.respond_to?(:call)
40
+ callable
41
+ else
42
+ raise ArgumentError, "Could not resolve #{callable.inspect}. Callable must be an object that responds to #call or a symbol that resolve to such an object or a class with a #call instance method."
43
+ end
44
+
45
+ end
46
+
47
+ # Nothing genius adjusted from:
48
+ # http://stackoverflow.com/questions/9524457/converting-string-from-snake-case-to-camel-case-in-ruby
49
+ def camel_case(string)
50
+ return string if string !~ /_/ && string =~ /[A-Z]+.*/
51
+ string.split('_').map{|e| e.capitalize}.join
52
+ end
53
+
54
+ end
55
+
56
+ # @option config [Symbol] :scm The SCM to use (default = :git)
57
+ # @option config [String, Pathname] :target_path The path/directory to put the release into
58
+ # @option config [String, Pathname]:build_path Temporary path used to build the release
59
+ # @option config [Boolean] :cleanup_build Wether or not to remove the build_path after we're done (default = true)
60
+ def initialize(project, config = {})
61
+ defaults = {
62
+ :scm => :git,
63
+ :source_path => Pathname.new(Dir.pwd) + "html",
64
+ :target_path => Pathname.new(Dir.pwd) + "releases",
65
+ :build_path => Pathname.new(Dir.pwd) + "build",
66
+ :cleanup_build => true
67
+ }
68
+
69
+ @config = {}.update(defaults).update(config)
70
+ @project = project
71
+ @stack = []
72
+ @finalizers = []
73
+ end
74
+
75
+ # Accessor for target_path
76
+ # The target_path is the path where the finalizers will put the release
77
+ #
78
+ # @return Pathname the target_path
79
+ def target_path
80
+ Pathname.new(self.config[:target_path])
81
+ end
82
+
83
+ # Accessor for build_path
84
+ # The build_path is a temporary directory where the release will be built
85
+ #
86
+ # @return Pathname the build_path
87
+ def build_path
88
+ Pathname.new(self.config[:build_path])
89
+ end
90
+
91
+ # Accessor for source_path
92
+ # The source path is the root of the mockup
93
+ #
94
+ # @return Pathanem the source_path
95
+ def source_path
96
+ Pathname.new(self.config[:source_path])
97
+ end
98
+
99
+ # Get the current SCM object
100
+ def scm(force = false)
101
+ return @_scm if @_scm && !force
102
+
103
+ case self.config[:scm]
104
+ when :git
105
+ @_scm = Release::Scm::Git.new(:path => self.source_path)
106
+ else
107
+ raise "Unknown SCM #{options[:scm].inspect}"
108
+ end
109
+ end
110
+
111
+ # Inject variables into files with an optional filter
112
+ #
113
+ # @examples
114
+ # release.inject({"VERSION" => release.version, "DATE" => release.date}, :into => %w{_doc/toc.html})
115
+ # release.inject({"CHANGELOG" => {:file => "", :filter => BlueCloth}}, :into => %w{_doc/changelog.html})
116
+ def inject(variables, options)
117
+ @stack << Injector.new(variables, options)
118
+ end
119
+
120
+ # Use a certain pre-processor
121
+ #
122
+ # @examples
123
+ # release.use :sprockets, sprockets_config
124
+ def use(processor, options = {})
125
+ @stack << [self.class.get_callable(processor, Roger::Release::Processors), options]
126
+ end
127
+
128
+ # Write out the whole release into a directory, zip file or anything you can imagine
129
+ # #finalize can be called multiple times, it just will run all of them.
130
+ #
131
+ # The default finalizer is :dir
132
+ #
133
+ # @param [Symbol, Proc] Finalizer to use
134
+ #
135
+ # @examples
136
+ # release.finalize :zip
137
+ def finalize(finalizer, options = {})
138
+ @finalizers << [self.class.get_callable(finalizer, Roger::Release::Finalizers), options]
139
+ end
140
+
141
+ # Files to clean up in the build directory just before finalization happens
142
+ #
143
+ # @param [String] Pattern to glob within build directory
144
+ #
145
+ # @examples
146
+ # release.cleanup "**/.DS_Store"
147
+ def cleanup(pattern)
148
+ @stack << Cleaner.new(pattern)
149
+ end
150
+
151
+ # Generates a banner if a block is given, or returns the currently set banner.
152
+ # It automatically takes care of adding comment marks around the banner.
153
+ #
154
+ # The default banner looks like this:
155
+ #
156
+ # =======================
157
+ # = Version : v1.0.0 =
158
+ # = Date : 2012-06-20 =
159
+ # =======================
160
+ #
161
+ #
162
+ # @option options [:css,:js,:html,false] :comment Wether or not to comment the output and in what style. (default=js)
163
+ def banner(options = {}, &block)
164
+ options = {
165
+ :comment => :js
166
+ }.update(options)
167
+
168
+ if block_given?
169
+ @_banner = yield.to_s
170
+ elsif !@_banner
171
+ banner = []
172
+ banner << "Version : #{self.scm.version}"
173
+ banner << "Date : #{self.scm.date.strftime("%Y-%m-%d")}"
174
+
175
+ size = banner.inject(0){|mem,b| b.size > mem ? b.size : mem }
176
+ banner.map!{|b| "= #{b.ljust(size)} =" }
177
+ div = "=" * banner.first.size
178
+ banner.unshift(div)
179
+ banner << div
180
+ @_banner = banner.join("\n")
181
+ end
182
+
183
+ if options[:comment]
184
+ self.comment(@_banner, :style => options[:comment])
185
+ else
186
+ @_banner
187
+ end
188
+ end
189
+
190
+ # Extract the mockup, this will happen anyway, and will always happen first
191
+ # This method gives you a way to pass options to the extractor.
192
+ #
193
+ # @param Hash options Options hash passed to extractor
194
+ #
195
+ # @deprecated Don't use the extractor anymore, use release.use(:mockup, options) processor
196
+ def extract(options = {})
197
+ self.warn(self, "Don't use the extractor anymore, use release.use(:mockup, options) and release.use(:url_relativizer, options) processors")
198
+ @extractor_options = options
199
+ end
200
+
201
+ # Actually perform the release
202
+ def run!
203
+ # Validate paths
204
+ validate_paths!
205
+
206
+ # Extract mockup
207
+ copy_source_path_to_build_path!
208
+
209
+ validate_stack!
210
+
211
+ # Run stack
212
+ run_stack!
213
+
214
+ # Run finalizers
215
+ run_finalizers!
216
+
217
+ # Cleanup
218
+ cleanup! if self.config[:cleanup_build]
219
+
220
+ end
221
+
222
+ # Write out a log message
223
+ def log(part, msg, verbose = false, &block)
224
+ if !verbose || verbose && self.project.options[:verbose]
225
+ self.project.shell.say "\033[37m#{part.class.to_s}\033[0m" + " : " + msg.to_s, nil, true
226
+ end
227
+ if block_given?
228
+ begin
229
+ self.project.shell.padding = self.project.shell.padding + 1
230
+ yield
231
+ ensure
232
+ self.project.shell.padding = self.project.shell.padding - 1
233
+ end
234
+ end
235
+ end
236
+
237
+ def debug(part, msg, &block)
238
+ self.log(part, msg, true, &block)
239
+ end
240
+
241
+ # Write out a warning message
242
+ def warn(part, msg)
243
+ self.project.shell.say "\033[37m#{part.class.to_s}\033[0m" + " : " + "\033[31m#{msg.to_s}\033[0m", nil, true
244
+ end
245
+
246
+
247
+ # @param [Array] globs an array of file path globs that will be globbed against the build_path
248
+ # @param [Array] excludes an array of regexps that will be excluded from the result
249
+ def get_files(globs, excludes = [])
250
+ files = globs.map{|g| Dir.glob(self.build_path + g) }.flatten
251
+ if excludes.any?
252
+ files.reject{|c| excludes.detect{|e| e.match(c) } }
253
+ else
254
+ files
255
+ end
256
+ end
257
+
258
+ protected
259
+
260
+ # ==============
261
+ # = The runway =
262
+ # ==============
263
+
264
+ # Checks if build path exists (and cleans it up)
265
+ # Checks if target path exists (if not, creates it)
266
+ def validate_paths!
267
+ if self.build_path.exist?
268
+ log self, "Cleaning up previous build \"#{self.build_path}\""
269
+ rm_rf(self.build_path)
270
+ end
271
+
272
+ if !self.target_path.exist?
273
+ log self, "Creating target path \"#{self.target_path}\""
274
+ mkdir self.target_path
275
+ end
276
+ end
277
+
278
+ # Checks if deprecated extractor options have been set
279
+ # Checks if the mockup will be runned
280
+ def validate_stack!
281
+
282
+ mockup_options = {}
283
+ relativizer_options = {}
284
+ run_relativizer = true
285
+ if @extractor_options
286
+ mockup_options = {:env => @extractor_options[:env]}
287
+ relativizer_options = {:url_attributes => @extractor_options[:url_attributes]}
288
+ run_relativizer = @extractor_options[:url_relativize]
289
+ end
290
+
291
+ unless @stack.find{|(processor, options)| processor.class == Roger::Release::Processors::Mockup }
292
+ @stack.unshift([Roger::Release::Processors::UrlRelativizer.new, relativizer_options])
293
+ @stack.unshift([Roger::Release::Processors::Mockup.new, mockup_options])
294
+ end
295
+ end
296
+
297
+ def copy_source_path_to_build_path!
298
+ mkdir(self.build_path)
299
+ cp_r(self.source_path.children, self.build_path)
300
+ end
301
+
302
+ def run_stack!
303
+ @stack = self.class.default_stack.dup if @stack.empty?
304
+
305
+ # call all objects in @stack
306
+ @stack.each do |task|
307
+ if (task.kind_of?(Array))
308
+ task[0].call(self, task[1])
309
+ else
310
+ task.call(self)
311
+ end
312
+ end
313
+ end
314
+
315
+ def run_finalizers!
316
+ @finalizers = self.class.default_finalizers.dup if @finalizers.empty?
317
+
318
+ # call all objects in @finalizes
319
+ @finalizers.each do |finalizer|
320
+ finalizer[0].call(self, finalizer[1])
321
+ end
322
+
323
+ end
324
+
325
+ def cleanup!
326
+ log(self, "Cleaning up build path #{self.build_path}")
327
+ rm_rf(self.build_path)
328
+ end
329
+
330
+ # @param [String] string The string to comment
331
+ #
332
+ # @option options [:html, :css, :js] :style The comment style to use (default=:js, which is the same as :css)
333
+ # @option options [Boolean] :per_line Comment per line or make one block? (default=true)
334
+ def comment(string, options = {})
335
+ options = {
336
+ :style => :css,
337
+ :per_line => true
338
+ }.update(options)
339
+
340
+ commenters = {
341
+ :html => Proc.new{|s| "<!-- #{s} -->" },
342
+ :css => Proc.new{|s| "/*! #{s} */" },
343
+ :js => Proc.new{|s| "/*! #{s} */" }
344
+ }
345
+
346
+ commenter = commenters[options[:style]] || commenters[:js]
347
+
348
+ if options[:per_line]
349
+ string = string.split(/\r?\n/)
350
+ string.map{|s| commenter.call(s) }.join("\n")
351
+ else
352
+ commenter.call(s)
353
+ end
354
+ end
355
+ end
356
+ end
357
+
358
+ require File.dirname(__FILE__) + "/extractor"
359
+ require File.dirname(__FILE__) + "/release/scm"
360
+ require File.dirname(__FILE__) + "/release/injector"
361
+ require File.dirname(__FILE__) + "/release/cleaner"
362
+ require File.dirname(__FILE__) + "/release/finalizers"
363
+ require File.dirname(__FILE__) + "/release/processors"
@@ -0,0 +1,119 @@
1
+ module Roger
2
+ class Resolver
3
+
4
+ attr_reader :load_paths
5
+
6
+ def initialize(paths)
7
+ raise ArgumentError, "Resolver base path can't be nil" if paths.nil?
8
+
9
+ # Convert to paths
10
+ @load_paths = [paths].flatten.map{|p| Pathname.new(p) }
11
+ end
12
+
13
+ # @param [String] url The url to resolve to a path
14
+ # @param [Hash] options Options
15
+ #
16
+ # @option options [true,false] :exact_match Wether or not to match exact paths, this is mainly used in the path_to_url method to match .js, .css, etc files.
17
+ # @option options [String] :preferred_extension The part to chop off and re-add to search for more complex double-extensions. (Makes it possible to have context aware partials)
18
+ def find_template(url, options = {})
19
+ orig_path, qs, anch = strip_query_string_and_anchor(url.to_s)
20
+
21
+ options = {
22
+ :exact_match => false,
23
+ :preferred_extension => "html"
24
+ }.update(options)
25
+
26
+ paths = self.load_paths.map{|base| File.join(base, orig_path) }
27
+
28
+ paths.find do |path|
29
+ if options[:exact_match] && File.exist?(path)
30
+ return Pathname.new(path)
31
+ end
32
+
33
+ # It's a directory, add "/index"
34
+ if File.directory?(path)
35
+ path = File.join(path, "index")
36
+ end
37
+
38
+ # 2. If it's preferred_extension, we strip of the extension
39
+ if path =~ /\.#{options[:preferred_extension]}\Z/
40
+ path.sub!(/\.#{options[:preferred_extension]}\Z/, "")
41
+ end
42
+
43
+ extensions = Tilt.default_mapping.template_map.keys + Tilt.default_mapping.lazy_map.keys
44
+
45
+ # We have to re-add preferred_extension again as we have stripped it in in step 2!
46
+ extensions += extensions.map{|ext| "#{options[:preferred_extension]}.#{ext}"}
47
+
48
+ if found_extension = extensions.find { |ext| File.exist?(path + "." + ext) }
49
+ return Pathname.new(path + "." + found_extension)
50
+ end
51
+
52
+ false #Next iteration
53
+ end
54
+ end
55
+ alias :url_to_path :find_template
56
+
57
+
58
+ # Convert a disk path on file to an url
59
+ def path_to_url(path, relative_to = nil)
60
+
61
+ # Find the parent path we're in
62
+ path = Pathname.new(path).realpath
63
+ base = self.load_paths.find{|lp| path.to_s =~ /\A#{Regexp.escape(lp.realpath.to_s)}/ }
64
+
65
+ path = path.relative_path_from(base).cleanpath
66
+
67
+ if relative_to
68
+ if relative_to.to_s =~ /\A\//
69
+ relative_to = Pathname.new(File.dirname(relative_to.to_s)).relative_path_from(base).cleanpath
70
+ else
71
+ relative_to = Pathname.new(File.dirname(relative_to.to_s))
72
+ end
73
+ path = Pathname.new("/" + path.to_s).relative_path_from(Pathname.new("/" + relative_to.to_s))
74
+ path.to_s
75
+ else
76
+ "/" + path.to_s
77
+ end
78
+
79
+ end
80
+
81
+ def url_to_relative_url(url, relative_to_path)
82
+ # Skip if the url doesn't start with a / (but not with //)
83
+ return false unless url =~ /\A\/[^\/]/
84
+
85
+ path, qs, anch = strip_query_string_and_anchor(url)
86
+
87
+ # Get disk path
88
+ if true_path = self.url_to_path(path, :exact_match => true)
89
+ path = self.path_to_url(true_path, relative_to_path)
90
+ path += qs if qs
91
+ path += anch if anch
92
+ path
93
+ else
94
+ false
95
+ end
96
+ end
97
+
98
+ def strip_query_string_and_anchor(url)
99
+ url = url.dup
100
+
101
+ # Strip off anchors
102
+ anchor = nil
103
+ url.gsub!(/(#.+)\Z/) do |r|
104
+ anchor = r
105
+ ""
106
+ end
107
+
108
+ # Strip off query strings
109
+ query = nil
110
+ url.gsub!(/(\?.+)\Z/) do |r|
111
+ query = r
112
+ ""
113
+ end
114
+
115
+ [url, query, anchor]
116
+ end
117
+
118
+ end
119
+ end
@@ -0,0 +1,117 @@
1
+ require 'rack'
2
+ require File.dirname(__FILE__) + "/template"
3
+ require File.dirname(__FILE__) + "/w3c_validator"
4
+ require File.dirname(__FILE__) + "/rack/roger"
5
+ require File.dirname(__FILE__) + "/rack/html_validator"
6
+
7
+ require 'webrick'
8
+ require 'webrick/https'
9
+
10
+ module Roger
11
+ class Server
12
+
13
+ attr_reader :options, :server_options
14
+
15
+ attr_reader :project
16
+
17
+ attr_accessor :port, :handler
18
+
19
+ def initialize(project, options={})
20
+ @stack = initialize_rack_builder
21
+
22
+ @project = project
23
+
24
+ @server_options = {}
25
+
26
+ set_options(options)
27
+ end
28
+
29
+ # Sets the options, this is a separate method as we want to override certain
30
+ # things set in the mockupfile from the commandline
31
+ def set_options(options)
32
+ @options = {
33
+ :handler => nil, # Autodetect
34
+ :port => 9000
35
+ }.update(options)
36
+
37
+ self.port = @options[:port]
38
+ self.handler = @options[:handler]
39
+ end
40
+
41
+ # Use the specified Rack middleware
42
+ #
43
+ # @see ::Rack::Builder#use
44
+ def use(*args, &block)
45
+ @stack.use *args, &block
46
+ end
47
+
48
+ # Use the map handler to map endpoints to certain urls
49
+ #
50
+ # @see ::Rack::Builder#map
51
+ def map(*args, &block)
52
+ @stack.map *args, &block
53
+ end
54
+
55
+ def run!
56
+ self.get_handler(self.handler).run self.application, self.server_options do |server|
57
+ trap(:INT) do
58
+ ## Use thins' hard #stop! if available, otherwise just #stop
59
+ server.respond_to?(:stop!) ? server.stop! : server.stop
60
+ puts "Roger, out!"
61
+ end
62
+ end
63
+ end
64
+ alias :run :run!
65
+
66
+ def port=(p)
67
+ self.server_options[:Port] = p
68
+ end
69
+
70
+ protected
71
+
72
+ # Build the final application that get's run by the Rack Handler
73
+ def application
74
+ return @app if @app
75
+
76
+ @stack.use Rack::HtmlValidator if self.options[:validate]
77
+ @stack.run Rack::Roger.new(self.project)
78
+
79
+ @app = @stack
80
+ end
81
+
82
+ # Initialize the Rack builder instance for this server
83
+ #
84
+ # @return ::Rack::Builder instance
85
+ def initialize_rack_builder
86
+ builder = ::Rack::Builder.new
87
+ builder.use ::Rack::ShowExceptions
88
+ builder.use ::Rack::Lint
89
+ builder.use ::Rack::ConditionalGet
90
+ builder.use ::Rack::Head
91
+
92
+ builder
93
+ end
94
+
95
+ # Get the actual handler for use in the server
96
+ # Will always return a handler, it will try to use the fallbacks
97
+ def get_handler(preferred_handler_name = nil)
98
+ servers = %w[puma mongrel thin webrick]
99
+ servers.unshift(preferred_handler_name) if preferred_handler_name
100
+
101
+ handler = nil
102
+ while((server_name = servers.shift) && handler === nil) do
103
+ begin
104
+ handler = ::Rack::Handler.get(server_name)
105
+ rescue LoadError
106
+ rescue NameError
107
+ end
108
+ end
109
+
110
+ if preferred_handler_name && server_name != preferred_handler_name
111
+ puts "Handler '#{preferred_handler_name}' not found, using fallback ('#{server_name}')."
112
+ end
113
+ handler
114
+ end
115
+
116
+ end
117
+ end