Compare commits

..

84 Commits

Author SHA1 Message Date
John Bintz
8cf0ebec60 deprecation notice 2018-04-04 08:29:05 -04:00
John Bintz
c0c47cc83b Merge pull request #72 from johnbintz/middleware-ordering-update
Middleware ordering update
2016-03-16 07:24:14 -04:00
John Bintz
16c1ba76ed [middleware-ordering] Improve middlware docs, fixes #51 2016-03-16 07:21:22 -04:00
John Bintz
f830396dbf [middleware-ordering] Switch from hashrockets -> new hash style 2016-03-16 07:21:00 -04:00
John Bintz
41bdc64bcb Bump version. 2015-07-03 12:33:50 -04:00
John Bintz
651d202dd2 Merge pull request #66 from johnbintz/travis-trimming
No one uses 1.8.7 or 1.9.2 anymore. Sorry, folks.
2015-07-03 12:33:29 -04:00
John Bintz
ffa5d63ffc Trim a little more out of the tests. 2015-07-03 12:28:20 -04:00
John Bintz
d06a756c8d RSpec modernization fixes. 2015-07-03 12:20:54 -04:00
John Bintz
ef3895cbcf No one uses 1.8.7 or 1.9.2 anymore. Sorry, folks. 2015-07-03 09:31:46 -04:00
John Bintz
727296953b Merge pull request #64 from cpjk/cpjk-fix-readme
Fix link to livereload.com
2015-07-03 09:24:56 -04:00
John Bintz
618feb865c Merge pull request #50 from rjocoleman/ignore_fullpath
parse fullpath for ignore
2015-07-03 09:24:47 -04:00
John Bintz
f5cbf07d21 Merge pull request #58 from pmoghaddam/fix_middleware_order
Update middleware order to resolve flash.keep issue and unnecessary logging
2015-07-03 09:21:33 -04:00
John Bintz
493aa6255d Merge pull request #53 from samcon/master
Set the protocol of the livereload source
2015-07-03 09:20:53 -04:00
Chris Kelly
e7cbf825d9 Fix link to livereload.com 2015-02-10 14:29:15 -08:00
Payam Moghaddam
9a4316ce37 Update middleware order to resolve flash.keep issue and unnecessary logging 2014-10-11 22:09:22 -07:00
John Bintz
952ea670dd Merge pull request #52 from johnkchow/master
Catch an IOError that happens on Ruby 2.0
2014-07-18 07:32:13 -04:00
John Bintz
5cdf2368e8 Merge pull request #56 from hirak/patch-1
explicitly state MIT License in gemspec
2014-06-10 14:37:48 -04:00
Hiraku NAKANO
5b5d62925f explicitly state MIT License in gemspec 2014-06-10 20:41:05 +09:00
samcon
27063b7d20 Set the protocol of the livereload source
This will allow a user to serve livereload from HTTPS
(Also requires using the :source => :livereload option)
2014-03-21 01:26:44 +02:00
John Chow
d67df1bb77 Catch an IOError that happens on Ruby 2.0
Would get a #<IOError: closed stream> error on Ruby 2.0
2014-03-01 13:12:48 -08:00
Robert Coleman
64449acbc9 parse fullpath for ignore 2013-11-26 13:35:21 +13:00
John Bintz
13ac1c85f3 Merge pull request #46 from zorab47/issue-45-clarify-readme-example
Clarify README's installation instructions
2013-09-16 07:17:21 -07:00
Charles Maresh
165f057cd3 Clarify README's installation instructions
Add detail to the Rails installation sections and show default
configuration options. Also simplify example's usage and add link to find
more information on Rails middlewares.
2013-09-13 13:55:17 -05:00
John Bintz
40a2ab41fb Merge pull request #44 from aspiers/patch-1
fix README reference to changing port
2013-09-06 13:51:00 -07:00
Adam Spiers
cd592e9c41 fix README reference to changing port
#25 forgot to update README.md to reference the new
`live_reload_port` option.
2013-09-06 16:17:07 +01:00
John Bintz
81a14b4357 Merge pull request #43 from swrobel/master
Fix typo in .travis.yml to prevent Rails 4 building on Ruby < 1.9.3
2013-08-22 18:36:05 -07:00
Stefan Wrobel
89b4a710e8 Fix typo in .travis.yml to prevent Rails 4 building on Ruby < 1.9.3 2013-08-21 23:57:55 -07:00
John Bintz
ab8fe2944a Merge pull request #42 from swrobel/only-get
Skip processing for non-GET requests
2013-08-20 16:02:07 -07:00
Stefan Wrobel
707d2955e0 Test on Ruby 2 && Rails 4 2013-08-20 15:56:47 -07:00
Stefan Wrobel
e9f4417307 Set max Nokogiri version for development on Ruby 1.8 2013-08-20 14:46:09 -07:00
Stefan Wrobel
a0ed81e1b4 Remove appraisal lock files to fix travis 2013-08-20 14:30:18 -07:00
Stefan Wrobel
117f251ff7 Skip processing for non-GET requests 2013-08-20 13:55:47 -07:00
John Bintz
81fb8384cb bump version 2013-05-24 08:32:50 -04:00
John Bintz
85a27e6bec Merge pull request #38 from sourcery/master
Reference the top-level Timeout, to avoid conflicts with Rack::Timeout
2013-05-24 05:31:54 -07:00
Ben Woosley
1891dbb920 Reference the top-level Timeout, to avoid conflicts with Rack::Timeout
Otherwise we get: #<NameError: uninitialized constant Rack::Timeout::Error>
2013-05-23 21:30:11 -07:00
John Bintz
e43835bdbf bump version for release 2013-05-15 10:58:30 -04:00
John Bintz
25a5d217f5 Merge pull request #37 from theo-bittencourt/dont-skip-processing-when-code-isnt-200
don't skip processing when response's status code isn't 200
2013-05-13 11:56:11 -07:00
Theo Bittencourt
d665d6e3f0 don't skip processing when response's status code isn't 200 2013-05-13 13:43:23 -03:00
John Bintz
176ce40003 a little more code shuffling to clean up the body processor class a bit 2013-05-06 09:36:15 -04:00
John Bintz
13e09428f3 add code climate badge, since we're starting to be more awesome in that regard 2013-04-30 10:07:23 -04:00
John Bintz
8cd7e10604 skipping the testing of a method bit me. d'oh 2013-04-26 10:47:41 -04:00
John Bintz
730a595be2 big refactoring 2013-04-23 16:28:23 -04:00
John Bintz
2eae7fd1bb Merge pull request #33 from ssendev/image-passthru
pass images through
2013-04-21 08:59:55 -07:00
ssendev
698ae43650 pass images through 2013-04-21 15:48:32 +02:00
John Bintz
03ea0608cd Merge pull request #27 from neo/master
Handle Rails apps running on relative root
2013-04-02 08:22:50 -07:00
Adam McCrea
45d5868aa6 Handle Rails apps running on relative root 2013-04-02 10:13:37 -04:00
John Bintz
1a6424b2bf bump version 2013-04-01 09:17:03 -04:00
John Bintz
20f03cbe7c ...and fix the test for that 2013-03-27 17:50:58 -04:00
John Bintz
2327695365 make sure rack livereload port gets set when swf is disabled 2013-03-27 17:50:06 -04:00
John Bintz
7c432f4438 bump version 2013-03-19 15:17:11 -04:00
John Bintz
306e5cd275 Merge pull request #30 from pdf/dont_mess_with_chunk_endcoded
Don't force iterating chunked (streamed) responses
2013-03-19 06:31:40 -07:00
Peter Fern
6b93954231 While we're at not messing with stuff, don't mess with inlined content 2013-03-19 17:32:46 +11:00
Peter Fern
75c50f7e92 Don't force iterating chunked (streamed) responses 2013-03-19 17:11:45 +11:00
John Bintz
c1415c32cb Merge pull request #28 from aptx4869/master
Escape ampersands in the generated URL
2013-03-18 03:25:24 -07:00
aptx4869
c9e096f77a escap & to &amp; 2013-03-17 08:57:39 +08:00
John Bintz
9aae5fa5fa Merge pull request #25 from davidmiani/master
Added an option to change the port used by rack-livereload
2013-02-03 06:07:38 -08:00
David Miani
206ce04e77 Adds the live_reload_port option.
This allows the javascript to work with a non default value for the
livereload port (specified with the port option in the guard command).

