oily_png 1.0.0.rc1 → 1.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,6 +14,11 @@ void Init_oily_png() {
14
14
  // Setup Color module
15
15
  VALUE OilyPNG_Color = rb_define_module_under(OilyPNG, "Color");
16
16
  rb_define_method(OilyPNG_Color, "compose_quick", oily_png_color_compose_quick, 2);
17
+
18
+ // Setup Operations module
19
+ VALUE OilyPNG_Operations = rb_define_module_under(OilyPNG, "Operations");
20
+ rb_define_method(OilyPNG_Operations, "compose!", oily_png_compose_bang, -1);
21
+ rb_define_method(OilyPNG_Operations, "replace!", oily_png_replace_bang, -1);
17
22
  }
18
23
 
19
24
  char oily_png_samples_per_pixel(char color_mode) {
@@ -28,6 +28,7 @@ typedef unsigned char BYTE; // Bytes use 8 bits unsigned integers
28
28
  #include "png_decoding.h"
29
29
  #include "png_encoding.h"
30
30
  #include "color.h"
31
+ #include "operations.h"
31
32
 
32
33
  /*
33
34
  Initialize the extension by creating the OilyPNG modules, and registering
@@ -0,0 +1,122 @@
1
+ #include "oily_png_ext.h"
2
+
3
+ void oily_png_check_size_constraints(long self_width, long self_height, long other_width, long other_height, long offset_x, long offset_y){
4
+ // For now, these raise a standard runtime error. They should however raise custom exception classes (OutOfBounds)
5
+ if(self_width < other_width + offset_x){
6
+ rb_raise(rb_eRuntimeError, "Background image width is too small!");
7
+ }
8
+ if(self_height < other_height + offset_y){
9
+ rb_raise(rb_eRuntimeError, "Background image height is too small!");
10
+ }
11
+ }
12
+
13
+ VALUE oily_png_compose_bang(int argc, VALUE *argv, VALUE self) {
14
+ // Corresponds to the other image(foreground) that we want to compose onto this one(background).
15
+ VALUE other;
16
+
17
+ // The offsets are optional arguments, so these may or may not be null pointers.
18
+ // We'll prefix them with 'opt' to identify this.
19
+ VALUE opt_offset_x;
20
+ VALUE opt_offset_y;
21
+
22
+ // Scan the passed in arguments, and populate the above-declared variables. Notice that '12'
23
+ // specifies that oily_png_compose_bang takes in 1 required parameter, and 2 optional ones (the offsets)
24
+ rb_scan_args(argc, argv, "12", &other,&opt_offset_x,&opt_offset_y);
25
+
26
+ // Regardless of whether offsets were provided, we must specify a default value for them since they will
27
+ // be used in calculating the position of the composed element.
28
+ long offset_x = 0;
29
+ long offset_y = 0;
30
+
31
+ // If offsets were provided, then the opt_offset_* variables will not be null pointers. FIXNUM_P checks
32
+ // whether they point to a fixnum object. If they do, then we can safely assign our offset_* variables to the values.
33
+ if(FIXNUM_P(opt_offset_x)){
34
+ offset_x = FIX2LONG(opt_offset_x);
35
+ }
36
+ if(FIXNUM_P(opt_offset_y)){
37
+ offset_y = FIX2LONG(opt_offset_y);
38
+ }
39
+
40
+ // Get the dimension data for both foreground and background images.
41
+ long self_width = FIX2LONG(rb_funcall(self, rb_intern("width"), 0));
42
+ long self_height = FIX2LONG(rb_funcall(self, rb_intern("height"), 0));
43
+ long other_width = FIX2LONG(rb_funcall(other, rb_intern("width"), 0));
44
+ long other_height = FIX2LONG(rb_funcall(other, rb_intern("height"), 0));
45
+
46
+ // Make sure that the 'other' image fits within the current image. If it doesn't, an exception is raised
47
+ // and the operation should be aborted.
48
+ oily_png_check_size_constraints( self_width, self_height, other_width, other_height, offset_x, offset_y );
49
+
50
+ // Get the pixel data for both the foreground(other) and background(self) pixels.
51
+ VALUE* bg_pixels = RARRAY_PTR(rb_funcall(self, rb_intern("pixels"), 0));
52
+ VALUE* fg_pixels = RARRAY_PTR(rb_funcall(other, rb_intern("pixels"), 0));
53
+
54
+ long x = 0;
55
+ long y = 0;
56
+ long bg_index = 0; // corresponds to the current index in the bg_pixels array.
57
+ for( y = 0; y < other_height; y++ ){
58
+ for( x = 0; x < other_width; x++ ){
59
+ // We need to find the value of bg_index twice, so we only calculate and store it once.
60
+ bg_index = ( x + offset_x ) + ( y + offset_y ) * self_width;
61
+ // Replace the background pixel with the composition of background + foreground
62
+ bg_pixels[bg_index] = UINT2NUM( oily_png_compose_color( NUM2UINT( fg_pixels[x+ y * other_width] ), NUM2UINT( bg_pixels[bg_index] ) ) );
63
+ }
64
+ }
65
+ return self;
66
+ }
67
+
68
+
69
+ VALUE oily_png_replace_bang(int argc, VALUE *argv, VALUE self) {
70
+ // Corresponds to the other image(foreground) that we want to compose onto this one(background).
71
+ VALUE other;
72
+
73
+ // The offsets are optional arguments, so these may or may not be null pointers.
74
+ // We'll prefix them with 'opt' to identify this.
75
+ VALUE opt_offset_x;
76
+ VALUE opt_offset_y;
77
+
78
+ // Scan the passed in arguments, and populate the above-declared variables. Notice that '12'
79
+ // specifies that oily_png_compose_bang takes in 1 required parameter, and 2 optional ones (the offsets)
80
+ rb_scan_args(argc, argv, "12", &other,&opt_offset_x,&opt_offset_y);
81
+
82
+ // Regardless of whether offsets were provided, we must specify a default value for them since they will
83
+ // be used in calculating the position of the composed element.
84
+ long offset_x = 0;
85
+ long offset_y = 0;
86
+
87
+ // If offsets were provided, then the opt_offset_* variables will not be null pointers. FIXNUM_P checks
88
+ // whether they point to a fixnum object. If they do, then we can safely assign our offset_* variables to the values.
89
+ if(FIXNUM_P(opt_offset_x)){
90
+ offset_x = FIX2LONG(opt_offset_x);
91
+ }
92
+ if(FIXNUM_P(opt_offset_y)){
93
+ offset_y = FIX2LONG(opt_offset_y);
94
+ }
95
+
96
+ // Get the dimension data for both foreground and background images.
97
+ long self_width = FIX2LONG(rb_funcall(self, rb_intern("width"), 0));
98
+ long self_height = FIX2LONG(rb_funcall(self, rb_intern("height"), 0));
99
+ long other_width = FIX2LONG(rb_funcall(other, rb_intern("width"), 0));
100
+ long other_height = FIX2LONG(rb_funcall(other, rb_intern("height"), 0));
101
+
102
+ // Make sure that the 'other' image fits within the current image. If it doesn't, an exception is raised
103
+ // and the operation should be aborted.
104
+ oily_png_check_size_constraints( self_width, self_height, other_width, other_height, offset_x, offset_y );
105
+
106
+ // Get the pixel data for both the foreground(other) and background(self) pixels.
107
+ VALUE* bg_pixels = RARRAY_PTR(rb_funcall(self, rb_intern("pixels"), 0));
108
+ VALUE* fg_pixels = RARRAY_PTR(rb_funcall(other, rb_intern("pixels"), 0));
109
+
110
+ long x = 0;
111
+ long y = 0;
112
+ long bg_index = 0; // corresponds to the current index in the bg_pixels array.
113
+ for( y = 0; y < other_height; y++ ){
114
+ for( x = 0; x < other_width; x++ ){
115
+ // We need to find the value of bg_index twice, so we only calculate and store it once.
116
+ bg_index = ( x + offset_x ) + ( y + offset_y ) * self_width;
117
+ // Replace the background pixel with the composition of background + foreground
118
+ bg_pixels[bg_index] = fg_pixels[x+ y * other_width];
119
+ }
120
+ }
121
+ return self;
122
+ }
@@ -0,0 +1,36 @@
1
+ #ifndef OILY_PNG_OPERATIONS_H
2
+ #define OILY_PNG_OPERATIONS_H
3
+
4
+ /*
5
+ Checks whether an image 'other' can fits into 'self'. Takes offset into account.
6
+ An exception is raised if the check fails.
7
+
8
+ Instead of taking in an object 'self' and an object 'other' and then calculating their parameters,
9
+ we ask for the respective height and width directly. This is because these variables will need to be calculated
10
+ by 'rb_intern()' within the method calling oily_png_check_size_constraints (ex: oily_png_compose), so there's no
11
+ use in calculating them twice.
12
+
13
+ */
14
+ void oily_png_check_size_constraints(long self_width, long self_height, long other_width, long other_height, long offset_x, long offset_y);
15
+
16
+ /*
17
+ C replacement method for composing another image onto this image using alpha blending.
18
+
19
+ TODO: Implement functionality with ChunkyPNG and OilyPNG so that an image can be composited onto another
20
+ regardless of its size: however, only the intersecting elements of both images should be mixed.
21
+
22
+ This method should replace ChunkyPNG::Canvas.compose!
23
+ */
24
+ VALUE oily_png_compose_bang(int argc, VALUE *argv, VALUE c);
25
+
26
+ /*
27
+ C replacement method for composing another image onto this image by simply replacing pixels.
28
+
29
+ TODO: Implement functionality with ChunkyPNG and OilyPNG so that an image can be composited onto another
30
+ regardless of its size: however, only the intersecting elements of both images should be mixed.
31
+
32
+ This method should replace ChunkyPNG::Canvas.replace!
33
+ */
34
+ VALUE oily_png_replace_bang(int argc, VALUE *argv, VALUE c);
35
+
36
+ #endif
@@ -354,14 +354,14 @@ VALUE oily_png_decode_png_image_pass(VALUE self, VALUE stream, VALUE width, VALU
354
354
 
355
355
  // Copy the bytes for this pass from the stream to a separate location
356
356
  // so we can work on this byte array directly.
357
- BYTE* bytes = ALLOCA_N(BYTE, pass_size);
357
+ BYTE* bytes = ALLOC_N(BYTE, pass_size);
358
358
  memcpy(bytes, RSTRING_PTR(stream) + FIX2LONG(start_pos), pass_size);
359
359
 
360
360
  // Get the decoding palette for indexed images.
361
361
  VALUE decoding_palette = Qnil;
362
362
  if (FIX2INT(color_mode) == OILY_PNG_COLOR_INDEXED) {
363
363
  decoding_palette = oily_png_decode_palette(self);
364
- }
364
+ }
365
365
 
366
366
  // Select the scanline decoder function for this color mode and bit depth.
367
367
  scanline_decoder_func scanline_decoder = oily_png_decode_scanline_func(FIX2INT(color_mode), FIX2INT(depth));
@@ -387,8 +387,10 @@ VALUE oily_png_decode_png_image_pass(VALUE self, VALUE stream, VALUE width, VALU
387
387
  bytes[line_start] = OILY_PNG_FILTER_NONE;
388
388
  scanline_decoder(pixels, bytes, line_start, FIX2LONG(width), decoding_palette);
389
389
  }
390
+
391
+ xfree(bytes);
390
392
  }
