ohboyohboyohboy-monocle-print 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/History.txt +4 -0
- data/Manifest.txt +21 -0
- data/README.txt +40 -0
- data/Rakefile +21 -0
- data/lib/monocle-print.rb +87 -0
- data/lib/monocle-print/atomic.rb +461 -0
- data/lib/monocle-print/geometry.rb +66 -0
- data/lib/monocle-print/graphics.rb +86 -0
- data/lib/monocle-print/graphics/registry.rb +63 -0
- data/lib/monocle-print/layout.rb +58 -0
- data/lib/monocle-print/list.rb +138 -0
- data/lib/monocle-print/output-device.rb +529 -0
- data/lib/monocle-print/presentation.rb +132 -0
- data/lib/monocle-print/progress.rb +192 -0
- data/lib/monocle-print/table.rb +229 -0
- data/lib/monocle-print/table/column.rb +98 -0
- data/lib/monocle-print/table/members.rb +252 -0
- data/lib/monocle-print/table/segments.rb +62 -0
- data/lib/monocle-print/terminal-escapes.rb +205 -0
- data/lib/monocle-print/utils.rb +23 -0
- metadata +98 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
module MonoclePrint
|
5
|
+
class Table
|
6
|
+
class Column
|
7
|
+
include MonoclePrint
|
8
|
+
|
9
|
+
def initialize( table, index )
|
10
|
+
@table = table
|
11
|
+
@index = index
|
12
|
+
@wrap = false
|
13
|
+
@flow = false
|
14
|
+
@alignment = :left
|
15
|
+
@fixed_width = nil
|
16
|
+
@cached_width = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :table, :index
|
20
|
+
attr_accessor :alignment
|
21
|
+
|
22
|
+
for m in %w( wrap flow )
|
23
|
+
attr_accessor( m )
|
24
|
+
alias_method( "#{m}?", m )
|
25
|
+
undef_method( m )
|
26
|
+
end
|
27
|
+
|
28
|
+
def malleable?
|
29
|
+
@wrap and @flow
|
30
|
+
end
|
31
|
+
|
32
|
+
def malleable=( bool )
|
33
|
+
@wrap = @flow = bool
|
34
|
+
end
|
35
|
+
|
36
|
+
def fixed?
|
37
|
+
not malleable?
|
38
|
+
end
|
39
|
+
|
40
|
+
def fixed=( bool )
|
41
|
+
self.malleable = !bool
|
42
|
+
end
|
43
|
+
|
44
|
+
def title
|
45
|
+
@table.titles[ @index ]
|
46
|
+
end
|
47
|
+
|
48
|
+
def cells
|
49
|
+
@table.grep( Row ) { | row | row[ @index ] || Line( '' ) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def previous_column
|
53
|
+
@index.zero? ? nil : @table.columns[ @index - 1 ]
|
54
|
+
end
|
55
|
+
|
56
|
+
def next_column
|
57
|
+
@table.columns[ @index + 1 ]
|
58
|
+
end
|
59
|
+
|
60
|
+
def first?
|
61
|
+
@index.zero?
|
62
|
+
end
|
63
|
+
|
64
|
+
def last?
|
65
|
+
@index == (table.columns.length - 1)
|
66
|
+
end
|
67
|
+
|
68
|
+
def prepare( cell_text )
|
69
|
+
cell_text = cell_text ? cell_text.dup : Text( ' ' )
|
70
|
+
@flow and cell_text.reflow!( false )
|
71
|
+
@wrap and cell_text = cell_text.wrap( width - 1 )
|
72
|
+
cell_text.align!( @alignment, width )
|
73
|
+
end
|
74
|
+
|
75
|
+
def width=( w )
|
76
|
+
@fixed_width = Utils.at_least( w.to_i, 1 )
|
77
|
+
end
|
78
|
+
|
79
|
+
def width
|
80
|
+
@fixed_width or @cached_width or calculate_width
|
81
|
+
end
|
82
|
+
|
83
|
+
def calculate_metrics
|
84
|
+
@cached_width = @fixed_width || calculate_width
|
85
|
+
end
|
86
|
+
|
87
|
+
def clear_metrics
|
88
|
+
@cached_width = nil
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
|
93
|
+
def calculate_width
|
94
|
+
@table.grep( Row ) { |r| c = r[ @index ] and c.width or 0 }.max || 0
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
module MonoclePrint
|
5
|
+
class Table
|
6
|
+
class Member
|
7
|
+
include MonoclePrint
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_reader :member_name
|
12
|
+
def define( member_name, sup = self, &body )
|
13
|
+
klass =
|
14
|
+
Class.new( sup ) do
|
15
|
+
@member_name = member_name
|
16
|
+
class_eval( &body )
|
17
|
+
end
|
18
|
+
|
19
|
+
define_method( "#{ member_name }!" ) do |*args|
|
20
|
+
klass.new( @table, *args ) { |m| link( m ) }.tail
|
21
|
+
end
|
22
|
+
return( klass )
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :table
|
27
|
+
attr_accessor :before, :after
|
28
|
+
protected :before=, :after=
|
29
|
+
|
30
|
+
def initialize( table, *args )
|
31
|
+
@table = table
|
32
|
+
@before = nil
|
33
|
+
@after = nil
|
34
|
+
@disabled = false
|
35
|
+
block_given? and yield( self )
|
36
|
+
initialize!( *args )
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize!( * )
|
40
|
+
# do nothing
|
41
|
+
end
|
42
|
+
|
43
|
+
def inspect( *args )
|
44
|
+
content = args.map! { |a| a.inspect }.join(', ')
|
45
|
+
"#{self.class.member_name}(#{content})"
|
46
|
+
end
|
47
|
+
|
48
|
+
def each
|
49
|
+
block_given? or return( enum_for( __method__ ) )
|
50
|
+
node = self
|
51
|
+
begin
|
52
|
+
yield( node )
|
53
|
+
node = node.after
|
54
|
+
end while( node )
|
55
|
+
end
|
56
|
+
|
57
|
+
def disable
|
58
|
+
@disabled = true
|
59
|
+
end
|
60
|
+
|
61
|
+
def enable
|
62
|
+
@disabled = false
|
63
|
+
end
|
64
|
+
|
65
|
+
def enabled?
|
66
|
+
not disabled?
|
67
|
+
end
|
68
|
+
|
69
|
+
def disabled?
|
70
|
+
@disabled
|
71
|
+
end
|
72
|
+
|
73
|
+
def first?
|
74
|
+
@before.nil?
|
75
|
+
end
|
76
|
+
|
77
|
+
def last?
|
78
|
+
@after.nil?
|
79
|
+
end
|
80
|
+
|
81
|
+
def link( item )
|
82
|
+
after, @after, item.before = @after, item, self
|
83
|
+
after ? item.link( after ) : item
|
84
|
+
end
|
85
|
+
|
86
|
+
def unlink
|
87
|
+
@before and @before.after = nil
|
88
|
+
@before = nil
|
89
|
+
return( self )
|
90
|
+
end
|
91
|
+
|
92
|
+
def render( out, style )
|
93
|
+
render!( out, style ) unless disabled?
|
94
|
+
end
|
95
|
+
|
96
|
+
def columns
|
97
|
+
table.columns
|
98
|
+
end
|
99
|
+
|
100
|
+
def tail
|
101
|
+
@after ? @after.tail : self
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
Blank =
|
106
|
+
Member.define( 'blank' ) do
|
107
|
+
def render!( * )
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
Row =
|
112
|
+
Member.define( 'row' ) do
|
113
|
+
def initialize!( *content )
|
114
|
+
@cells = [ content ].flatten!.map! { | c | Text( c ) }
|
115
|
+
@table.expand_columns( @cells.length )
|
116
|
+
end
|
117
|
+
|
118
|
+
def []( index )
|
119
|
+
@cells[ index ]
|
120
|
+
end
|
121
|
+
|
122
|
+
def []=(index, value)
|
123
|
+
@cells[ index ] = value
|
124
|
+
end
|
125
|
+
|
126
|
+
def cells
|
127
|
+
@table.columns.zip( @cells ).
|
128
|
+
map! { | col, cell | col.prepare( cell ) }
|
129
|
+
end
|
130
|
+
|
131
|
+
def height
|
132
|
+
cells.map! { | c | c.height }.max
|
133
|
+
end
|
134
|
+
|
135
|
+
def render!( out, style )
|
136
|
+
cells =
|
137
|
+
@table.columns.zip( @cells ).map! do | col, cell |
|
138
|
+
col.prepare( cell )
|
139
|
+
end
|
140
|
+
|
141
|
+
height = cells.map { | col, cell | cell ? cell.height : 1 }.max
|
142
|
+
|
143
|
+
joint = style.format( ' <v> ' )
|
144
|
+
left = style.format( '<v> ' )
|
145
|
+
right = style.format( ' <v>' )
|
146
|
+
|
147
|
+
result = cells.inject { | result, cell | result.juxtapose( cell, joint ) }
|
148
|
+
result.each do | line |
|
149
|
+
out.put!( left + line + right )
|
150
|
+
end
|
151
|
+
return( out )
|
152
|
+
end
|
153
|
+
|
154
|
+
def inspect
|
155
|
+
super( *cells )
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def prepare
|
161
|
+
height = cells.map { | c | c.height }.max
|
162
|
+
if height > 1
|
163
|
+
cell_lines.zip( @table.columns ) do | lines, col |
|
164
|
+
if lines.length < height
|
165
|
+
blank = col.fill_text( ' ' )
|
166
|
+
lines.fill( blank, lines.length, height - lines.length )
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
return( cell_lines )
|
171
|
+
end
|
172
|
+
|
173
|
+
def pad
|
174
|
+
n = @table.columns.length
|
175
|
+
m = @cells.length
|
176
|
+
@cells.fill( Text(' '), m, n - m ) if n > m
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
TitleRow =
|
181
|
+
Member.define( 'title_row', Row ) do
|
182
|
+
def initialize!( *content )
|
183
|
+
super
|
184
|
+
divider!( :title )
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
Divider =
|
189
|
+
Member.define( 'divider' ) do
|
190
|
+
attr_accessor :type
|
191
|
+
|
192
|
+
def initialize!( type )
|
193
|
+
@type = type.to_sym
|
194
|
+
end
|
195
|
+
|
196
|
+
def render( out, style )
|
197
|
+
super( out, style ) unless @after.is_a?( Divider )
|
198
|
+
end
|
199
|
+
|
200
|
+
def inspect( *args )
|
201
|
+
super( @type, *args )
|
202
|
+
end
|
203
|
+
|
204
|
+
def render!( out, style )
|
205
|
+
fills = @table.columns.map { | c | "<h:#{ c.width + 2 }>" }
|
206
|
+
template =
|
207
|
+
case @type
|
208
|
+
when :row, :title
|
209
|
+
'<nse>' << fills.join( '<hv>' ) << '<nsw>'
|
210
|
+
when :section_open
|
211
|
+
'<nse>' << fills.join( '<hs>' ) << '<nsw>'
|
212
|
+
when :section_close
|
213
|
+
'<nse>' << fills.join( '<hn>' ) << '<nsw>'
|
214
|
+
when :head
|
215
|
+
'<se>' << fills.join( '<hs>' ) << '<sw>'
|
216
|
+
when :foot
|
217
|
+
'<ne>' << fills.join( '<hn>' ) << '<nw>'
|
218
|
+
end
|
219
|
+
out.puts( style.format( template ) )
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
SectionTitle =
|
224
|
+
Member.define( 'section' ) do
|
225
|
+
attr_accessor :title, :alignment
|
226
|
+
|
227
|
+
def initialize!( title, options = {} )
|
228
|
+
@title = Text( title )
|
229
|
+
@alignment = options.fetch( :align, :left )
|
230
|
+
@before.divider!( :section_close )
|
231
|
+
divider!( :section_open )
|
232
|
+
end
|
233
|
+
|
234
|
+
def inspect
|
235
|
+
super( @title, @alignment )
|
236
|
+
end
|
237
|
+
|
238
|
+
def render!( out, style )
|
239
|
+
w = @table.inner_width
|
240
|
+
title = @title.width > w ? @title.wrap( w ) : @title
|
241
|
+
left = style.format( '<v> ' )
|
242
|
+
right = style.format( ' <v>' )
|
243
|
+
|
244
|
+
for line in title.align( @alignment, w )
|
245
|
+
out.puts( left + line + right )
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
module MonoclePrint
|
5
|
+
class Table
|
6
|
+
|
7
|
+
Segments = Struct.new(
|
8
|
+
:head, :title_row, :title_divider, :row, :row_divider,
|
9
|
+
:section, :section_close, :section_open, :foot
|
10
|
+
)
|
11
|
+
|
12
|
+
class Segments
|
13
|
+
include MonoclePrint
|
14
|
+
|
15
|
+
def self.default_filling( style )
|
16
|
+
fill = style.new.dup
|
17
|
+
new( fill, nil, fill, nil, fill, nil, fill, fill, fill )
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.default_joints( style )
|
21
|
+
head = style.format( "<h><hd><h>" )
|
22
|
+
row = style.format( " <v> " )
|
23
|
+
div = style.format( "<h><hv><h>" )
|
24
|
+
foot = style.format( "<h><hu><h>" )
|
25
|
+
|
26
|
+
new( head, row, div, row, div, nil, foot, head, foot )
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.default_left_edge( style )
|
30
|
+
head = style.format( "<dr><h>" )
|
31
|
+
row = style.format( "<v> " )
|
32
|
+
div = style.format( "<vr><h>" )
|
33
|
+
foot = style.format( "<ur><h>" )
|
34
|
+
new( head, row, div, row, div, row, div, div, foot )
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.default_right_edge( style )
|
38
|
+
head = style.format( "<h><dl>" )
|
39
|
+
row = style.format( " <v>" )
|
40
|
+
div = style.format( "<h><vl>" )
|
41
|
+
foot = style.format( "<h><ul>" )
|
42
|
+
new( head, row, div, row, div, row, div, div, foot )
|
43
|
+
end
|
44
|
+
|
45
|
+
def mask( inclusion_settings )
|
46
|
+
masked = self.class.new
|
47
|
+
each_pair do | name, text |
|
48
|
+
if text and inclusion_settings[ name ]
|
49
|
+
masked[ name ] = text
|
50
|
+
end
|
51
|
+
end
|
52
|
+
return( masked )
|
53
|
+
end
|
54
|
+
|
55
|
+
def width( inclusion_mask = nil )
|
56
|
+
inclusion_mask and return( self.mask( inclusion_mask ).width )
|
57
|
+
return( map { |text| text ? text.width : 0 }.max )
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
#require 'termios'
|
5
|
+
#require 'terminfo'
|
6
|
+
|
7
|
+
module MonoclePrint
|
8
|
+
module TerminalEscapes
|
9
|
+
ANSI_COLORS = {
|
10
|
+
:blue => 4, :white => 7, :magenta => 5,
|
11
|
+
:yellow => 3, :green => 2, :black => 0,
|
12
|
+
:red => 1, :cyan => 6
|
13
|
+
}
|
14
|
+
ANSI_COLOR_NAMES = ANSI_COLORS.keys.freeze
|
15
|
+
|
16
|
+
ANSI_MODIFIERS =
|
17
|
+
{
|
18
|
+
:bold => 1,
|
19
|
+
:underline => 4,
|
20
|
+
:blink => 5,
|
21
|
+
:reverse => 7,
|
22
|
+
:conceal => 8
|
23
|
+
}
|
24
|
+
ANSI_MODIFIER_NAMES = ANSI_MODIFIERS.keys.freeze
|
25
|
+
|
26
|
+
module_function
|
27
|
+
|
28
|
+
#def term_info
|
29
|
+
# @term_info ||= TermInfo.new
|
30
|
+
#end
|
31
|
+
|
32
|
+
def ansi_color( type, color, bold = nil )
|
33
|
+
offset =
|
34
|
+
case type
|
35
|
+
when ?f then 30 # foreground
|
36
|
+
when ?b then 40 # background
|
37
|
+
end
|
38
|
+
code = offset + ANSI_COLORS.fetch( color.to_sym ) do
|
39
|
+
raise( ArgumentError, "unknown color name `%s'" % color )
|
40
|
+
end
|
41
|
+
"\e[#{ '1;' if bold }#{ code }m"
|
42
|
+
end
|
43
|
+
|
44
|
+
def xterm_color( type, color_index, bold = nil )
|
45
|
+
prefix =
|
46
|
+
case type
|
47
|
+
when ?f then 38 # foreground
|
48
|
+
when ?b then 48 # background
|
49
|
+
end
|
50
|
+
"\e[#{ prefix };5;#{ color_index }m"
|
51
|
+
end
|
52
|
+
|
53
|
+
def konsole_color( type, r, g, b, bold = nil)
|
54
|
+
prefix =
|
55
|
+
case type
|
56
|
+
when ?f then 38 # foreground
|
57
|
+
when ?b then 48 # background
|
58
|
+
end
|
59
|
+
"\e[#{ prefix };2;#{ r };#{ g };#{ b }m"
|
60
|
+
end
|
61
|
+
|
62
|
+
def ansi_modifier( name )
|
63
|
+
code =
|
64
|
+
ANSI_MODIFIERS.fetch( name.to_sym ) do
|
65
|
+
fail ArgumentError, "unknown modifier name `%s'" % name
|
66
|
+
end
|
67
|
+
"\e[#{ code }m"
|
68
|
+
end
|
69
|
+
|
70
|
+
def clear_attr
|
71
|
+
"\e[0m"
|
72
|
+
end
|
73
|
+
|
74
|
+
def bold
|
75
|
+
"\e[1m"
|
76
|
+
end
|
77
|
+
|
78
|
+
def underline
|
79
|
+
"\e[4m"
|
80
|
+
end
|
81
|
+
|
82
|
+
def blink
|
83
|
+
"\e[5m"
|
84
|
+
end
|
85
|
+
|
86
|
+
def reverse
|
87
|
+
"\e[7m"
|
88
|
+
end
|
89
|
+
|
90
|
+
def conceal
|
91
|
+
"\e[8m"
|
92
|
+
end
|
93
|
+
|
94
|
+
def set_cursor( line = 0, column = 0 )
|
95
|
+
"\e[%i;%iH" % [ line, column ]
|
96
|
+
end
|
97
|
+
|
98
|
+
def cursor_up( lines = 1 )
|
99
|
+
"\e[%iA" % lines
|
100
|
+
end
|
101
|
+
|
102
|
+
def cursor_down( lines = 1 )
|
103
|
+
"\e[%iB" % lines
|
104
|
+
end
|
105
|
+
|
106
|
+
def cursor_forward( columns = 1 )
|
107
|
+
"\e[%iC" % columns
|
108
|
+
end
|
109
|
+
|
110
|
+
def cursor_backward( columns = 1 )
|
111
|
+
"\e[%iD" % columns
|
112
|
+
end
|
113
|
+
|
114
|
+
def save_cursor
|
115
|
+
"\e[s"
|
116
|
+
end
|
117
|
+
|
118
|
+
def restore_cursor
|
119
|
+
"\e[u"
|
120
|
+
end
|
121
|
+
|
122
|
+
#def clear_line
|
123
|
+
# "\e[K"
|
124
|
+
#end
|
125
|
+
|
126
|
+
# VT100 escapes
|
127
|
+
def double_height_top
|
128
|
+
"\e#3"
|
129
|
+
end
|
130
|
+
|
131
|
+
def dobule_height_bottom
|
132
|
+
"\e#4"
|
133
|
+
end
|
134
|
+
|
135
|
+
def single_width
|
136
|
+
"\e#5"
|
137
|
+
end
|
138
|
+
|
139
|
+
def double_width
|
140
|
+
"\e#6"
|
141
|
+
end
|
142
|
+
|
143
|
+
def clear_right
|
144
|
+
"\e[0K"
|
145
|
+
end
|
146
|
+
|
147
|
+
def clear_left
|
148
|
+
"\e[1K"
|
149
|
+
end
|
150
|
+
|
151
|
+
def clear_line
|
152
|
+
"\e[2K"
|
153
|
+
end
|
154
|
+
|
155
|
+
def clear_down
|
156
|
+
"\e[0J"
|
157
|
+
end
|
158
|
+
|
159
|
+
def clear_up
|
160
|
+
"\e[1J"
|
161
|
+
end
|
162
|
+
|
163
|
+
def clear_screen
|
164
|
+
"\e[2J"
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
if __FILE__ == $0
|
171
|
+
|
172
|
+
TE = MonoclePrint::TerminalEscapes
|
173
|
+
for attr in %w[ bold underline blink reverse conceal ]
|
174
|
+
print( "This should be #{ attr }:".ljust( 30 ) )
|
175
|
+
printf( "%swhateva\e[0m\n", TE.send( attr ) )
|
176
|
+
end
|
177
|
+
|
178
|
+
colors = TE::ANSI_COLORS.keys.map(&:to_s).sort!
|
179
|
+
for color in colors
|
180
|
+
print( "This should be #{ color }:".ljust( 30 ) )
|
181
|
+
printf( "%swhateva\e[0m\t", TE.ansi_color( ?f, color ) )
|
182
|
+
printf( "%swhateva\e[0m\t", TE.ansi_color( ?b, color ) )
|
183
|
+
printf( "%sbright_whateva\e[0m\n", TE.ansi_color( ?f, color, true ) )
|
184
|
+
end
|
185
|
+
|
186
|
+
for dir in %w( left right line )
|
187
|
+
name = "clear_#{ dir }"
|
188
|
+
code = TE.send( name )
|
189
|
+
puts
|
190
|
+
puts("[ Clear Code: #{ name } ]")
|
191
|
+
puts
|
192
|
+
puts("at start")
|
193
|
+
printf("%s<= %-14s\n", code, name)
|
194
|
+
puts("in middle")
|
195
|
+
printf("%-14s =>%s<= %-14s\n", name, code, name)
|
196
|
+
puts("at end")
|
197
|
+
printf("%-14s =>%s\n", name, code, name)
|
198
|
+
end
|
199
|
+
|
200
|
+
puts( "[ Tabs ]" )
|
201
|
+
puts( " \eHx \eHx \eHx ")
|
202
|
+
puts( "one\ttwo\tthree\tfour" )
|
203
|
+
puts( "\e[3g" )
|
204
|
+
puts( "one\ttwo\tthree\tfour" )
|
205
|
+
end
|