spritely 0.3.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/spritely.rb +11 -34
  3. data/lib/spritely/collection.rb +12 -11
  4. data/lib/spritely/engine.rb +4 -0
  5. data/lib/spritely/generators/base.rb +0 -16
  6. data/lib/spritely/generators/chunky_png.rb +1 -4
  7. data/lib/spritely/image_set.rb +2 -2
  8. data/lib/spritely/sass_functions.rb +30 -29
  9. data/lib/spritely/sprite_map.rb +14 -31
  10. data/lib/spritely/sprockets/preprocessor.rb +65 -0
  11. data/lib/spritely/sprockets/transformer.rb +43 -0
  12. data/lib/spritely/version.rb +1 -1
  13. data/spec/fixtures/rails-app-changes/app/assets/stylesheets/sprites.css.scss +10 -18
  14. data/spec/fixtures/rails-app/app/assets/images/sprites/application.png.sprite +5 -0
  15. data/spec/fixtures/rails-app/app/assets/images/sprites/foo.png.sprite +1 -0
  16. data/spec/fixtures/rails-app/app/assets/stylesheets/sprites.css.scss +6 -14
  17. data/spec/fixtures/rails-app/app/assets/stylesheets/sprites_2.css.scss +4 -6
  18. data/spec/integration/precompilation_spec.rb +2 -14
  19. data/spec/spec_helper.rb +0 -4
  20. data/spec/spritely/collection_spec.rb +9 -8
  21. data/spec/spritely/generators/chunky_png_spec.rb +8 -13
  22. data/spec/spritely/image_set_spec.rb +2 -8
  23. data/spec/spritely/sass_functions_spec.rb +38 -37
  24. data/spec/spritely/sprite_map_spec.rb +16 -52
  25. data/spec/spritely/sprockets/preprocessor_spec.rb +33 -0
  26. data/spec/support/rails_app_helpers.rb +4 -5
  27. metadata +38 -49
  28. data/lib/generators/spritely/install_generator.rb +0 -17
  29. data/lib/spritely/adapters/sprockets_2.rb +0 -13
  30. data/lib/spritely/adapters/sprockets_3.rb +0 -11
  31. data/lib/spritely/cache.rb +0 -51
  32. data/lib/spritely/options.rb +0 -59
  33. data/lib/spritely/sprockets/manifest.rb +0 -20
  34. data/spec/generators/spritely/install_generator_spec.rb +0 -28
  35. data/spec/spritely/cache_spec.rb +0 -24
  36. data/spec/spritely/options_spec.rb +0 -29
  37. data/spec/spritely_spec.rb +0 -41
  38. data/spec/support/shared_examples.rb +0 -40
  39. data/spec/support/shared_examples/sprockets_2_sass_functions_helpers.rb +0 -13
  40. data/spec/support/shared_examples/sprockets_3_sass_functions_helpers.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a37a86c16713d282bc5d617faa6ce4bc2652d66c
4
- data.tar.gz: 82a0dddc10e04adb99a4525d15c1ecbb4ae0ab6f
3
+ metadata.gz: b1afa4634c2ebe161a2557ad31168f308f4fd4de
4
+ data.tar.gz: 176498f2a2579987e8c8ac4c12cc960e1ceba4b5
5
5
  SHA512:
6
- metadata.gz: ac584c201e6875fd894a8f568e3063e627f637de133d834d2d750e35db16c9532ba8c295623db08f1150629bbb6e96f6d40490daaa75fc4a0f602dcd44b510f8
7
- data.tar.gz: a7a3991b7318105394797151922c890d31b7674bc328e5e33d4b840d24f11312f11bc1650696deb75cbcd21d7fa01def0c8e47ddb6d019166ee174efc01d9717
6
+ metadata.gz: 95d85bb25e1761b8223f6fc2bc2aeb50a9e3c8f7cb91e15842b5f767d7b8545fe330dfdec6f39b9919107d82b83ccae1eee973863f15a47dbad28f74aae8d78a
7
+ data.tar.gz: 6709371eaedb1db506fa2ab023f197e978e477f0017a1100e3f2337663b64c86c32e39250109cd3b91fedfce315259dd060b14c045202051f6245fc3cf70ecc5
@@ -1,38 +1,15 @@
1
- require 'sass'
2
- require 'sprockets/rails/version'
3
- require 'sprockets/railtie'
4
- require 'sprockets/version'
1
+ require 'sprockets'
5
2
  require 'spritely/sass_functions'