391
-
393
+
392
394
  // Now, return a new ChunkyPNG::Canvas instance with the decoded pixels.
393
395
  return rb_funcall(self, rb_intern("new"), 3, width, height, pixels);
394
396
  }
@@ -5,6 +5,7 @@ module OilyPNG
5
5
  class Canvas < ChunkyPNG::Canvas
6
6
  extend OilyPNG::PNGDecoding
7
7
  include OilyPNG::PNGEncoding
8
+ include OilyPNG::Operations
8
9
  end
9
10
 
10
11
  module Color
data/lib/oily_png.rb CHANGED
@@ -2,7 +2,7 @@ require 'chunky_png'
2
2
 
3
3
  module OilyPNG
4
4
 
5
- VERSION = "1.0.0.rc1"
5
+ VERSION = "1.0.0.rc2"
6
6
 
7
7
  def self.included(base)
8
8
  base::Canvas.send(:extend, OilyPNG::PNGDecoding)
data/oily_png.gemspec CHANGED
@@ -4,8 +4,8 @@ Gem::Specification.new do |s|
4
4
 
5
5
  # Do not change the version and date fields by hand. This will be done
6
6
  # automatically by the gem release script.
7
- s.version = "1.0.0.rc1"
8
- s.date = "2011-02-24"
7
+ s.version = "1.0.0.rc2"
8
+ s.date = "2011-03-02"
9
9
 
