slinky 0.7.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  module Slinky
2
2
  module ProxyServer
3
3
  HTTP_MATCHER = /(GET|POST|PUT|DELETE|HEAD) (.+?)(?= HTTP)/
4
- HOST_MATCHER = /Host: (.+)/
4
+ HOST_MATCHER = /Host: (\S+)/
5
5
 
6
6
  def self.process_proxies proxy_hash
7
7
  proxy_hash.map{|from, h|
data/lib/slinky/runner.rb CHANGED
@@ -70,7 +70,7 @@ module Slinky
70
70
  EM::run {
71
71
  @config ||= Config.empty
72
72
 
73
- Slinky::Server.dir = @options[:src_dir]
73
+ Slinky::Server.dir = @options[:src_dir] || @config.src_dir
74
74
  Slinky::Server.config = @config
75
75
  manifest = Manifest.new(Slinky::Server.dir,
76
76
  Slinky::Server.config)
data/lib/slinky/server.rb CHANGED
@@ -2,6 +2,11 @@ module Slinky
2
2
  module Server
3
3
  include EM::HttpServer
4
4
 
5
+ INJECT_CSS = File.read("#{File.dirname(__FILE__)}/templates/inject.css")
6
+ ERROR_CSS = File.read("#{File.dirname(__FILE__)}/templates/error.css")
7
+ ERROR_HAML = File.read("#{File.dirname(__FILE__)}/templates/error.haml")
8
+ ERROR_JS = File.read("#{File.dirname(__FILE__)}/templates/error.js")
9
+
5
10
  # Sets the root directory from which files should be served
6
11
  def self.dir= _dir; @dir = _dir; end
7
12
  # Gets the root directory from which files should be served
@@ -59,17 +64,48 @@ module Slinky
59
64
  resp
60
65
  end
61
66
 
67
+ # Produces nice error output for various kinds of formats
68
+ def self.format_error_output resp, mf, error
69
+ resp.content =
70
+ case Pathname.new(mf.output_path).extname
71
+ when ".html"
72
+ Haml::Engine.new(ERROR_HAML).
73
+ render(Object.new, errors: error.messages, css: ERROR_CSS)
74
+ when ".css"
75
+ resp.status = 200 # browsers ignore 500'd css
76
+ INJECT_CSS.gsub("{REPLACE_ERRORS}",
77
+ error.messages.join("\n").gsub("'", "\""))
78
+ when ".js"
79
+ resp.status = 200 # browsers ignore 500'd js
80
+ ERROR_JS
81
+ .gsub("{REPLACE_CSS}",
82
+ JSON.dump({css: ERROR_CSS.gsub("\n", "")}))
83
+ .gsub("{REPLACE_ERRORS}", JSON.dump(error.messages))
84
+ else
85
+ error.message
86
+ end
87
+ end
88
+
62
89
  # Takes a manifest file and produces a response for it
63
90
  def self.handle_file resp, mf, compile = true
64
91
  begin
65
- if path = mf.process(nil, compile)
66
- serve_file resp, path.to_s
92
+ path = mf.process(nil, compile)
93
+ serve_file resp, path.to_s
94
+ rescue SlinkyError => e
95
+ resp.status = 500
96
+ if self.config.enable_browser_errors
97
+ format_error_output(resp, mf, e)
67
98
  else
68
- raise StandardError.new
99
+ resp.content = e.message
69
100
  end
70
- rescue
101
+ e.messages.each{|m|
102
+ $stderr.puts(m.foreground(:red))
103
+ }
104
+ rescue => e
71
105
  resp.status = 500
72
- resp.content = "Error compiling #{mf.source}\n"
106
+ format_error_output(resp, mf, SlinkyError.new(
107
+ "Unknown error handling #{mf.source}: #{$!}\n"))
108
+ $stderr.puts("Unknown error handling #{mf.source}: #{$!}".foreground(:red))
73
109
  end
74
110
  resp
75
111
  end
@@ -0,0 +1,32 @@
1
+ #slinky-error {
2
+ font-family: Helvetica, Arial, Sans-Serif;
3
+ }
4
+
5
+ #slinky-error .slinky-header {
6
+ position: absolute;
7
+ top: 0;
8
+ left: 0;
9
+ right: 0;
10
+ height: 100px;
11
+ background-color: rgb(141, 55, 55);
12
+ text-align: center;
13
+ font-size: 1.5em;
14
+ line-height: 1.5em;
15
+ color: whitesmoke;
16
+ text-shadow: -1px -1px rgba(0, 0, 0, 0.2)
17
+ }
18
+
19
+ #slinky-error .slinky-body {
20
+ position: absolute;
21
+ top: 100px; left: 0; right: 0;
22
+ padding: 0 50px;
23
+ padding-bottom: 20px;
24
+ font-size: 1.25em;
25
+ color: black;
26
+ background-color: whitesmoke;
27
+ border-bottom: 1px solid #aaa;
28
+ }
29
+
30
+ #slinky-error .slinky-body li {
31
+ padding-top: 20px;
32
+ }
@@ -0,0 +1,13 @@
1
+ !!!5
2
+ %html
3
+ %head
4
+ %title Slinky encountered an error
5
+ %style{:type => "text/css"}= css
6
+ %body
7
+ #slinky-error
8
+ .slinky-header
9
+ %h1 Oh no! Build error!
10
+ .slinky-body
11
+ %ul
12
+ - errors.each do |error|
13
+ %li= error
@@ -0,0 +1,26 @@
1
+ (function(errors) {
2
+ var domLoaded = function() {
3
+ var css = {REPLACE_CSS};
4
+ var cssEl = document.createElement("style");
5
+ cssEl.type = "text/css";
6
+ cssEl.innerText = css.css;
7
+ document.head.appendChild(cssEl);
8
+
9
+ var el = document.getElementById("slinky-error");
10
+ if(el == null) {
11
+ el = document.createElement("div");
12
+ el.id = "slinky-error";
13
+ el.innerHTML = '<div class="slinky-header"><h1>Oh no! Build'
14
+ + ' error!</h1></div><div class="slinky-body"><ul></ul></div>';
15
+ document.body.appendChild(el);
16
+ }
17
+
18
+ var ul = el.getElementsByTagName("ul")[0];
19
+ errors.forEach(function(error) {
20
+ var li = document.createElement("li");
21
+ li.innerText = error;
22
+ ul.appendChild(li);
23
+ });
24
+ };
25
+ document.addEventListener("DOMContentLoaded", domLoaded, false);
26
+ })({REPLACE_ERRORS});
@@ -0,0 +1,27 @@
1
+ body:before {
2
+ font-family: Helvetica, Arial, Sans-Serif;
3
+ content: "oh no! build error!";
4
+ position: absolute;
5
+ top: 0;
6
+ left: 0;
7
+ right: 0;
8
+ height: 100px;
9
+ background-color: rgb(141, 55, 55);
10
+ text-align: center;
11
+ font-size: 2.5em;
12
+ line-height: 2.5em;
13
+ color: whitesmoke;
14
+ text-shadow: -1px -1px rgba(0, 0, 0, 0.2)
15
+ }
16
+
17
+ body:after {
18
+ font-family: Helvetica, Arial, Sans-Serif;
19
+ content: '{REPLACE_ERRORS}';
20
+ position: absolute;
21
+ top: 100px; left: 0; right: 0;
22
+ padding: 20px 50px;
23
+ font-size: 1.25em;
24
+ color: black;
25
+ background-color: whitesmoke;
26
+ border-bottom: 1px solid #aaa;
27
+ }
data/slinky.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: slinky 0.7.3 ruby lib
5
+ # stub: slinky 0.8.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "slinky"
9
- s.version = "0.7.3"
9
+ s.version = "0.8.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Micah Wylde"]
14
- s.date = "2014-04-10"
14
+ s.date = "2014-07-19"
15
15
  s.description = "A static file server for rich web apps that automatically compiles SASS, HAML, CoffeeScript and more"
