spritely 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9a3b754bc1e6fd1111a7eab9259f7239e45e4c4d
4
- data.tar.gz: 03f79678a60dd3967883a42828956e7b74074eb4
3
+ metadata.gz: 667e7648ac4d154e6ec6f9a98b08f1f50a2aae93
4
+ data.tar.gz: 8ce617668fea3fc218ac4bff2aad24988ba9bdc1
5
5
  SHA512:
6
- metadata.gz: 1fd3cba6154c6c96082e4c06331de30838f42edddf3607c462617a5e5ced06a2194ee5319092702a66656f71bcea910c1260598daab3fb57da4e72debea22b36
7
- data.tar.gz: 2af4928b574b2dbcba9d3bad26332a92a67dad95b767857fb02db5c41f798c1e7111e799e98d4134866d67ebbc5716d088319182c2a6f13a3eecb6df14a586e9
6
+ metadata.gz: 0b8fb5a5c96ebb17a0c3aefca9a4081f4036c259b182b82cbcfb37085b64da7f10cea73aa1c9b10bd74a0afc4a24ede9ca431ac8db8853559024236ef46aea82
7
+ data.tar.gz: ac6fc8c395a4a934217090846e458347cc6d150c09dca1ff87000dd6df91191662e0dca3eba6861da2356f5b2ce8a885224c024d9e9f1479df6ceeb9fe156c3c
@@ -1,6 +1,15 @@
1
1
  require 'digest/md5'
2
2
 
3
3
  module Spritely
4
+ # `Cache` is responsible for generating and fetching the current sprite cache
5
+ # `value. It is used to determine whether to generate a new sprite map or not.
6
+ #
7
+ # Passing in a set of objects that respond to `cache_key` will generate an MD5
8
+ # hash that can then be stored in the image itself.
9
+ #
10
+ # Fetching an existing cache key actually reads the PNG file itself. It scans
11
+ # the chunks of the PNG until it finds the `cache_key` keyword, returning the
12
+ # subsequent value found.
4
13
  class Cache < Struct.new(:filename)
5
14
  PNG_SIGNATURE_LENGTH = 8 # http://www.w3.org/TR/PNG/#5PNG-file-signature
6
15
  PNG_INFO_LENGTH = 8 # http://www.w3.org/TR/PNG/#5DataRep
@@ -1,6 +1,8 @@
1
1
  require 'spritely/image_set'
2
2
 
3
3
  module Spritely
4
+ # A `SpriteMap` has a `Collection` that knows how to calculate the size of the
5
+ # sprite, based on image repetition and spacing.
4
6
  class Collection < Struct.new(:files, :options)
5
7
  def self.create(*args)
6
8
  new(*args).tap do |collection|
@@ -20,6 +22,12 @@ module Spritely
20
22
  files.collect { |file| File.mtime(file) }.join
21
23
  end
22
24
 
25
+ # Returns the width of the to-be-generated sprite image. When none of the
26
+ # images repeat, it is simply the max width of all images in the sprite.
27
+ # When an image in the sprite is repeated, a calculation is performed based
28
+ # on the least common multiple of all repeated images. That least common
29
+ # multiple is then multiplied by the minimum multiple that will result in a
30
+ # value greater than or equal to the max width of all images in the sprite.
23
31
  def width
24
32
  return @width if @width
25
33
 
@@ -38,6 +46,8 @@ module Spritely
38
46
  heights.reduce(:+)
39
47
  end
40
48
 
49
+ # Upon creation, the collection is then positioned appropriately by
50
+ # positioning each image within the sprite.
41
51
  def position!
42
52
  image_sets.each_with_index do |image_set, index|
43
53
  image_set.top = heights[0..index].reduce(:+) - image_set.outer_height
@@ -1,6 +1,8 @@
1
1
  require 'spritely/image'
2
2
 
3
3
  module Spritely
4
+ # Each image in the sprite maps to an instance of `ImageSet` that stores the
5
+ # image data, width, height, and outer positioning.
4
6
  class ImageSet
5
7
  attr_accessor :top
6
8
  attr_reader :path, :options, :data, :width, :height, :left
@@ -37,6 +39,9 @@ module Spritely
37
39
  options[:position] == 'right'
38
40
  end
39
41
 
42
+ # When positioned in the sprite, we must take into account whether the image
43
+ # is configured to repeat, or is positioned to the right-hand side of the
44
+ # sprite map.
40
45
  def position_in!(collection_width)
41
46
  if repeated?
42
47
  left_position = 0
@@ -1,10 +1,28 @@
1
1
  require 'active_support/core_ext/hash/deep_merge'
2
2
  require 'active_support/core_ext/hash/except'
3
+ require 'active_support/core_ext/hash/keys'
3
4
  require 'active_support/core_ext/hash/slice'
4
5
 
5
6
  module Spritely
