aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/asciideck_parser.erl70
-rw-r--r--src/asciideck_to_manpage.erl8
-rw-r--r--test/parser_SUITE.erl52
3 files changed, 107 insertions, 23 deletions
diff --git a/src/asciideck_parser.erl b/src/asciideck_parser.erl
index 90ae115..2744138 100644
--- a/src/asciideck_parser.erl
+++ b/src/asciideck_parser.erl
@@ -203,7 +203,7 @@ p1_maybe_p(Lines=[{LN, _}|_], AST, St) ->
p1_p(Lines, AST, St, LN, []).
p1_p([{_, <<>>}|Tail], AST0, St, LN, [_|Acc]) ->
- Text = format(iolist_to_binary(lists:reverse(Acc))),
+ Text = format(iolist_to_binary(lists:reverse(Acc)), LN, St),
case AST0 of
[{block_title, Title}|AST] ->
p1(Tail, [paragraph(Text, #{title => Title}, ann(LN, St))|AST], St);
@@ -211,38 +211,64 @@ p1_p([{_, <<>>}|Tail], AST0, St, LN, [_|Acc]) ->
p1(Tail, [paragraph(Text, #{}, ann(LN, St))|AST], St)
end;
p1_p([{_, Line}|Tail], AST, St, LN, Acc) ->
+ %% @todo We need to keep line/col information. To do this
+ %% we probably should keep an index of character number -> line/col
+ %% that we pass to the format function. Otherwise the line/col
+ %% information on text will point to the paragraph start.
p1_p(Tail, AST, St, LN, [<<" ">>, Line|Acc]).
%% Inline formatting.
%% @todo Probably do it as part of the node functions that require it.
-format(Text) ->
- case format(Text, [], <<>>, $\s) of
+format(Text, LN, St) ->
+ case format(Text, LN, St, [], <<>>, $\s) of
[Bin] when is_binary(Bin) -> Bin;
Formatted -> Formatted
end.
-format(<<>>, Acc, <<>>, _) ->
+format(<<>>, _, _, Acc, <<>>, _) ->
lists:reverse(Acc);
-format(<<>>, Acc, BinAcc, _) ->
+format(<<>>, _, _, Acc, BinAcc, _) ->
lists:reverse([BinAcc|Acc]);
-format(<< "link:", Rest0/bits >>, Acc, BinAcc, Prev) when Prev =:= $\s ->
+format(<< "link:", Rest0/bits >>, LN, St, Acc0, BinAcc, Prev) when Prev =:= $\s ->
case re:run(Rest0, "^([^[]*)\\[([^]]*)\\](.*)", [{capture, all, binary}]) of
- nomatch -> format(Rest0, Acc, << BinAcc/binary, "link:" >>, $:);
- {match, [_, Link, Text, Rest]} -> format(Rest, [{link, Link, Text}, BinAcc|Acc], <<>>, $])
- end;
-format(<< $*, Rest0/bits >>, Acc, BinAcc, Prev) when Prev =:= $\s ->
- case binary:split(Rest0, << $* >>) of
- [_] -> format(Rest0, Acc, << BinAcc/binary, $* >>, $*);
- [Em, Rest] -> format(Rest, [{em, Em}, BinAcc|Acc], <<>>, $*)
+ nomatch ->
+ format(Rest0, LN, St, Acc0, << BinAcc/binary, "link:" >>, $:);
+ {match, [_, Link, Text, Rest]} ->
+ Acc = case BinAcc of
+ <<>> -> Acc0;
+ _ -> [BinAcc|Acc0]
+ end,
+ format(Rest, LN, St, [rel_link(Text, Link, ann(LN, St))|Acc], <<>>, $])
end;
-format(<< $`, Rest0/bits >>, Acc, BinAcc, Prev) when Prev =:= $\s ->
- case binary:split(Rest0, << $` >>) of
- [_] -> format(Rest0, Acc, << BinAcc/binary, $` >>, $`);
- [Mono, Rest] -> format(Rest, [{mono, Mono}, BinAcc|Acc], <<>>, $`)
+format(<< C, Rest0/bits >>, LN, St, Acc0, BinAcc, Prev) when Prev =:= $\s ->
+ %% @todo In some cases we must format inside the quoted text too.
+ %% Therefore we need to have some information about what to do here.
+ Quotes = #{
+ $* => {strong, text},
+ $` => {mono, literal}
+ },
+ case maps:get(C, Quotes, undefined) of
+ undefined ->
+ format(Rest0, LN, St, Acc0, << BinAcc/binary, C >>, C);
+ {NodeType, QuotedType} ->
+ case binary:split(Rest0, << C >>) of
+ [_] ->
+ format(Rest0, LN, St, Acc0, << BinAcc/binary, $* >>, $*);
+ [QuotedText0, Rest] ->
+ Acc = case BinAcc of
+ <<>> -> Acc0;
+ _ -> [BinAcc|Acc0]
+ end,
+ QuotedText = case QuotedType of
+ text -> format(QuotedText0, LN, St);
+ literal -> QuotedText0
+ end,
+ format(Rest, LN, St, [quoted(NodeType, QuotedText, ann(LN, St))|Acc], <<>>, $*)
+ end
end;
-format(<< C, Rest/bits >>, Acc, BinAcc, _) ->
- format(Rest, Acc, << BinAcc/binary, C >>, C).
+format(<< C, Rest/bits >>, LN, St, Acc, BinAcc, _) ->
+ format(Rest, LN, St, Acc, << BinAcc/binary, C >>, C).
%% Second pass.
@@ -308,6 +334,12 @@ ll(Nodes, Attrs, Ann) ->
paragraph(Text, Attrs, Ann) ->
{p, Attrs, Text, Ann}.
+quoted(NodeType, Text, Ann) ->
+ {NodeType, #{}, Text, Ann}.
+
+rel_link(Text, Link, Ann) ->
+ {rel_link, #{target => Link}, Text, Ann}.
+
row(Nodes, Ann) ->
{row, #{}, Nodes, Ann}.
diff --git a/src/asciideck_to_manpage.erl b/src/asciideck_to_manpage.erl
index dbdb74d..4158b4d 100644
--- a/src/asciideck_to_manpage.erl
+++ b/src/asciideck_to_manpage.erl
@@ -69,7 +69,7 @@ man([{ll, _Attrs, Items, _Ann}|Tail], Acc0) ->
%% @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, [{em, P}], Ann}], CAnn}
+ 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 = [[
@@ -133,16 +133,16 @@ man_table_contents_cols([{cell, _CAttrs, [{p, _PAttrs, Text, _PAnn}], _CAnn}|Tai
man_format(Text) when is_binary(Text) ->
Text;
-man_format({link, Link, Text}) ->
+man_format({rel_link, #{target := Link}, Text, _}) ->
case re:run(Text, "^([-_:.a-zA-Z]*)(\\([0-9]\\))$", [{capture, all, binary}]) of
nomatch -> [Text, " (", Link, ")"];
{match, [_, ManPage, ManSection]} -> ["\\fB", ManPage, "\\fR", ManSection]
end;
-man_format({em, Text}) ->
+man_format({strong, _, Text, _}) ->
["\\fB", man_format(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({mono, _, Text, _}) ->
man_format(Text);
man_format(Text) when is_list(Text) ->
[man_format(T) || T <- Text].
diff --git a/test/parser_SUITE.erl b/test/parser_SUITE.erl
index 8869ec1..eca21a3 100644
--- a/test/parser_SUITE.erl
+++ b/test/parser_SUITE.erl
@@ -39,6 +39,46 @@ empty_line_spaces(_) ->
[] = parse(" \n \n \n \n \n"),
ok.
+%% Text formatting.
+
+quoted_text_strong(_) ->
+ doc("Strong text formatting. (10.1)"),
+ [{p, _, [{strong, _, <<"Hello beautiful world!">>, _}], _}] =
+ parse("*Hello beautiful world!*"),
+ [{p, _, [{strong, _, <<"Hello">>, _}, <<" beautiful world!">>], _}] =
+ parse("*Hello* beautiful world!"),
+ [{p, _, [<<"Hello ">>, {strong, _, <<"beautiful">>, _}, <<" world!">>], _}] =
+ parse("Hello *beautiful* world!"),
+ [{p, _, [<<"Hello beautiful ">>, {strong, _, <<"world!">>, _}], _}] =
+ parse("Hello beautiful *world!*"),
+ [{p, _, [<<"Hello beautiful ">>, {strong, _, <<"multiline world!">>, _}, <<" lol">>], _}] =
+ parse("Hello beautiful *multiline\nworld!* lol"),
+ %% Nested formatting.
+ [{p, _, [{strong, _, [
+ <<"Hello ">>,
+ {rel_link, #{target := <<"downloads/cowboy-2.0.tgz">>}, <<"2.0">>, _},
+ <<" world!">>
+ ], _}], _}] =
+ parse("*Hello link:downloads/cowboy-2.0.tgz[2.0] world!*"),
+ ok.
+
+quoted_text_literal_mono(_) ->
+ doc("Literal monospace text formatting. (10.1)"),
+ [{p, _, [{mono, _, <<"Hello beautiful world!">>, _}], _}] =
+ parse("`Hello beautiful world!`"),
+ [{p, _, [{mono, _, <<"Hello">>, _}, <<" beautiful world!">>], _}] =
+ parse("`Hello` beautiful world!"),
+ [{p, _, [<<"Hello ">>, {mono, _, <<"beautiful">>, _}, <<" world!">>], _}] =
+ parse("Hello `beautiful` world!"),
+ [{p, _, [<<"Hello beautiful ">>, {mono, _, <<"world!">>, _}], _}] =
+ parse("Hello beautiful `world!`"),
+ [{p, _, [<<"Hello beautiful ">>, {mono, _, <<"multiline world!">>, _}, <<" lol">>], _}] =
+ parse("Hello beautiful `multiline\nworld!` lol"),
+ %% No text formatting must occur inside backticks.
+ [{p, _, [{mono, _, <<"Hello *beautiful* world!">>, _}], _}] =
+ parse("`Hello *beautiful* world!`"),
+ ok.
+
%% Title.
%% @todo Long titles. (11.1)
@@ -275,6 +315,18 @@ labeled_list(_) ->
%% Macros.
+rel_link(_) ->
+ doc("Relative links are built using the link:<target>[<caption>] macro. (21.1.3)"),
+ [{p, _, [
+ {rel_link, #{target := <<"downloads/cowboy-2.0.tgz">>}, <<"2.0">>, _}
+ ], _}] = parse("link:downloads/cowboy-2.0.tgz[2.0]"),
+ [{p, _, [
+ <<"Download ">>,
+ {rel_link, #{target := <<"downloads/cowboy-2.0.zip">>}, <<"Cowboy 2.0">>, _},
+ <<" as zip">>
+ ], _}] = parse("Download link:downloads/cowboy-2.0.zip[Cowboy 2.0] as zip"),
+ ok.
+
comment_line(_) ->
doc("Lines starting with two slashes are treated as comments. (21.2.3)"),
[{comment, _, <<"This is a comment.">>, _}] = parse("// This is a comment."),