montage 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/History.md +23 -0
  2. data/Rakefile +2 -4
  3. data/VERSION +1 -1
  4. data/bin/montage +11 -4
  5. data/lib/montage/commands/generate.rb +72 -11
  6. data/lib/montage/commands/init.rb +5 -11
  7. data/lib/montage/commands.rb +51 -18
  8. data/lib/montage/project.rb +54 -39
  9. data/lib/montage/source.rb +15 -44
  10. data/lib/montage/sprite.rb +25 -37
  11. data/lib/montage/sprite_definition.rb +127 -0
  12. data/lib/montage/templates/montage.yml +29 -19
  13. data/lib/montage/templates/sass_mixins.erb +1 -1
  14. data/lib/montage/templates/sources/{book.png → one/book.png} +0 -0
  15. data/lib/montage/templates/sources/{box-label.png → one/box-label.png} +0 -0
  16. data/lib/montage/templates/sources/{calculator.png → one/calculator.png} +0 -0
  17. data/lib/montage/templates/sources/{calendar-month.png → one/calendar-month.png} +0 -0
  18. data/lib/montage/templates/sources/{camera.png → one/camera.png} +0 -0
  19. data/lib/montage/templates/sources/{eraser.png → one/eraser.png} +0 -0
  20. data/lib/montage/templates/sources/two/inbox-image.png +0 -0
  21. data/lib/montage/templates/sources/two/magnet.png +0 -0
  22. data/lib/montage/templates/sources/two/newspaper.png +0 -0
  23. data/lib/montage/templates/sources/two/television.png +0 -0
  24. data/lib/montage/templates/sources/two/wand-hat.png +0 -0
  25. data/lib/montage/templates/sources/two/wooden-box-label.png +0 -0
  26. data/lib/montage.rb +10 -1
  27. data/montage.gemspec +17 -34
  28. data/spec/lib/project_helper.rb +41 -6
  29. data/spec/lib/shared_project_specs.rb +10 -11
  30. data/spec/montage/commands/generate_spec.rb +86 -132
  31. data/spec/montage/commands/init_spec.rb +18 -43
  32. data/spec/montage/project_spec.rb +77 -63
  33. data/spec/montage/sass_builder_spec.rb +33 -57
  34. data/spec/montage/source_spec.rb +10 -31
  35. data/spec/montage/sprite_definition_spec.rb +361 -0
  36. data/spec/montage/sprite_spec.rb +58 -57
  37. metadata +31 -70
  38. data/spec/fixtures/custom_dirs/montage.yml +0 -8
  39. data/spec/fixtures/default/montage.yml +0 -7
  40. data/spec/fixtures/default/public/images/sprites/src/one.png +0 -0
  41. data/spec/fixtures/default/public/images/sprites/src/three.png +0 -0
  42. data/spec/fixtures/default/public/images/sprites/src/two.png +0 -0
  43. data/spec/fixtures/directory_config/config/montage.yml +0 -5
  44. data/spec/fixtures/missing_source/montage.yml +0 -3
  45. data/spec/fixtures/missing_source_dir/montage.yml +0 -5
  46. data/spec/fixtures/root_config/montage.yml +0 -5
  47. data/spec/fixtures/root_config/public/images/sprites/src/source_one.png +0 -0
  48. data/spec/fixtures/root_config/public/images/sprites/src/source_three.jpg +0 -0
  49. data/spec/fixtures/root_config/public/images/sprites/src/source_two +0 -0
  50. data/spec/fixtures/sources/hundred.png +0 -0
  51. data/spec/fixtures/sources/mammoth.png +0 -0
  52. data/spec/fixtures/sources/other.png +0 -0
  53. data/spec/fixtures/sources/twenty.png +0 -0
  54. data/spec/fixtures/subdirs/montage.yml +0 -5
  55. data/spec/fixtures/subdirs/sub/sub/keep +0 -0
  56. data/spec/lib/fixtures.rb +0 -7
