├── .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 . 16 | 17 | require 'pusher' 18 | require 'uri' 19 | require 'net/http' 20 | 21 | Pusher.app_id = '3528' 22 | Pusher.key = '03cb0652f55212acc073' 23 | Pusher.secret = '908ae93e9c709a32f068' 24 | 25 | class FirecssController < ApplicationController 26 | 27 | #TODO reset on reload - no restarting server 28 | #TODO Test edits from different machines at same time - do we need to track source? 29 | #TODO password/chanel access 30 | #TODO save button 31 | #TODO setting for FireCSS server - get the server address from href of firecss script in page (saves working out editing of server address in prefs) 32 | #TODO Pick up new rules and edits in css panel 33 | #TODO Use same method for propagating html edits 34 | #TODO handle clicking the disbale css button. 35 | #TODO reset button? 36 | #TODO run using node on Duostack - and use node locally? 37 | #TODO disable buttons when no save or reset options (eg no edits or script not loaded). 38 | 39 | before_filter :id_client 40 | 41 | def id_client 42 | session[:client] ||= session.id 43 | @client = session[:client] 44 | end 45 | 46 | def index 47 | code = '' 48 | puts("Index Session ID: #{@client}") 49 | source = request.env["HTTP_REFERER"] 50 | pusher_channel = source.split('?').first.split('://').last.gsub('/','_') 51 | puts pusher_channel 52 | edits = params[:edits] 53 | mods = Rails.cache.fetch(pusher_channel){[]}.dup 54 | number_mods = mods.length 55 | edits.each_with_index do |edit, i| 56 | if (edit.to_i == -1) 57 | mods = [] 58 | code = "window.location.reload();" 59 | else 60 | mods << {:selector => params[:selectors][i], :property => params[:properties][i], :value => params[:values][i], :source => params[:sources][i], :line => params[:lines][i].to_f, :timestamp => params[:timestamps][i].to_i, :edit => number_mods + i, :client => @client} 61 | puts "#{params[:selectors][i]} {#{params[:properties][i]}: #{params[:values][i]}}" 62 | end 63 | end 64 | Pusher[pusher_channel].trigger('FireCSS', mods) 65 | Rails.cache.write(pusher_channel, mods) 66 | puts y mods 67 | render :text => code #nothing needs to be returned 68 | end 69 | 70 | def polling 71 | puts("Polling Session ID: #{@client}") 72 | source = request.env["HTTP_REFERER"] 73 | pusher_channel = source.split('?').first.split('://').last.gsub('/','_') 74 | mods = Rails.cache.read(pusher_channel) || []; 75 | from = (params[:edit] || -1).to_i + 1; 76 | if (from < 1) 77 | dummy = '1' 78 | end 79 | if (from > mods.length) #this browser needs to reset itself so send reset message 80 | render_json('reload'.to_json) 81 | else 82 | updates = mods[from, mods.length] || [] 83 | updates.each_with_index {|u, i| 84 | if u[:client] == @client 85 | updates[i] = u.dup.update(:selector => nil) #somehow if we don't dup it can update the cache copy even though that's a dup and written out - go figure! 86 | end 87 | } # remove selector so client that sent updates doesn't reprocess them. 88 | if updates.length > 0 89 | dummy = '1' 90 | end 91 | puts y updates 92 | render_json(updates.to_json) 93 | end 94 | end 95 | 96 | def downloadshow 97 | source = request.env["HTTP_REFERER"] 98 | sheets = params[:stylesheets] 99 | rules = params[:rules] 100 | # note firebug may insert its own rulesheet so we need to remove if its there. 101 | result = '' 102 | sheets.each_with_index do |sheet, i| 103 | result += sheet + "\n" + rules[i] 104 | end 105 | render :text => result.gsub("\n","
") + '

' + 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 |

Sorry

6 |

There were no modifications to download

