fat_table 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,208 @@
1
+ module FatTable
2
+ # A subclass of Formatter for rendering the table as a LaTeX table. It allows
3
+ # foreground colors through LaTeX's xcolor package but ignores background
4
+ # colors. You can see the valid color names with LaTeXFormatter.valid_colors.
5
+ class LaTeXFormatter < Formatter
6
+ # Return a new LaTeXFormatter for +table+. You can set the following
7
+ # +options+ with hash-like parameters:
8
+ #
9
+ # document::
10
+ # if set to true, include a document preamble and wrap the output in a
11
+ # LaTeX document environment so that the output can be compiled by a LaTeX
12
+ # processor such as +pdflatex+. By default, only the table environment is
13
+ # output.
14
+ #
15
+ # environment::
16
+ # set to a string, by default 'longtable' that indicates what kind of
17
+ # LaTeX tabular-like environment to use for the table. The default is good
18
+ # for tables that might continue over multiple pages since it repeats the
19
+ # header at the top of each continuation page.
20
+
21
+ def initialize(table = Table.new, **options)
22
+ super
23
+ @options[:document] = options.fetch(:document, false)
24
+ @options[:environment] = options.fetch(:environment, 'longtable')
25
+ end
26
+
27
+ # Taken from the Rainbow gem's list of valid colors.
28
+ self.valid_colors = %w(
29
+ none black blue brown cyan darkgray gray green lightgray lime magenta
30
+ olive orange pink purple red teal violet white yellow AntiqueWhite1
31
+ AntiqueWhite2 AntiqueWhite3 AntiqueWhite4 Aquamarine1 Aquamarine2
32
+ Aquamarine3 Aquamarine4 Azure1 Azure2 Azure3 Azure4 Bisque1 Bisque2
33
+ Bisque3 Bisque4 Blue1 Blue2 Blue3 Blue4 Brown1 Brown2 Brown3 Brown4
34
+ Burlywood1 Burlywood2 Burlywood3 Burlywood4 CadetBlue1 CadetBlue2
35
+ CadetBlue3 CadetBlue4 Chartreuse1 Chartreuse2 Chartreuse3 Chartreuse4
36
+ Chocolate1 Chocolate2 Chocolate3 Chocolate4 Coral1 Coral2 Coral3 Coral4
37
+ Cornsilk1 Cornsilk2 Cornsilk3 Cornsilk4 Cyan1 Cyan2 Cyan3 Cyan4
38
+ DarkGoldenrod1 DarkGoldenrod2 DarkGoldenrod3 DarkGoldenrod4
39
+ DarkOliveGreen1 DarkOliveGreen2 DarkOliveGreen3 DarkOliveGreen4
40
+ DarkOrange1 DarkOrange2 DarkOrange3 DarkOrange4 DarkOrchid1 DarkOrchid2
41
+ DarkOrchid3 DarkOrchid4 DarkSeaGreen1 DarkSeaGreen2 DarkSeaGreen3
42
+ DarkSeaGreen4 DarkSlateGray1 DarkSlateGray2 DarkSlateGray3 DarkSlateGray4
43
+ DeepPink1 DeepPink2 DeepPink3 DeepPink4 DeepSkyBlue1 DeepSkyBlue2
44
+ DeepSkyBlue3 DeepSkyBlue4 DodgerBlue1 DodgerBlue2 DodgerBlue3 DodgerBlue4
45
+ Firebrick1 Firebrick2 Firebrick3 Firebrick4 Gold1 Gold2 Gold3 Gold4
46
+ Goldenrod1 Goldenrod2 Goldenrod3 Goldenrod4 Gray0 Green0 Green1 Green2
47
+ Green3 Green4 Grey0 Honeydew1 Honeydew2 Honeydew3 Honeydew4 HotPink1
48
+ HotPink2 HotPink3 HotPink4 IndianRed1 IndianRed2 IndianRed3 IndianRed4
49
+ Ivory1 Ivory2 Ivory3 Ivory4 Khaki1 Khaki2 Khaki3 Khaki4 LavenderBlush1
50
+ LavenderBlush2 LavenderBlush3 LavenderBlush4 LemonChiffon1 LemonChiffon2
51
+ LemonChiffon3 LemonChiffon4 LightBlue1 LightBlue2 LightBlue3 LightBlue4
52
+ LightCyan1 LightCyan2 LightCyan3 LightCyan4 LightGoldenrod1
53
+ LightGoldenrod2 LightGoldenrod3 LightGoldenrod4 LightPink1 LightPink2
54
+ LightPink3 LightPink4 LightSalmon1 LightSalmon2 LightSalmon3 LightSalmon4
55
+ LightSkyBlue1 LightSkyBlue2 LightSkyBlue3 LightSkyBlue4 LightSteelBlue1
56
+ LightSteelBlue2 LightSteelBlue3 LightSteelBlue4 LightYellow1 LightYellow2
57
+ LightYellow3 LightYellow4 Magenta1 Magenta2 Magenta3 Magenta4 Maroon0
58
+ Maroon1 Maroon2 Maroon3 Maroon4 MediumOrchid1 MediumOrchid2 MediumOrchid3
59
+ MediumOrchid4 MediumPurple1 MediumPurple2 MediumPurple3 MediumPurple4
60
+ MistyRose1 MistyRose2 MistyRose3 MistyRose4 NavajoWhite1 NavajoWhite2
61
+ NavajoWhite3 NavajoWhite4 OliveDrab1 OliveDrab2 OliveDrab3 OliveDrab4
62
+ Orange1 Orange2 Orange3 Orange4 OrangeRed1 OrangeRed2 OrangeRed3
63
+ OrangeRed4 Orchid1 Orchid2 Orchid3 Orchid4 PaleGreen1 PaleGreen2
64
+ PaleGreen3 PaleGreen4 PaleTurquoise1 PaleTurquoise2 PaleTurquoise3
65
+ PaleTurquoise4 PaleVioletRed1 PaleVioletRed2 PaleVioletRed3 PaleVioletRed4
66
+ PeachPuff1 PeachPuff2 PeachPuff3 PeachPuff4 Pink1 Pink2 Pink3 Pink4 Plum1
67
+ Plum2 Plum3 Plum4 Purple0 Purple1 Purple2 Purple3 Purple4 Red1 Red2 Red3
68
+ Red4 RosyBrown1 RosyBrown2 RosyBrown3 RosyBrown4 RoyalBlue1 RoyalBlue2
69
+ RoyalBlue3 RoyalBlue4 Salmon1 Salmon2 Salmon3 Salmon4 SeaGreen1 SeaGreen2
70
+ SeaGreen3 SeaGreen4 Seashell1 Seashell2 Seashell3 Seashell4 Sienna1
71
+ Sienna2 Sienna3 Sienna4 SkyBlue1 SkyBlue2 SkyBlue3 SkyBlue4 SlateBlue1
72
+ SlateBlue2 SlateBlue3 SlateBlue4 SlateGray1 SlateGray2 SlateGray3
73
+ SlateGray4 Snow1 Snow2 Snow3 Snow4 SpringGreen1 SpringGreen2 SpringGreen3
74
+ SpringGreen4 SteelBlue1 SteelBlue2 SteelBlue3 SteelBlue4 Tan1 Tan2 Tan3
75
+ Tan4 Thistle1 Thistle2 Thistle3 Thistle4 Tomato1 Tomato2 Tomato3 Tomato4
76
+ Turquoise1 Turquoise2 Turquoise3 Turquoise4 VioletRed1 VioletRed2
77
+ VioletRed3 VioletRed4 Wheat1 Wheat2 Wheat3 Wheat4 Yellow1 Yellow2 Yellow3
78
+ Yellow4
79
+ )
80
+
81
+ # LaTeX commands to load the needed packages based on the :environement
82
+ # option. For now, just handles the default 'longtable' :environment. The
83
+ # preamble always includes a command to load the xcolor package.
84
+ def preamble
85
+ result = ''
86
+ result +=
87
+ case @options[:environment]
88
+ when 'longtable'
89
+ "\\usepackage{longtable}\n"
90
+ else
91
+ ''
92
+ end
93
+ result += "\\usepackage[pdftex,x11names]{xcolor}\n"
94
+ result
95
+ end
96
+
97
+ private
98
+
99
+ def color_valid?(clr)
100
+ valid_colors.include?(clr)
101
+ end
102
+
103
+ def invalid_color_msg(clr)
104
+ valid_colors_list = valid_colors.join(' ').wrap
105
+ "LaTeXFormatter invalid color '#{clr}'. Valid colors are:\n" +
106
+ valid_colors_list
107
+ end
108
+
109
+ # Add LaTeX control sequences. Ignore background color, underline, and
110
+ # blink. Alignment needs to be done by LaTeX, so we have to take it into
111
+ # account unless it's the same as the body alignment, since that is the
112
+ # default.
113
+ def decorate_string(str, istruct)
114
+ str = quote(str)
115
+ result = ''
116
+ result += '\\bfseries{}' if istruct.bold
117
+ result += '\\itshape{}' if istruct.italic
118
+ result += "\\color{#{istruct.color}}" if istruct.color && istruct.color != 'none'
119
+ result = "#{result}#{str}"
120
+ unless istruct.alignment == format_at[:body][istruct._h].alignment
121
+ ac = alignment_code(istruct.alignment)
122
+ result = "\\multicolumn{1}{#{ac}}{#{result}}"
123
+ end
124
+ result
125
+ end
126
+
127
+ # Return +str+ with quote marks oriented and special TeX characters quoted.
128
+ def quote(str)
129
+ # Replace single and double quotes with TeX oriented quotes.
130
+ result = str.gsub(/'([^']*)'/, "`\\1'")
131
+ result = result.gsub(/"([^"]*)"/, "``\\1''")
132
+ # Escape special TeX characters, such as $ and %
133
+ result.tex_quote
134
+ end
135
+
136
+ def pre_table
137
+ result = ''
138
+ if @options[:document]
139
+ result += "\\documentclass{article}\n"
140
+ result += preamble
141
+ result += "\\begin{document}\n"
142
+ end
143
+ result += "\\begin{#{@options[:environment]}}{"
144
+ table.headers.each do |h|
145
+ result += alignment_code(format_at[:body][h].alignment)
146
+ end
147
+ result += "}\n"
148
+ result
149
+ end
150
+
151
+ def post_table
152
+ result = "\\end{#{@options[:environment]}}\n"
153
+ result += "\\end{document}\n" if @options[:document]
154
+ result
155
+ end
156
+
157
+ def alignment_code(al_sym)
158
+ case al_sym
159
+ when :center
160
+ 'c'
161
+ when :right
162
+ 'r'
163
+ else
164
+ 'l'
165
+ end
166
+ end
167
+
168
+ def post_header(_widths)
169
+ "\\endhead\n"
170
+ end
171
+
172
+ def pre_row
173
+ ''
174
+ end
175
+
176
+ def pre_cell(_h)
177
+ ''
178
+ end
179
+
180
+ # We do quoting before applying decoration, so do not re-quote here. We
181
+ # will have LaTeX commands in v.
182
+ def quote_cell(v)
183
+ v
184
+ end
185
+
186
+ def post_cell
187
+ ''
188
+ end
189
+
190
+ def inter_cell
191
+ "&\n"
192
+ end
193
+
194
+ def post_row
195
+ "\\\\\n"
196
+ end
197
+
198
+ # Hlines look to busy in a printed table
199
+ def hline(_widths)
200
+ ''
201
+ end
202
+
203
+ # Hlines look too busy in a printed table
204
+ def post_footers(_widths)
205
+ ''
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,72 @@
1
+ module FatTable
2
+ # Output the table in the same way as org-mode for emacs does. This is almost
3
+ # identical to TextFormatter except that dates do get formatted as inactive
4
+ # timestamps and the connector at the beginning of hlines is a '|' rather than
5
+ # a '+' as for text tables.
6
+ class OrgFormatter < Formatter
7
+
8
+ self.default_format = default_format.dup
9
+ self.default_format[:date_fmt] = '[%F]'
10
+ self.default_format[:datetime_fmt] = '[%F %a %H:%M:%S]'
11
+
12
+ private
13
+
14
+ # Does this Formatter require a second pass over the cells to align the
15
+ # columns according to the alignment formatting instruction to the width of
16
+ # the widest cell in each column?
17
+ def aligned?
18
+ true
19
+ end
20
+
21
+ def pre_header(widths)
22
+ result = '|'
23
+ widths.values.each do |w|
24
+ result += '-' * (w + 2) + '+'
25
+ end
26
+ result[-1] = '|'
27
+ result + "\n"
28
+ end
29
+
30
+ def pre_row
31
+ '|'
32
+ end
33
+
34
+ def pre_cell(_h)
35
+ ''
36
+ end
37
+
38
+ def quote_cell(v)
39
+ v
40
+ end
41
+
42
+ def post_cell
43
+ ''
44
+ end
45
+
46
+ def inter_cell
47
+ '|'
48
+ end
49
+
50
+ def post_row
51
+ "|\n"
52
+ end
53
+
54
+ def hline(widths)
55
+ result = '|'
56
+ widths.values.each do |w|
57
+ result += '-' * (w + 2) + '+'
58
+ end
59
+ result[-1] = '|'
60
+ result + "\n"
61
+ end
62
+
63
+ def post_footers(widths)
64
+ result = '|'
65
+ widths.values.each do |w|
66
+ result += '-' * (w + 2) + '+'
67
+ end
68
+ result[-1] = '|'
69
+ result + "\n"
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,297 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'rainbow'
4
+
5
+ module FatTable
6
+ # Output the table as for a unicode-enabled ANSI terminal. This makes table
7
+ # gridlines drawable with unicode characters, as well as supporting colored
8
+ # text and backgrounds, and blink, and underline attributes. See
9
+ # TermFormatter.valid_colors for an Array of valid colors that you can use.
10
+ # The extent to which all of these are actually supported depends on your
11
+ # terminal. TermFormatter uses the +rainbow+ gem for forming colored strings.
12
+ # Use a
13
+ class TermFormatter < Formatter
14
+ # Return a new TermFormatter for +table+. You can set a few +options+ with
15
+ # the following hash-like parameters:
16
+ #
17
+ # unicode::
18
+ # if set true, use unicode characters to form the frame of the table on
19
+ # output; if set false, use ASCII characters for the frame. By default,
20
+ # this is true.
21
+ #
22
+ # framecolor::
23
+ # set to a string of the form '<color>' or '<color.color>' to set the
24
+ # color of the frame or the color and background color. By default, the
25
+ # framecolor is set to 'none.none', meaning that the normal terminal
26
+ # foreground and background colors will be used for the frame.
27
+ def initialize(table = Table.new, **options)
28
+ super
29
+ @options[:unicode] = options.fetch(:unicode, true)
30
+ @options[:framecolor] = options.fetch(:framecolor, 'none.none')
31
+ return unless @options[:framecolor] =~ /([-_a-zA-Z]*)(\.([-_a-zA-Z]*))/
32
+ @options[:frame_fg] = $1.downcase unless $1.blank?
33
+ @options[:frame_bg] = $3.downcase unless $3.blank?
34
+ end
35
+
36
+ # Valid colors for ANSI terminal using the rainbow gem's X11ColorNames.
37
+ self.valid_colors = ['none'] +
38
+ ::Rainbow::X11ColorNames::NAMES.keys.map(&:to_s).sort
39
+
40
+ private
41
+
42
+ def color_valid?(clr)
43
+ valid_colors.include?(clr)
44
+ end
45
+
46
+ def invalid_color_msg(clr)
47
+ valid_colors_list = valid_colors.join(' ').wrap
48
+ "TermFormatter invalid color '#{clr}'. Valid colors are:\n" +
49
+ valid_colors_list
50
+ end
51
+
52
+ # Compute the width of the string as displayed, taking into account the
53
+ # characteristics of the target device. For example, a colored string
54
+ # should not include in the width terminal control characters that simply
55
+ # change the color without occupying any space. Thus, this method must be
56
+ # overridden in a subclass if a simple character count does not reflect the
57
+ # width as displayed.
58
+ def width(str)
59
+ strip_ansi(str).length
60
+ end
61
+
62
+ def strip_ansi(str)
63
+ str&.gsub(/\e\[[0-9;]+m/, '')
64
+ end
65
+
66
+ # Add ANSI codes to string to implement the given decorations
67
+ def decorate_string(str, istruct)
68
+ result = Rainbow(str)
69
+ result = colorize(result, istruct.color, istruct.bgcolor)
70
+ result = result.bold if istruct.bold
71
+ result = result.italic if istruct.italic
72
+ result = result.underline if istruct.underline
73
+ result = result.blink if istruct.blink
74
+ result
75
+ end
76
+
77
+ def colorize(str, fg, bg)
78
+ fg = nil if fg == 'none'
79
+ bg = nil if bg == 'none'
80
+ return str unless fg || bg
81
+ result = Rainbow(str)
82
+ if fg
83
+ fg = fg.tr(' ', '').downcase.as_sym
84
+ result = result.color(fg) if fg
85
+ end
86
+ if bg
87
+ bg = bg.tr(' ', '').downcase.as_sym
88
+ result = result.bg(bg) if bg
89
+ end
90
+ result
91
+ end
92
+
93
+ # Colorize frame components
94
+ def frame_colorize(str)
95
+ colorize(str, @options[:frame_fg], @options[:frame_bg])
96
+ end
97
+
98
+ # :stopdoc:
99
+ # Unicode line-drawing characters. We use double lines before and after the
100
+ # table and single lines for the sides and hlines between groups and
101
+ # footers.
102
+ UPPER_LEFT = "\u2552".freeze
103
+ UPPER_RIGHT = "\u2555".freeze
104
+ DOUBLE_RULE = "\u2550".freeze
105
+ UPPER_TEE = "\u2564".freeze
106
+ VERTICAL_RULE = "\u2502".freeze
107
+ LEFT_TEE = "\u251C".freeze
108
+ HORIZONTAL_RULE = "\u2500".freeze
109
+ SINGLE_CROSS = "\u253C".freeze
110
+ RIGHT_TEE = "\u2524".freeze
111
+ LOWER_LEFT = "\u2558".freeze
112
+ LOWER_RIGHT = "\u255B".freeze
113
+ LOWER_TEE = "\u2567".freeze
114
+ # :startdoc:
115
+
116
+ def upper_left
117
+ if options[:unicode]
118
+ UPPER_LEFT
119
+ else
120
+ '+'
121
+ end
122
+ end
123
+
124
+ def upper_right
125
+ if options[:unicode]
126
+ UPPER_RIGHT
127
+ else
128
+ '+'
129
+ end
130
+ end
131
+
132
+ def double_rule
133
+ if options[:unicode]
134
+ DOUBLE_RULE
135
+ else
136
+ '='
137
+ end
138
+ end
139
+
140
+ def upper_tee
141
+ if options[:unicode]
142
+ UPPER_TEE
143
+ else
144
+ '+'
145
+ end
146
+ end
147
+
148
+ def vertical_rule
149
+ if options[:unicode]
150
+ VERTICAL_RULE
151
+ else
152
+ '|'
153
+ end
154
+ end
155
+
156
+ def left_tee
157
+ if options[:unicode]
158
+ LEFT_TEE
159
+ else
160
+ '+'
161
+ end
162
+ end
163
+
164
+ def horizontal_rule
165
+ if options[:unicode]
166
+ HORIZONTAL_RULE
167
+ else
168
+ '-'
169
+ end
170
+ end
171
+
172
+ def single_cross
173
+ if options[:unicode]
174
+ SINGLE_CROSS
175
+ else
176
+ '+'
177
+ end
178
+ end
179
+
180
+ def right_tee
181
+ if options[:unicode]
182
+ RIGHT_TEE
183
+ else
184
+ '+'
185
+ end
186
+ end
187
+
188
+ def lower_left
189
+ if options[:unicode]
190
+ LOWER_LEFT
191
+ else
192
+ '+'
193
+ end
194
+ end
195
+
196
+ def lower_right
197
+ if options[:unicode]
198
+ LOWER_RIGHT
199
+ else
200
+ '+'
201
+ end
202
+ end
203
+
204
+ def lower_tee
205
+ if options[:unicode]
206
+ LOWER_TEE
207
+ else
208
+ '+'
209
+ end
210
+ end
211
+
212
+ # Does this Formatter require a second pass over the cells to align the
213
+ # columns according to the alignment formatting instruction to the width of
214
+ # the widest cell in each column?
215
+ def aligned?
216
+ true
217
+ end
218
+
219
+ def pre_header(widths)
220
+ result = upper_left
221
+ widths.values.each do |w|
222
+ result += double_rule * (w + 2) + upper_tee
223
+ end
224
+ result[-1] = upper_right
225
+ result = colorize(result, @options[:frame_fg], @options[:frame_bg])
226
+ result + "\n"
227
+ end
228
+
229
+ def pre_row
230
+ frame_colorize(vertical_rule)
231
+ end
232
+
233
+ def pre_cell(_h)
234
+ ''
235
+ end
236
+
237
+ def quote_cell(v)
238
+ v
239
+ end
240
+
241
+ def post_cell
242
+ ''
243
+ end
244
+
245
+ def inter_cell
246
+ frame_colorize(vertical_rule)
247
+ end
248
+
249
+ def post_row
250
+ frame_colorize(vertical_rule) + "\n"
251
+ end
252
+
253
+ def hline(widths)
254
+ result = left_tee
255
+ widths.values.each do |w|
256
+ result += horizontal_rule * (w + 2) + single_cross
257
+ end
258
+ result[-1] = right_tee
259
+ result = frame_colorize(result)
260
+ result + "\n"
261
+ end
262
+
263
+ def pre_group
264
+ ''
265
+ end
266
+
267
+ def post_group
268
+ ''
269
+ end
270
+
271
+ def pre_gfoot
272
+ ''
273
+ end
274
+
275
+ def post_gfoot
276
+ ''
277
+ end
278
+
279
+ def pre_foot
280
+ ''
281
+ end
282
+
283
+ def post_foot
284
+ ''
285
+ end
286
+
287
+ def post_footers(widths)
288
+ result = lower_left
289
+ widths.values.each do |w|
290
+ result += double_rule * (w + 2) + lower_tee
291
+ end
292
+ result[-1] = lower_right
293
+ result = frame_colorize(result)
294
+ result + "\n"
295
+ end
296
+ end
297
+ end