7
+ # Provides a simpler method of querying a particular image's options. Rather
8
+ # than each `Image` needing to know how to parse the keyword arguments passed
9
+ # into the `spritely-map` function, this simplifies and de-duplicates the
10
+ # passed option keys.
11
+ #
12
+ # $application-sprite: spritely-map("application/*.png",
13
+ # $arrow-repeat: true,
14
+ # $arrow-spacing: 10px,
15
+ # $another-image-position: right,
16
+ # $spacing: 5px
17
+ # );
18
+ #
19
+ # The options passed in above will be easily accessible via an instance of
20
+ # this class.
21
+ #
22
+ # options['arrow'] => {repeat: true, spacing: 10}
23
+ # options['another-image'] => {position: 'right', spacing: 5}
6
24
  class Options < Struct.new(:hash)
7
- GLOBAL_OPTIONS = [:spacing, :position]
25
+ GLOBAL_OPTIONS = ['spacing', 'position']
8
26
 
9
27
  def cache_key
10
28
  stripped_hash.to_s
@@ -15,28 +33,26 @@ module Spritely
15
33
  end
16
34
 
17
35
  def [](key)
18
- options[key] || global_options
36
+ options[key.gsub('-', '_')] || global_options
19
37
  end
20
38
 
21
39
  private
22
40
 
23
41
  def options
24
42
  @options ||= stripped_hash.except(*GLOBAL_OPTIONS).inject({}) do |h, (key, value)|
25
- split_key = key.to_s.split('_')
26
- option = {split_key.pop.to_sym => value}
27
- image = split_key.join('-')
43
+ image, _, option = key.rpartition('_')
28
44
  h[image] ||= global_options.dup
29
- h.deep_merge!(image => option)
45
+ h.deep_merge!(image => {option.to_sym => value})
30
46
  end
31
47
  end
32
48
 
33
49
  def global_options
34
- @global_options ||= stripped_hash.slice(*GLOBAL_OPTIONS)
50
+ @global_options ||= stripped_hash.slice(*GLOBAL_OPTIONS).symbolize_keys!
35
51
  end
36
52
 
37
53
  def stripped_hash
38
54
  @stripped_hash ||= hash.inject({}) do |h, (key, sass_object)|
39
- h.merge!(key.to_sym => sass_object.value)
55
+ h.merge!(key => sass_object.value)
40
56
  end
41
57
  end
42
58
  end
@@ -30,6 +30,12 @@ module Spritely
30
30
 
31
31
  ::Sass::Script::Functions.declare :spritely_position, [:sprite_map, :image_name]
32
32
 
33
+ def spritely_background(sprite_map, image_name)
34
+ Sass::Script::List.new([spritely_url(sprite_map), spritely_position(sprite_map, image_name)], :space)
35
+ end
36
+
37
+ ::Sass::Script::Functions.declare :spritely_background, [:sprite_map, :image_name]
38
+
33
39
  def spritely_width(sprite_map, image_name)
34
40
  image = find_image(sprite_map, image_name)
35
41
 
@@ -2,6 +2,10 @@ require 'sprockets/manifest'
2
2
  require 'active_support/core_ext/module/aliasing'
3
3
 
4
4
  module Sprockets
5
+ # In order to hook into Sprockets' asset compilation appropriately, we must
6
+ # chain our own implementation of `compile`. Our extension calls up to the
7
+ # original, and then performs the same action on the generated sprite images,
8
+ # forcing the sprites to be part of the compiled asset manifest.
5
9
  class Manifest
6
10
  def compile_with_sprites(*args)
7
11
  compile_without_sprites(*args)
@@ -1,3 +1,3 @@
1
1
  module Spritely
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -14,8 +14,8 @@ describe Spritely::Options do
14
14
 
15
15
  subject(:options) { Spritely::Options.new(hash) }
16
16
 
17
- its(:inspect) { should eq("#<Spritely::Options global_options=#{{spacing: 901, position: 'left'}} options=#{{'some-new-image' => {spacing: 789, position: 'right', x: 123, y: 456}, 'another-image' => {spacing: 901, position: 'left', repeat: true}, 'yet-another-image' => {spacing: 901, position: 'left', repeat: false}}}>") }
18
- its(:cache_key) { should eq({some_new_image_spacing: 789, some_new_image_x: 123, some_new_image_y: 456, some_new_image_position: 'right', another_image_repeat: true, yet_another_image_repeat: false, spacing: 901, position: 'left'}.to_s) }
17
+ its(:inspect) { should eq("#<Spritely::Options global_options=#{{spacing: 901, position: 'left'}} options=#{{'some_new_image' => {spacing: 789, position: 'right', x: 123, y: 456}, 'another_image' => {spacing: 901, position: 'left', repeat: true}, 'yet_another_image' => {spacing: 901, position: 'left', repeat: false}}}>") }
18
+ its(:cache_key) { should eq({"some_new_image_spacing" => 789, "some_new_image_x" => 123, "some_new_image_y" => 456, "some_new_image_position" => 'right', "another_image_repeat" => true, "yet_another_image_repeat" => false, "spacing" => 901, "position" => 'left'}.to_s) }
19
19
 