7 | -------------------------------------------------------------------------------- /app/views/firecss/signup.html.erb: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | FireCSS 7 | 8 | 9 | 10 | 11 | 12 | 14 |
15 |
16 |
17 |
FireCSS
18 |
See CSS changes in all browsers as you edit
19 | 20 |
21 |
22 |
23 | 26 |
27 |   28 |
29 |
30 | 33 | 34 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # Don't change this file! 2 | # Configure your app in config/environment.rb and config/environments/*.rb 3 | 4 | RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) 5 | 6 | module Rails 7 | class << self 8 | def boot! 9 | unless booted? 10 | preinitialize 11 | pick_boot.run 12 | end 13 | end 14 | 15 | def booted? 16 | defined? Rails::Initializer 17 | end 18 | 19 | def pick_boot 20 | (vendor_rails? ? VendorBoot : GemBoot).new 21 | end 22 | 23 | def vendor_rails? 24 | File.exist?("#{RAILS_ROOT}/vendor/rails") 25 | end 26 | 27 | def preinitialize 28 | load(preinitializer_path) if File.exist?(preinitializer_path) 29 | end 30 | 31 | def preinitializer_path 32 | "#{RAILS_ROOT}/config/preinitializer.rb" 33 | end 34 | end 35 | 36 | class Boot 37 | def run 38 | load_initializer 39 | Rails::Initializer.run(:set_load_path) 40 | end 41 | end 42 | 43 | class VendorBoot < Boot 44 | def load_initializer 45 | require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" 46 | Rails::Initializer.run(:install_gem_spec_stubs) 47 | Rails::GemDependency.add_frozen_gem_path 48 | end 49 | end 50 | 51 | class GemBoot < Boot 52 | def load_initializer 53 | self.class.load_rubygems 54 | load_rails_gem 55 | require 'initializer' 56 | end 57 | 58 | def load_rails_gem 59 | if version = self.class.gem_version 60 | gem 'rails', version 61 | else 62 | gem 'rails' 63 | end 64 | rescue Gem::LoadError => load_error 65 | $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.) 66 | exit 1 67 | end 68 | 69 | class << self 70 | def rubygems_version 71 | Gem::RubyGemsVersion rescue nil 72 | end 73 | 74 | def gem_version 75 | if defined? RAILS_GEM_VERSION 76 | RAILS_GEM_VERSION 77 | elsif ENV.include?('RAILS_GEM_VERSION') 78 | ENV['RAILS_GEM_VERSION'] 79 | else 80 | parse_gem_version(read_environment_rb) 81 | end 82 | end 83 | 84 | def load_rubygems 85 | min_version = '1.3.2' 86 | require 'rubygems' 87 | unless rubygems_version >= min_version 88 | $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) 89 | exit 1 90 | end 91 | 92 | rescue LoadError 93 | $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) 94 | exit 1 95 | end 96 | 97 | def parse_gem_version(text) 98 | $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ 99 | end 100 | 101 | private 102 | def read_environment_rb 103 | File.read("#{RAILS_ROOT}/config/environment.rb") 104 | end 105 | end 106 | end 107 | end 108 | 109 | # All that for this: 110 | Rails.boot! 111 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3-ruby (not necessary on OS X Leopard) 3 | development: 4 | adapter: mysql 5 | database: firecss_development 6 | username: root 7 | password: 8 | host: 127.0.0.1 9 | 10 | # Warning: The database defined as "test" will be erased and 11 | # re-generated from your development database when you run "rake". 12 | # Do not set this db to the same as development or production. 13 | test: 14 | adapter: sqlite3 15 | database: db/test.sqlite3 16 | pool: 5 17 | timeout: 5000 18 | 19 | production: 20 | adapter: sqlite3 21 | database: db/production.sqlite3 22 | pool: 5 23 | timeout: 5000 24 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file 2 | 3 | # Specifies gem version of Rails to use when vendor/rails is not present 4 | RAILS_GEM_VERSION = '2.3.8' unless defined? RAILS_GEM_VERSION 5 | 6 | # Bootstrap the Rails environment, frameworks, and default configuration 7 | require File.join(File.dirname(__FILE__), 'boot') 8 | 9 | Rails::Initializer.run do |config| 10 | # Settings in config/environments/* take precedence over those specified here. 11 | # Application configuration should go into files in config/initializers 12 | # -- all .rb files in that directory are automatically loaded. 13 | 14 | # Add additional load paths for your own custom dirs 15 | # config.load_paths += %W( #{RAILS_ROOT}/extras ) 16 | 17 | # Specify gems that this application depends on and have them installed with rake gems:install 18 | # config.gem "bj" 19 | # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net" 20 | # config.gem "sqlite3-ruby", :lib => "sqlite3" 21 | # config.gem "aws-s3", :lib => "aws/s3" 22 | config.gem 'pusher' 23 | 24 | # Only load the plugins named here, in the order given (default is alphabetical). 25 | # :all can be used as a placeholder for all plugins not explicitly named 26 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 27 | 28 | # Skip frameworks you're not going to use. To use Rails without a database, 29 | # you must remove the Active Record framework. 30 | # config.frameworks -= [ :active_record, :active_resource, :action_mailer ] 31 | 32 | # Activate observers that should always be running 33 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 34 | 35 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 36 | # Run "rake -D time" for a list of tasks for finding time zone names. 37 | config.time_zone = 'UTC' 38 | 39 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 40 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')] 41 | # config.i18n.default_locale = :de 42 | end -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | # Settings specified here will take precedence over those in config/environment.rb 2 | 3 | # In the development environment your application's code is reloaded on 4 | # every request. This slows down response time but is perfect for development 5 | # since you don't have to restart the webserver when you make code changes. 6 | config.cache_classes = false 7 | 8 | # Log error messages when you accidentally call methods on nil. 9 | config.whiny_nils = true 10 | 11 | # Show full error reports and disable caching 12 | config.action_controller.consider_all_requests_local = true 13 | config.action_view.debug_rjs = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | config.action_controller.allow_forgery_protection = false -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | # Settings specified here will take precedence over those in config/environment.rb 2 | 3 | # The production environment is meant for finished, "live" apps. 4 | # Code is not reloaded between requests 5 | config.cache_classes = true 6 | 7 | # Full error reports are disabled and caching is turned on 8 | config.action_controller.consider_all_requests_local = false 9 | config.action_controller.perform_caching = true 10 | config.action_view.cache_template_loading = true 11 | 12 | # See everything in the log (default is :info) 13 | # config.log_level = :debug 14 | 15 | # Use a different logger for distributed setups 16 | # config.logger = SyslogLogger.new 17 | 18 | # Use a different cache store in production 19 | # config.cache_store = :mem_cache_store 20 | 21 | # Enable serving of images, stylesheets, and javascripts from an asset server 22 | # config.action_controller.asset_host = "http://assets.example.com" 23 | 24 | # Disable delivery errors, bad email addresses will be ignored 25 | # config.action_mailer.raise_delivery_errors = false 26 | 27 | # Enable threaded mode 28 | # config.threadsafe! -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # Settings specified here will take precedence over those in config/environment.rb 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | config.cache_classes = true 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.action_controller.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | config.action_view.cache_template_loading = true 16 | 17 | # Disable request forgery protection in test environment 18 | config.action_controller.allow_forgery_protection = false 19 | 20 | # Tell Action Mailer not to deliver emails to the real world. 21 | # The :test delivery method accumulates sent emails in the 22 | # ActionMailer::Base.deliveries array. 23 | config.action_mailer.delivery_method = :test 24 | 25 | # Use SQL instead of Active Record's schema dumper when creating the test database. 26 | # This is necessary if your schema can't be completely dumped by the schema dumper, 27 | # like if you have constraints or database-specific column types 28 | # config.active_record.schema_format = :sql -------------------------------------------------------------------------------- /config/initializers/array.rb: -------------------------------------------------------------------------------- 1 | class Array 2 | 3 | def extract! 4 | result = [] 5 | self.delete_if do |item| 6 | (yield(item)) && (result << item) 7 | end 8 | return result 9 | end 10 | 11 | def take_while! 12 | result = self.take_while {|item| yield(item)} 13 | self.slice!(0,result.length) 14 | return result 15 | end 16 | 17 | end -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying do debug a problem that might steem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! -------------------------------------------------------------------------------- /config/initializers/cookie_verification_secret.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | ActionController::Base.cookie_verifier_secret = '6c8d3c7bf740cd485c15b184ccbd51c0e5b19c7a3f8fb572ccd0bd5b5e2a99cb6826bb4be56231db2078170cd1a805babff4013b95ab89f5f1f2b361cf76e567'; 8 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /config/initializers/new_rails_defaults.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # These settings change the behavior of Rails 2 apps and will be defaults 4 | # for Rails 3. You can remove this initializer when Rails 3 is released. 5 | 6 | if defined?(ActiveRecord) 7 | # Include Active Record class name as root for JSON serialized output. 8 | ActiveRecord::Base.include_root_in_json = true 9 | 10 | # Store the full class name (including module namespace) in STI type column. 11 | ActiveRecord::Base.store_full_sti_class = true 12 | end 13 | 14 | ActionController::Routing.generate_best_match = false 15 | 16 | # Use ISO 8601 format for JSON serialized times and dates. 17 | ActiveSupport.use_standard_json_time_format = true 18 | 19 | # Don't escape HTML entities in JSON, leave that for the #json_escape helper. 20 | # if you're including raw json in an HTML page. 21 | ActiveSupport.escape_html_entities_in_json = false -------------------------------------------------------------------------------- /config/initializers/patches.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'zip/zip' 3 | require 'zip/zipfilesystem' 4 | 5 | module Zip 6 | class ZipFile < ZipCentralDirectory 7 | def initialize(fileName, create = nil) 8 | super() 9 | @name = fileName 10 | @comment = "" 11 | if (fileName.is_a?(TMail::Attachment)) 12 | read_from_stream(fileName) 13 | elsif (File.exists?(fileName)) 14 | File.open(name, "rb") { |f| read_from_stream(f) } 15 | elsif (create) 16 | @entrySet = ZipEntrySet.new 17 | else 18 | raise ZipError, "File #{fileName} not found" 19 | end 20 | @create = create 21 | @storedEntries = @entrySet.dup 22 | 23 | @restore_ownership = false 24 | @restore_permissions = false 25 | @restore_times = true 26 | end 27 | end 28 | end 29 | 30 | module Zip 31 | class ZipInputStream 32 | 33 | def initialize(filename, offset = 0, is_file = true) 34 | super() 35 | if (is_file) 36 | @archiveIO = File.open(filename, "rb") 37 | else 38 | @archiveIO = filename 39 | end 40 | @archiveIO.seek(offset, IO::SEEK_SET) 41 | @decompressor = NullDecompressor.instance 42 | @currentEntry = nil 43 | end 44 | 45 | def ZipInputStream.use(contents) 46 | return new(contents, 0, false) unless block_given? 47 | 48 | zio = new(contents, 0, false) 49 | yield zio 50 | ensure 51 | zio.close if zio 52 | end 53 | 54 | end 55 | end 56 | 57 | module Zip 58 | class ZipOutputStream 59 | 60 | def initialize(fileName, stream = nil) 61 | super() 62 | @fileName = fileName 63 | @outputStream = stream ? stream : File.new(@fileName, "wb") 64 | @entrySet = ZipEntrySet.new 65 | @compressor = NullCompressor.instance 66 | @closed = false 67 | @currentEntry = nil 68 | @comment = nil 69 | end 70 | 71 | def ZipOutputStream.use(fileName, stream) 72 | return new(fileName, stream) unless block_given? 73 | zos = new(fileName, stream) 74 | yield zos 75 | ensure 76 | zos.close if zos 77 | end 78 | 79 | 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying cookie session data integrity. 4 | # If you change this key, all old sessions will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | ActionController::Base.session = { 8 | :key => '_firecss_session', 9 | :secret => '53fa9af190b0f78bb8a7c5682ac3d81ce9eb1129771702d2c2972483287ef6fba0790bda3e77506927e5da38fe7883c1735241eb3848632349d95a6098b807a9' 10 | } 11 | 12 | # Use the database for sessions instead of the cookie-based default, 13 | # which shouldn't be used to store highly confidential information 14 | # (create the session table with "rake db:sessions:create") 15 | # ActionController::Base.session_store = :active_record_store 16 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | ActionController::Routing::Routes.draw do |map| 2 | # The priority is based upon order of creation: first created -> highest priority. 3 | 4 | # Sample of regular route: 5 | # map.connect 'products/:id', :controller => 'catalog', :action => 'view' 6 | # Keep in mind you can assign values other than :controller and :action 7 | 8 | # Sample of named route: 9 | # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase' 10 | # This route can be invoked with purchase_url(:id => product.id) 11 | 12 | # Sample resource route (maps HTTP verbs to controller actions automatically): 13 | # map.resources :products 14 | 15 | # Sample resource route with options: 16 | # map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get } 17 | 18 | # Sample resource route with sub-resources: 19 | # map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller 20 | 21 | # Sample resource route with more complex sub-resources 22 | # map.resources :products do |products| 23 | # products.resources :comments 24 | # products.resources :sales, :collection => { :recent => :get } 25 | # end 26 | 27 | # Sample resource route within a namespace: 28 | # map.namespace :admin do |admin| 29 | # # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb) 30 | # admin.resources :products 31 | # end 32 | 33 | # You can have the root of your site routed with map.root -- just remember to delete public/index.html. 34 | # map.root :controller => "welcome" 35 | 36 | # See how all your routes lay out with "rake routes" 37 | 38 | # Install the default routes as the lowest priority. 39 | # Note: These default routes make all actions in every controller accessible via GET requests. You should 40 | # consider removing or commenting them out if you're using named routes and resources. 41 | map.connect ':controller/:action/:id' 42 | map.connect ':controller/:action/:id.:format' 43 | end 44 | -------------------------------------------------------------------------------- /db/migrate/20110201020804_create_fanboys.rb: -------------------------------------------------------------------------------- 1 | class CreateFanboys < ActiveRecord::Migration 2 | def self.up 3 | create_table :fanboys do |t| 4 | t.string :email 5 | 6 | t.timestamps 7 | end 8 | end 9 | 10 | def self.down 11 | drop_table :fanboys 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead of editing this file, 2 | # please use the migrations feature of Active Record to incrementally modify your database, and 3 | # then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your database schema. If you need 6 | # to create the application database on another system, you should be using db:schema:load, not running 7 | # all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations 8 | # you'll amass, the slower it'll run and the greater likelihood for issues). 9 | # 10 | # It's strongly recommended to check this file into your version control system. 11 | 12 | ActiveRecord::Schema.define(:version => 20110201020804) do 13 | 14 | create_table "fanboys", :force => true do |t| 15 | t.string "email" 16 | t.datetime "created_at" 17 | t.datetime "updated_at" 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) 7 | # Major.create(:name => 'Daley', :city => cities.first) 8 | -------------------------------------------------------------------------------- /doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /php/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | 3 | RewriteCond %{REQUEST_FILENAME} !-f 4 | RewriteRule ^(.*)$ index.php [QSA] -------------------------------------------------------------------------------- /php/FirecssCache.class.php: -------------------------------------------------------------------------------- 1 | . 17 | 18 | /** 19 | * Manage cache for FireCSS 20 | * @author Simon Leblanc 21 | * @license http://www.gnu.org/licenses/ GNU GPL 22 | * @version 0.1 23 | * @package FireCSS 24 | */ 25 | class FirecssCache 26 | { 27 | /** 28 | * @var string The cache directory 29 | * @access private 30 | * @static 31 | * @since 0.1 32 | */ 33 | private static $cache_directory = null; 34 | 35 | /** 36 | * @var string The cache file path 37 | * @access private 38 | * @static 39 | * @since 0.1 40 | */ 41 | private static $cache_filename = null; 42 | 43 | 44 | /** 45 | * Save the datas in the cache file 46 | * 47 | * @param array $datas The datas to save 48 | * @param IFirecssSession $session The firecss session 49 | * @return bool True if it's OK, false else 50 | * @access public 51 | * @static 52 | * @since 0.1 53 | */ 54 | public static function setCache($datas, IFirecssSession $session) 55 | { 56 | FirecssCache::init($session); 57 | 58 | $datas_str = 'getId(); 125 | } 126 | 127 | 128 | /** 129 | * Initialize the cache directory and filename 130 | * 131 | * @param string $directory The cache directory 132 | * @return void 133 | * @access public 134 | * @static 135 | * @since 0.1 136 | */ 137 | public static function initDirectory($directory) 138 | { 139 | if (file_exists($directory) === true && is_dir($directory) === true) { 140 | FirecssCache::$cache_directory = $directory; 141 | } else { 142 | throw new Exception('The directory doesn\'t exists'); 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /php/FirecssController.class.php: -------------------------------------------------------------------------------- 1 | . 17 | 18 | /** 19 | * Manage controller for FireCSS 20 | * @author Simon Leblanc 21 | * @license http://www.gnu.org/licenses/ GNU GPL 22 | * @version 0.1 23 | * @package FireCSS 24 | */ 25 | class FirecssController 26 | { 27 | const UPDATE = '/firecss/polling'; 28 | const INDEX = '/firecss'; 29 | const DOWNLOAD = '/firecss/download'; 30 | 31 | /** 32 | * @var IFirecssSession The Firecss session 33 | * @access private 34 | * @since 0.1 35 | */ 36 | private $session = null; 37 | 38 | /** 39 | * @var mixed The datas to send 40 | * @access private 41 | * @since 0.1 42 | */ 43 | private $datas = null; 44 | 45 | /** 46 | * @var string|null The js callback to call with the datas 47 | * @access private 48 | * @since 0.1 49 | */ 50 | private $callback = null; 51 | 52 | 53 | /** 54 | * Construt the controller 55 | * 56 | * @param IFirecssSession $session The Firecss session 57 | * @return void 58 | * @access public 59 | * @since 0.1 60 | */ 61 | public function __construct(IFirecssSession $session) 62 | { 63 | $this->session = $session; 64 | } 65 | 66 | 67 | /** 68 | * Index controller (call to update the styles in the server) 69 | * 70 | * @param array $params The $_GET params send by Firecss addon 71 | * @return FirecssController The self object to chain methods 72 | * @access public 73 | * @since 0.1 74 | */ 75 | public function index($params = array()) 76 | { 77 | $mods = FirecssCache::getCache($this->session); 78 | $number_mods = count($mods); 79 | 80 | // Check parameters 81 | $parameters = array('edits', 'selectors', 'properties', 'values', 'sources', 'lines', 'timestamps'); 82 | foreach ($parameters as $parameter) { 83 | if ($parameter === 'edits' && (isset($params[$parameter]) === false || is_array($params[$parameter]) === false)) { 84 | throw new Exception($parameter.' must be an array'); 85 | } elseif ($parameter !== 'edits' && isset($params[$parameter]) === true && is_array($params[$parameter]) === false) { 86 | throw new Exception($parameter.' must be an array'); 87 | } 88 | 89 | $$parameter = $params[$parameter]; 90 | } 91 | 92 | // Get all changes 93 | foreach ($edits as $i => $edit) { 94 | if ((int)$edit === -1) { 95 | $mods = array(); 96 | $this->datas = 'window.location.reload();'; 97 | } else { 98 | $mods[] = array( 99 | 'selector' => $selectors[$i], 100 | 'property' => $properties[$i], 101 | 'value' => $values[$i], 102 | 'source' => $sources[$i], 103 | 'line' => (float)$lines[$i], 104 | 'timestamp' => (int)$timestamps[$i], 105 | 'edit' => $number_mods + $i, 106 | 'client' => $this->session->getId() 107 | ); 108 | } 109 | } 110 | 111 | FirecssCache::setCache($mods, $this->session); 112 | 113 | return $this; 114 | } 115 | 116 | 117 | /** 118 | * Update controller (call to update the styles in the browser) 119 | * 120 | * @param array $params The $_GET params send by Firecss addon 121 | * @return FirecssController The self object to chain methods 122 | * @access public 123 | * @since 0.1 124 | */ 125 | public function update($params = array()) 126 | { 127 | $mods = FirecssCache::getCache($this->session); 128 | $number_mods = count($mods); 129 | 130 | if (isset($params['edit']) === false) { 131 | $from = 0; 132 | } else { 133 | $from = (int)$params['edit'] + 1; 134 | } 135 | 136 | if ($from > $number_mods) { 137 | $this->datas = 'reload'; 138 | } else { 139 | $updates = array_slice($mods, $from, $number_mods); 140 | 141 | /*foreach ($updates as $i => $u) { 142 | if ($u['client'] == $this->session->getId()) { 143 | $u['selector'] = null; 144 | $updates[$i] = $u; 145 | } 146 | }*/ 147 | 148 | if (count($updates) > 0) { 149 | $this->datas = $updates; 150 | } 151 | 152 | // if acallback is send, use it for the response 153 | if (isset($params['callback']) === true && empty($params['callback']) === false) { 154 | $this->callback = $params['callback']; 155 | } 156 | } 157 | 158 | return $this; 159 | } 160 | 161 | 162 | /** 163 | * Download controller (call to download styles editing) 164 | * 165 | * @param array $params The $_POST params send by Firecss addon 166 | * @return FirecssController The self object to chain methods 167 | * @access public 168 | * @since 0.1 169 | * @todo code the method :-) 170 | */ 171 | public function download($params = array()) 172 | { 173 | return $this; 174 | } 175 | 176 | 177 | /** 178 | * Get the controller name to execute 179 | * 180 | * @return string The name of the controller to execute 181 | * @access public 182 | * @since 0.1 183 | */ 184 | public function getName() 185 | { 186 | $uri = strtok($_SERVER['REQUEST_URI'], '?'); 187 | if (substr($uri, -1) === '/') { 188 | $uri = substr($uri, 0, -1); 189 | } 190 | 191 | if (substr($uri, -1 * strlen(FirecssController::INDEX)) === FirecssController::INDEX) { 192 | return 'index'; 193 | } 194 | 195 | if (substr($uri, -1 * strlen(FirecssController::UPDATE)) === FirecssController::UPDATE) { 196 | return 'update'; 197 | } 198 | 199 | if (substr($uri, -1 * strlen(FirecssController::DOWNLOAD)) === FirecssController::DOWNLOAD) { 200 | return 'download'; 201 | } 202 | 203 | throw new Exception('The action doesn\'t exists'); 204 | } 205 | 206 | 207 | /** 208 | * Get the vars according to the controller name 209 | * 210 | * @return array The parameters to use 211 | * @access public 212 | * @since 0.1 213 | */ 214 | public function getRequestVar() 215 | { 216 | $name = $this->getName(); 217 | 218 | if ($name === 'download') { 219 | return $_POST; 220 | } else { 221 | return $_GET; 222 | } 223 | 224 | throw new Exception('The action doesn\'t exists'); 225 | } 226 | 227 | 228 | /** 229 | * Send the datas in the browser 230 | * 231 | * @return void 232 | * @access public 233 | * @since 0.1 234 | */ 235 | public function run() 236 | { 237 | if ($this->datas === null) { 238 | header("HTTP/1.0 204 No Content"); 239 | header('Content-Length: 0', true); 240 | header('Content-Type: text/html', true); 241 | flush(); 242 | exit; 243 | } 244 | 245 | $datas = json_encode($this->datas); 246 | if ($this->callback !== null) { 247 | $datas = $this->callback.'('.$datas.');'; 248 | } 249 | header("HTTP/1.0 200 OK"); 250 | header('Content-Length: '.mb_strlen($datas), true); 251 | header('Content-Type: application/json', true); 252 | die($datas); 253 | } 254 | } -------------------------------------------------------------------------------- /php/FirecssSession.class.php: -------------------------------------------------------------------------------- 1 | . 17 | 18 | /** 19 | * Manage session for FireCSS 20 | * @author Simon Leblanc 21 | * @license http://www.gnu.org/licenses/ GNU GPL 22 | * @version 0.1 23 | * @package FireCSS 24 | */ 25 | class FirecssSession implements IFirecssSession 26 | { 27 | /** 28 | * @var string The session id 29 | * @access private 30 | * @since 0.1 31 | */ 32 | private $session_id = null; 33 | 34 | /** 35 | * Construct the FireCSS session 36 | * 37 | * @return void 38 | * @access public 39 | * @since 0.1 40 | */ 41 | public function __construct() 42 | { 43 | // Build session identifier 44 | $this->init(); 45 | } 46 | 47 | 48 | /** 49 | * Return the session id 50 | * 51 | * @return string The session id 52 | * @access public 53 | * @since 0.1 54 | */ 55 | public function getId() 56 | { 57 | return $this->session_id; 58 | } 59 | 60 | 61 | /** 62 | * Build the session id. The session_id is composed to : 63 | * - HTTP_REFERER 64 | * 65 | * @return void 66 | * @access private 67 | * @since 0.1 68 | */ 69 | private function init() 70 | { 71 | if ($this->session_id !== null) { 72 | return; 73 | } 74 | 75 | $session_string = ''; 76 | 77 | if (isset($_SERVER['HTTP_REFERER']) === true && empty($_SERVER['HTTP_REFERER']) === false) { 78 | $session_string .= $_SERVER['HTTP_REFERER']; 79 | } else { 80 | throw new Exception('No referer !'); 81 | } 82 | 83 | $this->session_id = md5($session_string); 84 | } 85 | } -------------------------------------------------------------------------------- /php/IFirecssSession.class.php: -------------------------------------------------------------------------------- 1 | . 17 | 18 | /** 19 | * Interface of the FireCSS's session 20 | * @author Simon Leblanc 21 | * @license http://www.gnu.org/licenses/ GNU GPL 22 | * @version 0.1 23 | * @package FireCSS 24 | */ 25 | interface IFirecssSession 26 | { 27 | public function getId(); 28 | } -------------------------------------------------------------------------------- /php/index.php: -------------------------------------------------------------------------------- 1 | . 17 | 18 | /** 19 | * Main program 20 | * PHP Implementation of the FireCSS RoR server (RoR implementation author : Julian Cox) 21 | * 22 | * @author Simon Leblanc 23 | * @version 0.1 24 | */ 25 | 26 | require_once dirname(__FILE__).DIRECTORY_SEPARATOR.'IFirecssSession.class.php'; 27 | require_once dirname(__FILE__).DIRECTORY_SEPARATOR.'FirecssSession.class.php'; 28 | require_once dirname(__FILE__).DIRECTORY_SEPARATOR.'FirecssCache.class.php'; 29 | require_once dirname(__FILE__).DIRECTORY_SEPARATOR.'FirecssController.class.php'; 30 | 31 | try { 32 | $session = new FirecssSession(); 33 | $controller = new FirecssController($session); 34 | 35 | $controller_name = $controller->getName(); 36 | $params = $controller->getRequestVar(); 37 | 38 | $controller->$controller_name($params)->run(); 39 | } catch (Exception $e) { 40 | file_put_contents(dirname(__FILE__).'/logs.txt', date('YmdHis')."\t".$e->getMessage()."\n", FILE_APPEND); 41 | } -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | The page you were looking for doesn't exist (404) 9 | 21 | 22 | 23 | 24 | 25 |
26 |