16
16
  s.email = "micah@micahw.com"
17
17
  s.executables = ["slinky"]
@@ -36,18 +36,26 @@ Gem::Specification.new do |s|
36
36
  "lib/slinky/compilers/clojurescript-compiler.rb",
37
37
  "lib/slinky/compilers/coffee-compiler.rb",
38
38
  "lib/slinky/compilers/haml-compiler.rb",
39
+ "lib/slinky/compilers/jsx-compiler.rb",
39
40
  "lib/slinky/compilers/less-compiler.rb",
40
41
  "lib/slinky/compilers/sass-compiler.rb",
41
42
  "lib/slinky/config_reader.rb",
42
43
  "lib/slinky/em-popen3.rb",
44
+ "lib/slinky/errors.rb",
45
+ "lib/slinky/graph.rb",
43
46
  "lib/slinky/listener.rb",
44
47
  "lib/slinky/live_reload.rb",
45
48
  "lib/slinky/manifest.rb",
46
49
  "lib/slinky/proxy_server.rb",
47
50
  "lib/slinky/runner.rb",
48
51
  "lib/slinky/server.rb",
52
+ "lib/slinky/templates/error.css",
53
+ "lib/slinky/templates/error.haml",
54
+ "lib/slinky/templates/error.js",
55
+ "lib/slinky/templates/inject.css",
49
56
  "slinky.gemspec",
50
57
  "spec/compilers_spec.rb",
58
+ "spec/manifest_spec.rb",
51
59
  "spec/slinky_spec.rb",
52
60
  "spec/spec_helper.rb"
53
61
  ]
@@ -60,6 +68,7 @@ Gem::Specification.new do |s|
60
68
  s.specification_version = 4
61
69
 
62
70
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
71
+ s.add_runtime_dependency(%q<multi_json>, ["~> 1.9.2"])
63
72
  s.add_runtime_dependency(%q<eventmachine>, ["~> 1.0"])
64
73
  s.add_runtime_dependency(%q<eventmachine_httpserver>, ["~> 0.2"])
65
74
  s.add_runtime_dependency(%q<em-websocket>, ["~> 0.3"])
@@ -80,7 +89,9 @@ Gem::Specification.new do |s|
80
89
  s.add_development_dependency(%q<simplecov>, [">= 0"])
81
90
  s.add_development_dependency(%q<less>, [">= 2.2.0"])
82
91
  s.add_development_dependency(%q<therubyracer>, [">= 0"])
92
+ s.add_development_dependency(%q<react-jsx>, ["~> 0.8.0"])
83
93
  else
94
+ s.add_dependency(%q<multi_json>, ["~> 1.9.2"])
84
95
  s.add_dependency(%q<eventmachine>, ["~> 1.0"])
85
96
  s.add_dependency(%q<eventmachine_httpserver>, ["~> 0.2"])
86
97
  s.add_dependency(%q<em-websocket>, ["~> 0.3"])
@@ -101,8 +112,10 @@ Gem::Specification.new do |s|
101
112
  s.add_dependency(%q<simplecov>, [">= 0"])
102
113
  s.add_dependency(%q<less>, [">= 2.2.0"])
103
114
  s.add_dependency(%q<therubyracer>, [">= 0"])
115
+ s.add_dependency(%q<react-jsx>, ["~> 0.8.0"])
104
116
  end
105
117
  else
118
+ s.add_dependency(%q<multi_json>, ["~> 1.9.2"])
106
119
  s.add_dependency(%q<eventmachine>, ["~> 1.0"])
107
120
  s.add_dependency(%q<eventmachine_httpserver>, ["~> 0.2"])
108
121
  s.add_dependency(%q<em-websocket>, ["~> 0.3"])
@@ -123,6 +136,7 @@ Gem::Specification.new do |s|
123
136
  s.add_dependency(%q<simplecov>, [">= 0"])
124
137
  s.add_dependency(%q<less>, [">= 2.2.0"])
125
138
  s.add_dependency(%q<therubyracer>, [">= 0"])
139
+ s.add_dependency(%q<react-jsx>, ["~> 0.8.0"])
126
140
  end
127
141
  end
128
142
 
@@ -90,4 +90,19 @@ eos
90
90
  }
91
91
  end
92
92
  end
