css_image_embedder 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ = Version 0.1.1
2
+ * Fixed compressed (one line) embeds
3
+
4
+ = Version 0.1.0
5
+ * Gem existence started
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
data/LICENCE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Maciej Mensfeld
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.
data/Manifest ADDED
@@ -0,0 +1,23 @@
1
+ CHANGELOG.rdoc
2
+ Gemfile
3
+ LICENCE
4
+ README.md
5
+ Rakefile
6
+ init.rb
7
+ lib/css_image_embedder.rb
8
+ lib/css_image_embedder/converter.rb
9
+ lib/css_image_embedder/exception.rb
10
+ lib/css_image_embedder/helper.rb
11
+ spec/css_image_embedder_spec.rb
12
+ spec/files/bg.png
13
+ spec/files/bg_base64.png
14
+ spec/files/big_converted.css
15
+ spec/files/file_sources_invalid.css
16
+ spec/files/oneliner.css
17
+ spec/files/oneliner_converted.css
18
+ spec/files/std.css
19
+ spec/files/std_converted.css
20
+ spec/files/too_big.css
21
+ spec/files/too_big.jpg
22
+ spec/spec_helper.rb
23
+ Manifest
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # CSS Image Embedder
2
+
3
+ Css Image Embedder puts your background files directly into corresponding CSS file. Thanks to that - your server needs to handle fewer requests and the page layout renders smoothly (whole stylesheet at once).
4
+
5
+ ## INSTALL
6
+
7
+ gem install css_image_embedder
8
+
9
+ # USAGE
10
+
11
+ Add gem to your gemfile:
12
+
13
+ gem 'css_image_embedder'
14
+
15
+ and then you will gain a helper method called `image_embed_stylesheet_link_tag`. Use it instead of `stylesheet_link_tag` (params are the same). This helper method will not destroy (or modify) your original CSS files. It only changes cached CSS file, so if you do not use:
16
+
17
+ :cache => "/path/cached_stylesheets.css"
18
+
19
+ method will act the same way as `stylesheet_link_tag`. Also - you will not see any effect in development environment (except when you turn caching on).
20
+
21
+ ## Disadvantages
22
+
23
+ Css Image Embedder has one major disadvantage. Files converted into base64 are approximately 30% bigger then normal, so when you embed them into CSS file, the total size of CSS with backgrounds will not be equal to sum of CSS file and backgrounds files. However, if you use gzip compression on your server - it will compress CSS file and the total size will be approximately equal. So just turn gzip compression on :)
24
+ ## Example
25
+
26
+ Suppose we have two backgrounds: `bg1.png` and `bg2.jpg` and two CSS files (each for background declaration): `first.css` and `second.css`.
27
+
28
+ The background declarations will be normal. You don't need to do anything special in your CSS files. So lets take `first.css`:
29
+
30
+ #first { background: #fff url(/images/bg1.png) no-repeat;}
31
+
32
+ and second stylesheet:
33
+
34
+ #second { background: #fff url(/images/bg2.png) no-repeat;}
35
+
36
+ Now lets just use them in our layout:
37
+
38
+ image_embed_stylesheet_link_tag(
39
+ "first", "second",
40
+ :cache => "cached_stylesheet"
41
+ )
42
+
43
+ This will produce nice `cached_stylesheet` with both background images embedded directly into cached CSS file.
44
+
45
+
46
+ ## Note on Patches/Pull Requests
47
+
48
+ * Fork the project.
49
+ * Make your feature addition or bug fix.
50
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
51
+ * Commit, do not mess with Rakefile, version, or history.
52
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
53
+ * Send me a pull request. Bonus points for topic branches.
54
+
55
+ ## Copyright
56
+
57
+ Copyright (c) 2011 Maciej Mensfeld. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ Echoe.new('css_image_embedder', '0.1.1') do |p|
6
+ p.description = "Css Image Embedder puts your background files directly into corresponding CSS file. Thanks to that - your server needs to handle fewer requests and the page layout renders smoothly (whole stylesheet at once)."
7
+ p.url = "http://github.com/mensfeld/css_image_embedder"
8
+ p.author = "Maciej Mensfeld"
9
+ p.email = "maciej@mensfeld.pl"
10
+ p.ignore_pattern = ["tmp/*", "script/*"]
11
+ p.development_dependencies = ["rspec >=2.0.0", "rails"]
12
+ end
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{css_image_embedder}
5
+ s.version = "0.1.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Maciej Mensfeld"]
9
+ s.cert_chain = ["/home/mencio/.cert_keys/gem-public_cert.pem"]
10
+ s.date = %q{2011-04-09}
11
+ s.description = %q{Css Image Embedder puts your background files directly into corresponding CSS file. Thanks to that - your server needs to handle fewer requests and the page layout renders smoothly (whole stylesheet at once).}
12
+ s.email = %q{maciej@mensfeld.pl}
13
+ s.extra_rdoc_files = ["CHANGELOG.rdoc", "README.md", "lib/css_image_embedder.rb", "lib/css_image_embedder/converter.rb", "lib/css_image_embedder/exception.rb", "lib/css_image_embedder/helper.rb"]
14
+ s.files = ["CHANGELOG.rdoc", "Gemfile", "LICENCE", "README.md", "Rakefile", "init.rb", "lib/css_image_embedder.rb", "lib/css_image_embedder/converter.rb", "lib/css_image_embedder/exception.rb", "lib/css_image_embedder/helper.rb", "spec/css_image_embedder_spec.rb", "spec/files/bg.png", "spec/files/bg_base64.png", "spec/files/big_converted.css", "spec/files/file_sources_invalid.css", "spec/files/oneliner.css", "spec/files/oneliner_converted.css", "spec/files/std.css", "spec/files/std_converted.css", "spec/files/too_big.css", "spec/files/too_big.jpg", "spec/spec_helper.rb", "Manifest", "css_image_embedder.gemspec"]
15
+ s.homepage = %q{http://github.com/mensfeld/css_image_embedder}
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Css_image_embedder", "--main", "README.md"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{css_image_embedder}
19
+ s.rubygems_version = %q{1.5.2}
20
+ s.signing_key = %q{/home/mencio/.cert_keys/gem-private_key.pem}
21
+ s.summary = %q{Css Image Embedder puts your background files directly into corresponding CSS file. Thanks to that - your server needs to handle fewer requests and the page layout renders smoothly (whole stylesheet at once).}
22
+
23
+ if s.respond_to? :specification_version then
24
+ s.specification_version = 3
25
+
26
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
27
+ s.add_development_dependency(%q<rspec>, [">= 2.0.0"])
28
+ s.add_development_dependency(%q<rails>, [">= 0"])
29
+ else
30
+ s.add_dependency(%q<rspec>, [">= 2.0.0"])
31
+ s.add_dependency(%q<rails>, [">= 0"])
32
+ end
33
+ else
34
+ s.add_dependency(%q<rspec>, [">= 2.0.0"])
35
+ s.add_dependency(%q<rails>, [">= 0"])
36
+ end
37
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'css_image_embedder'
@@ -0,0 +1,119 @@
1
+ # coding: utf-8
2
+
3
+ # Converts CSS files - it will embed any img file attached as background
4
+ # background: url(...)... --> (base64) --> url(data:image/png;base64,...)
5
+ # Domyślnie osadza w CSSie tylko pliki mniejsze niz 32kb
6
+
7
+ require 'base64'
8
+ module CssImageEmbedder
9
+ class Converter
10
+ # IE does not accept embed files bigger then 32kb
11
+ DEFAULT_MAX_IMG_SIZE = 32*1024
12
+
13
+ attr_reader :result
14
+
15
+ def initialize(*sources)
16
+ options = sources.extract_options!.stringify_keys
17
+ source = options.delete("source")
18
+ @file_path = options.delete("path")
19
+ @root_path = options.delete("root")
20
+ @overwrite = options.delete("overwrite") || false
21
+ @result = nil
22
+
23
+ @img_max_size = options.delete("img_max_size") || DEFAULT_MAX_IMG_SIZE
24
+
25
+ if @file_path
26
+ raise CssNotFound unless File.exists?(@file_path)
27
+ @source = File.open(@file_path, 'r') { |file| file.read }
28
+ else
29
+ raise CssStringNotProvided unless source
30
+ @source = source
31
+ @overwrite = false
32
+ end
33
+
34
+ raise RootPathNotProvided unless @root_path
35
+ end
36
+
37
+ # Performe converting
38
+ def convert
39
+ if elements = pull
40
+ source = @source
41
+ elements.each do |el|
42
+ file_base64 = File.join(@root_path, el[:converted_path])
43
+ file_ext = el[:converted_path].split('.').last
44
+ begin
45
+ next if File.size?(file_base64) > @img_max_size
46
+ file_base64 = self.class.file_to_base64(file_base64)
47
+ source.gsub!(el[:original_url], "url(data:image/#{file_ext.downcase};base64,#{file_base64})")
48
+ rescue
49
+ next
50
+ end
51
+ end
52
+ @result = source
53
+ else
54
+ @result = @source
55
+ end
56
+ end
57
+
58
+ # Saves converted result to file
59
+ def save(filename = nil)
60
+ if @file_path && @overwrite && filename.nil?
61
+ p = @file_path
62
+ else
63
+ p = filename
64
+ end
65
+ f = File.new(p, "w")
66
+ f.write(@result)
67
+ f.close
68
+ end
69
+
70
+ # Force overwrite
71
+ def save!(filename = nil)
72
+ @overwrite = true
73
+ save(filename)
74
+ end
75
+
76
+ # Convert to base64 without new lines
77
+ def self.file_to_base64(path)
78
+ str = File.open(path, 'r') { |file| file.read }
79
+ Base64.encode64(str).gsub("\n", '')
80
+ end
81
+
82
+ private
83
+
84
+ # Pulls out files paths from url(smthng)
85
+ # Returns an array containing:
86
+ # - original path (./images/smthng.png)
87
+ # - converted path (/images/smthng.png)
88
+ # - original url (url(./images/smthng.png))
89
+ def pull
90
+ # Whole urls including stuff around (url('/images/smthng.png') )
91
+ whole_urls = @source.scan(/url\(.+?\)/i)
92
+ return nil if whole_urls.count == 0
93
+
94
+ img_bg_paths = []
95
+ whole_urls.each do |url|
96
+ img_bg_paths << url.scan(/url\(\s*["']?([^"']+)["']?\s*\)/i)[0][0]
97
+ end
98
+ img_bg_paths_original = img_bg_paths.clone
99
+
100
+ img_bg_paths.collect! do |img|
101
+ img = img.gsub('./', '/')
102
+ img = "/#{img}" if img[0] != '/'
103
+ img
104
+ end
105
+
106
+ result = []
107
+ img_bg_paths.each_with_index do |el, i|
108
+ result <<
109
+ {:original_path =>img_bg_paths_original[i],
110
+ :converted_path => el,
111
+ :original_url => whole_urls[i]
112
+ }
113
+ end
114
+ result.uniq!
115
+ result
116
+ end
117
+
118
+ end
119
+ end
@@ -0,0 +1,9 @@
1
+
2
+ # CSS file does not exist
3
+ class CssNotFound < StandardError; end
4
+
5
+ # CSS string not provided
6
+ class CssStringNotProvided < StandardError; end
7
+
8
+ # Root path not provided
9
+ class RootPathNotProvided < StandardError; end
@@ -0,0 +1,45 @@
1
+ # Helper methods for ActionView::Base in Rails
2
+ module CssImageEmbedder
3
+
4
+ module Helper
5
+
6
+ # Basicly it's just stylesheet_link_tag with one small difference ;)
7
+ # Will not test it - Rails guys already did it
8
+ def image_embed_stylesheet_link_tag(*sources)
9
+ options = sources.extract_options!.stringify_keys
10
+ concat = options.delete("concat")
11
+ cache = concat || options.delete("cache")
12
+ recursive = options.delete("recursive")
13
+ root = options.delete("root") || File.join(Rails.root, 'public')
14
+ env = options.delete("env") || Rails.env
15
+
16
+ if concat || (config.perform_caching && cache)
17
+ joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
18
+ joined_stylesheet_path = File.join(joined_stylesheet_name[/^#{File::SEPARATOR}/] ? config.assets_dir : config.stylesheets_dir, joined_stylesheet_name)
19
+
20
+ unless config.perform_caching && File.exists?(joined_stylesheet_path)
21
+ write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive))
22
+ # here's the magic ;)
23
+ css = CssImageEmbedder::Converter.new(
24
+ :path => joined_stylesheet_path,
25
+ :overwrite => true,
26
+ :root => root)
27
+ css.convert
28
+ css.save!
29
+ end
30
+ stylesheet_tag(joined_stylesheet_name, options)
31
+ else
32
+ sources = expand_stylesheet_sources(sources, recursive)
33
+ # When we use SASS and test env - will not work - and will raise
34
+ # exception - that css files does not exist. We make a workaround
35
+ # by not checking sources in test env
36
+ ensure_stylesheet_sources!(sources) if cache && env != 'test'
37
+ sources.collect { |source| stylesheet_tag(source, options) }.join("\n").html_safe
38
+ end
39
+ end
40
+
41
+ end # Helper
42
+
43
+ end # Base64CssEmbedder
44
+
45
+ ActionView::Base.send :include, CssImageEmbedder::Helper
@@ -0,0 +1,5 @@
1
+ path = File.expand_path(File.dirname(__FILE__))
2
+
3
+ require "#{path}/css_image_embedder/exception"
4
+ require "#{path}/css_image_embedder/helper"
5
+ require "#{path}/css_image_embedder/converter"
@@ -0,0 +1,140 @@
1
+ require 'spec_helper'
2
+
3
+ ROOT = File.expand_path(File.dirname(__FILE__))
4
+ FILES_ROOT = File.join(ROOT, 'files')
5
+
6
+ def file_content(f)
7
+ file = File.new("#{f}", "r")
8
+ content = ''
9
+ while (line = file.gets)
10
+ content+=line
11
+ end
12
+ content
13
+ end
14
+
15
+ def files_equal?(f1, f2)
16
+ file_content(f1) == file_content(f2)
17
+ end
18
+
19
+ describe CssImageEmbedder::Converter do
20
+ subject { CssImageEmbedder::Converter }
21
+
22
+ # Should clear all css files created in test
23
+ after(:all) do
24
+ FileUtils.rm("#{ROOT}/std.css")
25
+ end
26
+
27
+ context "when we have CSS file without any strange stuff" do
28
+ it "should convert std css file and embed pictures into it" do
29
+ converter = subject.new(
30
+ :path => "#{FILES_ROOT}/std.css",
31
+ :overwrite => true,
32
+ :root => ROOT
33
+ )
34
+ converter.convert
35
+ converter.result.should === file_content("#{FILES_ROOT}/std_converted.css")
36
+ end
37
+
38
+ context "and allow to overwrite" do
39
+ it "should embed backgrounds and overwrite source file" do
40
+ # Copy std.css to check if it will be overwritten
41
+ FileUtils.cp("#{FILES_ROOT}/std.css", "#{ROOT}/std.css")
42
+ converter = subject.new(
43
+ :path => "#{ROOT}/std.css",
44
+ :overwrite => true,
45
+ :root => ROOT
46
+ )
47
+ converter.convert
48
+ converter.save
49
+ converter.result.should == file_content("#{ROOT}/std.css")
50
+ converter.result.should == file_content("#{FILES_ROOT}/std_converted.css")
51
+ end
52
+ end
53
+
54
+ end
55
+
56
+ context "when we provide too big background" do
57
+ it "should not embed it by default" do
58
+ converter = subject.new(
59
+ :path => "#{FILES_ROOT}/too_big.css",
60
+ :overwrite => true,
61
+ :root => ROOT
62
+ )
63
+ converter.convert
64
+ converter.result.should === file_content("#{FILES_ROOT}/too_big.css")
65
+ end
66
+
67
+ context "but limit is bigger" do
68
+ it "should embed it" do
69
+ converter = subject.new(
70
+ :path => "#{FILES_ROOT}/too_big.css",
71
+ :overwrite => true,
72
+ :root => ROOT,
73
+ :img_max_size => 100*1024
74
+ )
75
+ converter.convert
76
+ converter.result.should === file_content("#{FILES_ROOT}/big_converted.css")
77
+ end
78
+ end
79
+ end
80
+
81
+ context "when we provide css file where paths don't point to valid files" do
82
+ it "should leave original css file structure" do
83
+ converter = subject.new(
84
+ :path => "#{FILES_ROOT}/file_sources_invalid.css",
85
+ :root => ROOT
86
+ )
87
+ converter.convert
88
+ converter.result.should == file_content("#{FILES_ROOT}/file_sources_invalid.css")
89
+ end
90
+ end
91
+
92
+ context "when we don't provide root path" do
93
+ it "should raise RootPathNotProvided" do
94
+ lambda {
95
+ subject.new(
96
+ :path => "#{FILES_ROOT}/std_converted.css"
97
+ )
98
+ }.should raise_exception(RootPathNotProvided)
99
+ end
100
+ end
101
+
102
+ context "when we provide invalid source css file" do
103
+ it "should raise Exceptions::CssNotFound" do
104
+ lambda {
105
+ subject.new(
106
+ :path => "#{FILES_ROOT}no_file.css"
107
+ )
108
+ }.should raise_exception(CssNotFound)
109
+ end
110
+
111
+ context "but we provide string containing css" do
112
+ it "should convert it from string" do
113
+ converter = subject.new(
114
+ :source => file_content("#{FILES_ROOT}/std.css"),
115
+ :root => ROOT
116
+ )
117
+ converter.convert
118
+ converter.result.should == file_content("#{FILES_ROOT}/std_converted.css")
119
+ end
120
+ end
121
+
122
+ context "when we try to convert file to base64" do
123
+ it "should convert it" do
124
+ subject.file_to_base64("#{FILES_ROOT}/bg.png").should == file_content("#{FILES_ROOT}/bg_base64.png").gsub("\n", '')
125
+ end
126
+ end
127
+
128
+ context "when we provide huge one liner" do
129
+ it "should convert it without hesitation" do
130
+ converter = subject.new(
131
+ :path => "#{FILES_ROOT}/oneliner.css",
132
+ :root => ROOT
133
+ )
134
+ converter.convert
135
+ converter.result.should == file_content("#{FILES_ROOT}/oneliner_converted.css")
136
+ end
137
+ end
138
+ end
139
+
140
+ end
data/spec/files/bg.png ADDED
Binary file
@@ -0,0 +1 @@
1
+ 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