defmodule GitGud.Packages.About do
@moduledoc """
What a package *is*, for people: a title, a one-line summary, a
longer description and whether it has an icon — one answer for the
packages pages, the org catalog and the mobile API.
Explicit wins. A summary or description set on the package (on the
packages page, or with `X-Package-Summary` / `X-Package-Description`
at upload) is what the publisher chose to say, so it goes first for
every format. After that each format's artifact speaks for itself
where it can — a wheel's `Summary`, a crate's `description`, a
.deb's synopsis — read from the newest version's metadata, which the
adapters already keep. Formats that carry nothing (an APK, a raw
file, an image) say nothing rather than repeating the name.
The title is the name a person would call it: an APK's `android:label`
rather than `org.example.app`. Everything else is the package name.
"""
alias GitGud.Packages
alias GitGud.Packages.Package
alias GitGud.Packages.PackageFile
alias GitGud.Packages.PackageVersion
@type t :: %{
title: String.t(),
summary: String.t() | nil,
description: String.t() | nil,
icon?: boolean()
}
@doc """
About every package in `packages`, keyed by id. One query for the
lot — a catalog asks about a whole namespace at once.
"""
@spec for_packages([Package.t()]) :: %{integer() => t()}
def for_packages(packages) do
latest = packages |> Enum.map(& &1.id) |> Packages.latest_files()
Map.new(packages, fn package -> {package.id, build(package, Map.get(latest, package.id))} end)
end
@doc "About one package."
@spec for_package(Package.t()) :: t()
def for_package(%Package{} = package),
do: package |> List.wrap() |> for_packages() |> Map.fetch!(package.id)
defp build(%Package{} = package, file) do
file_meta = (file && file.metadata) || %{}
version_meta = version_metadata(file)
title = present(file_meta["label"]) || package.name
description =
present(package.description) || native_description(package.format, version_meta)
summary =
present(package.summary) ||
native_summary(package.format, version_meta) ||
first_line(description)
%{
title: title,
summary: unless(summary && same_text?(summary, title), do: summary),
description: description,
icon?: is_binary(file_meta["icon_key"])
}
end
defp version_metadata(%PackageFile{package_version: %PackageVersion{metadata: meta}})
when is_map(meta),
do: meta
defp version_metadata(_), do: %{}
# Each format's own word for itself, from what its adapter stored.
defp native_summary("pypi", meta), do: present(meta["summary"])
defp native_summary("rpm", %{"rpm" => rpm}), do: present(rpm["summary"])
# A Debian description's first line *is* its synopsis, by policy.
defp native_summary("deb", %{"control" => %{"Description" => d}}), do: first_line(d)
defp native_summary(_format, _meta), do: nil
defp native_description(format, meta) when format in ~w(cargo hex),
do: present(meta["description"])
defp native_description("rpm", %{"rpm" => rpm}), do: present(rpm["description"])
# The extended part, below the synopsis; continuation lines are
# indented one space and a lone "." is a paragraph break.
defp native_description("deb", %{"control" => %{"Description" => d}}) when is_binary(d) do
case String.split(d, "\n", parts: 2) do
[_synopsis, rest] ->
rest
|> String.split("\n")
|> Enum.map_join("\n", fn line ->
case String.trim(line) do
"." -> ""
text -> text
end
end)
|> present()
_ ->
nil
end
end
defp native_description(_format, _meta), do: nil
defp first_line(nil), do: nil
defp first_line(text), do: text |> String.split("\n", parts: 2) |> hd() |> present()
defp present(text) when is_binary(text) do
case String.trim(text) do
"" -> nil
trimmed -> trimmed
end
end
defp present(_), do: nil
# A summary that only restates the title is worse than none.
defp same_text?(a, b), do: normalize(a) == normalize(b)
defp normalize(text),
do: text |> String.trim() |> String.trim_trailing(".") |> String.downcase()
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
4.2 KiB · text
History
a625083