tilleryj-CSS-Push 0.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/CSS-Push.gemspec CHANGED
@@ -2,24 +2,43 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{CSS-Push}
5
- s.version = "0.0.0"
5
+ s.version = "1.0.1"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+
9
+ s.add_dependency('json')
10
+ s.add_dependency('juggernaut')
11
+ s.add_dependency('daemons')
12
+
8
13
  s.authors = ["Jason Tillery", "Vishu Ramanathan"]
9
14
  s.date = %q{2009-06-17}
15
+ s.default_executable = %q{csspush}
10
16
  s.email = %q{tilleryj@gmail.com}
17
+ s.executables = ["csspush", "pushify"]
11
18
  s.extra_rdoc_files = [
12
19
  "LICENSE",
13
- "README"
20
+ "README.markdown"
14
21
  ]
15
22
  s.files = [
16
- "CSS-Push.gemspec",
23
+ ".gitignore",
24
+ "CSS-Push.gemspec",
17
25
  "LICENSE",
18
- "README",
26
+ "README.markdown",
19
27
  "Rakefile",
20
28
  "VERSION",
21
- "lib/CSS-Push.rb",
22
- "spec/CSS-Push_spec.rb",
29
+ "bin/csspush",
30
+ "bin/pushify",
31
+ "lib/css_push.rb",
32
+ "lib/css_push_server.rb",
33
+ "install/css_push.js",
34
+ "install/css_push_initializer.rb",
35
+ "install/expressinstall.swf",
36
+ "install/juggernaut.js",
37
+ "install/juggernaut.swf",
38
+ "install/juggernaut.yml",
39
+ "install/juggernaut_hosts.yml",
40
+ "install/swfobject.js",
41
+ "rails/init.rb",
23
42
  "spec/spec_helper.rb"
24
43
  ]
25
44
  s.has_rdoc = true
@@ -29,7 +48,7 @@ Gem::Specification.new do |s|
29
48
  s.rubygems_version = %q{1.3.1}
30
49
  s.summary = %q{See updates you make to css files appear immediately in all of your browsers without having to refresh.}
31
50
  s.test_files = [
32
- "spec/CSS-Push_spec.rb",
51
+ "spec/css_push_spec.rb",
33
52
  "spec/spec_helper.rb"
34
53
  ]
35
54
 
