asset-pocket 0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|