ruby_spriter 0.6.5

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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/CHANGELOG.md +217 -0
  4. data/Gemfile +17 -0
  5. data/LICENSE +21 -0
  6. data/README.md +561 -0
  7. data/bin/ruby_spriter +20 -0
  8. data/lib/ruby_spriter/cli.rb +249 -0
  9. data/lib/ruby_spriter/consolidator.rb +146 -0
  10. data/lib/ruby_spriter/dependency_checker.rb +174 -0
  11. data/lib/ruby_spriter/gimp_processor.rb +664 -0
  12. data/lib/ruby_spriter/metadata_manager.rb +116 -0
  13. data/lib/ruby_spriter/platform.rb +82 -0
  14. data/lib/ruby_spriter/processor.rb +251 -0
  15. data/lib/ruby_spriter/utils/file_helper.rb +57 -0
  16. data/lib/ruby_spriter/utils/output_formatter.rb +65 -0
  17. data/lib/ruby_spriter/utils/path_helper.rb +59 -0
  18. data/lib/ruby_spriter/version.rb +7 -0
  19. data/lib/ruby_spriter/video_processor.rb +139 -0
  20. data/lib/ruby_spriter.rb +31 -0
  21. data/ruby_spriter.gemspec +42 -0
  22. data/spec/fixtures/image_without_metadata.png +0 -0
  23. data/spec/fixtures/spritesheet_4x2.png +0 -0
  24. data/spec/fixtures/spritesheet_4x4.png +0 -0
  25. data/spec/fixtures/spritesheet_6x2.png +0 -0
  26. data/spec/fixtures/spritesheet_with_metadata.png +0 -0
  27. data/spec/fixtures/test_video.mp4 +0 -0
  28. data/spec/ruby_spriter/cli_spec.rb +1142 -0
  29. data/spec/ruby_spriter/consolidator_spec.rb +375 -0
  30. data/spec/ruby_spriter/dependency_checker_spec.rb +0 -0
  31. data/spec/ruby_spriter/gimp_processor_spec.rb +425 -0
  32. data/spec/ruby_spriter/metadata_manager_spec.rb +0 -0
  33. data/spec/ruby_spriter/platform_spec.rb +82 -0
  34. data/spec/ruby_spriter/processor_spec.rb +0 -0
  35. data/spec/ruby_spriter/utils/file_helper_spec.rb +71 -0
  36. data/spec/ruby_spriter/utils/output_formatter_spec.rb +0 -0
  37. data/spec/ruby_spriter/utils/path_helper_spec.rb +78 -0
  38. data/spec/ruby_spriter/video_processor_spec.rb +0 -0
  39. data/spec/spec_helper.rb +41 -0
  40. metadata +88 -0
