tilleryj-CSS-Push 0.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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