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
Make mix help list aliases #7805
Conversation
Not sure why the latest build failed: https://travis-ci.org/elixir-lang/elixir/jobs/397193995. Does the new code introduce something flaky? 😨 |
I was just wanting this exact functionality. Thanks for doing the pull request. |
The build failure seems unrelated. I rested the failed suite. |
lib/mix/lib/mix/tasks/help.ex
Outdated
|
||
defp task_str(task_name) when is_list(task_name) do | ||
task_strs = Enum.map(task_name, &task_str/1) |> Enum.join(", ") | ||
"[" <> task_strs <> "]" |
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 wonder about the format here with the list, but I'm not really sure what would be better.
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 would say we should just call inspect
. It is the simplest implementation and it would be similar to what we have today. But I am not sure.
I guess the formatting was the biggest concern for including aliases in the mix help
, as they can get quite verbose. Another option is to just have a simple text: "# Alias defined in mix.exs" and call it a day.
Thoughts?
I think in order for this feature to be complete, this PR should support This might also simplify the formatting of the aliases in
Thoughts? |
I like this solution. |
I like:
OR
|
@smorin If you want to add descriptions and what not, then I would say you should just go ahead and define a proper task. |
@josevalim Why force someone to write a task for something that takes 3 words. It's using a sledge hammer to drive a nail. Especially in cases, where the command is 2 or 3 separate words and the description is the longest part. Why clutter a code base, adding steps for a developer, and more lines of code to maintain. npm does it will good effect: https://docs.npmjs.com/misc/scripts |
lib/mix/lib/mix/tasks/help.ex
Outdated
{docs, max} = build_task_doc_list(modules) | ||
aliases = | ||
load_aliases() | ||
|> Enum.filter(&String.contains?(elem(&1, 0), pattern)) |
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.
We try to avoid single pipes. I would suggest to refactor this with a for
comprehension:
aliases =
for {alias_name, _} = alias <- load_aliases(),
String.contains?(alias_name, pattern),
do: alias
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.
why do we try to avoid single pipes?
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.
@Sihui mostly because if we don't have multiple expressions, the pipeline ends up being more noise than helping readability. We know it is subjective but that's why we decided to add a "rule", so it is a clear path and we remove subjectivity. :)
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.
@josevalim
A (weak) counter argument will be using single pipe makes adding additional pipe easier. But that's for another discussion.
I don't think we should avoid Enum.filter
b/c of avoiding single pipes though. I think Enum.filter
expresses our intention clearer than the for
comprehension. How about
Enum.filter(load_aliases, &String.contains?(elem(&1, 0), pattern))
A side note, we have quite a few usages of single pipes throughout the file (and the codebase), does it mean we want to replace all of them?
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.
Eventually we want to replace all pipes, but we try to avoid small changes like that in the codebase in order to keep the number of changes down. Enum.filter/2
is also very much fine with me, I just don't love elem/2
since it hides what the tuple looks like. What about:
Enum.filter(load_aliases(), fn {name, _} -> String.contains?(name, pattern) 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.
good call! I not a fan of elem
either.
lib/mix/lib/mix/tasks/help.ex
Outdated
@@ -116,6 +122,11 @@ defmodule Mix.Tasks.Help do | |||
|> Enum.filter(&(Mix.Task.moduledoc(&1) != false)) | |||
end | |||
|
|||
defp load_aliases() do | |||
Mix.Project.config()[:aliases] | |||
|> Enum.map(fn {k, v} -> {Atom.to_string(k), v} 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.
We try to avoid single pipes and single-letter variables. Maybe rewrite this as:
defp load_aliases() do
for {name, value} <- Mix.Project.config()[:aliases] do
{Atom.to_string(name), value}
end
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.
updated to
defp load_aliases() do
Mix.Project.config()[:aliases]
|> Enum.map(fn {k, v} -> {Atom.to_string(k), v} end)
|> Map.new()
end
to make verbose_doc
easier. (turning it to a map also makes more sense.)
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.
What do you think about these names to address the single-letter variables?
defp load_aliases() do
Mix.Project.config()[:aliases]
|> Enum.map(fn {alias_name, alias_tasks} -> {Atom.to_string(alias_name), alias_tasks} end)
|> Map.new()
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.
You're doing two passes this way. I didn't know you needed a map but if you do, I'd much rather go with into: %{}
in the for
comprehension or just Map.new/2
:
aliases = Mix.Project.config()[:aliases]
Map.new(aliases, fn {alias_name, alias_tasks} -> {Atom.to_string(alias_name), alias_tasks} end)
(also, definitely let's avoid single-letter variables!)
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.
@whatyouhide I used map b/c I couldn't find an easy way to accomplish Map.has_key?(aliases, task)
. If there's a way without having to use map, I would love to learn that! :)
lib/mix/lib/mix/tasks/help.ex
Outdated
"[" <> task_strs <> "]" | ||
end | ||
|
||
defp task_str(task_name) when is_bitstring(task_name), do: task_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.
Instead of is_bitstring/1
, maybe use is_binary/1
here?
I would say that ad-hoc aliases would end-up being more clutter than well-structured tasks but I see your point. We should move the discussion about revamping aliases elsewhere then. Maybe a proposal to the elixir-lang-core mailing list as the scope of this particular issue is well defined based on how aliases work today and we should not go off-topic. |
assert output =~ "# mix nested.h\n\n" | ||
|
||
assert output =~ | ||
~r/Alias for \[#Function<.*\/1 in .*ComplexAliases.project\/0>, \"h foo bar\"\]\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.
Couldn't find an easy way to format "nested.h": [&Mix.shell().info(inspect(&1)), "h foo bar"]
nicely.
Thoughts?
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.
@Sihui this is honestly good enough to me. :)
^^ Updated to:
|
lib/mix/lib/mix/tasks/help.ex
Outdated
|
||
## Arguments | ||
|
||
mix help - prints all tasks and their shortdoc | ||
mix help - prints all aliases, tasks and task shortdoc |
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 there is a typo here "prints all aliases, tasks and their shortdoc"
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 don't think we have shortdoc for aliases. shortdoc are marked with @shortdoc
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.
"and their summary" or "and their short description"?
lib/mix/lib/mix/tasks/help.ex
Outdated
@@ -4,11 +4,12 @@ defmodule Mix.Tasks.Help do | |||
@shortdoc "Prints help information for tasks" | |||
|
|||
@moduledoc """ | |||
Lists all tasks or prints the documentation for a given task. | |||
Lists all aliases and tasks or prints the documentation for a given task. |
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.
Minor nitpick: I would change the order of "tasks" and "aliases" here (and below). For some reason, in my head 😅 , it makes more sense to put "tasks" at the beginning as they are more relevant for this specific task :) So, "Lists all tasks and aliases or prints the documentation for a given task or alias."
lib/mix/test/mix/tasks/help_test.exs
Outdated
@@ -29,6 +29,20 @@ defmodule Mix.Tasks.HelpTest do | |||
end | |||
end | |||
|
|||
test "help list alias task", context 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.
Minor nitpick: I think it should be "lists", and maybe "help lists all aliases"?
lib/mix/test/mix/tasks/help_test.exs
Outdated
|
||
in_tmp(context.test, fn -> | ||
Mix.Tasks.Help.run(["--search", "c"]) | ||
assert_received {:mix_shell, :info, ["mix c" <> _]} |
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.
Could this test give false positives due to the match of other tasks like mix compile
?
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.
@fertapric good catch!
lib/mix/lib/mix/tasks/help.ex
Outdated
|
||
## Arguments | ||
|
||
mix help - prints all tasks and their shortdoc | ||
mix help - prints all aliases, tasks and task shortdoc | ||
mix help ALIAS - prints the definition for the given alias | ||
mix help TASK - prints full docs for the given task | ||
mix help --search PATTERN - prints all tasks that contain PATTERN in the 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.
"prints all tasks and aliases that contain PATTERN in the 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.
good catch!
lib/mix/test/mix/tasks/help_test.exs
Outdated
|
||
in_tmp(context.test, fn -> | ||
Mix.Tasks.Help.run(["--search", "h"]) | ||
assert_received {:mix_shell, :info, ["mix h" <> _]} |
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.
Unfortunately, this might also give false positives with mix help
itself, mix hex
or any mix hex.*
.
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 would either include a space, or match on the message live above:
assert_received {:mix_shell, :info, ["mix h" <> message]}
assert message =~ ~r/# Alias defined in mix.exs/
@josevalim makes sense moving to core-mailing list.
|
@josevalim @michalmuskala @whatyouhide Can this be merged? Anything else I need to do? |
We need two reviews to merge, so we are waiting on the second one. :) Once
somebody has some time available, they will review it and likely merge.
--
*José Valimwww.plataformatec.com.br
<http://www.plataformatec.com.br/>Founder and Director of R&D*
|
|> Enum.map(fn {k, _} -> Atom.to_string(k) end) | ||
Enum.map(Mix.Project.config()[:aliases], fn {alias_name, _} -> | ||
Atom.to_string(alias_name) | ||
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.
Why not use aliases = load_aliases()
here too?
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 mean that and then Enum.sort(Map.to_list(aliases) ++ tasks)
.
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.
Map.to_list(aliases)
won't work because aliases is [{alias1, taks1}, {alias2, task2}, ...]
.
If we want to use load_aliases()
, we might need to do
aliases = Enum.map(load_aliases(), fn {alias_name, _} -> alias_name end)
for info <- Enum.sort(aliases ++ tasks) do
Mix.shell().info(info)
end
At that point, we might as well use Mix.Project.config()[:aliases]
directly, so we don't have to iterate twice.
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.
Oh I missed that it was just the alias name, sorry!
Thanks @Sihui! 💟 |
Thank you all for reviewing! ❤️ ❤️ ❤️ |
ref: https://trello.com/c/9DzdXbp4/4-make-mix-help-list-aliases
cc @michalmuskala