data/README.markdown ADDED
@@ -0,0 +1,32 @@
1
+ # Css Push: Instantly push css changes to your browser(s) whenever you save.
2
+ See updates you make to css files appear immediately in all of your browsers without having to refresh.
3
+
4
+ # Limitations
5
+ * Will not work in windows development environments
6
+ * Currently is only available as a plugin to rails
7
+
8
+ # Getting Started
9
+
10
+ ## Installing
11
+
12
+ # Install the gem
13
+ sudo gem install tilleryj-CSS-Push
14
+ # Run the pushify script to install css push into your rails app
15
+ pushify
16
+ # call pushify from your view. Add this in the head of a view _after prototype.js_
17
+ <%= pushify %>
18
+
19
+ ## Start Servers
20
+ # Start servers with css_push
21
+ csspush start
22
+ # you stop them later with 'csspush stop'
23
+
24
+
25
+ # That's it
26
+ That's it. Load up the page in a browser and update the CSS file in your editor.
27
+
28
+ Now open up a different browser and update the CSS. Now you can watch the effect of you CSS updates on multiple browsers without refreshing.
29
+
30
+
31
+ Copyright (c) 2008-2009 Thinklink LLC (Jason Tillery and Vishu Ramanathan)
32
+
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 1.0.1
data/bin/csspush ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ require 'daemons'
3
+ require 'juggernaut'
4
+
5
+ require File.join(File.dirname(__FILE__), "..", "lib", "css_push_server")
6
+
7
+ # TODO: HACK
8
+ pids = File.join('tmp', 'pids')
9
+ pid = File.join(pids, 'juggernaut.pid')
10
+ if ARGV.include?('start')
11
+ system("juggernaut -cconfig/juggernaut.yml -d -P #{pid}")
12
+ else
13
+ system("kill #{File.read(pid)}")
14
+ system("rm #{pid}")
15
+ end
16
+
17
+ Daemons.run_proc(File.join(pids, 'css_push_server.rb')) { CSSPushServer.run }
data/bin/pushify ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # cp ../environment.rb config/environment.rb
4
+
5
+ require 'fileutils'
6
+
7
+ install_from = File.join(File.dirname(__FILE__), "..", "install")
8
+ install_to = Dir.pwd
9
+
10
+ FileUtils.mkdir_p(File.join(install_to, "public", "css_push"))
11
+ FileUtils.mkdir_p(File.join(install_to, "public", "javascripts", "css_push"))
12
+
13
+ puts "Installing CSS-Push..."
14
+ FileUtils.cp(File.join(install_from, "swfobject.js") , File.join(install_to, "public", "javascripts", "css_push"))
15
+ FileUtils.cp(File.join(install_from, "juggernaut.js") , File.join(install_to, "public", "javascripts", "css_push"))
16
+ FileUtils.cp(File.join(install_from, "css_push.js") , File.join(install_to, "public", "javascripts", "css_push"))
17
+ FileUtils.cp(File.join(install_from, "juggernaut.swf") , File.join(install_to, "public", "css_push"))
18
+ FileUtils.cp(File.join(install_from, "expressinstall.swf"), File.join(install_to, "public", "css_push"))
19
+
20
+ FileUtils.cp(File.join(install_from, "juggernaut_hosts.yml"), File.join(install_to, "config")) unless File.exist?(File.join(install_to, "config", "juggernaut_hosts.yml"))
21
+ FileUtils.cp(File.join(install_from, "juggernaut.yml"), File.join(install_to, "config")) unless File.exist?(File.join(install_to, "config", "juggernaut.yml"))
22
+
23
+ File.open('config/environments/development.rb', 'a') do |f|
24
+ f.write("\n\nconfig.gem 'CSS-Push', :lib => 'css_push'\n")
25
+ end
26
+ FileUtils.cp(File.join(install_from, "css_push_initializer.rb"), File.join(install_to, "config", "initializers"))
27
+
28
+
29
+
30
+
31
+ puts "CSS-Push has been successfully installed."
32
+
33
+
34
+
35
+
36
+
37
+ puts ""
38
+ puts IO.read(File.join(File.dirname(__FILE__), "..", 'README'))
39
+ puts ""
40
+
@@ -0,0 +1,15 @@
1
+ var CSSPush = {
2
+ touch: function(files) {
3
+ files.each(function(f) {
4
+ var css = "/stylesheets" + f.strip().toLowerCase();
5
+ window.$$("link").each(function(l) {
6
+ var root = window.location.protocol + "//" + window.location.host;
7
+ var link = l.href.strip().toLowerCase();
8
+ var compare = link.startsWith(root) ? root + css : css;
9
+ if (link == compare || link.startsWith(compare + "?")) {
10
+ l.href = compare + "?" + Math.random();
11
+ }
12
+ });
13
+ });
14
+ }
15
+ };
@@ -0,0 +1,15 @@
1
+
2
+ if defined?(CSSPush)
3
+ CSSPush.install
4
+ else
5
+ module CSSPush
6
+ module PushHelper
7
+ def pushify *args
8
+ ""
9
+ end
10
+ end
11
+ end
12
+ ActionView::Base.send(:include, CSSPush::PushHelper)
13
+ end
14
+
15
+
@@ -0,0 +1,201 @@
1
+ /*
2
+ Copyright (c) 2008 Alexander MacCaw
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ */
23
+
24
+ function Juggernaut(options) {
25
+ this.is_connected = false;
26
+ this.attempting_to_reconnect = false;
27
+ this.ever_been_connected = false;
28
+ this.hasLogger = "console" in window && "log" in window.console;
29
+ this.options = options;
30
+ this.bindToWindow();
31
+ };
32
+
33
+ Juggernaut.fn = Juggernaut.prototype;
34
+
35
+ Juggernaut.fn.logger = function(msg) {
36
+ if (this.options.debug) {
37
+ msg = "Juggernaut: " + msg + " on " + this.options.host + ':' + this.options.port;
38
+ this.hasLogger ? console.log(msg) : alert(msg);
39
+ }
40
+ };
41
+
42
+ Juggernaut.fn.initialized = function(){
43
+ this.fire_event('initialized');
44
+ this.connect();
45
+ };
46
+
47
+ Juggernaut.fn.broadcast = function(body, type, client_ids, channels){
48
+ var msg = {command: 'broadcast', body: body, type: (type||'to_channels')};
49
+ if(channels) msg['channels'] = channels;
50
+ if(client_ids) msg['client_ids'] = client_ids;
51
+ this.sendData(Juggernaut.toJSON(msg));
52
+ };
53
+
54
+ Juggernaut.fn.sendData = function(data){
55
+ this.swf().sendData(escape(data));
56
+ };
57
+
58
+ Juggernaut.fn.connect = function(){
59
+ if(!this.is_connected){
60
+ this.fire_event('connect');
61
+ this.swf().connect(this.options.host, this.options.port);
62
+ }
63
+ };
64
+
65
+ Juggernaut.fn.disconnect = function(){
66
+ if(this.is_connected) {
67
+ this.swf().disconnect();
68
+ this.is_connected = false;
69
+ }
70
+ };
71
+
72
+ Juggernaut.fn.handshake = function() {
73
+ var handshake = {};
74
+ handshake['command'] = 'subscribe';
75
+ if(this.options.session_id) handshake['session_id'] = this.options.session_id;
76
+ if(this.options.client_id) handshake['client_id'] = this.options.client_id;
77
+ if(this.options.channels) handshake['channels'] = this.options.channels;
78
+ if(this.currentMsgId) {
79
+ handshake['last_msg_id'] = this.currentMsgId;
80
+ handshake['signature'] = this.currentSignature;
81
+ }
82
+
83
+ return handshake;
84
+ };
85
+
86
+ Juggernaut.fn.connected = function(e) {
87
+ var json = Juggernaut.toJSON(this.handshake());
88
+ this.sendData(json);
89
+ this.ever_been_connected = true;
90
+ this.is_connected = true;
91
+ setTimeout(function(){
92
+ if(this.is_connected) this.attempting_to_reconnect = false;
93
+ }.bind(this), 1 * 1000);
94
+ this.logger('Connected');
95
+ this.fire_event('connected');
96
+ };
97
+
98
+ Juggernaut.fn.receiveData = function(e) {
99
+ var msg = Juggernaut.parseJSON(unescape(e.toString()));
100
+ this.currentMsgId = msg.id;
101
+ this.currentSignature = msg.signature;
102
+ this.logger("Received data:\n" + msg.body + "\n");
103
+ eval(msg.body);
104
+ };
105
+
106
+ var juggernaut;
107
+
108
+ // Prototype specific - override for other frameworks
109
+ Juggernaut.fn.fire_event = function(fx_name) {
110
+ $(document).fire("juggernaut:" + fx_name);
111
+ };
112
+
113
+ Juggernaut.fn.bindToWindow = function() {
114
+
115
+ Event.observe(window, 'load', function() {
116
+ juggernaut = this;
117
+ this.appendFlashObject();
118
+ }.bind(this));
119
+
120
+ };
121
+
122
+ Juggernaut.toJSON = function(hash) {
123
+ return Object.toJSON(hash);
124
+ };
125
+
126
+ Juggernaut.parseJSON = function(string) {
127
+ return string.evalJSON();
128
+ };
129
+
130
+ Juggernaut.fn.swf = function(){
131
+ return $(this.options.swf_name);
132
+ };
133
+
134
+ Juggernaut.fn.appendElement = function() {
135
+ this.element = new Element('div', { id: 'juggernaut' });
136
+ $(document.body).insert({ bottom: this.element });
137
+ };
138
+
139
+ /*** END PROTOTYPE SPECIFIC ***/
140
+
141
+ Juggernaut.fn.appendFlashObject = function(){
142
+ if(this.swf()) {
143
+ throw("Juggernaut error. 'swf_name' must be unique per juggernaut instance.");
144
+ }
145
+ Juggernaut.fn.appendElement();
146
+ swfobject.embedSWF(
147
+ this.options.swf_address,
148
+ 'juggernaut',
149
+ this.options.width,
150
+ this.options.height,
151
+ String(this.options.flash_version),
152
+ this.options.ei_swf_address,
153
+ {'bridgeName': this.options.bridge_name},
154
+ {},
155
+ {'id': this.options.swf_name, 'name': this.options.swf_name}
156
+ );
157
+ };
158
+
159
+ Juggernaut.fn.refreshFlashObject = function(){
160
+ this.swf().remove();
161
+ this.appendFlashObject();
162
+ };
163
+
164
+ Juggernaut.fn.errorConnecting = function(e) {
165
+ this.is_connected = false;
166
+ if(!this.attempting_to_reconnect) {
167
+ this.logger('There has been an error connecting');
168
+ this.fire_event('errorConnecting');
169
+ this.reconnect();
170
+ }
171
+ };
172
+
173
+ Juggernaut.fn.disconnected = function(e) {
174
+ this.is_connected = false;
175
+ if(!this.attempting_to_reconnect) {
176
+ this.logger('Connection has been lost');
177
+ this.fire_event('disconnected');
178
+ this.reconnect();
179
+ }
180
+ };
181
+
182
+ Juggernaut.fn.reconnect = function(){
183
+ if(this.options.reconnect_attempts){
184
+ this.attempting_to_reconnect = true;
185
+ this.fire_event('reconnect');
186
+ this.logger('Will attempt to reconnect ' + this.options.reconnect_attempts + ' times,\
187
+ the first in ' + (this.options.reconnect_intervals || 3) + ' seconds');
188
+ for(var i=0; i < this.options.reconnect_attempts; i++){
189
+ setTimeout(function(){
190
+ if(!this.is_connected){
191
+ this.logger('Attempting reconnect');
192
+ if(!this.ever_been_connected){
193
+ this.refreshFlashObject();
194
+ } else {
195
+ this.connect();
196
+ }
197
+ }
198
+ }.bind(this), (this.options.reconnect_intervals || 3) * 1000 * (i + 1));
199
+ }
200
+ }
201
+ };
@@ -0,0 +1,97 @@
1
+ # ======================
2
+ # Juggernaut Options
3
+ # ======================
4
+
5
+ # === Subscription authentication ===
6
+ # Leave all subscription options uncommented to allow anyone to subscribe.
7
+
8
+ # If specified, subscription_url is called everytime a client subscribes.
9
+ # Parameters passed are: session_id, client_id and an array of channels.
10
+ #
11
+ # The server should check that the session_id matches up to the client_id
12
+ # and that the client is allowed to access the specified channels.
13
+ #
14
+ # If a status code other than 200 is encountered, the subscription_request fails
15
+ # and the client is disconnected.
16
+ #
17
+ # :subscription_url: http://localhost:3000/sessions/juggernaut_subscription
18
+
19
+ # === Broadcast and query authentication ===
20
+ # Leave all broadcast/query options uncommented to allow anyone to broadcast/query.
21
+ #
22
+ # Broadcast authentication in a production environment is very importantant since broadcasters
23
+ # can execute JavaScript on subscribed clients, leaving you vulnerable to cross site scripting
24
+ # attacks if broadcasters aren't authenticated.
25
+
26
+ # 1) Via IP address
27
+ #
28
+ # If specified, if a client has an ip that is specified in allowed_ips, than it is automatically
29
+ # authenticated, even if a secret_key isn't provided.
30
+ #
31
+ # This is the recommended method for broadcast authentication.
32
+ #
33
+ :allowed_ips:
34
+ - 127.0.0.1
35
+ # - 192.168.0.1
36
+
37
+ # 2) Via HTTP request
38
+ #
39
+ # If specified, if a client attempts a broadcast/query, without a secret_key or using an IP
40
+ # no included in allowed_ips, then broadcast_query_login_url will be called.
41
+ # Parameters passed, if given, are: session_id, client_id, channels and type.
42
+ #
43
+ # The server should check that the session_id matches up to the client id, and the client
44
+ # is allowed to perform that particular type of broadcast/query.
45
+ #
46
+ # If a status code other than 200 is encountered, the broadcast_query_login_url fails
47
+ # and the client is disconnected.
48
+ #
49
+ # :broadcast_query_login_url: http://localhost:3000/sessions/juggernaut_broadcast
50
+
51
+ # 3) Via shared secret key
52
+ #
53
+ # This secret key must be sent with any query/broadcast commands.
54
+ # It must be the same as the one in the Rails config file.
55
+ #
56
+ # You shouldn't authenticate broadcasts from subscribed clients using this method
57
+ # since the secret_key will be easily visible in the page (and not so secret any more)!
58
+ #
59
+ # :secret_key: ddcb9de8a2e4361cd74730792b424fd8a1ac0a9a
60
+
61
+ # == Subscription Logout ==
62
+
63
+ # If specified, logout_connection_url is called everytime a specific connection from a subscribed client disconnects.
64
+ # Parameters passed are session_id, client_id and an array of channels specific to that connection.
65
+ #
66
+ # :logout_connection_url: http://localhost:3000/sessions/juggernaut_connection_logout
67
+
68
+ # Logout url is called when all connections from a subscribed client are closed.
69
+ # Parameters passed are session_id and client_id.
70
+ #
71
+ # :logout_url: http://localhost:3000/sessions/juggernaut_logout
72
+
73
+ # === Miscellaneous ===
74
+
75
+ # timeout defaults to 10. A timeout is the time between when a client closes a connection
76
+ # and a logout_request or logout_connection_request is made. The reason for this is that a client
77
+ # may only temporarily be disconnected, and may attempt a reconnect very soon.
78
+ #
79
+ # :timeout: 10
80
+
81
+ # store_messages defaults to false. If this option is true, messages send to connections will be stored.
82
+ # This is useful since a client can then receive broadcasted message that it has missed (perhaps it was disconnected).
83
+ #
84
+ # :store_messages: false
85
+
86
+ # === Server ===
87
+
88
+ # Host defaults to "0.0.0.0". You shouldn't need to change this.
89
+ # :host: 0.0.0.0
90
+
91
+ # Port is mandatory
92
+ :port: 5001
93
+
94
+ # Defaults to value of :port. If you are doing port forwarding you'll need to configure this to the same
95
+ # value as :public_port in the juggernaut_hosts.yml file
96
+ # :public_port: 5001
97
+
@@ -0,0 +1,18 @@
1
+ # You should list any juggernaut hosts here.
2
+ # You need only specify the secret key if you're using that type of authentication (see juggernaut.yml)
3
+ #
4
+ # Name: Mapping:
5
+ # :port internal push server's port
6
+ # :host internal push server's host/ip
7
+ # :public_host public push server's host/ip (accessible from external clients)
8
+ # :public_port public push server's port
9
+ # :secret_key (optional) shared secret (should map to the key specified in the push server's config)
10
+ # :environment (optional) limit host to a particular RAILS_ENV
11
+
12
+ :hosts:
13
+ - :port: 5001
14
+ :host: 127.0.0.1
15
+ :public_host: 127.0.0.1
16
+ :public_port: 5001
17
+ # :secret_key: your_secret_key
18
+ # :environment: :development
@@ -0,0 +1,5 @@
1
+ /* SWFObject v2.0 <http://code.google.com/p/swfobject/>
2
+ Copyright (c) 2007 Geoff Stearns, Michael Williams, and Bobby van der Sluis
3
+ This software is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
4
+ */
5
+ var swfobject=function(){var Z="undefined",P="object",B="Shockwave Flash",h="ShockwaveFlash.ShockwaveFlash",W="application/x-shockwave-flash",K="SWFObjectExprInst",G=window,g=document,N=navigator,f=[],H=[],Q=null,L=null,T=null,S=false,C=false;var a=function(){var l=typeof g.getElementById!=Z&&typeof g.getElementsByTagName!=Z&&typeof g.createElement!=Z&&typeof g.appendChild!=Z&&typeof g.replaceChild!=Z&&typeof g.removeChild!=Z&&typeof g.cloneNode!=Z,t=[0,0,0],n=null;if(typeof N.plugins!=Z&&typeof N.plugins[B]==P){n=N.plugins[B].description;if(n){n=n.replace(/^.*\s+(\S+\s+\S+$)/,"$1");t[0]=parseInt(n.replace(/^(.*)\..*$/,"$1"),10);t[1]=parseInt(n.replace(/^.*\.(.*)\s.*$/,"$1"),10);t[2]=/r/.test(n)?parseInt(n.replace(/^.*r(.*)$/,"$1"),10):0}}else{if(typeof G.ActiveXObject!=Z){var o=null,s=false;try{o=new ActiveXObject(h+".7")}catch(k){try{o=new ActiveXObject(h+".6");t=[6,0,21];o.AllowScriptAccess="always"}catch(k){if(t[0]==6){s=true}}if(!s){try{o=new ActiveXObject(h)}catch(k){}}}if(!s&&o){try{n=o.GetVariable("$version");if(n){n=n.split(" ")[1].split(",");t=[parseInt(n[0],10),parseInt(n[1],10),parseInt(n[2],10)]}}catch(k){}}}}var v=N.userAgent.toLowerCase(),j=N.platform.toLowerCase(),r=/webkit/.test(v)?parseFloat(v.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,i=false,q=j?/win/.test(j):/win/.test(v),m=j?/mac/.test(j):/mac/.test(v);/*@cc_on i=true;@if(@_win32)q=true;@elif(@_mac)m=true;@end@*/return{w3cdom:l,pv:t,webkit:r,ie:i,win:q,mac:m}}();var e=function(){if(!a.w3cdom){return }J(I);if(a.ie&&a.win){try{g.write("<script id=__ie_ondomload defer=true src=//:><\/script>");var i=c("__ie_ondomload");if(i){i.onreadystatechange=function(){if(this.readyState=="complete"){this.parentNode.removeChild(this);V()}}}}catch(j){}}if(a.webkit&&typeof g.readyState!=Z){Q=setInterval(function(){if(/loaded|complete/.test(g.readyState)){V()}},10)}if(typeof g.addEventListener!=Z){g.addEventListener("DOMContentLoaded",V,null)}M(V)}();function V(){if(S){return }if(a.ie&&a.win){var m=Y("span");try{var l=g.getElementsByTagName("body")[0].appendChild(m);l.parentNode.removeChild(l)}catch(n){return }}S=true;if(Q){clearInterval(Q);Q=null}var j=f.length;for(var k=0;k<j;k++){f[k]()}}function J(i){if(S){i()}else{f[f.length]=i}}function M(j){if(typeof G.addEventListener!=Z){G.addEventListener("load",j,false)}else{if(typeof g.addEventListener!=Z){g.addEventListener("load",j,false)}else{if(typeof G.attachEvent!=Z){G.attachEvent("onload",j)}else{if(typeof G.onload=="function"){var i=G.onload;G.onload=function(){i();j()}}else{G.onload=j}}}}}function I(){var l=H.length;for(var j=0;j<l;j++){var m=H[j].id;if(a.pv[0]>0){var k=c(m);if(k){H[j].width=k.getAttribute("width")?k.getAttribute("width"):"0";H[j].height=k.getAttribute("height")?k.getAttribute("height"):"0";if(O(H[j].swfVersion)){if(a.webkit&&a.webkit<312){U(k)}X(m,true)}else{if(H[j].expressInstall&&!C&&O("6.0.65")&&(a.win||a.mac)){D(H[j])}else{d(k)}}}}else{X(m,true)}}}function U(m){var k=m.getElementsByTagName(P)[0];if(k){var p=Y("embed"),r=k.attributes;if(r){var o=r.length;for(var n=0;n<o;n++){if(r[n].nodeName.toLowerCase()=="data"){p.setAttribute("src",r[n].nodeValue)}else{p.setAttribute(r[n].nodeName,r[n].nodeValue)}}}var q=k.childNodes;if(q){var s=q.length;for(var l=0;l<s;l++){if(q[l].nodeType==1&&q[l].nodeName.toLowerCase()=="param"){p.setAttribute(q[l].getAttribute("name"),q[l].getAttribute("value"))}}}m.parentNode.replaceChild(p,m)}}function F(i){if(a.ie&&a.win&&O("8.0.0")){G.attachEvent("onunload",function(){var k=c(i);if(k){for(var j in k){if(typeof k[j]=="function"){k[j]=function(){}}}k.parentNode.removeChild(k)}})}}function D(j){C=true;var o=c(j.id);if(o){if(j.altContentId){var l=c(j.altContentId);if(l){L=l;T=j.altContentId}}else{L=b(o)}if(!(/%$/.test(j.width))&&parseInt(j.width,10)<310){j.width="310"}if(!(/%$/.test(j.height))&&parseInt(j.height,10)<137){j.height="137"}g.title=g.title.slice(0,47)+" - Flash Player Installation";var n=a.ie&&a.win?"ActiveX":"PlugIn",k=g.title,m="MMredirectURL="+G.location+"&MMplayerType="+n+"&MMdoctitle="+k,p=j.id;if(a.ie&&a.win&&o.readyState!=4){var i=Y("div");p+="SWFObjectNew";i.setAttribute("id",p);o.parentNode.insertBefore(i,o);o.style.display="none";G.attachEvent("onload",function(){o.parentNode.removeChild(o)})}R({data:j.expressInstall,id:K,width:j.width,height:j.height},{flashvars:m},p)}}function d(j){if(a.ie&&a.win&&j.readyState!=4){var i=Y("div");j.parentNode.insertBefore(i,j);i.parentNode.replaceChild(b(j),i);j.style.display="none";G.attachEvent("onload",function(){j.parentNode.removeChild(j)})}else{j.parentNode.replaceChild(b(j),j)}}function b(n){var m=Y("div");if(a.win&&a.ie){m.innerHTML=n.innerHTML}else{var k=n.getElementsByTagName(P)[0];if(k){var o=k.childNodes;if(o){var j=o.length;for(var l=0;l<j;l++){if(!(o[l].nodeType==1&&o[l].nodeName.toLowerCase()=="param")&&!(o[l].nodeType==8)){m.appendChild(o[l].cloneNode(true))}}}}}return m}function R(AE,AC,q){var p,t=c(q);if(typeof AE.id==Z){AE.id=q}if(a.ie&&a.win){var AD="";for(var z in AE){if(AE[z]!=Object.prototype[z]){if(z=="data"){AC.movie=AE[z]}else{if(z.toLowerCase()=="styleclass"){AD+=' class="'+AE[z]+'"'}else{if(z!="classid"){AD+=" "+z+'="'+AE[z]+'"'}}}}}var AB="";for(var y in AC){if(AC[y]!=Object.prototype[y]){AB+='<param name="'+y+'" value="'+AC[y]+'" />'}}t.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+AD+">"+AB+"</object>";F(AE.id);p=c(AE.id)}else{if(a.webkit&&a.webkit<312){var AA=Y("embed");AA.setAttribute("type",W);for(var x in AE){if(AE[x]!=Object.prototype[x]){if(x=="data"){AA.setAttribute("src",AE[x])}else{if(x.toLowerCase()=="styleclass"){AA.setAttribute("class",AE[x])}else{if(x!="classid"){AA.setAttribute(x,AE[x])}}}}}for(var w in AC){if(AC[w]!=Object.prototype[w]){if(w!="movie"){AA.setAttribute(w,AC[w])}}}t.parentNode.replaceChild(AA,t);p=AA}else{var s=Y(P);s.setAttribute("type",W);for(var v in AE){if(AE[v]!=Object.prototype[v]){if(v.toLowerCase()=="styleclass"){s.setAttribute("class",AE[v])}else{if(v!="classid"){s.setAttribute(v,AE[v])}}}}for(var u in AC){if(AC[u]!=Object.prototype[u]&&u!="movie"){E(s,u,AC[u])}}t.parentNode.replaceChild(s,t);p=s}}return p}function E(k,i,j){var l=Y("param");l.setAttribute("name",i);l.setAttribute("value",j);k.appendChild(l)}function c(i){return g.getElementById(i)}function Y(i){return g.createElement(i)}function O(k){var j=a.pv,i=k.split(".");i[0]=parseInt(i[0],10);i[1]=parseInt(i[1],10);i[2]=parseInt(i[2],10);return(j[0]>i[0]||(j[0]==i[0]&&j[1]>i[1])||(j[0]==i[0]&&j[1]==i[1]&&j[2]>=i[2]))?true:false}function A(m,j){if(a.ie&&a.mac){return }var l=g.getElementsByTagName("head")[0],k=Y("style");k.setAttribute("type","text/css");k.setAttribute("media","screen");if(!(a.ie&&a.win)&&typeof g.createTextNode!=Z){k.appendChild(g.createTextNode(m+" {"+j+"}"))}l.appendChild(k);if(a.ie&&a.win&&typeof g.styleSheets!=Z&&g.styleSheets.length>0){var i=g.styleSheets[g.styleSheets.length-1];if(typeof i.addRule==P){i.addRule(m,j)}}}function X(k,i){var j=i?"visible":"hidden";if(S){c(k).style.visibility=j}else{A("#"+k,"visibility:"+j)}}return{registerObject:function(l,i,k){if(!a.w3cdom||!l||!i){return }var j={};j.id=l;j.swfVersion=i;j.expressInstall=k?k:false;H[H.length]=j;X(l,false)},getObjectById:function(l){var i=null;if(a.w3cdom&&S){var j=c(l);if(j){var k=j.getElementsByTagName(P)[0];if(!k||(k&&typeof j.SetVariable!=Z)){i=j}else{if(typeof k.SetVariable!=Z){i=k}}}}return i},embedSWF:function(n,u,r,t,j,m,k,p,s){if(!a.w3cdom||!n||!u||!r||!t||!j){return }r+="";t+="";if(O(j)){X(u,false);var q=(typeof s==P)?s:{};q.data=n;q.width=r;q.height=t;var o=(typeof p==P)?p:{};if(typeof k==P){for(var l in k){if(k[l]!=Object.prototype[l]){if(typeof o.flashvars!=Z){o.flashvars+="&"+l+"="+k[l]}else{o.flashvars=l+"="+k[l]}}}}J(function(){R(q,o,u);if(q.id==u){X(u,true)}})}else{if(m&&!C&&O("6.0.65")&&(a.win||a.mac)){X(u,false);J(function(){var i={};i.id=i.altContentId=u;i.width=r;i.height=t;i.expressInstall=m;D(i)})}}},getFlashPlayerVersion:function(){return{major:a.pv[0],minor:a.pv[1],release:a.pv[2]}},hasFlashPlayerVersion:O,createSWF:function(k,j,i){if(a.w3cdom&&S){return R(k,j,i)}else{return undefined}},createCSS:function(j,i){if(a.w3cdom){A(j,i)}},addDomLoadEvent:J,addLoadEvent:M,getQueryParamValue:function(m){var l=g.location.search||g.location.hash;if(m==null){return l}if(l){var k=l.substring(1).split("&");for(var j=0;j<k.length;j++){if(k[j].substring(0,k[j].indexOf("="))==m){return k[j].substring((k[j].indexOf("=")+1))}}}return""},expressInstallCallback:function(){if(C&&L){var i=c(K);if(i){i.parentNode.replaceChild(L,i);if(T){X(T,true);if(a.ie&&a.win){L.style.display="block"}}L=null;T=null;C=false}}}}}();
data/lib/css_push.rb ADDED
@@ -0,0 +1,88 @@
1
+ require "yaml"
2
+ require "socket"
3
+ require "erb"
4
+ require "json"
5
+
6
+ module CSSPush # :nodoc:
7
+ def self.install
8
+ ActionView::Base.send(:include, CSSPush::PushHelper)
9
+ ActionView::Helpers::AssetTagHelper.register_javascript_expansion :css_push => ['css_push/swfobject', 'css_push/juggernaut', 'css_push/css_push']
10
+ end
11
+ module PushHelper
12
+
13
+ def pushify(options = {})
14
+
15
+ hosts = Juggernaut::CONFIG[:hosts].select {|h| !h[:environment] or h[:environment] == ENV['RAILS_ENV'].to_sym }
16
+ random_host = hosts[rand(hosts.length)]
17
+ options = {
18
+ :host => (random_host[:public_host] || random_host[:host]),
19
+ :port => (random_host[:public_port] || random_host[:port]),
20
+ :width => '0px',
21
+ :height => '0px',
22
+ :session_id => request.session_options[:id],
23
+ :swf_address => "/css_push/juggernaut.swf",
24
+ :ei_swf_address => "/css_push/expressinstall.swf",
25
+ :flash_version => 8,
26
+ :flash_color => "#fff",
27
+ :swf_name => "juggernaut_flash",
28
+ :bridge_name => "juggernaut",
29
+ :debug => false,
30
+ :reconnect_attempts => 3,
31
+ :reconnect_intervals => 3
32
+ }.merge(options)
33
+ <<-HTML
34
+ #{ javascript_include_tag :css_push }
35
+ #{ javascript_tag "new Juggernaut(#{options.to_json});" }
36
+ HTML
37
+ end
38
+ end
39
+ end
40
+
41
+ ROOT = Dir.pwd unless defined? ROOT
42
+ module Juggernaut
43
+ CONFIG = YAML::load(ERB.new(IO.read("#{ROOT}/config/juggernaut_hosts.yml")).result).freeze
44
+ CR = "\0"
45
+
46
+ class << self
47
+
48
+ def send_to_all(data)
49
+ fc = {
50
+ :command => :broadcast,
51
+ :body => data,
52
+ :type => :to_channels,
53
+ :channels => []
54
+ }
55
+ send_data(fc)
56
+ end
57
+
58
+ def send_data(hash, response = false)
59
+ hash[:channels] = Array(hash[:channels]) if hash[:channels]
60
+ hash[:client_ids] = Array(hash[:client_ids]) if hash[:client_ids]
61
+
62
+ res = []
63
+ hosts.each do |address|
64
+ begin
65
+ hash[:secret_key] = address[:secret_key] if address[:secret_key]
66
+
67
+ @socket = TCPSocket.new(address[:host], address[:port])
68
+ # the \0 is to mirror flash
69
+ @socket.print(hash.to_json + CR)
70
+ @socket.flush
71
+ res << @socket.readline(CR) if response
72
+ ensure
73
+ @socket.close if @socket and !@socket.closed?
74
+ end
75
+ end
76
+ res.collect {|r| ActiveSupport::JSON.decode(r.chomp!(CR)) } if response
77
+ end
78
+
79
+ private
80
+
81
+ def hosts
82
+ CONFIG[:hosts].select {|h|
83
+ !h[:environment] or h[:environment].to_s == ENV['RAILS_ENV']
84
+ }
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,80 @@
1
+ require 'find'
2
+ require File.join(File.dirname(__FILE__), 'css_push')
3
+
4
+ ROOT = Dir.pwd unless defined? ROOT
5
+
6
+ class CSSPushServer
7
+ def self.run
8
+ self.new.run
9
+ end
10
+
11
+ attr_accessor :directories, :last_mtime
12
+
13
+ def initialize
14
+ css_root = File.join(ROOT, "public", "stylesheets")
15
+
16
+ self.directories = [css_root]
17
+ self.last_mtime = Time.now
18
+ end
19
+
20
+ def run
21
+ if (!Juggernaut)
22
+ puts "Juggernaut needs to be running for autospec to work"
23
+ return
24
+ end
25
+ begin
26
+ loop do
27
+ wait_for_changes
28
+ files = find_files_to_broadcast
29
+ self.last_mtime = files.values.map {|d| d[:mtime] }.max
30
+ broadcast_changes(files)
31
+ end
32
+ rescue Interrupt
33
+ puts
34
+ # Quit with ^C
35
+ end
36
+ end
37
+
38
+ def broadcast_changes(files)
39
+ puts "\n\nBroadcasting updates for: \n"
40
+ puts files.values.map{|d| d[:rio_name]}.join(", ")
41
+
42
+ Juggernaut.send_to_all("CSSPush.touch([#{ files.values.map{|d| "'" + d[:rio_name] + "'"}.join(", ") }])")
43
+ end
44
+
45
+ def wait_for_changes
46
+ Kernel.sleep 1 until !find_files_to_broadcast.empty?
47
+ end
48
+
49
+ def find_files_to_broadcast
50
+ files = find_files
51
+ files.each do |filename, data|
52
+ files.delete(filename) unless self.last_mtime < data[:mtime]
53
+ end
54
+ files
55
+ end
56
+
57
+ def find_files
58
+ result = {}
59
+ targets = self.directories
60
+
61
+ # from ZenTest
62
+ targets.each do |target|
63
+ Find.find(target) do |f|
64
+
65
+ next if test ?d, f
66
+ next if f =~ /(swp|~|rej|orig)$/ # temporary/patch files
67
+ next if f =~ /(\.svn)/ # svn files
68
+ next if f =~ /\/\.?#/ # Emacs autosave/cvs merge files
69
+ next if f =~ /\.DS_Store/ # OSX metadata
70
+
71
+ filename = f.sub(/^\.\//, '')
72
+
73
+ rio_name = Regexp.new("^#{Regexp.escape(target)}(.*)").match(filename)[1]
74
+ result[filename] = { :mtime => File.stat(filename).mtime, :rio_name => rio_name } rescue next
75
+ end
76
+ end
77
+
78
+ return result
79
+ end
80
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,5 @@
1
+ require File.join(File.dirname(__FILE__), "..", "lib", "css_push")
2
+
3
+ ActionView::Base.send(:include, CSSPush::PushHelper)
4
+
5
+ ActionView::Helpers::AssetTagHelper.register_javascript_expansion :css_push => ['css_push/swfobject', 'css_push/juggernaut', 'css_push/css_push']
File without changes
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tilleryj-CSS-Push
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Tillery
@@ -11,26 +11,68 @@ bindir: bin
11
11
  cert_chain: []
12
12
 
13
13
  date: 2009-06-17 00:00:00 -07:00
14
- default_executable:
15
- dependencies: []
16
-
14
+ default_executable: csspush
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: json
18
+ type: :runtime
19
+ version_requirement:
20
+ version_requirements: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ version:
26
+ - !ruby/object:Gem::Dependency
27
+ name: juggernaut
28
+ type: :runtime
29
+ version_requirement:
30
+ version_requirements: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: "0"
35
+ version:
36
+ - !ruby/object:Gem::Dependency
37
+ name: daemons
38
+ type: :runtime
39
+ version_requirement:
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: "0"
45
+ version:
17
46
  description:
18
47
  email: tilleryj@gmail.com
19
- executables: []
20
-
48
+ executables:
49
+ - csspush
50
+ - pushify
21
51
  extensions: []
22
52
 
23
53
  extra_rdoc_files:
24
54
  - LICENSE
25
- - README
55
+ - README.markdown
26
56
  files:
57
+ - .gitignore
27
58
  - CSS-Push.gemspec
28
59
  - LICENSE
29
- - README
60
+ - README.markdown
30
61
  - Rakefile
31
62
  - VERSION
32
- - lib/CSS-Push.rb
33
- - spec/CSS-Push_spec.rb
63
+ - bin/csspush
64
+ - bin/pushify
65
+ - lib/css_push.rb
66
+ - lib/css_push_server.rb
67
+ - install/css_push.js
68
+ - install/css_push_initializer.rb
69
+ - install/expressinstall.swf
70
+ - install/juggernaut.js
71
+ - install/juggernaut.swf
72
+ - install/juggernaut.yml
73
+ - install/juggernaut_hosts.yml
74
+ - install/swfobject.js
75
+ - rails/init.rb
34
76
  - spec/spec_helper.rb
35
77
  has_rdoc: true
36
78
  homepage: http://github.com/tilleryj/CSS-Push
@@ -59,5 +101,5 @@ signing_key:
59
101
  specification_version: 2
60
102
  summary: See updates you make to css files appear immediately in all of your browsers without having to refresh.
61
103
  test_files:
62
- - spec/CSS-Push_spec.rb
104
+ - spec/css_push_spec.rb
63
105
  - spec/spec_helper.rb
data/README DELETED
File without changes
data/lib/CSS-Push.rb DELETED
File without changes