slinky 0.7.3 → 0.8.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.
@@ -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