The page you were looking for doesn't exist.

27 |

You may have mistyped the address or the page may have moved.

28 |
29 | 30 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | The change you wanted was rejected (422) 9 | 21 | 22 | 23 | 24 | 25 |
26 |

The change you wanted was rejected.

27 |

Maybe you tried to change something you didn't have access to.

28 |
29 | 30 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | We're sorry, but something went wrong (500) 9 | 21 | 22 | 23 | 24 | 25 |
26 |

We're sorry, but something went wrong.

27 |

We've been notified about this issue and we'll take a look at it shortly.

28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /public/blank.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliancox/FireCSS/43d6be2f42b98f2db2d8124904ad27d4c22e59e8/public/blank.html -------------------------------------------------------------------------------- /public/csspanel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Firebug Side Panel 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 | 124 |
125 |
126 |
127 |  
128 |
129 |
130 | 131 | 132 | 133 | 135 | 137 | 139 | 140 | 141 | 145 | 146 | 147 | 151 | 159 | 163 | 164 | 165 | 169 | 177 | 191 | 192 | 193 |
142 |
143 | New watch expression...
144 |
194 |
195 | 196 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliancox/FireCSS/43d6be2f42b98f2db2d8124904ad27d4c22e59e8/public/favicon.ico -------------------------------------------------------------------------------- /public/file.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliancox/FireCSS/43d6be2f42b98f2db2d8124904ad27d4c22e59e8/public/file.pdf -------------------------------------------------------------------------------- /public/firecss@webspeed.co.nz/chrome.manifest: -------------------------------------------------------------------------------- 1 | content firecss chrome/content/firecss/ xpcnativewrappers=no 2 | overlay chrome://firebug/content/firebugOverlay.xul chrome://firecss/content/firecss.xul -------------------------------------------------------------------------------- /public/firecss@webspeed.co.nz/chrome/content/firecss/firecss.css: -------------------------------------------------------------------------------- 1 | #fbFireCSSSave, #fbFireCSSReset { 2 | margin: 0 1px 0 1px; 3 | padding: 0 1px 0 1px; 4 | margin-bottom: 1px; 5 | padding: 0px; 6 | opacity: 0.8; 7 | 8 | /* The default styling does not always keep button size content or image 9 | position constant, e.g. when [checked="true"]. These numbers were 10 | chosen to keep them constant against the default winstripe styling. */ 11 | padding-top: 4px; 12 | padding-bottom: 2px; 13 | -moz-padding-start: 4px; 14 | -moz-padding-end: 2px; 15 | 16 | } 17 | 18 | 19 | #fbFireCSSReset { 20 | list-style-image: url("chrome://firecss/content/images/reset.png"); 21 | } 22 | 23 | #fbFireCSSSave { 24 | list-style-image: url("chrome://firecss/content/images/save.png"); 25 | } 26 | 27 | #fbFireCSSSave > .toolbarbutton-text, #fbFireCSSReset > .toolbarbutton-text { 28 | display: none; 29 | } 30 | 31 | *{css: } 32 | -------------------------------------------------------------------------------- /public/firecss@webspeed.co.nz/chrome/content/firecss/firecss.js: -------------------------------------------------------------------------------- 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 . 16 | 17 | FBL.ns(function() { 18 | with (FBL) { 19 | 20 | const PATH = 'firecss'; 21 | const VERSION = 0.0 22 | 23 | var host = null; 24 | 25 | var isFireCSSPage = null; 26 | var FireCSSContext = null; 27 | var FireCSSQueue = []; 28 | var FireCSSTimeout = null; 29 | var ModCounter = 0; 30 | var pageLocation = null; 31 | var dirty = false; 32 | 33 | var scriptCode = function(url) { 34 | var result = "\ 35 | var script = document.createElement('script');\ 36 | script.src = '" + url + "';\ 37 | var head = document.getElementsByTagName('head')[0];\ 38 | head.appendChild(script);\ 39 | "; 40 | return result; 41 | } 42 | 43 | var loadScript = function(url) { 44 | var code = scriptCode(url); 45 | Firebug.CommandLine.evaluateInWebPage(code,FireCSSContext); 46 | } 47 | 48 | var fireCSSToServer = function() { 49 | if (FireCSSQueue.length > 0) { 50 | var args = ''; 51 | for (var i = 0; i < FireCSSQueue.length; i++) { 52 | args = args + '&selectors[]=' + escape(FireCSSQueue[i].selector); 53 | args = args + '&properties[]=' + escape(FireCSSQueue[i].property); 54 | args = args + '&values[]=' + escape(FireCSSQueue[i].value); 55 | args = args + '&sources[]=' + escape(FireCSSQueue[i].source); 56 | args = args + '&lines[]=' + escape(FireCSSQueue[i].line); 57 | args = args + '×tamps[]=' + escape(FireCSSQueue[i].timestamp); 58 | args = args + '&edits[]=' + escape(FireCSSQueue[i].edit); 59 | } 60 | FireCSSQueue = []; 61 | var url = host + '/' + PATH + '?v=' + VERSION + args; 62 | loadScript(url); 63 | dirty = true; 64 | } 65 | } 66 | 67 | var addCSSToQueue = function(selector, name, value, source, line) { 68 | clearTimeout(FireCSSTimeout); 69 | var cssObject = { 70 | selector: selector, 71 | property: name, 72 | value: value, 73 | source: source, 74 | line: line, 75 | timestamp: new Date().getTime(), 76 | edit: ModCounter 77 | } 78 | ModCounter = ModCounter + 1; 79 | // Firebug.Console.log('FireCSS! '+ selector + ' {'+name+': '+value+'} ' + source + ' (line: ' + line + ')'); 80 | FireCSSQueue.push(cssObject); 81 | FireCSSTimeout = setTimeout(fireCSSToServer,1000); 82 | } 83 | 84 | var getPath = function(node, path) { 85 | path = path || []; 86 | if(node.parentNode) { 87 | path = getPath(node.parentNode, path); 88 | } 89 | 90 | if(node.previousSibling) { 91 | var count = 1; 92 | var sibling = node.previousSibling 93 | do { 94 | if(sibling.nodeType == 1 && sibling.nodeName == node.nodeName) { 95 | count++; 96 | } 97 | sibling = sibling.previousSibling; 98 | } while(sibling); 99 | if(count == 1) { 100 | count = null; 101 | } 102 | } else if(node.nextSibling) { 103 | var sibling = node.nextSibling; 104 | do { 105 | if(sibling.nodeType == 1 && sibling.nodeName == node.nodeName) { 106 | var count = 1; 107 | sibling = null; 108 | } else { 109 | var count = null; 110 | sibling = sibling.previousSibling; 111 | } 112 | } while(sibling); 113 | } 114 | 115 | if(node.nodeType == 1) { 116 | path.push(node.nodeName.toLowerCase() + (node.id ? "#"+node.id : count > 0 ? ":nth-child("+count+")" : '')); 117 | } 118 | return path; 119 | }; 120 | 121 | var getAncestorByAttribute = function(element, name, value) { 122 | if (!element.parentNode || !element.parentNode.getAttribute) return null; 123 | var attr = element.parentNode.getAttribute(name) 124 | if (attr && (attr == value || (value.test && value.test(attr)))) return element.parentNode; 125 | return getAncestorByAttribute(element.parentNode,name,value); 126 | } 127 | 128 | var getAncestorByClassName = function(element, value) { 129 | return getAncestorByAttribute(element, 'class', new RegExp('(^|\\s)' + value + '($|\\s)')); 130 | } 131 | 132 | var ruleLineNo = function(element) { 133 | var lineNo = function(element) { 134 | var ruleid = element.getAttribute('ruleid').split('/'); 135 | if (ruleid[0] == 'new') { 136 | return element.getAttribute('fcslineno'); 137 | } else { 138 | return ruleid[ruleid.length - 1]; 139 | } 140 | } 141 | var prevLineNo = function(element) { 142 | if (!element.previousSibling) { 143 | return 0 144 | } else { 145 | var line = lineNo(element.previousSibling); 146 | if (line) { 147 | return line 148 | } else { 149 | return prevLineNo(element.previousSibling) 150 | } 151 | } 152 | } 153 | var nextLineNo = function(element) { 154 | if (!element.nextSibling) { 155 | return 9999999; // shouldn't be that number of lines in a single css file 156 | } else { 157 | var line = lineNo(element.nextSibling); 158 | if (line) { 159 | return line 160 | } else { 161 | return nextLineNo(element.nextSibling) 162 | } 163 | } 164 | } 165 | element = new RegExp('(^|\\s)cssRule($|\\s)').test(element.getAttribute('class')) ? element : getAncestorByClassName(element,'cssRule'); 166 | var line = lineNo(element); 167 | if (line) { 168 | return line 169 | } else { 170 | var prev = parseFloat(prevLineNo(element)); 171 | var postPrev = parseInt(prev) + 1 172 | var next = parseFloat(nextLineNo(element)); 173 | if (next > postPrev) { 174 | next = postPrev; 175 | } 176 | line = prev + ((next - prev)/2); 177 | element.setAttribute('fcslineno',line); 178 | return line; 179 | } 180 | } 181 | 182 | function CSSListener() 183 | { 184 | } 185 | 186 | CSSListener.prototype = 187 | { 188 | onBeginEdit: function(panel, editor, target, value) {}, 189 | onSaveEdit: function(panel, editor, target, value, oldValue) { 190 | try { 191 | if ((isFireCSSPage) && (target.className.indexOf('cssPropValue') >= 0)) { 192 | FireCSSContext.window.wrappedJSObject.lastEdit = target; // so we can explore the target and heirarchy in the firebug console 193 | // FireCSS is enabled for this page and a CSSValue has changed 194 | // Now extract the value and the other pertinant details 195 | var value = target.innerHTML; 196 | var rule = getAncestorByClassName(target,'cssRule'); 197 | var name = rule.getElementsByClassName('cssPropName')[0].innerHTML; 198 | var selector = rule.getElementsByClassName('cssSelector')[0].innerHTML; 199 | var ruleid = rule.getAttribute('ruleid').split('/'); 200 | var line = ruleLineNo(target) 201 | var source = pageLocation; 202 | if (target.ownerDocument.title == "Firebug Main Panel") { 203 | source = panel.location.href; 204 | } else if (target.ownerDocument.title == "Firebug Side Panel") { 205 | line = 0; 206 | var link = rule.parentNode.getElementsByClassName('objectLink')[0].repObject; 207 | if (link && link.href) { 208 | source = link.href; 209 | line = link.line || 0; 210 | } 211 | if (selector == 'element.style') { 212 | var htmlPanel = FireCSSContext.getPanel("html",true); 213 | var path = getPath(htmlPanel.selection).join(' > '); 214 | selector = path; 215 | } 216 | } 217 | if (selector) { 218 | addCSSToQueue(selector, name, value, source, line); 219 | } 220 | } 221 | } catch (e) { 222 | Firebug.Console.log('FireCSS EXCEPTION: '+e.message+'\n'+e.stack) 223 | } 224 | } 225 | } 226 | 227 | Firebug.FireCSS = extend(Firebug.Module, 228 | { 229 | initialize: function(owner) 230 | { 231 | Firebug.Module.initialize.apply(this, arguments); 232 | 233 | // Register NetMonitor listener 234 | this.cssListener = new CSSListener(); 235 | Firebug.Editor.addListener(this.cssListener); 236 | }, 237 | 238 | initContext: function(context, state) { 239 | FireCSSContext = context; 240 | var code = "window._FireCSSExtension = true"; 241 | Firebug.CommandLine.evaluateInWebPage(code,FireCSSContext); 242 | ModCounter = 0; 243 | pageLocation = context.window.wrappedJSObject.location.href.split('?')[0]; 244 | }, 245 | 246 | loadedContext: function(context) { 247 | isFireCSSPage = (context.window.wrappedJSObject._isFireCSSPage == true); 248 | if (isFireCSSPage) { 249 | // find where the firecss script is coming from so we can post back to that location 250 | var scripts = content.document.getElementsByTagName('script'); 251 | for (var i = 0; i < scripts.length; i++) { 252 | var src = scripts[i].getAttribute('src'); 253 | if ((src) && (src.indexOf('/firecss.js') > 0)) { 254 | var parts = src.split('://') 255 | host = parts[0] + '://' +parts[1].split('/')[0]; 256 | break; 257 | } 258 | } 259 | } 260 | }, 261 | 262 | shutdown: function() 263 | { 264 | Firebug.Module.shutdown.apply(this, arguments); 265 | 266 | // Unregister NetMonitor listener 267 | Firebug.Editor.removeListener(this.netListener); 268 | }, 269 | 270 | buttonFireCSSReset: function() { 271 | var doreset = ((!dirty) || (confirm('You have unsaved edits - are you sure you want to reset this page?'))); 272 | if (doreset) { 273 | args = "&edits[]=-1"; 274 | var url = host + '/' + PATH + '?v=' + VERSION + args; 275 | loadScript(url); 276 | } 277 | }, 278 | 279 | buttonFireCSSSave: function() { 280 | 281 | var form = content.document.getElementById('_FireCSSStyleForm'); 282 | if (form) { 283 | form.parentNode.removeChild(form); 284 | } 285 | form = content.document.createElement("form"); 286 | form.setAttribute("id", '_FireCSSStyleForm'); 287 | form.setAttribute("method", 'post'); 288 | form.setAttribute("target", '_blank'); 289 | form.setAttribute("action", host + '/firecss/download'); 290 | var stylesheets = content.document.styleSheets; 291 | 292 | var styleEls = content.document.getElementsByTagName('style'); 293 | var styleElCount = 0; 294 | 295 | for (var i = 0; i < stylesheets.length; i++) { 296 | var rules = stylesheets[i].cssRules; 297 | var css = '' 298 | for (var j = 0; j < rules.length; j++) { 299 | css += rules[j].cssText + "\n" 300 | } 301 | if (stylesheets[i].href) { 302 | // its an external stylesheet we're going to send the contents up 303 | var sheet = content.document.createElement("input"); 304 | sheet.setAttribute("type", 'hidden'); 305 | sheet.setAttribute("name", 'stylesheets[]'); 306 | sheet.setAttribute("value", stylesheets[i].href); 307 | form.appendChild(sheet); 308 | var details = content.document.createElement("input"); 309 | details.setAttribute("type", 'hidden'); 310 | details.setAttribute("name", 'rules[]'); 311 | details.setAttribute("value", css); 312 | form.appendChild(details); 313 | } else { 314 | // its an internal style tag - we're going to replace in the html and catch in the html content 315 | // first check to see if it’s the firebug stylesheet - in which case remove it. 316 | if ((rules.length > 0) && (rules[0].selectorText == '.firebugCanvas')) { 317 | styleEls[styleElCount].parentNode.removeChild(styleEls[styleElCount]); 318 | } else { 319 | styleEls[styleElCount].innerHTML = css; 320 | } 321 | styleElCount++; 322 | } 323 | } 324 | 325 | var html = content.document.createElement("input"); 326 | html.setAttribute("type", 'hidden'); 327 | html.setAttribute("name", 'html'); 328 | // test if document has doctype before use it 329 | if (content.document.doctype) { 330 | var docContent = '\n'; 335 | } else { 336 | // no doctype, very dirty code : set a default doctype 337 | // as you are a dirty girl or guy, the default doctype will be HTML 5 ! 338 | var docContent = '\n'; 339 | } 340 | docContent += ' 2 | 3 | 4 | 5 | 6 | 13 | 25 | 38 | 39 | 40 | 42 |
43 |
44 |
45 |
46 | 47 |
48 |
49 |
50 |
51 |
52 |
See CSS changes in all browsers as you edit
53 |
54 |

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.

