spritely 0.3.2 → 1.0.0

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.
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