@@ -8,27 +8,29 @@ defmodule IO.ANSI.Docs do
8
8
9
9
The supported values are:
10
10
11
- * `:enabled` - toggles coloring on and off (true)
12
- * `:doc_code` - code blocks (cyan, bright)
13
- * `:doc_inline_code` - inline code (cyan)
14
- * `:doc_headings` - h1 and h2 headings (yellow, bright)
15
- * `:doc_title` - top level heading (reverse, yellow, bright)
16
- * `:doc_bold` - bold text (bright)
17
- * `:doc_underline` - underlined text (underline)
18
- * `:width` - the width to format the text (80)
11
+ * `:enabled` - toggles coloring on and off (true)
12
+ * `:doc_bold` - bold text (bright)
13
+ * `:doc_code` - code blocks (cyan, bright)
14
+ * `:doc_headings` - h1 and h2 headings (yellow, bright)
15
+ * `:doc_inline_code` - inline code (cyan)
16
+ * `:doc_table_heading` - style for table headings
17
+ * `:doc_title` - top level heading (reverse, yellow, bright)
18
+ * `:doc_underline` - underlined text (underline)
19
+ * `:width` - the width to format the text (80)
19
20
20
21
Values for the color settings are strings with
21
22
comma-separated ANSI values.
22
23
"""
23
24
def default_options do
24
- [ enabled: true ,
25
- doc_code: [ :cyan , :bright ] ,
26
- doc_inline_code: [ :cyan ] ,
27
- doc_headings: [ :yellow , :bright ] ,
28
- doc_title: [ :reverse , :yellow , :bright ] ,
29
- doc_bold: [ :bright ] ,
30
- doc_underline: [ :underline ] ,
31
- width: 80 ]
25
+ [ enabled: true ,
26
+ doc_bold: [ :bright ] ,
27
+ doc_code: [ :cyan , :bright ] ,
28
+ doc_headings: [ :yellow , :bright ] ,
29
+ doc_inline_code: [ :cyan ] ,
30
+ doc_table_heading: [ :reverse ] ,
31
+ doc_title: [ :reverse , :yellow , :bright ] ,
32
+ doc_underline: [ :underline ] ,
33
+ width: 80 ]
32
34
end
33
35
34
36
@ doc """
@@ -43,12 +45,13 @@ defmodule IO.ANSI.Docs do
43
45
padding = div ( width + String . length ( heading ) , 2 )
44
46
heading = heading |> String . rjust ( padding ) |> String . ljust ( width )
45
47
write ( :doc_title , heading , options )
48
+ newline_after_block
46
49
end
47
50
48
51
@ doc """
49
52
Prints the documentation body.
50
53
51
- In addition to the priting string, takes a set of options
54
+ In addition to the printing string, takes a set of options
52
55
defined in `default_options/1`.
53
56
"""
54
57
def print ( doc , options \\ [ ] ) do
@@ -84,13 +87,17 @@ defmodule IO.ANSI.Docs do
84
87
process_code ( rest , [ line ] , indent , options )
85
88
end
86
89
87
- defp process ( [ line | rest ] , indent , options ) do
90
+ defp process ( all = [ line | rest ] , indent , options ) do
88
91
{ stripped , count } = strip_spaces ( line , 0 )
89
- case stripped do
90
- << bullet , ?\s , item :: binary >> when bullet in @ bullets ->
91
- process_list ( item , rest , count , indent , options )
92
- _ ->
93
- process_text ( rest , [ line ] , indent , false , options )
92
+ if is_table_line? ( stripped ) && length ( rest ) > 0 && is_table_line? ( hd ( rest ) ) do
93
+ process_table ( all , indent , options )
94
+ else
95
+ case stripped do
96
+ << bullet , ?\s , item :: binary >> when bullet in @ bullets ->
97
+ process_list ( item , rest , count , indent , options )
98
+ _ ->
99
+ process_text ( rest , [ line ] , indent , false , options )
100
+ end
94
101
end
95
102
end
96
103
@@ -110,11 +117,13 @@ defmodule IO.ANSI.Docs do
110
117
111
118
defp write_h2 ( heading , options ) do
112
119
write ( :doc_headings , heading , options )
120
+ newline_after_block
113
121
end
114
122
115
123
defp write_h3 ( heading , indent , options ) do
116
124
IO . write ( indent )
117
125
write ( :doc_headings , heading , options )
126
+ newline_after_block
118
127
end
119
128
120
129
## Lists
@@ -194,7 +203,7 @@ defmodule IO.ANSI.Docs do
194
203
|> String . split ( ~r{ \s } )
195
204
|> write_with_wrap ( options [ :width ] - byte_size ( indent ) , indent , from_list )
196
205
197
- unless from_list , do: IO . puts ( IO.ANSI . reset )
206
+ unless from_list , do: newline_after_block
198
207
end
199
208
200
209
## Code blocks
@@ -219,13 +228,103 @@ defmodule IO.ANSI.Docs do
219
228
220
229
defp write_code ( code , indent , options ) do
221
230
write ( :doc_code , "#{ indent } ┃ #{ Enum . join ( Enum . reverse ( code ) , "\n #{ indent } ┃ " ) } " , options )
231
+ newline_after_block
232
+ end
233
+
234
+ ## Tables
235
+
236
+ defp process_table ( lines , indent , options ) do
237
+ { table , rest } = Enum . split_while ( lines , & is_table_line? / 1 )
238
+ table_lines ( table , options )
239
+ newline_after_block
240
+ process ( rest , indent , options )
241
+ end
242
+
243
+ defp table_lines ( lines , options ) do
244
+ lines = Enum . map ( lines , & split_into_columns / 1 )
245
+ count = lines |> Enum . map ( & length / 1 ) |> Enum . max
246
+ lines = Enum . map ( lines , & pad_to_number_of_columns ( & 1 , count ) )
247
+
248
+ widths = for line <- lines , do:
249
+ ( for col <- line , do: effective_length ( col ) )
250
+
251
+ col_widths = Enum . reduce ( widths ,
252
+ List . duplicate ( 0 , count ) ,
253
+ & max_column_widths / 2 )
254
+
255
+ render_table ( lines , col_widths , options )
256
+ end
257
+
258
+ defp split_into_columns ( line ) do
259
+ line
260
+ |> String . strip ( ?| )
261
+ |> String . strip ( )
262
+ |> String . split ( ~r/ \s \| \s / )
263
+ end
264
+
265
+ defp pad_to_number_of_columns ( cols , col_count ) ,
266
+ do: cols ++ List . duplicate ( "" , col_count - length ( cols ) )
267
+
268
+ defp max_column_widths ( cols , widths ) do
269
+ Enum . zip ( cols , widths ) |> Enum . map ( fn { a , b } -> max ( a , b ) end )
270
+ end
271
+
272
+ defp effective_length ( text ) do
273
+ String . length ( Regex . replace ( ~r/ ((^|\b )[`*_]+)|([`*_]+\b )/ , text , "" ) )
274
+ end
275
+
276
+ # If second line is heading separator, use the heading style on the first
277
+ defp render_table ( [ first , second | rest ] , widths , options ) do
278
+ combined = Enum . zip ( first , widths )
279
+ if table_header? ( second ) do
280
+ draw_table_row ( combined , options , :heading )
281
+ render_table ( rest , widths , options )
282
+ else
283
+ draw_table_row ( combined , options )
284
+ render_table ( [ second | rest ] , widths , options )
285
+ end
286
+ end
287
+
288
+ defp render_table ( [ first | rest ] , widths , options ) do
289
+ combined = Enum . zip ( first , widths )
290
+ draw_table_row ( combined , options )
291
+ render_table ( rest , widths , options )
292
+ end
293
+
294
+ defp render_table ( [ ] , _ , _ ) , do: nil
295
+
296
+ defp table_header? ( row ) , do:
297
+ Enum . all? ( row , fn col -> col =~ ~r/ ^:?-+:?$/ end )
298
+
299
+ defp draw_table_row ( cols_and_widths , options , heading \\ false ) do
300
+ columns = for { col , width } <- cols_and_widths do
301
+ padding = width - effective_length ( col )
302
+ col = Regex . replace ( ~r/ \\ \| / x , col , "|" ) # escaped bars
303
+ text = col
304
+ |> handle_links
305
+ |> handle_inline ( nil , [ ] , [ ] , options )
306
+ text <> String . duplicate ( " " , padding )
307
+ end |> Enum . join ( " | " )
308
+
309
+ if heading do
310
+ write ( :doc_table_heading , columns , options )
311
+ else
312
+ IO . puts columns
313
+ end
222
314
end
223
315
224
316
## Helpers
225
317
318
+ @ table_line_re ~r'''
319
+ ( ^ \s {0,3} \| (?: [^|]+ \| )+ \s * $ )
320
+ |
321
+ (\s \| \s )
322
+ ''' x
323
+
324
+ defp is_table_line? ( line ) , do: Regex . match? ( @ table_line_re , line )
325
+
226
326
defp write ( style , string , options ) do
227
327
IO . puts [ color ( style , options ) , string , IO.ANSI . reset ]
228
- IO . puts IO.ANSI . reset
229
328
end
230
329
231
330
defp write_with_wrap ( [ ] , _available , _indent , _first ) do
@@ -363,10 +462,14 @@ defmodule IO.ANSI.Docs do
363
462
[ color_for ( h , options ) | t ]
364
463
end
365
464
366
- defp color_for ( "`" , colors ) , do: color ( :doc_inline_code , colors )
367
- defp color_for ( "_" , colors ) , do: color ( :doc_underline , colors )
368
- defp color_for ( "*" , colors ) , do: color ( :doc_bold , colors )
369
- defp color_for ( "**" , colors ) , do: color ( :doc_bold , colors )
465
+ defp color_for ( mark , colors ) do
466
+ case mark do
467
+ "`" -> color ( :doc_inline_code , colors )
468
+ "_" -> color ( :doc_underline , colors )
469
+ "*" -> color ( :doc_bold , colors )
470
+ "**" -> color ( :doc_bold , colors )
471
+ end
472
+ end
370
473
371
474
defp color ( style , colors ) do
372
475
color = colors [ style ]
@@ -377,4 +480,6 @@ defmodule IO.ANSI.Docs do
377
480
end
378
481
IO.ANSI . format_fragment ( color , colors [ :enabled ] )
379
482
end
483
+
484
+ defp newline_after_block , do: IO . puts ( IO.ANSI . reset )
380
485
end
0 commit comments