defmodule Kudos do @moduledoc """ Documentation for Kudos. """ @license_file_names ~w(LICENSE.txt LICENSE.md LICENSE license.txt license.md license LICENSE-2.0.txt) @readme_file_names ~w(README.md README.markdown) @doc """ Generates a licenses file in the root dir with the licenses of all deps. ## Examples iex> Kudos.generate() |> String.length() 219 iex> Kudos.generate(false) |> String.length() 219 iex> Kudos.generate(true) |> String.length() 19050 """ def generate(include_dev_deps \\ false) do load_deps_meta_data(include_dev_deps) |> Enum.reduce(header(), fn meta_data, resp -> resp <> format(meta_data) end) |> String.trim() end defp header() do """ # Project Licenses This file was generated by the kudos utility. It contains the name, version and checksum, description, links, maintainers and license information for every dependency in this project. ## Dependencies """ end defp format(:umbrella) do "" end defp format(:dev) do "" end defp format(meta_data) do """ ### #{meta_data.name} (Version #{meta_data.version} | Checksum: #{checksum(meta_data.checksum)}) #{meta_data.description} Links: #{links(meta_data.links)} Maintainers: #{maintainers(meta_data.maintainers)} Licenses Used: #{meta_data.licenses} #{meta_data.license_file} """ end defp checksum(value) when is_list(value) do value[:branch] end defp checksum(value) do value end defp maintainers(values) when is_nil(values) do "" end defp maintainers(values) when is_map(values) do Map.to_list(values) |> maintainers() end defp maintainers(values) when is_list(values) do Enum.join(values, ", ") end defp links(values) when is_nil(values) do "" end defp links(values) when is_map(values) do Map.to_list(values) |> Enum.map(&prepare_link(&1)) |> Enum.join(", ") end defp prepare_link(value) when is_tuple(value) do "[#{elem(value, 0)}](#{elem(value, 1)})" end defp prepare_link({key, value}) do "[#{key}](#{value})" end defp load_deps_meta_data(include_dev_deps) do Mix.Dep.load_on_environment([]) |> Enum.map(fn dep -> Mix.Dep.in_dependency(dep, fn _ -> case is_umbrella?(dep) do true -> :umbrella false -> case include_dev_deps || is_prod?(dep) do false -> :dev _ -> %{ name: Atom.to_string(Mix.Project.config()[:app]), version: Mix.Project.config()[:version], checksum: elem(dep.opts[:lock], 3), description: Mix.Project.config()[:description], source_url: Mix.Project.config()[:source_url], links: get_in(Mix.Project.config(), [:package, :links]), maintainers: get_in(Mix.Project.config(), [:package, :maintainers]), licenses: get_in(Mix.Project.config(), [:package, :licenses]), license_file: get_license_file_content(dep.opts[:dest]) } end end end) end) end defp is_umbrella?(dep) do case List.keyfind(dep.opts, :in_umbrella, 0, {:in_umbrella, false}) do {:in_umbrella, true} -> true {:in_umbrella, false} -> from_umbrella?(dep) end end defp from_umbrella?(dep) do case List.keyfind(dep.opts, :from_umbrella, 0, {:from_umbrella, false}) do {:from_umbrella, true} -> true {:from_umbrella, false} -> false end end defp is_prod?(dep) do only = List.keyfind(dep.opts, :only, 0, []) case only do [] -> true _ -> false end end defp get_license_file_content(path) do (File.ls!(path) -- File.ls!(path) -- @license_file_names) |> read_license_file(path) end defp read_license_file([], path) do read_license_from_readme_file(path) end defp read_license_file([first_file_name | _], path) do Path.join(path, first_file_name) |> File.read!() end defp read_license_from_readme_file(path) do files = File.ls!(path) -- File.ls!(path) -- @readme_file_names read_license_from_readme_file(files, path) end defp read_license_from_readme_file([], _path) do "Full license text not found in dependency source." end defp read_license_from_readme_file([first_file_name | _], path) do readme_content = Path.join(path, first_file_name) |> File.read!() case String.split(readme_content, "# License", parts: 2) do [_, content] -> content _ -> "Full license text not found in dependency source." end end end