@@ -0,0 +1,375 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe RubySpriter::Consolidator do
6
+ let(:spritesheet1) { File.join(__dir__, '..', 'fixtures', 'spritesheet_4x2.png') }
7
+ let(:spritesheet2) { File.join(__dir__, '..', 'fixtures', 'spritesheet_6x2.png') }
8
+ let(:spritesheet3) { File.join(__dir__, '..', 'fixtures', 'spritesheet_4x4.png') }
9
+ let(:output_file) { File.join($test_temp_dir, 'consolidated.png') }
10
+
11
+ describe '#initialize' do
12
+ it 'initializes with empty options by default' do
13
+ consolidator = described_class.new
14
+
15
+ expect(consolidator.options).to eq({})
16
+ end
17
+
18
+ it 'initializes with provided options' do
19
+ consolidator = described_class.new(debug: true, validate_columns: false)
20
+
21
+ expect(consolidator.options[:debug]).to be true
22
+ expect(consolidator.options[:validate_columns]).to be false
23
+ end
24
+ end
25
+
26
+ describe '#consolidate' do
27
+ let(:consolidator) { described_class.new }
28
+
29
+ # Helper to set up common mocks for successful consolidation
30
+ def stub_consolidation_success
31
+ allow(RubySpriter::MetadataManager).to receive(:embed)
32
+ allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
33
+ allow(File).to receive(:rename)
34
+ allow(File).to receive(:exist?).and_call_original
35
+ allow(File).to receive(:exist?).with(/consolidated.*temp/).and_return(true)
36
+ allow(File).to receive(:delete)
37
+ end
38
+
39
+ context 'with file validation' do
40
+ it 'raises error when given 0 files' do
41
+ expect {
42
+ consolidator.consolidate([], output_file)
43
+ }.to raise_error(RubySpriter::ValidationError, /Need at least 2 files/)
44
+ end
45
+
46
+ it 'raises error when given only 1 file' do
47
+ expect {
48
+ consolidator.consolidate([spritesheet1], output_file)
49
+ }.to raise_error(RubySpriter::ValidationError, /Need at least 2 files/)
50
+ end
51
+
52
+ it 'accepts 2 files minimum' do
53
+ stub_consolidation_success
54
+ allow(RubySpriter::MetadataManager).to receive(:read).and_return(
55
+ { columns: 2, rows: 2, frames: 4 }
56
+ )
57
+ allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
58
+ allow(File).to receive(:size).and_return(1000)
59
+
60
+ expect {
61
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
62
+ }.not_to raise_error
63
+ end
64
+
65
+ it 'raises error when file does not exist' do
66
+ non_existent = 'non_existent.png'
67
+
68
+ expect {
69
+ consolidator.consolidate([spritesheet1, non_existent], output_file)
70
+ }.to raise_error(RubySpriter::ValidationError, /not found/)
71
+ end
72
+ end
73
+
74
+ context 'with metadata reading' do
75
+ it 'reads metadata from all files' do
76
+ stub_consolidation_success
77
+ metadata1 = { columns: 2, rows: 2, frames: 4 }
78
+ metadata2 = { columns: 2, rows: 3, frames: 6 }
79
+
80
+ expect(RubySpriter::MetadataManager).to receive(:read).with(spritesheet1).and_return(metadata1)
81
+ expect(RubySpriter::MetadataManager).to receive(:read).with(spritesheet2).and_return(metadata2)
82
+
83
+ allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
84
+ allow(File).to receive(:size).and_return(1000)
85
+
86
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
87
+ end
88
+
89
+ it 'raises ValidationError when file missing metadata' do
90
+ allow(RubySpriter::MetadataManager).to receive(:read).with(spritesheet1).and_return(nil)
91
+
92
+ expect {
93
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
94
+ }.to raise_error(RubySpriter::ValidationError, /missing metadata/)
95
+ end
96
+
97
+ it 'raises error with helpful message about Ruby Spriter spritesheets' do
98
+ allow(RubySpriter::MetadataManager).to receive(:read).with(spritesheet1).and_return(nil)
99
+
100
+ expect {
101
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
102
+ }.to raise_error(RubySpriter::ValidationError, /All files must be Ruby Spriter spritesheets/)
103
+ end
104
+ end
105
+
106
+ context 'with column validation enabled (default)' do
107
+ let(:consolidator) { described_class.new(validate_columns: true) }
108
+
109
+ it 'passes when all files have same column count' do
110
+ stub_consolidation_success
111
+ metadata1 = { columns: 2, rows: 2, frames: 4 }
112
+ metadata2 = { columns: 2, rows: 3, frames: 6 }
113
+
114
+ allow(RubySpriter::MetadataManager).to receive(:read).and_return(metadata1, metadata2)
115
+ allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
116
+ allow(File).to receive(:size).and_return(1000)
117
+
118
+ expect {
119
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
120
+ }.not_to raise_error
121
+ end
122
+
123
+ it 'raises error when column counts do not match' do
124
+ metadata1 = { columns: 2, rows: 2, frames: 4 }
125
+ metadata2 = { columns: 4, rows: 1, frames: 4 }
126
+
127
+ allow(RubySpriter::MetadataManager).to receive(:read).and_return(metadata1, metadata2)
128
+
129
+ expect {
130
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
131
+ }.to raise_error(RubySpriter::ValidationError, /Column count mismatch/)
132
+ end
133
+
134
+ it 'error message shows expected and actual column counts' do
135
+ metadata1 = { columns: 2, rows: 2, frames: 4 }
136
+ metadata2 = { columns: 4, rows: 1, frames: 4 }
137
+
138
+ allow(RubySpriter::MetadataManager).to receive(:read).and_return(metadata1, metadata2)
139
+
140
+ expect {
141
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
142
+ }.to raise_error(RubySpriter::ValidationError, /Expected 2, found 4/)
143
+ end
144
+
145
+ it 'error message suggests --no-validate-columns flag' do
146
+ metadata1 = { columns: 2, rows: 2, frames: 4 }
147
+ metadata2 = { columns: 4, rows: 1, frames: 4 }
148
+
149
+ allow(RubySpriter::MetadataManager).to receive(:read).and_return(metadata1, metadata2)
150
+
151
+ expect {
152
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
153
+ }.to raise_error(RubySpriter::ValidationError, /--no-validate-columns/)
154
+ end
155
+ end
156
+
157
+ context 'with column validation disabled' do
158
+ let(:consolidator) { described_class.new(validate_columns: false) }
159
+ before { stub_consolidation_success }
160
+
161
+ it 'allows mismatched column counts' do
162
+ metadata1 = { columns: 2, rows: 2, frames: 4 }
163
+ metadata2 = { columns: 4, rows: 1, frames: 4 }
164
+
165
+ allow(RubySpriter::MetadataManager).to receive(:read).and_return(metadata1, metadata2)
166
+ allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
167
+ allow(File).to receive(:size).and_return(1000)
168
+
169
+ expect {
170
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
171
+ }.not_to raise_error
172
+ end
173
+ end
174
+
175
+ context 'with ImageMagick consolidation' do
176
+ before do
177
+ stub_consolidation_success
178
+ allow(RubySpriter::MetadataManager).to receive(:read).and_return(
179
+ { columns: 2, rows: 2, frames: 4 }
180
+ )
181
+ allow(File).to receive(:size).and_return(1000)
182
+ end
183
+
184
+ it 'calls ImageMagick with correct command' do
185
+ expect(Open3).to receive(:capture3) do |cmd|
186
+ expect(cmd).to include('convert')
187
+ expect(cmd).to include('-append')
188
+ expect(cmd).to include(spritesheet1)
189
+ expect(cmd).to include(spritesheet2)
190
+ expect(cmd).to include(output_file)
191
+ ['', '', instance_double(Process::Status, success?: true)]
192
+ end
193
+
194
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
195
+ end
196
+
197
+ it 'uses -append flag for vertical stacking' do
198
+ expect(Open3).to receive(:capture3) do |cmd|
199
+ expect(cmd).to include('-append')
200
+ ['', '', instance_double(Process::Status, success?: true)]
201
+ end
202
+
203
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
204
+ end
205
+
206
+ it 'raises ProcessingError when ImageMagick fails' do
207
+ allow(Open3).to receive(:capture3).and_return(
208
+ ['', 'ImageMagick error', instance_double(Process::Status, success?: false)]
209
+ )
210
+
211
+ expect {
212
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
213
+ }.to raise_error(RubySpriter::ProcessingError, /ImageMagick consolidation failed/)
214
+ end
215
+
216
+ it 'shows debug output when debug option enabled' do
217
+ consolidator = described_class.new(debug: true)
218
+
219
+ allow(RubySpriter::MetadataManager).to receive(:read).and_return(
220
+ { columns: 2, rows: 2, frames: 4 }
221
+ )
222
+ allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
223
+ allow(File).to receive(:size).and_return(1000)
224
+
225
+ expect {
226
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
227
+ }.to output(/DEBUG: ImageMagick command/).to_stdout
228
+ end
229
+ end
230
+
231
+ context 'with successful consolidation' do
232
+ before do
233
+ stub_consolidation_success
234
+ allow(RubySpriter::MetadataManager).to receive(:read).and_return(
235
+ { columns: 2, rows: 2, frames: 4 },
236
+ { columns: 2, rows: 3, frames: 6 }
237
+ )
238
+ allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
239
+ allow(File).to receive(:size).and_return(5000)
240
+ end
241
+
242
+ it 'calculates correct total frames' do
243
+ result = consolidator.consolidate([spritesheet1, spritesheet2], output_file)
244
+
245
+ expect(result[:frames]).to eq(10) # 4 + 6
246
+ end
247
+
248
+ it 'calculates correct row count' do
249
+ result = consolidator.consolidate([spritesheet1, spritesheet2], output_file)
250
+
251
+ # 10 frames / 2 columns = 5 rows
252
+ expect(result[:rows]).to eq(5)
253
+ end
254
+
255
+ it 'uses columns from first spritesheet' do
256
+ result = consolidator.consolidate([spritesheet1, spritesheet2], output_file)
257
+
258
+ expect(result[:columns]).to eq(2)
259
+ end
260
+
261
+ it 'embeds metadata in output file' do
262
+ expect(RubySpriter::MetadataManager).to receive(:embed).with(
263
+ anything,
264
+ output_file,
265
+ hash_including(columns: 2, rows: 5, frames: 10)
266
+ )
267
+
268
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
269
+ end
270
+
271
+ it 'returns hash with correct keys' do
272
+ result = consolidator.consolidate([spritesheet1, spritesheet2], output_file)
273
+
274
+ expect(result).to include(
275
+ output_file: output_file,
276
+ columns: 2,
277
+ rows: 5,
278
+ frames: 10,
279
+ size: 5000
280
+ )
281
+ end
282
+
283
+ it 'returns correct file size' do
284
+ result = consolidator.consolidate([spritesheet1, spritesheet2], output_file)
285
+
286
+ expect(result[:size]).to eq(5000)
287
+ end
288
+ end
289
+
290
+ context 'with 3 spritesheets' do
291
+ before do
292
+ stub_consolidation_success
293
+ allow(RubySpriter::MetadataManager).to receive(:read).and_return(
294
+ { columns: 4, rows: 1, frames: 4 },
295
+ { columns: 4, rows: 1, frames: 4 },
296
+ { columns: 4, rows: 1, frames: 4 }
297
+ )
298
+ allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
299
+ allow(File).to receive(:size).and_return(8000)
300
+ end
301
+
302
+ it 'successfully consolidates all 3 files' do
303
+ expect(Open3).to receive(:capture3) do |cmd|
304
+ expect(cmd).to include(spritesheet1)
305
+ expect(cmd).to include(spritesheet2)
306
+ expect(cmd).to include(spritesheet3)
307
+ ['', '', instance_double(Process::Status, success?: true)]
308
+ end
309
+
310
+ consolidator.consolidate([spritesheet1, spritesheet2, spritesheet3], output_file)
311
+ end
312
+
313
+ it 'calculates correct total frames for 3 files' do
314
+ result = consolidator.consolidate([spritesheet1, spritesheet2, spritesheet3], output_file)
315
+
316
+ expect(result[:frames]).to eq(12) # 4 + 4 + 4
317
+ end
318
+
319
+ it 'calculates correct row count for 3 files' do
320
+ result = consolidator.consolidate([spritesheet1, spritesheet2, spritesheet3], output_file)
321
+
322
+ # 12 frames / 4 columns = 3 rows
323
+ expect(result[:rows]).to eq(3)
324
+ end
325
+ end
326
+
327
+ context 'with output display' do
328
+ before do
329
+ stub_consolidation_success
330
+ allow(RubySpriter::MetadataManager).to receive(:read).and_return(
331
+ { columns: 2, rows: 2, frames: 4 },
332
+ { columns: 2, rows: 3, frames: 6 }
333
+ )
334
+ allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
335
+ allow(File).to receive(:size).and_return(5000)
336
+ end
337
+
338
+ it 'displays success message' do
339
+ expect {
340
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
341
+ }.to output(/Consolidated spritesheet created/).to_stdout
342
+ end
343
+
344
+ it 'displays output file path' do
345
+ expect {
346
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
347
+ }.to output(/Output:.*consolidated\.png/).to_stdout
348
+ end
349
+
350
+ it 'displays grid layout information' do
351
+ expect {
352
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
353
+ }.to output(/Grid Layout:.*Columns: 2.*Rows: 5.*Total Frames: 10/m).to_stdout
354
+ end
355
+
356
+ it 'displays Godot AnimatedSprite2D settings' do
357
+ expect {
358
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
359
+ }.to output(/Godot AnimatedSprite2D Settings:.*HFrames = 2.*VFrames = 5/m).to_stdout
360
+ end
361
+
362
+ it 'displays source breakdown with frame counts' do
363
+ expect {
364
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
365
+ }.to output(/Source Breakdown:.*spritesheet_4x2\.png.*grid \(4 frames\).*spritesheet_6x2\.png.*grid \(6 frames\)/m).to_stdout
366
+ end
367
+
368
+ it 'displays number of combined spritesheets' do
369
+ expect {
370
+ consolidator.consolidate([spritesheet1, spritesheet2], output_file)
371
+ }.to output(/Combined 2 spritesheets/).to_stdout
372
+ end
373
+ end
374
+ end
375
+ end
File without changes