For example, in config/environments/development.rb in rails:
  config.middleware.insert_after(ActionDispatch::Static, Rack::LiveReload, :live_reload_port => 22351)
works with the following in the Guardfile:
  guard 'livereload', port: '22351' do
    ...
  end

This allows multiple guard livereload processes to operate at the same time without
conflicts.
2013-02-03 22:46:38 +11:00
John Bintz
85855cb25a don't try to close text/event-stream bodies, should fix #24 2013-01-24 10:22:54 -05:00
John Bintz
fbbea710d7 bump version, prep for release 2012-12-17 13:45:18 -05:00
John Bintz
b3bd9fa9a4 Merge pull request #21 from matthewlehner/head_tag_only
Fixes for head tag regex
2012-12-14 06:28:52 -08:00
Matthew Lehner
0c5d9bc06b add specs for regex 2012-12-13 16:27:16 -08:00
Matthew Lehner
706d158328 extract head_tag_regex to constant 2012-12-13 16:27:06 -08:00
John Bintz
6f9576215a Merge pull request #19 from matthewlehner/head_tag_only
Fix for "head" and "header" tag confusion
2012-12-13 16:08:58 -08:00
John Bintz
13c50ca118 Merge pull request #20 from matthewlehner/master
Correct deprecated methods and requires in test suite
2012-12-13 16:08:23 -08:00
Matthew Lehner
657157fcf6 fix deprecated require for mocha 2012-12-13 14:49:31 -08:00
Matthew Lehner
a69cdf1103 remove deprecated rspec option from guardfile 2012-12-13 14:49:15 -08:00
Matthew Lehner
cc1ad71a62 don't insert into <header> tags. 2012-12-13 14:41:30 -08:00
John Bintz
ebeb331f0f bump version and update readme for new behavior 2012-12-05 09:04:42 -05:00
John Bintz
394af20be2 put the javascript at the start of the head block 2012-12-04 14:12:53 -05:00
John Bintz
7a6a51cb62 potential fix for turbolinks load order problem 2012-12-03 14:00:45 -05:00
John Bintz
d1b9a6ef7b Merge pull request #16 from teamon/patch-1
Rack::Lock undefined for threadsafe Rails apps
2012-11-01 05:50:43 -07:00
Tymon Tobolski
6661507d83 Updated README for rails
Changed rails install guide so it will work for threadsafe apps too
2012-11-01 13:46:19 +01:00
John Bintz
7115365877 add license, fixes #14 2012-10-12 18:42:22 -04:00
John Bintz
2988401306 bump version 2012-09-18 17:23:39 -04:00
John Bintz
e998fab430 make sure things inserted into the body are actually strings 2012-09-18 17:22:19 -04:00
John Bintz
8c83b90015 Merge branch 'master' of github.com:johnbintz/rack-livereload 2012-07-10 17:51:10 -04:00
John Bintz
0d82995d8c update livereload.js 2012-07-10 17:50:52 -04:00
John Bintz
e51b347653 Merge pull request #11 from artemk/master
integration with travis ci
2012-06-05 06:41:31 -07:00
John Bintz
2ebc246b61 Merge pull request #10 from artemk/f235eab4421a2fb19e53a18ff6e7f33c0c25768f
no sense to add anything that is not 200.
2012-06-05 03:50:03 -07:00
Artem Kramarenko
926f052122 add travis icon to readme 2012-06-05 12:19:48 +03:00
Artem Kramarenko
859558622a touch 2012-05-30 19:51:24 +03:00
Artem Kramarenko
2f23626fe2 Appraisals and travis 2012-05-30 19:47:31 +03:00
Artem Kramarenko
f235eab442 no sense to add anything that is not 200. 2012-05-30 18:45:36 +03:00
John Bintz
e59329ed38 bump version 2012-03-06 14:38:50 -05:00
20 changed files with 1167 additions and 702 deletions

2
.gitignore vendored
View File

