linner 0.1.1 → 0.1.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 96597b56da64c0335227695b713d6ca663a93192
4
- data.tar.gz: ac32c7dfd3bf23876cd8db939a4927a5b5fcf599
3
+ metadata.gz: 632ba0af08197976ece2c8920e3991868d0c5000
4
+ data.tar.gz: d1fe25a52c0a8af4edd46451b7cdbca1753530e2
5
5
  SHA512:
6
- metadata.gz: c4ffb6f987945e1aff80caf495e728a0013ac32cbfc200f86f4cbe65223f7a24b4ce3dfb23cd3f24240a95b963aead6829591f94fa7c7f91749e0edf6c3a3993
7
- data.tar.gz: d105dcab73e1f4b26a55f588e5d2aecb990dd1a9367e5086a8f59bd92ccfcd1dd9e7db1804163bb49cc682c87690b298f2cb6fdc48c6325efdff6b494735a110
6
+ metadata.gz: 2a5be3981994798ce60ca8b94af752b9bcbe13c2735dd99bc61416fc2e80544d63682628870cf53c90be190491cb9536869262b86e6995fe8d47ce4f7ea0afdb
7
+ data.tar.gz: 9c1eee6c25c28942848e0d9ca44b27d28e38b1c3e8986a379b0ee9fb4b30632af3abe8010414f44044e12a567957d2871e4086615ae3fdf61bd66fb807ad220d
data/.gitignore CHANGED
@@ -1,5 +1,6 @@
1
1
  *.gem
2
2
  *.rbc
3
+ .DS_Store
3
4
  .bundle
4
5
  .config
5
6
  .yardoc
@@ -2,11 +2,12 @@ require "linner/version"
2
2
  require "linner/command"
3
3
  require "linner/asset"
4
4
  require "linner/helper"
5
- require "linner/environment"
5
+ require "linner/reactor"
6
6
  require "linner/wrapper"
7
7
  require "linner/template"
8
8
  require "linner/notifier"
9
9
  require "linner/compressor"
10
+ require "linner/environment"
10
11
 
11
12
  module Linner
12
13
  extend self
@@ -33,8 +34,7 @@ module Linner
33
34
  Thread.new do
34
35
  dist = Asset.new(File.join environment.public_folder, dist)
35
36
  dist.content = ""
36
- matches = Dir.glob(File.join root, regex).uniq
37
- matches.sort_by_before_and_after(config["order"]["before"], config["order"]["after"]).each do |m|
37
+ Dir.glob(regex).uniq.sort_by_before_and_after(config["order"]["before"], config["order"]["after"]).each do |m|
38
38
  asset = Asset.new(m)
39
39
  content = asset.content
40
40
  if asset.wrappable?
@@ -51,11 +51,10 @@ module Linner
51
51
  def copy(config)
52
52
  config["copy"].each do |dist, regex|
53
53
  Thread.new do
54
- matches = Dir.glob(File.join root, regex)
55
- matches.each do |path|
54
+ Dir.glob(regex).each do |path|
56
55
  asset = Asset.new(path)
57
56
  asset.path = File.join(environment.public_folder, dist, asset.logical_path)
58
- next if File.exist?(asset.path) and FileUtils.uptodate?(path, [asset.path])
57
+ next if File.exist?(asset.path) and File.identical?(path, asset.path)
59
58
  asset.write
60
59
  end
61
60
  end.join
@@ -5,7 +5,11 @@ module Linner
5
5
 
6
6
  def initialize(path)
7
7
  @path = path
8
- @content ||= File.exist?(path) ? Tilt.new(path).render : ""
8
+ @content ||= begin
9
+ File.exist?(path) ? Tilt.new(path, :default_encoding => "UTF-8").render : ""
10
+ rescue RuntimeError
11
+ File.read(path)
12
+ end
9
13
  end
10
14
 
11
15
  def wrap
@@ -13,7 +17,7 @@ module Linner
13
17
  end
14
18
 
15
19
  def wrappable?
16
- !!(@path.include? Linner.root.join("app").to_path and Template.template_for_script?(@path))
20
+ !!(@path.include? Linner.environment.app_folder and Template.template_for_script?(@path))
17
21
  end
18
22
 
19
23
  def write
@@ -28,7 +32,7 @@ module Linner
28
32
  end
29
33
 
30
34
  def logical_path