6
- require 'spritely/sprockets/manifest'
7
- require 'spritely/adapters/sprockets_2'
8
- require 'spritely/adapters/sprockets_3'
3
+ require 'spritely/sprockets/preprocessor'
4
+ require 'spritely/sprockets/transformer'
9
5
 
10
- module Spritely
11
- def self.environment
12
- if sprockets_rails_version == 3
13
- ::Rails.application.assets || ::Sprockets::Railtie.build_environment(::Rails.application)
14
- else
15
- ::Rails.application.assets
16
- end
17
- end
18
-
19
- def self.directory
20
- ::Rails.root.join(relative_folder_path)
21
- end
22
-
23
- def self.relative_folder_path
24
- Pathname.new(File.join('app', 'assets', 'images', 'sprites'))
25
- end
26
-
27
- def self.sprockets_version
28
- Gem::Version.new(::Sprockets::VERSION).segments.first
29
- end
30
-
31
- def self.sprockets_rails_version
32
- Gem::Version.new(::Sprockets::Rails::VERSION).segments.first
33
- end
6
+ if defined?(::Rails::Engine)
7
+ require 'spritely/engine'
8
+ end
34
9
 
35
- def self.sprockets_adapter
36
- Adapters.const_get("Sprockets#{sprockets_version}").new
37
- end
10
+ module Spritely
38
11
  end
12
+
13
+ ::Sprockets.register_mime_type 'text/sprite', extensions: ['.png.sprite']
14
+ ::Sprockets.register_preprocessor 'text/sprite', Spritely::Sprockets::Preprocessor.new(comments: ["//", ["/*", "*/"]])
15
+ ::Sprockets.register_transformer 'text/sprite', 'image/png', Spritely::Sprockets::Transformer
@@ -21,6 +21,7 @@ module Spritely
21
21
  def cache_key
22
22
  files.collect { |file| Digest::MD5.file(file) }.join
23
23
  end
24
+ alias_method :to_s, :cache_key
24
25
 
25
26
  # Returns the width of the to-be-generated sprite image. When none of the
26
27
  # images repeat, it is simply the max width of all images in the sprite.
@@ -29,17 +30,13 @@ module Spritely
29
30
  # multiple is then multiplied by the minimum multiple that will result in a
30
31
  # value greater than or equal to the max width of all images in the sprite.
31
32
  def width
32
- return @width if @width
33
-
34
- max_width = image_sets.collect(&:width).max
35
- if image_sets.none?(&:repeated?)
36
- @width = max_width
33
+ @width ||= if image_sets.none?(&:repeated?)
34
+ max_width
37
35
  else
38
- @width = lcm = image_sets.select(&:repeated?).collect(&:width).reduce(:lcm)
39
- @width += lcm while @width < max_width
40
- end
36
+ lcm = image_sets.select(&:repeated?).collect(&:width).reduce(:lcm)
41
37
 
42
- @width
38
+ lcm * (max_width / lcm.to_f).ceil
39
+ end
43
40
  end
44
41
 
45
42
  def height
@@ -58,11 +55,15 @@ module Spritely
58
55
  private
59
56
 
60
57
  def image_sets
61
- @image_sets ||= files.collect { |file| ImageSet.new(file, options[File.basename(file, ".png")]) }
58
+ @image_sets ||= files.collect { |file| ImageSet.new(file, options[:images][File.basename(file, ".png")] || options[:global]) }
59
+ end
60
+
61
+ def max_width
62
+ @max_width ||= image_sets.collect(&:width).max
62
63
  end
63
64
 
64
65
  def heights
65
- image_sets.collect(&:outer_height)
66
+ @heights ||= image_sets.collect(&:outer_height)
66
67
  end
67
68
  end
68
69
  end