20
20
  its(['some-new-image']) { should eq({spacing: 789, position: 'right', x: 123, y: 456}) }
21
21
  its(['another-image']) { should eq({spacing: 901, repeat: true, position: 'left'}) }
@@ -23,7 +23,7 @@ describe Spritely::Options do
23
23
 
24
24
  describe '#[]' do
25
25
  it 'should fall back to an empty hash' do
26
- expect(options[:unknown]).to eq({spacing: 901, position: 'left'})
26
+ expect(options['unknown']).to eq({spacing: 901, position: 'left'})
27
27
  end
28
28
  end
29
29
  end
@@ -6,47 +6,41 @@ describe Spritely::SassFunctions do
6
6
  def files; []; end
7
7
  end
8
8
 
9
+ module AssetUrlModule
10
+ def asset_url(path)
11
+ Sass::Script::String.new("url(#{path})")
12
+ end
13
+ end
14
+
9
15
  let(:sprite_map) { SpriteMapDouble.new }
16
+ let(:image) { double(left: 10, top: 12, width: 25, height: 50) }
10
17
 
11
- before { allow(Spritely::SpriteMap).to receive(:create).and_return(sprite_map) }
18
+ before do
19
+ allow(Spritely::SpriteMap).to receive(:create).and_return(sprite_map)
20
+ allow(sprite_map).to receive(:find).with('bar').and_return(image)
21
+ end
12
22
 
13
- shared_examples "a sprite function that checks image existence" do
14
- before { allow(sprite_map).to receive(:find).with('bar').and_return(image) }
23
+ before(:all) { ::Sass::Script::Functions.send(:include, AssetUrlModule) }
24
+ after(:all) { ::Sass::Script::Functions.send(:undef_method, :asset_url) }
15
25
 
16
- context 'the image does not exist' do
17
- let(:image) { nil }
26
+ shared_examples "a sprite function that checks image existence" do
27
+ let(:image) { nil }
18
28
 
19
- it 'should raise a nice exception' do
20
- expect { subject }.to raise_error(Sass::SyntaxError, "No image 'bar' found in sprite map 'test'.")
21
- end
29
+ it 'should raise a nice exception' do
30
+ expect { subject }.to raise_error(Sass::SyntaxError, "No image 'bar' found in sprite map 'test'.")
22
31
  end
23
32
  end
24
33
 
25
34
  describe '#spritely_url' do
26
- before do
27
- asset_url_module = Module.new do
28
- def asset_url(path)
29
- path
30
- end
31
- end
32
- ::Sass::Script::Functions.send(:include, asset_url_module)
33
- end
34
-
35
- after do
36
- ::Sass::Script::Functions.send(:undef_method, :asset_url)
37
- end
35
+ subject { evaluate("spritely-url(spritely-map('test/*.png'))") }
38
36
 
39
- it "should use Rails' built-in asset_url function" do
40
- expect(evaluate("spritely-url(spritely-map('test/*.png'))")).to eq('sprites/test.png')
41
- end
37
+ it { should eq('url(sprites/test.png)') }
42
38
  end
43
39
 
44
40
  describe '#spritely_position' do
45
- let(:image) { double(left: 10, top: 12) }
46
-
47
41
  subject { evaluate("spritely-position(spritely-map('test/*.png'), 'bar')") }
48
42
 
49
- include_examples "a sprite function that checks image existence"
43
+ it_should_behave_like "a sprite function that checks image existence"
50
44
 
51
45
  it { should eq('-10px -12px') }
52
46
 
@@ -57,22 +51,26 @@ describe Spritely::SassFunctions do
57
51
  end
58
52
  end
59
53
 
60
- describe '#spritely_width' do
61
- let(:image) { double(width: 25) }
54
+ describe '#spritely_background' do
55
+ subject { evaluate("spritely-background(spritely-map('test/*.png'), 'bar')") }
56
+
57
+ it_should_behave_like "a sprite function that checks image existence"
62
58
 
59
+ it { should eq('url(sprites/test.png) -10px -12px') }
60
+ end
61
+
62
+ describe '#spritely_width' do
63
63
  subject { evaluate("spritely-width(spritely-map('test/*.png'), 'bar')") }
64
64
 
65
- include_examples "a sprite function that checks image existence"
65
+ it_should_behave_like "a sprite function that checks image existence"
66
66
 
67
67
  it { should eq('25px') }
68
68
  end
69
69
 
70
70
  describe '#spritely_height' do
71
- let(:image) { double(height: 50) }
72
-
73
71
  subject { evaluate("spritely-height(spritely-map('test/*.png'), 'bar')") }
74
72
 
75
- include_examples "a sprite function that checks image existence"
73
+ it_should_behave_like "a sprite function that checks image existence"
76
74
 
77
75
  it { should eq('50px') }
78
76
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spritely
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Robbin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-05 00:00:00.000000000 Z
11
+ date: 2014-06-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chunky_png