31
- @logical_path ||= @path.gsub(/#{Linner.root}\/app\/\w*\//, "")
35
+ @logical_path ||= @path.gsub(/#{Linner.environment.app_folder}\/\w*\//, "")
32
36
  end
33
37
  end
34
38
  end
@@ -18,21 +18,33 @@ module Linner
18
18
 
19
19
  desc "watch", "watch assets"
20
20
  def watch
21
- proc = Proc.new do |modified, added, removed|
21
+ @reactor = Reactor.new
22
+
23
+ @proc = Proc.new do |modified, added, removed|
22
24
  begin
23
25
  Notifier.info{ Linner.perform }
24
26
  rescue
25
27
  Notifier.error $!
26
28
  end
27
29
  end
28
- proc.call
29
- listener = Listen.to "app/", "vendor/", "test/", filter: /\.(js|coffee|css|sass|scss)$/
30
- listener.change &proc
30
+ @proc.call
31
+
32
+ Listen.to env.app_folder, env.vendor_folder, env.test_folder do |modified, added, removed|
33
+ @proc.call
34
+ end
35
+
36
+ Listen.to env.public_folder, :relative_paths => true do |modified, added, removed|
37
+ paths = [].push(modified, added, removed).flatten.compact
38
+ @reactor.reload_browser(paths)
39
+ end
40
+
31
41
  trap :INT do
32
42
  Notifier.exit
43
+ @reactor.stop
33
44
  exit!
34
45
  end
35
- listener.start!
46
+
47
+ sleep
36
48
  end
37
49
 
38
50
  desc "clean", "clean assets"
@@ -45,6 +57,11 @@ module Linner
45
57
  directory('templates', name)
46
58
  chmod("#{name}/bin/server", 0755)
47
59
  end
60
+
61
+ private
62
+ def env
63
+ Linner.environment
64
+ end
48
65
  end
49
66
  end
50
67
 
@@ -11,7 +11,7 @@ module Linner
11
11
 
12
12
  %w(app test vendor public).each do |method|
13
13
  define_method("#{method}_folder") do
14
- Linner.root.join(@env["paths"][method]).expand_path.to_path
14
+ @env["paths"][method]
15
15
  end
16
16
  end
17
17
 
@@ -11,7 +11,7 @@ module Linner
11
11
 
12
12
  def error(message)
13
13
  puts message = "👻 : #{message}!"
14
- if Linner.environment.notifications && TerminalNotifier.available?
14
+ if Linner.environment.notification && TerminalNotifier.available?
15
15
  TerminalNotifier.notify message, :title => 'Linner'
16
16
  end
17
17
  end
@@ -0,0 +1,101 @@
1
+ require "eventmachine"
2
+ require "em-websocket"
3
+ require "http/parser"
4
+
5
+ module Linner
6
+ # Steal from guard livereload
7
+ # https://github.com/guard/guard-livereload/blob/master/lib/guard/livereload/reactor.rb
8
+ class Reactor
9
+ OPTIONS = {
10
+ :host => '0.0.0.0',
11
+ :port => '35729',
12
+ :apply_css_live => true,
13
+ :override_url => false,
14
+ :grace_period => 0
15
+ }
16
+
17
+ attr_reader :web_sockets, :thread, :options
18
+
19
+ def initialize
20
+ @web_sockets = []
21
+ @options = OPTIONS
22
+ @thread = start_threaded_reactor(OPTIONS)
23
+ end
24
+
25
+ def stop
26
+ thread.kill
27
+ end
28
+
29
+ def reload_browser(paths = [])
30
+ paths.each do |path|
31
+ data = {
32
+ :command => 'reload',
33
+ :path => "/#{path}",
34
+ :liveCSS => @options[:apply_css_live]
35
+ }
36
+ web_sockets.each { |ws| ws.send(MultiJson.encode(data)) }
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def start_threaded_reactor(options)
43
+ Thread.new do
44
+ EventMachine.epoll
45
+ EventMachine.run do
46
+ EventMachine.start_server(options[:host], options[:port], Connection, {}) do |ws|
47
+ ws.onopen do
48
+ begin
49
+ ws.send MultiJson.encode({
50
+ :command => 'hello',
51
+ :protocols => ['http://livereload.com/protocols/official-7'],
52
+ :serverName => 'guard-livereload'
53
+ })
54
+ @web_sockets << ws
55
+ rescue
56
+ Notifier.error $!
57
+ end
58
+ end
59
+
60
+ ws.onclose do
61
+ @web_sockets.delete(ws)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+
69
+
70
+ class Connection < EventMachine::WebSocket::Connection
71
+ def dispatch data
72
+ parser = Http::Parser.new
73
+ parser << data
74
+ if parser.http_method != 'GET' || parser.upgrade?
75
+ super #pass the request to websocket
76
+ elsif parser.request_path == '/livereload.js'
77
+ serve_file File.join(File.dirname(__FILE__), "../../vendor", "livereload.js")
78
+ elsif File.exist?(parser.request_path[1..-1])
79
+ serve_file parser.request_path[1..-1] # Strip leading slash
80
+ else
81
+ send_data "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: 13\r\n\r\n404 Not Found"
82
+ close_connection_after_writing
83
+ end
84
+ end
85
+
86
+ def serve_file path
87
+ content_type = case File.extname(path)
88
+ when '.css' then 'text/css'
89
+ when '.js' then 'application/ecmascript'
90
+ when '.gif' then 'image/gif'
91
+ when '.jpeg', '.jpg' then 'image/jpeg'
92
+ when '.png' then 'image/png'
93
+ else; 'text/plain'
94
+ end
95
+ send_data "HTTP/1.1 200 OK\r\nContent-Type: #{content_type}\r\nContent-Length: #{File.size path}\r\n\r\n"
96
+ stream_file_data(path).callback { close_connection_after_writing }
97
+ end
98
+ end
99
+
100
+ end
101
+ end
@@ -20,11 +20,11 @@ module Linner
20
20
 
21
21
  class << self
22
22
  def template_for_script?(path)
23
- [Tilt::JavascriptTemplate, Tilt::CoffeeScriptTemplate].include? Tilt[path]
23
+ Tilt[path].default_mime_type == "application/javascript"
24
24
  end
25
25
 
26
26
  def template_for_style?(path)
27
- [Tilt::CSSTemplate, Tilt::SassTemplate, Tilt::ScssTemplate].include? Tilt[path]
27
+ Tilt[path].default_mime_type == "text/css"
28
28
  end
29
29
  end
30
30
  end
@@ -21,4 +21,4 @@ files:
21
21
  "/": "app/views/**/*.{html,hbs}"
22
22
  modules:
23
23
  wrapper: "CMD"
24
- notifications: true
24
+ notification: true
@@ -1,3 +1,3 @@
1
1
  module Linner
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
@@ -23,6 +23,9 @@ Gem::Specification.new do |spec|
23
23
  spec.add_dependency "sass", "~> 3.2"
24
24
  spec.add_dependency "listen", "~> 1.2"
25
25
  spec.add_dependency "uglifier", "~> 2.1"
26
+ spec.add_dependency "multi_json", "~> 1.7"
27
+ spec.add_dependency "em-websocket", "~> 0.5"
28
+ spec.add_dependency "eventmachine", "~> 1.0.3"
26
29
  spec.add_dependency "coffee-script", "~> 2.2"
27
30
  spec.add_dependency "yui-compressor", "~> 0.9.6"
28
31
  spec.add_dependency "terminal-notifier", "~> 1.4"
@@ -3,6 +3,19 @@ require "spec_helper"
3
3
  describe Asset do
4
4
 
5
5
  before(:each) do
6
- @asset = Asset.new("app.js")
6
+ @asset = Asset.new("app/scripts/app.js")
7
+ end
8
+
9
+ describe :logical_path do
10
+ it "should be return right logical_path" do
11
+ @asset.logical_path.should == "app.js"
12
+ end
13
+ end
14
+
15
+ describe :wrappable do
16
+ it "should wrapperable" do
17
+ @asset.wrappable?.should be_true
18
+ Asset.new("app/styles/app.css").wrappable?.should be_false
19
+ end
7
20
  end
8
21
  end
@@ -7,10 +7,10 @@ describe Environment do
7
7
 
8
8
  describe "convension" do
9
9
  it "should equals default path folder" do
10
- @env.app_folder.should == root.join("app").to_path
11
- @env.test_folder.should == root.join("test").to_path
12
- @env.vendor_folder.should == root.join("vendor").to_path
13
- @env.public_folder.should == root.join("public").to_path
10
+ @env.app_folder.should == "app"
11
+ @env.test_folder.should == "test"
12
+ @env.vendor_folder.should == "vendor"
13
+ @env.public_folder.should == "public"
14
14
  end
15
15
 
16
16
  it "should equals default config" do
@@ -13,14 +13,14 @@ describe Template do
13
13
  end
14
14
 
15
15
  it "should tempalte for script" do
16
- Template.template_for_script? "app.js".should be_true
17
- Template.template_for_script? "app.coffee".should be_true
16
+ Template.template_for_script?("app.js").should be_true
17
+ Template.template_for_script?("app.coffee").should be_true
18
18
  end
19
19
 
20
20
  it "should tempalte for style" do
21
- Template.template_for_style? "app.css".should be_true
22
- Template.template_for_style? "app.sass".should be_true
23
- Template.template_for_style? "app.scss".should be_true
21
+ Template.template_for_style?("app.css").should be_true
22
+ Template.template_for_style?("app.sass").should be_true
23
+ Template.template_for_style?("app.scss").should be_true
24
24
  end
25
25
  end
26
26
  end
@@ -5,32 +5,27 @@ paths:
5
5
  public: "public"
6
6
  files:
7
7
  scripts:
8
- concat:
9
- "scripts/app.js": "app/**/*.{js,coffee}"
10
- "scripts/vendor.js": "vendor/**/*.{js,coffee}"
8
+ concat: {}
11
9
  copy: {}
12
10
  order:
13
11
  before:
14
12
  - "vendor/jquery-1.10.2.js"
15
13
  after: []
16
14
  styles:
17
- concat:
18
- "styles/app.css": "app/**/[a-z]*.{css,scss,sass}"
15
+ concat: {}
19
16
  copy: {}
20
17
  order:
21
18
  before: []
22
19
  after: []
23
20
  images:
24
21
  concat: {}
25
- copy:
26
- "images/": "app/**/*.{png,gif}"
22
+ copy: {}
27
23
  order:
28
24
  before: []
29
25
  after: []
30
26
  views:
31
27
  concat: {}
32
- copy:
33
- "/": "app/**/*.{html,hbs}"
28
+ copy: {}
34
29
  order:
35
30
  before: []
36
31
  after: []
@@ -0,0 +1,1055 @@
1
+ (function() {
2
+ var __customevents = {}, __protocol = {}, __connector = {}, __timer = {}, __options = {}, __reloader = {}, __livereload = {}, __less = {}, __startup = {};
3
+
4
+ // customevents
5
+ var CustomEvents;
6
+ CustomEvents = {
7
+ bind: function(element, eventName, handler) {
8
+ if (element.addEventListener) {
9
+ return element.addEventListener(eventName, handler, false);
10
+ } else if (element.attachEvent) {
11
+ element[eventName] = 1;
12
+ return element.attachEvent('onpropertychange', function(event) {
13
+ if (event.propertyName === eventName) {
14
+ return handler();
15
+ }
16
+ });
17
+ } else {
18
+ throw new Error("Attempt to attach custom event " + eventName + " to something which isn't a DOMElement");
19
+ }
20
+ },
21
+ fire: function(element, eventName) {
22
+ var event;
23
+ if (element.addEventListener) {
24
+ event = document.createEvent('HTMLEvents');
25
+ event.initEvent(eventName, true, true);
26
+ return document.dispatchEvent(event);
27
+ } else if (element.attachEvent) {
28
+ if (element[eventName]) {
29
+ return element[eventName]++;
30
+ }
31
+ } else {
32
+ throw new Error("Attempt to fire custom event " + eventName + " on something which isn't a DOMElement");
33
+ }
34
+ }
35
+ };
36
+ __customevents.bind = CustomEvents.bind;
37
+ __customevents.fire = CustomEvents.fire;
38
+
39
+ // protocol
40
+ var PROTOCOL_6, PROTOCOL_7, Parser, ProtocolError;
41
+ var __indexOf = Array.prototype.indexOf || function(item) {
42
+ for (var i = 0, l = this.length; i < l; i++) {
43
+ if (this[i] === item) return i;
44
+ }
45
+ return -1;
46
+ };
47
+ __protocol.PROTOCOL_6 = PROTOCOL_6 = 'http://livereload.com/protocols/official-6';
48
+ __protocol.PROTOCOL_7 = PROTOCOL_7 = 'http://livereload.com/protocols/official-7';
49
+ __protocol.ProtocolError = ProtocolError = (function() {
50
+ function ProtocolError(reason, data) {
51
+ this.message = "LiveReload protocol error (" + reason + ") after receiving data: \"" + data + "\".";
52
+ }
53
+ return ProtocolError;
54
+ })();
55
+ __protocol.Parser = Parser = (function() {
56
+ function Parser(handlers) {
57
+ this.handlers = handlers;
58
+ this.reset();
59
+ }
60
+ Parser.prototype.reset = function() {
61
+ return this.protocol = null;
62
+ };
63
+ Parser.prototype.process = function(data) {
64
+ var command, message, options, _ref;
65
+ try {
66
+ if (!(this.protocol != null)) {
67
+ if (data.match(/^!!ver:([\d.]+)$/)) {
68
+ this.protocol = 6;
69
+ } else if (message = this._parseMessage(data, ['hello'])) {
70
+ if (!message.protocols.length) {
71
+ throw new ProtocolError("no protocols specified in handshake message");
72
+ } else if (__indexOf.call(message.protocols, PROTOCOL_7) >= 0) {
73
+ this.protocol = 7;
74
+ } else if (__indexOf.call(message.protocols, PROTOCOL_6) >= 0) {
75
+ this.protocol = 6;
76
+ } else {
77
+ throw new ProtocolError("no supported protocols found");
78
+ }
79
+ }
80
+ return this.handlers.connected(this.protocol);
81
+ } else if (this.protocol === 6) {
82
+ message = JSON.parse(data);
83
+ if (!message.length) {
84
+ throw new ProtocolError("protocol 6 messages must be arrays");
85
+ }
86
+ command = message[0], options = message[1];
87
+ if (command !== 'refresh') {
88
+ throw new ProtocolError("unknown protocol 6 command");
89
+ }
90
+ return this.handlers.message({
91
+ command: 'reload',
92
+ path: options.path,
93
+ liveCSS: (_ref = options.apply_css_live) != null ? _ref : true
94
+ });
95
+ } else {
96
+ message = this._parseMessage(data, ['reload', 'alert']);
97
+ return this.handlers.message(message);
98
+ }
99
+ } catch (e) {
100
+ if (e instanceof ProtocolError) {
101
+ return this.handlers.error(e);
102
+ } else {
103
+ throw e;
104
+ }
105
+ }
106
+ };
107
+ Parser.prototype._parseMessage = function(data, validCommands) {
108
+ var message, _ref;
109
+ try {
110
+ message = JSON.parse(data);
111
+ } catch (e) {
112
+ throw new ProtocolError('unparsable JSON', data);
113
+ }
114
+ if (!message.command) {
115
+ throw new ProtocolError('missing "command" key', data);
116
+ }
117
+ if (_ref = message.command, __indexOf.call(validCommands, _ref) < 0) {
118
+ throw new ProtocolError("invalid command '" + message.command + "', only valid commands are: " + (validCommands.join(', ')) + ")", data);
119
+ }
120
+ return message;
121
+ };
122
+ return Parser;
123
+ })();
124
+
125
+ // connector
126
+ // Generated by CoffeeScript 1.3.3
127
+ var Connector, PROTOCOL_6, PROTOCOL_7, Parser, Version, _ref;
128
+
129
+ _ref = __protocol, Parser = _ref.Parser, PROTOCOL_6 = _ref.PROTOCOL_6, PROTOCOL_7 = _ref.PROTOCOL_7;
130
+
131
+ Version = '2.0.8';
132
+
133
+ __connector.Connector = Connector = (function() {
134
+
135
+ function Connector(options, WebSocket, Timer, handlers) {
136
+ var _this = this;
137
+ this.options = options;
138
+ this.WebSocket = WebSocket;
139
+ this.Timer = Timer;
140
+ this.handlers = handlers;
141
+ this._uri = "ws://" + this.options.host + ":" + this.options.port + "/livereload";
142
+ this._nextDelay = this.options.mindelay;
143
+ this._connectionDesired = false;
144
+ this.protocol = 0;
145
+ this.protocolParser = new Parser({
146
+ connected: function(protocol) {
147
+ _this.protocol = protocol;
148
+ _this._handshakeTimeout.stop();
149
+ _this._nextDelay = _this.options.mindelay;
150
+ _this._disconnectionReason = 'broken';
151
+ return _this.handlers.connected(protocol);
152
+ },
153
+ error: function(e) {
154
+ _this.handlers.error(e);
155
+ return _this._closeOnError();
156
+ },
157
+ message: function(message) {
158
+ return _this.handlers.message(message);
159
+ }
160
+ });
161
+ this._handshakeTimeout = new Timer(function() {
162
+ if (!_this._isSocketConnected()) {
163
+ return;
164
+ }
165
+ _this._disconnectionReason = 'handshake-timeout';
166
+ return _this.socket.close();
167
+ });
168
+ this._reconnectTimer = new Timer(function() {
169
+ if (!_this._connectionDesired) {
170
+ return;
171
+ }
172
+ return _this.connect();
173
+ });
174
+ this.connect();
175
+ }
176
+
177
+ Connector.prototype._isSocketConnected = function() {
178
+ return this.socket && this.socket.readyState === this.WebSocket.OPEN;
179
+ };
180
+
181
+ Connector.prototype.connect = function() {
182
+ var _this = this;
183
+ this._connectionDesired = true;
184
+ if (this._isSocketConnected()) {
185
+ return;
186
+ }
187
+ this._reconnectTimer.stop();
188
+ this._disconnectionReason = 'cannot-connect';
189
+ this.protocolParser.reset();
190
+ this.handlers.connecting();
191
+ this.socket = new this.WebSocket(this._uri);
192
+ this.socket.onopen = function(e) {
193
+ return _this._onopen(e);
194
+ };
195
+ this.socket.onclose = function(e) {
196
+ return _this._onclose(e);
197
+ };
198
+ this.socket.onmessage = function(e) {
199
+ return _this._onmessage(e);
200
+ };
201
+ return this.socket.onerror = function(e) {
202
+ return _this._onerror(e);
203
+ };
204
+ };
205
+
206
+ Connector.prototype.disconnect = function() {
207
+ this._connectionDesired = false;
208
+ this._reconnectTimer.stop();
209
+ if (!this._isSocketConnected()) {
210
+ return;
211
+ }
212
+ this._disconnectionReason = 'manual';
213
+ return this.socket.close();
214
+ };
215
+
216
+ Connector.prototype._scheduleReconnection = function() {
217
+ if (!this._connectionDesired) {
218
+ return;
219
+ }
220
+ if (!this._reconnectTimer.running) {
221
+ this._reconnectTimer.start(this._nextDelay);
222
+ return this._nextDelay = Math.min(this.options.maxdelay, this._nextDelay * 2);
223
+ }
224
+ };
225
+
226
+ Connector.prototype.sendCommand = function(command) {
227
+ if (this.protocol == null) {
228
+ return;
229
+ }
230
+ return this._sendCommand(command);
231
+ };
232
+
233
+ Connector.prototype._sendCommand = function(command) {
234
+ return this.socket.send(JSON.stringify(command));
235
+ };
236
+
237
+ Connector.prototype._closeOnError = function() {
238
+ this._handshakeTimeout.stop();
239
+ this._disconnectionReason = 'error';
240
+ return this.socket.close();
241
+ };
242
+
243
+ Connector.prototype._onopen = function(e) {
244
+ var hello;
245
+ this.handlers.socketConnected();
246
+ this._disconnectionReason = 'handshake-failed';
247
+ hello = {
248
+ command: 'hello',
249
+ protocols: [PROTOCOL_6, PROTOCOL_7]
250
+ };
251
+ hello.ver = Version;
252
+ if (this.options.ext) {
253
+ hello.ext = this.options.ext;
254
+ }
255
+ if (this.options.extver) {
256
+ hello.extver = this.options.extver;
257
+ }
258
+ if (this.options.snipver) {
259
+ hello.snipver = this.options.snipver;
260
+ }
261
+ this._sendCommand(hello);
262
+ return this._handshakeTimeout.start(this.options.handshake_timeout);
263
+ };
264
+
265
+ Connector.prototype._onclose = function(e) {
266
+ this.protocol = 0;
267
+ this.handlers.disconnected(this._disconnectionReason, this._nextDelay);
268
+ return this._scheduleReconnection();
269
+ };
270
+
271
+ Connector.prototype._onerror = function(e) {};
272
+
273
+ Connector.prototype._onmessage = function(e) {
274
+ return this.protocolParser.process(e.data);
275
+ };
276
+
277
+ return Connector;
278
+
279
+ })();
280
+
281
+ // timer
282
+ var Timer;
283
+ var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
284
+ __timer.Timer = Timer = (function() {
285
+ function Timer(func) {
286
+ this.func = func;
287
+ this.running = false;
288
+ this.id = null;
289
+ this._handler = __bind(function() {
290
+ this.running = false;
291
+ this.id = null;
292
+ return this.func();
293
+ }, this);
294
+ }
295
+ Timer.prototype.start = function(timeout) {
296
+ if (this.running) {
297
+ clearTimeout(this.id);
298
+ }
299
+ this.id = setTimeout(this._handler, timeout);
300
+ return this.running = true;
301
+ };
302
+ Timer.prototype.stop = function() {
303
+ if (this.running) {
304
+ clearTimeout(this.id);
305
+ this.running = false;
306
+ return this.id = null;
307
+ }
308
+ };
309
+ return Timer;
310
+ })();
311
+ Timer.start = function(timeout, func) {
312
+ return setTimeout(func, timeout);
313
+ };
314
+
315
+ // options
316
+ var Options;
317
+ __options.Options = Options = (function() {
318
+ function Options() {
319
+ this.host = null;
320
+ this.port = 35729;
321
+ this.snipver = null;
322
+ this.ext = null;
323
+ this.extver = null;
324
+ this.mindelay = 1000;
325
+ this.maxdelay = 60000;
326
+ this.handshake_timeout = 5000;
327
+ }
328
+ Options.prototype.set = function(name, value) {
329
+ switch (typeof this[name]) {
330
+ case 'undefined':
331
+ break;
332
+ case 'number':
333
+ return this[name] = +value;
334
+ default:
335
+ return this[name] = value;
336
+ }
337
+ };
338
+ return Options;
339
+ })();
340
+ Options.extract = function(document) {
341
+ var element, keyAndValue, m, mm, options, pair, src, _i, _j, _len, _len2, _ref, _ref2;
342
+ _ref = document.getElementsByTagName('script');
343
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
344
+ element = _ref[_i];
345
+ if ((src = element.src) && (m = src.match(/^[^:]+:\/\/(.*)\/z?livereload\.js(?:\?(.*))?$/))) {
346
+ options = new Options();
347
+ if (mm = m[1].match(/^([^\/:]+)(?::(\d+))?$/)) {
348
+ options.host = mm[1];
349
+ if (mm[2]) {
350
+ options.port = parseInt(mm[2], 10);
351
+ }
352
+ }
353
+ if (m[2]) {
354
+ _ref2 = m[2].split('&');
355
+ for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
356
+ pair = _ref2[_j];
357
+ if ((keyAndValue = pair.split('=')).length > 1) {
358
+ options.set(keyAndValue[0].replace(/-/g, '_'), keyAndValue.slice(1).join('='));
359
+ }
360
+ }
361
+ }
362
+ return options;
363
+ }
364
+ }
365
+ return null;
366
+ };
367
+
368
+ // reloader
369
+ // Generated by CoffeeScript 1.3.1
370
+ (function() {
371
+ var IMAGE_STYLES, Reloader, numberOfMatchingSegments, pathFromUrl, pathsMatch, pickBestMatch, splitUrl;
372
+
373
+ splitUrl = function(url) {
374
+ var hash, index, params;
375
+ if ((index = url.indexOf('#')) >= 0) {
376
+ hash = url.slice(index);
377
+ url = url.slice(0, index);
378
+ } else {
379
+ hash = '';
380
+ }
381
+ if ((index = url.indexOf('?')) >= 0) {
382
+ params = url.slice(index);
383
+ url = url.slice(0, index);
384
+ } else {
385
+ params = '';
386
+ }
387
+ return {
388
+ url: url,
389
+ params: params,
390
+ hash: hash
391
+ };
392
+ };
393
+
394
+ pathFromUrl = function(url) {
395
+ var path;
396
+ url = splitUrl(url).url;
397
+ if (url.indexOf('file://') === 0) {
398
+ path = url.replace(/^file:\/\/(localhost)?/, '');
399
+ } else {
400
+ path = url.replace(/^([^:]+:)?\/\/([^:\/]+)(:\d*)?\//, '/');
401
+ }
402
+ return decodeURIComponent(path);
403
+ };
404
+
405
+ pickBestMatch = function(path, objects, pathFunc) {
406
+ var bestMatch, object, score, _i, _len;
407
+ bestMatch = {
408
+ score: 0
409
+ };
410
+ for (_i = 0, _len = objects.length; _i < _len; _i++) {
411
+ object = objects[_i];
412
+ score = numberOfMatchingSegments(path, pathFunc(object));
413
+ if (score > bestMatch.score) {
414
+ bestMatch = {
415
+ object: object,
416
+ score: score
417
+ };
418
+ }
419
+ }
420
+ if (bestMatch.score > 0) {
421
+ return bestMatch;
422
+ } else {
423
+ return null;
424
+ }
425
+ };
426
+
427
+ numberOfMatchingSegments = function(path1, path2) {
428
+ var comps1, comps2, eqCount, len;
429
+ path1 = path1.replace(/^\/+/, '').toLowerCase();
430
+ path2 = path2.replace(/^\/+/, '').toLowerCase();
431
+ if (path1 === path2) {
432
+ return 10000;
433
+ }
434
+ comps1 = path1.split('/').reverse();
435
+ comps2 = path2.split('/').reverse();
436
+ len = Math.min(comps1.length, comps2.length);
437
+ eqCount = 0;
438
+ while (eqCount < len && comps1[eqCount] === comps2[eqCount]) {
439
+ ++eqCount;
440
+ }
441
+ return eqCount;
442
+ };
443
+
444
+ pathsMatch = function(path1, path2) {
445
+ return numberOfMatchingSegments(path1, path2) > 0;
446
+ };
447
+
448
+ IMAGE_STYLES = [
449
+ {
450
+ selector: 'background',
451
+ styleNames: ['backgroundImage']
452
+ }, {
453
+ selector: 'border',
454
+ styleNames: ['borderImage', 'webkitBorderImage', 'MozBorderImage']
455
+ }
456
+ ];
457
+
458
+ __reloader.Reloader = Reloader = (function() {
459
+
460
+ Reloader.name = 'Reloader';
461
+
462
+ function Reloader(window, console, Timer) {
463
+ this.window = window;
464
+ this.console = console;
465
+ this.Timer = Timer;
466
+ this.document = this.window.document;
467
+ this.importCacheWaitPeriod = 200;
468
+ this.plugins = [];
469
+ }
470
+
471
+ Reloader.prototype.addPlugin = function(plugin) {
472
+ return this.plugins.push(plugin);
473
+ };
474
+
475
+ Reloader.prototype.analyze = function(callback) {
476
+ return results;
477
+ };
478
+
479
+ Reloader.prototype.reload = function(path, options) {
480
+ var plugin, _base, _i, _len, _ref;
481
+ this.options = options;
482
+ if ((_base = this.options).stylesheetReloadTimeout == null) {
483
+ _base.stylesheetReloadTimeout = 15000;
484
+ }
485
+ _ref = this.plugins;
486
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
487
+ plugin = _ref[_i];
488
+ if (plugin.reload && plugin.reload(path, options)) {
489
+ return;
490
+ }
491
+ }
492
+ if (options.liveCSS) {
493
+ if (path.match(/\.css$/i)) {
494
+ if (this.reloadStylesheet(path)) {
495
+ return;
496
+ }
497
+ }
498
+ }
499
+ if (options.liveImg) {
500
+ if (path.match(/\.(jpe?g|png|gif)$/i)) {
501
+ this.reloadImages(path);
502
+ return;
503
+ }
504
+ }
505
+ return this.reloadPage();
506
+ };
507
+
508
+ Reloader.prototype.reloadPage = function() {
509
+ return this.window.document.location.reload();
510
+ };
511
+
512
+ Reloader.prototype.reloadImages = function(path) {
513
+ var expando, img, selector, styleNames, styleSheet, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, _results;
514
+ expando = this.generateUniqueString();
515
+ _ref = this.document.images;
516
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
517
+ img = _ref[_i];
518
+ if (pathsMatch(path, pathFromUrl(img.src))) {
519
+ img.src = this.generateCacheBustUrl(img.src, expando);
520
+ }
521
+ }
522
+ if (this.document.querySelectorAll) {
523
+ for (_j = 0, _len1 = IMAGE_STYLES.length; _j < _len1; _j++) {
524
+ _ref1 = IMAGE_STYLES[_j], selector = _ref1.selector, styleNames = _ref1.styleNames;
525
+ _ref2 = this.document.querySelectorAll("[style*=" + selector + "]");
526
+ for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
527
+ img = _ref2[_k];
528
+ this.reloadStyleImages(img.style, styleNames, path, expando);
529
+ }
530
+ }
531
+ }
532
+ if (this.document.styleSheets) {
533
+ _ref3 = this.document.styleSheets;
534
+ _results = [];
535
+ for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) {
536
+ styleSheet = _ref3[_l];
537
+ _results.push(this.reloadStylesheetImages(styleSheet, path, expando));
538
+ }
539
+ return _results;
540
+ }
541
+ };
542
+
543
+ Reloader.prototype.reloadStylesheetImages = function(styleSheet, path, expando) {
544
+ var rule, rules, styleNames, _i, _j, _len, _len1;
545
+ try {
546
+ rules = styleSheet != null ? styleSheet.cssRules : void 0;
547
+ } catch (e) {
548
+
549
+ }
550
+ if (!rules) {
551
+ return;
552
+ }
553
+ for (_i = 0, _len = rules.length; _i < _len; _i++) {
554
+ rule = rules[_i];
555
+ switch (rule.type) {
556
+ case CSSRule.IMPORT_RULE:
557
+ this.reloadStylesheetImages(rule.styleSheet, path, expando);
558
+ break;
559
+ case CSSRule.STYLE_RULE:
560
+ for (_j = 0, _len1 = IMAGE_STYLES.length; _j < _len1; _j++) {
561
+ styleNames = IMAGE_STYLES[_j].styleNames;
562
+ this.reloadStyleImages(rule.style, styleNames, path, expando);
563
+ }
564
+ break;
565
+ case CSSRule.MEDIA_RULE:
566
+ this.reloadStylesheetImages(rule, path, expando);
567
+ }
568
+ }
569
+ };
570
+
571
+ Reloader.prototype.reloadStyleImages = function(style, styleNames, path, expando) {
572
+ var newValue, styleName, value, _i, _len,
573
+ _this = this;
574
+ for (_i = 0, _len = styleNames.length; _i < _len; _i++) {
575
+ styleName = styleNames[_i];
576
+ value = style[styleName];
577
+ if (typeof value === 'string') {
578
+ newValue = value.replace(/\burl\s*\(([^)]*)\)/, function(match, src) {
579
+ if (pathsMatch(path, pathFromUrl(src))) {
580
+ return "url(" + (_this.generateCacheBustUrl(src, expando)) + ")";
581
+ } else {
582
+ return match;
583
+ }
584
+ });
585
+ if (newValue !== value) {
586
+ style[styleName] = newValue;
587
+ }
588
+ }
589
+ }
590
+ };
591
+
592
+ Reloader.prototype.reloadStylesheet = function(path) {
593
+ var imported, link, links, match, style, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1,
594
+ _this = this;
595
+ links = (function() {
596
+ var _i, _len, _ref, _results;
597
+ _ref = this.document.getElementsByTagName('link');
598
+ _results = [];
599
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
600
+ link = _ref[_i];
601
+ if (link.rel === 'stylesheet' && !link.__LiveReload_pendingRemoval) {
602
+ _results.push(link);
603
+ }
604
+ }
605
+ return _results;
606
+ }).call(this);
607
+ imported = [];
608
+ _ref = this.document.getElementsByTagName('style');
609
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
610
+ style = _ref[_i];
611
+ if (style.sheet) {
612
+ this.collectImportedStylesheets(style, style.sheet, imported);
613
+ }
614
+ }
615
+ for (_j = 0, _len1 = links.length; _j < _len1; _j++) {
616
+ link = links[_j];
617
+ this.collectImportedStylesheets(link, link.sheet, imported);
618
+ }
619
+ if (this.window.StyleFix && this.document.querySelectorAll) {
620
+ _ref1 = this.document.querySelectorAll('style[data-href]');
621
+ for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
622
+ style = _ref1[_k];
623
+ links.push(style);
624
+ }
625
+ }
626
+ this.console.log("LiveReload found " + links.length + " LINKed stylesheets, " + imported.length + " @imported stylesheets");
627
+ match = pickBestMatch(path, links.concat(imported), function(l) {
628
+ return pathFromUrl(_this.linkHref(l));
629
+ });
630
+ if (match) {
631
+ if (match.object.rule) {
632
+ this.console.log("LiveReload is reloading imported stylesheet: " + match.object.href);
633
+ this.reattachImportedRule(match.object);
634
+ } else {
635
+ this.console.log("LiveReload is reloading stylesheet: " + (this.linkHref(match.object)));
636
+ this.reattachStylesheetLink(match.object);
637
+ }
638
+ } else {
639
+ this.console.log("LiveReload will reload all stylesheets because path '" + path + "' did not match any specific one");
640
+ for (_l = 0, _len3 = links.length; _l < _len3; _l++) {
641
+ link = links[_l];
642
+ this.reattachStylesheetLink(link);
643
+ }
644
+ }
645
+ return true;
646
+ };
647
+
648
+ Reloader.prototype.collectImportedStylesheets = function(link, styleSheet, result) {
649
+ var index, rule, rules, _i, _len;
650
+ try {
651
+ rules = styleSheet != null ? styleSheet.cssRules : void 0;
652
+ } catch (e) {
653
+
654
+ }
655
+ if (rules && rules.length) {
656
+ for (index = _i = 0, _len = rules.length; _i < _len; index = ++_i) {
657
+ rule = rules[index];
658
+ switch (rule.type) {
659
+ case CSSRule.CHARSET_RULE:
660
+ continue;
661
+ case CSSRule.IMPORT_RULE:
662
+ result.push({
663
+ link: link,
664
+ rule: rule,
665
+ index: index,
666
+ href: rule.href
667
+ });
668
+ this.collectImportedStylesheets(link, rule.styleSheet, result);
669
+ break;
670
+ default:
671
+ break;
672
+ }
673
+ }
674
+ }
675
+ };
676
+
677
+ Reloader.prototype.waitUntilCssLoads = function(clone, func) {
678
+ var callbackExecuted, executeCallback, poll,
679
+ _this = this;
680
+ callbackExecuted = false;
681
+ executeCallback = function() {
682
+ if (callbackExecuted) {
683
+ return;
684
+ }
685
+ callbackExecuted = true;
686
+ return func();
687
+ };
688
+ clone.onload = function() {
689
+ console.log("onload!");
690
+ _this.knownToSupportCssOnLoad = true;
691
+ return executeCallback();
692
+ };
693
+ if (!this.knownToSupportCssOnLoad) {
694
+ (poll = function() {
695
+ if (clone.sheet) {
696
+ console.log("polling!");
697
+ return executeCallback();
698
+ } else {
699
+ return _this.Timer.start(50, poll);
700
+ }
701
+ })();
702
+ }
703
+ return this.Timer.start(this.options.stylesheetReloadTimeout, executeCallback);
704
+ };
705
+
706
+ Reloader.prototype.linkHref = function(link) {
707
+ return link.href || link.getAttribute('data-href');
708
+ };
709
+
710
+ Reloader.prototype.reattachStylesheetLink = function(link) {
711
+ var clone, parent,
712
+ _this = this;
713
+ if (link.__LiveReload_pendingRemoval) {
714
+ return;
715
+ }
716
+ link.__LiveReload_pendingRemoval = true;
717
+ if (link.tagName === 'STYLE') {
718
+ clone = this.document.createElement('link');
719
+ clone.rel = 'stylesheet';
720
+ clone.media = link.media;
721
+ clone.disabled = link.disabled;
722
+ } else {
723
+ clone = link.cloneNode(false);
724
+ }
725
+ clone.href = this.generateCacheBustUrl(this.linkHref(link));
726
+ parent = link.parentNode;
727
+ if (parent.lastChild === link) {
728
+ parent.appendChild(clone);
729
+ } else {
730
+ parent.insertBefore(clone, link.nextSibling);
731
+ }
732
+ return this.waitUntilCssLoads(clone, function() {
733
+ var additionalWaitingTime;
734
+ if (/AppleWebKit/.test(navigator.userAgent)) {
735
+ additionalWaitingTime = 5;
736
+ } else {
737
+ additionalWaitingTime = 200;
738
+ }
739
+ return _this.Timer.start(additionalWaitingTime, function() {
740
+ var _ref;
741
+ if (!link.parentNode) {
742
+ return;
743
+ }
744
+ link.parentNode.removeChild(link);
745
+ clone.onreadystatechange = null;
746
+ return (_ref = _this.window.StyleFix) != null ? _ref.link(clone) : void 0;
747
+ });
748
+ });
749
+ };
750
+
751
+ Reloader.prototype.reattachImportedRule = function(_arg) {
752
+ var href, index, link, media, newRule, parent, rule, tempLink,
753
+ _this = this;
754
+ rule = _arg.rule, index = _arg.index, link = _arg.link;
755
+ parent = rule.parentStyleSheet;
756
+ href = this.generateCacheBustUrl(rule.href);
757
+ media = rule.media.length ? [].join.call(rule.media, ', ') : '';
758
+ newRule = "@import url(\"" + href + "\") " + media + ";";
759
+ rule.__LiveReload_newHref = href;
760
+ tempLink = this.document.createElement("link");
761
+ tempLink.rel = 'stylesheet';
762
+ tempLink.href = href;
763
+ tempLink.__LiveReload_pendingRemoval = true;
764
+ if (link.parentNode) {
765
+ link.parentNode.insertBefore(tempLink, link);
766
+ }
767
+ return this.Timer.start(this.importCacheWaitPeriod, function() {
768
+ if (tempLink.parentNode) {
769
+ tempLink.parentNode.removeChild(tempLink);
770
+ }
771
+ if (rule.__LiveReload_newHref !== href) {
772
+ return;
773
+ }
774
+ parent.insertRule(newRule, index);
775
+ parent.deleteRule(index + 1);
776
+ rule = parent.cssRules[index];
777
+ rule.__LiveReload_newHref = href;
778
+ return _this.Timer.start(_this.importCacheWaitPeriod, function() {
779
+ if (rule.__LiveReload_newHref !== href) {
780
+ return;
781
+ }
782
+ parent.insertRule(newRule, index);
783
+ return parent.deleteRule(index + 1);
784
+ });
785
+ });
786
+ };
787
+
788
+ Reloader.prototype.generateUniqueString = function() {
789
+ return 'livereload=' + Date.now();
790
+ };
791
+
792
+ Reloader.prototype.generateCacheBustUrl = function(url, expando) {
793
+ var hash, oldParams, params, _ref;
794
+ if (expando == null) {
795
+ expando = this.generateUniqueString();
796
+ }
797
+ _ref = splitUrl(url), url = _ref.url, hash = _ref.hash, oldParams = _ref.params;
798
+ if (this.options.overrideURL) {
799
+ if (url.indexOf(this.options.serverURL) < 0) {
800
+ url = this.options.serverURL + this.options.overrideURL + "?url=" + encodeURIComponent(url);
801
+ }
802
+ }
803
+ params = oldParams.replace(/(\?|&)livereload=(\d+)/, function(match, sep) {
804
+ return "" + sep + expando;
805
+ });
806
+ if (params === oldParams) {
807
+ if (oldParams.length === 0) {
808
+ params = "?" + expando;
809
+ } else {
810
+ params = "" + oldParams + "&" + expando;
811
+ }
812
+ }
813
+ return url + params + hash;
814
+ };
815
+
816
+ return Reloader;
817
+
818
+ })();
819
+
820
+ }).call(this);
821
+
822
+ // livereload
823
+ var Connector, LiveReload, Options, Reloader, Timer;
824
+
825
+ Connector = __connector.Connector;
826
+
827
+ Timer = __timer.Timer;
828
+
829
+ Options = __options.Options;
830
+
831
+ Reloader = __reloader.Reloader;
832
+
833
+ __livereload.LiveReload = LiveReload = (function() {
834
+
835
+ function LiveReload(window) {
836
+ var _this = this;
837
+ this.window = window;
838
+ this.listeners = {};
839
+ this.plugins = [];
840
+ this.pluginIdentifiers = {};
841
+ this.console = this.window.location.href.match(/LR-verbose/) && this.window.console && this.window.console.log && this.window.console.error ? this.window.console : {
842
+ log: function() {},
843
+ error: function() {}
844
+ };
845
+ if (!(this.WebSocket = this.window.WebSocket || this.window.MozWebSocket)) {
846
+ console.error("LiveReload disabled because the browser does not seem to support web sockets");
847
+ return;
848
+ }
849
+ if (!(this.options = Options.extract(this.window.document))) {
850
+ console.error("LiveReload disabled because it could not find its own <SCRIPT> tag");
851
+ return;
852
+ }
853
+ this.reloader = new Reloader(this.window, this.console, Timer);
854
+ this.connector = new Connector(this.options, this.WebSocket, Timer, {
855
+ connecting: function() {},
856
+ socketConnected: function() {},
857
+ connected: function(protocol) {
858
+ var _base;
859
+ if (typeof (_base = _this.listeners).connect === "function") {
860
+ _base.connect();
861
+ }
862
+ _this.log("LiveReload is connected to " + _this.options.host + ":" + _this.options.port + " (protocol v" + protocol + ").");
863
+ return _this.analyze();
864
+ },
865
+ error: function(e) {
866
+ if (e instanceof ProtocolError) {
867
+ return console.log("" + e.message + ".");
868
+ } else {
869
+ return console.log("LiveReload internal error: " + e.message);
870
+ }
871
+ },
872
+ disconnected: function(reason, nextDelay) {
873
+ var _base;
874
+ if (typeof (_base = _this.listeners).disconnect === "function") {
875
+ _base.disconnect();
876
+ }
877
+ switch (reason) {
878
+ case 'cannot-connect':
879
+ return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + ", will retry in " + nextDelay + " sec.");
880
+ case 'broken':
881
+ return _this.log("LiveReload disconnected from " + _this.options.host + ":" + _this.options.port + ", reconnecting in " + nextDelay + " sec.");
882
+ case 'handshake-timeout':
883
+ return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + " (handshake timeout), will retry in " + nextDelay + " sec.");
884
+ case 'handshake-failed':
885
+ return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + " (handshake failed), will retry in " + nextDelay + " sec.");
886
+ case 'manual':
887
+ break;
888
+ case 'error':
889
+ break;
890
+ default:
891
+ return _this.log("LiveReload disconnected from " + _this.options.host + ":" + _this.options.port + " (" + reason + "), reconnecting in " + nextDelay + " sec.");
892
+ }
893
+ },
894
+ message: function(message) {
895
+ switch (message.command) {
896
+ case 'reload':
897
+ return _this.performReload(message);
898
+ case 'alert':
899
+ return _this.performAlert(message);
900
+ }
901
+ }
902
+ });
903
+ }
904
+
905
+ LiveReload.prototype.on = function(eventName, handler) {
906
+ return this.listeners[eventName] = handler;
907
+ };
908
+
909
+ LiveReload.prototype.log = function(message) {
910
+ return this.console.log("" + message);
911
+ };
912
+
913
+ LiveReload.prototype.performReload = function(message) {
914
+ var _ref, _ref2;
915
+ this.log("LiveReload received reload request for " + message.path + ".");
916
+ return this.reloader.reload(message.path, {
917
+ liveCSS: (_ref = message.liveCSS) != null ? _ref : true,
918
+ liveImg: (_ref2 = message.liveImg) != null ? _ref2 : true,
919
+ originalPath: message.originalPath || '',
920
+ overrideURL: message.overrideURL || '',
921
+ serverURL: "http://" + this.options.host + ":" + this.options.port
922
+ });
923
+ };
924
+
925
+ LiveReload.prototype.performAlert = function(message) {
926
+ return alert(message.message);
927
+ };
928
+
929
+ LiveReload.prototype.shutDown = function() {
930
+ var _base;
931
+ this.connector.disconnect();
932
+ this.log("LiveReload disconnected.");
933
+ return typeof (_base = this.listeners).shutdown === "function" ? _base.shutdown() : void 0;
934
+ };
935
+
936
+ LiveReload.prototype.hasPlugin = function(identifier) {
937
+ return !!this.pluginIdentifiers[identifier];
938
+ };
939
+
940
+ LiveReload.prototype.addPlugin = function(pluginClass) {
941
+ var plugin;
942
+ var _this = this;
943
+ if (this.hasPlugin(pluginClass.identifier)) return;
944
+ this.pluginIdentifiers[pluginClass.identifier] = true;
945
+ plugin = new pluginClass(this.window, {
946
+ _livereload: this,
947
+ _reloader: this.reloader,
948
+ _connector: this.connector,
949
+ console: this.console,
950
+ Timer: Timer,
951
+ generateCacheBustUrl: function(url) {
952
+ return _this.reloader.generateCacheBustUrl(url);
953
+ }
954
+ });
955
+ this.plugins.push(plugin);
956
+ this.reloader.addPlugin(plugin);
957
+ };
958
+
959
+ LiveReload.prototype.analyze = function() {
960
+ var plugin, pluginData, pluginsData, _i, _len, _ref;
961
+ if (!(this.connector.protocol >= 7)) return;
962
+ pluginsData = {};
963
+ _ref = this.plugins;
964
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
965
+ plugin = _ref[_i];
966
+ pluginsData[plugin.constructor.identifier] = pluginData = (typeof plugin.analyze === "function" ? plugin.analyze() : void 0) || {};
967
+ pluginData.version = plugin.constructor.version;
968
+ }
969
+ this.connector.sendCommand({
970
+ command: 'info',
971
+ plugins: pluginsData,
972
+ url: this.window.location.href
973
+ });
974
+ };
975
+
976
+ return LiveReload;
977
+
978
+ })();
979
+
980
+ // less
981
+ var LessPlugin;
982
+ __less = LessPlugin = (function() {
983
+ LessPlugin.identifier = 'less';
984
+ LessPlugin.version = '1.0';
985
+ function LessPlugin(window, host) {
986
+ this.window = window;
987
+ this.host = host;
988
+ }
989
+ LessPlugin.prototype.reload = function(path, options) {
990
+ if (this.window.less && this.window.less.refresh) {
991
+ if (path.match(/\.less$/i)) {
992
+ return this.reloadLess(path);
993
+ }
994
+ if (options.originalPath.match(/\.less$/i)) {
995
+ return this.reloadLess(options.originalPath);
996
+ }
997
+ }
998
+ return false;
999
+ };
1000
+ LessPlugin.prototype.reloadLess = function(path) {
1001
+ var link, links, _i, _len;
1002
+ links = (function() {
1003
+ var _i, _len, _ref, _results;
1004
+ _ref = document.getElementsByTagName('link');
1005
+ _results = [];
1006
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
1007
+ link = _ref[_i];
1008
+ if (link.href && link.rel === 'stylesheet/less' || (link.rel.match(/stylesheet/) && link.type.match(/^text\/(x-)?less$/))) {
1009
+ _results.push(link);
1010
+ }
1011
+ }
1012
+ return _results;
1013
+ })();
1014
+ if (links.length === 0) {
1015
+ return false;
1016
+ }
1017
+ for (_i = 0, _len = links.length; _i < _len; _i++) {
1018
+ link = links[_i];
1019
+ link.href = this.host.generateCacheBustUrl(link.href);
1020
+ }
1021
+ this.host.console.log("LiveReload is asking LESS to recompile all stylesheets");
1022
+ this.window.less.refresh(true);
1023
+ return true;
1024
+ };
1025
+ LessPlugin.prototype.analyze = function() {
1026
+ return {
1027
+ disable: !!(this.window.less && this.window.less.refresh)
1028
+ };
1029
+ };
1030
+ return LessPlugin;
1031
+ })();
1032
+
1033
+ // startup
1034
+ var CustomEvents, LiveReload, k;
1035
+ CustomEvents = __customevents;
1036
+ LiveReload = window.LiveReload = new (__livereload.LiveReload)(window);
1037
+ for (k in window) {
1038
+ if (k.match(/^LiveReloadPlugin/)) {
1039
+ LiveReload.addPlugin(window[k]);
1040
+ }
1041
+ }
1042
+ LiveReload.addPlugin(__less);
1043
+ LiveReload.on('shutdown', function() {
1044
+ return delete window.LiveReload;
1045
+ });
1046
+ LiveReload.on('connect', function() {
1047
+ return CustomEvents.fire(document, 'LiveReloadConnect');
1048
+ });
1049
+ LiveReload.on('disconnect', function() {
1050
+ return CustomEvents.fire(document, 'LiveReloadDisconnect');
1051
+ });
1052
+ CustomEvents.bind(document, 'LiveReloadShutDown', function() {
1053
+ return LiveReload.shutDown();
1054
+ });
1055
+ })();