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.
- checksums.yaml +8 -8
- data/.gitignore +2 -0
- data/.travis.yml +12 -0
- data/CHANGELOG.md +102 -0
- data/Gemfile +5 -0
- data/MIT_LICENSE +20 -0
- data/README.md +10 -10
- data/Rakefile +9 -0
- data/bin/roger +5 -0
- data/doc/cli.md +46 -0
- data/doc/mockupfile.md +3 -0
- data/doc/templating.md +88 -0
- data/examples/default_template/.gitignore +2 -0
- data/examples/default_template/CHANGELOG +0 -0
- data/examples/default_template/Gemfile +3 -0
- data/examples/default_template/Mockupfile +1 -0
- data/examples/default_template/html/.empty_directory +0 -0
- data/examples/default_template/partials/.empty_directory +0 -0
- data/lib/roger/cli/command.rb +23 -0
- data/lib/roger/cli/generate.rb +5 -0
- data/lib/roger/cli/release.rb +10 -0
- data/lib/roger/cli/serve.rb +29 -0
- data/lib/roger/cli.rb +123 -0
- data/lib/roger/extractor.rb +95 -0
- data/lib/roger/generators/generator.rb +23 -0
- data/lib/roger/generators/new.rb +67 -0
- data/lib/roger/generators/templates/generator.tt +13 -0
- data/lib/roger/generators.rb +23 -0
- data/lib/roger/mockupfile.rb +63 -0
- data/lib/roger/project.rb +92 -0
- data/lib/roger/rack/html_validator.rb +26 -0
- data/lib/roger/rack/roger.rb +52 -0
- data/lib/roger/rack/sleep.rb +21 -0
- data/lib/roger/release/cleaner.rb +47 -0
- data/lib/roger/release/finalizers/dir.rb +29 -0
- data/lib/roger/release/finalizers/git_branch.rb +92 -0
- data/lib/roger/release/finalizers/rsync.rb +77 -0
- data/lib/roger/release/finalizers/zip.rb +42 -0
- data/lib/roger/release/finalizers.rb +19 -0
- data/lib/roger/release/injector.rb +99 -0
- data/lib/roger/release/processors/mockup.rb +93 -0
- data/lib/roger/release/processors/url_relativizer.rb +45 -0
- data/lib/roger/release/processors.rb +17 -0
- data/lib/roger/release/scm/git.rb +101 -0
- data/lib/roger/release/scm.rb +32 -0
- data/lib/roger/release.rb +363 -0
- data/lib/roger/resolver.rb +119 -0
- data/lib/roger/server.rb +117 -0
- data/lib/roger/template.rb +206 -0
- data/lib/roger/w3c_validator.rb +129 -0
- data/roger.gemspec +35 -0
- data/test/Mockupfile-syntax.rb +85 -0
- data/test/project/.rvmrc +1 -0
- data/test/project/Gemfile +7 -0
- data/test/project/Gemfile.lock +38 -0
- data/test/project/Mockupfile +13 -0
- data/test/project/html/formats/erb.html.erb +5 -0
- data/test/project/html/formats/index.html +1 -0
- data/test/project/html/formats/json.json.erb +0 -0
- data/test/project/html/formats/markdown.md +3 -0
- data/test/project/html/formats/mockup.html +5 -0
- data/test/project/html/front_matter/erb.html.erb +16 -0
- data/test/project/html/front_matter/markdown.md +7 -0
- data/test/project/html/layouts/content-for.html.erb +17 -0
- data/test/project/html/layouts/erb.html.erb +19 -0
- data/test/project/html/mockup/encoding.html +3 -0
- data/test/project/html/partials/erb.html.erb +10 -0
- data/test/project/html/partials/load_path.html.erb +3 -0
- data/test/project/html/partials/mockup.html +13 -0
- data/test/project/html/static/non-relative.html.erb +9 -0
- data/test/project/html/static/relative.html.erb +9 -0
- data/test/project/layouts/test.html.erb +34 -0
- data/test/project/layouts/yield.html.erb +1 -0
- data/test/project/lib/generators/test.rb +9 -0
- data/test/project/partials/formats/erb.html.erb +1 -0
- data/test/project/partials/partials-test.html.erb +1 -0
- data/test/project/partials/test/erb.html.erb +1 -0
- data/test/project/partials/test/front_matter.html.erb +1 -0
- data/test/project/partials/test/json.json.erb +1 -0
- data/test/project/partials/test/markdown.md +1 -0
- data/test/project/partials/test/mockup.part.html +1 -0
- data/test/project/partials/test/simple.html.erb +1 -0
- data/test/project/partials2/partials2-test.html.erb +1 -0
- data/test/unit/cli_test.rb +12 -0
- data/test/unit/generators_test.rb +75 -0
- data/test/unit/release/cleaner_test.rb +47 -0
- data/test/unit/resolver_test.rb +92 -0
- data/test/unit/template_test.rb +127 -0
- 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
|
data/lib/roger/server.rb
ADDED
@@ -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
|