montage 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.document +5 -0
  2. data/.gitignore +31 -0
  3. data/History.md +40 -0
  4. data/LICENSE +19 -0
  5. data/README.md +89 -0
  6. data/Rakefile +41 -0
  7. data/VERSION +1 -0
  8. data/bin/montage +22 -0
  9. data/lib/montage.rb +33 -0
  10. data/lib/montage/commands.rb +32 -0
  11. data/lib/montage/commands/generate.rb +234 -0
  12. data/lib/montage/commands/init.rb +119 -0
  13. data/lib/montage/core_ext.rb +80 -0
  14. data/lib/montage/project.rb +185 -0
  15. data/lib/montage/sass_builder.rb +37 -0
  16. data/lib/montage/source.rb +75 -0
  17. data/lib/montage/sprite.rb +132 -0
  18. data/lib/montage/templates/montage.yml +26 -0
  19. data/lib/montage/templates/sass_mixins.erb +20 -0
  20. data/lib/montage/templates/sources/book.png +0 -0
  21. data/lib/montage/templates/sources/box-label.png +0 -0
  22. data/lib/montage/templates/sources/calculator.png +0 -0
  23. data/lib/montage/templates/sources/calendar-month.png +0 -0
  24. data/lib/montage/templates/sources/camera.png +0 -0
  25. data/lib/montage/templates/sources/eraser.png +0 -0
  26. data/lib/montage/version.rb +3 -0
  27. data/montage.gemspec +145 -0
  28. data/spec/fixtures/custom_dirs/montage.yml +8 -0
  29. data/spec/fixtures/default/montage.yml +7 -0
  30. data/spec/fixtures/default/public/images/sprites/src/one.png +0 -0
  31. data/spec/fixtures/default/public/images/sprites/src/three.png +0 -0
  32. data/spec/fixtures/default/public/images/sprites/src/two.png +0 -0
  33. data/spec/fixtures/directory_config/config/montage.yml +5 -0
  34. data/spec/fixtures/missing_source/montage.yml +3 -0
  35. data/spec/fixtures/missing_source_dir/montage.yml +5 -0
  36. data/spec/fixtures/root_config/montage.yml +5 -0
  37. data/spec/fixtures/root_config/public/images/sprites/src/source_one.png +0 -0
  38. data/spec/fixtures/root_config/public/images/sprites/src/source_three.jpg +0 -0
  39. data/spec/fixtures/root_config/public/images/sprites/src/source_two +0 -0
  40. data/spec/fixtures/sources/hundred.png +0 -0
  41. data/spec/fixtures/sources/mammoth.png +0 -0
  42. data/spec/fixtures/sources/other.png +0 -0
  43. data/spec/fixtures/sources/twenty.png +0 -0
  44. data/spec/fixtures/subdirs/montage.yml +5 -0
  45. data/spec/fixtures/subdirs/sub/sub/keep +0 -0
  46. data/spec/lib/command_runner.rb +140 -0
  47. data/spec/lib/fixtures.rb +7 -0
  48. data/spec/lib/have_public_method_defined.rb +19 -0
  49. data/spec/lib/project_helper.rb +135 -0
  50. data/spec/lib/shared_project_specs.rb +32 -0
  51. data/spec/lib/shared_sprite_specs.rb +30 -0
  52. data/spec/montage/commands/generate_spec.rb +308 -0
  53. data/spec/montage/commands/init_spec.rb +120 -0
  54. data/spec/montage/core_ext_spec.rb +33 -0
  55. data/spec/montage/project_spec.rb +181 -0
  56. data/spec/montage/sass_builder_spec.rb +269 -0
  57. data/spec/montage/source_spec.rb +53 -0
  58. data/spec/montage/spec/have_public_method_defined_spec.rb +31 -0
  59. data/spec/montage/sprite_spec.rb +170 -0
  60. data/spec/rcov.opts +8 -0
  61. data/spec/spec.opts +4 -0
  62. data/spec/spec_helper.rb +19 -0
  63. data/tasks/spec.rake +17 -0
  64. data/tasks/yard.rake +11 -0
  65. metadata +249 -0
