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
Nest module aliases #906
Nest module aliases #906
Conversation
Normalizing happens before the config struct is created, so no default option exists at that point
@@ -109,6 +109,9 @@ defmodule ExDoc.Retriever do | |||
{title, id} = module_title_and_id(module_data) | |||
module_group = GroupMatcher.match_module(config.groups_for_modules, module, id) | |||
|
|||
module_aliases = Map.get(config, :nest_module_aliases) |
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.
I think you can do config.nest_module_aliases
. :)
defp truncate_if_aliased(title, aliases) do | ||
aliases | ||
|> Map.keys() | ||
|> Enum.find(&String.starts_with?(title, &1)) |
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.
I think we should actually change this to only match modules that are namespaced. So I would change it to something like:
|> Enum.find_value(fn prefix ->
case String.split(title, prefix) do
["", "." <> suffix] -> {prefix, suffix}
_ -> nil
end
end)
What do you think?
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.
I agree, as I hadn't really considered this being used for a non-nested module.
You could argue that if a (non nested) module's name needs to be changed, it should probably just be renamed in the code base, but there are projects that are essentially Elixir libraries/wrappers for some other project/service/company that has a really long name. In those cases, the root module name (i.e. first section) is usually a variation of the project/service/company name and "ex", which will therefore also be long. So it's possible this decision will have to be revisited in the future, but my preference would be to keep the implementation/concept simple initially (i.e. limit the feature to nested modules), and enhancing it later as issues arise in the community at large.
This leads me to my next question: how helpful should the code error handling be with improper configuration? E.g. trying to alias non nested modules, giving improper values for the alias value, etc. Should it just crash (e.g. failing to find a matching function clause due to type mismatch) and assume the user will refer to the documentation to find out the proper configuration? Or should "catch all" clauses be added where appropriate to return some sort of nice error message?
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.
So it's possible this decision will have to be revisited in the future, but my preference would be to keep the implementation/concept simple initially (i.e. limit the feature to nested modules), and enhancing it later as issues arise in the community at large.
Exactly. I think it is simpler to start more conservative and expand as necessary. We have the luxury to afford this as we are still pre-1.0. So here are my suggestions:
-
Consider only really nested modules in the group. So if you nest
Foo.Bar
,Foo.Bar.Baz
is part of the group butFoo.Bar
isn't -
As proposed by @whatyouhide, let's not support the
:as
option right now. The list given tonest_module_aliases
is just a simple list of atoms.
I also wouldn't worry about error handling right now. Let's write assertive code, that expects things to be in the proper format, and we can collect feedback in case the API is confusing.
Thanks @davidsulc! I really like this! I personally think you are in the correct direction! ❤️ I have added two comments. |
It took me quite some time to figure out what has been nested and what not. In the screen there is:
which, correct me if I'm wrong is supposed to mean
This PR provides a great flexibility, yet I am not convinced this is the right approach as it can lead to confusion. If I were writing this doc, it would probably make sense to write it exactly as presented on the screen shot, but from the readers perspective it is not clear. As the original issue started because of autogenerated library with multiple levels of nesting, I think it would be better to have either I like the idea as |
I have to say that by reading the rationale and intent of the PR, I am pretty confused by this feature. I don't really understand how to use it or why it's done this way. In #834, the last idea that @josevalim suggests (where you specify the prefix and if a module matches then it's nested) feels reasonable, but I am not sure adding |
Right, this is confusing because the feature as currently implemented is mixing both nested and non-nested things, which is why I proposed to only group together nested ones.
I would prefer to start with a simplest approach with one level hierarchy. I think a tree is just moving the problem around, as the tree is worst to navigate, and a deep tree will be a source of other issues.
The |
It was confusing indeed. Grouping per alias sounds good. Btw, what do you think about renaming |
@whatyouhide What initially prompted my interest in this issue (https://elixirforum.com/t/exdoc-long-module-names-in-sidebar-getting-clipped-what-to-do/17288/10) is an API wrapper I'm working on (https://github.com/davidsulc/scrapy_cloud_ex). It's name is This API has its endpoints split into 2 groups, "app" and "storage", which affects (e.g.) parameter formats a specific endpoint expects (e.g. pagination params are provided differently to endpoints depend on whether they belong to the "app" or "storage" endpoint group). The reason my initial implementation has an Also, it means the group name wouldn't be I understand @josevalim's concerns about allowing renaming modules (#834 (comment)), but that could be handled by e.g. checking the alias is a valid prefix of the aliased module. Finally, I thought it would be nice to be able to group modules under a common name while omitting long common prefixes. I hope this isn't coming across as argumentative, as I just wanted to shed some light on where I'm coming from and the reasoning behind my initial implementation. In effect, this issue was opened due to deeply nested modules, but my initial concern (https://elixirforum.com/t/exdoc-long-module-names-in-sidebar-getting-clipped-what-to-do/17288/10) was mainly about dealing with long module names. |
I like using something that refers to @davidsulc thanks for the clarification! 👍 Given there is some divergence, let's focus on the MVP and then iterate on that. :) And what do you think about the naming changes? |
Ok, I will udpate the PR to use only a list of module names as an MVP implementation. Regarding the attribute name, I think A lot of newcomers have to learn that module names are just atoms without a hierarchy, so talking about nesting could be counter-productive. |
@davidsulc you originally wrote |
@josevalim Yeah, I edited to change the name when I remembered the "dots don't imply hierarchy" you discover when new to Elixir. But you're right that "foo" is technically a prefix to "foo" itself, so |
Yeah in the |
There are also three options we can consider:
I think 2 is the clearest but it can be considered too noisy? Thoughts? |
This, for sure. I don't like the uppercasing at all as it doesn't match the actual module namespacing. |
I agree the uppercasing is an issue and I think we can revisit the uppercasing altogether, but let's go with baby steps for now. :D |
I strongly disagree with reusing groups for module nesting. It's visually confusing and mixes two very different things in the dame UI. Module names should always be non-bold and group names should always be bold. An alternative to nested modules would be to abreviate module names using the first letter of the module. For example: This is just one idea I've thought about now, but the main point is that i really dislike reusing the group system for this. |
I like @tmbb's idea. Here's what it could look like for (part of) the mentioned lib: Which would be obtained via a The idea would be that (in some future version):
I like this for the following reasons:
This would be driven by a As a first implementation, only a list of module prefixes would be accepted, and modules with matching prefixes will be collapsed, except for the module itself (if it exists) which would collapse until the last section. So given the option
You'll note that Later, depending on feedback, etc. we might introduce the @josevalim I think this is in line with your desire to have a straightforward initial implementation that could be enhanced later. Thoughts? |
If people believe this is a good approach, then please go ahead. :) My only reservations are the same as before:
I don't think we should touch the module itself. If you want to collapse it, then you can add the parent of the module itself to the list. I think having to say "it does this for everything except for X" is always a potential source of confusion. :)
For the reason above, I think we use the naming "nesting"/"nested" instead of "prefixes". |
I'd like to collapse it, because then the collapsed parts will align with the collapsed parts in the follwing lines (i.e. the nested modules) which makes it easier to follow things visually. Adding the uncollapsed parent to the list is a good idea, but what if that module doesn't exist?
👍 |
Elixir does not require the parent to exist. So you can say |
I misunderstood what you meant. I thought by "add it to the list" you meant the sidebar, when you were in fact talking about adding it to the option list. 👍 |
Two quick notes:
|
Regarding the 2nd point, I realized that shortly after posting. My inclination is to not handle this in the first (naive) implementation, as disambiguation would need a handled a bit more thoroughly in my opinion. E.g. handle the case where But as I said, I think I would leave that for a later improvement. In the meantime, users can either live with the ambiguity in their docs, or ensure the collapsing doesn't cause ambiguity (by only collapsing ambiguity-free sections). Also, as this would be opt-in, it can simply not be used in cases where the naive solution doesn't fit, until an enhanced implementation is provided in the future. |
The ambiguity can be removed by hovering the mouse over the link, so it shouldn't be too bad. But yeah, it's a problem. |
I agree with this as well.
After seeing that I am really not a fan of it, it seems exceptionally hard to read to me. I'd prefer it to be more like:
Or render like this or so: Some.Long.Module.Path
|
After seeing that I am *really* not a fan of it, it seems exceptionally
hard to read to me. I'd prefer it to be more like:
That’s what the group system would provide. Sure, we are relying on the
current group implementation but we could easily add change font style on
the heading too.
--
*José Valimwww.plataformatec.com.br
<http://www.plataformatec.com.br/>Founder and Director of R&D*
|
I'm taking the liberty of closing this in favor of #907 (and I humbly suggest the discussion continue there), but feel free to reopen. |
(Tests and documentation still need to be written, this is just for preliminary feedback.)
A basic implementation for #834
It leverages
groups_for_modules
to provide the usual UI presentation, and to leverage the current ordering ability. As suggested by José, anest_module_aliases
key was added to the docs config.This new value is an array of alias configs, each of which is one of:
ModuleName
(equivalent to{ModuleName, as: ModuleName}
){Super.Duper.Really.Long.ModuleName
, as:Some.ModuleName
}{Super.Duper.Really.Long.ModuleName
, as: "My module"}The
as:
values are used as group names (as if they had been provided togroups_for_modules
) and are removed from the module's displayed title.Hopefully, this will be familiar to language users as it combine the
as:
syntax of module aliases with the multiple value formats as can be used when specifying child configs in supervisors.The objective for this first implementation is to provide enough flexibility while keeping the code changes simple. It is to be noted that the aliasing is very naive: the first match is kept, without attempting to find the longest prefix possible.
With these modifications, nesting could be specified like this (adapted from ExDoc's
mix.exs
):Adding empty groups matching
as:
values (or the module name, if absent) innest_modules_aliases
allows controlling the ordering of the blocks of nested modules.Here's the generated result: