slinky 0.2.1 → 0.4.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.
- 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
|
}
|