data/History.md CHANGED
@@ -1,3 +1,26 @@
1
+ v0.3.0 - 2010-04-12
2
+ -------------------
3
+
4
+ * The "montage.yml" file has been replaced with ".montage" which should be
5
+ located in your project root. In addition, the file is now rather different,
6
+ and in most cases will never need to be edited when you want to add new
7
+ sources to the sprite.
8
+
9
+ * By default Montage will now save sprites to public/images, expected source
10
+ images to be in public/images/subdir -- where "subdir" will become the name
11
+ of the sprite. All sources in a subdirectory will be added to the same
12
+ sprite.
13
+
14
+ This behavior is entirely customisable in the .montage file.
15
+
16
+ * The ".montage_cache" file which was previously saved in the same directory
17
+ as sprites is now saved in the project root.
18
+
19
+ * The `montage` command now allows you to specify a path to a Montage
20
+ configuration file; for example `montage path/to/montage.yml`. When using
21
+ a non-standard directory structure, you can specify a "config.root" option
22
+ in the configuration file, containing the path to the project root.
23
+
1
24
  v0.2.0 - 2010-04-08
2
25
  -------------------
3
26
 
data/Rakefile CHANGED
@@ -20,13 +20,11 @@ begin
20
20
  gem.has_rdoc = false
21
21
 
22
22
  # Dependencies.
23
- gem.add_dependency 'activesupport', '>= 3.0.0.beta'
24
- gem.add_dependency 'rmagick', '>= 2.12'
25
- gem.add_dependency 'highline', '>= 1.5'
23
+ gem.add_dependency 'rmagick', '>= 2.12'
24
+ gem.add_dependency 'highline', '>= 1.5'
26
25
 
27
26
  # Development dependencies.
28
27
  gem.add_development_dependency 'rspec', '>= 1.3.0'
29
- gem.add_development_dependency 'cucumber', '>= 0.6'
30
28
  gem.add_development_dependency 'open4', '>= 1.0'
31
29
  gem.add_development_dependency 'haml', '>= 3.0.0.beta.1'
32
30
  gem.add_development_dependency 'yard', '>= 0.5'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.1
1
+ 0.3.0
data/bin/montage CHANGED
@@ -4,19 +4,26 @@ unless $:.include?(File.dirname(__FILE__) + '/../lib')
4
4
  $:.unshift(File.dirname(__FILE__) + '/../lib')
5
5
  end
6
6
 
7
+ require 'optparse'
7
8
  require 'rubygems' unless ENV['NORUBYGEMS']
8
9
 
9
10
  require 'montage'
10
11
  require 'montage/commands'
11
12
 
12
- Montage::Commands.print_masthead
13
+ require 'highline/import'
14
+
15
+ module Kernel
16
+ def_delegators :$terminal, :color
17
+ end
18
+
19
+ args = Montage::Commands.parse_options!(ARGV.dup)
20
+ puts
13
21
 
14
22
  case ARGV[0]
15
23
  when 'init'
16
24
  require 'montage/commands/init'
17
- Montage::Commands::Init.run(ARGV)
25
+ Montage::Commands::Init.run(args)
18
26
  else
19
27
  require 'montage/commands/generate'
20
- Montage::Commands::Generate.run(ARGV)
28
+ Montage::Commands::Generate.run(args)
21
29
  end
22
-
@@ -17,7 +17,20 @@ module Montage
17
17
  # The arguments given on the command line.
18
18
  #
19
19
  def self.run(argv)
20
- new(Montage::Project.find(Dir.pwd), argv.include?('--force')).run!
20
+ # If there are any arguments, the first one is a path to a montage
21
+ # config file.
22
+ if argv.first and not Pathname.new(argv.first).file?
23
+ say color(<<-ERROR.compress_lines, :red)
24
+ Couldn't find `#{argv.first}' configuration file. Are you
25
+ sure you got the path right?
26
+ ERROR
27
+
28
+ exit(1)
29
+ end
30
+
31
+
32
+ new(Montage::Project.find(argv.first || Dir.pwd),
33
+ Montage::Commands.config[:force]).run!
21
34
 