@ -1,5 +1,7 @@
*.gem
.bundle
Gemfile.lock
gemfiles/*.lock
pkg/*
*.orig
tmp/

10
.travis.yml Normal file
View File

@ -0,0 +1,10 @@
rvm:
- 1.9.3
- 2.0.0
branches:
only:
- master
gemfile:
- gemfiles/rails32.gemfile
- gemfiles/rails40.gemfile

7
Appraisals Normal file
View File

@ -0,0 +1,7 @@
appraise 'rails32' do
gem 'rails', '~> 3.2.0'
end
appraise 'rails40' do
gem 'rails', '~> 4.0.0'
end

View File

@ -1,7 +1,7 @@
# A sample Guardfile
# More info at https://github.com/guard/guard#readme
guard 'rspec', :version => 2, :cli => '-c' do
guard 'rspec', :cli => '-c' do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch('spec/spec_helper.rb') { "spec" }

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright © 2012 John Bintz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the “Software”), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,50 +1,75 @@
Hey, you've got [LiveReload](http://www.livereload.com/) in my [Rack](http://rack.rubyforge.org/)!
# Rack::LiveReload
_This fork is deprecated: Go check out https://github.com/onesupercoder/rack-livereload instead._
<a href="http://travis-ci.org/johnbintz/rack-livereload"><img src="https://secure.travis-ci.org/johnbintz/rack-livereload.png" /></a>
[![Code Climate](https://codeclimate.com/github/johnbintz/rack-livereload.png)](https://codeclimate.com/github/johnbintz/rack-livereload)
Hey, you've got [LiveReload](http://livereload.com/) in my [Rack](http://rack.rubyforge.org/)!
No need for browser extensions anymore! Just plug it in your middleware stack and go!
Even supports browsers without WebSockets!
Use this with [guard-livereload](http://github.com/guard/guard-livereload) for maximum fun!
## Install
`gem install rack-livereload`
## Using in...
## Installation
### Rails
In `config/environments/development.rb`:
Add the gem to your Gemfile.
```ruby
gem "rack-livereload", group: :development
```
Then add the middleware to your Rails middleware stack by editing your `config/environments/development.rb`.
```ruby
# config/environments/development.rb
MyApp::Application.configure do
config.middleware.insert_before(Rack::Lock, Rack::LiveReload)
# Add Rack::LiveReload to the bottom of the middleware stack with the default options:
config.middleware.insert_after ActionDispatch::Static, Rack::LiveReload
# ...or, change some options...
# or, if you're using better_errors:
config.middleware.insert_before Rack::Lock, Rack::LiveReload
config.middleware.insert_before(
Rack::Lock, Rack::LiveReload,
:min_delay => 500,
:max_delay => 10000,
:port => 56789,
:host => 'myhost.cool.wow',
:ignore => [ %r{dont/modify\.html$} ]
)
# ...
end
```
### config.ru/Sinatra
#### Tweaking the options
```ruby
# Specifying Rack::LiveReload options.
config.middleware.use(Rack::LiveReload,
min_delay : 500, # default 1000
max_delay : 10_000, # default 60_000
live_reload_port : 56789, # default 35729
host : 'myhost.cool.wow',
ignore : [ %r{dont/modify\.html$} ]
)
```
In addition, Rack::LiveReload's position within middleware stack can be
specified by inserting it relative to an exsiting middleware via
`insert_before` or `insert_after`. See the [Rails on Rack: Adding a
Middleware](http://guides.rubyonrails.org/rails_on_rack.html#adding-a-middleware)
section for more detail.
### Sinatra / config.ru
``` ruby
require 'rack-livereload'
use Rack::LiveReload
# ...or...
use Rack::LiveReload, :min_delay => 500, ...
use Rack::LiveReload, min_delay: 500, ...
```
## How it works
The necessary `script` tag to bring in a copy of [livereload.js](https://github.com/livereload/livereload-js) is
injected right before the closing `head` tag in any `text/html` pages that come through. The `script` tag is built in
injected right after the opening `head` tag in any `text/html` pages that come through. The `script` tag is built in
such a way that the `HTTP_HOST` is used as the LiveReload host, so you can connect from external machines (say, to
`mycomputer:3000` instead of `localhost:3000`) and as long as the LiveReload port is accessible from the external machine,
you'll connect and be LiveReloading away!
@ -66,17 +91,15 @@ your browser doesn't need it. The SWF WebSocket implementor won't be loaded unle
WebSockets support or if you force it in the middleware stack:
``` ruby
use Rack::LiveReload, :force_swf => true
use Rack::LiveReload, force_swf: true
```
If you don't want any of the web-sockets-js code included at all, use the `no_swf` option:
``` ruby
use Rack::LiveReload, :no_swf => true
use Rack::LiveReload, no_swf: true
```
Once more browsers support WebSockets than don't, this option will be reversed and you'll have
to explicitly include the Flash shim.
As usual, super-alpha!

View File

@ -1,4 +1,6 @@
require "bundler/gem_tasks"
require 'bundler/setup'
require 'appraisal'
desc 'Update livereload.js'
task :update_livereload_js do

7
gemfiles/rails32.gemfile Normal file
View File

@ -0,0 +1,7 @@
# This file was generated by Appraisal
source "http://rubygems.org"
gem "rails", "~> 3.2.0"
gemspec :path=>"../"

7
gemfiles/rails40.gemfile Normal file
View File

@ -0,0 +1,7 @@
# This file was generated by Appraisal
source "http://rubygems.org"
gem "rails", "~> 4.0.0"
gemspec :path=>"../"

View File

@ -123,12 +123,17 @@ __protocol.Parser = Parser = (function() {
})();
// connector
// Generated by CoffeeScript 1.3.3
var Connector, PROTOCOL_6, PROTOCOL_7, Parser, Version, _ref;
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
_ref = __protocol, Parser = _ref.Parser, PROTOCOL_6 = _ref.PROTOCOL_6, PROTOCOL_7 = _ref.PROTOCOL_7;
Version = '2.0.3';
Version = '2.0.8';
__connector.Connector = Connector = (function() {
function Connector(options, WebSocket, Timer, handlers) {
var _this = this;
this.options = options;
this.WebSocket = WebSocket;
this.Timer = Timer;
@ -138,64 +143,66 @@ __connector.Connector = Connector = (function() {
this._connectionDesired = false;
this.protocol = 0;
this.protocolParser = new Parser({
connected: __bind(function(protocol) {
this.protocol = protocol;
this._handshakeTimeout.stop();
this._nextDelay = this.options.mindelay;
this._disconnectionReason = 'broken';
return this.handlers.connected(protocol);
}, this),
error: __bind(function(e) {
this.handlers.error(e);
return this._closeOnError();
}, this),
message: __bind(function(message) {
return this.handlers.message(message);
}, this)
connected: function(protocol) {
_this.protocol = protocol;
_this._handshakeTimeout.stop();
_this._nextDelay = _this.options.mindelay;
_this._disconnectionReason = 'broken';
return _this.handlers.connected(protocol);
},
error: function(e) {
_this.handlers.error(e);
return _this._closeOnError();
},
message: function(message) {
return _this.handlers.message(message);
}
});
this._handshakeTimeout = new Timer(__bind(function() {
if (!this._isSocketConnected()) {
this._handshakeTimeout = new Timer(function() {
if (!_this._isSocketConnected()) {
return;
}
this._disconnectionReason = 'handshake-timeout';
return this.socket.close();
}, this));
this._reconnectTimer = new Timer(__bind(function() {
if (!this._connectionDesired) {
_this._disconnectionReason = 'handshake-timeout';
return _this.socket.close();
});
this._reconnectTimer = new Timer(function() {
if (!_this._connectionDesired) {
return;
}
return this.connect();
}, this));
return _this.connect();
});
this.connect();
}
Connector.prototype._isSocketConnected = function() {
return this.socket && this.socket.readyState === this.WebSocket.OPEN;
};
Connector.prototype.connect = function() {
var _this = this;
this._connectionDesired = true;
if (this._isSocketConnected()) {
return;
}
if (this._reconnectTimer) {
clearTimeout(this._reconnectTimer);
}
this._reconnectTimer.stop();
this._disconnectionReason = 'cannot-connect';
this.protocolParser.reset();
this.handlers.connecting();
this.socket = new this.WebSocket(this._uri);
this.socket.onopen = __bind(function(e) {
return this._onopen(e);
}, this);
this.socket.onclose = __bind(function(e) {
return this._onclose(e);
}, this);
this.socket.onmessage = __bind(function(e) {
return this._onmessage(e);
}, this);
return this.socket.onerror = __bind(function(e) {
return this._onerror(e);
}, this);
this.socket.onopen = function(e) {
return _this._onopen(e);
};
this.socket.onclose = function(e) {
return _this._onclose(e);
};
this.socket.onmessage = function(e) {
return _this._onmessage(e);
};
return this.socket.onerror = function(e) {
return _this._onerror(e);
};
};
Connector.prototype.disconnect = function() {
this._connectionDesired = false;
this._reconnectTimer.stop();
@ -205,6 +212,7 @@ __connector.Connector = Connector = (function() {
this._disconnectionReason = 'manual';
return this.socket.close();
};
Connector.prototype._scheduleReconnection = function() {
if (!this._connectionDesired) {
return;
@ -214,20 +222,24 @@ __connector.Connector = Connector = (function() {
return this._nextDelay = Math.min(this.options.maxdelay, this._nextDelay * 2);
}
};
Connector.prototype.sendCommand = function(command) {
if (this.protocol == null) {
return;
}
return this._sendCommand(command);
};
Connector.prototype._sendCommand = function(command) {
return this.socket.send(JSON.stringify(command));
};
Connector.prototype._closeOnError = function() {
this._handshakeTimeout.stop();
this._disconnectionReason = 'error';
return this.socket.close();
};
Connector.prototype._onopen = function(e) {
var hello;
this.handlers.socketConnected();
@ -249,16 +261,21 @@ __connector.Connector = Connector = (function() {
this._sendCommand(hello);
return this._handshakeTimeout.start(this.options.handshake_timeout);
};
Connector.prototype._onclose = function(e) {
this.protocol = 0;
this.handlers.disconnected(this._disconnectionReason, this._nextDelay);
return this._scheduleReconnection();
};
Connector.prototype._onerror = function(e) {};
Connector.prototype._onmessage = function(e) {
return this.protocolParser.process(e.data);
};
return Connector;
})();
// timer
@ -300,7 +317,7 @@ var Options;
__options.Options = Options = (function() {
function Options() {
this.host = null;
this.port = 35729;
this.port = RACK_LIVERELOAD_PORT;
this.snipver = null;
this.ext = null;
this.extver = null;
@ -349,8 +366,10 @@ Options.extract = function(document) {
};
// reloader
// Generated by CoffeeScript 1.3.1
(function() {
var IMAGE_STYLES, Reloader, numberOfMatchingSegments, pathFromUrl, pathsMatch, pickBestMatch, splitUrl;
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
splitUrl = function(url) {
var hash, index, params;
if ((index = url.indexOf('#')) >= 0) {
@ -371,6 +390,7 @@ splitUrl = function(url) {
hash: hash
};
};
pathFromUrl = function(url) {
var path;
url = splitUrl(url).url;
@ -381,6 +401,7 @@ pathFromUrl = function(url) {
}
return decodeURIComponent(path);
};
pickBestMatch = function(path, objects, pathFunc) {
var bestMatch, object, score, _i, _len;
bestMatch = {
@ -402,6 +423,7 @@ pickBestMatch = function(path, objects, pathFunc) {
return null;
}
};
numberOfMatchingSegments = function(path1, path2) {
var comps1, comps2, eqCount, len;
path1 = path1.replace(/^\/+/, '').toLowerCase();
@ -418,9 +440,11 @@ numberOfMatchingSegments = function(path1, path2) {
}
return eqCount;
};
pathsMatch = function(path1, path2) {
return numberOfMatchingSegments(path1, path2) > 0;
};
IMAGE_STYLES = [
{
selector: 'background',
@ -430,24 +454,34 @@ IMAGE_STYLES = [
styleNames: ['borderImage', 'webkitBorderImage', 'MozBorderImage']
}
];
__reloader.Reloader = Reloader = (function() {
Reloader.name = 'Reloader';
function Reloader(window, console, Timer) {
this.window = window;
this.console = console;
this.Timer = Timer;
this.document = this.window.document;
this.stylesheetGracePeriod = 200;
this.importCacheWaitPeriod = 200;
this.plugins = [];
}
Reloader.prototype.addPlugin = function(plugin) {
return this.plugins.push(plugin);
};
Reloader.prototype.analyze = function(callback) {
return results;
};
Reloader.prototype.reload = function(path, options) {
var plugin, _i, _len, _ref;
var plugin, _base, _i, _len, _ref;
this.options = options;
if ((_base = this.options).stylesheetReloadTimeout == null) {
_base.stylesheetReloadTimeout = 15000;
}
_ref = this.plugins;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
plugin = _ref[_i];
@ -470,11 +504,13 @@ __reloader.Reloader = Reloader = (function() {
}
return this.reloadPage();
};
Reloader.prototype.reloadPage = function() {
return this.window.document.location.reload();
};
Reloader.prototype.reloadImages = function(path) {
var expando, img, selector, styleNames, styleSheet, _i, _j, _k, _l, _len, _len2, _len3, _len4, _ref, _ref2, _ref3, _ref4, _results;
var expando, img, selector, styleNames, styleSheet, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, _results;
expando = this.generateUniqueString();
_ref = this.document.images;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
@ -484,27 +520,28 @@ __reloader.Reloader = Reloader = (function() {
}
}
if (this.document.querySelectorAll) {
for (_j = 0, _len2 = IMAGE_STYLES.length; _j < _len2; _j++) {
_ref2 = IMAGE_STYLES[_j], selector = _ref2.selector, styleNames = _ref2.styleNames;
_ref3 = this.document.querySelectorAll("[style*=" + selector + "]");
for (_k = 0, _len3 = _ref3.length; _k < _len3; _k++) {
img = _ref3[_k];
for (_j = 0, _len1 = IMAGE_STYLES.length; _j < _len1; _j++) {
_ref1 = IMAGE_STYLES[_j], selector = _ref1.selector, styleNames = _ref1.styleNames;
_ref2 = this.document.querySelectorAll("[style*=" + selector + "]");
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
img = _ref2[_k];
this.reloadStyleImages(img.style, styleNames, path, expando);
}
}
}
if (this.document.styleSheets) {
_ref4 = this.document.styleSheets;
_ref3 = this.document.styleSheets;
_results = [];
for (_l = 0, _len4 = _ref4.length; _l < _len4; _l++) {
styleSheet = _ref4[_l];
for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) {
styleSheet = _ref3[_l];
_results.push(this.reloadStylesheetImages(styleSheet, path, expando));
}
return _results;
}
};
Reloader.prototype.reloadStylesheetImages = function(styleSheet, path, expando) {
var rule, rules, styleNames, _i, _j, _len, _len2;
var rule, rules, styleNames, _i, _j, _len, _len1;
try {
rules = styleSheet != null ? styleSheet.cssRules : void 0;
} catch (e) {
@ -520,7 +557,7 @@ __reloader.Reloader = Reloader = (function() {
this.reloadStylesheetImages(rule.styleSheet, path, expando);
break;
case CSSRule.STYLE_RULE:
for (_j = 0, _len2 = IMAGE_STYLES.length; _j < _len2; _j++) {
for (_j = 0, _len1 = IMAGE_STYLES.length; _j < _len1; _j++) {
styleNames = IMAGE_STYLES[_j].styleNames;
this.reloadStyleImages(rule.style, styleNames, path, expando);
}
@ -530,27 +567,31 @@ __reloader.Reloader = Reloader = (function() {
}
}
};
Reloader.prototype.reloadStyleImages = function(style, styleNames, path, expando) {
var newValue, styleName, value, _i, _len;
var newValue, styleName, value, _i, _len,
_this = this;
for (_i = 0, _len = styleNames.length; _i < _len; _i++) {
styleName = styleNames[_i];
value = style[styleName];
if (typeof value === 'string') {
newValue = value.replace(/\burl\s*\(([^)]*)\)/, __bind(function(match, src) {
newValue = value.replace(/\burl\s*\(([^)]*)\)/, function(match, src) {
if (pathsMatch(path, pathFromUrl(src))) {
return "url(" + (this.generateCacheBustUrl(src, expando)) + ")";
return "url(" + (_this.generateCacheBustUrl(src, expando)) + ")";
} else {
return match;
}
}, this));
});
if (newValue !== value) {
style[styleName] = newValue;
}
}
}
};
Reloader.prototype.reloadStylesheet = function(path) {
var imported, link, links, match, style, _i, _j, _k, _len, _len2, _len3, _ref;
var imported, link, links, match, style, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1,
_this = this;
links = (function() {
var _i, _len, _ref, _results;
_ref = this.document.getElementsByTagName('link');
@ -571,40 +612,48 @@ __reloader.Reloader = Reloader = (function() {
this.collectImportedStylesheets(style, style.sheet, imported);
}
}
for (_j = 0, _len2 = links.length; _j < _len2; _j++) {
for (_j = 0, _len1 = links.length; _j < _len1; _j++) {
link = links[_j];
this.collectImportedStylesheets(link, link.sheet, imported);
}
if (this.window.StyleFix && this.document.querySelectorAll) {
_ref1 = this.document.querySelectorAll('style[data-href]');
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
style = _ref1[_k];
links.push(style);
}
}
this.console.log("LiveReload found " + links.length + " LINKed stylesheets, " + imported.length + " @imported stylesheets");
match = pickBestMatch(path, links.concat(imported), function(l) {
return pathFromUrl(l.href);
return pathFromUrl(_this.linkHref(l));
});
if (match) {
if (match.object.rule) {
this.console.log("LiveReload is reloading imported stylesheet: " + match.object.href);
this.reattachImportedRule(match.object);
} else {
this.console.log("LiveReload is reloading stylesheet: " + match.object.href);
this.console.log("LiveReload is reloading stylesheet: " + (this.linkHref(match.object)));
this.reattachStylesheetLink(match.object);
}
} else {
this.console.log("LiveReload will reload all stylesheets because path '" + path + "' did not match any specific one");
for (_k = 0, _len3 = links.length; _k < _len3; _k++) {
link = links[_k];
for (_l = 0, _len3 = links.length; _l < _len3; _l++) {
link = links[_l];
this.reattachStylesheetLink(link);
}
}
return true;
};
Reloader.prototype.collectImportedStylesheets = function(link, styleSheet, result) {
var index, rule, rules, _len;
var index, rule, rules, _i, _len;
try {
rules = styleSheet != null ? styleSheet.cssRules : void 0;
} catch (e) {
}
if (rules && rules.length) {
for (index = 0, _len = rules.length; index < _len; index++) {
for (index = _i = 0, _len = rules.length; _i < _len; index = ++_i) {
rule = rules[index];
switch (rule.type) {
case CSSRule.CHARSET_RULE:
@ -624,29 +673,84 @@ __reloader.Reloader = Reloader = (function() {
}
}
};
Reloader.prototype.waitUntilCssLoads = function(clone, func) {
var callbackExecuted, executeCallback, poll,
_this = this;
callbackExecuted = false;
executeCallback = function() {
if (callbackExecuted) {
return;
}
callbackExecuted = true;
return func();
};
clone.onload = function() {
console.log("onload!");
_this.knownToSupportCssOnLoad = true;
return executeCallback();
};
if (!this.knownToSupportCssOnLoad) {
(poll = function() {
if (clone.sheet) {
console.log("polling!");
return executeCallback();
} else {
return _this.Timer.start(50, poll);
}
})();
}
return this.Timer.start(this.options.stylesheetReloadTimeout, executeCallback);
};
Reloader.prototype.linkHref = function(link) {
return link.href || link.getAttribute('data-href');
};
Reloader.prototype.reattachStylesheetLink = function(link) {
var clone, parent, timer;
var clone, parent,
_this = this;
if (link.__LiveReload_pendingRemoval) {
return;
}
link.__LiveReload_pendingRemoval = true;
if (link.tagName === 'STYLE') {
clone = this.document.createElement('link');
clone.rel = 'stylesheet';
clone.media = link.media;
clone.disabled = link.disabled;
} else {
clone = link.cloneNode(false);
clone.href = this.generateCacheBustUrl(link.href);
}
clone.href = this.generateCacheBustUrl(this.linkHref(link));
parent = link.parentNode;
if (parent.lastChild === link) {
parent.appendChild(clone);
} else {
parent.insertBefore(clone, link.nextSibling);
}
timer = new this.Timer(function() {
if (link.parentNode) {
return link.parentNode.removeChild(link);
return this.waitUntilCssLoads(clone, function() {
var additionalWaitingTime;
if (/AppleWebKit/.test(navigator.userAgent)) {
additionalWaitingTime = 5;
} else {
additionalWaitingTime = 200;
}
return _this.Timer.start(additionalWaitingTime, function() {
var _ref;
if (!link.parentNode) {
return;
}
link.parentNode.removeChild(link);
clone.onreadystatechange = null;
return (_ref = _this.window.StyleFix) != null ? _ref.link(clone) : void 0;
});
});
return timer.start(this.stylesheetGracePeriod);
};
Reloader.prototype.reattachImportedRule = function(_arg) {
var href, index, link, media, newRule, parent, rule, tempLink;
var href, index, link, media, newRule, parent, rule, tempLink,
_this = this;
rule = _arg.rule, index = _arg.index, link = _arg.link;
parent = rule.parentStyleSheet;
href = this.generateCacheBustUrl(rule.href);
@ -660,7 +764,7 @@ __reloader.Reloader = Reloader = (function() {
if (link.parentNode) {
link.parentNode.insertBefore(tempLink, link);
}
return this.Timer.start(this.importCacheWaitPeriod, __bind(function() {
return this.Timer.start(this.importCacheWaitPeriod, function() {
if (tempLink.parentNode) {
tempLink.parentNode.removeChild(tempLink);
}
@ -671,24 +775,31 @@ __reloader.Reloader = Reloader = (function() {
parent.deleteRule(index + 1);
rule = parent.cssRules[index];
rule.__LiveReload_newHref = href;
return this.Timer.start(this.importCacheWaitPeriod, __bind(function() {
return _this.Timer.start(_this.importCacheWaitPeriod, function() {
if (rule.__LiveReload_newHref !== href) {
return;
}
parent.insertRule(newRule, index);
return parent.deleteRule(index + 1);
}, this));
}, this));
});
});
};
Reloader.prototype.generateUniqueString = function() {
return 'livereload=' + Date.now();
};
Reloader.prototype.generateCacheBustUrl = function(url, expando) {
var hash, oldParams, params, _ref;
if (expando == null) {
expando = this.generateUniqueString();
}
_ref = splitUrl(url), url = _ref.url, hash = _ref.hash, oldParams = _ref.params;
if (this.options.overrideURL) {
if (url.indexOf(this.options.serverURL) < 0) {
url = this.options.serverURL + this.options.overrideURL + "?url=" + encodeURIComponent(url);
}
}
params = oldParams.replace(/(\?|&)livereload=(\d+)/, function(match, sep) {
return "" + sep + expando;
});
@ -701,23 +812,33 @@ __reloader.Reloader = Reloader = (function() {
}
return url + params + hash;
};
return Reloader;
})();
}).call(this);
// livereload
var Connector, LiveReload, Options, Reloader, Timer;
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
Connector = __connector.Connector;
Timer = __timer.Timer;
Options = __options.Options;
Reloader = __reloader.Reloader;
__livereload.LiveReload = LiveReload = (function() {
function LiveReload(window) {
var _this = this;
this.window = window;
this.listeners = {};
this.plugins = [];
this.pluginIdentifiers = {};
this.console = this.window.console && this.window.console.log && this.window.console.error ? this.window.console : {
this.console = this.window.location.href.match(/LR-verbose/) && this.window.console && this.window.console.log && this.window.console.error ? this.window.console : {
log: function() {},
error: function() {}
};
@ -731,87 +852,95 @@ __livereload.LiveReload = LiveReload = (function() {
}
this.reloader = new Reloader(this.window, this.console, Timer);
this.connector = new Connector(this.options, this.WebSocket, Timer, {
connecting: __bind(function() {}, this),
socketConnected: __bind(function() {}, this),
connected: __bind(function(protocol) {
connecting: function() {},
socketConnected: function() {},
connected: function(protocol) {
var _base;
if (typeof (_base = this.listeners).connect === "function") {
if (typeof (_base = _this.listeners).connect === "function") {
_base.connect();
}
this.log("LiveReload is connected to " + this.options.host + ":" + this.options.port + " (protocol v" + protocol + ").");
return this.analyze();
}, this),
error: __bind(function(e) {
_this.log("LiveReload is connected to " + _this.options.host + ":" + _this.options.port + " (protocol v" + protocol + ").");
return _this.analyze();
},
error: function(e) {
if (e instanceof ProtocolError) {
return console.log("" + e.message + ".");
} else {
return console.log("LiveReload internal error: " + e.message);
}
}, this),
disconnected: __bind(function(reason, nextDelay) {
},
disconnected: function(reason, nextDelay) {
var _base;
if (typeof (_base = this.listeners).disconnect === "function") {
if (typeof (_base = _this.listeners).disconnect === "function") {
_base.disconnect();
}
switch (reason) {
case 'cannot-connect':
return this.log("LiveReload cannot connect to " + this.options.host + ":" + this.options.port + ", will retry in " + nextDelay + " sec.");
return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + ", will retry in " + nextDelay + " sec.");
case 'broken':
return this.log("LiveReload disconnected from " + this.options.host + ":" + this.options.port + ", reconnecting in " + nextDelay + " sec.");
return _this.log("LiveReload disconnected from " + _this.options.host + ":" + _this.options.port + ", reconnecting in " + nextDelay + " sec.");
case 'handshake-timeout':
return this.log("LiveReload cannot connect to " + this.options.host + ":" + this.options.port + " (handshake timeout), will retry in " + nextDelay + " sec.");
return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + " (handshake timeout), will retry in " + nextDelay + " sec.");
case 'handshake-failed':
return this.log("LiveReload cannot connect to " + this.options.host + ":" + this.options.port + " (handshake failed), will retry in " + nextDelay + " sec.");
return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + " (handshake failed), will retry in " + nextDelay + " sec.");
case 'manual':
break;
case 'error':
break;
default:
return this.log("LiveReload disconnected from " + this.options.host + ":" + this.options.port + " (" + reason + "), reconnecting in " + nextDelay + " sec.");
return _this.log("LiveReload disconnected from " + _this.options.host + ":" + _this.options.port + " (" + reason + "), reconnecting in " + nextDelay + " sec.");
}
}, this),
message: __bind(function(message) {
},
message: function(message) {
switch (message.command) {
case 'reload':
return this.performReload(message);
return _this.performReload(message);
case 'alert':
return this.performAlert(message);
return _this.performAlert(message);
}
}
}, this)
});
}
LiveReload.prototype.on = function(eventName, handler) {
return this.listeners[eventName] = handler;
};
LiveReload.prototype.log = function(message) {
return this.console.log("" + message);
};
LiveReload.prototype.performReload = function(message) {
var _ref, _ref2;
this.log("LiveReload received reload request for " + message.path + ".");
return this.reloader.reload(message.path, {
liveCSS: (_ref = message.liveCSS) != null ? _ref : true,
liveImg: (_ref2 = message.liveImg) != null ? _ref2 : true,
originalPath: message.originalPath || ''
originalPath: message.originalPath || '',
overrideURL: message.overrideURL || '',
serverURL: "http://" + this.options.host + ":" + this.options.port
});
};
LiveReload.prototype.performAlert = function(message) {
return alert(message.message);
};
LiveReload.prototype.shutDown = function() {
var _base;
this.connector.disconnect();
this.log("LiveReload disconnected.");
return typeof (_base = this.listeners).shutdown === "function" ? _base.shutdown() : void 0;
};
LiveReload.prototype.hasPlugin = function(identifier) {
return !!this.pluginIdentifiers[identifier];
};
LiveReload.prototype.addPlugin = function(pluginClass) {
var plugin;
if (this.hasPlugin(pluginClass.identifier)) {
return;
}
var _this = this;
if (this.hasPlugin(pluginClass.identifier)) return;
this.pluginIdentifiers[pluginClass.identifier] = true;
plugin = new pluginClass(this.window, {
_livereload: this,
@ -819,18 +948,17 @@ __livereload.LiveReload = LiveReload = (function() {
_connector: this.connector,
console: this.console,
Timer: Timer,
generateCacheBustUrl: __bind(function(url) {
return this.reloader.generateCacheBustUrl(url);
}, this)
generateCacheBustUrl: function(url) {
return _this.reloader.generateCacheBustUrl(url);
}
});
this.plugins.push(plugin);
this.reloader.addPlugin(plugin);
};
LiveReload.prototype.analyze = function() {
var plugin, pluginData, pluginsData, _i, _len, _ref;
if (!(this.connector.protocol >= 7)) {
return;
}
if (!(this.connector.protocol >= 7)) return;
pluginsData = {};
_ref = this.plugins;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
@ -844,7 +972,9 @@ __livereload.LiveReload = LiveReload = (function() {
url: this.window.location.href
});
};
return LiveReload;
})();
// less
@ -901,13 +1031,12 @@ __less = LessPlugin = (function() {
})();
// startup
var CustomEvents, LiveReload, k, v;
var CustomEvents, LiveReload, k;
CustomEvents = __customevents;
LiveReload = window.LiveReload = new (__livereload.LiveReload)(window);
for (k in window) {
v = window[k];
if (k.match(/^LiveReloadPlugin/)) {
LiveReload.addPlugin(v);
LiveReload.addPlugin(window[k]);
}
}
LiveReload.addPlugin(__less);

View File

@ -1,6 +1,6 @@
require "rack/livereload"
class Rack::LiveReload
VERSION = '0.3.5'
VERSION = '0.3.16'
end

View File

@ -1,46 +1,15 @@
require 'erb'
require 'rack/livereload/processing_skip_analyzer'
require 'rack/livereload/body_processor'
module Rack
class LiveReload
LIVERELOAD_JS_PATH = '/__rack/livereload.js'
LIVERELOAD_LOCAL_URI = 'http://localhost:35729/livereload.js'
BAD_USER_AGENTS = [ %r{MSIE} ]
attr_reader :app
def initialize(app, options = {})
@app, @options = app, options
end
def use_vendored?
return @use_vendored if @use_vendored
if @options[:source]
@use_vendored = (@options[:source] == :vendored)
else
require 'net/http'
require 'uri'
uri = URI.parse(LIVERELOAD_LOCAL_URI)
http = Net::HTTP.new(uri.host, uri.port)
http.read_timeout = 1
begin
http.send_request('GET', uri.path)
@use_vendored = false
rescue Timeout::Error, Errno::ECONNREFUSED, EOFError
@use_vendored = true
rescue => e
$stderr.puts e.inspect
raise e
end
end
@use_vendored
end
def call(env)
dup._call(env)
end
@ -51,58 +20,21 @@ module Rack
if path == '__rack' && ::File.file?(target = ::File.expand_path("../../../js/#{file}", __FILE__))
deliver_file(target)
else
status, headers, body = @app.call(env)
status, headers, body = result = @app.call(env)
new_body = []
return result if ProcessingSkipAnalyzer.skip_processing?(result, env, @options)
body.each do |line|
new_body << line
processor = BodyProcessor.new(body, @options)
processor.process!(env)
headers['Content-Length'] = processor.content_length.to_s
if processor.livereload_added
headers['X-Rack-LiveReload'] = '1'
end
body.close if body.respond_to?(:close)
if !ignored?(env['PATH_INFO']) && !bad_browser?(env['HTTP_USER_AGENT'])
if headers['Content-Type'] && headers['Content-Type'][%r{text/html}]
content_length = 0
new_body.each do |line|
if !headers['X-Rack-LiveReload'] && line['</head>']
host_to_use = (@options[:host] || env['HTTP_HOST'] || 'localhost').gsub(%r{:.*}, '')
if use_vendored?
src = LIVERELOAD_JS_PATH.dup + "?host=#{host_to_use}"
else
src = LIVERELOAD_LOCAL_URI.dup.gsub('localhost', host_to_use) + '?'
[ status, headers, processor.new_body ]
end
src << "&mindelay=#{@options[:min_delay]}" if @options[:min_delay]
src << "&maxdelay=#{@options[:max_delay]}" if @options[:max_delay]
src << "&port=#{@options[:port]}" if @options[:port]
template = ERB.new(::File.read(::File.expand_path('../../../skel/livereload.html.erb', __FILE__)))
line.gsub!('</head>', %{#{template.result(binding)}</head>})
headers["X-Rack-LiveReload"] = '1'
end
content_length += line.bytesize
end
headers['Content-Length'] = content_length.to_s
end
end
[ status, headers, new_body ]
end
end
def ignored?(path_info)
@options[:ignore] and @options[:ignore].any? { |filter| path_info[filter] }
end
def bad_browser?(user_agent)
BAD_USER_AGENTS.any? { |pattern| (user_agent || '')[pattern] }
end
private
@ -116,14 +48,6 @@ module Rack
[ 200, { 'Content-Type' => type, 'Content-Length' => ::File.size(file).to_s }, [ ::File.read(file) ] ]
end
def force_swf?
@options[:force_swf]
end
def with_swf?
!@options[:no_swf]
end
end
end

View File

@ -0,0 +1,116 @@
require 'rack/livereload'
module Rack
class LiveReload
class BodyProcessor
LIVERELOAD_JS_PATH = '/__rack/livereload.js'
HEAD_TAG_REGEX = /<head>|<head[^(er)][^<]*>/
LIVERELOAD_PORT = 35729
attr_reader :content_length, :new_body, :livereload_added
def protocol
@options[:protocol] || "http"
end
def livereload_local_uri
"#{protocol}://localhost:#{@options[:live_reload_port]}/livereload.js"
end
def initialize(body, options)
@body, @options = body, options
@options[:live_reload_port] ||= LIVERELOAD_PORT
@processed = false
end
def force_swf?
@options[:force_swf]
end
def with_swf?
!@options[:no_swf]
end
def use_vendored?
return @use_vendored if @use_vendored
if @options[:source]
@use_vendored = (@options[:source] == :vendored)
else
require 'net/http'
require 'uri'
uri = URI.parse(livereload_local_uri)
http = Net::HTTP.new(uri.host, uri.port)
http.read_timeout = 1
begin
http.send_request('GET', uri.path)
@use_vendored = false
rescue ::Timeout::Error, Errno::ECONNREFUSED, EOFError, IOError
@use_vendored = true
rescue => e
$stderr.puts e.inspect
raise e
end
end
@use_vendored
end
def processed?
@processed
end
def process!(env)
@env = env
@body.close if @body.respond_to?(:close)
@new_body = [] ; @body.each { |line| @new_body << line.to_s }
@content_length = 0
@livereload_added = false
@new_body.each do |line|
if !@livereload_added && line['<head']
line.gsub!(HEAD_TAG_REGEX) { |match| %{#{match}#{template.result(binding)}} }
@livereload_added = true
end
@content_length += line.bytesize
@processed = true
end
end
def app_root
ENV['RAILS_RELATIVE_URL_ROOT'] || ''
end
def host_to_use
(@options[:host] || @env['HTTP_HOST'] || 'localhost').gsub(%r{:.*}, '')
end
def template
ERB.new(::File.read(::File.expand_path('../../../../skel/livereload.html.erb', __FILE__)))
end
def livereload_source
if use_vendored?
src = "#{app_root}#{LIVERELOAD_JS_PATH.dup}?host=#{host_to_use}"
else
src = livereload_local_uri.dup.gsub('localhost', host_to_use) + '?'
end
src << "&amp;mindelay=#{@options[:min_delay]}" if @options[:min_delay]
src << "&amp;maxdelay=#{@options[:max_delay]}" if @options[:max_delay]
src << "&amp;port=#{@options[:port]}" if @options[:port]
src
end
end
end
end

View File

@ -0,0 +1,49 @@
require 'rack/livereload'
module Rack
class LiveReload
class ProcessingSkipAnalyzer
BAD_USER_AGENTS = [ %r{MSIE} ]
def self.skip_processing?(result, env, options)
new(result, env, options).skip_processing?
end
def initialize(result, env, options)
@env, @options = env, options
@status, @headers, @body = result
end
def skip_processing?
!html? || chunked? || inline? || ignored? || bad_browser? || !get?
end
def chunked?
@headers['Transfer-Encoding'] == 'chunked'
end
def inline?
@headers['Content-Disposition'] =~ %r{^inline}
end
def ignored?
path = @env['QUERY_STRING'].empty? ? @env['PATH_INFO'] : "#{@env['PATH_INFO']}?#{@env['QUERY_STRING']}"
@options[:ignore] and @options[:ignore].any? { |filter| path[filter] }
end
def bad_browser?
BAD_USER_AGENTS.any? { |pattern| @env['HTTP_USER_AGENT'] =~ pattern }
end
def html?
@headers['Content-Type'] =~ %r{text/html}
end
def get?
@env['REQUEST_METHOD'] == 'GET'
end
end
end
end

View File

@ -8,6 +8,7 @@ Gem::Specification.new do |s|
s.authors = ["John Bintz"]
s.email = ["john@coswellproductions.com"]
s.homepage = ""
s.license = "MIT"
s.summary = %q{Insert LiveReload into your app easily as Rack middleware}
s.description = %q{Insert LiveReload into your app easily as Rack middleware}
@ -32,7 +33,8 @@ Gem::Specification.new do |s|
s.add_development_dependency "guard-cucumber"
s.add_development_dependency "guard-livereload"
s.add_development_dependency "webmock"
s.add_development_dependency "nokogiri", ("< 1.6" if RUBY_VERSION < "1.9") # Nokogiri >= 1.6 requires Ruby >= 1.9
s.add_development_dependency 'appraisal', '~> 0.4'
s.add_runtime_dependency "rack"
end

View File

@ -5,8 +5,11 @@
WEB_SOCKET_FORCE_FLASH = true;
<% end %>
</script>
<script type="text/javascript" src="/__rack/swfobject.js"></script>
<script type="text/javascript" src="/__rack/web_socket.js"></script>
<script type="text/javascript" src="<%= app_root %>/__rack/swfobject.js"></script>
<script type="text/javascript" src="<%= app_root %>/__rack/web_socket.js"></script>
<% end %>
<script type="text/javascript" src="<%= src %>"></script>
<script type="text/javascript">
RACK_LIVERELOAD_PORT = <%= @options[:live_reload_port] %>;
</script>
<script type="text/javascript" src="<%= livereload_source %>"></script>

View File

@ -0,0 +1,200 @@
require 'spec_helper'
require 'nokogiri'
describe Rack::LiveReload::BodyProcessor do
describe 'head tag regex' do
let(:regex) { described_class::HEAD_TAG_REGEX }
subject { regex }
it { should be_kind_of(Regexp) }
it 'only picks a valid <head> tag' do
regex.match("<head></head>").to_s.should eq('<head>')
regex.match("<head><title></title></head>").to_s.should eq('<head>')
regex.match("<head attribute='something'><title></title></head>").to_s.should eq("<head attribute='something'>")
end
it 'responds false when no head tag' do
regex.match("<header></header>").should be_falsey
end
end
let(:processor) { described_class.new(body, options) }
let(:body) { [ page_html ] }
let(:options) { {} }
let(:page_html) { '<head></head>' }
let(:processor_result) do
if !processor.processed?
processor.process!(env)
end
processor
end
subject { processor }
describe "livereload local uri" do
context 'does not exist' do
before do
stub_request(:any, 'localhost:35729/livereload.js').to_timeout
end
it { should use_vendored }
end
context 'exists' do
before do
stub_request(:any, 'localhost:35729/livereload.js')
end
it { should_not use_vendored }
end
context 'with custom port' do
let(:options) { {:live_reload_port => '12348'}}
context 'exists' do
before do
stub_request(:any, 'localhost:12348/livereload.js')
end
it { should_not use_vendored }
end
end
context 'specify vendored' do
let(:options) { { :source => :vendored } }
it { should use_vendored }
end
context 'specify LR' do
let(:options) { { :source => :livereload } }
it { should_not use_vendored }
end
end
context 'text/html' do
before do
processor.stubs(:use_vendored?).returns(true)
end
let(:host) { 'host' }
let(:env) { { 'HTTP_HOST' => host } }
let(:processed_body) { processor_result.new_body.join('') }
let(:length) { processor_result.content_length }
let(:page_html) { '<head></head>' }
context 'vendored' do
it 'should add the vendored livereload js script tag' do
processed_body.should include("script")
processed_body.should include(described_class::LIVERELOAD_JS_PATH)
length.to_s.should == processed_body.length.to_s
described_class::LIVERELOAD_JS_PATH.should_not include(host)
processed_body.should include('swfobject')
processed_body.should include('web_socket')
end
end
context 'at the top of the head tag' do
let(:page_html) { '<head attribute="attribute"><script type="text/javascript" insert="first"></script><script type="text/javascript" insert="before"></script></head>' }
let(:body_dom) { Nokogiri::XML(processed_body) }
it 'should add the livereload js script tag before all other script tags' do
body_dom.at_css("head")[:attribute].should == 'attribute'
body_dom.at_css("script:eq(5)")[:src].should include(described_class::LIVERELOAD_JS_PATH)
body_dom.at_css("script:last-child")[:insert].should == "before"
end
context 'when a relative URL root is specified' do
before do
ENV['RAILS_RELATIVE_URL_ROOT'] = '/a_relative_path'
end
it 'should prepend the relative path to the script src' do
body_dom.at_css("script:eq(5)")[:src].should match(%r{^/a_relative_path/})
end
end
end
describe "LIVERELOAD_PORT value" do
let(:options) { { :live_reload_port => 12345 }}
it "sets the variable at the top of the file" do
processed_body.should include 'RACK_LIVERELOAD_PORT = 12345'
end
end
context 'in header tags' do
let(:page_html) { "<header class='hero'><h1>Just a normal header tag</h1></header>" }
let(:body_dom) { Nokogiri::XML(processed_body) }
it 'should not add the livereload js' do
body_dom.at_css("header")[:class].should == 'hero'
body_dom.css('script').should be_empty
end
end
context 'not vendored' do
before do
processor.stubs(:use_vendored?).returns(false)
end
it 'should add the LR livereload js script tag' do
processed_body.should include("script")
processed_body.should include(processor.livereload_local_uri.gsub('localhost', 'host'))
end
end
context 'set options' do
let(:options) { { :host => new_host, :port => port, :min_delay => min_delay, :max_delay => max_delay } }
let(:min_delay) { 5 }
let(:max_delay) { 10 }
let(:port) { 23 }
let(:new_host) { 'myhost' }
it 'should add the livereload.js script tag' do
processed_body.should include("mindelay=#{min_delay}")
processed_body.should include("maxdelay=#{max_delay}")
processed_body.should include("port=#{port}")
processed_body.should include("host=#{new_host}")
end
end
context 'force flash' do
let(:options) { { :force_swf => true } }
it 'should not add the flash shim' do
processed_body.should include('WEB_SOCKET_FORCE_FLASH')
processed_body.should include('swfobject')
processed_body.should include('web_socket')
end
end
context 'no flash' do
let(:options) { { :no_swf => true } }
it 'should not add the flash shim' do
processed_body.should_not include('swfobject')
processed_body.should_not include('web_socket')
end
end
context 'no host at all' do
let(:env) { {} }
it 'should use localhost' do
processed_body.should include('localhost')
end
end
end
end

View File

@ -0,0 +1,137 @@
require 'spec_helper'
describe Rack::LiveReload::ProcessingSkipAnalyzer do
subject { described_class.new(result, env, options) }
let(:result) { [ status, headers, body ] }
let(:env) { { 'HTTP_USER_AGENT' => user_agent } }
let(:options) { {} }
let(:user_agent) { 'Firefox' }
let(:status) { 200 }
let(:headers) { {} }
let(:body) { [] }
describe '#skip_processing?' do
it "should skip processing" do
subject.skip_processing?.should be_truthy
end
end
describe '#ignored?' do
let(:options) { { :ignore => [ %r{file} ] } }
context 'path contains ignore pattern' do
let(:env) { { 'PATH_INFO' => '/this/file', 'QUERY_STRING' => '' } }
it { should be_ignored }
end
context 'root path' do
let(:env) { { 'PATH_INFO' => '/', 'QUERY_STRING' => '' } }
it { should_not be_ignored }
end
end
describe '#chunked?' do
context 'regular response' do
it { should_not be_chunked }
end
context 'chunked response' do
let(:headers) { { 'Transfer-Encoding' => 'chunked' } }
it { should be_chunked }
end
end
describe '#inline?' do
context 'inline disposition' do
let(:headers) { { 'Content-Disposition' => 'inline; filename=my_inlined_file' } }
it { should be_inline }
end
end
describe '#ignored?' do
let(:path_info) { 'path info' }
let(:query_string) { 'query_string' }
let(:env) { { 'PATH_INFO' => path_info, 'QUERY_STRING' => query_string } }
context 'no ignore set' do
it { should_not be_ignored }
end
context 'ignore set' do
let(:options) { { :ignore => [ %r{#{path_info}} ] } }
it { should be_ignored }
end
context 'ignore set including query_string' do
let(:options) { { :ignore => [ %r{#{path_info}\?#{query_string}} ] } }
it { should be_ignored }
end
end
describe '#bad_browser?' do
context 'Firefox' do
it { should_not be_bad_browser }
end
context 'BAD browser' do
let(:user_agent) { described_class::BAD_USER_AGENTS.first.source }
it { should be_bad_browser }
end
end
describe '#html?' do
context 'HTML content' do
let(:headers) { { 'Content-Type' => 'text/html' } }
it { should be_html }
end
context 'PDF content' do
let(:headers) { { 'Content-Type' => 'application/pdf' } }
it { should_not be_html }
end
end
describe '#get?' do
context 'GET request' do
let(:env) { { 'REQUEST_METHOD' => 'GET' } }
it { should be_get }
end
context 'PUT request' do
let(:env) { { 'REQUEST_METHOD' => 'PUT' } }
it { should_not be_get }
end
context 'POST request' do
let(:env) { { 'REQUEST_METHOD' => 'POST' } }
it { should_not be_get }
end
context 'DELETE request' do
let(:env) { { 'REQUEST_METHOD' => 'DELETE' } }
it { should_not be_get }
end
context 'PATCH request' do
let(:env) { { 'REQUEST_METHOD' => 'PATCH' } }
it { should_not be_get }
end
end
end

View File

@ -1,4 +1,5 @@
require 'spec_helper'
require 'nokogiri'
describe Rack::LiveReload do
let(:middleware) { described_class.new(app, options) }
@ -6,196 +7,23 @@ describe Rack::LiveReload do
subject { middleware }
its(:app) { should == app }
it 'should be an app' do
middleware.app.should be == app
end
let(:env) { {} }
let(:options) { {} }
describe described_class::LIVERELOAD_LOCAL_URI do
context 'does not exist' do
before do
stub_request(:any, 'localhost:35729/livereload.js').to_timeout
end
it { should use_vendored }
end
context 'exists' do
before do
stub_request(:any, 'localhost:35729/livereload.js')
end
it { should_not use_vendored }
end
context 'specify vendored' do
let(:options) { { :source => :vendored } }
it { should use_vendored }
end
context 'specify LR' do
let(:options) { { :source => :livereload } }
it { should_not use_vendored }
end
end
context 'not text/html' do
let(:ret) { [ 200, { 'Content-Type' => 'image/png' }, [ '<head></head>' ] ] }
before do
app.stubs(:call).with(env).returns(ret)
end
it 'should pass through' do
middleware.call(env).should == ret
end
end
context 'unknown Content-Type' do
let(:ret) { [ 200, {}, [ 'hey ho' ] ] }
before do
app.stubs(:call).with(env).returns(ret)
end
it 'should not break' do
middleware.call(env).should_not raise_error(NoMethodError, /You have a nil object/)
end
end
context 'text/html' do
before do
app.stubs(:call).with(env).returns([ 200, { 'Content-Type' => 'text/html', 'Content-Length' => 0 }, [ '<head></head>' ] ])
middleware.stubs(:use_vendored?).returns(true)
end
let(:host) { 'host' }
let(:env) { { 'HTTP_HOST' => host } }
let(:ret) { middleware._call(env) }
let(:body) { ret.last.join }
let(:length) { ret[1]['Content-Length'] }
context 'vendored' do
it 'should add the vendored livereload js script tag' do
body.should include("script")
body.should include(described_class::LIVERELOAD_JS_PATH)
length.should == body.length.to_s
described_class::LIVERELOAD_JS_PATH.should_not include(host)
body.should include('swfobject')
body.should include('web_socket')
end
end
context 'not vendored' do
before do
middleware.stubs(:use_vendored?).returns(false)
end
it 'should add the LR livereload js script tag' do
body.should include("script")
body.should include(described_class::LIVERELOAD_LOCAL_URI.gsub('localhost', 'host'))
end
end
context 'set options' do
let(:middleware) { described_class.new(app, :host => new_host, :port => port, :min_delay => min_delay, :max_delay => max_delay) }
let(:min_delay) { 5 }
let(:max_delay) { 10 }
let(:port) { 23 }
let(:new_host) { 'myhost' }
it 'should add the livereload.js script tag' do
body.should include("mindelay=#{min_delay}")
body.should include("maxdelay=#{max_delay}")
body.should include("port=#{port}")
body.should include("host=#{new_host}")
end
end
context 'force flash' do
let(:middleware) { described_class.new(app, :force_swf => true) }
it 'should not add the flash shim' do
body.should include('WEB_SOCKET_FORCE_FLASH')
body.should include('swfobject')
body.should include('web_socket')
end
end
context 'no flash' do
let(:middleware) { described_class.new(app, :no_swf => true) }
it 'should not add the flash shim' do
body.should_not include('swfobject')
body.should_not include('web_socket')
end
end
context 'no host at all' do
let(:env) { {} }
it 'should use localhost' do
body.should include('localhost')
end
end
context 'ignored' do
let(:options) { { :ignore => [ %r{file} ] } }
context 'not root' do
let(:env) { { 'PATH_INFO' => '/this/file' } }
it 'should have no change' do
body.should_not include('script')
end
end
context 'root' do
let(:env) { { 'PATH_INFO' => '/' } }
it 'should have script' do
body.should include('script')
end
end
end
end
context '/__rack/livereload.js' do
let(:env) { { 'PATH_INFO' => described_class::LIVERELOAD_JS_PATH } }
let(:env) { { 'PATH_INFO' => described_class::BodyProcessor::LIVERELOAD_JS_PATH } }
before do
middleware.expects(:deliver_file).returns(true)
end
it 'should return the js file' do
middleware._call(env).should be_true
end
end
describe '#ignored?' do
let(:path_info) { 'path info' }
context 'no ignore set' do
it { should_not be_ignored(path_info) }
end
context 'ignore set' do
let(:options) { { :ignore => [ %r{#{path_info}} ] } }
it { should be_ignored(path_info) }
end
end
describe '#bad_browser?' do
let(:user_agent) { described_class::BAD_USER_AGENTS.first.source }
it { should be_bad_browser(user_agent) }
middleware._call(env).should be_truthy
end
end
end

View File

@ -1,4 +1,4 @@
require 'mocha'
require 'mocha/api'
require 'webmock/rspec'
require 'rack-livereload'