@@ -0,0 +1,7 @@
1
+ Spec::Runner.configure do |config|
2
+ config.include(Module.new do
3
+ def fixture_path(name, *segments)
4
+ File.join(File.expand_path("../../fixtures/#{name}", __FILE__), *segments)
5
+ end
6
+ end)
7
+ end
@@ -0,0 +1,19 @@
1
+ ::Spec::Matchers.define :have_public_method_defined do |value|
2
+ match do |klass|
3
+ klass.public_method_defined?(value.to_sym)
4
+ end
5
+
6
+ description do
7
+ "should define public instance method ##{value.to_s}"
8
+ end
9
+
10
+ failure_message_for_should do |klass|
11
+ "expected #{klass.inspect} to define public instance method " \
12
+ "#{value.inspect}, but it didn't"
13
+ end
14
+
15
+ failure_message_for_should_not do |klass|
16
+ "expected #{klass.inspect} to not define public instance method " \
17
+ "#{value.inspect}, but it did"
18
+ end
19
+ end
@@ -0,0 +1,135 @@
1
+ require 'rbconfig'
2
+ require 'tempfile'
3
+
4
+ module Montage
5
+ module Spec
6
+ # Provides useful functionality for dealing with temporary project
7
+ # directories when running tests.
8
+ #
9
+ # Any helper classes which use ProjectHelper will always run in a single
10
+ # temporary directory (which is removed and recreated each time) and thus
11
+ # doesn't need to be cleaned up afterwards.
12
+ #
13
+ class ProjectHelper
14
+
15
+ # --- Class methods ----------------------------------------------------
16
+
17
+ def self.project_dir
18
+ @project_dir ||= Pathname.new(Dir.mktmpdir)
19
+ end
20
+
21
+ def project_dir
22
+ self.class.project_dir
23
+ end
24
+
25
+ def self.cleanup!
26
+ FileUtils.remove_entry_secure(project_dir) if project_dir.directory?
27
+ end
28
+
29
+ # ----------------------------------------------------------------------
30
+
31
+ def initialize
32
+ # Wipe out the temporary directory to ensure
33
+ # we have a clean state before each run.
34
+ self.class.cleanup!
35
+ project_dir.mkpath
36
+
37
+ self.sources_path = "public/images/sprites/src"
38
+ self.sprites_path = "public/images/sprites"
39
+ end
40
+
41
+ def sources_path=(path)
42
+ @sources_path = Pathname.new(path)
43
+ end
44
+
45
+ def sprites_path=(path)
46
+ @sprites_path = Pathname.new(path)
47
+ end
48
+
49
+ # Returns a project instance representing the contents of the test
50
+ # directory.
51
+ #
52
+ # @return [Montage::Project]
53
+ #
54
+ def project
55
+ Project.find(project_dir)
56
+ end
57
+
58
+ # --- Paths ------------------------------------------------------------
59
+
60
+ # Returns the path to a file.
61
+ #
62
+ # @param [String] name
63
+ # The name of the source file.
64
+ #
65
+ def path_to_file(name)
66
+ (project_dir + name).expand_path
67
+ end
68
+
69
+ # Returns the path to a source file (sans extension).
70
+ #
71
+ # @param [String] name
72
+ # The name of the source file.
73
+ #
74
+ def path_to_source(name)
75
+ path_to_file @sources_path + "#{name}.png"
76
+ end
77
+
78
+ # Returns the path to a sprite file (sans extension).
79
+ #
80
+ # @param [String] name
81
+ # The name of the sprite file.
82
+ #
83
+ def path_to_sprite(name)
84
+ path_to_file @sprites_path + "#{name}.png"
85
+ end
86
+
87
+ # --- File Writers -----------------------------------------------------
88
+
89
+ # Writes montage.yml file in the project root with the given contents.
90
+ #
91
+ # @param [String] contents
92
+ # The contents to be saved as the config file.
93
+ #
94
+ def write_config(contents)
95
+ File.open(project_dir + 'montage.yml', 'w') do |file|
96
+ file.puts contents.unindent
97
+ end
98
+ end
99
+
100
+ # Writes a source image file to the src directory.
101
+ #
102
+ # @param [String] name
103
+ # The name of the source image file, sans extension.
104
+ # @param [Integer] width
105
+ # The width of the source file in pixels.
106
+ # @param [Integer] height
107
+ # The height of the source file in pixels.
108
+ #
109
+ def write_source(name, width = 50, height = 20)
110
+ source_path = path_to_source(name)
111
+
112
+ # Create the sources directory.
113
+ source_path.dirname.mkpath
114
+
115
+ Magick::Image.new(width, height) do
116
+ self.background_color = '#CCC'
117
+ end.write(source_path)
118
+
119
+ unless source_path.file?
120
+ raise "Source #{name} was not successfully saved"
121
+ end
122
+ end
123
+
124
+ # Creates a directory in the project.
125
+ #
126
+ # @param [String] dir
127
+ # Path to the directory, relative to the project root.
128
+ #
129
+ def mkdir(path)
130
+ (project_dir + path).mkpath
131
+ end
132
+
133
+ end # ProjectHelper
134
+ end # Spec
135
+ end # Montage
@@ -0,0 +1,32 @@
1
+ describe 'a project with correct paths', :shared => true do
2
+ # Requires:
3
+ #
4
+ # @project => Montage::Project
5
+ # @root => Pathname (path to project root)
6
+ # @config => Pathname (path to config file)
7
+ #
8
+
9
+ it 'should set the project root path' do
10
+ @project.paths.root.should == @root
11
+ end
12
+
13
+ it 'should set the configuration file path' do
14
+ @project.paths.config.should == @config
15
+ end
16
+
17
+ it 'should set the sources path' do
18
+ @project.paths.sources.should == @root + 'public/images/sprites/src'
19
+ end
20
+
21
+ it 'should set the sprite path' do
22
+ @project.paths.sprites.should == @root + 'public/images/sprites'
23
+ end
24
+
25
+ it 'should set the SASS output path' do
26
+ @project.paths.sass.should == @root + 'public/stylesheets/sass'
27
+ end
28
+
29
+ it 'should set the CSS sprite URL' do
30
+ @project.paths.url.should == '/images/sprites'
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ describe 'saving a sprite', :shared => true do
2
+ # Requires:
3
+ #
4
+ # @sprite => Montage::Sprite
5
+ # @dir => Pathname (path at which the sprite is saved)
6
+ # @output => Pathname (path for the final sprite)
7
+ #
8
+ it 'should save the sprite to the specified directory' do
9
+ lambda { @sprite.write }.should \
10
+ change(&lambda { @output.file? })
11
+ end
12
+
13
+ it 'should save an 8-bit PNG with transparency' do
14
+ @sprite.write
15
+ image = Magick::Image.ping(@output).first
16
+ image.format.should == 'PNG'
17
+ image.quantum_depth.should == 8 # 8-bits per channel.
18
+ end
19
+
20
+ it 'should overwrite an existingn file' do
21
+ FileUtils.touch(@output)
22
+ orig_size = @output.size
23
+ @sprite.write
24
+
25
+ # Using touch should create an empty file. Saving the PNG
26
+ # should result in a larger file.
27
+ @output.should be_file
28
+ @output.size.should > orig_size
29
+ end
30
+ end
@@ -0,0 +1,308 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ # ----------------------------------------------------------------------------
4
+
5
+ context 'Generating a single sprite with two sources' do
6
+ before(:all) do
7
+ @runner = Montage::Spec::CommandRunner.new('montage')
8
+ @runner.write_config <<-CONFIG
9
+ ---
10
+ sprite_one:
11
+ - one
12
+ - two
13
+ CONFIG
14
+ @runner.write_source('one')
15
+ @runner.write_source('two')
16
+ @runner.run!
17
+ end
18
+
19
+ it { @runner.should be_success }
20
+ it { @runner.stdout.should =~ /Generating "sprite_one": Done/ }
21
+ it { @runner.path_to_sprite('sprite_one').should be_file }
22
+ it { @runner.dimensions_of('sprite_one').should == [50, 60] }
23
+
24
+ # Sass.
25
+ it { @runner.path_to_file('public/stylesheets/sass/_montage.sass').should be_file }
26
+
27
+ # PNGOut.
28
+ it { @runner.stdout.should =~ /Optimising "sprite_one": Done/ }
29
+ end
30
+
31
+ # ----------------------------------------------------------------------------
32
+
33
+ context 'Generating multiple sprites' do
34
+ before(:all) do
35
+ @runner = Montage::Spec::CommandRunner.new('montage')
36
+ @runner.write_config <<-CONFIG
37
+ ---
38
+ sprite_one:
39
+ - one
40
+
41
+ sprite_two:
42
+ - two
43
+ - three
44
+ CONFIG
45
+ @runner.write_source('one')
46
+ @runner.write_source('two')
47
+ @runner.write_source('three', 200, 200)
48
+ @runner.run!
49
+ end
50
+
51
+ it { @runner.should be_success }
52
+
53
+ it { @runner.stdout.should =~ /Generating "sprite_one": Done/ }
54
+ it { @runner.path_to_sprite('sprite_one').should be_file }
55
+ it { @runner.dimensions_of('sprite_one').should == [50, 20] }
56
+
57
+ it { @runner.stdout.should =~ /Generating "sprite_two": Done/ }
58
+ it { @runner.path_to_sprite('sprite_two').should be_file }
59
+ it { @runner.dimensions_of('sprite_two').should == [200, 240] }
60
+
61
+ # PNGOut.
62
+ it { @runner.stdout.should =~ /Optimising "sprite_one": Done/ }
63
+ it { @runner.stdout.should =~ /Optimising "sprite_two": Done/ }
64
+ end
65
+
66
+ # ----------------------------------------------------------------------------
67
+
68
+ context 'Generating a single sprite with custom padding' do
69
+ before(:all) do
70
+ @runner = Montage::Spec::CommandRunner.new('montage')
71
+ @runner.write_config <<-CONFIG
72
+ ---
73
+ config.padding: 50
74
+
75
+ sprite_one:
76
+ - one
77
+ - two
78
+ CONFIG
79
+ @runner.write_source('one')
80
+ @runner.write_source('two')
81
+ @runner.run!
82
+ end
83
+
84
+ it { @runner.should be_success }
85
+ it { @runner.stdout.should =~ /Generating "sprite_one": Done/ }
86
+ it { @runner.path_to_sprite('sprite_one').should be_file }
87
+ it { @runner.dimensions_of('sprite_one').should == [50, 90] }
88
+ end
89
+
90
+ # ----------------------------------------------------------------------------
91
+
92
+ context 'Generating a single sprite using custom directories' do
93
+ before(:all) do
94
+ @runner = Montage::Spec::CommandRunner.new('montage')
95
+ @runner.sources_path = 'img/sources'
96
+ @runner.sprites_path = 'img/sprites'
97
+
98
+ @runner.write_config <<-CONFIG
99
+ ---
100
+ config.sources: img/sources
101
+ config.sprites: img/sprites
102
+
103
+ sprite_one:
104
+ - one
105
+ - two
106
+ CONFIG
107
+
108
+ @runner.write_source('one')
109
+ @runner.write_source('two')
110
+ @runner.run!
111
+ end
112
+
113
+ it { @runner.should be_success }
114
+ it { @runner.stdout.should =~ /Generating "sprite_one": Done/ }
115
+ it { @runner.path_to_sprite('sprite_one').should be_file }
116
+ end
117
+
118
+ # ----------------------------------------------------------------------------
119
+
120
+ context 'Trying to generate sprites in a non-project directory' do
121
+ before(:all) do
122
+ @runner = Montage::Spec::CommandRunner.new('montage').run!
123
+ end
124
+
125
+ it { @runner.should be_failure }
126
+ it { @runner.stdout.should =~ /Couldn't find a Montage project/ }
127
+ end
128
+
129
+ # ----------------------------------------------------------------------------
130
+
131
+ context 'Trying to generate sprites when a source is missing' do
132
+ before(:all) do
133
+ @runner = Montage::Spec::CommandRunner.new('montage')
134
+ @runner.write_config <<-CONFIG
135
+ ---
136
+ sprite_one:
137
+ - one
138
+ CONFIG
139
+ @runner.mkdir('public/images/sprites/src')
140
+ @runner.run!
141
+ end
142
+
143
+ it { @runner.should be_failure }
144
+ it { @runner.stdout.should =~ /Couldn't find a matching file for source image `one'/ }
145
+ end
146
+
147
+ # ----------------------------------------------------------------------------
148
+
149
+ context 'Trying to generate sprites when the source directory does not exist' do
150
+ before(:all) do
151
+ @runner = Montage::Spec::CommandRunner.new('montage')
152
+ @runner.write_config <<-CONFIG
153
+ ---
154
+ sprite_one:
155
+ - one
156
+ CONFIG
157
+ @runner.run!
158
+ end
159
+
160
+ it { @runner.should be_failure }
161
+ it { @runner.stdout.should =~ /Couldn't find the source directory/ }
162
+ end
163
+
164
+ # ----------------------------------------------------------------------------
165
+
166
+ context 'Trying to generate sprites when the sprite directory is not writable' do
167
+ before(:all) do
168
+ @runner = Montage::Spec::CommandRunner.new('montage')
169
+ @runner.write_config <<-CONFIG
170
+ ---
171
+ sprite_one:
172
+ - one
173
+ CONFIG
174
+ @runner.write_source('one')
175
+
176
+ old_mode = @runner.project.paths.sprites.stat.mode
177
+ @runner.project.paths.sprites.chmod(040555)
178
+
179
+ begin
180
+ @runner.run!
181
+ ensure
182
+ @runner.project.paths.sprites.chmod(old_mode)
183
+ end
184
+ end
185
+
186
+ it { @runner.should be_failure }
187
+ it { @runner.stdout.should =~ /can't save the sprite in .* isn't writable/m }
188
+ end
189
+
190
+ # ----------------------------------------------------------------------------
191
+
192
+ context 'Generating two sprites, one of which is unchanged' do
193
+ before(:all) do
194
+ @runner = Montage::Spec::CommandRunner.new('montage')
195
+ @runner.write_config <<-CONFIG
196
+ ---
197
+ sprite_one:
198
+ - one
199
+ - two
200
+
201
+ sprite_two:
202
+ - three
203
+ CONFIG
204
+ @runner.write_source('one')
205
+ @runner.write_source('two')
206
+ @runner.write_source('three')
207
+ @runner.run!
208
+
209
+ # Change the 'one' source.
210
+ @runner.write_source('one', 100, 25)
211
+ @runner.run!
212
+ end
213
+
214
+ it { @runner.should be_success }
215
+
216
+ it { @runner.stdout.should =~ /Generating "sprite_one": Done/ }
217
+ it { @runner.path_to_sprite('sprite_one').should be_file }
218
+ it { @runner.dimensions_of('sprite_one').should == [100, 65] }
219
+
220
+ it { @runner.stdout.should =~ /Generating "sprite_two": Unchanged; ignoring/ }
221
+ it { @runner.path_to_sprite('sprite_two').should be_file }
222
+ it { @runner.dimensions_of('sprite_two').should == [50, 20] }
223
+
224
+ # PNGOut.
225
+ it { @runner.stdout.should =~ /Optimising "sprite_one": Done/ }
226
+ it { @runner.stdout.should_not =~ /Optimising "sprite_two": Done/ }
227
+ end
228
+
229
+ # ----------------------------------------------------------------------------
230
+
231
+ context 'Generating two sprites, one of which is unchanged when using the --force option' do
232
+ before(:all) do
233
+ @runner = Montage::Spec::CommandRunner.new('montage')
234
+ @runner.write_config <<-CONFIG
235
+ ---
236
+ sprite_one:
237
+ - one
238
+ - two
239
+
240
+ sprite_two:
241
+ - three
242
+ CONFIG
243
+ @runner.write_source('one')
244
+ @runner.write_source('two')
245
+ @runner.write_source('three')
246
+ @runner.run!
247
+
248
+ # Change the 'one' source.
249
+ @runner.write_source('one', 100, 25)
250
+ @runner.run('montage --force')
251
+ end
252
+
253
+ it { @runner.should be_success }
254
+ it { @runner.stdout.should =~ /Generating "sprite_one": Done/ }
255
+ it { @runner.stdout.should =~ /Generating "sprite_two": Done/ }
256
+
257
+ # PNGOut.
258
+ it { @runner.stdout.should =~ /Optimising "sprite_one": Done/ }
259
+ it { @runner.stdout.should =~ /Optimising "sprite_two": Done/ }
260
+ end
261
+
262
+ # ----------------------------------------------------------------------------
263
+
264
+ context 'Generating an unchanged sprite which has been deleted' do
265
+ before(:all) do
266
+ @runner = Montage::Spec::CommandRunner.new('montage')
267
+ @runner.write_config <<-CONFIG
268
+ ---
269
+ sprite_one:
270
+ - one
271
+
272
+ CONFIG
273
+ @runner.write_source('one')
274
+ @runner.run!
275
+
276
+ # Remove the generated sprite.
277
+ @runner.path_to_sprite('sprite_one').unlink
278
+ @runner.run!
279
+ end
280
+
281
+ it { @runner.should be_success }
282
+
283
+ it { @runner.stdout.should =~ /Generating "sprite_one": Done/ }
284
+ it { @runner.path_to_sprite('sprite_one').should be_file }
285
+
286
+ # PNGOut.
287
+ it { @runner.stdout.should =~ /Optimising "sprite_one": Done/ }
288
+ end
289
+
290
+ # ----------------------------------------------------------------------------
291
+
292
+ context 'Generating sprites with a project which disables Sass' do
293
+ before(:all) do
294
+ @runner = Montage::Spec::CommandRunner.new('montage')
295
+ @runner.write_config <<-CONFIG
296
+ ---
297
+ config.sass: false
298
+
299
+ sprite_one:
300
+ - one
301
+
302
+ CONFIG
303
+ @runner.write_source('one')
304
+ @runner.run!
305
+ end
306
+
307
+ it { @runner.path_to_file('public/stylesheets/sass/_montage.sass').should_not be_file }
308
+ end