@@ -0,0 +1,4 @@
1
+ module Spritely
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -1,25 +1,9 @@
1
1
  module Spritely
2
2
  module Generators
3
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
- raise("'#{Spritely.relative_folder_path}' doesn't exist. Run `rails generate spritely:install`.") unless File.exist?(Spritely.directory)
14
- end
15
-
16
4
  def build!
17
5
  raise NotImplementedError, "#{self.class} must implement #build!"
18
6
  end
19
-
20
- def save!
21
- raise NotImplementedError, "#{self.class} must implement #save!"
22
- end
23
7
  end
24
8
  end
25
9
  end
@@ -9,11 +9,8 @@ module Spritely
9
9
  png = ::ChunkyPNG::Image.from_blob(image.data)
10
10
  canvas.replace!(png, image.left, image.top)
11
11
  end
12
- end
13
12
 
14
- def save!
15
- canvas.metadata['cache_key'] = sprite_map.cache_key
16
- canvas.save(sprite_map.filename, :fast_rgba)
13
+ canvas.to_blob(:fast_rgba)
17
14
  end
18
15
 
19
16
  private
@@ -28,11 +28,11 @@ module Spritely
28
28
  end
29
29
 
30
30
  def spacing
31
- options[:spacing] || 0
31
+ options[:spacing].to_i
32
32
  end
33
33
 
34
34
  def repeated?
35
- !!options[:repeat]
35
+ options[:repeat] == 'true'
36
36
  end
37
37
 
38
38
  def right?
@@ -1,28 +1,17 @@
1
- require 'spritely/sprite_map'
1
+ require 'sass'
2
2
 
3
3
  module Spritely
4
4
  module SassFunctions
5
- def spritely_map(glob, kwargs = {})
6
- SpriteMap.create(glob.value, kwargs).tap do |sprite_map|
7
- Spritely.sprockets_adapter.reset_cache!(sprockets_environment, sprite_map.filename)
8
- sprockets_context.depend_on(Spritely.directory)
9
- sprite_map.files.each do |file|
10
- sprockets_context.depend_on(file)
11
- sprockets_context.depend_on_asset(file)
12
- end
13
- end
14
- end
15
-
16
- ::Sass::Script::Functions.declare :spritely_map, [:glob], var_kwargs: true
5
+ def spritely_url(sprite_name)
6
+ sprockets_context.link_asset("sprites/#{sprite_name.value}.png")
17
7
 
18
- def spritely_url(sprite_map)
19
- asset_url(Sass::Script::String.new("sprites/#{sprite_map.name}.png"))
8
+ asset_url(Sass::Script::String.new("sprites/#{sprite_name.value}.png"))
20
9
  end
21
10
 
22
- ::Sass::Script::Functions.declare :spritely_url, [:sprite_map]
11
+ ::Sass::Script::Functions.declare :spritely_url, [:sprite_name]
23
12
 
24
- def spritely_position(sprite_map, image_name)
25
- image = find_image(sprite_map, image_name)
13
+ def spritely_position(sprite_name, image_name)
14
+ image = find_image(sprite_name, image_name)
26
15
 
27
16
  x = Sass::Script::Number.new(-image.left, image.left == 0 ? [] : ['px'])
28
17
  y = Sass::Script::Number.new(-image.top, image.top == 0 ? [] : ['px'])
@@ -30,35 +19,47 @@ module Spritely
30
19
  Sass::Script::List.new([x, y], :space)
31
20
  end
32
21
 
33
- ::Sass::Script::Functions.declare :spritely_position, [:sprite_map, :image_name]
22
+ ::Sass::Script::Functions.declare :spritely_position, [:sprite_name, :image_name]
34
23
 
35
- def spritely_background(sprite_map, image_name)
36
- Sass::Script::List.new([spritely_url(sprite_map), spritely_position(sprite_map, image_name)], :space)
24
+ def spritely_background(sprite_name, image_name)
25
+ Sass::Script::List.new([spritely_url(sprite_name), spritely_position(sprite_name, image_name)], :space)
37
26
  end
38
27
 