10
10
  s.summary = "Native mixin to speed up ChunkyPNG"
11
11
  s.description = <<-EOT
@@ -30,6 +30,6 @@ Gem::Specification.new do |s|
30
30
 
31
31
  # Do not change the files and test_files fields by hand. This will be done
32
32
  # automatically by the gem release script.
33
- s.files = %w(.gitignore .infinity_test Gemfile LICENSE README.rdoc Rakefile ext/oily_png/color.c ext/oily_png/color.h ext/oily_png/extconf.rb ext/oily_png/oily_png_ext.c ext/oily_png/oily_png_ext.h ext/oily_png/png_decoding.c ext/oily_png/png_decoding.h ext/oily_png/png_encoding.c ext/oily_png/png_encoding.h lib/oily_png.rb lib/oily_png/canvas.rb oily_png.gemspec spec/color_spec.rb spec/decoding_spec.rb spec/encoding_spec.rb spec/resources/basi0g01.png spec/resources/basi0g02.png spec/resources/basi0g04.png spec/resources/basi0g08.png spec/resources/basi0g16.png spec/resources/basi2c08.png spec/resources/basi2c16.png spec/resources/basi3p01.png spec/resources/basi3p02.png spec/resources/basi3p04.png spec/resources/basi3p08.png spec/resources/basi4a08.png spec/resources/basi4a16.png spec/resources/basi6a08.png spec/resources/basi6a16.png spec/resources/basn0g01.png spec/resources/basn0g02.png spec/resources/basn0g04.png spec/resources/basn0g08.png spec/resources/basn0g16.png spec/resources/basn2c08.png spec/resources/basn2c16.png spec/resources/basn3p01.png spec/resources/basn3p02.png spec/resources/basn3p04.png spec/resources/basn3p08.png spec/resources/basn4a08.png spec/resources/basn4a16.png spec/resources/basn6a08.png spec/resources/basn6a16.png spec/resources/gray.png spec/resources/interlaced.png spec/resources/nonsquare.png spec/resources/s01i3p01.png spec/resources/s01n3p01.png spec/resources/s02i3p01.png spec/resources/s02n3p01.png spec/resources/s03i3p01.png spec/resources/s03n3p01.png spec/resources/s04i3p01.png spec/resources/s04n3p01.png spec/resources/s05i3p02.png spec/resources/s05n3p02.png spec/resources/s06i3p02.png spec/resources/s06n3p02.png spec/resources/s07i3p02.png spec/resources/s07n3p02.png spec/resources/s08i3p02.png spec/resources/s08n3p02.png spec/resources/s09i3p02.png spec/resources/s09n3p02.png spec/resources/s32i3p04.png spec/resources/s32n3p04.png spec/resources/s33i3p04.png spec/resources/s33n3p04.png spec/resources/s34i3p04.png spec/resources/s34n3p04.png spec/resources/s35i3p04.png spec/resources/s35n3p04.png spec/resources/s36i3p04.png spec/resources/s36n3p04.png spec/resources/s37i3p04.png spec/resources/s37n3p04.png spec/resources/s38i3p04.png spec/resources/s38n3p04.png spec/resources/s39i3p04.png spec/resources/s39n3p04.png spec/resources/s40i3p04.png spec/resources/s40n3p04.png spec/resources/square.png spec/spec_helper.rb tasks/github-gem.rake tasks/testing.rake)
34
- s.test_files = %w(spec/color_spec.rb spec/decoding_spec.rb spec/encoding_spec.rb)
33
+ s.files = %w(.gitignore .infinity_test Gemfile LICENSE README.rdoc Rakefile ext/oily_png/color.c ext/oily_png/color.h ext/oily_png/extconf.rb ext/oily_png/oily_png_ext.c ext/oily_png/oily_png_ext.h ext/oily_png/operations.c ext/oily_png/operations.h ext/oily_png/png_decoding.c ext/oily_png/png_decoding.h ext/oily_png/png_encoding.c ext/oily_png/png_encoding.h lib/oily_png.rb lib/oily_png/canvas.rb oily_png.gemspec spec/color_spec.rb spec/decoding_spec.rb spec/encoding_spec.rb spec/operations_spec.rb spec/resources/basi0g01.png spec/resources/basi0g02.png spec/resources/basi0g04.png spec/resources/basi0g08.png spec/resources/basi0g16.png spec/resources/basi2c08.png spec/resources/basi2c16.png spec/resources/basi3p01.png spec/resources/basi3p02.png spec/resources/basi3p04.png spec/resources/basi3p08.png spec/resources/basi4a08.png spec/resources/basi4a16.png spec/resources/basi6a08.png spec/resources/basi6a16.png spec/resources/basn0g01.png spec/resources/basn0g02.png spec/resources/basn0g04.png spec/resources/basn0g08.png spec/resources/basn0g16.png spec/resources/basn2c08.png spec/resources/basn2c16.png spec/resources/basn3p01.png spec/resources/basn3p02.png spec/resources/basn3p04.png spec/resources/basn3p08.png spec/resources/basn4a08.png spec/resources/basn4a16.png spec/resources/basn6a08.png spec/resources/basn6a16.png spec/resources/composited.png spec/resources/gray.png spec/resources/interlaced.png spec/resources/nonsquare.png spec/resources/operations.png spec/resources/replaced.png spec/resources/s01i3p01.png spec/resources/s01n3p01.png spec/resources/s02i3p01.png spec/resources/s02n3p01.png spec/resources/s03i3p01.png spec/resources/s03n3p01.png spec/resources/s04i3p01.png spec/resources/s04n3p01.png spec/resources/s05i3p02.png spec/resources/s05n3p02.png spec/resources/s06i3p02.png spec/resources/s06n3p02.png spec/resources/s07i3p02.png spec/resources/s07n3p02.png spec/resources/s08i3p02.png spec/resources/s08n3p02.png spec/resources/s09i3p02.png spec/resources/s09n3p02.png spec/resources/s32i3p04.png spec/resources/s32n3p04.png spec/resources/s33i3p04.png spec/resources/s33n3p04.png spec/resources/s34i3p04.png spec/resources/s34n3p04.png spec/resources/s35i3p04.png spec/resources/s35n3p04.png spec/resources/s36i3p04.png spec/resources/s36n3p04.png spec/resources/s37i3p04.png spec/resources/s37n3p04.png spec/resources/s38i3p04.png spec/resources/s38n3p04.png spec/resources/s39i3p04.png spec/resources/s39n3p04.png spec/resources/s40i3p04.png spec/resources/s40n3p04.png spec/resources/square.png spec/spec_helper.rb tasks/github-gem.rake tasks/testing.rake)
34
+ s.test_files = %w(spec/color_spec.rb spec/decoding_spec.rb spec/encoding_spec.rb spec/operations_spec.rb)
35
35
  end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe OilyPNG::Operations do
