asset-pocket 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/features/images.feature +20 -0
- data/features/javascripts.feature +89 -0
- data/features/rails.feature +52 -0
- data/features/sass.feature +69 -0
- data/features/sprites.feature +94 -0
- data/features/step_definitions/files.rb +23 -0
- data/features/step_definitions/rails.rb +78 -0
- data/features/step_definitions/sprites.rb +76 -0
- data/features/support/env.rb +37 -0
- data/features/support/matchers.rb +6 -0
- data/lib/asset_pocket.rb +4 -0
- data/lib/asset_pocket/compressor.rb +29 -0
- data/lib/asset_pocket/css_sprites.rb +141 -0
- data/lib/asset_pocket/generator.rb +107 -0
- data/lib/asset_pocket/pocket.rb +165 -0
- data/lib/asset_pocket/rails.rb +15 -0
- data/lib/asset_pocket/source_filter.rb +52 -0
- data/lib/tasks/pocket.rake +12 -0
- data/rails/init.rb +1 -0
- metadata +79 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
Feature: copy files from one directory to another
|
3
|
+
|
4
|
+
@files
|
5
|
+
Scenario: keep the directory tree
|
6
|
+
Given a file named "sources/a/one" with "1"
|
7
|
+
And a file named "sources/a/two" with "2"
|
8
|
+
And a file named "sources/b/three" with "3"
|
9
|
+
And a file named "sources/b/d/four" with "4"
|
10
|
+
When generate a pocket with:
|
11
|
+
"""
|
12
|
+
files "generated/copied/" do
|
13
|
+
base "sources"
|
14
|
+
use "sources/**/*"
|
15
|
+
end
|
16
|
+
"""
|
17
|
+
Then a file named "generated/copied/a/one" contains "1"
|
18
|
+
And a file named "generated/copied/a/two" contains "2"
|
19
|
+
And a file named "generated/copied/b/three" contains "3"
|
20
|
+
And a file named "generated/copied/b/d/four" contains "4"
|
@@ -0,0 +1,89 @@
|
|
1
|
+
|
2
|
+
Feature: JavaScripts Pocket
|
3
|
+
|
4
|
+
@javascript
|
5
|
+
Scenario: combine several files just concat them
|
6
|
+
Given a file named "sources/foo.js" with "foo();"
|
7
|
+
And a file named "sources/bar.js" with "bar();"
|
8
|
+
When generate a pocket with:
|
9
|
+
"""
|
10
|
+
js "generated/dest.js" do
|
11
|
+
use "sources/foo.js"
|
12
|
+
use "sources/bar.js"
|
13
|
+
end
|
14
|
+
"""
|
15
|
+
Then a file named "generated/dest.js" contains "foo();bar();"
|
16
|
+
|
17
|
+
@javascript
|
18
|
+
Scenario: combine several files with a separator
|
19
|
+
Given a file named "sources/foo.js" with "foo();"
|
20
|
+
And a file named "sources/bar.js" with "bar();"
|
21
|
+
When generate a pocket with:
|
22
|
+
"""
|
23
|
+
js "generated/dest.js" do
|
24
|
+
use "sources/foo.js"
|
25
|
+
use "sources/bar.js"
|
26
|
+
|
27
|
+
separator "|"
|
28
|
+
end
|
29
|
+
"""
|
30
|
+
Then a file named "generated/dest.js" contains "foo();|bar();"
|
31
|
+
|
32
|
+
|
33
|
+
@javascript
|
34
|
+
Scenario: a pattern can be used to append files sorted by name
|
35
|
+
Given a file named "sources/a/a.js" with "1"
|
36
|
+
And a file named "sources/c/b.js" with "5"
|
37
|
+
And a file named "sources/a/b.js" with "2"
|
38
|
+
And a file named "sources/b/a.js" with "4"
|
39
|
+
And a file named "sources/a/c.js" with "3"
|
40
|
+
When generate a pocket with:
|
41
|
+
"""
|
42
|
+
js "generated/everything.js" do
|
43
|
+
use "sources/**/*.js"
|
44
|
+
end
|
45
|
+
"""
|
46
|
+
Then a file named "generated/everything.js" contains "12345"
|
47
|
+
|
48
|
+
@javascript
|
49
|
+
Scenario: a custom compressor can be defined in the configuration
|
50
|
+
Given a file named "sources/foo.js" with "foo"
|
51
|
+
And a file named "sources/bar.js" with "bar"
|
52
|
+
When generate a pocket with:
|
53
|
+
"""
|
54
|
+
class UpperCompressor
|
55
|
+
def compress(content)
|
56
|
+
content.upcase
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
compressor :upper, :handler => UpperCompressor.new
|
61
|
+
js "generated/upper.js" do
|
62
|
+
compress :upper
|
63
|
+
use "sources/**/*.js"
|
64
|
+
end
|
65
|
+
"""
|
66
|
+
Then a file named "generated/upper.js" contains "BARFOO"
|
67
|
+
|
68
|
+
@javascript
|
69
|
+
Scenario: compressors can receive parameters
|
70
|
+
Given a file named "sources/foo.js" with "foo"
|
71
|
+
And a file named "sources/bar.js" with "bar"
|
72
|
+
When generate a pocket with:
|
73
|
+
"""
|
74
|
+
class AppendContentCompressor
|
75
|
+
attr_accessor :extra
|
76
|
+
def compress(content)
|
77
|
+
content + extra
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
compressor :append, :handler => AppendContentCompressor.new
|
82
|
+
compressor :append, :extra => "baz"
|
83
|
+
js "generated/append.js" do
|
84
|
+
compress :append
|
85
|
+
use "sources/**/*.js"
|
86
|
+
end
|
87
|
+
"""
|
88
|
+
Then a file named "generated/append.js" contains "barfoobaz"
|
89
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
Feature: Rails integration
|
3
|
+
Rails views have a helper method sprite_tag to create a HTML tag which will
|
4
|
+
show the image.
|
5
|
+
|
6
|
+
The rake task pocket:update will generate the pocket.
|
7
|
+
|
8
|
+
@rails
|
9
|
+
Scenario: create a new application and define a pocket with some files
|
10
|
+
Given a rails application in a temporary directory
|
11
|
+
And a file named "app/views/css/a.css" with "1"
|
12
|
+
And a file named "app/views/css/b.css" with "2"
|
13
|
+
And the application has a pocket with:
|
14
|
+
"""
|
15
|
+
css "public/stylesheets/application.css" do
|
16
|
+
use "app/views/css/*"
|
17
|
+
separator "\n"
|
18
|
+
end
|
19
|
+
"""
|
20
|
+
When update the pocket
|
21
|
+
Then a file named "public/stylesheets/application.css" contains "1\n2"
|
22
|
+
|
23
|
+
@rails
|
24
|
+
Scenario: create a CSS sprite in a rails application
|
25
|
+
Given a rails application in a temporary directory
|
26
|
+
And a file named "app/views/images/first.png" with a random image of 10x20
|
27
|
+
And a file named "app/views/images/second.png" with a random image of 10x30
|
28
|
+
And the application has a pocket with:
|
29
|
+
"""
|
30
|
+
css "public/stylesheets/sprites.css" do
|
31
|
+
sprite "icons" do
|
32
|
+
layout :vertical
|
33
|
+
use "app/views/images/*"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
"""
|
37
|
+
And the controller "foo" has a view "index" with:
|
38
|
+
"""
|
39
|
+
<%= sprite_tag "icons/first" %>
|
40
|
+
generated HTML from rails
|
41
|
+
<%= sprite_tag "icons/second" %>
|
42
|
+
"""
|
43
|
+
When update the pocket
|
44
|
+
Then the sprite "icons" is generated in "public/stylesheets/sprites.css"
|
45
|
+
And this sprite has 2 images
|
46
|
+
And this sprite is 10x50
|
47
|
+
And this sprite format is PNG
|
48
|
+
And this sprite has the image "first"
|
49
|
+
And this sprite has the image "second"
|
50
|
+
Then the page at "/foo/index" include "generated HTML from rails"
|
51
|
+
And this page has a tag matched with ".sprite-icons--first"
|
52
|
+
And this page has a tag matched with ".sprite-icons--second"
|
@@ -0,0 +1,69 @@
|
|
1
|
+
|
2
|
+
Feature: Sass integration
|
3
|
+
|
4
|
+
@sass
|
5
|
+
Scenario: filenames ended with .scss are compiled with Sass
|
6
|
+
Given a file named "sources/foo.scss" with "$color: blue; div { span { color: $color; } }"
|
7
|
+
When generate a pocket with:
|
8
|
+
"""
|
9
|
+
css "generated/base.css" do
|
10
|
+
use "sources/foo.scss"
|
11
|
+
end
|
12
|
+
"""
|
13
|
+
Then a file named "generated/base.css" contains "div span {\n color: blue; }\n"
|
14
|
+
|
15
|
+
@sass
|
16
|
+
Scenario: Sass options can be modified
|
17
|
+
Given a file named "sources/bar.scss" with "div { span { display: block; } }"
|
18
|
+
When generate a pocket with:
|
19
|
+
"""
|
20
|
+
sass :style => :compressed
|
21
|
+
css "generated/compressed.css" do
|
22
|
+
use "sources/bar.scss"
|
23
|
+
end
|
24
|
+
"""
|
25
|
+
Then a file named "generated/compressed.css" contains "div span{display:block}\n"
|
26
|
+
|
27
|
+
@sass
|
28
|
+
Scenario: multiple Sass sources can be loaded importing them
|
29
|
+
Given a file named "sources/one.scss" with "$color: blue;"
|
30
|
+
And a file named "sources/two.scss" with "$background: black;"
|
31
|
+
And a file named "sources/three.scss" with "$color: blue !default;"
|
32
|
+
And a file named "sources/final.scss" with "div { background: $background; color: $color; }"
|
33
|
+
When generate a pocket with:
|
34
|
+
"""
|
35
|
+
sass :style => :compressed
|
36
|
+
sass "generated/compressed.css" do
|
37
|
+
import "sources/one.scss"
|
38
|
+
import "sources/two.scss"
|
39
|
+
import "sources/three.scss"
|
40
|
+
import "sources/final.scss"
|
41
|
+
end
|
42
|
+
"""
|
43
|
+
Then a file named "generated/compressed.css" contains "div{background:black;color:blue}\n"
|
44
|
+
|
45
|
+
@sass
|
46
|
+
Scenario: a bundle can be created with previous bundles
|
47
|
+
Given a file named "sources/one.css" with "div {}"
|
48
|
+
And a file named "sources/two.css" with "span {}"
|
49
|
+
And a file named "sources/three.scss" with "div { span { display: block; } }"
|
50
|
+
When generate a pocket with:
|
51
|
+
"""
|
52
|
+
css "generated/first.css" do
|
53
|
+
use "sources/one.css"
|
54
|
+
use "sources/two.css"
|
55
|
+
|
56
|
+
separator "\n"
|
57
|
+
end
|
58
|
+
|
59
|
+
sass :style => :compressed
|
60
|
+
css "generated/second.css" do
|
61
|
+
use "sources/three.scss"
|
62
|
+
end
|
63
|
+
|
64
|
+
css "generated/both.css" do
|
65
|
+
use "generated/first.css"
|
66
|
+
use "generated/second.css"
|
67
|
+
end
|
68
|
+
"""
|
69
|
+
Then a file named "generated/both.css" contains "div {}\nspan {}div span{display:block}\n"
|
@@ -0,0 +1,94 @@
|
|
1
|
+
|
2
|
+
Feature: generate CSS sprites from the pocket
|
3
|
+
|
4
|
+
@sprites
|
5
|
+
Scenario: create a simple sprite with different images
|
6
|
+
Given a file named "images/a.png" with a random image of 10x10
|
7
|
+
And a file named "images/b.png" with a random image of 10x20
|
8
|
+
And a file named "images/c.png" with a random image of 20x20
|
9
|
+
When generate a pocket with:
|
10
|
+
"""
|
11
|
+
css "sprites/css/first.css" do
|
12
|
+
sprite "all" do
|
13
|
+
layout :vertical
|
14
|
+
use "images/*"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
"""
|
18
|
+
Then the sprite "all" is generated in "sprites/css/first.css"
|
19
|
+
And this sprite has 3 images
|
20
|
+
And this sprite is 20x50
|
21
|
+
And this sprite format is PNG
|
22
|
+
|
23
|
+
@sprites
|
24
|
+
Scenario Outline: sprites can be generated with different layoutes
|
25
|
+
Given 20 random images at "images/<case>/firstNN.png" with a random image of 10x30
|
26
|
+
When generate a pocket with:
|
27
|
+
"""
|
28
|
+
css "sprites/css/<case>.css" do
|
29
|
+
sprite "all-<case>" do
|
30
|
+
layout <layout>
|
31
|
+
use "images/<case>/*"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
"""
|
35
|
+
Then the sprite "all-<case>" is generated in "sprites/css/<case>.css"
|
36
|
+
And this sprite has 20 images
|
37
|
+
And this sprite is <size>
|
38
|
+
And this sprite format is PNG
|
39
|
+
|
40
|
+
Scenarios:
|
41
|
+
| case | layout | size |
|
42
|
+
| vertical | :vertical | 10x600 |
|
43
|
+
| horizontal | :horizontal | 200x30 |
|
44
|
+
| table | :columns => 5 | 50x120 |
|
45
|
+
|
46
|
+
@sprites
|
47
|
+
Scenario: different formats create different sprites
|
48
|
+
Given a file named "images/a.png" with a random image of 10x10
|
49
|
+
And a file named "images/b.png" with a random image of 10x20
|
50
|
+
And a file named "images/c.gif" with a random image of 20x20
|
51
|
+
And 10 random images at "images/firstNN.jpeg" with a random image of 10x30
|
52
|
+
When generate a pocket with:
|
53
|
+
"""
|
54
|
+
css "sprites/css/multiformat.css" do
|
55
|
+
sprite "multiformat" do
|
56
|
+
layout :vertical
|
57
|
+
use "images/*"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
"""
|
61
|
+
Then the sprite "multiformat" encoded in PNG is generated in "sprites/css/multiformat.css"
|
62
|
+
And this sprite has 2 images
|
63
|
+
And this sprite is 10x30
|
64
|
+
And the sprite "multiformat" encoded in GIF is generated in "sprites/css/multiformat.css"
|
65
|
+
And this sprite has 1 image
|
66
|
+
And this sprite is 20x20
|
67
|
+
And the sprite "multiformat" encoded in JPEG is generated in "sprites/css/multiformat.css"
|
68
|
+
And this sprite has 10 images
|
69
|
+
And this sprite is 10x300
|
70
|
+
|
71
|
+
@sprites
|
72
|
+
Scenario: the sprites can be generated using different quality values
|
73
|
+
Given a file named "images/a.jpeg" with a random image of 10x10
|
74
|
+
And a file named "images/b.jpeg" with a random image of 10x20
|
75
|
+
And a file named "images/c.jpeg" with a random image of 20x20
|
76
|
+
When generate a pocket with:
|
77
|
+
"""
|
78
|
+
css "sprites/css/everything.css" do
|
79
|
+
sprite "best" do
|
80
|
+
layout :vertical
|
81
|
+
quality 100
|
82
|
+
use "images/*"
|
83
|
+
end
|
84
|
+
|
85
|
+
sprite "worst" do
|
86
|
+
layout :vertical
|
87
|
+
quality 1
|
88
|
+
use "images/*"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
"""
|
92
|
+
Then the sprite "worst" is generated in "sprites/css/everything.css"
|
93
|
+
And the sprite "best" is generated in "sprites/css/everything.css"
|
94
|
+
And the size of the sprite "worst" is smaller than the size of the sprite "best"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
Given /^a file named "([^"]*)" with "([^"]*)"$/ do |filename, content|
|
3
|
+
filename = @temp_dir.join(filename)
|
4
|
+
filename.exist?.should be_false
|
5
|
+
|
6
|
+
@created_files.unshift filename
|
7
|
+
filename.dirname.mkpath
|
8
|
+
filename.open("w") {|f| f.write content }
|
9
|
+
end
|
10
|
+
|
11
|
+
When /^generate a pocket with:$/ do |pocket_content|
|
12
|
+
generator = AssetPocket::Generator.new
|
13
|
+
generator.root_path = @temp_dir
|
14
|
+
generator.parse_string pocket_content
|
15
|
+
generator.run!
|
16
|
+
end
|
17
|
+
|
18
|
+
Then /^a file named "([^"]*)" contains "([^"]*)"$/ do |filename, content|
|
19
|
+
filename = @temp_dir.join(filename)
|
20
|
+
filename.file?.should be_true
|
21
|
+
filename.read.inspect[1..-2].should eql(content)
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
|
2
|
+
Given /^a rails application in a temporary directory$/ do
|
3
|
+
@temp_dir = @temp_dir.join("test_pocket")
|
4
|
+
case (`rails -v` =~ /rails (\d+)/i && $1).to_i
|
5
|
+
when 3
|
6
|
+
`rails new #{@temp_dir}`
|
7
|
+
@temp_dir.join("config/initializers/rack_test.rb").open("w") {|f| f.puts <<-EOI }
|
8
|
+
RackTestApplication = proc { Rails::Application.instance }
|
9
|
+
EOI
|
10
|
+
when 2
|
11
|
+
`rails #{@temp_dir}`
|
12
|
+
@temp_dir.join("config/initializers/rack_test.rb").open("w") {|f| f.puts <<-EOI }
|
13
|
+
gem "rack-test"
|
14
|
+
require 'rack/test'
|
15
|
+
RackTestApplication = proc { ActionController::Dispatcher.new }
|
16
|
+
|
17
|
+
class String
|
18
|
+
def html_safe
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
EOI
|
23
|
+
else
|
24
|
+
pending
|
25
|
+
end
|
26
|
+
|
27
|
+
plugin_dir = @temp_dir.join("vendor/plugins")
|
28
|
+
plugin_dir.mkpath
|
29
|
+
FileUtils.cp_r Pathname.new(__FILE__).dirname.join("../../"), plugin_dir
|
30
|
+
end
|
31
|
+
|
32
|
+
Given /^the application has a pocket with:$/ do |pocket|
|
33
|
+
pocket_file = @temp_dir.join("config/pocket.rb")
|
34
|
+
pocket_file.exist?.should be_false
|
35
|
+
pocket_file.open("w") {|f| f.write pocket }
|
36
|
+
end
|
37
|
+
|
38
|
+
Given /^the controller "([^"]*)" has a view "([^"]*)" with:$/ do |controller, action, erb|
|
39
|
+
controller_file = @temp_dir.join("app/controllers/#{controller}_controller.rb")
|
40
|
+
if not controller_file.exist?
|
41
|
+
controller_file.dirname.mkpath
|
42
|
+
controller_file.open("w") {|f| f.puts "class #{"#{controller}_controller".gsub(/(?:^|_)([a-z])/) { $1.upcase }} < ApplicationController\nend" }
|
43
|
+
|
44
|
+
# This route should works in both Rails 3 and 2
|
45
|
+
routes = @temp_dir.join("config/routes.rb")
|
46
|
+
current_routes = routes.read
|
47
|
+
routes.open("w") {|f| f.write current_routes.sub(/end\s*\Z/, %[map.connect "/#{controller}/:action", :controller => "#{controller}"\nend\n]) }
|
48
|
+
end
|
49
|
+
|
50
|
+
view_file = @temp_dir.join("app/views/#{controller}/#{action}.html.erb")
|
51
|
+
view_file.dirname.mkpath
|
52
|
+
view_file.open("w") {|f| f.write erb }
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
Then /^the page at "([^"]*)" include "([^"]*)"$/ do |uri,html|
|
57
|
+
if @temp_dir.join("script/rails").exist?
|
58
|
+
runner = "rails runner"
|
59
|
+
else
|
60
|
+
runner = "script/runner"
|
61
|
+
end
|
62
|
+
|
63
|
+
@page_body = `cd #{@temp_dir}; #{runner} 'puts Rack::Test::Session.new(RackTestApplication.call).get("#{uri}").body'`
|
64
|
+
@page_body.should include(html)
|
65
|
+
end
|
66
|
+
|
67
|
+
Then /^this page has a tag matched with "([^"]*)"$/ do |selector|
|
68
|
+
@page_dom ||= Nokogiri::HTML(@page_body)
|
69
|
+
@page_dom.search(selector).should_not be_empty
|
70
|
+
end
|
71
|
+
|
72
|
+
When /^update the pocket$/ do
|
73
|
+
Process.wait(fork do
|
74
|
+
Dir.chdir @temp_dir
|
75
|
+
STDOUT.reopen "/dev/null", "w"
|
76
|
+
exec "rake", "pocket:update"
|
77
|
+
end)
|
78
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
|
2
|
+
def random_image(filename, width, height)
|
3
|
+
img = Magick::Image.new(width, height)
|
4
|
+
img.import_pixels 0, 0, width, height, "RGB", File.read("/dev/urandom", width*height*3)
|
5
|
+
|
6
|
+
filename = @temp_dir.join(filename)
|
7
|
+
filename.dirname.mkpath
|
8
|
+
img.write filename.to_s
|
9
|
+
|
10
|
+
@created_files.unshift filename if @created_files
|
11
|
+
end
|
12
|
+
|
13
|
+
Given /^(\d+) random images at "([^"]*)" with a random image of (\d+)x(\d+)$/ do |count, pattern, width, height|
|
14
|
+
width = width.to_i
|
15
|
+
height = height.to_i
|
16
|
+
count.to_i.times do |iter|
|
17
|
+
random_image pattern.sub("NN", iter.to_s), width, height
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Given /^a file named "([^"]*)" with a random image of (\d+)x(\d+)$/ do |filename, width, height|
|
22
|
+
random_image filename, width.to_i, height.to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def just_parse_css(content)
|
27
|
+
content.scan(/(\S+)\s*\{(.*?)\}/m).inject({}) do |hash, item|
|
28
|
+
rule, attributes = item
|
29
|
+
hash[rule] = attributes.split(";").map {|attribute| attribute.strip }
|
30
|
+
hash
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
Then /^the sprite "([^"]*)" is generated in "([^"]*)"$/ do |sprite, css_file|
|
35
|
+
Then %|the sprite "#{sprite}" encoded in \\w+ is generated in "#{css_file}"|
|
36
|
+
end
|
37
|
+
|
38
|
+
Then /^the sprite "([^"]*)" encoded in (\S+) is generated in "([^"]*)"$/ do |sprite, format, css_file|
|
39
|
+
|
40
|
+
@found_sprites ||= {}
|
41
|
+
|
42
|
+
@sprite_name = sprite
|
43
|
+
|
44
|
+
css_file = @temp_dir.join(css_file)
|
45
|
+
@css_loaded = just_parse_css(css_file.read)
|
46
|
+
@css_loaded.should include_key(/\.sprite-#{sprite}--/)
|
47
|
+
|
48
|
+
@sprite = @css_loaded.reject {|key, value| key !~ /\.sprite-#{sprite}--/ or value.join !~ /url\([^)]+\.#{format}\)/i }
|
49
|
+
@image_sprite = Magick::Image.read(css_file.dirname.join(@sprite.values.to_s.first =~ /url\((.*?\.#{format})\)/i && $1).to_s).first
|
50
|
+
|
51
|
+
@found_sprites[sprite] = @image_sprite
|
52
|
+
end
|
53
|
+
|
54
|
+
Then /^this sprite has (\d+) images?$/ do |count|
|
55
|
+
@sprite.size.should eql(count.to_i)
|
56
|
+
end
|
57
|
+
|
58
|
+
Then /^this sprite is (\d+)x(\d+)$/ do |width, height|
|
59
|
+
@image_sprite.columns.should eql(width.to_i)
|
60
|
+
@image_sprite.rows.should eql(height.to_i)
|
61
|
+
end
|
62
|
+
|
63
|
+
Then /^this sprite format is (\S+)$/ do |format|
|
64
|
+
@image_sprite.format.downcase.should eql(format.downcase)
|
65
|
+
end
|
66
|
+
|
67
|
+
Then /^this sprite has the image "([^"]*)"$/ do |image_name|
|
68
|
+
@css_loaded.should include_key(/\.sprite-#{@sprite_name}--#{image_name}\b/)
|
69
|
+
end
|
70
|
+
|
71
|
+
Then /^the size of the sprite "([^"]*)" is smaller than the size of the sprite "([^"]*)"$/ do |big_sprite, small_sprite|
|
72
|
+
@found_sprites[big_sprite].should_not be_nil
|
73
|
+
@found_sprites[small_sprite].should_not be_nil
|
74
|
+
(@found_sprites[big_sprite].to_blob > @found_sprites[small_sprite].to_blob).should be_true
|
75
|
+
end
|
76
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
|
2
|
+
require 'fileutils'
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
$: << File.expand_path("../../../lib/", __FILE__)
|
6
|
+
require 'asset_pocket/generator'
|
7
|
+
|
8
|
+
$temp_directory_count = 0
|
9
|
+
def make_temp_directory
|
10
|
+
temp_dir = Pathname.new("/tmp/asset_pocket_test/t#$$/#{$temp_directory_count += 1}")
|
11
|
+
temp_dir.mkpath
|
12
|
+
temp_dir
|
13
|
+
end
|
14
|
+
|
15
|
+
class Pathname
|
16
|
+
def rmdir_when_empty
|
17
|
+
rmdir
|
18
|
+
true
|
19
|
+
rescue Errno::ENOTEMPTY
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Before do
|
25
|
+
@created_files = []
|
26
|
+
@temp_dir = make_temp_directory
|
27
|
+
|
28
|
+
AssetPocket::SourceFilter::Sass.default_options[:cache_location] = @temp_dir.join("sass-cache").to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
After do
|
32
|
+
@created_files.each do |filename|
|
33
|
+
filename.delete
|
34
|
+
filename.dirname.rmdir_when_empty
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
data/lib/asset_pocket.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
module AssetPocket
|
3
|
+
class Compressor
|
4
|
+
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
class <<self
|
8
|
+
attr_accessor :available
|
9
|
+
|
10
|
+
def parse(name, options = {})
|
11
|
+
name = name.to_s
|
12
|
+
if not available.has_key?(name)
|
13
|
+
if handler = options[:handler]
|
14
|
+
available[name] = handler
|
15
|
+
else
|
16
|
+
raise Error, "Unknown compressor: #{name}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
hanlder = available[name]
|
21
|
+
options.each_pair {|key, value| hanlder.send("#{key}=", value) unless key == :handler }
|
22
|
+
|
23
|
+
hanlder
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
self.available = {}
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
|
2
|
+
require 'RMagick'
|
3
|
+
require 'md5'
|
4
|
+
|
5
|
+
module AssetPocket
|
6
|
+
class Pocket
|
7
|
+
class SpriteDefition
|
8
|
+
attr_accessor :layout, :quality
|
9
|
+
attr_reader :name, :css_block, :images_location
|
10
|
+
|
11
|
+
def initialize(name, css_block)
|
12
|
+
@name = name
|
13
|
+
@css_block = css_block
|
14
|
+
@files = {}
|
15
|
+
@layout = :vertical
|
16
|
+
|
17
|
+
self.images_location = "../images/"
|
18
|
+
end
|
19
|
+
|
20
|
+
def images_location=(location)
|
21
|
+
@images_location = css_block.pocket.generator.root_path.join(css_block.filename).dirname.join(location)
|
22
|
+
end
|
23
|
+
|
24
|
+
def generate!
|
25
|
+
@images_location.mkpath
|
26
|
+
|
27
|
+
@files.each_pair do |format, images|
|
28
|
+
|
29
|
+
sprite_name = @images_location.join("sprite-#{name}.#{format.downcase}")
|
30
|
+
url_css_sprite = sprite_name.relative_path_from(css_block.full_filename.dirname)
|
31
|
+
|
32
|
+
# Compute the canvas size
|
33
|
+
width = 0
|
34
|
+
height = 0
|
35
|
+
|
36
|
+
distribute_images images do |image, cx, cy|
|
37
|
+
cx = cx + image.columns
|
38
|
+
cy = cy + image.rows
|
39
|
+
|
40
|
+
height = cy if cy > height
|
41
|
+
width = cx if cx > width
|
42
|
+
end
|
43
|
+
|
44
|
+
canvas = Magick::Image.new(width, height)
|
45
|
+
canvas.format = format
|
46
|
+
|
47
|
+
distribute_images images do |image, cx, cy|
|
48
|
+
canvas.composite! image, cx, cy, Magick::OverCompositeOp
|
49
|
+
|
50
|
+
css = ".sprite.sprite-#{name}--#{File.basename(image.filename).gsub(/\.\w+$/, "")} {"
|
51
|
+
css << "background: url(#{url_css_sprite}) -#{cx}px -#{cy}px;"
|
52
|
+
css << "width: #{image.columns}px;"
|
53
|
+
css << "height: #{image.rows}px;"
|
54
|
+
css << "} \n"
|
55
|
+
css_block.content << [ :string, css]
|
56
|
+
end
|
57
|
+
|
58
|
+
images.clear
|
59
|
+
quality = quality() # Force variable creation. Block passed to canvas.write losses instance reference (called with instance_eval?)
|
60
|
+
canvas.write(sprite_name.to_s) do |info|
|
61
|
+
info.quality = quality if quality
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def use(pattern)
|
67
|
+
@files ||= []
|
68
|
+
Dir[css_block.pocket.generator.root_path.join(pattern)].each do |image_file|
|
69
|
+
begin
|
70
|
+
image = Magick::Image.read(image_file)
|
71
|
+
if image.size != 1
|
72
|
+
# Animated images can not be sprited
|
73
|
+
# TODO log the action
|
74
|
+
next
|
75
|
+
end
|
76
|
+
|
77
|
+
image = image.first
|
78
|
+
|
79
|
+
rescue Magick::ImageMagickError
|
80
|
+
# TODO log the error
|
81
|
+
next
|
82
|
+
end
|
83
|
+
|
84
|
+
@files[image.format] ||= []
|
85
|
+
@files[image.format] << image
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
def distribute_images(images, &block)
|
91
|
+
cx = cy = 0
|
92
|
+
valid_layout = true
|
93
|
+
|
94
|
+
case layout
|
95
|
+
when :vertical
|
96
|
+
images.each do |image|
|
97
|
+
block.call image, cx, cy
|
98
|
+
cy += image.rows
|
99
|
+
end
|
100
|
+
|
101
|
+
when :horizontal
|
102
|
+
images.each do |image|
|
103
|
+
block.call image, cx, cy
|
104
|
+
cx += image.columns
|
105
|
+
end
|
106
|
+
|
107
|
+
when Hash
|
108
|
+
if layout[:columns]
|
109
|
+
column = 0
|
110
|
+
row_height = 0
|
111
|
+
columns_per_row = layout[:columns]
|
112
|
+
|
113
|
+
images.each do |image|
|
114
|
+
column += 1
|
115
|
+
if column > columns_per_row
|
116
|
+
column = 1
|
117
|
+
cx = 0
|
118
|
+
cy += row_height
|
119
|
+
row_height = 0
|
120
|
+
end
|
121
|
+
|
122
|
+
block.call image, cx, cy
|
123
|
+
cx += image.columns
|
124
|
+
row_height = image.rows if image.rows > row_height
|
125
|
+
end
|
126
|
+
|
127
|
+
else
|
128
|
+
valid_layout = false
|
129
|
+
end
|
130
|
+
|
131
|
+
else
|
132
|
+
valid_layout = false
|
133
|
+
end
|
134
|
+
|
135
|
+
unless valid_layout
|
136
|
+
raise ArgumentError, "Unknown layout: #{layout} in sprite #{name}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
|
2
|
+
require 'asset_pocket/pocket'
|
3
|
+
require 'asset_pocket/source_filter'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'pathname'
|
6
|
+
|
7
|
+
module AssetPocket
|
8
|
+
class Generator
|
9
|
+
attr_reader :root_path
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
self.root_path = "."
|
13
|
+
@pocket = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def root_path=(path)
|
17
|
+
@root_path = Pathname.new(path.to_s)
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_string(content)
|
21
|
+
@pocket = Pocket.new(self)
|
22
|
+
@pocket.parse_string content
|
23
|
+
end
|
24
|
+
|
25
|
+
def read_file(filename)
|
26
|
+
root_path.join(filename).read
|
27
|
+
end
|
28
|
+
|
29
|
+
def run!
|
30
|
+
@pocket.definitions.each do |definition|
|
31
|
+
send "process_#{definition.process}", definition
|
32
|
+
end
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def process_create_file(definition)
|
37
|
+
generated_filename = root_path.join(definition.filename)
|
38
|
+
FileUtils.mkpath File.dirname(generated_filename)
|
39
|
+
File.open(generated_filename, "w") do |generated_file|
|
40
|
+
generated_content = []
|
41
|
+
definition.content.each do |content|
|
42
|
+
case content[0]
|
43
|
+
when :pattern
|
44
|
+
generated_content.concat(
|
45
|
+
Dir[root_path.join(content[1])].
|
46
|
+
sort!.
|
47
|
+
map! {|found_file| SourceFilter.filter found_file })
|
48
|
+
|
49
|
+
when :string
|
50
|
+
generated_content << content[1]
|
51
|
+
|
52
|
+
else
|
53
|
+
raise ArgumentError, "Unknown content type: #{content[0]}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
generated_content = generated_content.join(definition.separator)
|
58
|
+
|
59
|
+
if definition.use_compressor?
|
60
|
+
generated_content = definition.compressor.compress(generated_content)
|
61
|
+
end
|
62
|
+
|
63
|
+
generated_content = definition.post_process(generated_content)
|
64
|
+
generated_file.write(generated_content)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def process_copy_files(definition)
|
69
|
+
base = Pathname.new(File.expand_path(definition.base, root_path))
|
70
|
+
dest_dir = root_path.join(definition.filename)
|
71
|
+
dest_dir.mkpath
|
72
|
+
|
73
|
+
definition.content.each do |content|
|
74
|
+
case content[0]
|
75
|
+
when :pattern
|
76
|
+
Dir[root_path.join(content[1])].each do |source_file|
|
77
|
+
source_file = Pathname.new(source_file)
|
78
|
+
|
79
|
+
# Only work with regular file
|
80
|
+
next unless source_file.file?
|
81
|
+
|
82
|
+
dest_file = dest_dir.join(source_file.relative_path_from(base))
|
83
|
+
|
84
|
+
if dest_file.exist?
|
85
|
+
# Compare it using timestamps and size
|
86
|
+
if source_file.size == dest_file.size and source_file.mtime == dest_file.mtime
|
87
|
+
next
|
88
|
+
end
|
89
|
+
|
90
|
+
# Remove it, since we have to recreate
|
91
|
+
dest_file.delete
|
92
|
+
end
|
93
|
+
|
94
|
+
# Try to create a hard link (Unix on same mount points).
|
95
|
+
# If it fails, copy it
|
96
|
+
dest_file.dirname.mkpath
|
97
|
+
begin
|
98
|
+
dest_file.make_link source_file
|
99
|
+
rescue Exception => e
|
100
|
+
dest_file.open("w") {|f| f.write source_file.read }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
|
2
|
+
require 'asset_pocket/compressor'
|
3
|
+
require 'asset_pocket/css_sprites'
|
4
|
+
|
5
|
+
module AssetPocket
|
6
|
+
class Pocket
|
7
|
+
|
8
|
+
attr_reader :definitions, :generator
|
9
|
+
|
10
|
+
def initialize(generator)
|
11
|
+
@generator = generator
|
12
|
+
@definitions = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse_string(content)
|
16
|
+
instance_eval content
|
17
|
+
end
|
18
|
+
|
19
|
+
def compressor(name, options = {})
|
20
|
+
AssetPocket::Compressor.parse name, options
|
21
|
+
end
|
22
|
+
|
23
|
+
def sass(option = {}, &block)
|
24
|
+
case option
|
25
|
+
when Hash
|
26
|
+
SourceFilter::Sass.default_options.merge! option
|
27
|
+
when String
|
28
|
+
defs = SassDefinitions.new(self, option)
|
29
|
+
defs.instance_eval(&block)
|
30
|
+
definitions << defs
|
31
|
+
else
|
32
|
+
raise ArgumentError, "Unknown argument type: #{options.class}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def css(filename, &block)
|
37
|
+
defs = CSSDefinitions.new(self, filename)
|
38
|
+
defs.instance_eval(&block)
|
39
|
+
definitions << defs
|
40
|
+
end
|
41
|
+
|
42
|
+
def js(filename, &block)
|
43
|
+
defs = JSDefinitions.new(self, filename)
|
44
|
+
defs.instance_eval(&block)
|
45
|
+
definitions << defs
|
46
|
+
end
|
47
|
+
|
48
|
+
def files(dirname, &block)
|
49
|
+
defs = FilesDefinitions.new(self, dirname)
|
50
|
+
defs.instance_eval(&block)
|
51
|
+
definitions << defs
|
52
|
+
end
|
53
|
+
|
54
|
+
class Definitions
|
55
|
+
attr_reader :content, :compressor, :filename, :pocket
|
56
|
+
|
57
|
+
def initialize(pocket, filename)
|
58
|
+
@pocket = pocket
|
59
|
+
@content = []
|
60
|
+
@filename = filename
|
61
|
+
@compressor = nil
|
62
|
+
@separator = ""
|
63
|
+
end
|
64
|
+
|
65
|
+
def full_filename
|
66
|
+
pocket.generator.root_path.join filename
|
67
|
+
end
|
68
|
+
|
69
|
+
def process
|
70
|
+
:create_file
|
71
|
+
end
|
72
|
+
|
73
|
+
def use_compressor?
|
74
|
+
@compressor
|
75
|
+
end
|
76
|
+
|
77
|
+
def use(pattern)
|
78
|
+
@content << [ :pattern, pattern ]
|
79
|
+
end
|
80
|
+
|
81
|
+
def compress(name, options = {})
|
82
|
+
@compressor = AssetPocket::Compressor.parse name, options
|
83
|
+
end
|
84
|
+
|
85
|
+
def separator(new_value = nil)
|
86
|
+
@separator = new_value unless new_value.nil?
|
87
|
+
@separator
|
88
|
+
end
|
89
|
+
|
90
|
+
def post_process(generated_content)
|
91
|
+
generated_content
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
class CSSDefinitions < Definitions
|
97
|
+
|
98
|
+
def sprite(name, &block)
|
99
|
+
@current_sprite = SpriteDefition.new(name, self)
|
100
|
+
block.call
|
101
|
+
@current_sprite.generate!
|
102
|
+
ensure
|
103
|
+
@current_sprite = nil
|
104
|
+
end
|
105
|
+
|
106
|
+
def layout(value)
|
107
|
+
ensure_sprite!
|
108
|
+
@current_sprite.layout = value
|
109
|
+
end
|
110
|
+
|
111
|
+
def images_location(value)
|
112
|
+
ensure_sprite!
|
113
|
+
@current_sprite.images_location = value
|
114
|
+
end
|
115
|
+
|
116
|
+
def quality(value)
|
117
|
+
ensure_sprite!
|
118
|
+
@current_sprite.quality = value
|
119
|
+
end
|
120
|
+
|
121
|
+
def use(pattern)
|
122
|
+
if @current_sprite
|
123
|
+
@current_sprite.use pattern
|
124
|
+
else
|
125
|
+
super
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
def ensure_sprite!
|
131
|
+
raise NameError, "this macro has to be used in a sprite block" if @current_sprite.nil?
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class JSDefinitions < Definitions
|
136
|
+
end
|
137
|
+
|
138
|
+
class SassDefinitions < Definitions
|
139
|
+
def import(filename)
|
140
|
+
@content << [ :string, "@import \"#{File.expand_path(filename, pocket.generator.root_path)}\";\n" ]
|
141
|
+
end
|
142
|
+
|
143
|
+
def post_process(generated_content)
|
144
|
+
SourceFilter::Sass.render("#{filename}.scss", generated_content)
|
145
|
+
end
|
146
|
+
|
147
|
+
undef_method :use
|
148
|
+
end
|
149
|
+
|
150
|
+
class FilesDefinitions < Definitions
|
151
|
+
def process
|
152
|
+
:copy_files
|
153
|
+
end
|
154
|
+
|
155
|
+
def base(value = nil)
|
156
|
+
@base = value if value
|
157
|
+
@base || "."
|
158
|
+
end
|
159
|
+
|
160
|
+
undef_method :separator
|
161
|
+
undef_method :compress
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
module AssetPocket
|
3
|
+
module ViewHelperMethods
|
4
|
+
def sprite_tag(name, options = {})
|
5
|
+
# TODO options: tag_name, :class, :style, something like that
|
6
|
+
# TODO Generate the CSS class name inside AssetPocket, and not hardcoded
|
7
|
+
group, image = name.split("/")
|
8
|
+
%[<span class="sprite-#{group}--#{image}"> </span>].html_safe
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
ActionView::Base.class_eval do
|
14
|
+
include AssetPocket::ViewHelperMethods
|
15
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
module AssetPocket
|
3
|
+
module SourceFilter
|
4
|
+
extend self
|
5
|
+
|
6
|
+
module Sass
|
7
|
+
extend self
|
8
|
+
|
9
|
+
attr_accessor :default_options
|
10
|
+
self.default_options = {
|
11
|
+
:css_location => "./tmp/sass-cache"
|
12
|
+
}
|
13
|
+
|
14
|
+
def render(filename, content)
|
15
|
+
options = { :filename => filename }.merge(default_options)
|
16
|
+
|
17
|
+
if filename =~ /\.sass$/i
|
18
|
+
options[:syntax] = :sass
|
19
|
+
elsif filename =~ /\.scss$/i
|
20
|
+
options[:syntax] = :scss
|
21
|
+
else
|
22
|
+
return content
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'sass' # Load only when needed
|
26
|
+
|
27
|
+
begin
|
28
|
+
::Sass::Engine.new(content, options).render
|
29
|
+
|
30
|
+
rescue ::Sass::SyntaxError => error
|
31
|
+
"/* #{File.basename(filename)}: #{error.to_s.gsub("*/", "* /")} */"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
Filters = [
|
37
|
+
[ /.*\.s[ac]ss$/i, Sass.method(:render) ]
|
38
|
+
]
|
39
|
+
|
40
|
+
def filter(filename)
|
41
|
+
content = File.read filename
|
42
|
+
|
43
|
+
Filters.each do |filter|
|
44
|
+
if filename =~ filter[0]
|
45
|
+
return filter[1].call filename, content
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
content
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
namespace :pocket do
|
3
|
+
|
4
|
+
desc "Update the pocket using AssetPocket"
|
5
|
+
task :update => :environment do
|
6
|
+
require 'asset_pocket/generator'
|
7
|
+
generator = AssetPocket::Generator.new
|
8
|
+
generator.root_path = Rails.root
|
9
|
+
generator.parse_string Rails.root.join("config/pocket.rb").read
|
10
|
+
generator.run!
|
11
|
+
end
|
12
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'asset_pocket/rails'
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: asset-pocket
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
version: "0.1"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- Ayose Cazorla
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2010-08-17 00:00:00 +01:00
|
17
|
+
default_executable:
|
18
|
+
dependencies: []
|
19
|
+
|
20
|
+
description: Using a config file (pocket) you can create multiple kind of assets groups
|
21
|
+
email: setepo@gmail.com
|
22
|
+
executables: []
|
23
|
+
|
24
|
+
extensions: []
|
25
|
+
|
26
|
+
extra_rdoc_files: []
|
27
|
+
|
28
|
+
files:
|
29
|
+
- features/support/matchers.rb
|
30
|
+
- features/support/env.rb
|
31
|
+
- features/step_definitions/files.rb
|
32
|
+
- features/step_definitions/rails.rb
|
33
|
+
- features/step_definitions/sprites.rb
|
34
|
+
- features/rails.feature
|
35
|
+
- features/images.feature
|
36
|
+
- features/sass.feature
|
37
|
+
- features/javascripts.feature
|
38
|
+
- features/sprites.feature
|
39
|
+
- lib/asset_pocket/pocket.rb
|
40
|
+
- lib/asset_pocket/css_sprites.rb
|
41
|
+
- lib/asset_pocket/source_filter.rb
|
42
|
+
- lib/asset_pocket/compressor.rb
|
43
|
+
- lib/asset_pocket/generator.rb
|
44
|
+
- lib/asset_pocket/rails.rb
|
45
|
+
- lib/asset_pocket.rb
|
46
|
+
- lib/tasks/pocket.rake
|
47
|
+
- rails/init.rb
|
48
|
+
has_rdoc: true
|
49
|
+
homepage: http://github.com/setepo/asset_pocket
|
50
|
+
licenses: []
|
51
|
+
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
71
|
+
requirements: []
|
72
|
+
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 1.3.6
|
75
|
+
signing_key:
|
76
|
+
specification_version: 3
|
77
|
+
summary: Manages assets in a versatile way
|
78
|
+
test_files: []
|
79
|
+
|