39
- ::Sass::Script::Functions.declare :spritely_background, [:sprite_map, :image_name]
28
+ ::Sass::Script::Functions.declare :spritely_background, [:sprite_name, :image_name]
40
29
 
41
- def spritely_width(sprite_map, image_name)
42
- image = find_image(sprite_map, image_name)
30
+ def spritely_width(sprite_name, image_name)
31
+ image = find_image(sprite_name, image_name)
43
32
 
44
33
  Sass::Script::Number.new(image.width, ['px'])
45
34
  end
46
35
 
47
- ::Sass::Script::Functions.declare :spritely_width, [:sprite_map, :image_name]
36
+ ::Sass::Script::Functions.declare :spritely_width, [:sprite_name, :image_name]
48
37
 
49
- def spritely_height(sprite_map, image_name)
50
- image = find_image(sprite_map, image_name)
38
+ def spritely_height(sprite_name, image_name)
39
+ image = find_image(sprite_name, image_name)
51
40
 
52
41
  Sass::Script::Number.new(image.height, ['px'])
53
42
  end
54
43
 
55
- ::Sass::Script::Functions.declare :spritely_height, [:sprite_map, :image_name]
44
+ ::Sass::Script::Functions.declare :spritely_height, [:sprite_name, :image_name]
56
45
 
57
46
  private
58
47
 
59
- def find_image(sprite_map, image_name)
48
+ def find_image(sprite_name, image_name)
49
+ sprockets_context.link_asset("sprites/#{sprite_name.value}.png")
50
+
51
+ sprite_map = sprite_maps.fetch(sprite_name.value) do |name|
52
+ asset = sprockets_environment.find_asset("sprites/#{name}.png.sprite") || raise(Sass::SyntaxError, "No sprite map '#{name}' found.")
53
+
54
+ sprite_maps[name] = SpriteMap.new(name, sprockets_environment, asset.metadata[:sprite_directives])
55
+ end
56
+
60
57
  sprite_map.find(image_name.value) || raise(Sass::SyntaxError, "No image '#{image_name.value}' found in sprite map '#{sprite_map.name}'.")
61
58
  end
59
+
60
+ def sprite_maps
61
+ @sprite_maps ||= {}
62
+ end
62
63
  end
63
64
  end
64
65
 
@@ -1,58 +1,41 @@
1
1
  require 'forwardable'
2
- require 'spritely/options'
3
- require 'spritely/cache'
2
+ require 'digest/md5'
4
3
  require 'spritely/collection'
5
4
  require 'spritely/generators/chunky_png'
6
5
 
7
6
  module Spritely
8
- class SpriteMap < Sass::Script::Literal
7
+ class SpriteMap
9
8
  extend Forwardable
10
9
 
11
10
  def_delegators :collection, :find, :width, :height, :images
12
11
 
13
- attr_reader :glob, :options
12
+ attr_reader :name, :glob, :environment, :options
14
13
 
15
- def self.create(*args)
16
- new(*args).tap do |sprite_map|
17
- sprite_map.generate! if sprite_map.needs_generation?
18
- end
14
+ def initialize(name, environment, options)
15
+ @name = name
16
+ @glob = [name, "*.png"].join("/")
17
+ @environment = environment
18
+ @options = options
19
19
  end
20
20
 
21
- def initialize(glob, options = {})
22
- @glob = glob
23
- @options = Options.new(options)
21
+ def inspect
22
+ "#<Spritely::SpriteMap name=#{name} options=#{options}>"
24
23
  end
25
24
 
26
25
  def cache_key
27
- @cache_key ||= Cache.generate(options, collection)
28
- end
29
-
30
- def inspect
31
- "#<Spritely::SpriteMap name=#{name} options=#{options}>"
26
+ @cache_key ||= Digest::MD5.hexdigest([options, collection].join)
32
27
  end
33
28
 
34
29
  def collection
35
30
  @collection ||= Collection.create(files, options)
36
31
  end
37
32
 
