slinky 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "eventmachine", ">= 0.12.0"
4
+ gem "eventmachine_httpserver", ">= 0.2.0"
5
+
6
+ group :development do
7
+ gem "bacon", ">= 0"
8
+ gem "yard", "~> 0.6.0"
9
+ gem "bundler", "~> 1.0.0"
10
+ gem "jeweler", "~> 1.5.2"
11
+ gem "rcov", ">= 0"
12
+ end
13
+
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 mwylde
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,6 @@
1
+ #Slinky
2
+
3
+ Slinky is a static file server designed to make development of static
4
+ web sites and applications simpler. Simply run slinky in the home
5
+ directory of your app and it will serve while compiling things like
6
+ SASS, HAML and CoffeeScript on the fly.
data/Rakefile ADDED
@@ -0,0 +1,42 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "slinky"
16
+ gem.homepage = "http://github.com/mwylde/slinky"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Static file server for javascript apps}
19
+ gem.description = %Q{A static file server for rich javascript apps that automatically compiles SASS, HAML, CoffeeScript and more}
20
+ gem.email = "mwylde@wesleyan.edu"
21
+ gem.authors = ["mwylde"]
22
+ end
23
+ Jeweler::RubygemsDotOrgTasks.new
24
+
25
+ require 'rake/testtask'
26
+ Rake::TestTask.new(:spec) do |spec|
27
+ spec.libs << 'lib' << 'spec'
28
+ spec.pattern = 'spec/**/*_spec.rb'
29
+ spec.verbose = true
30
+ end
31
+
32
+ require 'rcov/rcovtask'
33
+ Rcov::RcovTask.new do |spec|
34
+ spec.libs << 'spec'
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.verbose = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'yard'
42
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/slinky ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'slinky'
4
+
5
+ Slinky::Runner.run
@@ -0,0 +1,56 @@
1
+ module Slinky
2
+ # Stores information about compiled files, including location,
3
+ # source file and last modified time
4
+ class CompiledFile
5
+ attr_reader :source, :last_compiled
6
+
7
+ # Creates a new CompiledFile, compiling the provided source file
8
+ # with the provided compiler class.
9
+ def initialize source, compiler
10
+ @source = source
11
+ @compiler = compiler
12
+ @last_compiled = Time.new(0)
13
+ end
14
+
15
+ # Compiles the source file to a temporary location
16
+ def compile &cb
17
+ path = @path || tmp_path
18
+
19
+ command = @compiler.command @source, path
20
+
21
+ puts "Running command '#{command}'"
22
+
23
+ EM.system3 command do |stdout, stderr, status|
24
+ @last_compiled = Time.now
25
+ if status.exitstatus != 0
26
+ $stderr.write "Failed on #{@source}: #{stderr.strip}\n"
27
+ else
28
+ puts "Compiled #{@source}"
29
+ @path = path
30
+ end
31
+ cb.call(@path, status, stdout, stderr)
32
+ end
33
+ end
34
+
35
+ # Calls the supplied callback with the path of the compiled file,
36
+ # compiling the source file first if necessary.
37
+ def file &cb
38
+ if needs_update?
39
+ compile &cb
40
+ else
41
+ cb.call @path, nil, nil, nil
42
+ end
43
+ end
44
+
45
+ # Returns whether the source file has changed since it was last
46
+ # compiled.
47
+ def needs_update?
48
+ File.new(source).mtime > @last_compiled
49
+ end
50
+
51
+ private
52
+ def tmp_path
53
+ Tempfile.new("slinky").path
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,12 @@
1
+ module Slinky
2
+ module CoffeeCompiler
3
+ Server.register_compiler self,
4
+ :inputs => ["coffee"],
5
+ :outputs => ["js"]
6
+
7
+ def CoffeeCompiler::command from, to
8
+ root = File.dirname(__FILE__)
9
+ %Q?#{root}/coffee-helper "#{from}" "#{to}"?
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ coffee --compile --print $1 > $2
@@ -0,0 +1,11 @@
1
+ module Slinky
2
+ module HamlCompiler
3
+ Server.register_compiler self,
4
+ :inputs => ["haml"],
5
+ :outputs => ["html"]
6
+
7
+ def HamlCompiler::command from, to
8
+ %Q?haml "#{from}" "#{to}"?
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Slinky
2
+ module SassCompiler
3
+ Server.register_compiler self,
4
+ :inputs => ["sass", "scss"],
5
+ :outputs => ["css"]
6
+
7
+ def SassCompiler::command from, to
8
+ %Q?sass "#{from}:#{to}"?
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,67 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+
4
+ if RUBY_VERSION < "1.8.7"
5
+ def __method__
6
+ caller = /`(.*)'/
7
+ Kernel.caller.first.split(caller).last.to_sym
8
+ end
9
+ end
10
+
11
+ def log *message
12
+ p [Time.now, *message]
13
+ end
14
+
15
+ module EventMachine
16
+ def self.popen3(*args)
17
+ new_stderr = $stderr.dup
18
+ rd, wr = IO::pipe
19
+ $stderr.reopen wr
20
+ connection = EM.popen(*args)
21
+ $stderr.reopen new_stderr
22
+ EM.attach rd, Popen3StderrHandler, connection
23
+ connection
24
+ end
25
+
26
+ class Popen3StderrHandler < EventMachine::Connection
27
+ def initialize(connection)
28
+ @connection = connection
29
+ end
30
+
31
+ def receive_data(data)
32
+ @connection.receive_stderr(data)
33
+ end
34
+ end
35
+ end
36
+
37
+ class ProcessHandler < EventMachine::Connection
38
+ def initialize cb
39
+ @cb = cb
40
+ @stdout = []
41
+ @stderr = []
42
+ end
43
+
44
+ def receive_data data
45
+ @stdout << data
46
+ end
47
+
48
+ def receive_stderr data
49
+ @stderr << data
50
+ end
51
+
52
+ def unbind
53
+ @cb.call @stdout.join(''), @stderr.join(''), get_status if @cb
54
+ end
55
+ end
56
+
57
+ def EventMachine::system3 cmd, *args, &cb
58
+ cb ||= args.pop if args.last.is_a? Proc
59
+ init = args.pop if args.last.is_a? Proc
60
+
61
+ # merge remaining arguments into the command
62
+ cmd = ([cmd] + args.map{|a|a.to_s.dump}).join(' ')
63
+
64
+ EM.get_subprocess_pid(EM.popen3(cmd, ProcessHandler, cb) do |c|
65
+ init[c] if init
66
+ end.signature)
67
+ end
@@ -0,0 +1,12 @@
1
+ module Slinky
2
+ class Runner
3
+ class << self
4
+ def run
5
+ EM::run {
6
+ EM::start_server "0.0.0.0", 5323, Slinky::Server
7
+ puts "Started static file server on port 5323"
8
+ }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,115 @@
1
+ module Slinky
2
+ class Server < EventMachine::Connection
3
+ include EM::HttpServer
4
+
5
+ @compilers = []
6
+ @compilers_by_ext = {}
7
+
8
+
9
+ @files = {}
10
+
11
+ class << self
12
+ def register_compiler klass, options
13
+ options[:klass] = klass
14
+ @compilers << options
15
+ options[:outputs].each do |output|
16
+ @compilers_by_ext[output] ||= []
17
+ @compilers_by_ext[output] << options
18
+ end
19
+ end
20
+
21
+ def compilers_by_ext
22
+ @compilers_by_ext
23
+ end
24
+ end
25
+
26
+ def files
27
+ self.class.instance_variable_get(:@files)
28
+ end
29
+
30
+ EXTENSION_REGEX = /(^[\w\d\:\/\.]*)\.(\w+)/
31
+ def process_http_request
32
+ @resp = EventMachine::DelegatedHttpResponse.new(self)
33
+
34
+ _, _, _, _, _, path, _, query = URI.split @http_request_uri
35
+ path = path[1..-1] #get rid of the leading /
36
+ _, file, extension = path.match(EXTENSION_REGEX).to_a
37
+
38
+ compilers = self.class.compilers_by_ext
39
+
40
+ # Check if we've already seen this file. If so, we can skip a
41
+ # bunch of processing.
42
+ if files[path]
43
+ serve_compiled_file files[path]
44
+ return
45
+ end
46
+
47
+ # if there's a file extension and we have a compiler that
48
+ # outputs that kind of file, look for an input with the same
49
+ # name and an extension in our list
50
+ if extension && extension != "" && compilers[extension]
51
+ files_by_ext = {}
52
+ # find possible source files
53
+ Dir.glob("#{file}.*").each do |f|
54
+ _, _, ext = f.match(EXTENSION_REGEX).to_a
55
+ files_by_ext[ext] = f
56
+ end
57
+
58
+ cfile = nil
59
+ # find a compiler that outputs the request kind of file and
60
+ # which has an input file type that exists on the file system
61
+ compilers[extension].each do |c|
62
+ c[:inputs].each do |i|
63
+ if files_by_ext[i]
64
+ cfile = CompiledFile.new files_by_ext[i], c[:klass]
65
+ files[path] = cfile
66
+ break
67
+ end
68
+ end
69
+ break if cfile
70
+ end
71
+
72
+ if cfile
73
+ serve_compiled_file cfile
74
+ else
75
+ serve_file path
76
+ end
77
+ else
78
+ serve_file path
79
+ end
80
+ end
81
+
82
+ def serve_compiled_file cfile
83
+ cfile.file do |path, status, stdout, stderr|
84
+ if path
85
+ serve_file path
86
+ else
87
+ puts "Status: #{status.inspect}"
88
+ @resp.status = 500
89
+ @resp.content = "Error compiling #{cfile.source}:\n #{stdout}"
90
+ @resp.send_response
91
+ end
92
+ end
93
+ end
94
+
95
+ def serve_file path
96
+ if File.exists? path
97
+ size = File.size? path
98
+ if true || size < EventMachine::FileStreamer::MappingThreshold
99
+ s = File.open(path).read
100
+ @resp.content = s
101
+ @resp.send_response
102
+ else
103
+ stream_file_data path do
104
+ @resp.send_headers
105
+ @resp.send_trailer
106
+ end
107
+ end
108
+ else
109
+ @resp.status = 404
110
+ @resp.content = "File '#{path}' not found."
111
+ @resp.send_response
112
+ end
113
+ end
114
+ end
115
+ end
data/lib/slinky.rb ADDED
@@ -0,0 +1,22 @@
1
+ ROOT = File.expand_path(File.dirname(__FILE__))
2
+
3
+ require 'eventmachine'
4
+ require 'evma_httpserver'
5
+ require 'uri'
6
+ require 'tempfile'
7
+
8
+ require "#{ROOT}/slinky/em-popen3"
9
+ require "#{ROOT}/slinky/compiled_file"
10
+ require "#{ROOT}/slinky/server"
11
+ require "#{ROOT}/slinky/runner"
12
+
13
+ # load compilers
14
+ Dir.glob("#{ROOT}/slinky/compilers/*.rb").each{|compiler|
15
+ begin
16
+ require compiler
17
+ rescue
18
+ puts "Failed to load #{compiler}: #{$!}"
19
+ rescue LoadError
20
+ puts "Failed to load #{compiler}: syntax error"
21
+ end
22
+ }
data/slinky.gemspec ADDED
@@ -0,0 +1,82 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{slinky}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["mwylde"]
12
+ s.date = %q{2011-03-11}
13
+ s.default_executable = %q{slinky}
14
+ s.description = %q{A static file server for rich javascript apps that automatically compiles SASS, HAML, CoffeeScript and more}
15
+ s.email = %q{mwylde@wesleyan.edu}
16
+ s.executables = ["slinky"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE.txt",
19
+ "README.md"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ "Gemfile",
24
+ "LICENSE.txt",
25
+ "README.md",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "bin/slinky",
29
+ "lib/slinky.rb",
30
+ "lib/slinky/compiled_file.rb",
31
+ "lib/slinky/compilers/coffee-compiler.rb",
32
+ "lib/slinky/compilers/coffee-helper",
33
+ "lib/slinky/compilers/haml-compiler.rb",
34
+ "lib/slinky/compilers/sass-compiler.rb",
35
+ "lib/slinky/em-popen3.rb",
36
+ "lib/slinky/runner.rb",
37
+ "lib/slinky/server.rb",
38
+ "slinky.gemspec",
39
+ "spec/slinky_spec.rb",
40
+ "spec/spec_helper.rb"
41
+ ]
42
+ s.homepage = %q{http://github.com/mwylde/slinky}
43
+ s.licenses = ["MIT"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.5.2}
46
+ s.summary = %q{Static file server for javascript apps}
47
+ s.test_files = [
48
+ "spec/slinky_spec.rb",
49
+ "spec/spec_helper.rb"
50
+ ]
51
+
52
+ if s.respond_to? :specification_version then
53
+ s.specification_version = 3
54
+
55
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
56
+ s.add_runtime_dependency(%q<eventmachine>, [">= 0.12.0"])
57
+ s.add_runtime_dependency(%q<eventmachine_httpserver>, [">= 0.2.0"])
58
+ s.add_development_dependency(%q<bacon>, [">= 0"])
59
+ s.add_development_dependency(%q<yard>, ["~> 0.6.0"])
60
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
61
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
62
+ s.add_development_dependency(%q<rcov>, [">= 0"])
63
+ else
64
+ s.add_dependency(%q<eventmachine>, [">= 0.12.0"])
65
+ s.add_dependency(%q<eventmachine_httpserver>, [">= 0.2.0"])
66
+ s.add_dependency(%q<bacon>, [">= 0"])
67
+ s.add_dependency(%q<yard>, ["~> 0.6.0"])
68
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
69
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
70
+ s.add_dependency(%q<rcov>, [">= 0"])
71
+ end
72
+ else
73
+ s.add_dependency(%q<eventmachine>, [">= 0.12.0"])
74
+ s.add_dependency(%q<eventmachine_httpserver>, [">= 0.2.0"])
75
+ s.add_dependency(%q<bacon>, [">= 0"])
76
+ s.add_dependency(%q<yard>, ["~> 0.6.0"])
77
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
78
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
79
+ s.add_dependency(%q<rcov>, [">= 0"])
80
+ end
81
+ end
82
+
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Slinky" do
4
+ it "fails" do
5
+ should.flunk "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'bacon'
11
+
12
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ require 'static-runner'
15
+
16
+ Bacon.summary_on_exit
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: slinky
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - mwylde
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-03-11 00:00:00 -05:00
14
+ default_executable: slinky
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: eventmachine
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.12.0
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: eventmachine_httpserver
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 0.2.0
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: bacon
40
+ requirement: &id003 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ type: :development
47
+ prerelease: false
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: yard
51
+ requirement: &id004 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ~>
55
+ - !ruby/object:Gem::Version
56
+ version: 0.6.0
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: bundler
62
+ requirement: &id005 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ~>
66
+ - !ruby/object:Gem::Version
67
+ version: 1.0.0
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: *id005
71
+ - !ruby/object:Gem::Dependency
72
+ name: jeweler
73
+ requirement: &id006 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ~>
77
+ - !ruby/object:Gem::Version
78
+ version: 1.5.2
79
+ type: :development
80
+ prerelease: false
81
+ version_requirements: *id006
82
+ - !ruby/object:Gem::Dependency
83
+ name: rcov
84
+ requirement: &id007 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: "0"
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: *id007
93
+ description: A static file server for rich javascript apps that automatically compiles SASS, HAML, CoffeeScript and more
94
+ email: mwylde@wesleyan.edu
95
+ executables:
96
+ - slinky
97
+ extensions: []
98
+
99
+ extra_rdoc_files:
100
+ - LICENSE.txt
101
+ - README.md
102
+ files:
103
+ - .document
104
+ - Gemfile
105
+ - LICENSE.txt
106
+ - README.md
107
+ - Rakefile
108
+ - VERSION
109
+ - bin/slinky
110
+ - lib/slinky.rb
111
+ - lib/slinky/compiled_file.rb
112
+ - lib/slinky/compilers/coffee-compiler.rb
113
+ - lib/slinky/compilers/coffee-helper
114
+ - lib/slinky/compilers/haml-compiler.rb
115
+ - lib/slinky/compilers/sass-compiler.rb
116
+ - lib/slinky/em-popen3.rb
117
+ - lib/slinky/runner.rb
118
+ - lib/slinky/server.rb
119
+ - slinky.gemspec
120
+ - spec/slinky_spec.rb
121
+ - spec/spec_helper.rb
122
+ has_rdoc: true
123
+ homepage: http://github.com/mwylde/slinky
124
+ licenses:
125
+ - MIT
126
+ post_install_message:
127
+ rdoc_options: []
128
+
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ none: false
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ hash: -191776360948002665
137
+ segments:
138
+ - 0
139
+ version: "0"
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ none: false
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: "0"
146
+ requirements: []
147
+
148
+ rubyforge_project:
149
+ rubygems_version: 1.5.2
150
+ signing_key:
151
+ specification_version: 3
152
+ summary: Static file server for javascript apps
153
+ test_files:
154
+ - spec/slinky_spec.rb
155
+ - spec/spec_helper.rb