- 
                Notifications
    You must be signed in to change notification settings 
- Fork 352
feat: add Markdown formatter #2148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| 📦 Docs artifacts are ready: https://github.com/elixir-lang/ex_doc/actions/runs/18916968511/artifacts/4408945305 | 
| @josevalim any thoughts here thus far? | 
| Sorry, a bit busy with Elixir v1.19-rc and today I was out of focus. It is in my inbox and I will review it as soon as I can. | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @yordis that's amazing! Let me share some ideas that could be useful.
I was inspecting the generated Markdown files and thinking if we could apply some changes. See the differences here https://gist.github.com/leandrocp/ee4f0ba8325b410b8650ccd26b9b2351 (CompiledWithDocs.md vs CompiledWithDocs_PROPOSAL.md)
- Use frontmatter block to describe global values/notes
- Use the format "Summary / Functions" similar to HTML pages (so Functions become a level 2 heading)
- Include source links
- Reorganize metadata, for eg: add (deprecated) and doc in summary
- Remove links to hexdocs.pm because 1) it breaks the content and I guess that would make it harder for LLMs to parse; 2) I'm not sure it should link to html pages
You can see some examples on https://shopify.dev/docs/api/liquid/basics.md and https://vercel.com/docs/rest-api/reference/sdk.md
e1c6f8f    to
    a6870e3      
    Compare
  
    | @leandrocp thanks for the help, about the formatting, I do not have any strong opinions of the final output, I will leave it to @josevalim and you to decide on that front, I can adjust it | 
a6870e3    to
    5131f1a      
    Compare
  
    | Just a heads up I am on holidays. This is still on top of my list but I cannot promise anything until I am back (next Wednesday). Sorry! | 
| Hey @yordis no strong opinions either, just trying to figure out a format that presents all the info. Regarding links, just to be clear it should contain links eventually but I believe that HexDocs must define how to serve Markdown pages first, for eg something like https://hexdocs.pm/elixir/1.18.4/Enum.md | 
| Some additional context: ExDoc is not tied to HexDocs and vice versa. We should design links with the assumptions it is served by any static host. I am thinking just using Enum.md with an anchor, same as html, is fine though? The anchor won’t actually work, but at least it is some context? | 
| 
 Yeah it seems appropriate. Shopify docs links to "canonical" URLs, for eg in https://shopify.dev/docs/api/liquid/tags/form you can see a bunch of anchor links, but in the .md docs (https://shopify.dev/docs/api/liquid/tags/form.md) they are rendered as  Vercel is not much different, they include relative links inside .md docs, for eg  Claude use relative links with anchors, for eg in https://docs.claude.com/en/docs/build-with-claude/prompt-caching you can see a link  | 
| I am back. My goal is to release the new hexdocs.pm, review #2055, and then focus on this. | 
| I am around, once you folks aligned, please share a action-driven feedback, I am can put the work on | 
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
…rgument and updating related calls
…ation synopses for Markdown
5131f1a    to
    9770ce7      
    Compare
  
    …c_ast.ex and templates.ex
