montage 0.2.1

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