63 | 64 |
65 |
66 | 67 |
68 | 69 | 70 | 71 |
72 | 73 | 74 | 75 |
76 | 77 |
78 |
79 |
80 | 87 |
88 |
89 |
90 |

FireCSS sponsors help make it free for you

91 |

Please support them

92 |
93 |
94 |
95 | 98 |
99 | Zerigo provides managed DNS hosting for firecss.com. 100 | They make DNS management a breeze. 101 |
102 | 103 |
104 |
105 |
106 |

Thanks

107 |
108 |
109 | 110 |
111 |
112 |

Why FireCSS?

113 |

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 |

Status

135 |

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 |

How it works

144 |

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 |

Where's the demo?

169 |

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 |

So who's done this?

184 |

185 | I'm Julian Cox.
186 | I run a small web consulting and development company called Webspeed Ltd, in Dunedin, New Zealand.

187 |

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 |

193 |

Thanks for reading.

194 |
195 | 196 |
197 | 198 | 199 | 200 |
201 | 202 | 203 | 204 |
205 |

Comments

206 |
207 |
208 | 209 | 242 |
243 | 244 |
245 | 260 | 261 | blog comments powered by Disqus 262 |
263 |
264 | 267 | 268 | -------------------------------------------------------------------------------- /public/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // Place your application-specific JavaScript functions and classes here 2 | // This file is automatically included by javascript_include_tag :defaults 3 | -------------------------------------------------------------------------------- /public/javascripts/firecss.js: -------------------------------------------------------------------------------- 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 . 16 | 17 | (function() { 18 | 19 | var PATH = '/firecss/polling'; 20 | window._isFireCSSPage = true; //enable the FireCSS firebug extension 21 | 22 | if (window._FireCSSExtension) { // (window.console && window.console.firebug) { 23 | // window._isFireCSSPage = true; //enable the FireCSS firebug extension 24 | console.log('firecss server'); 25 | } else { 26 | //this is one of the other clients of register for push updates and act on them 27 | 28 | var isArray = function(input) { 29 | return (Object.prototype.toString.apply(input) === '[object Array]'); 30 | } 31 | 32 | var insertAfter = function(newElement,targetElement) { 33 | var parent = targetElement.parentNode; 34 | if(parent.lastchild == targetElement) { 35 | parent.appendChild(newElement); 36 | } else { 37 | parent.insertBefore(newElement, targetElement.nextSibling); 38 | } 39 | } 40 | 41 | var mySrc = function() { 42 | var scripts = document.getElementsByTagName('script'); 43 | for (var i = 0; i < scripts.length; i++) { 44 | var src = scripts[i].getAttribute('src'); 45 | if ((src) && (src.indexOf('/firecss.js') > 0)) { 46 | return src; 47 | } 48 | } 49 | return null; 50 | } 51 | 52 | var insertionPoints = {}; 53 | var lastEdit = -1; 54 | 55 | //get all style tags so we can find out where to insert inline and document stuff 56 | //we do this before examining links because we're going to add style tags then 57 | var styles = document.getElementsByTagName('style'); 58 | var el = document.createElement('style'); 59 | if (styles.length > 0) { 60 | insertAfter(el, styles[styles.length - 1]); 61 | } else { 62 | document.getElementsByTagName('head')[0].appendChild(el); 63 | } 64 | insertionPoints[window.location.href] = el; 65 | 66 | var links = document.getElementsByTagName('link'); //find all links so we can work out where the stylesheets are 67 | for (var i = 0; i < links.length; i++) { 68 | var link = links[i]; 69 | if (link.getAttribute('rel') == 'stylesheet') { 70 | var href = link.getAttribute('href'); 71 | var tag = document.createElement('style'); 72 | insertAfter(tag, link) 73 | insertionPoints[href] = tag; 74 | } 75 | } 76 | 77 | processUpdate = function(data) { 78 | if (data) { 79 | if (isArray(data)) { // its an array of updates so process as such 80 | for (var i = 0; i < data.length; i++) { 81 | var css = data[i]; 82 | if (css.selector) { //server can send down null values just to update last edit 83 | var rule = css.selector+' {'+css.property+': '+css.value+'}\n'; 84 | console.log(css.source); 85 | try { 86 | insertionPoints[css.source].innerHTML = insertionPoints[css.source].innerHTML + rule; 87 | } catch(exc) { 88 | insertionPoints[css.source].styleSheet.addRule(css.selector, css.property+': '+css.value); 89 | } 90 | } else { 91 | // alert('Nil selector') 92 | } 93 | if (css.edit > lastEdit) { 94 | lastEdit = css.edit; 95 | } 96 | } 97 | } else if (data == 'reload') { 98 | window.location.reload(); 99 | } 100 | } 101 | } 102 | 103 | if (false) { 104 | // var pusherChannel = window.location.host + window.location.pathname.split('?')[0].replace('/','_'); 105 | //var pusher = new Pusher('03cb0652f55212acc073',pusherChannel); 106 | // if (pusher.connection) { 107 | // pusher.bind('FireCSS', processUpdate); 108 | } else { 109 | var location = mySrc(); 110 | var host = location.split('://')[1].split('/')[0]; 111 | var pollingUrl = 'http://' + host + PATH; 112 | var pollScript = document.createElement('script'); 113 | pollScript.src = pollingUrl 114 | document.getElementsByTagName('head')[0].appendChild(pollScript); 115 | poll = function() { 116 | pollScript.parentNode.removeChild(pollScript); 117 | pollScript = document.createElement('script'); 118 | pollScript.src = pollingUrl + '?callback=processUpdate&edit=' + lastEdit + '×tamp=' + new Date().getTime(); 119 | document.getElementsByTagName('head')[0].appendChild(pollScript); 120 | } 121 | setInterval(poll, 1000); 122 | } 123 | } 124 | 125 | })(); 126 | -------------------------------------------------------------------------------- /public/javascripts/test.js: -------------------------------------------------------------------------------- 1 | // alert("hello there"); -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /public/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | body { 2 | line-height: 22px; 3 | background-color: #003300; 4 | } -------------------------------------------------------------------------------- /public/stylesheets/other.css: -------------------------------------------------------------------------------- 1 | /* 2 | Document : other 3 | Created on : Feb 15, 2011, 9:45:31 PM 4 | Author : julian 5 | Description: 6 | Purpose of the stylesheet follows. 7 | */ 8 | 9 | /* 10 | TODO customize this sample style 11 | Syntax recommendation http://www.w3.org/TR/REC-CSS2/ 12 | */ 13 | 14 | a { 15 | text-decoration: none; 16 | } 17 | 18 | a:hover { 19 | text-decoration: underline; border: 1px solid red; 20 | } -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | font: 13px "Lucida Grande","Lucida Sans Unicode","Lucida Sans",Verdana,sans-serif; 3 | background-color: #EFEFEF; 4 | 5 | } 6 | 7 | body { 8 | } 9 | 10 | P { 11 | margin-bottom: 2em; 12 | color: #666666; 13 | line-height: 1.3em 14 | } 15 | 16 | #content { 17 | width: 830px; 18 | margin: 2em auto; 19 | border: 1px solid #333333; 20 | border-radius: 10px; -moz-border-radius: 10px; -webkit-border-radius: 10px; 21 | background-color: white; 22 | padding: 2em; 23 | } 24 | 25 | h2 { 26 | font-size: 1.3em; 27 | font-weight: bold; 28 | line-height: 1.8em; 29 | margin-top: 2em; 30 | } 31 | 32 | em { 33 | font-weight: bold; 34 | color: #333333; 35 | } 36 | 37 | .title { 38 | font-size: 2.5em; 39 | font-weight: bold; 40 | margin-bottom: 15px; 41 | border-bottom: 1px dotted #cccccc; 42 | padding-bottom: 10px; 43 | } 44 | 45 | .tagline { 46 | font-size: 0.85em; 47 | } 48 | 49 | #index .video { 50 | float: right; 51 | -moz-box-shadow: 6px 6px 8px #333; 52 | -webkit-box-shadow: 6px 6px 8px #333; 53 | box-shadow: 6px 6px 8px #333; 54 | /* For IE 8 */ 55 | -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=6, Direction=135, Color='#333333')"; 56 | /* For IE 5.5 - 7 */ 57 | filter: progid:DXImageTransform.Microsoft.Shadow(Strength=6, Direction=135, Color='#333333'); 58 | } 59 | 60 | #index .description { 61 | margin-top: 2em; 62 | width: 235px; 63 | text-align: justify; 64 | } 65 | 66 | #index .details { 67 | margin-top: 3em; 68 | width: 500px; 69 | text-align: justify; 70 | } 71 | 72 | .sign-up { 73 | text-align: center; 74 | background-color: #FFFFAA; 75 | border: 1px solid #CCCCCC; 76 | padding: 1.2em; 77 | padding-bottom: 0.6em; 78 | margin-left: -2em; 79 | margin-right: -2em; 80 | margin-top: 3em; 81 | font-weight: bold; 82 | font-size: 1.3em 83 | } 84 | 85 | .sign-up .text-input { 86 | font-size: 0.75em; 87 | font-weight: normal; 88 | } 89 | 90 | 91 | .clear { 92 | clear: both; 93 | } 94 | 95 | .no-spam { 96 | font-weight: normal; 97 | font-size: 0.7em; 98 | margin-top: 5px; 99 | color: #666666; 100 | } 101 | 102 | #disqus_thread { 103 | width: 500px; 104 | } 105 | 106 | #dsq-content .dsq-comment { 107 | clear: none !important; 108 | } 109 | 110 | #contributors { 111 | float: right; 112 | font-family: "lucida grande",lucida,tahoma,helvetica,arial,sans-serif; 113 | font-size: 12px; 114 | margin: 3em; 115 | margin-right: 0; 116 | } 117 | 118 | .contributors { 119 | background-color: #8EC1DA; 120 | border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px; 121 | text-align: center; 122 | } 123 | 124 | .contributors h4 { 125 | font-size: 16px; 126 | line-height: 1.2; 127 | } 128 | 129 | .contributors h3 { 130 | font-size: 11px; 131 | line-height: 1.2; 132 | } 133 | 134 | .contributors-hd { 135 | padding: 10px; 136 | text-align: left; 137 | color: white; 138 | } 139 | 140 | .contributors-bd { 141 | background-color: #ffffff; 142 | border-radius: 6px; -moz-border-radius: 6px; -webkit-border-radius: 6px; 143 | border-left: 1px solid #8EC1DA; 144 | border-right: 1px solid #8EC1DA; 145 | padding: 15px; 146 | } 147 | 148 | .contributors-ft { 149 | padding: 10px; 150 | color: white; 151 | } 152 | 153 | .contributor-notes { 154 | text-align: left; 155 | margin-top: 5px; 156 | } -------------------------------------------------------------------------------- /public/test.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | FireCSS 7 | 8 | 9 | 10 | 11 | 17 | 18 | 37 | 38 | 39 | 40 | 41 | 42 |
43 | 46 |
47 |