4
+
5
+ describe '#compose!' do
6
+ subject { oily_reference_canvas('operations') }
7
+
8
+ it "should compose two images without offset exactly the same way as ChunkyPNG" do
9
+ subcanvas = ChunkyPNG::Canvas.new(4, 8, ChunkyPNG::Color.rgba(0, 0, 0, 75))
10
+ subject.compose!(subcanvas)
11
+ ChunkyPNG::Canvas.from_canvas(subject).should == reference_canvas('operations').compose(subcanvas)
12
+ end
13
+
14
+ it "should compose two images with offset exactly the same way as ChunkyPNG" do
15
+ subject.compose!(ChunkyPNG::Canvas.new(4, 8, ChunkyPNG::Color.rgba(0, 0, 0, 75)), 8, 4)
16
+ subject.should == oily_reference_canvas('composited')
17
+ end
18
+
19
+ it "should return itself" do
20
+ subject.compose!(OilyPNG::Canvas.new(1,1)).should equal(subject)
21
+ end
22
+
23
+ it "should raise an exception when the pixels to compose fall outside the image" do
24
+ # For now this raises a runtime error, but it should probably raise a ChunkyPNG::OutOfBounds error
25
+ lambda { subject.compose!(OilyPNG::Canvas.new(1,1), 16, 16) }.should raise_error
26
+ end
27
+ end
28
+
29
+ describe '#replace!' do
30
+ subject { oily_reference_canvas('operations') }
31
+
32
+ it "should compose two images without offset exactly the same way as ChunkyPNG" do
33
+ subcanvas = ChunkyPNG::Canvas.new(3, 2, ChunkyPNG::Color.rgb(200, 255, 0))
34
+ subject.replace!(subcanvas)
35
+ ChunkyPNG::Canvas.from_canvas(subject).should == reference_canvas('operations').replace(subcanvas)
36
+ end
37
+
38
+ it "should compose two images with offset exactly the same way as ChunkyPNG" do
39
+ subject.replace!(ChunkyPNG::Canvas.new(3, 2, ChunkyPNG::Color.rgb(200, 255, 0)), 5, 4)
40
+ subject.should == oily_reference_canvas('replaced')
41
+ end
42
+
43
+ it "should return itself" do
44
+ subject.replace!(OilyPNG::Canvas.new(1,1)).should equal(subject)
45
+ end
46
+
47
+ it "should raise an exception when the pixels to compose fall outside the image" do
48
+ # For now this raises a runtime error, but it should probably raise a ChunkyPNG::OutOfBounds error
49
+ lambda { subject.replace!(OilyPNG::Canvas.new(1,1), 16, 16) }.should raise_error
50
+ end
51
+ end
52
+ end
Binary file
Binary file
Binary file
data/spec/spec_helper.rb CHANGED
@@ -28,6 +28,10 @@ module CanvasHelper
28
28
  def reference_canvas(name)
