ruby-vips 2.0.14 → 2.1.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.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +42 -0
- data/.github/workflows/test.yml +80 -0
- data/.standard.yml +17 -0
- data/.yardopts +0 -1
- data/CHANGELOG.md +41 -0
- data/Gemfile +3 -1
- data/README.md +42 -41
- data/Rakefile +13 -21
- data/TODO +18 -10
- data/VERSION +1 -1
- data/example/annotate.rb +6 -6
- data/example/connection.rb +26 -0
- data/example/daltonize8.rb +16 -18
- data/example/draw_lines.rb +30 -0
- data/example/example1.rb +5 -6
- data/example/example2.rb +6 -6
- data/example/example3.rb +5 -5
- data/example/example4.rb +4 -4
- data/example/example5.rb +6 -7
- data/example/inheritance_with_refcount.rb +36 -54
- data/example/progress.rb +30 -0
- data/example/thumb.rb +8 -10
- data/example/trim8.rb +5 -5
- data/example/watermark.rb +2 -2
- data/example/wobble.rb +1 -1
- data/lib/ruby-vips.rb +1 -1
- data/lib/vips.rb +199 -93
- data/lib/vips/align.rb +0 -1
- data/lib/vips/angle.rb +0 -1
- data/lib/vips/angle45.rb +0 -1
- data/lib/vips/bandformat.rb +0 -2
- data/lib/vips/blend_mode.rb +29 -27
- data/lib/vips/coding.rb +0 -1
- data/lib/vips/compass_direction.rb +0 -1
- data/lib/vips/connection.rb +46 -0
- data/lib/vips/direction.rb +0 -1
- data/lib/vips/extend.rb +0 -1
- data/lib/vips/gobject.rb +26 -15
- data/lib/vips/gvalue.rb +61 -55
- data/lib/vips/image.rb +455 -282
- data/lib/vips/interesting.rb +0 -1
- data/lib/vips/interpolate.rb +3 -7
- data/lib/vips/interpretation.rb +0 -1
- data/lib/vips/kernel.rb +0 -1
- data/lib/vips/methods.rb +791 -124
- data/lib/vips/mutableimage.rb +173 -0
- data/lib/vips/object.rb +178 -68
- data/lib/vips/operation.rb +277 -130
- data/lib/vips/operationboolean.rb +0 -1
- data/lib/vips/operationcomplex.rb +0 -1
- data/lib/vips/operationcomplex2.rb +0 -1
- data/lib/vips/operationcomplexget.rb +0 -1
- data/lib/vips/operationmath.rb +0 -1
- data/lib/vips/operationmath2.rb +0 -1
- data/lib/vips/operationrelational.rb +0 -1
- data/lib/vips/operationround.rb +0 -1
- data/lib/vips/region.rb +73 -0
- data/lib/vips/size.rb +0 -1
- data/lib/vips/source.rb +88 -0
- data/lib/vips/sourcecustom.rb +89 -0
- data/lib/vips/target.rb +86 -0
- data/lib/vips/targetcustom.rb +77 -0
- data/lib/vips/version.rb +1 -2
- data/ruby-vips.gemspec +26 -21
- metadata +39 -51
- data/.rubocop.yml +0 -10
- data/.rubocop_todo.yml +0 -730
- data/.travis.yml +0 -62
- data/install-vips.sh +0 -26
data/example/annotate.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
|
3
|
-
require
|
3
|
+
require "vips"
|
4
4
|
|
5
|
-
im = Vips::Image.new_from_file ARGV[0], :
|
5
|
+
im = Vips::Image.new_from_file ARGV[0], access: :sequential
|
6
6
|
|
7
|
-
left_text = Vips::Image.text "left corner", :
|
7
|
+
left_text = Vips::Image.text "left corner", dpi: 300
|
8
8
|
left = left_text.embed 50, 50, im.width, 150
|
9
9
|
|
10
|
-
right_text = Vips::Image.text "right corner", :
|
10
|
+
right_text = Vips::Image.text "right corner", dpi: 300
|
11
11
|
right = right_text.embed im.width - right_text.width - 50, 50, im.width, 150
|
12
12
|
|
13
|
-
footer = (left | right).ifthenelse(0, [255, 0, 0], :
|
13
|
+
footer = (left | right).ifthenelse(0, [255, 0, 0], blend: true)
|
14
14
|
|
15
|
-
im = im.insert footer, 0, im.height, :
|
15
|
+
im = im.insert footer, 0, im.height, expand: true
|
16
16
|
|
17
17
|
im.write_to_file ARGV[1]
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require "vips"
|
4
|
+
require "down/http"
|
5
|
+
|
6
|
+
# byte_source = File.open ARGV[0], "rb"
|
7
|
+
# eg. https://images.unsplash.com/photo-1491933382434-500287f9b54b
|
8
|
+
byte_source = Down::Http.open(ARGV[0])
|
9
|
+
|
10
|
+
source = Vips::SourceCustom.new
|
11
|
+
source.on_read do |length|
|
12
|
+
puts "reading #{length} bytes ..."
|
13
|
+
byte_source.read length
|
14
|
+
end
|
15
|
+
source.on_seek do |offset, whence|
|
16
|
+
puts "seeking to #{offset}, #{whence}"
|
17
|
+
byte_source.seek(offset, whence)
|
18
|
+
end
|
19
|
+
|
20
|
+
byte_target = File.open ARGV[1], "wb"
|
21
|
+
target = Vips::TargetCustom.new
|
22
|
+
target.on_write { |chunk| byte_target.write(chunk) }
|
23
|
+
target.on_finish { byte_target.close }
|
24
|
+
|
25
|
+
image = Vips::Image.new_from_source source, "", access: :sequential
|
26
|
+
image.write_to_target target, ".jpg"
|
data/example/daltonize8.rb
CHANGED
@@ -7,20 +7,20 @@
|
|
7
7
|
# http://libvips.blogspot.co.uk/2013/05/daltonize-in-ruby-vips-carrierwave-and.html
|
8
8
|
# for a discussion of this code
|
9
9
|
|
10
|
-
require
|
10
|
+
require "vips"
|
11
11
|
|
12
|
-
#Vips.set_debug true
|
12
|
+
# Vips.set_debug true
|
13
13
|
|
14
14
|
# matrices to convert D65 XYZ to and from bradford cone space
|
15
15
|
xyz_to_brad = [
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
[0.8951, 0.2664, -0.1614],
|
17
|
+
[-0.7502, 1.7135, 0.0367],
|
18
|
+
[0.0389, -0.0685, 1.0296]
|
19
19
|
]
|
20
20
|
brad_to_xyz = [
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
[0.987, -0.147, 0.16],
|
22
|
+
[0.432, 0.5184, 0.0493],
|
23
|
+
[-0.0085, 0.04, 0.968]
|
24
24
|
]
|
25
25
|
|
26
26
|
im = Vips::Image.new_from_file ARGV[0]
|
@@ -29,13 +29,13 @@ im = Vips::Image.new_from_file ARGV[0]
|
|
29
29
|
alpha = nil
|
30
30
|
if im.bands == 4
|
31
31
|
alpha = im[3]
|
32
|
-
im = im.extract_band 0, :
|
32
|
+
im = im.extract_band 0, n: 3
|
33
33
|
end
|
34
34
|
|
35
35
|
begin
|
36
36
|
# import to XYZ with lcms
|
37
37
|
# if there's no profile there, we'll fall back to the thing below
|
38
|
-
xyz = im.icc_import :
|
38
|
+
xyz = im.icc_import embedded: true, pcs: :xyz
|
39
39
|
rescue Vips::Error
|
40
40
|
# nope .. use the built-in converter instead
|
41
41
|
xyz = im.colourspace :xyz
|
@@ -47,9 +47,9 @@ brad = xyz.recomb xyz_to_brad
|
|
47
47
|
# we need rows to sum to 1 in Bradford space --- the matrix in the original
|
48
48
|
# Python code sums to 1.742
|
49
49
|
deut = brad.recomb [
|
50
|
-
|
51
|
-
|
52
|
-
|
50
|
+
[1, 0, 0],
|
51
|
+
[0.7, 0, 0.3],
|
52
|
+
[0, 0, 1]
|
53
53
|
]
|
54
54
|
|
55
55
|
xyz = deut.recomb brad_to_xyz
|
@@ -61,11 +61,9 @@ rgb = xyz.colourspace :srgb
|
|
61
61
|
err = im - rgb
|
62
62
|
|
63
63
|
# add the error back to other channels to make a compensated image
|
64
|
-
im
|
65
|
-
|
66
|
-
|
67
|
-
[0.7, 0, 1]
|
68
|
-
])
|
64
|
+
im += err.recomb([[0, 0, 0],
|
65
|
+
[0.7, 1, 0],
|
66
|
+
[0.7, 0, 1]])
|
69
67
|
|
70
68
|
# reattach any alpha we saved above
|
71
69
|
if alpha
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require "vips"
|
4
|
+
|
5
|
+
# load and stream into memory
|
6
|
+
image = Vips::Image.new_from_file(ARGV[0], access: :sequential).copy_memory
|
7
|
+
|
8
|
+
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
9
|
+
|
10
|
+
lines = image
|
11
|
+
(0..1).step 0.01 do |i|
|
12
|
+
lines = lines.draw_line 255, lines.width * i, 0, 0, lines.height * (1 - i)
|
13
|
+
end
|
14
|
+
|
15
|
+
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
16
|
+
puts "non-destructive took #{ending - starting}s"
|
17
|
+
|
18
|
+
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
19
|
+
|
20
|
+
lines = image
|
21
|
+
lines = lines.mutate do |x|
|
22
|
+
(0..1).step 0.01 do |i|
|
23
|
+
x.draw_line! 255, x.width * i, 0, 0, x.height * (1 - i)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
28
|
+
puts "mutate took #{ending - starting}s"
|
29
|
+
|
30
|
+
lines.write_to_file ARGV[1]
|
data/example/example1.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
|
-
#!/usr/bin/
|
1
|
+
#!/usr/bin/ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "logger"
|
4
|
+
require "vips"
|
5
5
|
|
6
|
-
GLib
|
6
|
+
GLib.logger.level = Logger::DEBUG
|
7
7
|
|
8
|
-
|
8
|
+
Vips::Operation.new "black"
|
9
9
|
|
10
|
-
op = nil
|
11
10
|
GC.start
|
12
11
|
Vips::Operation.print_all
|
data/example/example2.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "logger"
|
4
|
+
require "vips"
|
5
5
|
|
6
6
|
puts ""
|
7
7
|
puts "starting up:"
|
8
8
|
|
9
9
|
# this makes vips keep a list of all active objects which we can print out
|
10
|
-
Vips
|
10
|
+
Vips.leak_set true
|
11
11
|
|
12
12
|
# disable the operation cache
|
13
|
-
Vips
|
13
|
+
Vips.cache_set_max 0
|
14
14
|
|
15
15
|
# GLib::logger.level = Logger::DEBUG
|
16
16
|
|
@@ -20,7 +20,7 @@ n.times do |i|
|
|
20
20
|
puts ""
|
21
21
|
puts "call #{i} ..."
|
22
22
|
out = Vips::Operation.call "black", [200, 300]
|
23
|
-
if out.width != 200
|
23
|
+
if out.width != 200 || out.height != 300
|
24
24
|
puts "bad image result from black"
|
25
25
|
end
|
26
26
|
end
|
@@ -28,7 +28,7 @@ end
|
|
28
28
|
puts ""
|
29
29
|
puts "after #{n} calls:"
|
30
30
|
GC.start
|
31
|
-
Vips::Object
|
31
|
+
Vips::Object.print_all
|
32
32
|
|
33
33
|
puts ""
|
34
34
|
puts "shutting down:"
|
data/example/example3.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
|
3
|
-
require
|
3
|
+
require "vips"
|
4
4
|
|
5
5
|
# this makes vips keep a list of all active objects
|
6
|
-
Vips
|
6
|
+
Vips.leak_set true
|
7
7
|
|
8
8
|
# disable the operation cache
|
9
9
|
# Vips::cache_set_max 0
|
10
10
|
|
11
11
|
# turn on debug logging
|
12
|
-
GLib
|
12
|
+
GLib.logger.level = Logger::DEBUG
|
13
13
|
|
14
|
-
|
14
|
+
10.times do |i|
|
15
15
|
puts "loop #{i} ..."
|
16
16
|
im = Vips::Image.new_from_file ARGV[0]
|
17
|
-
im = im.embed 100, 100, 3000, 3000, :
|
17
|
+
im = im.embed 100, 100, 3000, 3000, extend: :mirror
|
18
18
|
im.write_to_file "x.v"
|
19
19
|
end
|
data/example/example4.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
|
3
|
-
require
|
3
|
+
require "vips"
|
4
4
|
|
5
5
|
# this makes vips keep a list of all active objects
|
6
|
-
Vips
|
6
|
+
Vips.leak_set true
|
7
7
|
|
8
8
|
# disable the operation cache
|
9
|
-
#Vips::cache_set_max 0
|
9
|
+
# Vips::cache_set_max 0
|
10
10
|
|
11
11
|
# turn on debug logging
|
12
|
-
#Vips.set_debug true
|
12
|
+
# Vips.set_debug true
|
13
13
|
|
14
14
|
ARGV.each do |filename|
|
15
15
|
im = Vips::Image.new_from_file filename
|
data/example/example5.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
|
3
|
-
require
|
3
|
+
require "vips"
|
4
4
|
|
5
5
|
# this makes vips keep a list of all active objects
|
6
6
|
# Vips::leak_set true
|
@@ -9,23 +9,22 @@ require 'vips'
|
|
9
9
|
# Vips::cache_set_max 0
|
10
10
|
|
11
11
|
# turn on debug logging
|
12
|
-
#Vips.set_debug true
|
12
|
+
# Vips.set_debug true
|
13
13
|
|
14
14
|
if ARGV.length < 2
|
15
15
|
raise "usage: #{$PROGRAM_NAME}: input-file output-file"
|
16
16
|
end
|
17
17
|
|
18
|
-
im = Vips::Image.new_from_file ARGV[0], :
|
18
|
+
im = Vips::Image.new_from_file ARGV[0], access: :sequential
|
19
19
|
|
20
20
|
im *= [1, 2, 1]
|
21
21
|
|
22
22
|
# we want to be able to specify a scale for the convolution mask, so we have to
|
23
23
|
# make it ourselves
|
24
24
|
# if you are OK with scale=1, you can just pass the array directly to .conv()
|
25
|
-
mask = Vips::Image.new_from_array [
|
26
|
-
|
27
|
-
|
28
|
-
[-1, -1, -1]], 8
|
25
|
+
mask = Vips::Image.new_from_array [[-1, -1, -1],
|
26
|
+
[-1, 16, -1],
|
27
|
+
[-1, -1, -1]], 8
|
29
28
|
im = im.conv mask
|
30
29
|
|
31
30
|
im.write_to_file ARGV[1]
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "ffi"
|
4
|
+
require "forwardable"
|
5
5
|
|
6
6
|
# this is really very crude logging
|
7
7
|
|
@@ -29,13 +29,10 @@ end
|
|
29
29
|
|
30
30
|
module GLib
|
31
31
|
extend FFI::Library
|
32
|
-
ffi_lib
|
32
|
+
ffi_lib "gobject-2.0"
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
def self.set_log_domain(domain)
|
38
|
-
glib_log_domain = domain
|
34
|
+
def self.set_log_domain(_domain)
|
35
|
+
# FIXME: this needs hooking up
|
39
36
|
end
|
40
37
|
|
41
38
|
# we have a set of things we need to inherit in different ways:
|
@@ -71,8 +68,8 @@ module GLib
|
|
71
68
|
def self.included(base)
|
72
69
|
base.class_eval do
|
73
70
|
layout :g_type_instance, :pointer,
|
74
|
-
|
75
|
-
|
71
|
+
:ref_count, :uint,
|
72
|
+
:qdata, :pointer
|
76
73
|
end
|
77
74
|
end
|
78
75
|
end
|
@@ -88,9 +85,8 @@ module GLib
|
|
88
85
|
|
89
86
|
def self.release(ptr)
|
90
87
|
log "GLib::GObject::ManagedStruct.release: unreffing #{ptr}"
|
91
|
-
GLib
|
88
|
+
GLib.g_object_unref(ptr) unless ptr.null?
|
92
89
|
end
|
93
|
-
|
94
90
|
end
|
95
91
|
|
96
92
|
# the plain struct ... cast with this
|
@@ -101,7 +97,6 @@ module GLib
|
|
101
97
|
log "GLib::GObject::Struct.new: #{ptr}"
|
102
98
|
super
|
103
99
|
end
|
104
|
-
|
105
100
|
end
|
106
101
|
|
107
102
|
# don't allow ptr == nil, we never want to allocate a GObject struct
|
@@ -121,7 +116,7 @@ module GLib
|
|
121
116
|
|
122
117
|
class << self
|
123
118
|
def ffi_struct
|
124
|
-
|
119
|
+
const_get(:Struct)
|
125
120
|
end
|
126
121
|
end
|
127
122
|
|
@@ -132,23 +127,21 @@ module GLib
|
|
132
127
|
|
133
128
|
class << self
|
134
129
|
def ffi_managed_struct
|
135
|
-
|
130
|
+
const_get(:ManagedStruct)
|
136
131
|
end
|
137
132
|
end
|
138
|
-
|
139
133
|
end
|
140
134
|
|
141
135
|
# :gtype will usually be 64-bit, but will be 32-bit on 32-bit Windows
|
142
136
|
typedef :ulong, :GType
|
143
|
-
|
144
137
|
end
|
145
138
|
|
146
139
|
module Vips
|
147
140
|
extend FFI::Library
|
148
|
-
ffi_lib
|
141
|
+
ffi_lib "vips"
|
149
142
|
|
150
143
|
LOG_DOMAIN = "VIPS"
|
151
|
-
GLib
|
144
|
+
GLib.set_log_domain(LOG_DOMAIN)
|
152
145
|
|
153
146
|
# need to repeat this
|
154
147
|
typedef :ulong, :GType
|
@@ -160,19 +153,19 @@ module Vips
|
|
160
153
|
attach_function :vips_error_clear, [], :void
|
161
154
|
|
162
155
|
def self.get_error
|
163
|
-
errstr = Vips
|
164
|
-
Vips
|
156
|
+
errstr = Vips.vips_error_buffer
|
157
|
+
Vips.vips_error_clear
|
165
158
|
errstr
|
166
159
|
end
|
167
160
|
|
168
|
-
if Vips
|
169
|
-
puts Vips
|
161
|
+
if Vips.vips_init($0) != 0
|
162
|
+
puts Vips.get_error
|
170
163
|
exit 1
|
171
164
|
end
|
172
165
|
|
173
|
-
at_exit
|
174
|
-
Vips
|
175
|
-
|
166
|
+
at_exit do
|
167
|
+
Vips.vips_shutdown
|
168
|
+
end
|
176
169
|
|
177
170
|
attach_function :vips_object_print_all, [], :void
|
178
171
|
attach_function :vips_leak_set, [:int], :void
|
@@ -189,22 +182,21 @@ module Vips
|
|
189
182
|
end
|
190
183
|
|
191
184
|
class VipsObject < GLib::GObject
|
192
|
-
|
193
185
|
# the layout of the VipsObject struct
|
194
186
|
module VipsObjectLayout
|
195
187
|
def self.included(base)
|
196
188
|
base.class_eval do
|
197
189
|
# don't actually need most of these, remove them later
|
198
190
|
layout :parent, GLib::GObject::Struct,
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
191
|
+
:constructed, :int,
|
192
|
+
:static_object, :int,
|
193
|
+
:argument_table, :pointer,
|
194
|
+
:nickname, :string,
|
195
|
+
:description, :string,
|
196
|
+
:preclose, :int,
|
197
|
+
:close, :int,
|
198
|
+
:postclose, :int,
|
199
|
+
:local_memory, :size_t
|
208
200
|
end
|
209
201
|
end
|
210
202
|
end
|
@@ -216,7 +208,6 @@ module Vips
|
|
216
208
|
log "Vips::VipsObject::Struct.new: #{ptr}"
|
217
209
|
super
|
218
210
|
end
|
219
|
-
|
220
211
|
end
|
221
212
|
|
222
213
|
class ManagedStruct < GLib::GObject::ManagedStruct
|
@@ -226,13 +217,10 @@ module Vips
|
|
226
217
|
log "Vips::VipsObject::ManagedStruct.new: #{ptr}"
|
227
218
|
super
|
228
219
|
end
|
229
|
-
|
230
220
|
end
|
231
|
-
|
232
221
|
end
|
233
222
|
|
234
223
|
class VipsImage < VipsObject
|
235
|
-
|
236
224
|
# the layout of the VipsImage struct
|
237
225
|
module VipsImageLayout
|
238
226
|
def self.included(base)
|
@@ -250,7 +238,6 @@ module Vips
|
|
250
238
|
log "Vips::VipsImage::Struct.new: #{ptr}"
|
251
239
|
super
|
252
240
|
end
|
253
|
-
|
254
241
|
end
|
255
242
|
|
256
243
|
class ManagedStruct < VipsObject::ManagedStruct
|
@@ -260,27 +247,22 @@ module Vips
|
|
260
247
|
log "Vips::VipsImage::ManagedStruct.new: #{ptr}"
|
261
248
|
super
|
262
249
|
end
|
263
|
-
|
264
250
|
end
|
265
251
|
|
266
252
|
def self.new_partial
|
267
|
-
VipsImage.new(Vips
|
253
|
+
VipsImage.new(Vips.vips_image_new)
|
268
254
|
end
|
269
|
-
|
270
255
|
end
|
271
256
|
|
272
257
|
attach_function :vips_image_new, [], :pointer
|
273
|
-
|
274
258
|
end
|
275
259
|
|
276
260
|
puts "creating image"
|
277
|
-
begin
|
278
|
-
x = Vips::VipsImage.new_partial
|
279
|
-
puts "x = #{x}"
|
280
|
-
puts ""
|
281
|
-
puts "x[:parent] = #{x[:parent]}"
|
282
|
-
puts ""
|
283
|
-
puts "x[:parent][:description] = #{x[:parent][:description]}"
|
284
|
-
puts ""
|
285
|
-
end
|
286
261
|
|
262
|
+
x = Vips::VipsImage.new_partial
|
263
|
+
puts "x = #{x}"
|
264
|
+
puts ""
|
265
|
+
puts "x[:parent] = #{x[:parent]}"
|
266
|
+
puts ""
|
267
|
+
puts "x[:parent][:description] = #{x[:parent][:description]}"
|
268
|
+
puts ""
|