diff options
-rw-r--r-- | src/asciideck_parser.erl | 70 | ||||
-rw-r--r-- | src/asciideck_to_manpage.erl | 8 | ||||
-rw-r--r-- | test/parser_SUITE.erl | 52 |
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."), |