22
35
  rescue Montage::MissingProject
23
36
  say color(<<-ERROR.compress_lines, :red)
@@ -52,6 +65,7 @@ module Montage
52
65
  optimise_with_pngout!
53
66
  write_cache!
54
67
  write_sass!
68
+ warn_deviants!
55
69
  end
56
70
  end
57
71
 
@@ -63,7 +77,7 @@ module Montage
63
77
  #
64
78
  def cache
65
79
  @_sprite_caches ||= begin
66
- cache_path = @project.paths.sprites + '.montage_cache'
80
+ cache_path = @project.paths.root + '.montage_cache'
67
81
  cache_path.file? ? YAML.load_file(cache_path) || {} : {}
68
82
  end
69
83
  end
@@ -75,15 +89,16 @@ module Montage
75
89
  # Returns true if at least one sprite has been updated.
76
90
  #
77
91
  def generate_sprites!
78
- unless @project.paths.sprites.directory?
79
- @project.paths.sprites.mkpath
80
- end
81
-
82
92
  @project.sprites.each do |sprite|
83
93
  digest = sprite.digest
84
94
 
95
+ # Ensure that we can write to the output directory.
96
+ unless sprite.save_path.dirname.directory?
97
+ sprite.save_path.dirname.mkpath
98
+ end
85
99
 
86
- if @force or cache[sprite.name] != digest or not sprite.path.file?
100
+ if @force or cache[sprite.name] != digest or
101
+ not sprite.save_path.file?
87
102
  with_feedback %(Generating "#{sprite.name}"), 'Generating' do
88
103
  sprite.write
89
104
  cache[sprite.name] = digest
@@ -124,18 +139,19 @@ module Montage
124
139
  end
125
140
 
126
141
  @generated.each do |sprite|
127
- original_size = sprite.path.size
142
+ original_size = sprite.save_path.size
143
+ save_path = sprite.save_path
128
144
 
129
145
  with_feedback %(Optimising "#{sprite.name}"), 'Optimising' do
130
146
  5.times do |i|
131
147
  # Optimise until pngout reports that it can't compress further,
132
148
  # or until we've tried five times.
133
- out = `#{pngout} #{sprite.path} #{sprite.path} -s0 -k0 -y`
149
+ out = `#{pngout} #{save_path} #{save_path} -s0 -k0 -y`
134
150
  break if out =~ /Unable to compress further/
135
151
  end
136
152
  end
137
153
 
138
- new_size = sprite.path.size
154
+ new_size = save_path.size
139
155
 
