antw-kin 0.3.2 → 0.3.3

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