├── .gems
├── .gitignore
├── README.md
├── Rakefile
├── app
├── controllers
│ ├── application_controller.rb
│ └── firecss_controller.rb
├── helpers
│ ├── application_helper.rb
│ └── firecss_helper.rb
├── models
│ └── fanboy.rb
└── views
│ └── firecss
│ ├── _thanks_fanboy.html.erb
│ ├── download.html.erb
│ └── signup.html.erb
├── config
├── boot.rb
├── database.yml
├── environment.rb
├── environments
│ ├── development.rb
│ ├── production.rb
│ └── test.rb
├── initializers
│ ├── array.rb
│ ├── backtrace_silencers.rb
│ ├── cookie_verification_secret.rb
│ ├── inflections.rb
│ ├── mime_types.rb
│ ├── new_rails_defaults.rb
│ ├── patches.rb
│ └── session_store.rb
├── locales
│ └── en.yml
└── routes.rb
├── db
├── migrate
│ └── 20110201020804_create_fanboys.rb
├── schema.rb
└── seeds.rb
├── doc
└── README_FOR_APP
├── php
├── .htaccess
├── FirecssCache.class.php
├── FirecssController.class.php
├── FirecssSession.class.php
├── IFirecssSession.class.php
└── index.php
├── public
├── 404.html
├── 422.html
├── 500.html
├── blank.html
├── cssfile.html
├── csspanel.html
├── favicon.ico
├── file.pdf
├── firecss@webspeed.co.nz
│ ├── chrome.manifest
│ ├── chrome
│ │ └── content
│ │ │ └── firecss
│ │ │ ├── firecss.css
│ │ │ ├── firecss.js
│ │ │ ├── firecss.xul
│ │ │ └── images
│ │ │ ├── reset.png
│ │ │ └── save.png
│ └── install.rdf
├── firecss_app.crx
├── firecss_app.pem
├── firecss_app
│ ├── 128.png
│ └── manifest.json
├── images
│ ├── bigfire.jpg
│ ├── bigfire.png
│ ├── bigfire2.jpg
│ ├── bigfire3.jpg
│ ├── busy.gif
│ ├── fire.jpg
│ ├── fire2.jpg
│ ├── logo.png
│ ├── logo.pxm
│ ├── logo2.png
│ ├── logo3.png
│ ├── logo4.png
│ ├── logo5.png
│ ├── logo6.png
│ ├── logo7.png
│ ├── rails.png
│ └── trans.gif
├── index.html
├── javascripts
│ ├── application.js
│ ├── firecss.js
│ └── test.js
├── robots.txt
├── stylesheets
│ ├── application.css
│ ├── other.css
│ └── style.css
├── test.html
├── test2.html
└── test_1.html
├── script
├── about
├── console
├── dbconsole
├── destroy
├── generate
├── performance
│ ├── benchmarker
│ └── profiler
├── plugin
├── runner
└── server
├── test
├── fixtures
│ └── fanboys.yml
├── functional
│ └── firecss_controller_test.rb
├── performance
│ └── browsing_test.rb
├── test_helper.rb
└── unit
│ ├── fanboy_test.rb
│ └── helpers
│ └── firecss_helper_test.rb
└── vendor
└── plugins
└── rubyzip
├── MIT-LICENSE
├── README
├── Rakefile
├── init.rb
├── install.rb
├── lib
├── ioextras.rb
├── stdrubyext.rb
├── tempfile_bugfixed.rb
├── zip.rb
├── zip
│ ├── ioextras.rb
│ ├── stdrubyext.rb
│ ├── tempfile_bugfixed.rb
│ ├── zip.rb
│ ├── zipfilesystem.rb
│ └── ziprequire.rb
├── zipfilesystem.rb
└── ziprequire.rb
├── tasks
└── rubyzip_tasks.rake
├── test
└── rubyzip_test.rb
└── uninstall.rb
/.gems:
--------------------------------------------------------------------------------
1 | rails --version 2.3.8
2 | pusher
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | log
2 | tmp
3 | db/*.sqlite3
4 | db/data.yml
5 | nbproject/*
6 | *.DS_Store
7 | *.orig
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | FireCSS
2 | =======
3 |
4 | With FireCSS you can edit html and css in Firebug for Firefox and see the changes as you edit. Go to [http://firecss.com](http://firecss.com) for a video showing it in action
5 |
6 |
7 | Warning
8 | -------
9 |
10 | This is an alpha release.
11 |
12 | Some key features are missing (which I've outlined below) and its reasonably untested except running locally on a single machine.
13 |
14 | I've developed and tested it in Firefox 3.6.17 with Firebug 1.6.2 on a Mac. I have no idea if it will work with other versions.
15 | I'd be surprised if it works in Firefox 4 and Firebug 1.7
16 |
17 |
18 | Getting Started
19 | ---------------
20 |
21 | You will need to be comfortable setting up a Ruby on Rails server to get this release running (eventually it's likely to be ported to node with a hosted option).
22 | I recommend Heroku.com if you are familiar with Git, unfamiliar with RoR and want to get a server up and running quickly.
23 |
24 | There are three key parts to FireCSS:
25 |
26 | ### Firebug Plugin
27 |
28 | The plugin monitors changes to your CSS and forwards it to the server.
29 | It currently sits in the Public folder of the rails application: firecss@webspeed.co.nz
30 |
31 | I haven't bothered to package the plugin up yet but It's easy to install:
32 | 1. In Firefox select Help menu:Troubleshooting Information
33 | 2. Click the 'Show in Finder' button beside Profile Directory
34 | 3. Open the highlighted folder in the finder and open the extensions folder within that
35 | 4. Copy the firecss@webspeed.co.nz folder to that extensions folder
36 | 5. Start or restart Firefox
37 |
38 | If it is installed you will see two new FireCSS buttons in the Firebug menu bar
39 |
40 |
41 | ### Firecss Javascript
42 |
43 | When you want to enable a page for editing through FireCSS you need to include the public/javascripts/firecss.js script
44 |
45 | This script enables the FireCSS plugin within Firebug when running if Firefox and manages the polling for and displaying of CSS changes in other browsers
46 |
47 | To keep it simple I don't bother checking for DOMContentLoaded so its best to include it at the bottom of the page. See public/test.html for an example
48 |
49 | The source of this script is used to determine where to send and retrieve updates so it must be served from the rails application.
50 | The html page itself can be hosted anywhere.
51 |
52 |
53 | ### FireCSS Server
54 |
55 | The server handles incoming updates from the FireCSS plugin and farms the updates out to any copies of the page in other browsers through the polling that the javascript file initiates.
56 | It also handles wrapping the changes into a zip file of sources when they are downloaded.
57 |
58 | It's pretty simple - all handled by the index, polling, and download methods in the firecss controller.
59 |
60 | Initially I tried using push updates through the pusher plugin to avoid polling - this proved too slow.
61 | I've left the code in place for now but other than using the pusher channel name no push updates are used.
62 | Eventually I plan to use websockets where possible after porting to node.js
63 |
64 |
65 | Editing
66 | -------
67 |
68 | Once you've installed the FireCSS plugin, started your RoR server, and added the script tag referencing the javascript file to the bottom of your html page...
69 |
70 | Open the page in Firefox and another browser.
71 | Note: that the page address is currently used to identify changes so all browsers need to have the same address for the page.
72 | If you are running locally on one machine you need to reference the page via an ip address that the other machine can reach - localhost will not work (its fine if all browsers are running on the same machine).
73 |
74 | Open Firebug and start editing the CSS in the html or CSS panels. You should be able to see the changes reflected in the other page almost immediately.
75 |
76 | At any time you can reset your changes and revert to the original page.
77 | The reset button is the Fireball heading right with a red cross in the Firebug toolbar.
78 | When you click it all browsers should revert (it will take a couple of seconds for the Firefox version to update).
79 |
80 | When you have finished editing click the Fireball heading left with a green tick to save your changes.
81 | An HTML file (if there are no external CSS files) or zip archive (with the html source and css includes as separate files) will be downloaded.
82 | You then need replace the original files on the origin server(s). Your original files will not be overwritten automatically.
83 |
84 |
85 | Gotchas
86 | -------
87 |
88 | If you ferret around in the FireCSS javascript file you'll see that for each stylesheet link or style tag a shadow one is added immediately after to receive the updates.
89 | For the non firefox clients, all updates will take precedence over all original CSS rules in that rule set. In Firefox the updates will generally be to the original in the rule set.
90 | This means you may well see some differences between the Firefox version and other browsers where precedence is an issue (watch our for the !important tag particularly).
91 | On saving changes the current Firefox version is used so you should find your updates remain true to Firefox when your updated files are loaded in the other browsers.
92 | This issue should be fixable.
93 |
94 | Firefox will drop any tags it doesn’t understand. When the updates are sent back to the server the downloaded files will only include the Mozilla rules.
95 | I'm planning to incorporate a dictionary of equivalents into the server at some stage so that rules like -moz-border-radius will automatically get the webkit and other equivalents.
96 |
97 |
98 | Missing features
99 | ----------------
100 |
101 | A bonus in this version is that any HTML edits you make in Firebug to the page will be saved as part of the download.
102 | Unfortunately in this version changes to the HTML don't get updated in the other browser as they are made. Only CSS changes.
103 |
104 | There is no update channel password so anyone who loads the page while you're editing it (assuming they can load from the same address) will see your changes.
105 |
106 | The FireCSS buttons are currently always visible and enabled in Firebug even if the page your viewing doesn't have the FireCSS javascript tag.
107 |
108 | In the downloaded files all rules are formatted on a single line.
109 | It would be nice to add some formatting options so that the format of the update CSS files matched your preferences.
110 |
111 | You can edit the same page in two different versions of Firefox with the Firebug and FireCSS plugins.
112 | Other browsers should reflect all changes but in this version each copy of Firefox will not be updated with the changes from the the other copy.
113 | (The earlier version I used for the video on FireCSS.com did update from multiple sources).
114 |
115 |
116 | Comments/Questions
117 | ------------------
118 |
119 | Drop me a comment at [firecss.com](http://firecss.com)
120 |
121 | License
122 | -------
123 |
124 | Copyright (C) 2011 Julian Cox, Webspeed Ltd.
125 |
126 | This program is free software: you can redistribute it and/or modify
127 | it under the terms of the GNU General Public License as published by
128 | the Free Software Foundation, either version 3 of the License, or
129 | (at your option) any later version.
130 |
131 | This program is distributed in the hope that it will be useful,
132 | but WITHOUT ANY WARRANTY; without even the implied warranty of
133 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
134 | GNU General Public License for more details.
135 |
136 | You should have received a copy of the GNU General Public License
137 | along with this program. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).
138 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require(File.join(File.dirname(__FILE__), 'config', 'boot'))
5 |
6 | require 'rake'
7 | require 'rake/testtask'
8 | require 'rake/rdoctask'
9 |
10 | require 'tasks/rails'
11 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | # Filters added to this controller apply to all controllers in the application.
2 | # Likewise, all the methods added will be available for all controllers.
3 |
4 | class ApplicationController < ActionController::Base
5 | helper :all # include all helpers, all the time
6 | protect_from_forgery # See ActionController::RequestForgeryProtection for details
7 |
8 | # Scrub sensitive parameters from your log
9 | # filter_parameter_logging :password
10 |
11 | def render_json(json, options={})
12 | if (json.is_a? Hash)
13 | json = render_to_string(json)
14 | end
15 | callback, variable = params[:callback], params[:variable]
16 | response = begin
17 | if callback && variable
18 | "var #{variable} = #{json};\n#{callback}(#{variable});"
19 | elsif variable
20 | "var #{variable} = #{json};"
21 | elsif callback
22 | "#{callback}(#{json});"
23 | else
24 | json
25 | end
26 | end
27 | render({:content_type => 'application/json', :text => response}.merge(options))
28 | end
29 |
30 | end
31 |
--------------------------------------------------------------------------------
/app/controllers/firecss_controller.rb:
--------------------------------------------------------------------------------
1 | # FireCSS - See CSS changes in all browsers as you edit
2 | # Copyright (C) 2011 Julian Cox, Webspeed Ltd.
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program. If not, see
") + '
' + ERB::Util::html_escape(params[:html]) + '' 106 | end 107 | 108 | def download 109 | source = request.env["HTTP_REFERER"] 110 | name_and_suffix = source.split('?').first.split('/').last.split('.') 111 | filename = name_and_suffix[0..(name_and_suffix.length - 2)].join('.') 112 | sheets = params[:stylesheets] 113 | rules = params[:rules] 114 | html = params[:html] 115 | if (sheets.length > 0) 116 | data = StringIO.new 117 | Zip::ZipOutputStream.use('zip.css', data) do |z| 118 | sheets.each_with_index do |sheet, i| 119 | z.put_next_entry(sheet.split('?').first.split('/').last) 120 | z.print rules[i] 121 | end 122 | z.put_next_entry(name_and_suffix.join('.')) 123 | z.print html 124 | end 125 | send_data data.string, :filename => "#{filename}.zip", :type => 'application/zip', :disposition => 'attachment' 126 | else 127 | send_data html, :filename => "#{filename}.html", :type => 'text/html', :disposition => 'attachment' 128 | end 129 | end 130 | 131 | 132 | def downloadX 133 | source = params[:referer] 134 | pusher_channel = source.split('?').first.split('://').last.gsub('/','_') 135 | mods = Rails.cache.read(pusher_channel) || []; 136 | if (mods.length > 0) 137 | mods_by_source = {} 138 | mods.each do |m| 139 | mods_by_source[m[:source]] ||= [] 140 | mods_by_source[m[:source]] << m.dup 141 | end 142 | #apply to mods to the css files 143 | css_data = {} 144 | mods_by_source.each do |source, mods| 145 | filename = source.split('?').first.split('/').last 146 | text = load_source(source); 147 | updated_text = apply_changes(text, mods) 148 | css_data[filename] = updated_text 149 | end 150 | if (css_data.length > 1) #have multiple file changes - zip them up and output zip file 151 | data = StringIO.new 152 | Zip::ZipOutputStream.use('zip.css', data) do |z| 153 | css_data.each do |name, css| 154 | z.put_next_entry(name) 155 | z.print css 156 | end 157 | end 158 | send_data data.string, :filename => 'css.zip', :type => 'application/zip', :disposition => 'attachment' 159 | else #single file just download the css file 160 | data = css_data.to_a.flatten 161 | send_data data[1], :filename => data[0], :type => 'text/css', :disposition => 'attachment' 162 | end 163 | end 164 | end 165 | 166 | def signup 167 | fb = Fanboy.add(params[:email]); 168 | if request.xhr? 169 | render :partial => 'firecss/thanks_fanboy' 170 | end 171 | end 172 | 173 | private 174 | 175 | def load_source url #get a file from url or local file structure 176 | uri = URI.parse(url) 177 | if (uri.host == request.host) && (uri.port == request.port) && (FileTest.exist?("#{RAILS_ROOT}/public#{uri.path}")) #file is from this server and it’s a public file - not some controller request 178 | return IO.read("#{RAILS_ROOT}/public#{uri.path}") 179 | else #file from some other host download the content 180 | return Net::HTTP.get(uri) 181 | end 182 | end 183 | 184 | def apply_changes text, mods #apply an array of css changes to the incoming text file 185 | # sort mods by line number, selector, and mod number 186 | # now we also have new rule edits which add a fraction to the line number 187 | # add these edits in so they don't affect the current line numbering (ie at the end of an existing edit) 188 | # since they're in order we can add to end of existing rule even if it has a new rule added 189 | # to find the end of an existing rule we need the first unquoted } or } unwrapped by () 190 | lines = text.split("\n") #StringIO.new(text).readlines 191 | mods.sort!{|a,b| a[:line] != b[:line] ? a[:line] <=> b[:line] : (a[:property] != b[:property] ? a[:property] <=> b[:property] : a[:edit] <=> b[:edit])} 192 | no_line_mods = mods.take_while!{|m| m[:line] == 0} # mods now has only ones with line numbers 193 | mods.each_with_index do |m, i| 194 | puts m[:line] 195 | if (i == (mods.length-1)) || ((m[:line] != mods[i+1][:line]) || (m[:selector] != mods[i+1][:selector]) || (m[:property] != mods[i+1][:property])) 196 | #this line in not superceeded by the next one so apply it. 197 | regx = Regexp.new(m[:property]+'\s?:\s?[^};$]+',true) 198 | lineNo = m[:line] - 1 #array of line starts at 0 - file lines start at 1 199 | lines[lineNo..lines.length].each_with_index do |line, i| 200 | result = line.sub!(regx,"#{m[:property]}: #{m[:value]}") 201 | break if result; 202 | if line =~ /\}/ #we found the closing bracket but haven't been able to replace so insert prior to bracket #TODO handle close brackets in quotes or brackets 203 | prev = i+lineNo-1 204 | if (prev > 0) 205 | lines[prev] += ';' if (!(lines[prev] =~ /\{.?\z/) && !(lines[prev] =~ /;.?\z/)) 206 | whitespace = /\A\s+/.match(lines[prev]) 207 | end 208 | line.sub!(/\}/,"#{(whitespace ? whitespace[0] : '')}#{m[:property]}: #{m[:value]};\n}") 209 | break; 210 | end 211 | end 212 | end 213 | end 214 | #now we need to handle unnumbered lines + html 215 | #apply changes with line numbers first as any additions we make will change line numbering 216 | # for each change with line number 217 | # find the line the ruleset comes from 218 | # get the rule set 219 | # change existing rule or add new rule to bottom of rule set on same line as } so line numbering doesn't change 220 | # end 221 | # 222 | # changes without a line number may include previous and post rules?? but in the meantime add rules to bottom of the file 223 | # or if its html before last style before if no and before head 224 | # Yup confirmed this is not working even though html file is being saved. 225 | return lines.join("\n") 226 | end 227 | 228 | def apply_changes_to_css text, mods 229 | return text 230 | end 231 | 232 | def apply_changes_to_html text, mods 233 | #html needs to be treated differently because of the ability to add new rules or 234 | return text 235 | end 236 | 237 | end 238 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # Methods added to this helper will be available to all templates in the application. 2 | module ApplicationHelper 3 | end 4 | -------------------------------------------------------------------------------- /app/helpers/firecss_helper.rb: -------------------------------------------------------------------------------- 1 | module FirecssHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/models/fanboy.rb: -------------------------------------------------------------------------------- 1 | class Fanboy < ActiveRecord::Base 2 | 3 | def Fanboy.add(mail) 4 | Fanboy.find_by_email(mail) || Fanboy.create(:email => mail) if mail.to_s != '' 5 | end 6 | 7 | end 8 | -------------------------------------------------------------------------------- /app/views/firecss/_thanks_fanboy.html.erb: -------------------------------------------------------------------------------- 1 | Thanks 2 | -------------------------------------------------------------------------------- /app/views/firecss/download.html.erb: -------------------------------------------------------------------------------- 1 | <%# 2 | # If we get here then download failed 3 | %> 4 | 5 |
There were no modifications to download
7 | -------------------------------------------------------------------------------- /app/views/firecss/signup.html.erb: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 |You may have mistyped the address or the page may have moved.
28 |Maybe you tried to change something you didn't have access to.
28 |We've been notified about this issue and we'll take a look at it shortly.
28 |134 | | 135 |136 | | 137 |138 | | 139 |
142 |
143 | New watch expression...
144 | |
145 | ||
148 |
149 |
150 | |
151 |
152 |
153 |
154 |
155 |
156 | lastEdit.parentNode.parentNode.parentNode.parentNode.parentNode.innerHTML
157 |
158 | |
159 | 160 | 161 | "<div ruleid="p/10" role...iv class=" "></div></a>" 162 | | 163 |
166 |
167 |
168 | |
169 |
170 |
171 |
172 |
173 |
174 | lastEdit
175 |
176 | |
177 | 178 | 179 | 180 | 181 | span 182 | 183 | 184 | 185 | .cssPropValue 186 | 187 | 188 | 189 | 190 | | 191 |
55 | FireCSS is an add-on for Firebug. 56 | You edit CSS in Firebug. 57 | FireCSS sends the changes to any browser open on that page. 58 | When you’re done you can download the modified CSS files. 59 |
60 |If your interested in learning more check out this quick demo video and read on.
61 |If you’d like to see FireCSS become an actual (opensource) product: share this page, add your email address to the notification list, leave a comment, or email:
62 | julian at firecss dot com.
114 | Firebug is a fantastic tool (thanks Joe Hewitt and the Firebug team from changing web debugging from a serious time-wasting pain in the you-know-what to a minor chore. 115 | In fact, I may be wrong, but I'm sure a lot of the credit for the decent tools we have in other browsers lies with Joe's pioneering work). 116 |
117 |118 | Anyway, lately I've been using Firebug more and more to design and test CSS styling. Firebug's is an amazingly good CSS editor for a 119 | javascript in web-browser version. It's got a heck of a lot of the features you'd expect in a dedicated editor. 120 |
121 |122 | But here's the rub. You end up copying and pasting a lot of CSS from Firebug into your editor (because as soon as you hit that refresh button 123 | those changes are gone). And you end up designing for Firefox and then checking it on other browsers, which are usually fine, excep,t of course, 124 | Internet Explorer. And debugging CSS on Internet Explorer is still a serious time-wasting pain in the you-know-what. 125 |
126 |127 | It struck me that if I could have a tool that would save my Firebug CSS edits and let me see what was happening on Internet Explorer (and other browsers) 128 | as I was making changes — that would be seriously cool. 129 |
130 |131 | Now if other people thought this was a cool idea too, I thought I would open source it and return something back to the Firebug community 132 | for all the time that Firebug has saved me. 133 |
134 |136 | Right now FireCSS is very incomplete. It works as a proof of concept and I'm pretty confident I can get it to work as a reasonable product. 137 | But there's quite a bit of work to get it there — not much point if I'm the only one whose going to be using it. 138 |
139 |140 | So I thought I'd throw up this page and a quick video to solicit some feedback to see if there's enough demand to make doing the extra work worthwhile. 141 | So please share this page, contact me (julian at firecss dot com), leave a comment, and/or sign up for the beta list to encourage me to get off my chuff. 142 |
143 |145 | FireCSS has three parts. And add-on for Firebug, a web service, and some javascript. 146 |
147 |148 | You install the add-on in Firefox and it just sits doing noting. 149 | You add a temporary javascript tag to any page you want to edit using FireCSS. 150 | That javascript activates FireCSS on Firefox or connects other browsers to the web service to get CSS updates. 151 | Then every time you edit CSS in Firefox is sends a CSS Update event to the web service which farms the updates out to any other connected browsers on that page. 152 |
153 |154 | This means FireCSS works across multiple boxes anywhere - the browsers don't have to be running on the same machine or in the same country. 155 | I've also allowed Firefox to pick up updates from other instances of Firefox with Firebug running which means two or more people can collaborate 156 | editing the CSS on a single page. 157 |
158 |159 | The web server keeps a change log of all the updates to each CSS source whether inline or to an external CSS file. 160 | When editing is complete the server can download the original CSS files (from whatever source) and apply the changes to that file to create 161 | an archive of updated CSS files. So I'm planning to add a save button to Firebug to save and download changes. 162 |
163 |164 | Although I've focused on CSS initially at its my main pain in the you-know-what, I can’t see any reason why this technique shouldn't work for 165 | the HTML in the page as well. So it should be possible to develop a plugin that lets you do the whole hog and develop complete designs and see them in all browsers as you go. 166 | (It does help to have at least two monitors if you're working on a single box). 167 |
168 |170 | I've tried setting up FireCSS in a couple of ways including using push notifications to browsers. 171 | I'm sure I can improve things but, so far, polling gives me the fastest response times and requires the least amount of cross-browser support. 172 | Unfortunately to provide a responsive polling solution I need a reasonable number of web connections, which costs money on 173 | Heroku (my preferred hosting platform). 174 |
175 |176 | With a single dyno on the free version of Heroku, any demo would quickly grind to a halt, so there is no demo available at the moment. 177 | As soon as I get this sorted I'll let anyone who's signed up for the beta list know. 178 |
179 |180 | And, if anyone can provide me a low/no cost host, please get in touch (julian at firecss dot com). 181 | The web service is currently a rails app, but it doesn't need to be. In fact, it would probably make more sense as a node.js app. 182 |
183 |
185 | I'm Julian Cox.
186 | I run a small web consulting and development company called Webspeed Ltd, in Dunedin, New Zealand.
188 | Most of our services are only really applicable to New Zealand but if you'd like to see some of what we get up to try checking out:
189 |
190 | EFTPlus (a transaction-based loyalty service using EFTPOS and credit cards) and
191 | TiXT (create your own automated txt code that can send email too).
192 |
Thanks for reading.
194 | 205 |48 | See CSS changes in all browsers as you edit 49 |
50 |54 | FireCSS is an add-on for Firebug. 55 | You edit CSS in Firebug. 56 | FireCSS sends the changes to any browser open on that page. 57 | When you’re done you can download the modified CSS files. 58 |
59 |38 | See CSS changes in all browsers as you edit 39 |
40 |44 | FireCSS is an add-on for Firebug. 45 | You edit CSS in Firebug. 46 | FireCSS sends the changes to any browser open on that page. 47 | When you’re done you can download the modified CSS files. 48 |
49 |my.zip
containing a normal entry with the name
14 | # first.txt
, a directory entry named mydir
15 | # and finally another normal entry named second.txt
16 | #
17 | # require 'zip/zipfilesystem'
18 | #
19 | # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
20 | # |zipfile|
21 | # zipfile.file.open("first.txt", "w") { |f| f.puts "Hello world" }
22 | # zipfile.dir.mkdir("mydir")
23 | # zipfile.file.open("mydir/second.txt", "w") { |f| f.puts "Hello again" }
24 | # }
25 | #
26 | # Reading is as easy as writing, as the following example shows. The
27 | # example writes the contents of first.txt
from zip archive
28 | # my.zip
to standard out.
29 | #
30 | # require 'zip/zipfilesystem'
31 | #
32 | # Zip::ZipFile.open("my.zip") {
33 | # |zipfile|
34 | # puts zipfile.file.read("first.txt")
35 | # }
36 |
37 | module ZipFileSystem
38 |
39 | def initialize # :nodoc:
40 | mappedZip = ZipFileNameMapper.new(self)
41 | @zipFsDir = ZipFsDir.new(mappedZip)
42 | @zipFsFile = ZipFsFile.new(mappedZip)
43 | @zipFsDir.file = @zipFsFile
44 | @zipFsFile.dir = @zipFsDir
45 | end
46 |
47 | # Returns a ZipFsDir which is much like ruby's builtin Dir (class)
48 | # object, except it works on the ZipFile on which this method is
49 | # invoked
50 | def dir
51 | @zipFsDir
52 | end
53 |
54 | # Returns a ZipFsFile which is much like ruby's builtin File (class)
55 | # object, except it works on the ZipFile on which this method is
56 | # invoked
57 | def file
58 | @zipFsFile
59 | end
60 |
61 | # Instances of this class are normally accessed via the accessor
62 | # ZipFile::file. An instance of ZipFsFile behaves like ruby's
63 | # builtin File (class) object, except it works on ZipFile entries.
64 | #
65 | # The individual methods are not documented due to their
66 | # similarity with the methods in File
67 | class ZipFsFile
68 |
69 | attr_writer :dir
70 | # protected :dir
71 |
72 | class ZipFsStat
73 | def initialize(zipFsFile, entryName)
74 | @zipFsFile = zipFsFile
75 | @entryName = entryName
76 | end
77 |
78 | def forward_invoke(msg)
79 | @zipFsFile.send(msg, @entryName)
80 | end
81 |
82 | def kind_of?(t)
83 | super || t == ::File::Stat
84 | end
85 |
86 | forward_message :forward_invoke, :file?, :directory?, :pipe?, :chardev?
87 | forward_message :forward_invoke, :symlink?, :socket?, :blockdev?
88 | forward_message :forward_invoke, :readable?, :readable_real?
89 | forward_message :forward_invoke, :writable?, :writable_real?
90 | forward_message :forward_invoke, :executable?, :executable_real?
91 | forward_message :forward_invoke, :sticky?, :owned?, :grpowned?
92 | forward_message :forward_invoke, :setuid?, :setgid?
93 | forward_message :forward_invoke, :zero?
94 | forward_message :forward_invoke, :size, :size?
95 | forward_message :forward_invoke, :mtime, :atime, :ctime
96 |
97 | def blocks; nil; end
98 |
99 | def get_entry
100 | @zipFsFile.__send__(:get_entry, @entryName)
101 | end
102 | private :get_entry
103 |
104 | def gid
105 | e = get_entry
106 | if e.extra.member? "IUnix"
107 | e.extra["IUnix"].gid || 0
108 | else
109 | 0
110 | end
111 | end
112 |
113 | def uid
114 | e = get_entry
115 | if e.extra.member? "IUnix"
116 | e.extra["IUnix"].uid || 0
117 | else
118 | 0
119 | end
120 | end
121 |
122 | def ino; 0; end
123 |
124 | def dev; 0; end
125 |
126 | def rdev; 0; end
127 |
128 | def rdev_major; 0; end
129 |
130 | def rdev_minor; 0; end
131 |
132 | def ftype
133 | if file?
134 | return "file"
135 | elsif directory?
136 | return "directory"
137 | else
138 | raise StandardError, "Unknown file type"
139 | end
140 | end
141 |
142 | def nlink; 1; end
143 |
144 | def blksize; nil; end
145 |
146 | def mode
147 | e = get_entry
148 | if e.fstype == 3
149 | e.externalFileAttributes >> 16
150 | else
151 | 33206 # 33206 is equivalent to -rw-rw-rw-
152 | end
153 | end
154 | end
155 |
156 | def initialize(mappedZip)
157 | @mappedZip = mappedZip
158 | end
159 |
160 | def get_entry(fileName)
161 | if ! exists?(fileName)
162 | raise Errno::ENOENT, "No such file or directory - #{fileName}"
163 | end
164 | @mappedZip.find_entry(fileName)
165 | end
166 | private :get_entry
167 |
168 | def unix_mode_cmp(fileName, mode)
169 | begin
170 | e = get_entry(fileName)
171 | e.fstype == 3 && ((e.externalFileAttributes >> 16) & mode ) != 0
172 | rescue Errno::ENOENT
173 | false
174 | end
175 | end
176 | private :unix_mode_cmp
177 |
178 | def exists?(fileName)
179 | expand_path(fileName) == "/" || @mappedZip.find_entry(fileName) != nil
180 | end
181 | alias :exist? :exists?
182 |
183 | # Permissions not implemented, so if the file exists it is accessible
184 | alias owned? exists?
185 | alias grpowned? exists?
186 |
187 | def readable?(fileName)
188 | unix_mode_cmp(fileName, 0444)
189 | end
190 | alias readable_real? readable?
191 |
192 | def writable?(fileName)
193 | unix_mode_cmp(fileName, 0222)
194 | end
195 | alias writable_real? writable?
196 |
197 | def executable?(fileName)
198 | unix_mode_cmp(fileName, 0111)
199 | end
200 | alias executable_real? executable?
201 |
202 | def setuid?(fileName)
203 | unix_mode_cmp(fileName, 04000)
204 | end
205 |
206 | def setgid?(fileName)
207 | unix_mode_cmp(fileName, 02000)
208 | end
209 |
210 | def sticky?(fileName)
211 | unix_mode_cmp(fileName, 01000)
212 | end
213 |
214 | def umask(*args)
215 | ::File.umask(*args)
216 | end
217 |
218 | def truncate(fileName, len)
219 | raise StandardError, "truncate not supported"
220 | end
221 |
222 | def directory?(fileName)
223 | entry = @mappedZip.find_entry(fileName)
224 | expand_path(fileName) == "/" || (entry != nil && entry.directory?)
225 | end
226 |
227 | def open(fileName, openMode = "r", &block)
228 | case openMode
229 | when "r"
230 | @mappedZip.get_input_stream(fileName, &block)
231 | when "w"
232 | @mappedZip.get_output_stream(fileName, &block)
233 | else
234 | raise StandardError, "openmode '#{openMode} not supported" unless openMode == "r"
235 | end
236 | end
237 |
238 | def new(fileName, openMode = "r")
239 | open(fileName, openMode)
240 | end
241 |
242 | def size(fileName)
243 | @mappedZip.get_entry(fileName).size
244 | end
245 |
246 | # Returns nil for not found and nil for directories
247 | def size?(fileName)
248 | entry = @mappedZip.find_entry(fileName)
249 | return (entry == nil || entry.directory?) ? nil : entry.size
250 | end
251 |
252 | def chown(ownerInt, groupInt, *filenames)
253 | filenames.each { |fileName|
254 | e = get_entry(fileName)
255 | unless e.extra.member?("IUnix")
256 | e.extra.create("IUnix")
257 | end
258 | e.extra["IUnix"].uid = ownerInt
259 | e.extra["IUnix"].gid = groupInt
260 | }
261 | filenames.size
262 | end
263 |
264 | def chmod (modeInt, *filenames)
265 | filenames.each { |fileName|
266 | e = get_entry(fileName)
267 | e.fstype = 3 # force convertion filesystem type to unix
268 | e.externalFileAttributes = modeInt << 16
269 | }
270 | filenames.size
271 | end
272 |
273 | def zero?(fileName)
274 | sz = size(fileName)
275 | sz == nil || sz == 0
276 | rescue Errno::ENOENT
277 | false
278 | end
279 |
280 | def file?(fileName)
281 | entry = @mappedZip.find_entry(fileName)
282 | entry != nil && entry.file?
283 | end
284 |
285 | def dirname(fileName)
286 | ::File.dirname(fileName)
287 | end
288 |
289 | def basename(fileName)
290 | ::File.basename(fileName)
291 | end
292 |
293 | def split(fileName)
294 | ::File.split(fileName)
295 | end
296 |
297 | def join(*fragments)
298 | ::File.join(*fragments)
299 | end
300 |
301 | def utime(modifiedTime, *fileNames)
302 | fileNames.each { |fileName|
303 | get_entry(fileName).time = modifiedTime
304 | }
305 | end
306 |
307 | def mtime(fileName)
308 | @mappedZip.get_entry(fileName).mtime
309 | end
310 |
311 | def atime(fileName)
312 | e = get_entry(fileName)
313 | if e.extra.member? "UniversalTime"
314 | e.extra["UniversalTime"].atime
315 | else
316 | nil
317 | end
318 | end
319 |
320 | def ctime(fileName)
321 | e = get_entry(fileName)
322 | if e.extra.member? "UniversalTime"
323 | e.extra["UniversalTime"].ctime
324 | else
325 | nil
326 | end
327 | end
328 |
329 | def pipe?(filename)
330 | false
331 | end
332 |
333 | def blockdev?(filename)
334 | false
335 | end
336 |
337 | def chardev?(filename)
338 | false
339 | end
340 |
341 | def symlink?(fileName)
342 | false
343 | end
344 |
345 | def socket?(fileName)
346 | false
347 | end
348 |
349 | def ftype(fileName)
350 | @mappedZip.get_entry(fileName).directory? ? "directory" : "file"
351 | end
352 |
353 | def readlink(fileName)
354 | raise NotImplementedError, "The readlink() function is not implemented"
355 | end
356 |
357 | def symlink(fileName, symlinkName)
358 | raise NotImplementedError, "The symlink() function is not implemented"
359 | end
360 |
361 | def link(fileName, symlinkName)
362 | raise NotImplementedError, "The link() function is not implemented"
363 | end
364 |
365 | def pipe
366 | raise NotImplementedError, "The pipe() function is not implemented"
367 | end
368 |
369 | def stat(fileName)
370 | if ! exists?(fileName)
371 | raise Errno::ENOENT, fileName
372 | end
373 | ZipFsStat.new(self, fileName)
374 | end
375 |
376 | alias lstat stat
377 |
378 | def readlines(fileName)
379 | open(fileName) { |is| is.readlines }
380 | end
381 |
382 | def read(fileName)
383 | @mappedZip.read(fileName)
384 | end
385 |
386 | def popen(*args, &aProc)
387 | File.popen(*args, &aProc)
388 | end
389 |
390 | def foreach(fileName, aSep = $/, &aProc)
391 | open(fileName) { |is| is.each_line(aSep, &aProc) }
392 | end
393 |
394 | def delete(*args)
395 | args.each {
396 | |fileName|
397 | if directory?(fileName)
398 | raise Errno::EISDIR, "Is a directory - \"#{fileName}\""
399 | end
400 | @mappedZip.remove(fileName)
401 | }
402 | end
403 |
404 | def rename(fileToRename, newName)
405 | @mappedZip.rename(fileToRename, newName) { true }
406 | end
407 |
408 | alias :unlink :delete
409 |
410 | def expand_path(aPath)
411 | @mappedZip.expand_path(aPath)
412 | end
413 | end
414 |
415 | # Instances of this class are normally accessed via the accessor
416 | # ZipFile::dir. An instance of ZipFsDir behaves like ruby's
417 | # builtin Dir (class) object, except it works on ZipFile entries.
418 | #
419 | # The individual methods are not documented due to their
420 | # similarity with the methods in Dir
421 | class ZipFsDir
422 |
423 | def initialize(mappedZip)
424 | @mappedZip = mappedZip
425 | end
426 |
427 | attr_writer :file
428 |
429 | def new(aDirectoryName)
430 | ZipFsDirIterator.new(entries(aDirectoryName))
431 | end
432 |
433 | def open(aDirectoryName)
434 | dirIt = new(aDirectoryName)
435 | if block_given?
436 | begin
437 | yield(dirIt)
438 | return nil
439 | ensure
440 | dirIt.close
441 | end
442 | end
443 | dirIt
444 | end
445 |
446 | def pwd; @mappedZip.pwd; end
447 | alias getwd pwd
448 |
449 | def chdir(aDirectoryName)
450 | unless @file.stat(aDirectoryName).directory?
451 | raise Errno::EINVAL, "Invalid argument - #{aDirectoryName}"
452 | end
453 | @mappedZip.pwd = @file.expand_path(aDirectoryName)
454 | end
455 |
456 | def entries(aDirectoryName)
457 | entries = []
458 | foreach(aDirectoryName) { |e| entries << e }
459 | entries
460 | end
461 |
462 | def foreach(aDirectoryName)
463 | unless @file.stat(aDirectoryName).directory?
464 | raise Errno::ENOTDIR, aDirectoryName
465 | end
466 | path = @file.expand_path(aDirectoryName).ensure_end("/")
467 |
468 | subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$")
469 | @mappedZip.each {
470 | |fileName|
471 | match = subDirEntriesRegex.match(fileName)
472 | yield(match[1]) unless match == nil
473 | }
474 | end
475 |
476 | def delete(entryName)
477 | unless @file.stat(entryName).directory?
478 | raise Errno::EINVAL, "Invalid argument - #{entryName}"
479 | end
480 | @mappedZip.remove(entryName)
481 | end
482 | alias rmdir delete
483 | alias unlink delete
484 |
485 | def mkdir(entryName, permissionInt = 0755)
486 | @mappedZip.mkdir(entryName, permissionInt)
487 | end
488 |
489 | def chroot(*args)
490 | raise NotImplementedError, "The chroot() function is not implemented"
491 | end
492 |
493 | end
494 |
495 | class ZipFsDirIterator # :nodoc:all
496 | include Enumerable
497 |
498 | def initialize(arrayOfFileNames)
499 | @fileNames = arrayOfFileNames
500 | @index = 0
501 | end
502 |
503 | def close
504 | @fileNames = nil
505 | end
506 |
507 | def each(&aProc)
508 | raise IOError, "closed directory" if @fileNames == nil
509 | @fileNames.each(&aProc)
510 | end
511 |
512 | def read
513 | raise IOError, "closed directory" if @fileNames == nil
514 | @fileNames[(@index+=1)-1]
515 | end
516 |
517 | def rewind
518 | raise IOError, "closed directory" if @fileNames == nil
519 | @index = 0
520 | end
521 |
522 | def seek(anIntegerPosition)
523 | raise IOError, "closed directory" if @fileNames == nil
524 | @index = anIntegerPosition
525 | end
526 |
527 | def tell
528 | raise IOError, "closed directory" if @fileNames == nil
529 | @index
530 | end
531 | end
532 |
533 | # All access to ZipFile from ZipFsFile and ZipFsDir goes through a
534 | # ZipFileNameMapper, which has one responsibility: ensure
535 | class ZipFileNameMapper # :nodoc:all
536 | include Enumerable
537 |
538 | def initialize(zipFile)
539 | @zipFile = zipFile
540 | @pwd = "/"
541 | end
542 |
543 | attr_accessor :pwd
544 |
545 | def find_entry(fileName)
546 | @zipFile.find_entry(expand_to_entry(fileName))
547 | end
548 |
549 | def get_entry(fileName)
550 | @zipFile.get_entry(expand_to_entry(fileName))
551 | end
552 |
553 | def get_input_stream(fileName, &aProc)
554 | @zipFile.get_input_stream(expand_to_entry(fileName), &aProc)
555 | end
556 |
557 | def get_output_stream(fileName, &aProc)
558 | @zipFile.get_output_stream(expand_to_entry(fileName), &aProc)
559 | end
560 |
561 | def read(fileName)
562 | @zipFile.read(expand_to_entry(fileName))
563 | end
564 |
565 | def remove(fileName)
566 | @zipFile.remove(expand_to_entry(fileName))
567 | end
568 |
569 | def rename(fileName, newName, &continueOnExistsProc)
570 | @zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName),
571 | &continueOnExistsProc)
572 | end
573 |
574 | def mkdir(fileName, permissionInt = 0755)
575 | @zipFile.mkdir(expand_to_entry(fileName), permissionInt)
576 | end
577 |
578 | # Turns entries into strings and adds leading /
579 | # and removes trailing slash on directories
580 | def each
581 | @zipFile.each {
582 | |e|
583 | yield("/"+e.to_s.chomp("/"))
584 | }
585 | end
586 |
587 | def expand_path(aPath)
588 | expanded = aPath.starts_with("/") ? aPath : @pwd.ensure_end("/") + aPath
589 | expanded.gsub!(/\/\.(\/|$)/, "")
590 | expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, "")
591 | expanded.empty? ? "/" : expanded
592 | end
593 |
594 | private
595 |
596 | def expand_to_entry(aPath)
597 | expand_path(aPath).lchop
598 | end
599 | end
600 | end
601 |
602 | class ZipFile
603 | include ZipFileSystem
604 | end
605 | end
606 |
607 | # Copyright (C) 2002, 2003 Thomas Sondergaard
608 | # rubyzip is free software; you can redistribute it and/or
609 | # modify it under the terms of the ruby license.
610 |
--------------------------------------------------------------------------------
/vendor/plugins/rubyzip/lib/zip/ziprequire.rb:
--------------------------------------------------------------------------------
1 | # With ziprequire you can load ruby modules from a zip file. This means
2 | # ruby's module include path can include zip-files.
3 | #
4 | # The following example creates a zip file with a single entry
5 | # log/simplelog.rb
that contains a single function
6 | # simpleLog
:
7 | #
8 | # require 'zip/zipfilesystem'
9 | #
10 | # Zip::ZipFile.open("my.zip", true) {
11 | # |zf|
12 | # zf.file.open("log/simplelog.rb", "w") {
13 | # |f|
14 | # f.puts "def simpleLog(v)"
15 | # f.puts ' Kernel.puts "INFO: #{v}"'
16 | # f.puts "end"
17 | # }
18 | # }
19 | #
20 | # To use the ruby module stored in the zip archive simply require
21 | # zip/ziprequire
and include the my.zip
zip
22 | # file in the module search path. The following command shows one
23 | # way to do this:
24 | #
25 | # ruby -rzip/ziprequire -Imy.zip -e " require 'log/simplelog'; simpleLog 'Hello world' "
26 |
27 | #$: << 'data/rubycode.zip' << 'data/rubycode2.zip'
28 |
29 |
30 | require 'zip/zip'
31 |
32 | class ZipList #:nodoc:all
33 | def initialize(zipFileList)
34 | @zipFileList = zipFileList
35 | end
36 |
37 | def get_input_stream(entry, &aProc)
38 | @zipFileList.each {
39 | |zfName|
40 | Zip::ZipFile.open(zfName) {
41 | |zf|
42 | begin
43 | return zf.get_input_stream(entry, &aProc)
44 | rescue Errno::ENOENT
45 | end
46 | }
47 | }
48 | raise Errno::ENOENT,
49 | "No matching entry found in zip files '#{@zipFileList.join(', ')}' "+
50 | " for '#{entry}'"
51 | end
52 | end
53 |
54 |
55 | module Kernel #:nodoc:all
56 | alias :oldRequire :require
57 |
58 | def require(moduleName)
59 | zip_require(moduleName) || oldRequire(moduleName)
60 | end
61 |
62 | def zip_require(moduleName)
63 | return false if already_loaded?(moduleName)
64 | get_resource(ensure_rb_extension(moduleName)) {
65 | |zis|
66 | eval(zis.read); $" << moduleName
67 | }
68 | return true
69 | rescue Errno::ENOENT => ex
70 | return false
71 | end
72 |
73 | def get_resource(resourceName, &aProc)
74 | zl = ZipList.new($:.grep(/\.zip$/))
75 | zl.get_input_stream(resourceName, &aProc)
76 | end
77 |
78 | def already_loaded?(moduleName)
79 | moduleRE = Regexp.new("^"+moduleName+"(\.rb|\.so|\.dll|\.o)?$")
80 | $".detect { |e| e =~ moduleRE } != nil
81 | end
82 |
83 | def ensure_rb_extension(aString)
84 | aString.sub(/(\.rb)?$/i, ".rb")
85 | end
86 | end
87 |
88 | # Copyright (C) 2002 Thomas Sondergaard
89 | # rubyzip is free software; you can redistribute it and/or
90 | # modify it under the terms of the ruby license.
91 |
--------------------------------------------------------------------------------
/vendor/plugins/rubyzip/lib/zipfilesystem.rb:
--------------------------------------------------------------------------------
1 | require 'zip/zip'
2 |
3 | module Zip
4 |
5 | # The ZipFileSystem API provides an API for accessing entries in
6 | # a zip archive that is similar to ruby's builtin File and Dir
7 | # classes.
8 | #
9 | # Requiring 'zip/zipfilesystem' includes this module in ZipFile
10 | # making the methods in this module available on ZipFile objects.
11 | #
12 | # Using this API the following example creates a new zip file
13 | # my.zip
containing a normal entry with the name
14 | # first.txt
, a directory entry named mydir
15 | # and finally another normal entry named second.txt
16 | #
17 | # require 'zip/zipfilesystem'
18 | #
19 | # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
20 | # |zipfile|
21 | # zipfile.file.open("first.txt", "w") { |f| f.puts "Hello world" }
22 | # zipfile.dir.mkdir("mydir")
23 | # zipfile.file.open("mydir/second.txt", "w") { |f| f.puts "Hello again" }
24 | # }
25 | #
26 | # Reading is as easy as writing, as the following example shows. The
27 | # example writes the contents of first.txt
from zip archive
28 | # my.zip
to standard out.
29 | #
30 | # require 'zip/zipfilesystem'
31 | #
32 | # Zip::ZipFile.open("my.zip") {
33 | # |zipfile|
34 | # puts zipfile.file.read("first.txt")
35 | # }
36 |
37 | module ZipFileSystem
38 |
39 | def initialize # :nodoc:
40 | mappedZip = ZipFileNameMapper.new(self)
41 | @zipFsDir = ZipFsDir.new(mappedZip)
42 | @zipFsFile = ZipFsFile.new(mappedZip)
43 | @zipFsDir.file = @zipFsFile
44 | @zipFsFile.dir = @zipFsDir
45 | end
46 |
47 | # Returns a ZipFsDir which is much like ruby's builtin Dir (class)
48 | # object, except it works on the ZipFile on which this method is
49 | # invoked
50 | def dir
51 | @zipFsDir
52 | end
53 |
54 | # Returns a ZipFsFile which is much like ruby's builtin File (class)
55 | # object, except it works on the ZipFile on which this method is
56 | # invoked
57 | def file
58 | @zipFsFile
59 | end
60 |
61 | # Instances of this class are normally accessed via the accessor
62 | # ZipFile::file. An instance of ZipFsFile behaves like ruby's
63 | # builtin File (class) object, except it works on ZipFile entries.
64 | #
65 | # The individual methods are not documented due to their
66 | # similarity with the methods in File
67 | class ZipFsFile
68 |
69 | attr_writer :dir
70 | # protected :dir
71 |
72 | class ZipFsStat
73 | def initialize(zipFsFile, entryName)
74 | @zipFsFile = zipFsFile
75 | @entryName = entryName
76 | end
77 |
78 | def forward_invoke(msg)
79 | @zipFsFile.send(msg, @entryName)
80 | end
81 |
82 | def kind_of?(t)
83 | super || t == ::File::Stat
84 | end
85 |
86 | forward_message :forward_invoke, :file?, :directory?, :pipe?, :chardev?
87 | forward_message :forward_invoke, :symlink?, :socket?, :blockdev?
88 | forward_message :forward_invoke, :readable?, :readable_real?
89 | forward_message :forward_invoke, :writable?, :writable_real?
90 | forward_message :forward_invoke, :executable?, :executable_real?
91 | forward_message :forward_invoke, :sticky?, :owned?, :grpowned?
92 | forward_message :forward_invoke, :setuid?, :setgid?
93 | forward_message :forward_invoke, :zero?
94 | forward_message :forward_invoke, :size, :size?
95 | forward_message :forward_invoke, :mtime, :atime, :ctime
96 |
97 | def blocks; nil; end
98 |
99 | def get_entry
100 | @zipFsFile.__send__(:get_entry, @entryName)
101 | end
102 | private :get_entry
103 |
104 | def gid
105 | e = get_entry
106 | if e.extra.member? "IUnix"
107 | e.extra["IUnix"].gid || 0
108 | else
109 | 0
110 | end
111 | end
112 |
113 | def uid
114 | e = get_entry
115 | if e.extra.member? "IUnix"
116 | e.extra["IUnix"].uid || 0
117 | else
118 | 0
119 | end
120 | end
121 |
122 | def ino; 0; end
123 |
124 | def dev; 0; end
125 |
126 | def rdev; 0; end
127 |
128 | def rdev_major; 0; end
129 |
130 | def rdev_minor; 0; end
131 |
132 | def ftype
133 | if file?
134 | return "file"
135 | elsif directory?
136 | return "directory"
137 | else
138 | raise StandardError, "Unknown file type"
139 | end
140 | end
141 |
142 | def nlink; 1; end
143 |
144 | def blksize; nil; end
145 |
146 | def mode
147 | e = get_entry
148 | if e.fstype == 3
149 | e.externalFileAttributes >> 16
150 | else
151 | 33206 # 33206 is equivalent to -rw-rw-rw-
152 | end
153 | end
154 | end
155 |
156 | def initialize(mappedZip)
157 | @mappedZip = mappedZip
158 | end
159 |
160 | def get_entry(fileName)
161 | if ! exists?(fileName)
162 | raise Errno::ENOENT, "No such file or directory - #{fileName}"
163 | end
164 | @mappedZip.find_entry(fileName)
165 | end
166 | private :get_entry
167 |
168 | def unix_mode_cmp(fileName, mode)
169 | begin
170 | e = get_entry(fileName)
171 | e.fstype == 3 && ((e.externalFileAttributes >> 16) & mode ) != 0
172 | rescue Errno::ENOENT
173 | false
174 | end
175 | end
176 | private :unix_mode_cmp
177 |
178 | def exists?(fileName)
179 | expand_path(fileName) == "/" || @mappedZip.find_entry(fileName) != nil
180 | end
181 | alias :exist? :exists?
182 |
183 | # Permissions not implemented, so if the file exists it is accessible
184 | alias owned? exists?
185 | alias grpowned? exists?
186 |
187 | def readable?(fileName)
188 | unix_mode_cmp(fileName, 0444)
189 | end
190 | alias readable_real? readable?
191 |
192 | def writable?(fileName)
193 | unix_mode_cmp(fileName, 0222)
194 | end
195 | alias writable_real? writable?
196 |
197 | def executable?(fileName)
198 | unix_mode_cmp(fileName, 0111)
199 | end
200 | alias executable_real? executable?
201 |
202 | def setuid?(fileName)
203 | unix_mode_cmp(fileName, 04000)
204 | end
205 |
206 | def setgid?(fileName)
207 | unix_mode_cmp(fileName, 02000)
208 | end
209 |
210 | def sticky?(fileName)
211 | unix_mode_cmp(fileName, 01000)
212 | end
213 |
214 | def umask(*args)
215 | ::File.umask(*args)
216 | end
217 |
218 | def truncate(fileName, len)
219 | raise StandardError, "truncate not supported"
220 | end
221 |
222 | def directory?(fileName)
223 | entry = @mappedZip.find_entry(fileName)
224 | expand_path(fileName) == "/" || (entry != nil && entry.directory?)
225 | end
226 |
227 | def open(fileName, openMode = "r", &block)
228 | case openMode
229 | when "r"
230 | @mappedZip.get_input_stream(fileName, &block)
231 | when "w"
232 | @mappedZip.get_output_stream(fileName, &block)
233 | else
234 | raise StandardError, "openmode '#{openMode} not supported" unless openMode == "r"
235 | end
236 | end
237 |
238 | def new(fileName, openMode = "r")
239 | open(fileName, openMode)
240 | end
241 |
242 | def size(fileName)
243 | @mappedZip.get_entry(fileName).size
244 | end
245 |
246 | # Returns nil for not found and nil for directories
247 | def size?(fileName)
248 | entry = @mappedZip.find_entry(fileName)
249 | return (entry == nil || entry.directory?) ? nil : entry.size
250 | end
251 |
252 | def chown(ownerInt, groupInt, *filenames)
253 | filenames.each { |fileName|
254 | e = get_entry(fileName)
255 | unless e.extra.member?("IUnix")
256 | e.extra.create("IUnix")
257 | end
258 | e.extra["IUnix"].uid = ownerInt
259 | e.extra["IUnix"].gid = groupInt
260 | }
261 | filenames.size
262 | end
263 |
264 | def chmod (modeInt, *filenames)
265 | filenames.each { |fileName|
266 | e = get_entry(fileName)
267 | e.fstype = 3 # force convertion filesystem type to unix
268 | e.externalFileAttributes = modeInt << 16
269 | }
270 | filenames.size
271 | end
272 |
273 | def zero?(fileName)
274 | sz = size(fileName)
275 | sz == nil || sz == 0
276 | rescue Errno::ENOENT
277 | false
278 | end
279 |
280 | def file?(fileName)
281 | entry = @mappedZip.find_entry(fileName)
282 | entry != nil && entry.file?
283 | end
284 |
285 | def dirname(fileName)
286 | ::File.dirname(fileName)
287 | end
288 |
289 | def basename(fileName)
290 | ::File.basename(fileName)
291 | end
292 |
293 | def split(fileName)
294 | ::File.split(fileName)
295 | end
296 |
297 | def join(*fragments)
298 | ::File.join(*fragments)
299 | end
300 |
301 | def utime(modifiedTime, *fileNames)
302 | fileNames.each { |fileName|
303 | get_entry(fileName).time = modifiedTime
304 | }
305 | end
306 |
307 | def mtime(fileName)
308 | @mappedZip.get_entry(fileName).mtime
309 | end
310 |
311 | def atime(fileName)
312 | e = get_entry(fileName)
313 | if e.extra.member? "UniversalTime"
314 | e.extra["UniversalTime"].atime
315 | else
316 | nil
317 | end
318 | end
319 |
320 | def ctime(fileName)
321 | e = get_entry(fileName)
322 | if e.extra.member? "UniversalTime"
323 | e.extra["UniversalTime"].ctime
324 | else
325 | nil
326 | end
327 | end
328 |
329 | def pipe?(filename)
330 | false
331 | end
332 |
333 | def blockdev?(filename)
334 | false
335 | end
336 |
337 | def chardev?(filename)
338 | false
339 | end
340 |
341 | def symlink?(fileName)
342 | false
343 | end
344 |
345 | def socket?(fileName)
346 | false
347 | end
348 |
349 | def ftype(fileName)
350 | @mappedZip.get_entry(fileName).directory? ? "directory" : "file"
351 | end
352 |
353 | def readlink(fileName)
354 | raise NotImplementedError, "The readlink() function is not implemented"
355 | end
356 |
357 | def symlink(fileName, symlinkName)
358 | raise NotImplementedError, "The symlink() function is not implemented"
359 | end
360 |
361 | def link(fileName, symlinkName)
362 | raise NotImplementedError, "The link() function is not implemented"
363 | end
364 |
365 | def pipe
366 | raise NotImplementedError, "The pipe() function is not implemented"
367 | end
368 |
369 | def stat(fileName)
370 | if ! exists?(fileName)
371 | raise Errno::ENOENT, fileName
372 | end
373 | ZipFsStat.new(self, fileName)
374 | end
375 |
376 | alias lstat stat
377 |
378 | def readlines(fileName)
379 | open(fileName) { |is| is.readlines }
380 | end
381 |
382 | def read(fileName)
383 | @mappedZip.read(fileName)
384 | end
385 |
386 | def popen(*args, &aProc)
387 | File.popen(*args, &aProc)
388 | end
389 |
390 | def foreach(fileName, aSep = $/, &aProc)
391 | open(fileName) { |is| is.each_line(aSep, &aProc) }
392 | end
393 |
394 | def delete(*args)
395 | args.each {
396 | |fileName|
397 | if directory?(fileName)
398 | raise Errno::EISDIR, "Is a directory - \"#{fileName}\""
399 | end
400 | @mappedZip.remove(fileName)
401 | }
402 | end
403 |
404 | def rename(fileToRename, newName)
405 | @mappedZip.rename(fileToRename, newName) { true }
406 | end
407 |
408 | alias :unlink :delete
409 |
410 | def expand_path(aPath)
411 | @mappedZip.expand_path(aPath)
412 | end
413 | end
414 |
415 | # Instances of this class are normally accessed via the accessor
416 | # ZipFile::dir. An instance of ZipFsDir behaves like ruby's
417 | # builtin Dir (class) object, except it works on ZipFile entries.
418 | #
419 | # The individual methods are not documented due to their
420 | # similarity with the methods in Dir
421 | class ZipFsDir
422 |
423 | def initialize(mappedZip)
424 | @mappedZip = mappedZip
425 | end
426 |
427 | attr_writer :file
428 |
429 | def new(aDirectoryName)
430 | ZipFsDirIterator.new(entries(aDirectoryName))
431 | end
432 |
433 | def open(aDirectoryName)
434 | dirIt = new(aDirectoryName)
435 | if block_given?
436 | begin
437 | yield(dirIt)
438 | return nil
439 | ensure
440 | dirIt.close
441 | end
442 | end
443 | dirIt
444 | end
445 |
446 | def pwd; @mappedZip.pwd; end
447 | alias getwd pwd
448 |
449 | def chdir(aDirectoryName)
450 | unless @file.stat(aDirectoryName).directory?
451 | raise Errno::EINVAL, "Invalid argument - #{aDirectoryName}"
452 | end
453 | @mappedZip.pwd = @file.expand_path(aDirectoryName)
454 | end
455 |
456 | def entries(aDirectoryName)
457 | entries = []
458 | foreach(aDirectoryName) { |e| entries << e }
459 | entries
460 | end
461 |
462 | def foreach(aDirectoryName)
463 | unless @file.stat(aDirectoryName).directory?
464 | raise Errno::ENOTDIR, aDirectoryName
465 | end
466 | path = @file.expand_path(aDirectoryName).ensure_end("/")
467 |
468 | subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$")
469 | @mappedZip.each {
470 | |fileName|
471 | match = subDirEntriesRegex.match(fileName)
472 | yield(match[1]) unless match == nil
473 | }
474 | end
475 |
476 | def delete(entryName)
477 | unless @file.stat(entryName).directory?
478 | raise Errno::EINVAL, "Invalid argument - #{entryName}"
479 | end
480 | @mappedZip.remove(entryName)
481 | end
482 | alias rmdir delete
483 | alias unlink delete
484 |
485 | def mkdir(entryName, permissionInt = 0755)
486 | @mappedZip.mkdir(entryName, permissionInt)
487 | end
488 |
489 | def chroot(*args)
490 | raise NotImplementedError, "The chroot() function is not implemented"
491 | end
492 |
493 | end
494 |
495 | class ZipFsDirIterator # :nodoc:all
496 | include Enumerable
497 |
498 | def initialize(arrayOfFileNames)
499 | @fileNames = arrayOfFileNames
500 | @index = 0
501 | end
502 |
503 | def close
504 | @fileNames = nil
505 | end
506 |
507 | def each(&aProc)
508 | raise IOError, "closed directory" if @fileNames == nil
509 | @fileNames.each(&aProc)
510 | end
511 |
512 | def read
513 | raise IOError, "closed directory" if @fileNames == nil
514 | @fileNames[(@index+=1)-1]
515 | end
516 |
517 | def rewind
518 | raise IOError, "closed directory" if @fileNames == nil
519 | @index = 0
520 | end
521 |
522 | def seek(anIntegerPosition)
523 | raise IOError, "closed directory" if @fileNames == nil
524 | @index = anIntegerPosition
525 | end
526 |
527 | def tell
528 | raise IOError, "closed directory" if @fileNames == nil
529 | @index
530 | end
531 | end
532 |
533 | # All access to ZipFile from ZipFsFile and ZipFsDir goes through a
534 | # ZipFileNameMapper, which has one responsibility: ensure
535 | class ZipFileNameMapper # :nodoc:all
536 | include Enumerable
537 |
538 | def initialize(zipFile)
539 | @zipFile = zipFile
540 | @pwd = "/"
541 | end
542 |
543 | attr_accessor :pwd
544 |
545 | def find_entry(fileName)
546 | @zipFile.find_entry(expand_to_entry(fileName))
547 | end
548 |
549 | def get_entry(fileName)
550 | @zipFile.get_entry(expand_to_entry(fileName))
551 | end
552 |
553 | def get_input_stream(fileName, &aProc)
554 | @zipFile.get_input_stream(expand_to_entry(fileName), &aProc)
555 | end
556 |
557 | def get_output_stream(fileName, &aProc)
558 | @zipFile.get_output_stream(expand_to_entry(fileName), &aProc)
559 | end
560 |
561 | def read(fileName)
562 | @zipFile.read(expand_to_entry(fileName))
563 | end
564 |
565 | def remove(fileName)
566 | @zipFile.remove(expand_to_entry(fileName))
567 | end
568 |
569 | def rename(fileName, newName, &continueOnExistsProc)
570 | @zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName),
571 | &continueOnExistsProc)
572 | end
573 |
574 | def mkdir(fileName, permissionInt = 0755)
575 | @zipFile.mkdir(expand_to_entry(fileName), permissionInt)
576 | end
577 |
578 | # Turns entries into strings and adds leading /
579 | # and removes trailing slash on directories
580 | def each
581 | @zipFile.each {
582 | |e|
583 | yield("/"+e.to_s.chomp("/"))
584 | }
585 | end
586 |
587 | def expand_path(aPath)
588 | expanded = aPath.starts_with("/") ? aPath : @pwd.ensure_end("/") + aPath
589 | expanded.gsub!(/\/\.(\/|$)/, "")
590 | expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, "")
591 | expanded.empty? ? "/" : expanded
592 | end
593 |
594 | private
595 |
596 | def expand_to_entry(aPath)
597 | expand_path(aPath).lchop
598 | end
599 | end
600 | end
601 |
602 | class ZipFile
603 | include ZipFileSystem
604 | end
605 | end
606 |
607 | # Copyright (C) 2002, 2003 Thomas Sondergaard
608 | # rubyzip is free software; you can redistribute it and/or
609 | # modify it under the terms of the ruby license.
610 |
--------------------------------------------------------------------------------
/vendor/plugins/rubyzip/lib/ziprequire.rb:
--------------------------------------------------------------------------------
1 | # With ziprequire you can load ruby modules from a zip file. This means
2 | # ruby's module include path can include zip-files.
3 | #
4 | # The following example creates a zip file with a single entry
5 | # log/simplelog.rb
that contains a single function
6 | # simpleLog
:
7 | #
8 | # require 'zip/zipfilesystem'
9 | #
10 | # Zip::ZipFile.open("my.zip", true) {
11 | # |zf|
12 | # zf.file.open("log/simplelog.rb", "w") {
13 | # |f|
14 | # f.puts "def simpleLog(v)"
15 | # f.puts ' Kernel.puts "INFO: #{v}"'
16 | # f.puts "end"
17 | # }
18 | # }
19 | #
20 | # To use the ruby module stored in the zip archive simply require
21 | # zip/ziprequire
and include the my.zip
zip
22 | # file in the module search path. The following command shows one
23 | # way to do this:
24 | #
25 | # ruby -rzip/ziprequire -Imy.zip -e " require 'log/simplelog'; simpleLog 'Hello world' "
26 |
27 | #$: << 'data/rubycode.zip' << 'data/rubycode2.zip'
28 |
29 |
30 | require 'zip/zip'
31 |
32 | class ZipList #:nodoc:all
33 | def initialize(zipFileList)
34 | @zipFileList = zipFileList
35 | end
36 |
37 | def get_input_stream(entry, &aProc)
38 | @zipFileList.each {
39 | |zfName|
40 | Zip::ZipFile.open(zfName) {
41 | |zf|
42 | begin
43 | return zf.get_input_stream(entry, &aProc)
44 | rescue Errno::ENOENT
45 | end
46 | }
47 | }
48 | raise Errno::ENOENT,
49 | "No matching entry found in zip files '#{@zipFileList.join(', ')}' "+
50 | " for '#{entry}'"
51 | end
52 | end
53 |
54 |
55 | module Kernel #:nodoc:all
56 | alias :oldRequire :require
57 |
58 | def require(moduleName)
59 | zip_require(moduleName) || oldRequire(moduleName)
60 | end
61 |
62 | def zip_require(moduleName)
63 | return false if already_loaded?(moduleName)
64 | get_resource(ensure_rb_extension(moduleName)) {
65 | |zis|
66 | eval(zis.read); $" << moduleName
67 | }
68 | return true
69 | rescue Errno::ENOENT => ex
70 | return false
71 | end
72 |
73 | def get_resource(resourceName, &aProc)
74 | zl = ZipList.new($:.grep(/\.zip$/))
75 | zl.get_input_stream(resourceName, &aProc)
76 | end
77 |
78 | def already_loaded?(moduleName)
79 | moduleRE = Regexp.new("^"+moduleName+"(\.rb|\.so|\.dll|\.o)?$")
80 | $".detect { |e| e =~ moduleRE } != nil
81 | end
82 |
83 | def ensure_rb_extension(aString)
84 | aString.sub(/(\.rb)?$/i, ".rb")
85 | end
86 | end
87 |
88 | # Copyright (C) 2002 Thomas Sondergaard
89 | # rubyzip is free software; you can redistribute it and/or
90 | # modify it under the terms of the ruby license.
91 |
--------------------------------------------------------------------------------
/vendor/plugins/rubyzip/tasks/rubyzip_tasks.rake:
--------------------------------------------------------------------------------
1 | # desc "Explaining what the task does"
2 | # task :rubyzip do
3 | # # Task goes here
4 | # end
5 |
--------------------------------------------------------------------------------
/vendor/plugins/rubyzip/test/rubyzip_test.rb:
--------------------------------------------------------------------------------
1 | require 'test/unit'
2 |
3 | class RubyzipTest < Test::Unit::TestCase
4 | # Replace this with your real tests.
5 | def test_this_plugin
6 | flunk
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/vendor/plugins/rubyzip/uninstall.rb:
--------------------------------------------------------------------------------
1 | # Uninstall hook code here
2 |
--------------------------------------------------------------------------------