slinky 0.2.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/Gemfile +8 -3
- data/README.md +84 -6
- data/Rakefile +22 -10
- data/VERSION +1 -1
- data/bin/slinky +1 -1
- data/lib/slinky/builder.rb +8 -0
- data/lib/slinky/compiled_file.rb +41 -23
- data/lib/slinky/compilers/coffee-compiler.rb +2 -3
- data/lib/slinky/compilers/haml-compiler.rb +10 -4
- data/lib/slinky/compilers/sass-compiler.rb +2 -3
- data/lib/slinky/compilers.rb +72 -0
- data/lib/slinky/manifest.rb +371 -0
- data/lib/slinky/runner.rb +56 -7
- data/lib/slinky/server.rb +46 -97
- data/lib/slinky.rb +13 -7
- data/slinky.gemspec +27 -8
- data/spec/slinky_spec.rb +314 -3
- data/spec/spec_helper.rb +113 -13
- metadata +76 -17
@@ -0,0 +1,371 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module Slinky
|
5
|
+
# extensions of files that can contain build directives
|
6
|
+
DIRECTIVE_FILES = %w{js css html haml sass scss coffee}
|
7
|
+
REQUIRE_DIRECTIVE = /^\W*(slinky_require)\((".*"|'.+'|)\)\W*$/
|
8
|
+
SCRIPTS_DIRECTIVE = /^\W*(slinky_scripts)\W*$/
|
9
|
+
STYLES_DIRECTIVE = /^\W*(slinky_styles)\W*$/
|
10
|
+
BUILD_DIRECTIVES = Regexp.union(REQUIRE_DIRECTIVE, SCRIPTS_DIRECTIVE, STYLES_DIRECTIVE)
|
11
|
+
CSS_URL_MATCHER = /url\(['"]?([^'"\/][^\s)]+\.[a-z]+)(\?\d+)?['"]?\)/
|
12
|
+
|
13
|
+
# Raised when a compilation fails for any reason
|
14
|
+
class BuildFailedError < StandardError; end
|
15
|
+
# Raised when a required file is not found.
|
16
|
+
class FileNotFoundError < StandardError; end
|
17
|
+
# Raised when there is a cycle in the dependency graph (i.e., file A
|
18
|
+
# requires file B which requires C which requires A)
|
19
|
+
class DependencyError < StandardError; end
|
20
|
+
|
21
|
+
class Manifest
|
22
|
+
attr_accessor :manifest_dir, :dir
|
23
|
+
|
24
|
+
def initialize dir, options = {}
|
25
|
+
@dir = dir
|
26
|
+
@build_to = if d = options[:build_to]
|
27
|
+
File.absolute_path(d)
|
28
|
+
else
|
29
|
+
dir
|
30
|
+
end
|
31
|
+
@manifest_dir = ManifestDir.new dir, @build_to, self
|
32
|
+
@devel = (options[:devel].nil?) ? true : options[:devel]
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a list of all files contained in this manifest
|
36
|
+
#
|
37
|
+
# @return [ManifestFile] a list of manifest files
|
38
|
+
def files
|
39
|
+
@files = []
|
40
|
+
files_rec @manifest_dir
|
41
|
+
@files
|
42
|
+
end
|
43
|
+
|
44
|
+
# Finds the file at the given path in the manifest if one exists,
|
45
|
+
# otherwise nil.
|
46
|
+
#
|
47
|
+
# @param String path the path of the file relative to the manifest
|
48
|
+
#
|
49
|
+
# @return ManifestFile the manifest file at that path if one exists
|
50
|
+
def find_by_path path
|
51
|
+
@manifest_dir.find_by_path path
|
52
|
+
end
|
53
|
+
|
54
|
+
def scripts_string
|
55
|
+
if @devel
|
56
|
+
dependency_list.reject{|x| x.output_path.extname != ".js"}.collect{|d|
|
57
|
+
%Q\<script type="text/javascript" src="#{d.relative_output_path}"></script>\
|
58
|
+
}.join("")
|
59
|
+
else
|
60
|
+
'<script type="text/javscript" src="/scripts.js"></script>'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def compress ext, output, compressor
|
65
|
+
scripts = dependency_list.reject{|x| x.output_path.extname != ext}
|
66
|
+
|
67
|
+
s = scripts.collect{|s|
|
68
|
+
f = File.open(s.build_to.to_s, 'rb'){|f| f.read}
|
69
|
+
(block_given?) ? (yield s, f) : f
|
70
|
+
}.join("\n")
|
71
|
+
|
72
|
+
File.open(output, "w+"){|f|
|
73
|
+
f.write(compressor.compress(s))
|
74
|
+
}
|
75
|
+
scripts.collect{|s| FileUtils.rm(s.build_to)}
|
76
|
+
end
|
77
|
+
|
78
|
+
def compress_scripts
|
79
|
+
compressor = YUI::JavaScriptCompressor.new(:munge => true)
|
80
|
+
compress(".js", "#{@build_to}/scripts.js", compressor)
|
81
|
+
end
|
82
|
+
|
83
|
+
def compress_styles
|
84
|
+
compressor = YUI::CssCompressor.new()
|
85
|
+
|
86
|
+
compress(".css", "#{@build_to}/styles.css", compressor){|s, css|
|
87
|
+
css.gsub(CSS_URL_MATCHER){|url|
|
88
|
+
p = s.relative_output_path.dirname.to_s + "/#{$1}"
|
89
|
+
"url('#{p}')"
|
90
|
+
}
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def styles_string
|
95
|
+
if @devel
|
96
|
+
dependency_list.reject{|x| x.output_path.extname != ".css"}.collect{|d|
|
97
|
+
%Q\<link rel="stylesheet" href="#{d.relative_output_path}" />\
|
98
|
+
}.join("")
|
99
|
+
else
|
100
|
+
'<link rel="stylesheet" href="/styles.css" />'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Builds the directed graph representing the dependencies of all
|
105
|
+
# files in the manifest that contain a slinky_require
|
106
|
+
# declaration. The graph is represented as a list of pairs
|
107
|
+
# (required, by), each of which describes an edge.
|
108
|
+
#
|
109
|
+
# @return [[ManifestFile, ManifestFile]] the graph
|
110
|
+
def build_dependency_graph
|
111
|
+
graph = []
|
112
|
+
files.each{|mf|
|
113
|
+
mf.directives[:slinky_require].each{|rf|
|
114
|
+
required = mf.parent.find_by_path(rf)
|
115
|
+
if required
|
116
|
+
graph << [required, mf]
|
117
|
+
else
|
118
|
+
error = "Could not find file #{rf} required by #{mf.source}"
|
119
|
+
$stderr.puts error.foreground(:red)
|
120
|
+
raise FileNotFoundError.new(error)
|
121
|
+
end
|
122
|
+
} if mf.directives[:slinky_require]
|
123
|
+
}
|
124
|
+
@dependency_graph = graph
|
125
|
+
end
|
126
|
+
|
127
|
+
# Builds a list of files in topological order, so that when
|
128
|
+
# required in this order all dependencies are met. See
|
129
|
+
# http://en.wikipedia.org/wiki/Topological_sorting for more
|
130
|
+
# information.
|
131
|
+
def dependency_list
|
132
|
+
build_dependency_graph unless @dependency_graph
|
133
|
+
graph = @dependency_graph.clone
|
134
|
+
# will contain sorted elements
|
135
|
+
l = []
|
136
|
+
# start nodes, those with no incoming edges
|
137
|
+
s = @files.reject{|mf| mf.directives[:slinky_require]}
|
138
|
+
while s.size > 0
|
139
|
+
n = s.delete s.first
|
140
|
+
l << n
|
141
|
+
@files.each{|m|
|
142
|
+
e = graph.find{|e| e[0] == n && e[1] == m}
|
143
|
+
next unless e
|
144
|
+
graph.delete e
|
145
|
+
s << m unless graph.any?{|e| e[1] == m}
|
146
|
+
}
|
147
|
+
end
|
148
|
+
if graph != []
|
149
|
+
problems = graph.collect{|e| e.collect{|x| x.source}.join(" -> ")}
|
150
|
+
$stderr.puts "Dependencies #{problems.join(", ")} could not be satisfied".foreground(:red)
|
151
|
+
raise DependencyError
|
152
|
+
end
|
153
|
+
l
|
154
|
+
end
|
155
|
+
|
156
|
+
def build
|
157
|
+
@manifest_dir.build
|
158
|
+
unless @devel
|
159
|
+
compress_scripts
|
160
|
+
compress_styles
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
def files_rec md
|
166
|
+
@files += md.files
|
167
|
+
md.children.each do |c|
|
168
|
+
files_rec c
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
class ManifestDir
|
174
|
+
attr_accessor :dir, :files, :children
|
175
|
+
def initialize dir, build_dir, manifest
|
176
|
+
@dir = dir
|
177
|
+
@files = []
|
178
|
+
@children = []
|
179
|
+
@build_dir = Pathname.new(build_dir)
|
180
|
+
@manifest = manifest
|
181
|
+
|
182
|
+
Dir.glob("#{dir}/*").each do |path|
|
183
|
+
# skip the build dir
|
184
|
+
next if Pathname.new(File.absolute_path(path)) == Pathname.new(build_dir)
|
185
|
+
if File.directory? path
|
186
|
+
build_dir = (@build_dir + File.basename(path)).cleanpath
|
187
|
+
@children << ManifestDir.new(path, build_dir, manifest)
|
188
|
+
else
|
189
|
+
@files << ManifestFile.new(path, @build_dir, manifest, self)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Finds the file at the given path in the directory if one exists,
|
195
|
+
# otherwise nil.
|
196
|
+
#
|
197
|
+
# @param String path the path of the file relative to the directory
|
198
|
+
#
|
199
|
+
# @return ManifestFile the manifest file at that path if one exists
|
200
|
+
def find_by_path path
|
201
|
+
components = path.to_s.split(File::SEPARATOR).reject{|x| x == ""}
|
202
|
+
case components.size
|
203
|
+
when 0
|
204
|
+
self
|
205
|
+
when 1
|
206
|
+
@files.find{|f| f.matches? components[0]}
|
207
|
+
else
|
208
|
+
child = @children.find{|d|
|
209
|
+
Pathname.new(d.dir).basename.to_s == components[0]
|
210
|
+
}
|
211
|
+
child ? child.find_by_path(components[1..-1].join(File::SEPARATOR)) : nil
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def build
|
216
|
+
if !@build_dir.exist?
|
217
|
+
@build_dir.mkdir
|
218
|
+
end
|
219
|
+
(@files + @children).each{|m|
|
220
|
+
m.build
|
221
|
+
}
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
class ManifestFile
|
226
|
+
attr_accessor :source, :build_path
|
227
|
+
attr_reader :last_built, :directives, :parent, :manifest
|
228
|
+
|
229
|
+
def initialize source, build_path, manifest, parent = nil, options = {:devel => false}
|
230
|
+
@parent = parent
|
231
|
+
@source = source
|
232
|
+
@last_built = Time.new(0)
|
233
|
+
|
234
|
+
@cfile = Compilers.cfile_for_file(@source)
|
235
|
+
|
236
|
+
@directives = find_directives
|
237
|
+
@build_path = build_path
|
238
|
+
@manifest = manifest
|
239
|
+
@devel = true if options[:devel]
|
240
|
+
end
|
241
|
+
|
242
|
+
# Predicate which determines whether the supplied name is the same
|
243
|
+
# as the file's name, taking into account compiled file
|
244
|
+
# extensions. For example, if mf refers to "/tmp/test/hello.sass",
|
245
|
+
# `mf.matches? "hello.sass"` and `mf.matches? "hello.css"` should
|
246
|
+
# both return true.
|
247
|
+
#
|
248
|
+
# @param [String] a filename
|
249
|
+
# @return [Bool] True if the filename matches, false otherwise
|
250
|
+
def matches? s
|
251
|
+
name = Pathname.new(@source).basename.to_s
|
252
|
+
name == s || output_path.basename.to_s == s
|
253
|
+
end
|
254
|
+
|
255
|
+
# Returns the path to which this file should be output. This is
|
256
|
+
# equal to the source path unless the file needs to be compiled,
|
257
|
+
# in which case the extension returned is the output extension
|
258
|
+
#
|
259
|
+
# @return Pathname the output path
|
260
|
+
def output_path
|
261
|
+
if @cfile
|
262
|
+
Pathname.new(@source).sub_ext ".#{@cfile.output_ext}"
|
263
|
+
else
|
264
|
+
Pathname.new(@source)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Returns the output path relative to the manifest directory
|
269
|
+
def relative_output_path
|
270
|
+
output_path.relative_path_from Pathname.new(@manifest.dir)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Looks through the file for directives
|
274
|
+
# @return {Symbol => [String]} the directives in the file
|
275
|
+
def find_directives
|
276
|
+
_, _, ext = @source.match(EXTENSION_REGEX).to_a
|
277
|
+
directives = {}
|
278
|
+
# check if this file might include directives
|
279
|
+
if @cfile || DIRECTIVE_FILES.include?(ext)
|
280
|
+
# make sure the file isn't too big to scan
|
281
|
+
stat = File::Stat.new(@source)
|
282
|
+
if stat.size < 1024*1024
|
283
|
+
File.open(@source) {|f|
|
284
|
+
matches = f.read.scan(BUILD_DIRECTIVES).to_a
|
285
|
+
matches.each{|slice|
|
286
|
+
key, value = slice.compact
|
287
|
+
directives[key.to_sym] ||= []
|
288
|
+
directives[key.to_sym] << value[1..-2] if value
|
289
|
+
}
|
290
|
+
} rescue nil
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
directives
|
295
|
+
end
|
296
|
+
|
297
|
+
# If there are any build directives for this file, the file is
|
298
|
+
# read and the directives are handled appropriately and a new file
|
299
|
+
# is written to a temp location.
|
300
|
+
#
|
301
|
+
# @return String the path of the de-directivefied file
|
302
|
+
def handle_directives path, to = nil
|
303
|
+
if @directives.size > 0
|
304
|
+
begin
|
305
|
+
out = File.read(path)
|
306
|
+
out.gsub!(REQUIRE_DIRECTIVE, "")
|
307
|
+
out.gsub!(SCRIPTS_DIRECTIVE, @manifest.scripts_string)
|
308
|
+
out.gsub!(STYLES_DIRECTIVE, @manifest.styles_string)
|
309
|
+
to = to || Tempfile.new("slinky").path
|
310
|
+
File.open(to, "w+"){|f|
|
311
|
+
f.write(out)
|
312
|
+
}
|
313
|
+
to
|
314
|
+
rescue
|
315
|
+
nil
|
316
|
+
end
|
317
|
+
else
|
318
|
+
path
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
# Takes a path and compiles the file if necessary.
|
323
|
+
# @return Pathname the path of the compiled file, or the original
|
324
|
+
# path if compiling is not necessary
|
325
|
+
def compile path, to = nil
|
326
|
+
if @cfile
|
327
|
+
cfile = @cfile.clone
|
328
|
+
cfile.source = path
|
329
|
+
cfile.print_name = @source
|
330
|
+
cfile.output_path = to if to
|
331
|
+
cfile.file do |cpath, _, _, _|
|
332
|
+
path = cpath
|
333
|
+
end
|
334
|
+
end
|
335
|
+
path ? Pathname.new(path) : nil
|
336
|
+
end
|
337
|
+
|
338
|
+
# Gets manifest file ready for serving or building by handling the
|
339
|
+
# directives and compiling the file if neccesary.
|
340
|
+
# @param String path to which the file should be compiled
|
341
|
+
#
|
342
|
+
# @return String the path of the processed file, ready for serving
|
343
|
+
def process to = nil
|
344
|
+
# mangle file appropriately
|
345
|
+
handle_directives (compile @source), to
|
346
|
+
end
|
347
|
+
|
348
|
+
# Path to which the file will be built
|
349
|
+
def build_to
|
350
|
+
Pathname.new(@build_path) + output_path.basename
|
351
|
+
end
|
352
|
+
|
353
|
+
# Builds the file by handling and compiling it and then copying it
|
354
|
+
# to the build path
|
355
|
+
def build
|
356
|
+
if !File.exists? @build_path
|
357
|
+
FileUtils.mkdir_p(@build_path)
|
358
|
+
end
|
359
|
+
to = build_to
|
360
|
+
path = process to
|
361
|
+
|
362
|
+
if !path
|
363
|
+
raise BuildFailedError
|
364
|
+
elsif path != to
|
365
|
+
FileUtils.cp(path.to_s, to.to_s)
|
366
|
+
@last_built = Time.now
|
367
|
+
end
|
368
|
+
to
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
data/lib/slinky/runner.rb
CHANGED
@@ -1,12 +1,61 @@
|
|
1
1
|
module Slinky
|
2
2
|
class Runner
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
COMMANDS = %w{start build}
|
4
|
+
|
5
|
+
def initialize argv
|
6
|
+
@argv = argv
|
7
|
+
@options = {
|
8
|
+
:build_dir => "build",
|
9
|
+
:port => 5323,
|
10
|
+
:src_dir => "."
|
11
|
+
}
|
12
|
+
|
13
|
+
parser.parse! @argv
|
14
|
+
@command = @argv.shift
|
15
|
+
@arguments = @argv
|
16
|
+
end
|
17
|
+
|
18
|
+
def version
|
19
|
+
root = File.expand_path(File.dirname(__FILE__))
|
20
|
+
File.open("#{root}/../../VERSION"){|f|
|
21
|
+
puts "slinky #{f.read.strip}"
|
22
|
+
}
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
|
26
|
+
def parser
|
27
|
+
@parser ||= OptionParser.new do |opts|
|
28
|
+
opts.banner = "Usage: slinky [options] #{COMMANDS.join('|')}"
|
29
|
+
opts.on("-v", "--version", "Outputs current version number and exits"){ version }
|
30
|
+
opts.on("-o DIR", "--build-dir DIR", "Directory to which the site will be built.", "Use in conjunction with the 'build' command."){|dir| @options[:build_dir] = File.expand_path(dir)}
|
31
|
+
opts.on("-p PORT", "--port PORT", "Port to run on (default: #{@options[:port]})"){|p| @options[:port] = p.to_i}
|
32
|
+
opts.on("-s DIR", "--src-dir DIR", "Directory containing project source"){|p| @options[:src_dir] = p}
|
9
33
|
end
|
10
|
-
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def run
|
37
|
+
case @command
|
38
|
+
when "start" then command_start
|
39
|
+
when "build" then command_build
|
40
|
+
when nil
|
41
|
+
abort "Must provide a command (one of #{COMMANDS.join(', ')})"
|
42
|
+
else
|
43
|
+
abort "Unknown command: #{@command}. Must be on of #{COMMANDS.join(', ')}."
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def command_start
|
48
|
+
Signal.trap('INT') { puts "Slinky fading away ... "; exit(0); }
|
49
|
+
|
50
|
+
EM::run {
|
51
|
+
Slinky::Server.dir = @options[:src_dir]
|
52
|
+
EM::start_server "0.0.0.0", @options[:port], Slinky::Server
|
53
|
+
puts "Started static file server on port #{@options[:port]}"
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def command_build
|
58
|
+
Builder.build(@options[:src_dir], @options[:build_dir])
|
59
|
+
end
|
11
60
|
end
|
12
61
|
end
|
data/lib/slinky/server.rb
CHANGED
@@ -1,123 +1,72 @@
|
|
1
1
|
module Slinky
|
2
|
-
|
3
|
-
'html' => 'text/html',
|
4
|
-
'js' => 'application/x-javascript',
|
5
|
-
'css' => 'text/css'
|
6
|
-
}
|
7
|
-
|
8
|
-
EXTENSION_REGEX = /(.+)\.(\w+)/
|
9
|
-
|
10
|
-
class Server < EventMachine::Connection
|
2
|
+
module Server
|
11
3
|
include EM::HttpServer
|
12
4
|
|
13
|
-
|
14
|
-
@
|
15
|
-
|
16
|
-
@
|
17
|
-
|
18
|
-
class << self
|
19
|
-
def register_compiler klass, options
|
20
|
-
options[:klass] = klass
|
21
|
-
@compilers << options
|
22
|
-
options[:outputs].each do |output|
|
23
|
-
@compilers_by_ext[output] ||= []
|
24
|
-
@compilers_by_ext[output] << options
|
25
|
-
end
|
26
|
-
end
|
5
|
+
# Sets the root directory from which files should be served
|
6
|
+
def self.dir= _dir; @dir = _dir; end
|
7
|
+
# Gets the root directory from which files should be served
|
8
|
+
def self.dir; @dir || "."; end
|
27
9
|
|
28
|
-
|
29
|
-
|
30
|
-
|
10
|
+
# Splits a uri into its components, returning only the path sans
|
11
|
+
# initial forward slash.
|
12
|
+
def self.path_for_uri uri
|
13
|
+
_, _, _, _, _, path, _, _ = URI.split uri
|
14
|
+
path[1..-1] #get rid of the leading /
|
31
15
|
end
|
32
16
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
@resp = EventMachine::DelegatedHttpResponse.new(self)
|
39
|
-
|
40
|
-
_, _, _, _, _, path, _, query = URI.split @http_request_uri
|
41
|
-
path = path[1..-1] #get rid of the leading /
|
42
|
-
_, file, extension = path.match(EXTENSION_REGEX).to_a
|
43
|
-
|
44
|
-
compilers = self.class.compilers_by_ext
|
45
|
-
|
46
|
-
# Check if we've already seen this file. If so, we can skip a
|
47
|
-
# bunch of processing.
|
48
|
-
if files[path]
|
49
|
-
serve_compiled_file files[path]
|
50
|
-
return
|
51
|
-
end
|
52
|
-
|
53
|
-
# if there's a file extension and we have a compiler that
|
54
|
-
# outputs that kind of file, look for an input with the same
|
55
|
-
# name and an extension in our list
|
56
|
-
if extension && extension != "" && compilers[extension]
|
57
|
-
files_by_ext = {}
|
58
|
-
# find possible source files
|
59
|
-
Dir.glob("#{file}.*").each do |f|
|
60
|
-
_, _, ext = f.match(EXTENSION_REGEX).to_a
|
61
|
-
files_by_ext[ext] = f
|
62
|
-
end
|
63
|
-
|
64
|
-
cfile = nil
|
65
|
-
# find a compiler that outputs the request kind of file and
|
66
|
-
# which has an input file type that exists on the file system
|
67
|
-
compilers[extension].each do |c|
|
68
|
-
c[:inputs].each do |i|
|
69
|
-
if files_by_ext[i]
|
70
|
-
cfile = CompiledFile.new files_by_ext[i], c[:klass], extension
|
71
|
-
files[path] = cfile
|
72
|
-
break
|
73
|
-
end
|
74
|
-
end
|
75
|
-
break if cfile
|
76
|
-
end
|
77
|
-
|
78
|
-
if cfile
|
79
|
-
serve_compiled_file cfile
|
17
|
+
# Takes a manifest file and produces a response for it
|
18
|
+
def self.handle_file resp, mf
|
19
|
+
if mf
|
20
|
+
if path = mf.process
|
21
|
+
serve_file resp, path.to_s
|
80
22
|
else
|
81
|
-
|
23
|
+
resp.status = 500
|
24
|
+
resp.content = "Error compiling #{mf.source}"
|
82
25
|
end
|
83
26
|
else
|
84
|
-
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def serve_compiled_file cfile
|
89
|
-
cfile.file do |path, status, stdout, stderr|
|
90
|
-
if path
|
91
|
-
serve_file path
|
92
|
-
else
|
93
|
-
puts "Status: #{status.inspect}"
|
94
|
-
@resp.status = 500
|
95
|
-
@resp.content = "Error compiling #{cfile.source}:\n #{stdout}"
|
96
|
-
@resp.send_response
|
97
|
-
end
|
27
|
+
not_found resp
|
98
28
|
end
|
29
|
+
resp
|
99
30
|
end
|
100
31
|
|
101
|
-
|
102
|
-
|
32
|
+
# Serves a file from the file system
|
33
|
+
def self.serve_file resp, path
|
34
|
+
if File.exists?(path) && !File.directory?(path)
|
35
|
+
size = File.size(path)
|
103
36
|
_, _, extension = path.match(EXTENSION_REGEX).to_a
|
104
|
-
|
37
|
+
resp.content_type MIME::Types.type_for(path).first
|
105
38
|
# File reading code from rack/file.rb
|
106
39
|
File.open path do |file|
|
107
|
-
|
40
|
+
resp.content = ""
|
108
41
|
while size > 0
|
109
42
|
part = file.read([8192, size].min)
|
110
43
|
break unless part
|
111
44
|
size -= part.length
|
112
|
-
|
45
|
+
resp.content << part
|
113
46
|
end
|
114
47
|
end
|
115
|
-
@resp.send_response
|
116
48
|
else
|
117
|
-
|
118
|
-
|
119
|
-
|
49
|
+
not_found resp
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the proper responce for files that do not exist
|
54
|
+
def self.not_found resp
|
55
|
+
resp.status = 404
|
56
|
+
resp.content = "File not found"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Method called for every HTTP request made
|
60
|
+
def process_http_request
|
61
|
+
@manifest = Manifest.new(Server.dir)
|
62
|
+
|
63
|
+
path = Server.path_for_uri(@http_request_uri)
|
64
|
+
file = @manifest.find_by_path(path)
|
65
|
+
resp = EventMachine::DelegatedHttpResponse.new(self)
|
66
|
+
if file.is_a? ManifestDir
|
67
|
+
file = @manifest.find_by_path(path+"/index.html")
|
120
68
|
end
|
69
|
+
Server.handle_file(resp, file).send_response
|
121
70
|
end
|
122
71
|
end
|
123
72
|
end
|
data/lib/slinky.rb
CHANGED
@@ -5,19 +5,25 @@ require 'evma_httpserver'
|
|
5
5
|
require 'uri'
|
6
6
|
require 'tempfile'
|
7
7
|
require 'rainbow'
|
8
|
+
require 'optparse'
|
9
|
+
require 'mime/types'
|
10
|
+
require 'yui/compressor'
|
8
11
|
|
9
12
|
require "#{ROOT}/slinky/em-popen3"
|
13
|
+
require "#{ROOT}/slinky/compilers"
|
14
|
+
require "#{ROOT}/slinky/manifest"
|
10
15
|
require "#{ROOT}/slinky/compiled_file"
|
11
16
|
require "#{ROOT}/slinky/server"
|
12
17
|
require "#{ROOT}/slinky/runner"
|
18
|
+
require "#{ROOT}/slinky/builder"
|
13
19
|
|
14
20
|
# load compilers
|
15
21
|
Dir.glob("#{ROOT}/slinky/compilers/*.rb").each{|compiler|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
22
|
+
begin
|
23
|
+
require compiler
|
24
|
+
rescue
|
25
|
+
puts "Failed to load #{compiler}: #{$!}"
|
26
|
+
rescue LoadError
|
27
|
+
puts "Failed to load #{compiler}: syntax error"
|
28
|
+
end
|
23
29
|
}
|