Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 77 additions & 7 deletions lib/mix/tasks/hex.info.ex
Original file line number Diff line number Diff line change
Expand Up @@ -117,28 +117,73 @@ defmodule Mix.Tasks.Hex.Info do
retirements = package["retirements"] || %{}
Hex.Shell.info("Config: " <> package["configs"]["mix.exs"])
print_locked_package(locked_package)
Hex.Shell.info(["Releases: "] ++ format_releases(releases, Map.keys(retirements)) ++ ["\n"])

Hex.Shell.info(["\nRecent releases:\n" | format_releases(releases, Map.keys(retirements))])

print_downloads(package["downloads"])
print_meta(meta)
end

defp format_releases(releases, retirements) do
{releases, rest} = Enum.split(releases, 8)

Enum.map(releases, &format_version(&1, retirements))
|> Enum.intersperse([", "])
releases
|> Enum.map(&[" ", format_version(&1, retirements), "\n"])
|> add_ellipsis(rest)
end

defp format_version(%{"version" => version}, retirements) do
defp format_version(%{"version" => version} = release, retirements) do
date = format_release_date(release["inserted_at"])

if version in retirements do
[:yellow, version, " (retired)", :reset]
[:yellow, version, date, " (retired)", :reset]
else
[version]
[version, date]
end
end

defp format_release_date(nil), do: ""

defp format_release_date(date_string) do
case parse_date(date_string) do
{:ok, date} -> " (#{date})"
_ -> ""
end
end

defp add_ellipsis(output, []), do: output
defp add_ellipsis(output, _rest), do: output ++ [", ..."]
defp add_ellipsis(output, _rest), do: output ++ [" ...\n"]

defp print_downloads(nil), do: :ok

defp print_downloads(downloads) do
parts =
Enum.reject(
[
format_download_count("Yesterday", downloads["day"]),
format_download_count("Last 7 days", downloads["week"]),
format_download_count("All time", downloads["all"])
],
&is_nil/1
)

if parts != [] do
Hex.Shell.info("Downloads:\n " <> Enum.join(parts, "\n ") <> "\n")
end
end

defp format_download_count(_label, nil), do: nil
defp format_download_count(label, count), do: "#{label}: #{add_thousands_separators(count)}"

defp add_thousands_separators(count, separator \\ " ") do
count
|> Integer.to_string()
|> String.reverse()
|> String.codepoints()
|> Enum.chunk_every(3)
|> Enum.join(separator)
|> String.reverse()
end

defp print_meta(meta) do
print_list(meta, "licenses")
Expand All @@ -150,11 +195,14 @@ defmodule Mix.Tasks.Hex.Info do

print_retirement(release)
Hex.Shell.info("Config: " <> release["configs"]["mix.exs"])
print_release_published_at(release["inserted_at"])

if release["has_docs"] do
Hex.Shell.info("Documentation at: #{Hex.Utils.hexdocs_url(organization, package, version)}")
end

print_release_downloads(release["downloads"])

if requirements = release["requirements"] do
Hex.Shell.info("Dependencies:")

Expand All @@ -169,6 +217,28 @@ defmodule Mix.Tasks.Hex.Info do
print_publisher(release)
end

defp print_release_downloads(count) when is_integer(count) do
Hex.Shell.info("Downloads: #{add_thousands_separators(count)}")
end

defp print_release_downloads(_), do: :ok

defp print_release_published_at(nil), do: :ok

defp print_release_published_at(date_string) do
case parse_date(date_string) do
{:ok, date} -> Hex.Shell.info("Released: #{date}")
_ -> :ok
end
end

defp parse_date(date_string) do
case DateTime.from_iso8601(date_string) do
{:ok, datetime, _offset} -> {:ok, DateTime.to_date(datetime)}
_ -> Date.from_iso8601(date_string)
end
end

defp print_locked_package(nil), do: nil

defp print_locked_package(locked_package) do
Expand Down
104 changes: 101 additions & 3 deletions test/mix/tasks/hex.info_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ defmodule Mix.Tasks.Hex.InfoTest do
Mix.Tasks.Hex.Info.run(["ex_doc"])
assert_received {:mix_shell, :info, ["Some description\n"]}
assert_received {:mix_shell, :info, ["Config: {:ex_doc, \"~> 0.1.0\"}"]}
assert_received {:mix_shell, :info, ["Releases: 0.1.0, 0.1.0-rc1, 0.0.1\n"]}
assert_received {:mix_shell, :info, ["\nRecent releases:\n" <> releases]}
today = Date.utc_today()
assert releases == " 0.1.0 (#{today})\n 0.1.0-rc1 (#{today})\n 0.0.1 (#{today})\n"

assert catch_throw(Mix.Tasks.Hex.Info.run(["no_package"])) == {:exit_code, 1}
assert_received {:mix_shell, :error, ["No package with name no_package"]}
Expand All @@ -26,6 +28,52 @@ defmodule Mix.Tasks.Hex.InfoTest do
assert_received {:mix_shell, :error, ["Package name is empty"]}
end

