fat_table 0.2.2

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.
@@ -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