antw-kin 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -44,7 +44,7 @@ module Kin
44
44
  # @api public
45
45
  #
46
46
  def display_navigation(name, options = {})
47
- (options[:formatter] || Merb::Plugins.config[:kin][:nav_formatter]).new(
47
+ (options[:formatter] || Kin::Nav.get(name).formatter).new(
48
48
  Kin::Nav.get(name), self, options
49
49
  ).to_html
50
50
  end
@@ -0,0 +1,100 @@
1
+ require 'rmagick'
2
+
3
+ module Kin
4
+ ##
5
+ # Takes 16x16 pixel icons and smushes them all together in to one sprite.
6
+ # Not auto-loaded, so remember to require 'kin/sprites' if you need it.
7
+ #
8
+ module Sprites
9
+ SpriteError = Class.new(StandardError)
10
+
11
+ ##
12
+ # A simple class which represents a list of icons (wrapper around an
13
+ # array).
14
+ #
15
+ class IconSet
16
+ include Enumerable
17
+
18
+ ##
19
+ # Creates a new IconSet instance.
20
+ #
21
+ # @param [Array<String>] icons
22
+ # An array of source icon files.
23
+ #
24
+ def initialize(icons)
25
+ @icons = icons.uniq
26
+ end
27
+
28
+ ##
29
+ # Returns the vertical offset of a given icon in a sprite.
30
+ #
31
+ # @param [String]
32
+ # The name of an icon which appears in the list.
33
+ #
34
+ # @return [Integer]
35
+ #
36
+ # @example
37
+ # list = IconSet.new(%w( one two three ))
38
+ # list.location_of('one') # => 0
39
+ # list.location_of('two') # => 40
40
+ # list.location_of('three') # => 80
41
+ #
42
+ # @raise [ArgumentError]
43
+ # Raises an ArgumentError if the given icon does not appear in the
44
+ # list.
45
+ #
46
+ def location_of(icon)
47
+ unless @icons.include?(icon)
48
+ raise ArgumentError, "Icon does not appear in the list: #{icon}"
49
+ end
50
+
51
+ @icons.index(icon) * 40
52
+ end
53
+
54
+ ##
55
+ # Loops through, and yields, each icon in turn.
56
+ #
57
+ # @yield [String] The icon name.
58
+ #
59
+ def each
60
+ @icons.each { |i| yield i }
61
+ end
62
+
63
+ ##
64
+ # Returns the number of icons in this set.
65
+ #
66
+ # @return [Integer]
67
+ #
68
+ def length
69
+ @icons.length
70
+ end
71
+
72
+ ##
73
+ # Returns a slice of the set. Note: This returns a Set, not an IconSet.
74
+ #
75
+ # @param [Numeric, Range] slice
76
+ # The element or range of elements to return.
77
+ #
78
+ # @return [Set]
79
+ #
80
+ def [](*slice)
81
+ @icons[*slice]
82
+ end
83
+
84
+ ##
85
+ # Returns a hash for the icon list. Assuming the list contains the same
86
+ # icons, and in the same order, the hash will always be the same.
87
+ #
88
+ # @return [String]
89
+ #
90
+ def hash
91
+ Digest::SHA256.hexdigest(@icons.join("|"))
92
+ end
93
+ end # IconSet
94
+ end # Sprites
95
+ end # Kin
96
+
97
+ sprites = File.expand_path(File.join(File.dirname(__FILE__), 'sprites'))
98
+
99
+ require File.join(sprites, 'image_generator')
100
+ require File.join(sprites, 'sass_generator')
@@ -0,0 +1,75 @@
1
+ module Kin
2
+ module Sprites
3
+ ##
4
+ # Responsible for taking source icon files and creating a sprite.
5
+ #
6
+ class ImageGenerator
7
+ IconNotReadable = Class.new(SpriteError)
8
+ TargetNotWriteable = Class.new(SpriteError)
9
+
10
+ ##
11
+ # Creates a new Generator instance.
12
+ #
13
+ # @param [IconSet] set
14
+ # An IconSet instance.
15
+ # @param [String] source_dir
16
+ # A path to the directory in which the source files reside.
17
+ #
18
+ # @api public
19
+ #
20
+ def initialize(set, source_dir)
21
+ @set, @source_dir = set, source_dir
22
+ end
23
+
24
+ ##
25
+ # Uses RMagick to create a transparent PNG containing the individual
26
+ # icons as a sprite.
27
+ #
28
+ # If a file already exists at the given path it will be overwritten.
29
+ #
30
+ # @param [String] path
31
+ # The path at which to save the sprite.
32
+ #
33
+ # @raise [Kin::Sprites::ImageGenerator::IconNotReadable]
34
+ # Raised when an icon could not be found.
35
+ #
36
+ # @raise [Kin::Sprites::ImageGenerator::TargetNotWriteable]
37
+ # Raised when RMagick couldn't save the image because the target path
38
+ # was not writeable.
39
+ #
40
+ # @raise [Magick::ImageMagickError]
41
+ # Raised if some other error occured with RMagick.
42
+ #
43
+ # @api public
44
+ #
45
+ def save(path)
46
+ list = @set.inject(Magick::ImageList.new) do |m, icon|
47
+ m << Magick::Image.read(File.join(@source_dir, "#{icon}.png"))[0]
48
+ end
49
+
50
+ # RMagick uses instance_eval, @set isn't available in the block below.
51
+ set_length = @set.length
52
+
53
+ montage = list.montage do
54
+ # Transparent background.
55
+ self.background_color = '#FFF0'
56
+ # Each icon is 16x16 with 12 pixels of top & bottom padding = 16x40.
57
+ self.geometry = Magick::Geometry.new(16, 16, 0, 12)
58
+ self.tile = Magick::Geometry.new(1, set_length)
59
+ end
60
+
61
+ # Remove the blank space from both the top and bottom of the image.
62
+ montage.crop!(0, 12, 0, (40 * set_length) - 24)
63
+ montage.write("PNG32:#{path}")
64
+ rescue Magick::ImageMagickError => ex
65
+ # Nicely rescue and re-raise if the target directory wasn't writable,
66
+ # or if one of the icon files could not be opened.
67
+ case ex.message
68
+ when /Permission denied/ then raise TargetNotWriteable, ex.message
69
+ when /unable to open/ then raise IconNotReadable, ex.message
70
+ else raise ex
71
+ end
72
+ end
73
+ end # ImageGenerator
74
+ end # Sprites
75
+ end # Kin
@@ -0,0 +1,179 @@
1
+ require 'digest/sha2'
2
+
3
+ module Kin
4
+ module Sprites
5
+ ##
6
+ # A Rake helper which generates all the sprites for a given application.
7
+ #
8
+ # RakeRunner expects a sprites.yml config file which defines the sprites
9
+ # and the icons in each sprite. The contents of each sprites are hashed
10
+ # and cached in a .hashes file (the the same directory as the saved
11
+ # sprites) so that re-running the task won't result in them being
12
+ # re-generated. See README for examples.
13
+ #
14
+ # Not auto-loaded by requiring either 'kin' or 'kin/sprites'; if you want
15
+ # this Rake helper you need to require it explicitly.
16
+ #
17
+ class RakeRunner
18
+ TEMPLATE = File.read(__FILE__).split(/^__END__/)[1]
19
+
20
+ SetInfo = Struct.new(:name, :set, :skipped)
21
+
22
+ ##
23
+ # Creates a new Generator instance.
24
+ #
25
+ # @param [String] sprites_yml
26
+ # The path to the sprites.yml file which defines the sprites.
27
+ # @param [String] sprites_dir
28
+ # The path to sprites directory, in which the generated sprites will
29
+ # be saved. The source icons will be assumed to be in a src/
30
+ # subdirectory.
31
+ # @param [String] sass_partial
32
+ # The path where the SASS partial should be saved.
33
+ #
34
+ # @raise [SpriteError]
35
+ # Raised if the sprites.yml file wasn't at the specified path.
36
+ #
37
+ # @api public
38
+ #
39
+ def initialize(sprites_yml, sprites_dir, sass_partial)
40
+ @sprites_yml = sprites_yml
41
+ @sprites_dir = sprites_dir
42
+ @sass_partial = sass_partial
43
+ @source_dir = File.join(@sprites_dir, 'src')
44
+ @dot_hashes = File.join(@sprites_dir, '.hashes')
45
+
46
+ unless File.exists?(@sprites_yml)
47
+ raise SpriteError,
48
+ "sprites.yml not found; expected to be at: #{@sprites_yml}"
49
+ end
50
+
51
+ @sets = YAML.load(File.read(@sprites_yml)).map do |name, icons|
52
+ SetInfo.new(name, Kin::Sprites::IconSet.new(icons), false)
53
+ end
54
+
55
+ # Sort the sets in alphanumeric order such that SASS partials don't
56
+ # radically change when regenerated (since Ruby 1.8's hash is
57
+ # unordered)
58
+ @sets.sort! { |left, right| left.name <=> right.name }
59
+ end
60
+
61
+ ##
62
+ # Generates the sprite images, and creates a _sprites.sass partial.
63
+ #
64
+ # @param [Boolean] force
65
+ # Regenerate sprite images even if the existing image contains the
66
+ # exact same icons as the new one will.
67
+ #
68
+ # @api public
69
+ #
70
+ def generate!(force = false)
71
+ @sets.each do |set|
72
+ generate_sprite!(set, force)
73
+ end
74
+
75
+ if @sets.any? { |set| not set.skipped }
76
+ generate_sass!
77
+ dump_hashes!
78
+ end
79
+
80
+ $stdout.puts
81
+ $stdout.puts("All done.")
82
+ end
83
+
84
+ private # ==============================================================
85
+
86
+ ##
87
+ # Generates a single sprite.
88
+ #
89
+ # @api private
90
+ #
91
+ def generate_sprite!(setinfo, force)
92
+ if requires_regenerating?(setinfo) or force
93
+ gen = Kin::Sprites::ImageGenerator.new(setinfo.set, @source_dir)
94
+ gen.save(File.join(@sprites_dir, "#{setinfo.name}.png"))
95
+ $stdout.puts("Regenerated #{setinfo.name.inspect} sprite.")
96
+ else
97
+ # Existing sprite file is identical, and the user doesn't want to
98
+ # force re-creating the file.
99
+ setinfo.skipped = true
100
+ $stdout.puts("Ignoring #{setinfo.name.inspect} sprite (unchanged).")
101
+ end
102
+ end
103
+
104
+ ##
105
+ # Generates the SASS partial for all sprites.
106
+ #
107
+ # @api private
108
+ #
109
+ def generate_sass!
110
+ sass = @sets.map do |setinfo|
111
+ set_sass = "// #{setinfo.name} sprite "
112
+ set_sass += "=" * (80 - 13 - setinfo.name.length)
113
+ set_sass += Kin::Sprites::SassGenerator.new(
114
+ setinfo.set, setinfo.name).to_sass('/images/sprites')
115
+ end.join("\n")
116
+
117
+ FileUtils.mkdir_p(File.dirname(@sass_partial))
118
+
119
+ File.open(@sass_partial, 'w') do |f|
120
+ f.puts(TEMPLATE.sub(/^SASS/, sass))
121
+ end
122
+
123
+ $stdout.puts('Saved SASS partial.')
124
+ end
125
+
126
+ ##
127
+ # Creates a fresh .hashes file containing hashes for the currently
128
+ # loaded sets.
129
+ #
130
+ # @api private
131
+ #
132
+ def dump_hashes!
133
+ hashes = @sets.inject({}) do |hashes, setinfo|
134
+ hashes[setinfo.name] = setinfo.set.hash ; hashes
135
+ end
136
+
137
+ File.open(@dot_hashes, 'w') do |f|
138
+ f.puts(YAML.dump(hashes))
139
+ end
140
+ end
141
+
142
+ ##
143
+ # Returns if a sprite needs to be regenerated.
144
+ #
145
+ # @api private
146
+ #
147
+ def requires_regenerating?(setinfo)
148
+ hash_for(setinfo.name) != setinfo.set.hash ||
149
+ (! File.exists?(File.join(@sprites_dir, "#{setinfo.name}.png")))
150
+ end
151
+
152
+ ##
153
+ # Returns the hash checksum for the current sprite file, if it exists.
154
+ #
155
+ # @param [String] name
156
+ # The name of the sprite to check.
157
+ #
158
+ # @return [String, nil]
159
+ # Returns a string if a hash was available, otherwise nil.
160
+ #
161
+ # @api private
162
+ #
163
+ def hash_for(name)
164
+ @_sprite_hashes ||= begin
165
+ File.exists?(@dot_hashes) ? YAML.load(File.read(@dot_hashes)) : {}
166
+ end
167
+
168
+ @_sprite_hashes[name]
169
+ end
170
+ end # RakeTaskRunner
171
+ end # Sprites
172
+ end # Kin
173
+
174
+ __END__
175
+ // The mixins in this file are automatically generated by the `sprites` rake
176
+ // task provided in the Kin gem. Don't edit this file directly; rather you
177
+ // should change config/sprites.yaml and then run `rake sprites`.
178
+
179
+ SASS
@@ -0,0 +1,81 @@
1
+ module Kin
2
+ module Sprites
3
+ ##
4
+ # Converts an IconSet to a SASS partial.
5
+ #
6
+ class SassGenerator
7
+ TEMPLATE = File.read(__FILE__).split(/^__END__/)[1]
8
+
9
+ ##
10
+ # Creates a new Generator instance.
11
+ #
12
+ # @param [IconSet] set
13
+ # An IconSet instance.
14
+ # @param [String] name
15
+ # The name of this sprite.
16
+ #
17
+ # @api public
18
+ #
19
+ def initialize(set, name)
20
+ @set, @name = set, name
21
+ end
22
+
23
+ ##
24
+ # Converts the icon set to a SASS partial.
25
+ #
26
+ # @param [String] dir
27
+ # The web directory in which the sprite files are located (no trailing
28
+ # space please).
29
+ #
30
+ # @api public
31
+ #
32
+ def to_sass(dir)
33
+ # Holds all the if/else statements.
34
+ conditions = []
35
+ conditions << if_statement(@set[0], true)
36
+ @set[1..-1].each { |icon| conditions << if_statement(icon) }
37
+
38
+ # Permits supplying an offset, rather than an icon name.
39
+ conditions << ' @else'
40
+ conditions << ' !pos = !icon'
41
+ conditions << ''
42
+
43
+ # Put the data into the template.
44
+ template = TEMPLATE.dup
45
+ template.gsub!(/NAME/, @name)
46
+ template.gsub!(/DIR/, dir)
47
+ template.gsub!(/CONDITIONS/, conditions.join("\n"))
48
+ template
49
+ end
50
+
51
+ private
52
+
53
+ ##
54
+ # Creates an if statement for the given icon.
55
+ #
56
+ # @param [String] icon
57
+ # The name of an icons
58
+ # @param [Boolean] is_first
59
+ # Is this the first icon to appear in the if statement?
60
+ #
61
+ def if_statement(icon, is_first = false)
62
+ else_modifier = is_first ? '' : 'else '
63
+
64
+ %[ @#{else_modifier}if !icon == "#{icon}"\n] +
65
+ %[ !pos = -#{@set.location_of(icon)}px]
66
+ end
67
+ end # SassGenerator
68
+ end # Sprites
69
+ end # Kin
70
+
71
+ __END__
72
+
73
+ =NAME-icon(!icon, !hpos = 0px, !voff = 0px)
74
+ !pos = !voff
75
+ CONDITIONS
76
+ :background = "url(DIR/NAME.png)" !hpos !pos "no-repeat"
77
+
78
+ =NAME-icon-pos(!icon, !hpos = 0px, !voff = 0px)
79
+ !pos = !voff
80
+ CONDITIONS
81
+ :background-position = !hpos !pos