@@ -8,27 +8,29 @@ defmodule IO.ANSI.Docs do
88
99 The supported values are:
1010
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)
1920
2021 Values for the color settings are strings with
2122 comma-separated ANSI values.
2223 """
2324 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 ]
3234 end
3335
3436 @ doc """
@@ -43,12 +45,13 @@ defmodule IO.ANSI.Docs do
4345 padding = div ( width + String . length ( heading ) , 2 )
4446 heading = heading |> String . rjust ( padding ) |> String . ljust ( width )
4547 write ( :doc_title , heading , options )
48+ newline_after_block
4649 end
4750
4851 @ doc """
4952 Prints the documentation body.
5053
51- In addition to the priting string, takes a set of options
54+ In addition to the printing string, takes a set of options
5255 defined in `default_options/1`.
5356 """
5457 def print ( doc , options \\ [ ] ) do
@@ -84,13 +87,17 @@ defmodule IO.ANSI.Docs do
8487 process_code ( rest , [ line ] , indent , options )
8588 end
8689
87- defp process ( [ line | rest ] , indent , options ) do
90+ defp process ( all = [ line | rest ] , indent , options ) do
8891 { 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
94101 end
95102 end
96103
@@ -110,11 +117,13 @@ defmodule IO.ANSI.Docs do
110117
111118 defp write_h2 ( heading , options ) do
112119 write ( :doc_headings , heading , options )
120+ newline_after_block
113121 end
114122
115123 defp write_h3 ( heading , indent , options ) do
116124 IO . write ( indent )
117125 write ( :doc_headings , heading , options )
126+ newline_after_block
118127 end
119128
120129 ## Lists
@@ -194,7 +203,7 @@ defmodule IO.ANSI.Docs do
194203 |> String . split ( ~r{ \s } )
195204 |> write_with_wrap ( options [ :width ] - byte_size ( indent ) , indent , from_list )
196205
197- unless from_list , do: IO . puts ( IO.ANSI . reset )
206+ unless from_list , do: newline_after_block
198207 end
199208
200209 ## Code blocks
@@ -219,13 +228,103 @@ defmodule IO.ANSI.Docs do
219228
220229 defp write_code ( code , indent , options ) do
221230 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
222314 end
223315
224316 ## Helpers
225317
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+
226326 defp write ( style , string , options ) do
227327 IO . puts [ color ( style , options ) , string , IO.ANSI . reset ]
228- IO . puts IO.ANSI . reset
229328 end
230329
231330 defp write_with_wrap ( [ ] , _available , _indent , _first ) do
@@ -363,10 +462,14 @@ defmodule IO.ANSI.Docs do
363462 [ color_for ( h , options ) | t ]
364463 end
365464
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
370473
371474 defp color ( style , colors ) do
372475 color = colors [ style ]
@@ -377,4 +480,6 @@ defmodule IO.ANSI.Docs do
377480 end
378481 IO.ANSI . format_fragment ( color , colors [ :enabled ] )
379482 end
483+
484+ defp newline_after_block , do: IO . puts ( IO.ANSI . reset )
380485end
0 commit comments