From d8d7b9c4fbf54e6e337dc3ce4eeec4a20d941dfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 21 May 2024 12:29:58 +0200 Subject: [PATCH] Add class annotation to HTML from fenced blocks This can be used, for example, to discard Mermaid diagrams when printing documentation in the shell. --- lib/stdlib/src/shell_docs_markdown.erl | 40 ++++++++------- lib/stdlib/test/shell_docs_markdown_SUITE.erl | 50 +++++++++++++++---- 2 files changed, 62 insertions(+), 28 deletions(-) diff --git a/lib/stdlib/src/shell_docs_markdown.erl b/lib/stdlib/src/shell_docs_markdown.erl index c0307f3e9a1b..e9b3a9545294 100644 --- a/lib/stdlib/src/shell_docs_markdown.erl +++ b/lib/stdlib/src/shell_docs_markdown.erl @@ -73,12 +73,12 @@ format_line(Ls) -> OmissionSet :: sets:set(atom()). format_line([], _BlockSet0) -> []; -format_line([{Tag, [], List} | Rest], BlockSet0) -> +format_line([{Tag, Attrs, List} | Rest], BlockSet0) -> case format_line(List, sets:add_element(Tag, BlockSet0)) of [] -> format_line(Rest, BlockSet0); Ls -> - [{Tag, [], Ls}] ++ format_line(Rest, BlockSet0) + [{Tag, Attrs, Ls}] ++ format_line(Rest, BlockSet0) end; format_line([Bin | Rest], BlockSet0) when is_binary(Bin) -> %% Ignores formatting these elements @@ -365,8 +365,8 @@ process_kind_block([<<">", _/binary>>=Line | Rest], Block) -> %% %% process block code %% -process_kind_block([<<"```", _Line/binary>> | Rest], Block) -> - Block ++ process_fence_code(Rest, []); +process_kind_block([<<"```", Line/binary>> | Rest], Block) -> + Block ++ process_fence_code(Rest, [], Line); %% %% New line %% @@ -459,8 +459,9 @@ strip_spaces(Rest, Acc, _) -> ol | li | dl | dt | dd | h1 | h2 | h3 | h4 | h5 | h6. -type chunk_element_attrs() :: []. --type quote() :: {blockquote,[], shell_docs:chunk_elements()}. --type code() :: {pre, chunk_element_attrs(), [{code,[], shell_docs:chunk_elements()}]}. +-type code_element_attrs() :: [{class,unicode:chardata()}]. +-type quote() :: {blockquote, chunk_element_attrs(), shell_docs:chunk_elements()}. +-type code() :: {pre, chunk_element_attrs(), [{code, code_element_attrs(), shell_docs:chunk_elements()}]}. -type p() :: {p, chunk_element_attrs(), shell_docs:chunk_elements()}. -type i() :: {i, chunk_element_attrs(), shell_docs:chunk_elements()}. -type em() :: {em, chunk_element_attrs(), shell_docs:chunk_elements()}. @@ -803,27 +804,30 @@ format(Format, Line0) when is_list(Line0)-> PrevLines :: [binary()], %% Represent unprocessed lines. HtmlErlang :: shell_docs:chunk_elements(). process_code([], Block) -> - [create_code(Block)]; + [create_code(Block, [])]; process_code([<<" ", Line/binary>> | Rest], Block) -> %% process blank line followed by code process_code(Rest, [Line | Block]); process_code(Rest, Block) -> process_code([], Block) ++ parse_md(Rest, []). -process_fence_code([], Block) -> - [create_code(Block)]; -process_fence_code([<<"```">> | Rest], Block) -> +process_fence_code([], Block, Leading) -> + case string:trim(hd(binary:split(Leading, [~"\t", ~" "]))) of + <<>> -> [create_code(Block, [])]; + Trimmed -> [create_code(Block, [{class, <<"language-", Trimmed/binary>>}])] + end; +process_fence_code([<<"```">> | Rest], Block, Leading) -> %% close block - process_fence_code([], Block) ++ parse_md(Rest, []); -process_fence_code([Line | Rest], Block) -> + process_fence_code([], Block, Leading) ++ parse_md(Rest, []); +process_fence_code([Line | Rest], Block, Leading) -> {Stripped, _} = strip_spaces(Line, 0, infinity), maybe <<"```", RestLine/binary>> ?= Stripped, {<<>>, _} ?= strip_spaces(RestLine, 0, infinity), - process_fence_code([<<"```">> | Rest], Block) + process_fence_code([<<"```">> | Rest], Block, Leading) else _ -> - process_fence_code(Rest, [Line | Block]) + process_fence_code(Rest, [Line | Block], Leading) end. -spec process_comment(Line :: [binary()]) -> [binary()]. @@ -853,14 +857,14 @@ create_paragraph(<<$\s, Line/binary>>) -> create_paragraph(Line) when is_binary(Line) -> p(Line). --spec create_code(Lines :: [binary()]) -> code(). -create_code(CodeBlocks) when is_list(CodeBlocks) -> +-spec create_code(Lines :: [binary()], code_element_attrs()) -> code(). +create_code(CodeBlocks, CodeAttrs) when is_list(CodeBlocks) -> %% assumes that the code block is in reverse order Bin = trim_and_add_new_line(CodeBlocks), - {pre,[], [{code,[], [Bin]}]}. + {pre, [], [{code, CodeAttrs, [Bin]}]}. create_table(Table) when is_list(Table) -> - {pre,[], [{code,[], Table}]}. + {pre, [], [{code, [{class, ~"table"}], Table}]}. -spec quote(Quote :: list()) -> quote(). diff --git a/lib/stdlib/test/shell_docs_markdown_SUITE.erl b/lib/stdlib/test/shell_docs_markdown_SUITE.erl index 116c4ba6ccc0..def3d0b56a1e 100644 --- a/lib/stdlib/test/shell_docs_markdown_SUITE.erl +++ b/lib/stdlib/test/shell_docs_markdown_SUITE.erl @@ -48,8 +48,9 @@ %% fence code -export([single_line_fence_code_test/1, multiple_line_fence_code_test/1, + single_line_fence_code_no_language_test/1, single_line_fence_code_no_language_spaces_test/1, paragraph_between_fence_code_test/1, fence_code_ignores_link_format_test/1, - fence_code_with_spaces/1]). + fence_code_with_spaces/1, fence_code_with_tabs/1]). %% br -export([start_with_br_test/1, multiple_br_followed_by_paragraph_test/1, @@ -188,9 +189,11 @@ code_tests() -> fence_code_tests() -> [single_line_fence_code_test, multiple_line_fence_code_test, + single_line_fence_code_no_language_test, + single_line_fence_code_no_language_spaces_test, paragraph_between_fence_code_test, fence_code_ignores_link_format_test, - fence_code_with_spaces + fence_code_with_spaces, fence_code_with_tabs ]. br_tests() -> @@ -492,7 +495,7 @@ single_line_fence_code_test(_Conf) -> ```erlang test() -> ok. ```", - Result = [ code(~"test() -> ok.\n")], + Result = [ code(~"test() -> ok.\n", [{class, ~"language-erlang"}])], compile_and_compare(Input, Result). multiple_line_fence_code_test(_Conf) -> @@ -501,9 +504,24 @@ multiple_line_fence_code_test(_Conf) -> test() -> ok. ```", - Result = [ code(~"test() ->\n ok.\n")], + Result = [ code(~"test() ->\n ok.\n", [{class, ~"language-erlang"}])], compile_and_compare(Input, Result). +single_line_fence_code_no_language_test(_Conf) -> + Input = ~" +``` +test() -> ok. +```", + Result = [ code(~"test() -> ok.\n")], + compile_and_compare(Input, Result). + +single_line_fence_code_no_language_spaces_test(_Conf) -> + Input = ~" +```\s\s +test() -> ok. +```", + Result = [ code(~"test() -> ok.\n")], + compile_and_compare(Input, Result). paragraph_between_fence_code_test(_Conf) -> Input = ~"This is a test: @@ -512,7 +530,7 @@ test() -> ok. ```", Result = [p(~"This is a test:"), - code(~"test() ->\n ok.\n")], + code(~"test() ->\n ok.\n", [{class, ~"language-erlang"}])], compile_and_compare(Input, Result). fence_code_ignores_link_format_test(_Conf) -> @@ -521,15 +539,23 @@ fence_code_ignores_link_format_test(_Conf) -> [foo](bar) ```", Result = [p(~"This is a test:"), - code(~"[foo](bar)\n")], + code(~"[foo](bar)\n", [{class, ~"language-erlang"}])], compile_and_compare(Input, Result). fence_code_with_spaces(_Config) -> Input = -~" ```erlang +~" ```erlang\s\s + [foo](bar) +```", + Result = [code(~" [foo](bar)\n", [{class, ~"language-erlang"}])], + compile_and_compare(Input, Result). + +fence_code_with_tabs(_Config) -> + Input = +~" ```erlang\ttrailing [foo](bar) ```", - Result = [code(~" [foo](bar)\n")], + Result = [code(~" [foo](bar)\n", [{class, ~"language-erlang"}])], compile_and_compare(Input, Result). start_with_br_test(_Conf) -> @@ -1019,10 +1045,14 @@ header(Level, Text) when is_integer(Level) -> {HeadingLevelAtom, [], [Text]}. code(X) -> - {pre,[],[inline_code(X)]}. + code(X, []). +code(X, Attrs) when is_list(X) -> + {pre,[],[{code,Attrs,X}]}; +code(X, Attrs) -> + {pre,[],[{code,Attrs,[X]}]}. table(Table) when is_list(Table) -> - {pre,[], [inline_code(Table)]}. + {pre,[], [{code, [{class, ~"table"}], Table}]}. inline_code(X) when is_list(X) -> {code,[],X};