93
+
94
+ context "JSXCompiler" do
95
+ it "should be able to compile .jsx files" do
96
+ src = <<-EOF
97
+ /** @jsx React.DOM */
98
+ React.renderComponent(
99
+ <h1>Hello, world!</h1>,
100
+ document.getElementById('example')
101
+ );
102
+ EOF
103
+ compiler_test("/compilers/test.jsx", ".js", src){|s|
104
+ s.include?("Hello, world!")
105
+ }
106
+ end
107
+ end
93
108
  end
@@ -0,0 +1,750 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Manifest" do
4
+ before :each do
5
+ @mprod = Slinky::Manifest.new("/src", @config, :devel => false, :build_to => "/build")
6
+ @mdevel = Slinky::Manifest.new("/src", @config)
7
+ @md_prod = @mprod.manifest_dir
8
+ @md_devel = @mdevel.manifest_dir
9
+ end
10
+
11
+ context "General" do
12
+ it "should build manifest dir with all files in current dir" do
13
+ @md_prod.files.collect{|f| f.source}.should == ["/src/test.haml"]
14
+ @md_devel.files.collect{|f| f.source}.should == ["/src/test.haml"]
15
+ end
16
+
17
+ it "should build manifest with all files in fs" do
18
+ @mprod.files.collect{|f|
19
+ f.source
20
+ }.sort.should == @files.collect{|x| "/src/" + x}.sort
21
+ @mdevel.files.collect{|f|
22
+ f.source
23
+ }.sort.should == @files.collect{|x| "/src/" + x}.sort
24
+ end
25
+
26
+ it "should not include dot files" do
27
+ File.open("/src/.index.haml.swp", "w+"){|f| f.write("x")}
28
+ manifest = Slinky::Manifest.new("/src", @config)
29
+ manifest.files.map{|x| x.source}.include?("/src/.index.haml.swp").should == false
30
+ end
31
+
32
+ it "should be able to compute a hash for the entire manifest" do
33
+ m = @mdevel
34
+ hash1 = m.md5
35
+ File.open("/src/hello.html", "w+") {|f| f.write("Hell!") }
36
+ $stdout.should_receive(:puts).with("Compiled /src/test.haml".foreground(:green)).exactly(2).times
37
+ m.add_all_by_path(["/src/hello.html"])
38
+ m.md5.should_not == hash1
39
+ end
40
+
41
+ it "should find files in the manifest by path" do
42
+ @mdevel.find_by_path("test.haml").first.source.should == "/src/test.haml"
43
+ @mdevel.find_by_path("asdf.haml").first.should == nil
44
+ @mdevel.find_by_path("l1/l2/test.txt").first.source.should == "/src/l1/l2/test.txt"
45
+ @mdevel.find_by_path("l1/test.css").first.source.should == "/src/l1/test.sass"
46
+ l1 = @mdevel.manifest_dir.children.find{|c| c.dir == "/src/l1"}
47
+ l1.find_by_path("../test.haml").first.source.should == "/src/test.haml"
48
+ end
49
+
50
+ it "should produce the correct scripts string for production" do
51
+ @mprod.scripts_string.should match \
52
+ %r!<script type="text/javascript" src="/scripts.js\?\d+"></script>!
53
+ end
54
+
55
+ it "should produce the correct scripts string for devel" do
56
+ @mdevel.scripts_string.should == [
57
+ '<script type="text/javascript" src="/l1/test5.js"></script>',
58
+ '<script type="text/javascript" src="/l1/l2/test6.js"></script>',
59
+ '<script type="text/javascript" src="/l1/test2.js"></script>',
60
+ '<script type="text/javascript" src="/l1/l2/test3.js"></script>',
61
+ '<script type="text/javascript" src="/l1/test.js"></script>'].join("\n")
62
+ end
63
+
64
+ it "should produce the correct styles string for production" do
65
+ @mprod.styles_string.should match \
66
+ %r!<link rel="stylesheet" href="/styles.css\?\d+" />!
67
+ end
68
+
69
+ it "should produce the correct styles string for development" do
70
+ File.open("/src/l1/l2/bad.sass", "w+"){|f|
71
+ f.write "require('../test.sass')\ncolor: red;"
72
+ }
73
+ manifest = Slinky::Manifest.new("/src", @config)
74
+ @mdevel.styles_string.should == [
75
+ '<link rel="stylesheet" href="/l1/test.css" />',
76
+ '<link rel="stylesheet" href="/l1/l2/test2.css" />'].join("\n")
77
+ end
78
+
79
+ it "should allow the creation of ManifestFiles" do
80
+ mf = Slinky::ManifestFile.new("/src/test.haml", "", @mprod)
81
+ end
82
+
83
+ it "should correctly determine output_path" do
84
+ mf = Slinky::ManifestFile.new("/src/test.haml", "/src/build", @mprod)
85
+ mf.output_path.to_s.should == "/src/test.html"
86
+ mf = Slinky::ManifestFile.new("/src/l1/test.js", "/src/build", @mprod)
87
+ mf.output_path.to_s.should == "/src/l1/test.js"
88
+ end
89
+
90
+ it "should correctly determine relative_output_path" do
91
+ mf = Slinky::ManifestFile.new("/src/test.haml", "/src/build", @mprod)
92
+ mf.relative_output_path.to_s.should == "test.html"
93
+ mf = Slinky::ManifestFile.new("/src/l1/test.js", "/src/build", @mprod)
94
+ mf.relative_output_path.to_s.should == "l1/test.js"
95
+ end
96
+
97
+ it "should build tmp file without directives" do
98
+ original = "/src/test.haml"
99
+ mf = Slinky::ManifestFile.new("/src/test.haml", "/src/build", @mprod)
100
+ path = mf.handle_directives mf.source
101
+ File.read(original).match(/slinky_scripts|slinky_styles/).should_not == nil
102
+ File.read(path).match(/slinky_scripts|slinky_styles/).should == nil
103
+ end
104
+
105
+ it "should build tmp file without directives for .js" do
106
+ original = "/src/l1/test.js"
107
+ mf = Slinky::ManifestFile.new("/src/l1/test.js", "/src/build", @mprod)
108
+ path = mf.handle_directives mf.source
109
+ File.read(original).match(/slinky_require/).should_not == nil
110
+ File.read(path).match(/slinky_require/).should == nil
111
+ end
112
+
113
+ it "should compile files that need it" do
114
+ $stdout.should_receive(:puts).with("Compiled /src/test.haml".foreground(:green))
115
+ mf = Slinky::ManifestFile.new("/src/test.haml", "/src/build", @mprod)
116
+ FileUtils.mkdir("/src/build")
117
+ build_path = mf.process mf.build_to
118
+ build_path.to_s.split(".")[-1].should == "html"
119
+ File.read("/src/test.haml").match("<head>").should == nil
120
+ File.read(build_path).match("<head>").should_not == nil
121
+
122
+ $stdout.should_receive(:puts).with("Compiled /src/test.haml".foreground(:green))
123
+ mf = Slinky::ManifestFile.new("/src/test.haml", "/src/build", @mprod)
124
+ build_path = mf.process "/src/build/test.html"
125
+ File.read("/src/build/test.html").match("<head>").should_not == nil
126
+ FileUtils.rm_rf("/src/build") rescue nil
127
+ end
128
+
129
+ it "should give the proper error message for compilers with unmet dependencies" do
130
+ File.open("/src/test.fake", "w+"){|f| f.write("hello, fake data")}
131
+ $stderr.should_receive(:puts).with(/Missing dependency/)
132
+ mf = Slinky::ManifestFile.new("/src/test.fake", "/src/build", @mprod)
133
+ build_path = mf.process
134
+ end
135
+
136
+ it "should report errors for bad files" do
137
+ File.open("/src/l1/l2/bad.sass", "w+"){|f|
138
+ f.write "color: red;"
139
+ }
140
+ mf = Slinky::ManifestFile.new("/src/l1/l2/bad.sass", "/src/build", @mprod)
141
+ expect { mf.process }.to raise_error Slinky::BuildFailedError
142
+ end
143
+
144
+ it "shouldn't crash on syntax errors" do
145
+ File.open("/src/l1/asdf.haml", "w+"){|f|
146
+ f.write("%h1{:width => 50px}")
147
+ }
148
+ mf = Slinky::ManifestFile.new("/src/l1/asdf.haml", "/src/build", @mprod)
149
+ expect { mf.process }.to raise_error Slinky::BuildFailedError
150
+ end
151
+
152
+ it "should properly determine build directives" do
153
+ mf = Slinky::ManifestFile.new("/src/test.haml", "/src/build", @mprod)
154
+ mf.find_directives.should == {:slinky_scripts => [], :slinky_styles => []}
155
+ mf = Slinky::ManifestFile.new("/src/l1/test.js", "/src/build", @mprod)
156
+ mf.find_directives.should == {:slinky_require => ["test2.js", "l2/test3.js"]}
157
+ end
158
+
159
+ it "should properly determine build_to path" do
160
+ mf = Slinky::ManifestFile.new("/src/test.haml", "/src/build", @mprod)
161
+ mf.build_to.should == Pathname.new("/src/build/test.html")
162
+ mf = Slinky::ManifestFile.new("/src/l1/test.js", "/src/build", @mprod)
163
+ mf.build_to.should == Pathname.new("/src/build/test.js")
164
+ end
165
+
166
+ it "should build both compiled files and non-compiled files" do
167
+ $stdout.should_receive(:puts).with("Compiled /src/test.haml".foreground(:green))
168
+ mf = Slinky::ManifestFile.new("/src/test.haml", "/src/build", @mprod)
169
+ path = mf.build
170
+ path.to_s.should == "/src/build/test.html"
171
+ File.read("/src/test.haml").match("<head>").should == nil
172
+ File.read(path).match("<head>").should_not == nil
173
+
174
+ $stdout.should_not_receive(:puts)
175
+ mf = Slinky::ManifestFile.new("/src/l1/l2/test.txt", "/src/build/l1/l2", @mprod)
176
+ path = mf.build
177
+ path.to_s.should == "/src/build/l1/l2/test.txt"
178
+ File.read("/src/build/l1/l2/test.txt").should == "hello\n"
179
+ end
180
+
181
+ it "should match files" do
182
+ mf = Slinky::ManifestFile.new("/src/l1/l2/test.txt", "/src/build/l1/l2", @mprod)
183
+ mf.matches?("test.txt").should == true
184
+ mf = Slinky::ManifestFile.new("/src/l1/test.sass", "", @mprod)
185
+ mf.matches?("test.css").should == true
186
+ mf.matches?("test.sass").should == true
187
+ end
188
+
189
+ it "should match file paths" do
190
+ mf = Slinky::ManifestFile.new("/src/l1/l2/test.txt", "/src/build/l1/l2", @mprod)
191
+ mf.matches_path?("test.txt").should == false
192
+ mf.matches_path?("/l1/l2/test.txt").should == true
193
+ mf.matches_path?("/l1/l2/*.txt").should == false
194
+ mf.matches_path?("/l1/l2/*.txt", true).should == true
195
+ mf = Slinky::ManifestFile.new("/src/l1/test.sass", "", @mprod)
196
+ mf.matches_path?("/l1/test.css").should == true
197
+ mf.matches_path?("/l1/test.sass").should == true
198
+ mf.matches_path?("/l1/*.css", true).should == true
199
+ mf.matches_path?("/l1/*.sass", true).should == true
200
+ mf.matches_path?("l1/test.sass").should == false
201
+ end
202
+
203
+ it "should should properly determine if in tree" do
204
+ mf = Slinky::ManifestFile.new("/src/l1/l2/test.txt", "/src/build/l1/l2", @mprod)
205
+ mf.in_tree?("/l1").should == true
206
+ mf.in_tree?("/l1/l2").should == true
207
+ mf.in_tree?("/l1/l2/test.txt").should == true
208
+ mf.in_tree?("/l1/l3").should == false
209
+ mf.in_tree?("test.txt").should == false
210
+ end
211
+
212
+ it "should correctly build the dependency graph" do
213
+ @mprod.dependency_graph.collect{|x| x.collect{|y| y.source}}.sort.should ==
214
+ [["/src/l1/test2.js", "/src/l1/test.js"],
215
+ ["/src/l1/l2/test3.coffee", "/src/l1/test.js"],
216
+ ["/src/l1/test5.js", "/src/l1/test2.js"],
217
+ ["/src/l1/l2/test6.js", "/src/l1/l2/test3.coffee"]].sort
218
+ end
219
+
220
+ it "should fail if a required file isn't in the manifest" do
221
+ FileUtils.rm("/src/l1/test2.js")
222
+ manifest = Slinky::Manifest.new("/src", @config, :devel => false, :build_to => "/build")
223
+ proc {
224
+ manifest.dependency_graph
225
+ }.should raise_error Slinky::FileNotFoundError
226
+ end
227
+
228
+ it "should build a correct dependency list" do
229
+ @mprod.dependency_list.collect{|x| x.source}.should == ["/src/test.haml", "/src/l1/test.sass", "/src/l1/test5.js", "/src/l1/l2/test.txt", "/src/l1/l2/test2.css", "/src/l1/l2/test6.js", "/src/l1/l2/l3/test2.txt", "/src/l1/test2.js", "/src/l1/l2/test3.coffee", "/src/l1/test.js"]
230
+ end
231
+
232
+ it "should fail if there is a cycle in the dependency graph" do
233
+ File.open("/src/l1/test5.js", "w+"){|f| f.write("slinky_require('test.js')")}
234
+ manifest = Slinky::Manifest.new("/src", @config, :devel => false, :build_to => "/build")
235
+ proc { manifest.dependency_list }.should raise_error Slinky::DependencyError
236
+ end
237
+
238
+ it "should handle depends directives" do
239
+ File.open("/src/l1/test5.coffee", "w+"){|f| f.write("slinky_depends('test.sass')")}
240
+ manifest = Slinky::Manifest.new("/src", @config, :devel => true)
241
+ f = manifest.find_by_path("l1/test5.js").first
242
+ f.should_not == nil
243
+ $stdout.should_receive(:puts).with(/Compiled \/src\/l1\/test.sass/)
244
+ $stdout.should_receive(:puts).with(/Compiled \/src\/l1\/test5.coffee/)
245
+ f.process
246
+ end
247
+
248
+ it "should handle depends directives with glob patterns" do
249
+ File.open("/src/l1/test5.coffee", "w+"){|f| f.write("slinky_depends('*.sass')")}
250
+ File.open("/src/l1/test2.sass", "w+"){|f| f.write("body\n\tcolor: red")}
251
+ manifest = Slinky::Manifest.new("/src", @config, :devel => true)
252
+ f = manifest.find_by_path("l1/test5.js").first
253
+ f.should_not == nil
254
+ $stdout.should_receive(:puts).with(/Compiled \/src\/l1\/test.sass/)
255
+ $stdout.should_receive(:puts).with(/Compiled \/src\/l1\/test2.sass/)
256
+ $stdout.should_receive(:puts).with(/Compiled \/src\/l1\/test5.coffee/)
257
+ f.process
258
+ end
259
+
260
+ it "should handle depends directives with infinite loops" do
261
+ File.open("/src/l1/test5.coffee", "w+"){|f| f.write("slinky_depends('*.sass')")}
262
+ File.open("/src/l1/test2.sass", "w+"){|f| f.write("/* slinky_depends('*.coffee')")}
263
+ manifest = Slinky::Manifest.new("/src", @config, :devel => true)
264
+ f = manifest.find_by_path("l1/test5.js").first
265
+ f.should_not == nil
266
+ $stdout.should_receive(:puts).with(/Compiled \/src\/l1\/test.sass/)
267
+ $stdout.should_receive(:puts).with(/Compiled \/src\/l1\/test2.sass/)
268
+ $stdout.should_receive(:puts).with(/Compiled \/src\/l1\/test5.coffee/)
269
+ f.process
270
+ end
271
+
272
+ it "should handle depends directives in config" do
273
+ cf "/depend/script/vendor/backbone.js"
274
+ cf "/depend/script/vendor/jquery.js"
275
+ cf "/depend/script/vendor/underscore.js"
276
+
277
+ config = <<eos
278
+ dependencies:
279
+ "/script/vendor/backbone.js":
280
+ - "/script/vendor/jquery.js"
281
+ - "/script/vendor/underscore.js"
282
+ eos
283
+ config = Slinky::ConfigReader.new(config)
284
+
285
+ mdevel = Slinky::Manifest.new("/depend", config, :devel => true)
286
+
287
+ files = mdevel.files_for_product("/scripts.js").map{|x| x.source}
288
+ files[2].should == "/depend/script/vendor/backbone.js"
289
+ end
290
+
291
+ it "should cache files" do
292
+ File.open("/src/l1/cache.coffee", "w+"){|f| f.write("() -> 'hello, world!'\n")}
293
+ manifest = Slinky::Manifest.new("/src", @config, :devel => true)
294
+ f = manifest.find_by_path("l1/cache.js").first
295
+ $stdout.should_receive(:puts).with(/Compiled \/src\/l1\/cache.coffee/)
296
+ f.process
297
+ f.process
298
+ File.open("/src/l1/cache.coffee", "a"){|f| f.write("() -> 'goodbye, world!'\n")}
299
+ $stdout.should_receive(:puts).with(/Compiled \/src\/l1\/cache.coffee/)
300
+ f.process
301
+ end
302
+
303
+ it "should handle new directives" do
304
+ manifest = Slinky::Manifest.new("/src", @config, :devel => true)
305
+ f = manifest.find_by_path("l1/test.js").first
306
+ f.process
307
+ f.directives.should == {:slinky_require=>["test2.js", "l2/test3.js"]}
308
+ File.open("/src/l1/test.js", "a"){|f| f.write("slinky_require('test5.js')\n")}
309
+ f.process
310
+ f.directives.should == {:slinky_require=>["test2.js", "l2/test3.js", "test5.js"]}
311
+ end
312
+
313
+ it "should detect new files" do
314
+ $stdout.should_receive(:puts).with(/Compiled \/src\/test.haml/)
315
+ manifest = Slinky::Manifest.new("/src", @config, :devel => true)
316
+ File.open("/src/l1/cache.coffee", "w+"){|f| f.write("console.log 'hello'")}
317
+ manifest.add_all_by_path(["/src/l1/cache.coffee"])
318
+ f = manifest.find_by_path("l1/cache.js").first
319
+ f.should_not == nil
320
+ manifest.scripts_string.match("cache.js").should_not == nil
321
+ FileUtils.mkdir("/src/l1/hello")
322
+ File.open("/src/l1/hello/asdf.sass", "w+"){|f| f.write("hello")}
323
+ f = manifest.find_by_path("l1/hello/asdf.sass")
324
+ f.should_not == nil
325
+ end
326
+
327
+ it "should handle deletion of files" do
328
+ File.open("/src/l1/cache.coffee", "w+"){|f| f.write("console.log 'hello'")}
329
+ $stdout.should_receive(:puts).with(/Compiled \/src\/test.haml/)
330
+ manifest = Slinky::Manifest.new("/src", @config, :devel => true)
331
+ f = manifest.find_by_path("l1/cache.coffee").first
332
+ f.should_not == nil
333
+ manifest.scripts_string.match("l1/cache.js").should_not == nil
334
+
335
+ FileUtils.rm("/src/l1/cache.coffee")
336
+ File.exists?("/src/l1/cache.coffee").should == false
337
+ manifest.remove_all_by_path(["/src/l1/cache.coffee"])
338
+ f = manifest.find_by_path("l1/cache.coffee").first
339
+ f.should == nil
340
+ manifest.scripts_string.match("l1/cache.js").should == nil
341
+ end
342
+
343
+ it "should ignore the build directory" do
344
+ $stdout.should_receive(:puts).with(/Compiled \/src\/.+/).exactly(6).times
345
+ options = {:src_dir => "/src", :build_dir => "/src/build"}
346
+ Slinky::Builder.build(options, @config)
347
+ File.exists?("/src/build/build").should_not == true
348
+ File.exists?("/src/build/test.html").should == true
349
+ Slinky::Builder.build(options, @config)
350
+ File.exists?("/src/build/build").should == false
351
+ end
352
+
353
+ it "should combine and compress javascript" do
354
+ FileUtils.rm_rf("/build") rescue nil
355
+ $stdout.should_receive(:puts).with(/Compiled \/src\/.+/).exactly(3).times
356
+ @mprod.build
357
+ File.exists?("/build/scripts.js").should == true
358
+ File.size("/build/scripts.js").should > 90
359
+ File.read("/build/scripts.js").match('"Hello, world"').should_not == nil
360
+ File.exists?("/build/l1/test.js").should == false
361
+ end
362
+
363
+ it "should combine and compress css" do
364
+ $stdout.should_receive(:puts).with(/Compiled \/src\/.+/).exactly(3).times
365
+ @mprod.build
366
+ File.exists?("/build/styles.css").should == true
367
+ File.size("/build/styles.css").should > 25
368
+ File.read("/build/styles.css").match(/color:\s*red/).should_not == nil
369
+ File.exists?("/build/l1/test.css").should == false
370
+ end
371
+
372
+ it "should modify css urls to point to correct paths when compiled" do
373
+ $stdout.should_receive(:puts).with(/Compiled \/src\/.+/).exactly(3).times
374
+ @mprod.build
375
+ css = File.read("/build/styles.css")
376
+ css.include?("url('/l1/asdf.png')").should == true
377
+ css.include?("url('/l1/bg.png')").should == true
378
+ css.include?("url('/l1/l2/l3/hello.png')").should == true
379
+ end
380
+
381
+ it "should properly filter out ignores in files list" do
382
+ config = Slinky::ConfigReader.new("ignore:\n - /l1/test2.js")
383
+ config.ignore.should == ["/l1/test2.js"]
384
+ mdevel = Slinky::Manifest.new("/src", config)
385
+ mfiles = mdevel.files(false).map{|x| x.source}.sort
386
+ files = (@files - ["l1/test2.js"]).map{|x| "/src/" + x}.sort
387
+ mfiles.should == files
388
+ # double check
389
+ mfiles.include?("/src/l1/test2.js").should == false
390
+ end
391
+
392
+ it "should properly filter out relative ignores in files list" do
393
+ config = Slinky::ConfigReader.new("ignore:\n - l1/test2.js")
394
+ config.ignore.should == ["l1/test2.js"]
395
+ mdevel = Slinky::Manifest.new("/src", config)
396
+ mfiles = mdevel.files(false).map{|x| x.source}.sort
397
+ files = (@files - ["l1/test2.js"]).map{|x| "/src/" + x}.sort
398
+ mfiles.should == files
399
+ # double check
400
+ mfiles.include?("/src/l1/test2.js").should == false
401
+ end
402
+
403
+ it "should properly filter out directory ignores in files list" do
404
+ config = Slinky::ConfigReader.new("ignore:\n - /l1/l2")
405
+ config.ignore.should == ["/l1/l2"]
406
+ mdevel = Slinky::Manifest.new("/src", config)
407
+ mfiles = mdevel.files(false).map{|x| x.source}.sort
408
+ files = (@files.reject{|x| x.start_with?("l1/l2")}).map{|x| "/src/" + x}.sort
409
+ mfiles.should == files
410
+ end
411
+
412
+ it "should properly handle ignores for scripts" do
413
+ File.open("/src/l1/l2/ignore.js", "w+"){|f| f.write("IGNORE!!!")}
414
+ config = Slinky::ConfigReader.new("ignore:\n - /l1/l2/ignore.js")
415
+ config.ignore.should == ["/l1/l2/ignore.js"]
416
+
417
+ mdevel = Slinky::Manifest.new("/src", config)
418
+ mdevel.scripts_string.scan(/src=\"(.+?)\"/).flatten.
419
+ include?("/l1/l2/ignore.js").should == false
420
+
421
+ mprod = Slinky::Manifest.new("/src", config, :devel => false,
422
+ :build_to => "/build")
423
+
424
+ $stdout.should_receive(:puts).with(/Compiled \/src\/.+/).exactly(3).times
425
+ mprod.build
426
+
427
+ File.read("/build/scripts.js").include?("IGNORE!!!").should == false
428
+ end
429
+
430
+ it "should properly handle ignores for styles" do
431
+ File.open("/src/l1/l2/ignore.css", "w+"){|f| f.write("IGNORE!!!")}
432
+ config = Slinky::ConfigReader.new("ignore:\n - /l1/l2/ignore.css")
433
+ config.ignore.should == ["/l1/l2/ignore.css"]
434
+
435
+ mdevel = Slinky::Manifest.new("/src", config)
436
+ mdevel.styles_string.scan(/href=\"(.+?)\"/).flatten.
437
+ include?("/l1/l2/ignore.css").should == false
438
+
439
+ mprod = Slinky::Manifest.new("/src", config, :devel => false,
440
+ :build_to => "/build")
441
+
442
+ $stdout.should_receive(:puts).with(/Compiled \/src\/.+/).exactly(3).times
443
+ mprod.build
444
+
445
+ File.read("/build/styles.css").include?("IGNORE!!!").should == false
446
+ end
447
+ end
448
+
449
+ context "FindByPattern" do
450
+ def test(md, pattern, files)
451
+ md.find_by_pattern(pattern).map{|mf| mf.relative_source_path.to_s}
452
+ .sort.should == files.sort
453
+ end
454
+
455
+ before :all do
456
+ FileUtils.mkdir("/find")
457
+
458
+ cf("/find/main.js")
459
+ cf("/find/main/main.js")
460
+ cf("/find/main/test.js")
461
+ cf("/find/src/main.coffee")
462
+ cf("/find/src/main_test.js")
463
+ cf("/find/src/main/main.js")
464
+ cf("/find/src/component/main.js")
465
+ cf("/find/src/component/test.js")
466
+ cf("/find/src/component/test/test.js")
467
+
468
+ @md = Slinky::Manifest.new("/find", @config, :devel => false)
469
+ end
470
+
471
+ it "find_by_pattern should follow rule #1" do
472
+ test(@md, "/src/", [
473
+ "src/main.coffee",
474
+ "src/main_test.js",
475
+ "src/main/main.js",
476
+ "src/component/main.js",
477
+ "src/component/test.js",
478
+ "src/component/test/test.js"
479
+ ])
480
+ end
481
+
482
+ it "find_by_pattern should follow rule #2" do
483
+ test(@md, "test.js", ["main/test.js",
484
+ "src/component/test.js",
485
+ "src/component/test/test.js"])
486
+ end
487
+
488
+ it "find_by_pattern should follow rule #3" do
489
+ test(@md, "/main/*.js", [
490
+ "main/main.js",
491
+ "main/test.js"
492
+ ])
493
+ end
494
+
495
+ it "find_by_pattern should follow rule #4" do
496
+ test(@md, "main/", [
497
+ "main/main.js",
498
+ "main/test.js",
499
+ "src/main/main.js"
500
+ ])
501
+
502
+ test(@md, "main/main*", [
503
+ "main/main.js",
504
+ "src/main/main.js"
505
+ ])
506
+ end
507
+
508
+ it "find_by_pattern should follow rule #5" do
509
+ test(@md, "/*/*.js", [
510
+ "main/main.js",
511
+ "main/test.js",
512
+ "src/main.coffee",
513
+ "src/main_test.js"
514
+ ])
515
+
516
+ test(@md, "/*/*test.js", [
517
+ "main/test.js",
518
+ "src/main_test.js"
519
+ ])
520
+ end
521
+
522
+ it "find_by_pattern should follow rule #6" do
523
+ test(@md, "/**/*.js", [
524
+ "main.js",
525
+ "main/main.js",
526
+ "main/test.js",
527
+ "src/main.coffee",
528
+ "src/main_test.js",
529
+ "src/main/main.js",
530
+ "src/component/main.js",
531
+ "src/component/test.js",
532
+ "src/component/test/test.js"
533
+ ])
534
+
535
+ test(@md, "src/**/*.js", [
536
+ "src/main.coffee",
537
+ "src/main_test.js",
538
+ "src/main/main.js",
539
+ "src/component/main.js",
540
+ "src/component/test.js",
541
+ "src/component/test/test.js"
542
+ ])
543
+ end
544
+ end
545
+
546
+ context "Products" do
547
+ it "should fail if non-configured product is requested" do
548
+ proc { @mdevel.files_for_product("/something.js") }
549
+ .should raise_error Slinky::NoSuchProductError
550
+ end
551
+
552
+ it "should allow specification of products" do
553
+ config = <<eos
554
+ produce:
555
+ "/special.js":
556
+ include:
557
+ - "/l1/*.js"
558
+ exclude:
559
+ - "/l1/exclude.js"
560
+ eos
561
+ config = Slinky::ConfigReader.new(config)
562
+ config.produce.should == {"/special.js" => {
563
+ "include" => ["/l1/*.js"],
564
+ "exclude" => ["/l1/exclude.js"]
565
+ }}
566
+
567
+ File.open("/src/l1/exclude.js", "w+").close
568
+
569
+ mdevel = Slinky::Manifest.new("/src", config, :devel => true)
570
+
571
+ mdevel.files_for_product("/special.js")
572
+ .map{|x| x.source}.sort.should == [
573
+ "/src/l1/test2.js", "/src/l1/test.js", "/src/l1/test5.js",
574
+ "/src/l1/l2/test3.coffee", "/src/l1/l2/test6.js"].sort
575
+ end
576
+
577
+ it "should not require an exclude for products" do
578
+ config = {"produce" => {"/special.js" => {"include" => ["/l1/*.js"]}}}
579
+
580
+ config = Slinky::ConfigReader.new(config)
581
+
582
+ File.open("/src/l1/exclude.js", "w+").close
583
+
584
+ mdevel = Slinky::Manifest.new("/src", config, :devel => true)
585
+
586
+ mdevel.files_for_product("/special.js")
587
+ .map{|x| x.source}.sort.should == ["/src/l1/exclude.js",
588
+ "/src/l1/l2/test3.coffee",
589
+ "/src/l1/l2/test6.js",
590
+ "/src/l1/test.js",
591
+ "/src/l1/test2.js",
592
+ "/src/l1/test5.js"].sort
593
+ end
594
+
595
+ it "should support full match syntax for excludes" do
596
+ config = <<eos
597
+ produce:
598
+ "/special.js":
599
+ include:
600
+ - "/l1/*.js"
601
+ exclude:
602
+ - "exclude.js"
603
+ eos
604
+ config = Slinky::ConfigReader.new(config)
605
+ config.produce.should == {"/special.js" => {
606
+ "include" => ["/l1/*.js"],
607
+ "exclude" => ["exclude.js"]
608
+ }}
609
+
610
+ File.open("/src/l1/exclude.js", "w+").close
611
+
612
+ mdevel = Slinky::Manifest.new("/src", config, :devel => true)
613
+
614
+ mdevel.files_for_product("/special.js")
615
+ .map{|x| x.source}.sort.should == [
616
+ "/src/l1/test2.js", "/src/l1/test.js", "/src/l1/test5.js",
617
+ "/src/l1/l2/test3.coffee", "/src/l1/l2/test6.js"].sort
618
+ end
619
+
620
+ it "should fail if a product rule does not match anything" do
621
+ config = <<eos
622
+ produce:
623
+ "/special.js":
624
+ include:
625
+ - "/l1/doesntexist.js"
626
+ eos
627
+ config = Slinky::ConfigReader.new(config)
628
+
629
+ mdevel = Slinky::Manifest.new("/src", config, :devel => true)
630
+
631
+ proc { mdevel.files_for_product("/special.js") }
632
+ .should raise_error Slinky::FileNotFoundError
633
+ end
634
+
635
+ it "should allow product directives" do
636
+ config = <<eos
637
+ produce:
638
+ "/special.js":
639
+ include:
640
+ - "/l1/*.js"
641
+ eos
642
+ config = Slinky::ConfigReader.new(config)
643
+
644
+ File.open("/src/product.html", "w+"){|f|
645
+ f.write("<html><head>\nslinky_product('/special.js')\n</head></html")
646
+ }
647
+
648
+ mdevel = Slinky::Manifest.new("/src", config, :devel => true)
649
+ path = mdevel.find_by_path("/product.html").first.process(nil, true)
650
+
651
+ contents = File.read(path)
652
+ contents.include?("slinky_product").should == false
653
+ contents.include?('<script type="text/javascript" src="/l1/test.js">').
654
+ should == true
655
+ end
656
+
657
+ it "should full match syntax" do
658
+ config = {"produce" => {"/special.js" => {"include" => ["/l1/**/*.js"]}}}
659
+
660
+ config = Slinky::ConfigReader.new(config)
661
+
662
+ mdevel = Slinky::Manifest.new("/src", config, :devel => true)
663
+
664
+ mdevel.files_for_product("/special.js")
665
+ .map{|x| x.source}.sort.should == ["/src/l1/l2/test3.coffee",
666
+ "/src/l1/l2/test6.js",
667
+ "/src/l1/test.js",
668
+ "/src/l1/test2.js",
669
+ "/src/l1/test5.js"].sort
670
+ end
671
+
672
+ it "should build products in production mode" do
673
+ config = <<eos
674
+ produce:
675
+ "/special.js":
676
+ include:
677
+ - "/l1/*.js"
678
+ eos
679
+ config = Slinky::ConfigReader.new(config)
680
+
681
+ File.open("/src/product.html", "w+"){|f|
682
+ f.write("<html><head>\nslinky_product('/special.js')\n</head></html")
683
+ }
684
+
685
+ mprod = Slinky::Manifest.new("/src",
686
+ config,
687
+ :devel => false,
688
+ :build_to => "/build_special")
689
+
690
+ $stdout.should_receive(:puts).with(/Compiled/).exactly(2).times
691
+ path = mprod.find_by_path("/product.html").first.process(nil, true)
692
+ mprod.build
693
+
694
+ File.exists?("/build_special/special.js").should == true
695
+ File.read("/build_special/special.js").
696
+ include?('console.log("Hello!");').should == true
697
+ File.exists?("/build_special/scripts.js").should == false
698
+ File.exists?("/build_special/styles.css").should == false
699
+ end
700
+
701
+ it "should build products inside directories" do
702
+ config = <<eos
703
+ produce:
704
+ "/d1/d2/d3/special.js":
705
+ include:
706
+ - "/l1/*.js"
707
+ eos
708
+ config = Slinky::ConfigReader.new(config)
709
+
710
+ mprod = Slinky::Manifest.new("/src",
711
+ config,
712
+ :devel => false,
713
+ :build_to => "/build_nested")
714
+
715
+ $stdout.should_receive(:puts).with(/Compiled/).exactly(2).times
716
+ mprod.build
717
+
718
+ File.exists?("/build_nested/d1/d2/d3/special.js").should == true
719
+ end
720
+
721
+ it "should not build scripts if they are not included in products" do
722
+ config = <<eos
723
+ produce:
724
+ "/special.js":
725
+ include:
726
+ - "/l1/l2/test6.js"
727
+ eos
728
+ config = Slinky::ConfigReader.new(config)
729
+
730
+ File.open("/src/product.html", "w+"){|f|
731
+ f.write("<html><head>\nslinky_product('/special.js')\n</head></html")
732
+ }
733
+
734
+ mprod = Slinky::Manifest.new("/src",
735
+ config,
736
+ :devel => false,
737
+ :build_to => "/build_special")
738
+
739
+ $stdout.should_receive(:puts).with(/Compiled/).exactly(1).times
740
+ path = mprod.find_by_path("/product.html").first.process(nil, true)
741
+ mprod.build
742
+
743
+ File.exists?("/build_special/special.js").should == true
744
+ File.read("/build_special/special.js").
745
+ include?('console.log("Hello!");').should == false
746
+ File.exists?("/build_special/scripts.js").should == false
747
+ File.exists?("/build_special/styles.css").should == false
748
+ end
749
+ end
750
+ end