38
- def generate!
39
- Generators::ChunkyPng.create!(self)
40
- end
41
-
42
- def name
43
- glob.split('/')[0..-2].join('-')
44
- end
45
-
46
- def filename
47
- Spritely.directory.join("#{name}.png")
48
- end
49
-
50
- def needs_generation?
51
- !File.exist?(filename) || Cache.busted?(filename, cache_key)
33
+ def save!
34
+ Generators::ChunkyPng.new(self).build!
52
35
  end
53
36
 
54
37
  def files
55
- Spritely.environment.paths.flat_map { |path| Dir.glob(File.join(path, glob)) }.sort
38
+ environment.paths.flat_map { |path| Dir.glob(File.join(path, glob)) }.sort
56
39
  end
57
40
  end
58
41
  end
@@ -0,0 +1,65 @@
1
+ require 'sprockets/directive_processor'
2
+
3
+ module Spritely
4
+ module Sprockets
5
+ # Converts Sprockets directives from this:
6
+ #
7
+ # //= repeat arrow true
8
+ # //= spacing arrow 10
9
+ # //= position another-image right
10
+ # //= spacing 5
11
+ #
12
+ # To this:
13
+ #
14
+ # {
15
+ # global: { spacing: 5 },
16
+ # images: {
17
+ # 'arrow' => { repeat: 'true', spacing: '10' },
18
+ # 'another-image' => { position: 'right', spacing: '5' }
19
+ # }
20
+ # }
21
+ class Preprocessor < ::Sprockets::DirectiveProcessor
22
+ GLOBAL_DIRECTIVES = %w(position spacing).freeze
23
+ IMAGE_DIRECTIVES = %w(repeat position spacing).freeze
24
+
25
+ def _call(input)
26
+ @sprite_directives = { global: {}, images: {} }
27
+
28
+ super.tap do
29
+ merge_global_options!
30
+
31
+ input[:metadata][:sprite_directives] = @sprite_directives
32
+ end
33
+ end
34
+
35
+ (GLOBAL_DIRECTIVES + IMAGE_DIRECTIVES).uniq.each do |directive|
36
+ define_method("process_#{directive}_directive") do |image_or_value, value_or_nil = nil|
37
+ if value_or_nil
38
+ process_image_option(directive, image_or_value, value_or_nil)
39
+ else
40
+ process_global_option(directive, image_or_value)
41
+ end
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def process_image_option(directive, image, value)
48
+ @sprite_directives[:images][image] ||= {}
49
+ @sprite_directives[:images][image][directive.to_sym] = value
50
+ end
51
+
52
+ def process_global_option(directive, value)
53
+ raise ArgumentError, "'#{directive}' is not a valid global option" unless GLOBAL_DIRECTIVES.include?(directive)
54
+
55
+ @sprite_directives[:global][directive.to_sym] = value
56
+ end
57
+
58
+ def merge_global_options!
59
+ @sprite_directives[:images].each do |image, options|
60
+ options.merge!(@sprite_directives[:global]) { |key, left, right| left }
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,43 @@
1
+ require 'spritely/version'
2
+ require 'spritely/sprite_map'
3
+
4
+ module Spritely
5
+ module Sprockets
6
+ class Transformer < Struct.new(:input)
7
+ def self.call(input)
8
+ new(input).call
9
+ end
10
+
11
+ def self.cache_key
12
+ @cache_key ||= "#{name}:#{Spritely::VERSION}".freeze
13
+ end
14
+
15
+ def call
16
+ data = cache.fetch([self.class.cache_key, input[:name], sprite_map.cache_key]) do
17
+ sprite_map.files.each do |file|
18
+ context.depend_on(File.dirname(file))
19
+ context.link_asset(file)
20
+ end
21
+
22
+ sprite_map.save!
23
+ end
24
+
25
+ context.metadata.merge(data: data)
26
+ end
27
+
28
+ private
29
+
30
+ def context
31
+ @context ||= input[:environment].context_class.new(input)
32
+ end
33
+
34
+ def cache
35
+ @cache ||= input[:cache]
36
+ end
37
+
38
+ def sprite_map
39
+ @sprite_map ||= SpriteMap.new(input[:name].remove("sprites/"), input[:environment], input[:metadata][:sprite_directives])
40
+ end
41
+ end
42
+ end
43
+ end