| @josevalim could you give a second pass to the code review 🙏🏻 | 
…nd Markdown templates
| @@ -0,0 +1,177 @@ | |||
| defmodule ExDoc.Formatter.MARKDOWN do | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't follow the naming convention.
| defmodule ExDoc.Formatter.MARKDOWN do | |
| defmodule ExDoc.Formatter.Markdown do | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eksperimental I had the same reaction, but there is code path that assume the module as all uppercase, so I had to rollback to this. I will fix at some point
| <%= if deprecated = node.deprecated do %> | ||
| > This <%= node.type %> is deprecated. <%= h(deprecated) %>. | ||
| <% end %> | ||
|  | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| <%= if deprecated = node.deprecated do %> | |
| > This <%= node.type %> is deprecated. <%= h(deprecated) %>. | |
| <% end %> | |
| <%= if deprecated = node.deprecated do %> | |
| > This <%= node.type %> is deprecated. <%= h(deprecated) %>. | |
| <% end %> | 
| <%= if deprecated = module.deprecated do %> | ||
| > This <%= module.type %> is deprecated. <%=h deprecated %>. | ||
| <% end %> | ||
|  | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| <%= if deprecated = module.deprecated do %> | |
| > This <%= module.type %> is deprecated. <%=h deprecated %>. | |
| <% end %> | |
| <%= if deprecated = module.deprecated do %> | |
| > This <%= module.type %> is deprecated. <%=h deprecated %>. | |
| <% end %> | 
| <%= for {name, nodes} <- summary, _key = text_to_id(name) do %> | ||
|  | ||
| ### <%=h to_string(name) %> | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| <%= for {name, nodes} <- summary, _key = text_to_id(name) do %> | |
| ### <%=h to_string(name) %> | |
| <%= for {name, nodes} <- summary, _key = text_to_id(name) do %> | |
| ### <%=h to_string(name) %> | 
| <%= if deprecated = node.deprecated do %> | ||
| > <%= h(deprecated) %> | ||
| <% end %> | ||
|  | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| <%= if deprecated = node.deprecated do %> | |
| > <%= h(deprecated) %> | |
| <% end %> | |
| <%= if deprecated = node.deprecated do %> | |
| > <%= h(deprecated) %> | |
| <% end %> | 
|  | ||
| def node_synopsis(_), do: nil | ||
|  | ||
| # Extract synopsis as DocAST (similar to ExDoc.DocAST.synopsis but returns AST instead of HTML string) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| # Extract synopsis as DocAST (similar to ExDoc.DocAST.synopsis but returns AST instead of HTML string) | |
| # Extract synopsis as DocAST (similar to ExDoc.DocAST.synopsis but returns an AST instead of an HTML string). | 
| @spec synopsis(nil) :: nil | ||
| def synopsis(doc) when is_binary(doc) do | ||
| case :binary.split(doc, "\n\n") do | ||
| [left, _] -> String.trim_trailing(left, ": ") <> "\n\n" | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String.trim_trailing/2 does not strip every character in the second argument.
Also note that String.trim_trailing/1 removes all Unicode white-spaces, so it's better to use that. There's an edgecase that will not be covered though "foo  :   :  :"
| [left, _] -> String.trim_trailing(left, ": ") <> "\n\n" | |
| [left, _] -> String.trim_trailing(left) |> String.trim_trailing(left, ":") <> "\n\n" | 
| @heading_regex ~r/^(\#{1,6})\s+(.*)/m | ||
| defp rewrite_headings(content) when is_binary(content) do | ||
| @heading_regex | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Storing regular expressions in module attributes has been deprecated in Elixir v1.19.
| @heading_regex ~r/^(\#{1,6})\s+(.*)/m | |
| defp rewrite_headings(content) when is_binary(content) do | |
| @heading_regex | |
| defp rewrite_headings(content) when is_binary(content) do | |
| ~r/^(\#{1,6})\s+(.*)/m | 
| @@ -0,0 +1,9 @@ | |||
| # <%= config.project %> v<%= config.version %> - Documentation - Table of contents | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| # <%= config.project %> v<%= config.version %> - Documentation - Table of contents | |
| # <%= config.project %> v<%= config.version %> - Documentation - Table of Contents | 
| generate_docs(doc_config(context)) | ||
|  | ||
| content = File.read!(tmp_dir <> "/markdown/index.md") | ||
| assert content =~ ~r{^# Elixir v1\.0\.1 - Documentation - Table of contents$}m | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| assert content =~ ~r{^# Elixir v1\.0\.1 - Documentation - Table of contents$}m | |
| assert content =~ ~r{^# Elixir v1\.0\.1 - Documentation - Table of Contents$}m | 
Signed-off-by: Yordis Prieto yordis.prieto@gmail.com