Commit | Line | Data |
3fea05b9 |
1 | # Copyright (C) 2005-2009, Sebastian Riedel. |
2 | |
3 | package Text::SimpleTable; |
4 | |
5 | use strict; |
6 | use warnings; |
7 | |
8 | our $VERSION = '2.0'; |
9 | |
10 | # Top |
11 | our $TOP_LEFT = '.-'; |
12 | our $TOP_BORDER = '-'; |
13 | our $TOP_SEPARATOR = '-+-'; |
14 | our $TOP_RIGHT = '-.'; |
15 | |
16 | # Middle |
17 | our $MIDDLE_LEFT = '+-'; |
18 | our $MIDDLE_BORDER = '-'; |
19 | our $MIDDLE_SEPARATOR = '-+-'; |
20 | our $MIDDLE_RIGHT = '-+'; |
21 | |
22 | # Left |
23 | our $LEFT_BORDER = '| '; |
24 | our $SEPARATOR = ' | '; |
25 | our $RIGHT_BORDER = ' |'; |
26 | |
27 | # Bottom |
28 | our $BOTTOM_LEFT = "'-"; |
29 | our $BOTTOM_SEPARATOR = "-+-"; |
30 | our $BOTTOM_BORDER = '-'; |
31 | our $BOTTOM_RIGHT = "-'"; |
32 | |
33 | # Wrapper |
34 | our $WRAP = '-'; |
35 | |
36 | sub new { |
37 | my ($class, @args) = @_; |
38 | |
39 | # Instantiate |
40 | $class = ref $class || $class; |
41 | my $self = bless {}, $class; |
42 | |
43 | # Columns and titles |
44 | my $cache = []; |
45 | my $max = 0; |
46 | for my $arg (@args) { |
47 | my $width; |
48 | my $name; |
49 | |
50 | if (ref $arg) { |
51 | $width = $arg->[0]; |
52 | $name = $arg->[1]; |
53 | } |
54 | else { $width = $arg } |
55 | |
56 | # Fix size |
57 | $width = 2 if $width < 2; |
58 | |
59 | # Wrap |
60 | my $title = $name ? $self->_wrap($name, $width) : []; |
61 | |
62 | # Column |
63 | my $col = [$width, [], $title]; |
64 | $max = @{$col->[2]} if $max < @{$col->[2]}; |
65 | push @$cache, $col; |
66 | } |
67 | |
68 | # Padding |
69 | for my $col (@$cache) { |
70 | push @{$col->[2]}, '' while @{$col->[2]} < $max; |
71 | } |
72 | $self->{columns} = $cache; |
73 | |
74 | return $self; |
75 | } |
76 | |
77 | # The implementation is not very elegant, but gets the job done very well |
78 | sub draw { |
79 | my $self = shift; |
80 | |
81 | # Shortcut |
82 | return unless $self->{columns}; |
83 | |
84 | my $rows = @{$self->{columns}->[0]->[1]} - 1; |
85 | my $columns = @{$self->{columns}} - 1; |
86 | my $output = ''; |
87 | |
88 | # Top border |
89 | for my $j (0 .. $columns) { |
90 | |
91 | my $column = $self->{columns}->[$j]; |
92 | my $width = $column->[0]; |
93 | my $text = $TOP_BORDER x $width; |
94 | |
95 | if (($j == 0) && ($columns == 0)) { |
96 | $text = "$TOP_LEFT$text$TOP_RIGHT"; |
97 | } |
98 | elsif ($j == 0) { $text = "$TOP_LEFT$text$TOP_SEPARATOR" } |
99 | elsif ($j == $columns) { $text = "$text$TOP_RIGHT" } |
100 | else { $text = "$text$TOP_SEPARATOR" } |
101 | |
102 | $output .= $text; |
103 | } |
104 | $output .= "\n"; |
105 | |
106 | my $title = 0; |
107 | for my $column (@{$self->{columns}}) { |
108 | $title = @{$column->[2]} if $title < @{$column->[2]}; |
109 | } |
110 | |
111 | if ($title) { |
112 | |
113 | # Titles |
114 | for my $i (0 .. $title - 1) { |
115 | |
116 | for my $j (0 .. $columns) { |
117 | |
118 | my $column = $self->{columns}->[$j]; |
119 | my $width = $column->[0]; |
120 | my $text = $column->[2]->[$i] || ''; |
121 | |
122 | $text = sprintf "%-${width}s", $text; |
123 | |
124 | if (($j == 0) && ($columns == 0)) { |
125 | $text = "$LEFT_BORDER$text$RIGHT_BORDER"; |
126 | } |
127 | elsif ($j == 0) { $text = "$LEFT_BORDER$text$SEPARATOR" } |
128 | elsif ($j == $columns) { $text = "$text$RIGHT_BORDER" } |
129 | else { $text = "$text$SEPARATOR" } |
130 | |
131 | $output .= $text; |
132 | } |
133 | |
134 | $output .= "\n"; |
135 | } |
136 | |
137 | # Title separator |
138 | $output .= $self->_draw_hr; |
139 | |
140 | } |
141 | |
142 | # Rows |
143 | for my $i (0 .. $rows) { |
144 | |
145 | # Check for hr |
146 | if (!grep { defined $self->{columns}->[$_]->[1]->[$i] } 0 .. $columns) |
147 | { |
148 | $output .= $self->_draw_hr; |
149 | next; |
150 | } |
151 | |
152 | for my $j (0 .. $columns) { |
153 | |
154 | my $column = $self->{columns}->[$j]; |
155 | my $width = $column->[0]; |
156 | my $text = (defined $column->[1]->[$i]) ? $column->[1]->[$i] : ''; |
157 | |
158 | $text = sprintf "%-${width}s", $text; |
159 | |
160 | if (($j == 0) && ($columns == 0)) { |
161 | $text = "$LEFT_BORDER$text$RIGHT_BORDER"; |
162 | } |
163 | elsif ($j == 0) { $text = "$LEFT_BORDER$text$SEPARATOR" } |
164 | elsif ($j == $columns) { $text = "$text$RIGHT_BORDER" } |
165 | else { $text = "$text$SEPARATOR" } |
166 | |
167 | $output .= $text; |
168 | } |
169 | |
170 | $output .= "\n"; |
171 | } |
172 | |
173 | # Bottom border |
174 | for my $j (0 .. $columns) { |
175 | |
176 | my $column = $self->{columns}->[$j]; |
177 | my $width = $column->[0]; |
178 | my $text = $BOTTOM_BORDER x $width; |
179 | |
180 | if (($j == 0) && ($columns == 0)) { |
181 | $text = "$BOTTOM_LEFT$text$BOTTOM_RIGHT"; |
182 | } |
183 | elsif ($j == 0) { $text = "$BOTTOM_LEFT$text$BOTTOM_SEPARATOR" } |
184 | elsif ($j == $columns) { $text = "$text$BOTTOM_RIGHT" } |
185 | else { $text = "$text$BOTTOM_SEPARATOR" } |
186 | |
187 | $output .= $text; |
188 | } |
189 | |
190 | $output .= "\n"; |
191 | |
192 | return $output; |
193 | } |
194 | |
195 | sub hr { |
196 | my $self = shift; |
197 | |
198 | for (0 .. @{$self->{columns}} - 1) { |
199 | push @{$self->{columns}->[$_]->[1]}, undef; |
200 | } |
201 | |
202 | return $self; |
203 | } |
204 | |
205 | sub row { |
206 | my ($self, @texts) = @_; |
207 | my $size = @{$self->{columns}} - 1; |
208 | |
209 | # Shortcut |
210 | return $self if $size < 0; |
211 | |
212 | for (1 .. $size) { |
213 | last if $size <= @texts; |
214 | push @texts, ''; |
215 | } |
216 | |
217 | my $cache = []; |
218 | my $max = 0; |
219 | |
220 | for my $i (0 .. $size) { |
221 | |
222 | my $text = shift @texts; |
223 | my $column = $self->{columns}->[$i]; |
224 | my $width = $column->[0]; |
225 | my $pieces = $self->_wrap($text, $width); |
226 | |
227 | push @{$cache->[$i]}, @$pieces; |
228 | $max = @$pieces if @$pieces > $max; |
229 | } |
230 | |
231 | for my $col (@{$cache}) { push @{$col}, '' while @{$col} < $max } |
232 | |
233 | for my $i (0 .. $size) { |
234 | my $column = $self->{columns}->[$i]; |
235 | my $store = $column->[1]; |
236 | push @{$store}, @{$cache->[$i]}; |
237 | } |
238 | |
239 | return $self; |
240 | } |
241 | |
242 | sub _draw_hr { |
243 | my $self = shift; |
244 | my $columns = @{$self->{columns}} - 1; |
245 | my $output = ''; |
246 | |
247 | for my $j (0 .. $columns) { |
248 | |
249 | my $column = $self->{columns}->[$j]; |
250 | my $width = $column->[0]; |
251 | my $text = $MIDDLE_BORDER x $width; |
252 | |
253 | if (($j == 0) && ($columns == 0)) { |
254 | $text = "$MIDDLE_LEFT$text$MIDDLE_RIGHT"; |
255 | } |
256 | elsif ($j == 0) { $text = "$MIDDLE_LEFT$text$MIDDLE_SEPARATOR" } |
257 | elsif ($j == $columns) { $text = "$text$MIDDLE_RIGHT" } |
258 | else { $text = "$text$MIDDLE_SEPARATOR" } |
259 | $output .= $text; |
260 | } |
261 | |
262 | $output .= "\n"; |
263 | |
264 | return $output; |
265 | } |
266 | |
267 | # Wrap text |
268 | sub _wrap { |
269 | my ($self, $text, $width) = @_; |
270 | |
271 | my @cache; |
272 | my @parts = split "\n", $text; |
273 | |
274 | for my $part (@parts) { |
275 | |
276 | while (length $part > $width) { |
277 | my $subtext; |
278 | $subtext = substr $part, 0, $width - length($WRAP), ''; |
279 | push @cache, "$subtext$WRAP"; |
280 | } |
281 | |
282 | push @cache, $part if defined $part; |
283 | } |
284 | |
285 | return \@cache; |
286 | } |
287 | |
288 | 1; |
289 | __END__ |
290 | |
291 | =head1 NAME |
292 | |
293 | Text::SimpleTable - Simple Eyecandy ASCII Tables |
294 | |
295 | =head1 SYNOPSIS |
296 | |
297 | use Text::SimpleTable; |
298 | |
299 | my $t1 = Text::SimpleTable->new(5, 10); |
300 | $t1->row('foobarbaz', 'yadayadayada'); |
301 | print $t1->draw; |
302 | |
303 | .-------+------------. |
304 | | foob- | yadayaday- | |
305 | | arbaz | ada | |
306 | '-------+------------' |
307 | |
308 | my $t2 = Text::SimpleTable->new([5, 'Foo'], [10, 'Bar']); |
309 | $t2->row('foobarbaz', 'yadayadayada'); |
310 | $t2->row('barbarbarbarbar', 'yada'); |
311 | print $t2->draw; |
312 | |
313 | .-------+------------. |
314 | | Foo | Bar | |
315 | +-------+------------+ |
316 | | foob- | yadayaday- | |
317 | | arbaz | ada | |
318 | | barb- | yada | |
319 | | arba- | | |
320 | | rbar- | | |
321 | | bar | | |
322 | '-------+------------' |
323 | |
324 | my $t3 = Text::SimpleTable->new([5, 'Foo'], [10, 'Bar']); |
325 | $t3->row('foobarbaz', 'yadayadayada'); |
326 | $t3->hr; |
327 | $t3->row('barbarbarbarbar', 'yada'); |
328 | print $t3->draw; |
329 | |
330 | .-------+------------. |
331 | | Foo | Bar | |
332 | +-------+------------+ |
333 | | foob- | yadayaday- | |
334 | | arbaz | ada | |
335 | +-------+------------+ |
336 | | barb- | yada | |
337 | | arba- | | |
338 | | rbar- | | |
339 | | bar | | |
340 | '-------+------------' |
341 | |
342 | =head1 DESCRIPTION |
343 | |
344 | Simple eyecandy ASCII tables. |
345 | |
346 | =head1 METHODS |
347 | |
348 | L<Text::SimpleTable> implements the following methods. |
349 | |
350 | =head2 C<new> |
351 | |
352 | my $t = Text::SimpleTable->new(5, 10); |
353 | my $t = Text::SimpleTable->new([5, 'Col1', 10, 'Col2']); |
354 | |
355 | =head2 C<draw> |
356 | |
357 | my $ascii = $t->draw; |
358 | |
359 | =head2 C<hr> |
360 | |
361 | $t = $t->hr; |
362 | |
363 | =head2 C<row> |
364 | |
365 | $t = $t->row('col1 data', 'col2 data'); |
366 | |
367 | =head1 AUTHOR |
368 | |
369 | Sebastian Riedel, C<sri@cpan.org>. |
370 | |
371 | =head1 CREDITS |
372 | |
373 | In alphabetical order: |
374 | |
375 | Brian Cassidy |
376 | |
377 | =head1 COPYRIGHT AND LICENSE |
378 | |
379 | Copyright (C) 2005-2009, Sebastian Riedel. |
380 | |
381 | This program is free software, you can redistribute it and/or modify it under |
382 | the terms of the Artistic License version 2.0. |
383 | |
384 | =cut |