29
29
  ChunkyPNG::Canvas.from_file(resource_file("#{name}.png"))
30
30
  end
31
+
32
+ def oily_reference_canvas(name)
33
+ OilyPNG::Canvas.from_file(resource_file("#{name}.png"))
34
+ end
31
35
  end
32
36
 
33
37
  RSpec.configure do |config|
metadata CHANGED
@@ -6,8 +6,8 @@ version: !ruby/object:Gem::Version
6
6
  - 1
7
7
  - 0
8
8
  - 0
9
- - rc1
10
- version: 1.0.0.rc1
9
+ - rc2
10
+ version: 1.0.0.rc2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Willem van Bergen
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-02-24 00:00:00 -05:00
18
+ date: 2011-03-02 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -94,6 +94,8 @@ files:
94
94
  - ext/oily_png/extconf.rb
95
95
  - ext/oily_png/oily_png_ext.c
96
96
  - ext/oily_png/oily_png_ext.h
97
+ - ext/oily_png/operations.c
98
+ - ext/oily_png/operations.h
97
99
  - ext/oily_png/png_decoding.c
98
100
  - ext/oily_png/png_decoding.h
99
101
  - ext/oily_png/png_encoding.c
@@ -104,6 +106,7 @@ files:
104
106
  - spec/color_spec.rb
105
107
  - spec/decoding_spec.rb
106
108
  - spec/encoding_spec.rb
109
+ - spec/operations_spec.rb
107
110
  - spec/resources/basi0g01.png
108
111
  - spec/resources/basi0g02.png
109
112
  - spec/resources/basi0g04.png
@@ -134,9 +137,12 @@ files:
134
137
  - spec/resources/basn4a16.png
135
138
  - spec/resources/basn6a08.png
136
139
  - spec/resources/basn6a16.png
140
+ - spec/resources/composited.png
137
141
  - spec/resources/gray.png
138
142
  - spec/resources/interlaced.png
139
143
  - spec/resources/nonsquare.png
144
+ - spec/resources/operations.png
145
+ - spec/resources/replaced.png
140
146
  - spec/resources/s01i3p01.png
141
147
  - spec/resources/s01n3p01.png
142
148
  - spec/resources/s02i3p01.png
@@ -221,3 +227,4 @@ test_files:
221
227
  - spec/color_spec.rb
222
228
  - spec/decoding_spec.rb
223
229
  - spec/encoding_spec.rb
230
+ - spec/operations_spec.rb