48 | See CSS changes in all browsers as you edit 49 |

50 |
51 |
52 |
53 |

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 |
60 |
61 | 62 |
63 |
64 | PDF
65 | Test 66 |
67 |
68 | 69 | 70 | -------------------------------------------------------------------------------- /public/test2.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | FireCSS 7 | 8 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 36 |
37 |

38 | See CSS changes in all browsers as you edit 39 |

40 |
41 |
42 |
43 |

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 |
50 |
51 | 52 |
53 |
54 | PDF
55 | Test 56 |
57 |
58 | 59 | 60 | -------------------------------------------------------------------------------- /public/test_1.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | FireCSS 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /script/about: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | $LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info" 4 | require 'commands/about' 5 | -------------------------------------------------------------------------------- /script/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/console' 4 | -------------------------------------------------------------------------------- /script/dbconsole: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/dbconsole' 4 | -------------------------------------------------------------------------------- /script/destroy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/destroy' 4 | -------------------------------------------------------------------------------- /script/generate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/generate' 4 | -------------------------------------------------------------------------------- /script/performance/benchmarker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../../config/boot', __FILE__) 3 | require 'commands/performance/benchmarker' 4 | -------------------------------------------------------------------------------- /script/performance/profiler: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../../config/boot', __FILE__) 3 | require 'commands/performance/profiler' 4 | -------------------------------------------------------------------------------- /script/plugin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/plugin' 4 | -------------------------------------------------------------------------------- /script/runner: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/runner' 4 | -------------------------------------------------------------------------------- /script/server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/server' 4 | -------------------------------------------------------------------------------- /test/fixtures/fanboys.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html 2 | 3 | one: 4 | email: MyString 5 | 6 | two: 7 | email: MyString 8 | -------------------------------------------------------------------------------- /test/functional/firecss_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class FirecssControllerTest < ActionController::TestCase 4 | # Replace this with your real tests. 5 | test "the truth" do 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/performance/browsing_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'performance_test_help' 3 | 4 | # Profiling results for each test method are written to tmp/performance. 5 | class BrowsingTest < ActionController::PerformanceTest 6 | def test_homepage 7 | get '/' 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = "test" 2 | require File.expand_path(File.dirname(__FILE__) + "/../config/environment") 3 | require 'test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Transactional fixtures accelerate your tests by wrapping each test method 7 | # in a transaction that's rolled back on completion. This ensures that the 8 | # test database remains unchanged so your fixtures don't have to be reloaded 9 | # between every test method. Fewer database queries means faster tests. 10 | # 11 | # Read Mike Clark's excellent walkthrough at 12 | # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting 13 | # 14 | # Every Active Record database supports transactions except MyISAM tables 15 | # in MySQL. Turn off transactional fixtures in this case; however, if you 16 | # don't care one way or the other, switching from MyISAM to InnoDB tables 17 | # is recommended. 18 | # 19 | # The only drawback to using transactional fixtures is when you actually 20 | # need to test transactions. Since your test is bracketed by a transaction, 21 | # any transactions started in your code will be automatically rolled back. 22 | self.use_transactional_fixtures = true 23 | 24 | # Instantiated fixtures are slow, but give you @david where otherwise you 25 | # would need people(:david). If you don't want to migrate your existing 26 | # test cases which use the @david style and don't mind the speed hit (each 27 | # instantiated fixtures translates to a database query per test method), 28 | # then set this back to true. 29 | self.use_instantiated_fixtures = false 30 | 31 | # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. 32 | # 33 | # Note: You'll currently still have to declare fixtures explicitly in integration tests 34 | # -- they do not yet inherit this setting 35 | fixtures :all 36 | 37 | # Add more helper methods to be used by all tests here... 38 | end 39 | -------------------------------------------------------------------------------- /test/unit/fanboy_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class FanboyTest < ActiveSupport::TestCase 4 | # Replace this with your real tests. 5 | test "the truth" do 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/unit/helpers/firecss_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class FirecssHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /vendor/plugins/rubyzip/MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 [name of plugin creator] 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /vendor/plugins/rubyzip/README: -------------------------------------------------------------------------------- 1 | Rubyzip 2 | ======= 3 | 4 | Introduction goes here. 5 | 6 | 7 | Example 8 | ======= 9 | 10 | Example goes here. 11 | 12 | 13 | Copyright (c) 2009 [name of plugin creator], released under the MIT license 14 | -------------------------------------------------------------------------------- /vendor/plugins/rubyzip/Rakefile: -------------------------------------------------------------------------------- 1 | # Rakefile for RubyGems -*- ruby -*- 2 | 3 | require 'rubygems' 4 | require 'rake/clean' 5 | require 'rake/testtask' 6 | require 'rake/packagetask' 7 | require 'rake/gempackagetask' 8 | require 'rake/rdoctask' 9 | require 'rake/contrib/sshpublisher' 10 | require 'net/ftp' 11 | 12 | PKG_NAME = 'rubyzip' 13 | PKG_VERSION = File.read('lib/zip/zip.rb').match(/\s+VERSION\s*=\s*'(.*)'/)[1] 14 | 15 | PKG_FILES = FileList.new 16 | 17 | PKG_FILES.add %w{ README NEWS TODO ChangeLog install.rb Rakefile } 18 | PKG_FILES.add %w{ samples/*.rb } 19 | PKG_FILES.add %w{ test/*.rb } 20 | PKG_FILES.add %w{ test/data/* } 21 | PKG_FILES.exclude "test/data/generated" 22 | PKG_FILES.add %w{ lib/**/*.rb } 23 | 24 | def clobberFromCvsIgnore(path) 25 | CLOBBER.add File.readlines(path+'/.cvsignore').map { 26 | |f| File.join(path, f.chomp) 27 | } rescue StandardError 28 | end 29 | 30 | clobberFromCvsIgnore '.' 31 | clobberFromCvsIgnore 'samples' 32 | clobberFromCvsIgnore 'test' 33 | clobberFromCvsIgnore 'test/data' 34 | 35 | task :default => [:test] 36 | 37 | desc "Run unit tests" 38 | task :test do 39 | ruby %{-C test alltests.rb} 40 | end 41 | 42 | # Shortcuts for test targets 43 | task :ut => [:test] 44 | 45 | spec = Gem::Specification.new do |s| 46 | s.name = PKG_NAME 47 | s.version = PKG_VERSION 48 | s.author = "Thomas Sondergaard" 49 | s.email = "thomas(at)sondergaard.cc" 50 | s.homepage = "http://rubyzip.sourceforge.net/" 51 | s.platform = Gem::Platform::RUBY 52 | s.summary = "rubyzip is a ruby module for reading and writing zip files" 53 | s.files = PKG_FILES.to_a 54 | s.require_path = 'lib' 55 | end 56 | 57 | Rake::GemPackageTask.new(spec) do |pkg| 58 | pkg.need_zip = true 59 | pkg.need_tar = true 60 | end 61 | 62 | Rake::RDocTask.new do |rd| 63 | rd.main = "README" 64 | rd.rdoc_files.add %W{ lib/zip/*.rb README NEWS TODO ChangeLog } 65 | rd.options << "--title 'rubyzip documentation' --webcvs http://cvs.sourceforge.net/viewcvs.py/rubyzip/rubyzip/" 66 | # rd.options << "--all" 67 | end 68 | 69 | desc "Publish documentation" 70 | task :pdoc => [:rdoc] do 71 | Rake::SshFreshDirPublisher. 72 | new("thomas@rubyzip.sourceforge.net", "/home/groups/r/ru/rubyzip/htdocs", "html").upload 73 | end 74 | 75 | desc "Publish package" 76 | task :ppackage => [:package] do 77 | Net::FTP.open("upload.sourceforge.net", 78 | "ftp", 79 | ENV['USER']+"@"+ENV['HOSTNAME']) { 80 | |ftpclient| 81 | ftpclient.passive = true 82 | ftpclient.chdir "incoming" 83 | Dir['pkg/*.{tgz,zip,gem}'].each { 84 | |e| 85 | ftpclient.putbinaryfile(e, File.basename(e)) 86 | } 87 | } 88 | end 89 | 90 | desc "Generate the ChangeLog file" 91 | task :ChangeLog do 92 | puts "Updating ChangeLog" 93 | system %{cvs2cl} 94 | end 95 | 96 | desc "Make a release" 97 | task :release => [:tag_release, :pdoc, :ppackage] do 98 | end 99 | 100 | desc "Make a release tag" 101 | task :tag_release do 102 | tag = "release-#{PKG_VERSION.gsub('.','-')}" 103 | 104 | puts "Checking for tag '#{tag}'" 105 | if (Regexp.new("^\\s+#{tag}") =~ `cvs log README`) 106 | abort "Tag '#{tag}' already exists" 107 | end 108 | puts "Tagging module with '#{tag}'" 109 | system("cvs tag #{tag}") 110 | end 111 | -------------------------------------------------------------------------------- /vendor/plugins/rubyzip/init.rb: -------------------------------------------------------------------------------- 1 | # Include hook code here 2 | -------------------------------------------------------------------------------- /vendor/plugins/rubyzip/install.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $VERBOSE = true 4 | 5 | require 'rbconfig' 6 | require 'find' 7 | require 'ftools' 8 | 9 | include Config 10 | 11 | files = %w{ stdrubyext.rb ioextras.rb zip.rb zipfilesystem.rb ziprequire.rb tempfile_bugfixed.rb } 12 | 13 | INSTALL_DIR = File.join(CONFIG["sitelibdir"], "zip") 14 | File.makedirs(INSTALL_DIR) 15 | 16 | SOURCE_DIR = File.join(File.dirname($0), "lib/zip") 17 | 18 | files.each { 19 | |filename| 20 | installPath = File.join(INSTALL_DIR, filename) 21 | File::install(File.join(SOURCE_DIR, filename), installPath, 0644, true) 22 | } 23 | -------------------------------------------------------------------------------- /vendor/plugins/rubyzip/lib/ioextras.rb: -------------------------------------------------------------------------------- 1 | module IOExtras #:nodoc: 2 | 3 | CHUNK_SIZE = 32768 4 | 5 | RANGE_ALL = 0..-1 6 | 7 | def self.copy_stream(ostream, istream) 8 | s = '' 9 | ostream.write(istream.read(CHUNK_SIZE, s)) until istream.eof? 10 | end 11 | 12 | 13 | # Implements kind_of? in order to pretend to be an IO object 14 | module FakeIO 15 | def kind_of?(object) 16 | object == IO || super 17 | end 18 | end 19 | 20 | # Implements many of the convenience methods of IO 21 | # such as gets, getc, readline and readlines 22 | # depends on: input_finished?, produce_input and read 23 | module AbstractInputStream 24 | include Enumerable 25 | include FakeIO 26 | 27 | def initialize 28 | super 29 | @lineno = 0 30 | @outputBuffer = "" 31 | end 32 | 33 | attr_accessor :lineno 34 | 35 | def read(numberOfBytes = nil, buf = nil) 36 | tbuf = nil 37 | 38 | if @outputBuffer.length > 0 39 | if numberOfBytes <= @outputBuffer.length 40 | tbuf = @outputBuffer.slice!(0, numberOfBytes) 41 | else 42 | numberOfBytes -= @outputBuffer.length if (numberOfBytes) 43 | rbuf = sysread(numberOfBytes, buf) 44 | tbuf = @outputBuffer 45 | tbuf << rbuf if (rbuf) 46 | @outputBuffer = "" 47 | end 48 | else 49 | tbuf = sysread(numberOfBytes, buf) 50 | end 51 | 52 | return nil unless (tbuf) 53 | 54 | if buf 55 | buf.replace(tbuf) 56 | else 57 | buf = tbuf 58 | end 59 | 60 | buf 61 | end 62 | 63 | def readlines(aSepString = $/) 64 | retVal = [] 65 | each_line(aSepString) { |line| retVal << line } 66 | return retVal 67 | end 68 | 69 | def gets(aSepString=$/) 70 | @lineno = @lineno.next 71 | return read if aSepString == nil 72 | aSepString="#{$/}#{$/}" if aSepString == "" 73 | 74 | bufferIndex=0 75 | while ((matchIndex = @outputBuffer.index(aSepString, bufferIndex)) == nil) 76 | bufferIndex=@outputBuffer.length 77 | if input_finished? 78 | return @outputBuffer.empty? ? nil : flush 79 | end 80 | @outputBuffer << produce_input 81 | end 82 | sepIndex=matchIndex + aSepString.length 83 | return @outputBuffer.slice!(0...sepIndex) 84 | end 85 | 86 | def flush 87 | retVal=@outputBuffer 88 | @outputBuffer="" 89 | return retVal 90 | end 91 | 92 | def readline(aSepString = $/) 93 | retVal = gets(aSepString) 94 | raise EOFError if retVal == nil 95 | return retVal 96 | end 97 | 98 | def each_line(aSepString = $/) 99 | while true 100 | yield readline(aSepString) 101 | end 102 | rescue EOFError 103 | end 104 | 105 | alias_method :each, :each_line 106 | end 107 | 108 | 109 | # Implements many of the output convenience methods of IO. 110 | # relies on << 111 | module AbstractOutputStream 112 | include FakeIO 113 | 114 | def write(data) 115 | self << data 116 | data.to_s.length 117 | end 118 | 119 | 120 | def print(*params) 121 | self << params.to_s << $\.to_s 122 | end 123 | 124 | def printf(aFormatString, *params) 125 | self << sprintf(aFormatString, *params) 126 | end 127 | 128 | def putc(anObject) 129 | self << case anObject 130 | when Fixnum then anObject.chr 131 | when String then anObject 132 | else raise TypeError, "putc: Only Fixnum and String supported" 133 | end 134 | anObject 135 | end 136 | 137 | def puts(*params) 138 | params << "\n" if params.empty? 139 | params.flatten.each { 140 | |element| 141 | val = element.to_s 142 | self << val 143 | self << "\n" unless val[-1,1] == "\n" 144 | } 145 | end 146 | 147 | end 148 | 149 | end # IOExtras namespace module 150 | 151 | 152 | 153 | # Copyright (C) 2002-2004 Thomas Sondergaard 154 | # rubyzip is free software; you can redistribute it and/or 155 | # modify it under the terms of the ruby license. 156 | -------------------------------------------------------------------------------- /vendor/plugins/rubyzip/lib/stdrubyext.rb: -------------------------------------------------------------------------------- 1 | unless Enumerable.method_defined?(:inject) 2 | module Enumerable #:nodoc:all 3 | def inject(n = 0) 4 | each { |value| n = yield(n, value) } 5 | n 6 | end 7 | end 8 | end 9 | 10 | module Enumerable #:nodoc:all 11 | # returns a new array of all the return values not equal to nil 12 | # This implementation could be faster 13 | def select_map(&aProc) 14 | map(&aProc).reject { |e| e.nil? } 15 | end 16 | end 17 | 18 | unless Object.method_defined?(:object_id) 19 | class Object #:nodoc:all 20 | # Using object_id which is the new thing, so we need 21 | # to make that work in versions prior to 1.8.0 22 | alias object_id id 23 | end 24 | end 25 | 26 | unless File.respond_to?(:read) 27 | class File # :nodoc:all 28 | # singleton method read does not exist in 1.6.x 29 | def self.read(fileName) 30 | open(fileName) { |f| f.read } 31 | end 32 | end 33 | end 34 | 35 | class String #:nodoc:all 36 | def starts_with(aString) 37 | rindex(aString, 0) == 0 38 | end 39 | 40 | def ends_with(aString) 41 | index(aString, -aString.size) 42 | end 43 | 44 | def ensure_end(aString) 45 | ends_with(aString) ? self : self + aString 46 | end 47 | 48 | def lchop 49 | slice(1, length) 50 | end 51 | end 52 | 53 | class Time #:nodoc:all 54 | 55 | #MS-DOS File Date and Time format as used in Interrupt 21H Function 57H: 56 | # 57 | # Register CX, the Time: 58 | # Bits 0-4 2 second increments (0-29) 59 | # Bits 5-10 minutes (0-59) 60 | # bits 11-15 hours (0-24) 61 | # 62 | # Register DX, the Date: 63 | # Bits 0-4 day (1-31) 64 | # bits 5-8 month (1-12) 65 | # bits 9-15 year (four digit year minus 1980) 66 | 67 | 68 | def to_binary_dos_time 69 | (sec/2) + 70 | (min << 5) + 71 | (hour << 11) 72 | end 73 | 74 | def to_binary_dos_date 75 | (day) + 76 | (month << 5) + 77 | ((year - 1980) << 9) 78 | end 79 | 80 | # Dos time is only stored with two seconds accuracy 81 | def dos_equals(other) 82 | to_i/2 == other.to_i/2 83 | end 84 | 85 | def self.parse_binary_dos_format(binaryDosDate, binaryDosTime) 86 | second = 2 * ( 0b11111 & binaryDosTime) 87 | minute = ( 0b11111100000 & binaryDosTime) >> 5 88 | hour = (0b1111100000000000 & binaryDosTime) >> 11 89 | day = ( 0b11111 & binaryDosDate) 90 | month = ( 0b111100000 & binaryDosDate) >> 5 91 | year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980 92 | begin 93 | return Time.local(year, month, day, hour, minute, second) 94 | end 95 | end 96 | end 97 | 98 | class Module #:nodoc:all 99 | def forward_message(forwarder, *messagesToForward) 100 | methodDefs = messagesToForward.map { 101 | |msg| 102 | "def #{msg}; #{forwarder}(:#{msg}); end" 103 | } 104 | module_eval(methodDefs.join("\n")) 105 | end 106 | end 107 | 108 | 109 | # Copyright (C) 2002, 2003 Thomas Sondergaard 110 | # rubyzip is free software; you can redistribute it and/or 111 | # modify it under the terms of the ruby license. 112 | -------------------------------------------------------------------------------- /vendor/plugins/rubyzip/lib/tempfile_bugfixed.rb: -------------------------------------------------------------------------------- 1 | # 2 | # tempfile - manipulates temporary files 3 | # 4 | # $Id: tempfile_bugfixed.rb,v 1.2 2005/02/19 20:30:33 thomas Exp $ 5 | # 6 | 7 | require 'delegate' 8 | require 'tmpdir' 9 | 10 | module BugFix #:nodoc:all 11 | 12 | # A class for managing temporary files. This library is written to be 13 | # thread safe. 14 | class Tempfile < DelegateClass(File) 15 | MAX_TRY = 10 16 | @@cleanlist = [] 17 | 18 | # Creates a temporary file of mode 0600 in the temporary directory 19 | # whose name is basename.pid.n and opens with mode "w+". A Tempfile 20 | # object works just like a File object. 21 | # 22 | # If tmpdir is omitted, the temporary directory is determined by 23 | # Dir::tmpdir provided by 'tmpdir.rb'. 24 | # When $SAFE > 0 and the given tmpdir is tainted, it uses 25 | # /tmp. (Note that ENV values are tainted by default) 26 | def initialize(basename, tmpdir=Dir::tmpdir) 27 | if $SAFE > 0 and tmpdir.tainted? 28 | tmpdir = '/tmp' 29 | end 30 | 31 | lock = nil 32 | n = failure = 0 33 | 34 | begin 35 | Thread.critical = true 36 | 37 | begin 38 | tmpname = sprintf('%s/%s%d.%d', tmpdir, basename, $$, n) 39 | lock = tmpname + '.lock' 40 | n += 1 41 | end while @@cleanlist.include?(tmpname) or 42 | File.exist?(lock) or File.exist?(tmpname) 43 | 44 | Dir.mkdir(lock) 45 | rescue 46 | failure += 1 47 | retry if failure < MAX_TRY 48 | raise "cannot generate tempfile `%s'" % tmpname 49 | ensure 50 | Thread.critical = false 51 | end 52 | 53 | @data = [tmpname] 54 | @clean_proc = Tempfile.callback(@data) 55 | ObjectSpace.define_finalizer(self, @clean_proc) 56 | 57 | @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600) 58 | @tmpname = tmpname 59 | @@cleanlist << @tmpname 60 | @data[1] = @tmpfile 61 | @data[2] = @@cleanlist 62 | 63 | super(@tmpfile) 64 | 65 | # Now we have all the File/IO methods defined, you must not 66 | # carelessly put bare puts(), etc. after this. 67 | 68 | Dir.rmdir(lock) 69 | end 70 | 71 | # Opens or reopens the file with mode "r+". 72 | def open 73 | @tmpfile.close if @tmpfile 74 | @tmpfile = File.open(@tmpname, 'r+') 75 | @data[1] = @tmpfile 76 | __setobj__(@tmpfile) 77 | end 78 | 79 | def _close # :nodoc: 80 | @tmpfile.close if @tmpfile 81 | @data[1] = @tmpfile = nil 82 | end 83 | protected :_close 84 | 85 | # Closes the file. If the optional flag is true, unlinks the file 86 | # after closing. 87 | # 88 | # If you don't explicitly unlink the temporary file, the removal 89 | # will be delayed until the object is finalized. 90 | def close(unlink_now=false) 91 | if unlink_now 92 | close! 93 | else 94 | _close 95 | end 96 | end 97 | 98 | # Closes and unlinks the file. 99 | def close! 100 | _close 101 | @clean_proc.call 102 | ObjectSpace.undefine_finalizer(self) 103 | end 104 | 105 | # Unlinks the file. On UNIX-like systems, it is often a good idea 106 | # to unlink a temporary file immediately after creating and opening 107 | # it, because it leaves other programs zero chance to access the 108 | # file. 109 | def unlink 110 | # keep this order for thread safeness 111 | File.unlink(@tmpname) if File.exist?(@tmpname) 112 | @@cleanlist.delete(@tmpname) if @@cleanlist 113 | end 114 | alias delete unlink 115 | 116 | if RUBY_VERSION > '1.8.0' 117 | def __setobj__(obj) 118 | @_dc_obj = obj 119 | end 120 | else 121 | def __setobj__(obj) 122 | @obj = obj 123 | end 124 | end 125 | 126 | # Returns the full path name of the temporary file. 127 | def path 128 | @tmpname 129 | end 130 | 131 | # Returns the size of the temporary file. As a side effect, the IO 132 | # buffer is flushed before determining the size. 133 | def size 134 | if @tmpfile 135 | @tmpfile.flush 136 | @tmpfile.stat.size 137 | else 138 | 0 139 | end 140 | end 141 | alias length size 142 | 143 | class << self 144 | def callback(data) # :nodoc: 145 | pid = $$ 146 | lambda{ 147 | if pid == $$ 148 | path, tmpfile, cleanlist = *data 149 | 150 | print "removing ", path, "..." if $DEBUG 151 | 152 | tmpfile.close if tmpfile 153 | 154 | # keep this order for thread safeness 155 | File.unlink(path) if File.exist?(path) 156 | cleanlist.delete(path) if cleanlist 157 | 158 | print "done\n" if $DEBUG 159 | end 160 | } 161 | end 162 | 163 | # If no block is given, this is a synonym for new(). 164 | # 165 | # If a block is given, it will be passed tempfile as an argument, 166 | # and the tempfile will automatically be closed when the block 167 | # terminates. In this case, open() returns nil. 168 | def open(*args) 169 | tempfile = new(*args) 170 | 171 | if block_given? 172 | begin 173 | yield(tempfile) 174 | ensure 175 | tempfile.close 176 | end 177 | 178 | nil 179 | else 180 | tempfile 181 | end 182 | end 183 | end 184 | end 185 | 186 | end # module BugFix 187 | if __FILE__ == $0 188 | # $DEBUG = true 189 | f = Tempfile.new("foo") 190 | f.print("foo\n") 191 | f.close 192 | f.open 193 | p f.gets # => "foo\n" 194 | f.close! 195 | end 196 | -------------------------------------------------------------------------------- /vendor/plugins/rubyzip/lib/zip/ioextras.rb: -------------------------------------------------------------------------------- 1 | module IOExtras #:nodoc: 2 | 3 | CHUNK_SIZE = 32768 4 | 5 | RANGE_ALL = 0..-1 6 | 7 | def self.copy_stream(ostream, istream) 8 | s = '' 9 | ostream.write(istream.read(CHUNK_SIZE, s)) until istream.eof? 10 | end 11 | 12 | 13 | # Implements kind_of? in order to pretend to be an IO object 14 | module FakeIO 15 | def kind_of?(object) 16 | object == IO || super 17 | end 18 | end 19 | 20 | # Implements many of the convenience methods of IO 21 | # such as gets, getc, readline and readlines 22 | # depends on: input_finished?, produce_input and read 23 | module AbstractInputStream 24 | include Enumerable 25 | include FakeIO 26 | 27 | def initialize 28 | super 29 | @lineno = 0 30 | @outputBuffer = "" 31 | end 32 | 33 | attr_accessor :lineno 34 | 35 | def read(numberOfBytes = nil, buf = nil) 36 | tbuf = nil 37 | 38 | if @outputBuffer.length > 0 39 | if numberOfBytes <= @outputBuffer.length 40 | tbuf = @outputBuffer.slice!(0, numberOfBytes) 41 | else 42 | numberOfBytes -= @outputBuffer.length if (numberOfBytes) 43 | rbuf = sysread(numberOfBytes, buf) 44 | tbuf = @outputBuffer 45 | tbuf << rbuf if (rbuf) 46 | @outputBuffer = "" 47 | end 48 | else 49 | tbuf = sysread(numberOfBytes, buf) 50 | end 51 | 52 | return nil unless (tbuf) 53 | 54 | if buf 55 | buf.replace(tbuf) 56 | else 57 | buf = tbuf 58 | end 59 | 60 | buf 61 | end 62 | 63 | def readlines(aSepString = $/) 64 | retVal = [] 65 | each_line(aSepString) { |line| retVal << line } 66 | return retVal 67 | end 68 | 69 | def gets(aSepString=$/) 70 | @lineno = @lineno.next 71 | return read if aSepString == nil 72 | aSepString="#{$/}#{$/}" if aSepString == "" 73 | 74 | bufferIndex=0 75 | while ((matchIndex = @outputBuffer.index(aSepString, bufferIndex)) == nil) 76 | bufferIndex=@outputBuffer.length 77 | if input_finished? 78 | return @outputBuffer.empty? ? nil : flush 79 | end 80 | @outputBuffer << produce_input 81 | end 82 | sepIndex=matchIndex + aSepString.length 83 | return @outputBuffer.slice!(0...sepIndex) 84 | end 85 | 86 | def flush 87 | retVal=@outputBuffer 88 | @outputBuffer="" 89 | return retVal 90 | end 91 | 92 | def readline(aSepString = $/) 93 | retVal = gets(aSepString) 94 | raise EOFError if retVal == nil 95 | return retVal 96 | end 97 | 98 | def each_line(aSepString = $/) 99 | while true 100 | yield readline(aSepString) 101 | end 102 | rescue EOFError 103 | end 104 | 105 | alias_method :each, :each_line 106 | end 107 | 108 | 109 | # Implements many of the output convenience methods of IO. 110 | # relies on << 111 | module AbstractOutputStream 112 | include FakeIO 113 | 114 | def write(data) 115 | self << data 116 | data.to_s.length 117 | end 118 | 119 | 120 | def print(*params) 121 | self << params.to_s << $\.to_s 122 | end 123 | 124 | def printf(aFormatString, *params) 125 | self << sprintf(aFormatString, *params) 126 | end 127 | 128 | def putc(anObject) 129 | self << case anObject 130 | when Fixnum then anObject.chr 131 | when String then anObject 132 | else raise TypeError, "putc: Only Fixnum and String supported" 133 | end 134 | anObject 135 | end 136 | 137 | def puts(*params) 138 | params << "\n" if params.empty? 139 | params.flatten.each { 140 | |element| 141 | val = element.to_s 142 | self << val 143 | self << "\n" unless val[-1,1] == "\n" 144 | } 145 | end 146 | 147 | end 148 | 149 | end # IOExtras namespace module 150 | 151 | 152 | 153 | # Copyright (C) 2002-2004 Thomas Sondergaard 154 | # rubyzip is free software; you can redistribute it and/or 155 | # modify it under the terms of the ruby license. 156 | -------------------------------------------------------------------------------- /vendor/plugins/rubyzip/lib/zip/stdrubyext.rb: -------------------------------------------------------------------------------- 1 | unless Enumerable.method_defined?(:inject) 2 | module Enumerable #:nodoc:all 3 | def inject(n = 0) 4 | each { |value| n = yield(n, value) } 5 | n 6 | end 7 | end 8 | end 9 | 10 | module Enumerable #:nodoc:all 11 | # returns a new array of all the return values not equal to nil 12 | # This implementation could be faster 13 | def select_map(&aProc) 14 | map(&aProc).reject { |e| e.nil? } 15 | end 16 | end 17 | 18 | unless Object.method_defined?(:object_id) 19 | class Object #:nodoc:all 20 | # Using object_id which is the new thing, so we need 21 | # to make that work in versions prior to 1.8.0 22 | alias object_id id 23 | end 24 | end 25 | 26 | unless File.respond_to?(:read) 27 | class File # :nodoc:all 28 | # singleton method read does not exist in 1.6.x 29 | def self.read(fileName) 30 | open(fileName) { |f| f.read } 31 | end 32 | end 33 | end 34 | 35 | class String #:nodoc:all 36 | def starts_with(aString) 37 | rindex(aString, 0) == 0 38 | end 39 | 40 | def ends_with(aString) 41 | index(aString, -aString.size) 42 | end 43 | 44 | def ensure_end(aString) 45 | ends_with(aString) ? self : self + aString 46 | end 47 | 48 | def lchop 49 | slice(1, length) 50 | end 51 | end 52 | 53 | class Time #:nodoc:all 54 | 55 | #MS-DOS File Date and Time format as used in Interrupt 21H Function 57H: 56 | # 57 | # Register CX, the Time: 58 | # Bits 0-4 2 second increments (0-29) 59 | # Bits 5-10 minutes (0-59) 60 | # bits 11-15 hours (0-24) 61 | # 62 | # Register DX, the Date: 63 | # Bits 0-4 day (1-31) 64 | # bits 5-8 month (1-12) 65 | # bits 9-15 year (four digit year minus 1980) 66 | 67 | 68 | def to_binary_dos_time 69 | (sec/2) + 70 | (min << 5) + 71 | (hour << 11) 72 | end 73 | 74 | def to_binary_dos_date 75 | (day) + 76 | (month << 5) + 77 | ((year - 1980) << 9) 78 | end 79 | 80 | # Dos time is only stored with two seconds accuracy 81 | def dos_equals(other) 82 | to_i/2 == other.to_i/2 83 | end 84 | 85 | def self.parse_binary_dos_format(binaryDosDate, binaryDosTime) 86 | second = 2 * ( 0b11111 & binaryDosTime) 87 | minute = ( 0b11111100000 & binaryDosTime) >> 5 88 | hour = (0b1111100000000000 & binaryDosTime) >> 11 89 | day = ( 0b11111 & binaryDosDate) 90 | month = ( 0b111100000 & binaryDosDate) >> 5 91 | year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980 92 | begin 93 | return Time.local(year, month, day, hour, minute, second) 94 | end 95 | end 96 | end 97 | 98 | class Module #:nodoc:all 99 | def forward_message(forwarder, *messagesToForward) 100 | methodDefs = messagesToForward.map { 101 | |msg| 102 | "def #{msg}; #{forwarder}(:#{msg}); end" 103 | } 104 | module_eval(methodDefs.join("\n")) 105 | end 106 | end 107 | 108 | 109 | # Copyright (C) 2002, 2003 Thomas Sondergaard 110 | # rubyzip is free software; you can redistribute it and/or 111 | # modify it under the terms of the ruby license. 112 | -------------------------------------------------------------------------------- /vendor/plugins/rubyzip/lib/zip/tempfile_bugfixed.rb: -------------------------------------------------------------------------------- 1 | # 2 | # tempfile - manipulates temporary files 3 | # 4 | # $Id: tempfile_bugfixed.rb,v 1.2 2005/02/19 20:30:33 thomas Exp $ 5 | # 6 | 7 | require 'delegate' 8 | require 'tmpdir' 9 | 10 | module BugFix #:nodoc:all 11 | 12 | # A class for managing temporary files. This library is written to be 13 | # thread safe. 14 | class Tempfile < DelegateClass(File) 15 | MAX_TRY = 10 16 | @@cleanlist = [] 17 | 18 | # Creates a temporary file of mode 0600 in the temporary directory 19 | # whose name is basename.pid.n and opens with mode "w+". A Tempfile 20 | # object works just like a File object. 21 | # 22 | # If tmpdir is omitted, the temporary directory is determined by 23 | # Dir::tmpdir provided by 'tmpdir.rb'. 24 | # When $SAFE > 0 and the given tmpdir is tainted, it uses 25 | # /tmp. (Note that ENV values are tainted by default) 26 | def initialize(basename, tmpdir=Dir::tmpdir) 27 | if $SAFE > 0 and tmpdir.tainted? 28 | tmpdir = '/tmp' 29 | end 30 | 31 | lock = nil 32 | n = failure = 0 33 | 34 | begin 35 | Thread.critical = true 36 | 37 | begin 38 | tmpname = sprintf('%s/%s%d.%d', tmpdir, basename, $$, n) 39 | lock = tmpname + '.lock' 40 | n += 1 41 | end while @@cleanlist.include?(tmpname) or 42 | File.exist?(lock) or File.exist?(tmpname) 43 | 44 | Dir.mkdir(lock) 45 | rescue 46 | failure += 1 47 | retry if failure < MAX_TRY 48 | raise "cannot generate tempfile `%s'" % tmpname 49 | ensure 50 | Thread.critical = false 51 | end 52 | 53 | @data = [tmpname] 54 | @clean_proc = Tempfile.callback(@data) 55 | ObjectSpace.define_finalizer(self, @clean_proc) 56 | 57 | @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600) 58 | @tmpname = tmpname 59 | @@cleanlist << @tmpname 60 | @data[1] = @tmpfile 61 | @data[2] = @@cleanlist 62 | 63 | super(@tmpfile) 64 | 65 | # Now we have all the File/IO methods defined, you must not 66 | # carelessly put bare puts(), etc. after this. 67 | 68 | Dir.rmdir(lock) 69 | end 70 | 71 | # Opens or reopens the file with mode "r+". 72 | def open 73 | @tmpfile.close if @tmpfile 74 | @tmpfile = File.open(@tmpname, 'r+') 75 | @data[1] = @tmpfile 76 | __setobj__(@tmpfile) 77 | end 78 | 79 | def _close # :nodoc: 80 | @tmpfile.close if @tmpfile 81 | @data[1] = @tmpfile = nil 82 | end 83 | protected :_close 84 | 85 | # Closes the file. If the optional flag is true, unlinks the file 86 | # after closing. 87 | # 88 | # If you don't explicitly unlink the temporary file, the removal 89 | # will be delayed until the object is finalized. 90 | def close(unlink_now=false) 91 | if unlink_now 92 | close! 93 | else 94 | _close 95 | end 96 | end 97 | 98 | # Closes and unlinks the file. 99 | def close! 100 | _close 101 | @clean_proc.call 102 | ObjectSpace.undefine_finalizer(self) 103 | end 104 | 105 | # Unlinks the file. On UNIX-like systems, it is often a good idea 106 | # to unlink a temporary file immediately after creating and opening 107 | # it, because it leaves other programs zero chance to access the 108 | # file. 109 | def unlink 110 | # keep this order for thread safeness 111 | File.unlink(@tmpname) if File.exist?(@tmpname) 112 | @@cleanlist.delete(@tmpname) if @@cleanlist 113 | end 114 | alias delete unlink 115 | 116 | if RUBY_VERSION > '1.8.0' 117 | def __setobj__(obj) 118 | @_dc_obj = obj 119 | end 120 | else 121 | def __setobj__(obj) 122 | @obj = obj 123 | end 124 | end 125 | 126 | # Returns the full path name of the temporary file. 127 | def path 128 | @tmpname 129 | end 130 | 131 | # Returns the size of the temporary file. As a side effect, the IO 132 | # buffer is flushed before determining the size. 133 | def size 134 | if @tmpfile 135 | @tmpfile.flush 136 | @tmpfile.stat.size 137 | else 138 | 0 139 | end 140 | end 141 | alias length size 142 | 143 | class << self 144 | def callback(data) # :nodoc: 145 | pid = $$ 146 | lambda{ 147 | if pid == $$ 148 | path, tmpfile, cleanlist = *data 149 | 150 | print "removing ", path, "..." if $DEBUG 151 | 152 | tmpfile.close if tmpfile 153 | 154 | # keep this order for thread safeness 155 | File.unlink(path) if File.exist?(path) 156 | cleanlist.delete(path) if cleanlist 157 | 158 | print "done\n" if $DEBUG 159 | end 160 | } 161 | end 162 | 163 | # If no block is given, this is a synonym for new(). 164 | # 165 | # If a block is given, it will be passed tempfile as an argument, 166 | # and the tempfile will automatically be closed when the block 167 | # terminates. In this case, open() returns nil. 168 | def open(*args) 169 | tempfile = new(*args) 170 | 171 | if block_given? 172 | begin 173 | yield(tempfile) 174 | ensure 175 | tempfile.close 176 | end 177 | 178 | nil 179 | else 180 | tempfile 181 | end 182 | end 183 | end 184 | end 185 | 186 | end # module BugFix 187 | if __FILE__ == $0 188 | # $DEBUG = true 189 | f = Tempfile.new("foo") 190 | f.print("foo\n") 191 | f.close 192 | f.open 193 | p f.gets # => "foo\n" 194 | f.close! 195 | end 196 | -------------------------------------------------------------------------------- /vendor/plugins/rubyzip/lib/zip/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/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 | --------------------------------------------------------------------------------