test "package downloads" do
bypass = Bypass.open()
Hex.State.put(:api_url, "http://localhost:#{bypass.port}/api")

today = Date.utc_today()
inserted_at = "#{today}T12:00:00Z"

package_body = %{
"name" => "ex_doc",
"meta" => %{
"description" => "Some description",
"licenses" => ["GPL-2.0", "MIT", "Apache-2.0"],
"links" => %{"docs" => "http://docs", "repo" => "http://repo"}
},
"configs" => %{"mix.exs" => "{:ex_doc, \"~> 0.1.0\"}"},
"releases" => [
%{"version" => "0.1.0", "inserted_at" => inserted_at},
%{"version" => "0.1.0-rc1", "inserted_at" => inserted_at},
%{"version" => "0.0.1", "inserted_at" => inserted_at}
],
"retirements" => %{},
"downloads" => %{
"all" => 96_128_698,
"day" => 21_494,
"recent" => 1_421_136,
"week" => 124_095
}
}

Bypass.expect(bypass, "GET", "/api/packages/ex_doc", fn conn ->
conn
|> Plug.Conn.put_resp_header("content-type", "application/vnd.hex+erlang")
|> Plug.Conn.resp(200, Hex.Utils.safe_serialize_erlang(package_body))
end)

Mix.Tasks.Hex.Info.run(["ex_doc"])
assert_received {:mix_shell, :info, ["Downloads:\n" <> downloads]}

assert String.split(downloads, "\n") == [
" Yesterday: 21 494",
" Last 7 days: 124 095",
" All time: 96 128 698",
""
]
end

test "locked package" do
Mix.Project.push(Simple)

Expand All @@ -38,7 +86,17 @@ defmodule Mix.Tasks.Hex.InfoTest do
assert_received {:mix_shell, :info, ["Some description\n"]}
assert_received {:mix_shell, :info, ["Locked version: 0.2.0"]}
assert_received {:mix_shell, :info, ["Config: {:ecto, \"~> 3.3\"}"]}
assert_received {:mix_shell, :info, ["Releases: 3.3.2, 3.3.1, 0.2.1, 0.2.0\n"]}
assert_received {:mix_shell, :info, ["\nRecent releases:\n" <> releases]}

today = Date.utc_today()

assert String.split(releases, "\n") == [
" 3.3.2 (#{today})",
" 3.3.1 (#{today})",
" 0.2.1 (#{today})",
" 0.2.0 (#{today})",
""
]
end)
after
purge([
Expand All @@ -50,7 +108,9 @@ defmodule Mix.Tasks.Hex.InfoTest do

test "package with retired release" do
Mix.Tasks.Hex.Info.run(["tired"])
assert_received {:mix_shell, :info, ["Releases: 0.2.0, 0.1.0 (retired)\n"]}
today = Date.utc_today()
assert_received {:mix_shell, :info, ["\nRecent releases:\n" <> releases]}
assert releases == " 0.2.0 (#{today})\n 0.1.0 (#{today}) (retired)\n"
end

test "package with --organization flag" do
Expand Down Expand Up @@ -83,8 +143,46 @@ defmodule Mix.Tasks.Hex.InfoTest do
assert_received {:mix_shell, :error, ["No release with name ex_doc 1.2.3"]}
end

test "release downloads" do
bypass = Bypass.open()
Hex.State.put(:api_url, "http://localhost:#{bypass.port}/api")

today = Date.utc_today()
inserted_at = "#{today}T12:00:00Z"

release_body = %{
"version" => "1.5.0-alpha.2",
"checksum" => "6dcaa0d9fdc22afe9b4d362f17f20844a85f121c50b6e9b9466ac04fe39f3665",
"inserted_at" => inserted_at,
"updated_at" => inserted_at,
"retirement" => nil,
"publisher" => nil,
"downloads" => 26_208,
"configs" => %{
"erlang.mk" => "dep_jason = hex 1.5.0-alpha.2",
"mix.exs" => "{:jason, \"~\u003E 1.5.0-alpha.2\"}",
"rebar.config" => "{jason, \"1.5.0-alpha.2\"}"
}
}

Bypass.expect(bypass, "GET", "/api/packages/ex_doc/releases/0.1.0", fn conn ->
conn
|> Plug.Conn.put_resp_header("content-type", "application/vnd.hex+erlang")
|> Plug.Conn.resp(200, Hex.Utils.safe_serialize_erlang(release_body))
end)

Mix.Tasks.Hex.Info.run(["ex_doc", "0.1.0"])
assert_received {:mix_shell, :info, ["Downloads: 26 208"]}
end

test "prints publisher info for releases" do
Mix.Tasks.Hex.Info.run(["ex_doc", "0.0.1"])
assert_received {:mix_shell, :info, ["Published by: user (user@mail.com)"]}
end

test "prints release date for releases" do
Mix.Tasks.Hex.Info.run(["ex_doc", "0.0.1"])
assert_received {:mix_shell, :info, ["Released: " <> date]}
assert date == "#{Date.utc_today()}"
end
end
Loading