140
156
  reduction = ('%.1fkb (%d' % [
141
157
  (original_size.to_f - new_size) / 1024,
@@ -158,7 +174,7 @@ module Montage
158
174
  # Step 3: Writes the cached digests to the cache file.
159
175
  #
160
176
  def write_cache!
161
- cache_path = @project.paths.sprites + '.montage_cache'
177
+ cache_path = @project.paths.root + '.montage_cache'
162
178
 
163
179
  File.open(cache_path, 'w') do |cache_writer|
164
180
  cache_writer.puts YAML.dump(cache)
@@ -176,6 +192,37 @@ module Montage
176
192
  end
177
193
  end
178
194
 
195
+ # Step 5: Warn about images which are more than one standard deviation
196
+ # from the mean width.
197
+ #
198
+ def warn_deviants!
199
+ @generated.each do |sprite|
200
+ next if sprite.sources.size < 2
201
+
202
+ mean, std_dev = standard_deviation(sprite.sources.map do |source|
203
+ source.image.columns
204
+ end)
205
+
206
+ next if std_dev < 100 # Skip relatively narrow images.
207
+
208
+ sprite.sources.each do |source|
209
+ width = source.image.columns
210
+ if width > mean + std_dev || width < mean - std_dev
211
+ say <<-MESSAGE.compress_lines
212
+ The "#{source.name}" source image in the "#{sprite.name}"
213
+ sprite deviates significantly from the average width. You
214
+ might want to consider removing this source from the sprite.
215
+
216
+ The mean width for sources in this sprite is #{mean}px,
217
+ while this source is #{width}px wide.
218
+
219
+ MESSAGE
220
+ say Montage::Commands::BLANK
221
+ end
222
+ end
223
+ end
224
+ end
225
+
179
226
  # --- Optimisation Output ----------------------------------------------
180
227
 
181
228
  # Executes a block while providing live feedback to the user.
@@ -229,6 +276,20 @@ module Montage
229
276
  say "#{RESET}#{prefix}"
230
277
  end
231
278
 
279
+ # Knuth. via Wikipedia. :/
280
+ def standard_deviation(data)
281
+ n, mean, m2 = 0, 0, 0
282
+
283
+ data.each do |x|
284
+ n = n + 1
285
+ delta = x - mean
286
+ mean = mean + delta / n
287
+ m2 = m2 + delta * (x - mean)
288
+ end
289
+
290
+ [mean, Math.sqrt(m2 / (n - 1))]
291
+ end
292
+
232
293
  end # Generate
233
294
  end # Commands
234
295
  end # Montage
@@ -72,13 +72,13 @@ module Montage
72
72
 
73
73
  @sprites_path =
74
74
  ask("Where do you want generated sprites to be stored?") do |query|
75
- query.default = 'public/images/sprites'
75
+ query.default = File.join('public', 'images')
76
76
  query.answer_type = normalise_path
77
77
  end
78
78
 
79
79
  @sources_path =
80
80
  ask("Where are the source images stored?") do |query|
81
- query.default = "#{@sprites_path}/src"
81
+ query.default = (@sprites_path + "sprites").to_s
82
82
  query.answer_type = normalise_path
83
83
  end
84
84
  end
@@ -94,16 +94,10 @@ module Montage
94
94
  #
95
95
  def create_config!
96
96
  template = File.read(TEMPLATES + 'montage.yml')
97
- template.gsub!(/<sprites>/, %("#{@sprites_path.to_s}"))
98
- template.gsub!(/<sources>/, %("#{@sources_path.to_s}"))
97
+ template.gsub!(/<sprites>/, @sprites_path.to_s)
98
+ template.gsub!(/<sources>/, @sources_path.to_s)
99
99
 
100
- if (@dir + 'config').directory?
101
- config_path = @dir + 'config/montage.yml'
102
- else
103
- config_path = @dir + 'montage.yml'
104
- end
105
-
106
- File.open(config_path, 'w') do |config|
100
+ File.open(@dir + '.montage', 'w') do |config|
107
101
  config.puts template
108
102
  end
109
103
  end
@@ -1,29 +1,62 @@
1
- require 'highline/import'
2
-
3
- HighLine.use_color = ! ARGV.delete('--no-color') && ! ARGV.delete('--no-colour')
4
- HighLine.use_color = false if !STDOUT.tty? && !ENV.has_key?("AUTOTEST")
5
-
6
- module Kernel
7
- def_delegators :$terminal, :color
8
- end
9
-
10
1
  module Montage
11
2
  module Commands
3
+ extend self
4
+
5
+ # A blank line; HighLine doesn't allow calling +say+ without an argument.
12
6
  BLANK = "\n".freeze
13
7
 
14
- extend self
8
+ # Returns a configuration hash, containing options defined on the
9
+ # command-line.
10
+ #
11
+ # @return [Hash]
12
+ #
13
+ def config
14
+ @config ||= { :force => false, :quiet => false }
15
+ end
15
16
 
16
- # Prints the Montage masthead, introducing the programme, and including
17
- # the current version number.
18
- def print_masthead
19
- say BLANK
20
- say "Montage v#{Montage::VERSION}"
21
- say "=========#{'=' * Montage::VERSION.length}"
22
- say BLANK
17
+ # Uses OptParse to parse command-line arguments.
18
+ #
19
+ # Returns any unparsed command-line arguments.
20
+ #
21
+ def parse_options!(argv)
22
+ HighLine.use_color = false if !STDOUT.tty? && !ENV.has_key?("AUTOTEST")
23
+
24
+ OptionParser.new do |opts|
25
+ opts.banner = "Usage: montage [config file path] [options]"
26
+
27
+ opts.on('-c', '--[no-]color', '--[no-]colour',
28
+ 'Enables and disables colour output.') do |color|
29
+ HighLine.use_color = color
30
+ end
31
+
32
+ opts.on('-f', '--force',
33
+ 'Regenerate sprites even if no changes have been made.') do
34
+ Montage::Commands.config[:force] = true
35
+ end
36
+
37
+ # opts.on('-q', '--quiet',
38
+ # 'Tell Montage to shut up. No messages sent to STDOUT.') do
39
+ # Montage::Commands.config[:quiet] = true
40
+ # end
41
+
42
+ opts.on_tail("-h", "--help", "Shows this message.") do
43
+ say BLANK
44
+ say opts.to_s
45
+ exit
46
+ end
47
+
48
+ opts.on_tail("--version", "Print the current Montage version.") do
49
+ say BLANK
50
+ say "Montage v#{Montage::VERSION}"
51
+ exit
52
+ end
53
+ end.parse!(argv)
54
+
55
+ argv
23
56
  end
24
57
 
25
58
  # Exits immediately, outputting a blank line first.
26
- def exit(status)
59
+ def exit(status = 0)
27
60
  say BLANK
28
61
  Kernel.exit(status)
29
62
  end
@@ -3,14 +3,16 @@ module Montage
3
3
  # configuration file, and source images.
4
4
  class Project
5
5
  DEFAULTS = {
6
- :sources => 'public/images/sprites/src',
7
- :sprites => 'public/images/sprites',
8
- :sass => 'public/stylesheets/sass',
9
- :sprite_url => '/images/sprites'
6
+ :sources => 'public/images/sprites/src',
7
+ :sprites => 'public/images/sprites',
8
+ :sass => 'public/stylesheets/sass',
9
+ :to => "public/images/:name.png",
10
+ :url => "/images/:name.png",
11
+ :padding => 20
10
12
  }
11
13
 
12
14
  # Stores all the paths the project needs.
13
- Paths = Struct.new(:root, :config, :sources, :sprites, :sass, :url)
15
+ Paths = Struct.new(:root, :config, :sass, :url)
14
16
 
15
17
  # Returns the Paths instance for the project.
16
18
  #
@@ -37,31 +39,29 @@ module Montage
37
39
  # to be correct. If you're not sure of the exact paths, use +Project.find+
38
40
  # instead.
39
41
  #
40
- # @param [String, Pathname] root_path
41
- # Path to the root of the Montage project.
42
42
  # @param [String, Pathname] config_path
43
43
  # Path to the config file.
44
44
  #
45
- def initialize(root_path, config_path)
46
- root_path = Pathname.new(root_path)
45
+ def initialize(config_path)
47
46
  config_path = Pathname.new(config_path)
47
+ config = YAML.load_file(config_path)
48
+ root_path = determine_project_root(config_path, config)
48
49
 
49
- config = YAML.load_file(config_path)
50
+ # Sass path may be a string representing a path, or `false`.
51
+ sass_path = config.delete("config.sass") { DEFAULTS[:sass] }
52
+ sass_path = sass_path.is_a?(String) ? root_path + sass_path : sass_path
50
53
 
51
54
  @paths = Paths.new(
52
- root_path, config_path,
53
- extract_path_from_config(config, :sources, root_path),
54
- extract_path_from_config(config, :sprites, root_path),
55
- extract_path_from_config(config, :sass, root_path),
56
- config.delete('config.sprite_url') { DEFAULTS[:sprite_url] }
55
+ root_path, config_path, sass_path,
56
+ config.delete('config.url') { DEFAULTS[:url] }
57
57
  )
58
58
 
59
59
  @padding = (config.delete('config.padding') || 20).to_i
60
60
 
61
61
  # All remaining config keys are sprite defintions.
62
- @sprites = config.inject([]) do |sprites, (name, sources)|
63
- sprites << Sprite.new(name, sources, self)
64
- end
62
+ @sprites = config.map do |path, opts|
63
+ Montage::SpriteDefinition.new(self, path, opts).to_sprites
64
+ end.flatten
65
65
  end
66
66
 
67
67
  # Returns a particular sprite identified by +name+.
@@ -82,9 +82,9 @@ module Montage
82
82
  #
83
83
  # The configuration item will be _removed_ from the hash.
84
84
  #
85
- # @param [Hash] config The configuration Hash.
86
- # @param [Symbol] key The configuration key.
87
- # @param [Pathname] root The project root path.
85
+ # @param [Hash] config The configuration Hash.
86
+ # @param [Symbol] key The configuration key.
87
+ # @param [Pathname] root The project root path.
88
88
  #
89
89
  # @return [Pathname, false]
90
90
  #
@@ -93,6 +93,30 @@ module Montage
93
93
  value.is_a?(String) ? root + value : value
94
94
  end
95
95
 
96
+ # Attempts to find the project root for the configuration file. If the
97
+ # config file is in a directory called 'config' then the project root is
98
+ # assumed to be one level up.
99
+ #
100
+ # @param [Pathname] config_path The path to the config file.
101
+ # @param [Hash] config The project configuration.
102
+ #
103
+ # @return [Pathname]
104
+ #
105
+ def determine_project_root(config_path, config)
106
+ config_dir = config_path.dirname
107
+
108
+ if config.has_key?('config.root')
109
+ root_dir = Pathname.new(config.delete('config.root'))
110
+ root_dir.absolute? ? root_dir : (config_dir + root_dir)
111
+ else
112
+ if config_dir.split.last.to_s == 'config'
113
+ config_dir + '..'
114
+ else
115
+ config_dir
116
+ end
117
+ end
118
+ end
119
+
96
120
  # === Class Methods ======================================================
97
121
 
98
122
  class << self
@@ -111,7 +135,7 @@ module Montage
111
135
  #
112
136
  # If given a path to a directory:
113
137
  #
114
- # * Montage will look for montage.yml, or config/montage.yml.
138
+ # * Montage will look for a .montage file.
115
139
  #
116
140
  # * If a configuration couldn't be found, +find+ looks in the next
117
141
  # directory up. It continues until it finds a valid project or runs
@@ -131,37 +155,28 @@ module Montage
131
155
  config_path, root_path = nil, nil
132
156
 
133
157
  if path.file?
134
- root_path = find_root(path)
135
158
  config_path = path
136
159
  elsif path.directory?
137
- if config_path = find_config(path)
138
- root_path = path
139
- else
140
- # Assume we're in a subdirectory of the current project.
141
- path.split.first.ascend do |directory|
142
- if config_path = find_config(directory)
143
- break if root_path = find_root(config_path)
144
- end
145
- end
160
+ path.ascend do |directory|
161
+ break if config_path = contains_config?(directory)
146
162
  end
147
163
  end
148
164
 
149
165
  raise MissingProject, "Montage couldn't find a project to work " \
150
- "on at `#{path}'" if root_path.nil?
166
+ "on at `#{path}'" unless config_path
151
167
 
152
- new(root_path, config_path)
168
+ new(config_path)
153
169
  end
154
170
 
155
171
  private
156
172
 
157
- # Attempt to find the configuration file, first by looking in
158
- # ./montage.yml, then ./config/montage.yml
173
+ # Looks for a .montage configuration file in the given directory.
159
174
  #
160
175
  # @return [String]
161
176
  #
162
- def find_config(dir)
163
- config_paths = [ dir + 'montage.yml', dir + 'config/montage.yml' ]
164
- config_paths.detect { |config| config.file? }
177
+ def contains_config?(dir)
178
+ expected = (dir + '.montage')
179
+ expected.file? and expected
165
180
  end
166
181
 
167
182
  # Attempts to find the project root for the configuration file. If the
@@ -3,56 +3,21 @@ module Montage
3
3
  #
4
4
  class Source
5
5
 
6
- attr_reader :name
6
+ attr_reader :name, :path
7
7
  alias_method :to_s, :name
8
8
 
9
9
  # Creates a new Source instance.
10
10
  #
11
- # @param [Pathname] dir
12
- # The directory in which the source image should stored.
13
- # @param [String] name
14
- # The name of the source image, sans extension
15
- # @param [String] sprite
16
- # The name of the sprite to which the source belongs. Used only in
17
- # error messages.
11
+ # @param [Pathname] path
12
+ # The path the the source image.
18
13
  #
19
- def initialize(dir, name, sprite_name)
20
- @dir, @name, @sprite_name = dir, name, sprite_name
14
+ def initialize(path)
15
+ @path = Pathname.new(path)
16
+ @name = @path.basename.to_s.chomp(@path.extname.to_s)
21
17
  end
22
18
 
23
19
  def inspect # :nodoc
24
- "#<Montage::Source #{@sprite_name}:#{@name}>"
25
- end
26
-
27
- # Returns the full path to the source image.
28
- #
29
- # @return [Pathname]
30
- # @raise [Montage::MissingSource]
31
- #
32
- def path
33
- @path ||= begin
34
- unless @dir.directory?
35
- raise MissingSource, <<-MESSAGE.compress_lines
36
- Couldn't find the source directory for the `#{@sprite_name}'
37
- sprite. Montage was looking for #{@dir}; if your sprites are in a
38
- different location, add a 'config.sources' option your config
39
- file.
40
- MESSAGE
41
- end
42
-
43
- path = @dir.entries.detect do |entry|
44
- entry.to_s.chomp(entry.extname) == @name
45
- end
46
-
47
- if path.nil?
48
- raise MissingSource, <<-MESSAGE.compress_lines
49
- Couldn't find a matching file for source image `#{@name}' as part
50
- of the `#{@sprite_name}' sprite. Was looking in `#{@dir}'.
51
- MESSAGE
52
- end
53
-
54
- @dir + path
55
- end
20
+ "#<Montage::Source #{@name}>"
56
21
  end
57
22
 
58
23
  # Returns the RMagick image instance representing the source.
@@ -60,7 +25,10 @@ module Montage
60
25
  # @return [Magick::Image]
61
26
  #
62
27
  def image
63
- @image ||= Magick::Image.read(path).first
28
+ raise MissingSource, "Couldn't find the source file `#{@path}'" \
29
+ unless @path.file?
30
+
31
+ @image ||= Magick::Image.read(@path).first
64
32
  end
65
33
 
66
34
  # Returns a digest which represents the sprite name and file contents.
@@ -68,7 +36,10 @@ module Montage
68
36
  # @return [String]
69
37
  #
70
38
  def digest
71
- Digest::SHA256.hexdigest(@name + Digest::SHA256.file(path).to_s)
39
+ raise MissingSource, "Couldn't find the source file `#{@path}'" \
40
+ unless @path.file?
41
+
42
+ Digest::SHA256.hexdigest(@name + Digest::SHA256.file(@path).to_s)
72
43
  end
73
44
 
74
45
  end # Source