aboutsummaryrefslogtreecommitdiffstats
path: root/src/asciideck_to_manpage.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/asciideck_to_manpage.erl')
-rw-r--r--src/asciideck_to_manpage.erl236
1 files changed, 140 insertions, 96 deletions
diff --git a/src/asciideck_to_manpage.erl b/src/asciideck_to_manpage.erl
index bdff90e..37e4e73 100644
--- a/src/asciideck_to_manpage.erl
+++ b/src/asciideck_to_manpage.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2016, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2016-2018, Loïc Hoguin <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
@@ -19,7 +19,7 @@
-export([translate/2]).
translate(AST, Opts) ->
- {Man, Section, Output0} = translate_man(AST, Opts),
+ {Man, Section, Output0} = man(AST, Opts),
{CompressExt, Output} = case Opts of
#{compress := gzip} -> {".gz", zlib:gzip(Output0)};
_ -> {"", Output0}
@@ -32,7 +32,9 @@ translate(AST, Opts) ->
Output
end.
-translate_man([{title, #{level := 0}, Title0, _Ann}|AST], Opts) ->
+%% Header of the man page file.
+
+man([{section_title, #{level := 0}, Title0, _Ann}|AST], Opts) ->
ensure_name_section(AST),
[Title, << Section:1/binary, _/bits >>] = binary:split(Title0, <<"(">>),
Extra1 = maps:get(extra1, Opts, today()),
@@ -42,10 +44,10 @@ translate_man([{title, #{level := 0}, Title0, _Ann}|AST], Opts) ->
".TH \"", Title, "\" \"", Section, "\" \"",
Extra1, "\" \"", Extra2, "\" \"", Extra3, "\"\n"
".ta T 4n\n\\&\n",
- man(AST, [])
+ ast(AST)
]}.
-ensure_name_section([{title, #{level := 1}, Title, _}|_]) ->
+ensure_name_section([{section_title, #{level := 1}, Title, _}|_]) ->
case string:to_lower(string:strip(binary_to_list(Title))) of
"name" -> ok;
_ -> error(badarg)
@@ -57,22 +59,56 @@ today() ->
{{Y, M, D}, _} = calendar:universal_time(),
io_lib:format("~b-~2.10.0b-~2.10.0b", [Y, M, D]).
-man([], Acc) ->
- lists:reverse(Acc);
-man([{title, #{level := 1}, Title, _Ann}|Tail], Acc) ->
- man(Tail, [[".SH ", string:to_upper(binary_to_list(Title)), "\n"]|Acc]);
-man([{title, #{level := 2}, Title, _Ann}|Tail], Acc) ->
- man(Tail, [[".SS ", Title, "\n"]|Acc]);
-man([{p, _Attrs, Text, _Ann}|Tail], Acc) ->
- man(Tail, [[".LP\n", man_format(Text), "\n.sp\n"]|Acc]);
-man([{listing, Attrs, Listing, _Ann}|Tail], Acc0) ->
- Acc1 = case Attrs of
- #{title := Title} ->
- [[".PP\n\\fB", Title, "\\fR\n"]|Acc0];
- _ ->
- Acc0
- end,
- Acc = [[
+%% Loop over all types of AST nodes.
+
+ast(AST) ->
+ fold(AST, fun ast_node/1).
+
+fold(AST, Fun) ->
+ lists:reverse(lists:foldl(
+ fun(Node, Acc) -> [Fun(Node)|Acc] end,
+ [], AST)).
+
+ast_node(Node={Type, _, _, _}) ->
+ try
+ case Type of
+ section_title -> section_title(Node);
+ paragraph -> paragraph(Node);
+ listing_block -> listing_block(Node);
+ list -> list(Node);
+ table -> table(Node);
+ comment_line -> comment_line(Node);
+ _ ->
+ io:format("Ignored AST node ~p~n", [Node]),
+ []
+ end
+ catch _:_ ->
+ io:format("Ignored AST node ~p~n", [Node]),
+ []
+ end.
+
+%% Section titles.
+
+section_title({section_title, #{level := 1}, Title, _}) ->
+ [".SH ", string:to_upper(binary_to_list(Title)), "\n"];
+section_title({section_title, #{level := 2}, Title, _}) ->
+ [".SS ", Title, "\n"].
+
+%% Paragraphs.
+
+paragraph({paragraph, _, Text, _}) ->
+ [".LP\n", inline(Text), "\n.sp\n"].
+
+%% Listing blocks.
+
+listing_block({listing_block, Attrs, Listing, _}) ->
+ [
+ case Attrs of
+ #{<<"title">> := Title} ->
+ [".PP\n\\fB", Title, "\\fR\n"];
+ _ ->
+ []
+ end,
".if n \\{\\\n"
".RS 4\n"
".\\}\n"
@@ -82,55 +118,18 @@ man([{listing, Attrs, Listing, _Ann}|Tail], Acc0) ->
".fi\n"
".if n \\{\\\n"
".RE\n"
- ".\\}\n"]|Acc1],
- man(Tail, Acc);
-man([{ul, _Attrs, Items, _Ann}|Tail], Acc0) ->
- Acc = man_ul(Items, Acc0),
- man(Tail, Acc);
-man([{ll, _Attrs, Items, _Ann}|Tail], Acc0) ->
- Acc = man_ll(Items, Acc0),
- man(Tail, Acc);
-%% @todo Attributes.
-%% Currently acts as if options="headers" was always set.
-man([{table, _TAttrs, [{row, RowAttrs, Headers0, RowAnn}|Rows0], _TAnn}|Tail], Acc0) ->
- Headers = [{cell, CAttrs, [{p, Attrs, [{strong, #{}, P, CAnn}], Ann}], CAnn}
- || {cell, CAttrs, [{p, Attrs, P, Ann}], CAnn} <- Headers0],
- Rows = [{row, RowAttrs, Headers, RowAnn}|Rows0],
- Acc = [[
- ".TS\n"
- "allbox tab(:);\n",
- man_table_style(Rows, []),
- man_table_contents(Rows),
- ".TE\n"
- ".sp 1\n"]|Acc0],
- man(Tail, Acc);
-%% Skip everything we don't understand.
-man([_Ignore|Tail], Acc) ->
- io:format("Ignore ~p~n", [_Ignore]), %% @todo lol io:format
- man(Tail, Acc).
-
-man_ll([], Acc) ->
- Acc;
-man_ll([{li, #{label := Label}, Item, _LiAnn}|Tail], Acc0) ->
- Acc = [[
- ".PP\n"
- "\\fB", Label, "\\fR\n",
- ".RS 4\n",
- man_ll_item(Item),
- ".RE\n"]|Acc0],
- man_ll(Tail, Acc).
-
-man_ll_item([{ul, _Attrs, Items, _Ann}]) ->
- [man_ul(Items, []), "\n"];
-man_ll_item([{p, _PAttrs, Text, _PAnn}]) ->
- [man_format(Text), "\n"];
-man_ll_item([{p, _PAttrs, Text, _PAnn}|Tail]) ->
- [man_format(Text), "\n\n", man_ll_item(Tail)].
-
-man_ul([], Acc) ->
- Acc;
-man_ul([{li, _LiAttrs, [{p, _PAttrs, Text, _PAnn}], _LiAnn}|Tail], Acc0) ->
- Acc = [[
+ ".\\}\n"
+ ].
+
+%% Lists.
+
+list({list, #{type := bulleted}, Items, _}) ->
+ fold(Items, fun bulleted_list_item/1);
+list({list, #{type := labeled}, Items, _}) ->
+ fold(Items, fun labeled_list_item/1).
+
+bulleted_list_item({list_item, _, [{paragraph, _, Text, _}|AST], _}) ->
+ [
".ie n \\{\\\n"
".RS 2\n"
"\\h'-02'\\(bu\\h'+01'\\c\n"
@@ -140,40 +139,85 @@ man_ul([{li, _LiAttrs, [{p, _PAttrs, Text, _PAnn}], _LiAnn}|Tail], Acc0) ->
".sp -1\n"
".IP \\(bu 2.3\n"
".\\}\n",
- man_format(Text), "\n"
- ".RE\n"]|Acc0],
- man_ul(Tail, Acc).
+ inline(Text), "\n",
+ ast(AST),
+ ".RE\n"
+ ].
+
+labeled_list_item({list_item, #{label := Label}, [{paragraph, _, Text, _}|AST], _}) ->
+ [
+ ".PP\n"
+ "\\fB", inline(Label), "\\fR\n",
+ ".RS 4\n",
+ inline(Text), "\n",
+ ast(AST),
+ ".RE\n"
+ ].
+
+%% Tables.
+
+table({table, _, Rows0, _}) ->
+ Rows = table_apply_options(Rows0),
+ [
+ ".TS\n"
+ "allbox tab(:);\n",
+ table_style(Rows), ".\n",
+ table_contents(Rows),
+ ".TE\n"
+ ".sp 1\n"
+ ].
+
+%% @todo Currently acts as if options="headers" was always set.
+table_apply_options([{row, RAttrs, Headers0, RAnn}|Tail]) ->
+ Headers = [{cell, CAttrs, [{strong, #{}, CText, CAnn}], CAnn}
+ || {cell, CAttrs, CText, CAnn} <- Headers0],
+ [{row, RAttrs, Headers, RAnn}|Tail].
+
+table_style(Rows) ->
+ [[table_style_cells(Cells), "\n"]
+ || {row, _, Cells, _} <- Rows].
+
+table_style_cells(Cells) ->
+ ["lt " || {cell, _, _, _} <- Cells].
+
+table_contents(Rows) ->
+ [[table_contents_cells(Cells), "\n"]
+ || {row, _, Cells, _} <- Rows].
+
+table_contents_cells([FirstCell|Cells]) ->
+ [table_contents_cell(FirstCell),
+ [[":", table_contents_cell(Cell)] || Cell <- Cells]].
-man_table_style([], [_|Acc]) ->
- lists:reverse([".\n"|Acc]);
-man_table_style([{row, _, Cols, _}|Tail], Acc) ->
- man_table_style(Tail, [$\n, man_table_style_cols(Cols, [])|Acc]).
+table_contents_cell({cell, _, Text, _}) ->
+ ["T{\n", inline(Text), "\nT}"].
-man_table_style_cols([], [_|Acc]) ->
- lists:reverse(Acc);
-man_table_style_cols([{cell, _, _, _}|Tail], Acc) ->
- man_table_style_cols(Tail, [$\s, "lt"|Acc]).
+%% Comment lines are printed in the generated file
+%% but are not visible in viewers.
-man_table_contents(Rows) ->
- [man_table_contents_cols(Cols, []) || {row, _, Cols, _} <- Rows].
+comment_line({comment_line, _, Text, _}) ->
+ ["\\# ", Text, "\n"].
-man_table_contents_cols([], [_|Acc]) ->
- lists:reverse(["\n"|Acc]);
-man_table_contents_cols([{cell, _CAttrs, [{p, _PAttrs, Text, _PAnn}], _CAnn}|Tail], Acc) ->
- man_table_contents_cols(Tail, [$:, "\nT}", man_format(Text), "T{\n"|Acc]).
+%% Inline formatting.
-man_format(Text) when is_binary(Text) ->
+inline(Text) when is_binary(Text) ->
Text;
-man_format({rel_link, #{target := Link}, Text, _}) ->
+%% When the link is the text we only print it once.
+inline({link, #{target := Link}, Link, _}) ->
+ Link;
+inline({link, #{target := Link}, Text, _}) ->
case re:run(Text, "^([-_:.a-zA-Z0-9]*)(\\([0-9]\\))$", [{capture, all, binary}]) of
nomatch -> [Text, " (", Link, ")"];
{match, [_, ManPage, ManSection]} -> ["\\fB", ManPage, "\\fR", ManSection]
end;
-man_format({strong, _, Text, _}) ->
- ["\\fB", man_format(Text), "\\fR"];
+inline({emphasized, _, Text, _}) ->
+ ["\\fI", inline(Text), "\\fR"];
+inline({strong, _, Text, _}) ->
+ ["\\fB", inline(Text), "\\fR"];
%% We are already using a monospace font.
-%% @todo Maybe there's a readable formatting we could use to differentiate from normal text?
-man_format({mono, _, Text, _}) ->
- man_format(Text);
-man_format(Text) when is_list(Text) ->
- [man_format(T) || T <- Text].
+inline({inline_literal_passthrough, _, Text, _}) ->
+ inline(Text);
+%% Xref links appear as plain text in manuals.
+inline({xref, _, Text, _}) ->
+ inline(Text);
+inline(Text) when is_list(Text) ->
+ [inline(T) || T <- Text].