spritely 0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d722c5631f45aecb25c733b67c27477cabb3087b
4
+ data.tar.gz: cacdcf82d6ec8c69a7916443c076ec2cb3f6471d
5
+ SHA512:
6
+ metadata.gz: 47639393b85c3b0a5bf61103de2d2ccbfd68fc9facae1a21e97b1c492f71edb6e3374e2379cf5af5f48e03605475da54f2caea7500ff001c5039d1bdbf413bdd
7
+ data.tar.gz: d2e6d843569997d59e82ee7228dea3cd494b2f7e0ad2dba5d2313c6ff34026dd30e3fe97154224a95d9c6f4cd0b10306e1e3482b9ea44b5403d2535038cf20f0
@@ -0,0 +1,25 @@
1
+ module Spritely
2
+ module Generators
3
+ class Base < Struct.new(:sprite_map)
4
+ def self.create!(sprite_map)
5
+ new(sprite_map).tap do |generator|
6
+ generator.build!
7
+ generator.ensure_directory_exists!
8
+ generator.save!
9
+ end
10
+ end
11
+
12
+ def ensure_directory_exists!
13
+ FileUtils.mkdir_p(Spritely.directory)
14
+ end
15
+
16
+ def build!
17
+ raise NotImplementedError, "#{self.class} must implement #build!"
18
+ end
19
+
20
+ def save!
21
+ raise NotImplementedError, "#{self.class} must implement #save!"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ require 'spritely/generators/base'
2
+ require 'chunky_png'
3
+
4
+ module Spritely
5
+ module Generators
6
+ class ChunkyPng < Spritely::Generators::Base
7
+ def build!
8
+ sprite_map.images.each do |image|
9
+ png = ::ChunkyPNG::Image.from_blob(image.data)
10
+ canvas.replace!(png, image.left, image.top)
11
+ end
12
+ end
13
+
14
+ def save!
15
+ canvas.save(sprite_map.filename, :fast_rgba)
16
+ end
17
+
18
+ private
19
+
20
+ def canvas
21
+ @canvas ||= ::ChunkyPNG::Image.new(sprite_map.images.max_width, sprite_map.images.total_height)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,20 @@
1
+ module Spritely
2
+ class Image
3
+ attr_accessor :top
4
+ attr_reader :path, :data, :width, :height
5
+
6
+ def initialize(path)
7
+ @path = path
8
+ @data = File.read(path)
9
+ @width, @height = data[0x10..0x18].unpack('NN')
10
+ end
11
+
12
+ def left
13
+ 0
14
+ end
15
+
16
+ def name
17
+ File.basename(path, ".png")
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,41 @@
1
+ require 'spritely/image'
2
+
3
+ module Spritely
4
+ class ImagesSet
5
+ extend Forwardable
6
+
7
+ def_delegator :@images, :each
8
+
9
+ attr_reader :files, :images
10
+
11
+ def initialize(files)
12
+ @files = files
13
+ @images = files.collect { |file| Image.new(file) }.sort_by(&:width).reverse
14
+ position!
15
+ end
16
+
17
+ def find(name)
18
+ images.find { |image| image.name == name }
19
+ end
20
+
21
+ def max_width
22
+ @max_width ||= images.collect(&:width).max
23
+ end
24
+
25
+ def total_height
26
+ @total_height ||= images.collect(&:height).reduce(:+)
27
+ end
28
+
29
+ def last_modification_time
30
+ files.collect { |file| Spritely.modification_time(file) }.max
31
+ end
32
+
33
+ private
34
+
35
+ def position!
36
+ images.each_with_index do |image, index|
37
+ image.top = images.collect(&:height)[0..index].reduce(:+) - image.height
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,44 @@
1
+ require 'spritely/sprite_map'
2
+
3
+ module Spritely
4
+ module SassFunctions
5
+ def sprite_map(glob)
6
+ SpriteMap.create(glob)
7
+ end
8
+
9
+ ::Sass::Script::Functions.declare :sprite_map, [:glob]
10
+
11
+ def sprite_url(sprite_map)
12
+ asset_url(Sass::Script::String.new("sprites/#{sprite_map.name}.png"))
13
+ end
14
+
15
+ ::Sass::Script::Functions.declare :sprite_url, [:sprite_map]
16
+
17
+ def sprite_position(sprite_map, image_name)
18
+ image = sprite_map.images.find(image_name.value)
19
+
20
+ x = Sass::Script::Number.new(image.left, image.left == 0 ? [] : ['px'])
21
+ y = Sass::Script::Number.new(-image.top, image.top == 0 ? [] : ['px'])
22
+
23
+ Sass::Script::List.new([x, y], :space)
24
+ end
25
+
26
+ ::Sass::Script::Functions.declare :sprite_position, [:sprite_map, :image_name]
27
+
28
+ def sprite_width(sprite_map, image_name)
29
+ image = sprite_map.images.find(image_name.value)
30
+
31
+ Sass::Script::Number.new(image.width, ['px'])
32
+ end
33
+
34
+ ::Sass::Script::Functions.declare :sprite_width, [:sprite_map, :image_name]
35
+
36
+ def sprite_height(sprite_map, image_name)
37
+ image = sprite_map.images.find(image_name.value)
38
+
39
+ Sass::Script::Number.new(image.height, ['px'])
40
+ end
41
+
42
+ ::Sass::Script::Functions.declare :sprite_height, [:sprite_map, :image_name]
43
+ end
44
+ end
@@ -0,0 +1,53 @@
1
+ require 'spritely/images_set'
2
+ require 'spritely/generators/chunky_png'
3
+
4
+ module Spritely
5
+ class SpriteMap
6
+ attr_accessor :options # Sass expects an object returned from a script function to have an #options accessor
7
+ attr_reader :glob
8
+
9
+ def self.create(*args)
10
+ new(*args).tap do |sprite_map|
11
+ sprite_map.generate! if sprite_map.needs_generation?
12
+ end
13
+ end
14
+
15
+ def initialize(glob)
16
+ @glob = glob.value
17
+ end
18
+
19
+ def images
20
+ @images ||= ImagesSet.new(files)
21
+ end
22
+
23
+ def generate!
24
+ Generators::ChunkyPng.create!(self)
25
+ end
26
+
27
+ def name
28
+ glob.split('/')[0..-2].join('-')
29
+ end
30
+
31
+ def filename
32
+ Spritely.directory.join("#{name}.png")
33
+ end
34
+
35
+ def needs_generation?
36
+ !File.exist?(filename) || outdated?
37
+ end
38
+
39
+ private
40
+
41
+ def files
42
+ Spritely.environment.paths.flat_map { |path| Dir.glob(File.join(path, glob)) }
43
+ end
44
+
45
+ def outdated?
46
+ images.last_modification_time > modification_time
47
+ end
48
+
49
+ def modification_time
50
+ Spritely.modification_time(filename)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module Spritely
2
+ VERSION = "0.0.1"
3
+ end
data/lib/spritely.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'sass'
2
+ require 'spritely/sass_functions'
3
+
4
+ raise LoadError, "Spritely cannot be used in conjunction with Compass. Hope you choose Spritely!" if defined?(::Compass)
5
+
6
+ module Spritely
7
+ def self.environment
8
+ ::Rails.application.assets
9
+ end
10
+
11
+ def self.directory
12
+ ::Rails.root.join('app', 'assets', 'images', 'sprites')
13
+ end
14
+
15
+ def self.modification_time(filename)
16
+ File.mtime(filename).to_i
17
+ end
18
+ end
19
+
20
+ ::Sass::Script::Functions.send(:include, Spritely::SassFunctions)
Binary file
@@ -0,0 +1,5 @@
1
+ require 'rspec'
2
+ require 'pry-byebug'
3
+ require 'spritely'
4
+
5
+ Dir["./spec/support/**/*.rb"].sort.each {|f| require f}
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+ require 'ostruct'
3
+
4
+ describe Spritely::Generators::ChunkyPng do
5
+ let(:png_canvas) { double }
6
+
7
+ subject { Spritely::Generators::ChunkyPng.new(sprite_map) }
8
+
9
+ include_examples "a generator"
10
+
11
+ before { allow(::ChunkyPNG::Image).to receive(:new).with(100, 200).and_return(png_canvas) }
12
+
13
+ describe '#build!' do
14
+ before do
15
+ allow(::ChunkyPNG::Image).to receive(:from_blob).with('first image data').and_return('first image chunk')
16
+ allow(::ChunkyPNG::Image).to receive(:from_blob).with('second image data').and_return('second image chunk')
17
+ end
18
+
19
+ it 'should append each image to the PNG canvas' do
20
+ expect(png_canvas).to receive(:replace!).with('first image chunk', 1, 10)
21
+ expect(png_canvas).to receive(:replace!).with('second image chunk', 2, 20)
22
+ subject.build!
23
+ end
24
+ end
25
+
26
+ describe '#save!' do
27
+ it 'should save the PNG canvas' do
28
+ expect(png_canvas).to receive(:save).with('blah.png', :fast_rgba)
29
+ subject.save!
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spritely::Image do
4
+ let(:path) { "#{__dir__}/../fixtures/test/foo.png" }
5
+
6
+ subject { Spritely::Image.new(path) }
7
+
8
+ its(:path) { should eq(path) }
9
+ its(:data) { should eq(File.read(path)) }
10
+ its(:width) { should eq(1) }
11
+ its(:height) { should eq(1) }
12
+ its(:left) { should eq(0) }
13
+ its(:name) { should eq('foo') }
14
+
15
+ describe '#top' do
16
+ before { subject.top = 123 }
17
+
18
+ its(:top) { should eq(123) }
19
+ end
20
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+ require 'ostruct'
3
+
4
+ describe Spritely::ImagesSet do
5
+ class ImageDouble < OpenStruct
6
+ attr_accessor :top
7
+ end
8
+
9
+ let(:first_image) { ImageDouble.new(name: 'foo', width: 1, height: 10) }
10
+ let(:second_image) { ImageDouble.new(name: 'bar', width: 100, height: 100) }
11
+
12
+ before do
13
+ allow(Spritely::Image).to receive(:new).with('first').and_return(first_image)
14
+ allow(Spritely::Image).to receive(:new).with('second').and_return(second_image)
15
+ end
16
+
17
+ subject! { Spritely::ImagesSet.new(['first', 'second']) }
18
+
19
+ its(:files) { should eq(['first', 'second']) }
20
+ its(:images) { should eq([second_image, first_image]) }
21
+ its(:max_width) { should eq(100) }
22
+ its(:total_height) { should eq(110) }
23
+
24
+ describe 'positioning' do
25
+ it 'should set the #top position of each image' do
26
+ expect(first_image.top).to eq(100)
27
+ expect(second_image.top).to eq(0)
28
+ end
29
+ end
30
+
31
+ describe '#each delegation' do
32
+ it 'should pass along the #each method call to the internal #images array' do
33
+ expect(first_image).to receive(:blah!)
34
+ expect(second_image).to receive(:blah!)
35
+ subject.each(&:blah!)
36
+ end
37
+ end
38
+
39
+ describe '#find' do
40
+ it 'should find the correct image by name' do
41
+ expect(subject.find('foo')).to eq(first_image)
42
+ expect(subject.find('bar')).to eq(second_image)
43
+ end
44
+ end
45
+
46
+ describe '#last_modification_time' do
47
+ before do
48
+ allow(Spritely).to receive(:modification_time).with('first').and_return(10)
49
+ allow(Spritely).to receive(:modification_time).with('second').and_return(100)
50
+ end
51
+
52
+ its(:last_modification_time) { should eq(100) }
53
+ end
54
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spritely::SpriteMap do
4
+ subject { Spritely::SpriteMap.new(double(value: 'test/*.png')) }
5
+
6
+ before { Spritely.stub(:directory).and_return(File) }
7
+
8
+ its(:glob) { should eq('test/*.png') }
9
+ its(:name) { should eq('test') }
10
+ its(:filename) { should eq('test.png') }
11
+
12
+ describe '.create' do
13
+ let(:sprite_map) { double(needs_generation?: true) }
14
+
15
+ it 'should attempt to generate the sprite' do
16
+ allow(Spritely::SpriteMap).to receive(:new).with('test/*.png').and_return(sprite_map)
17
+ expect(sprite_map).to receive(:generate!)
18
+ Spritely::SpriteMap.create('test/*.png')
19
+ end
20
+ end
21
+
22
+ describe '#images' do
23
+ before do
24
+ Spritely.stub_chain(:environment, :paths).and_return(["#{__dir__}/../fixtures"])
25
+ allow(Spritely::ImagesSet).to receive(:new).with(["#{__dir__}/../fixtures/test/foo.png"]).and_return('images set')
26
+ end
27
+
28
+ its(:images) { should eq('images set') }
29
+ end
30
+
31
+ describe '#generate!' do
32
+ it 'should start the ChunkyPNG generator' do
33
+ expect(Spritely::Generators::ChunkyPng).to receive(:create!).with(subject)
34
+ subject.generate!
35
+ end
36
+ end
37
+
38
+ describe '#needs_generation?' do
39
+ let(:file_exists) { false }
40
+
41
+ before { allow(File).to receive(:exist?).with('test.png').and_return(file_exists) }
42
+
43
+ its(:needs_generation?) { should be_true }
44
+
45
+ context 'the sprite file already exists' do
46
+ let(:file_exists) { true }
47
+
48
+ before do
49
+ subject.stub_chain(:images, :last_modification_time).and_return(456)
50
+ allow(Spritely).to receive(:modification_time).and_return(123)
51
+ end
52
+
53
+ its(:needs_generation?) { should be_true }
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spritely do
4
+ describe '.environment' do
5
+ before { stub_const('::Rails', double(application: double(assets: 'assets environment'))) }
6
+
7
+ its(:environment) { should eq('assets environment') }
8
+ end
9
+
10
+ describe '.directory' do
11
+ before { stub_const('::Rails', double(root: File)) }
12
+
13
+ its(:directory) { should eq('app/assets/images/sprites') }
14
+ end
15
+
16
+ describe '.modification_time' do
17
+ before { allow(File).to receive(:mtime).with('foo').and_return('123') }
18
+
19
+ it 'should give us the modification time' do
20
+ expect(Spritely.modification_time('foo')).to eq(123)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,39 @@
1
+ shared_examples "a generator" do
2
+ class ImagesDouble
3
+ def max_width; 100; end
4
+ def total_height; 200; end
5
+
6
+ def each(&block)
7
+ [
8
+ OpenStruct.new(data: 'first image data', left: 1, top: 10),
9
+ OpenStruct.new(data: 'second image data', left: 2, top: 20)
10
+ ].each(&block)
11
+ end
12
+ end
13
+
14
+ let(:sprite_map) { double(images: ImagesDouble.new, filename: 'blah.png') }
15
+
16
+ its(:sprite_map) { should eq(sprite_map) }
17
+
18
+ describe '.create!' do
19
+ let(:generator) { double }
20
+
21
+ before { allow(described_class).to receive(:new).with(sprite_map).and_return(generator) }
22
+
23
+ it 'should work some magic' do
24
+ expect(generator).to receive(:build!)
25
+ expect(generator).to receive(:ensure_directory_exists!)
26
+ expect(generator).to receive(:save!)
27
+ described_class.create!(sprite_map)
28
+ end
29
+ end
30
+
31
+ describe '#ensure_directory_exists!' do
32
+ before { allow(Spritely).to receive(:directory).and_return('blah') }
33
+
34
+ it 'should mkdir -p' do
35
+ expect(FileUtils).to receive(:mkdir_p).with('blah')
36
+ subject.ensure_directory_exists!
37
+ end
38
+ end
39
+ end
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spritely
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Alex Robbin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: chunky_png
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sass
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sprockets-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email:
99
+ - alex@robbinsweb.biz
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - lib/spritely.rb
105
+ - lib/spritely/generators/base.rb
106
+ - lib/spritely/generators/chunky_png.rb
107
+ - lib/spritely/image.rb
108
+ - lib/spritely/images_set.rb
109
+ - lib/spritely/sass_functions.rb
110
+ - lib/spritely/sprite_map.rb
111
+ - lib/spritely/version.rb
112
+ - spec/fixtures/test/foo.png
113
+ - spec/spec_helper.rb
114
+ - spec/spritely/generators/chunky_png_spec.rb
115
+ - spec/spritely/image_spec.rb
116
+ - spec/spritely/images_set_spec.rb
117
+ - spec/spritely/sprite_map_spec.rb
118
+ - spec/spritely_spec.rb
119
+ - spec/support/shared_examples.rb
120
+ homepage: http://www.robbinsweb.biz
121
+ licenses:
122
+ - MIT
123
+ metadata: {}
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 2.2.2
141
+ signing_key:
142
+ specification_version: 4
143
+ summary: Hooks into the Rails asset pipeline to allow you to easily generate sprite
144
+ maps
145
+ test_files:
146
+ - spec/fixtures/test/foo.png
147
+ - spec/spec_helper.rb
148
+ - spec/spritely/generators/chunky_png_spec.rb
149
+ - spec/spritely/image_spec.rb
150
+ - spec/spritely/images_set_spec.rb
151
+ - spec/spritely/sprite_map_spec.rb
152
+ - spec/spritely_spec.rb
153
+ - spec/support/shared_examples.rb