lib/spdx_cli.ex

# SPDX-FileCopyrightText: 2021 Rosa Richter
#
# SPDX-License-Identifier: MIT

defmodule SpdxCli do
  @moduledoc """
  Documentation for `SpdxCli`.
  """

  def main(argv) do
    Optimus.new!(
      name: "spdx",
      description: "Software license tool",
      version: "1.1.0",
      author: "Rosa Richter <cosmic.lady.rosa@gmail.com>",
      about: "Utility for searching and using the SPDX software license database",
      allow_unknown_args: false,
      parse_double_dash: true,
      options: [
        field: [
          value_name: "FIELD",
          short: "-f",
          long: "--field",
          help: "Part of the license info to fetch. {text|header|name}",
          default: "text",
          parser: fn arg ->
            if arg in ["text", "header", "name"] do
              {:ok, arg}
            else
              {:error, "Unknown field"}
            end
          end
        ]
      ],
      args: [
        license: [
          name: "LICENSE_ID",
          help: "SPDX identifier of the license."
        ]
      ],
      subcommands: [
        list: [
          name: "ls",
          about: "list the license database"
        ]
      ]
    )
    |> Optimus.parse!(argv)
    |> do_command()
  end

  defp do_command({[:list], _command}) do
    {:ok, _} = HTTPoison.start()

    with {:ok, response} when response.status_code == 200 <-
           HTTPoison.get("https://spdx.org/licenses/licenses.json"),
         {:ok, licenses_data} <- Poison.decode(response.body) do
      licenses_data["licenses"]
      |> Enum.map(fn license ->
        "#{license["licenseId"]} #{license["name"]}"
      end)
      |> Enum.join("\n")
      |> IO.puts()
    else
      {:ok, %HTTPoison.Response{status_code: status}} ->
        IO.puts(:stderr, "Received #{status} response from spdx.org")
        System.stop(2)

      {:error, %HTTPoison.Error{} = e} ->
        IO.puts(:stderr, "Failed to connect to spdx.org: #{Exception.message(e)}")
        System.stop(3)

      {:error, e} ->
        IO.puts(:stderr, "Failed to parse response from spdx.org: #{Exception.message(e)}")
        System.stop(4)
    end
  end

  defp do_command(%Optimus.ParseResult{} = command) do
    license_id = command.args.license
    field = translate_field_name(command.options.field)

    {:ok, _} = HTTPoison.start()

    with {:ok, response} when response.status_code == 200 <-
           HTTPoison.get("https://spdx.org/licenses/#{license_id}.json"),
         {:ok, license_data} <- Poison.decode(response.body) do
      IO.puts(license_data[field])
    else
      {:ok, %HTTPoison.Response{status_code: 404}} ->
        IO.puts(:stderr, "No license found")
        System.stop(1)

      {:ok, %HTTPoison.Response{status_code: status}} ->
        IO.puts(:stderr, "Received #{status} response from spdx.org")
        System.stop(2)

      {:error, %HTTPoison.Error{} = e} ->
        IO.puts(:stderr, "Failed to connect to spdx.org: #{Exception.message(e)}")
        System.stop(3)

      {:error, e} ->
        IO.puts(:stderr, "Failed to parse response from spdx.org: #{Exception.message(e)}")
        System.stop(4)
    end
  end

  defp translate_field_name("text"), do: "licenseText"
  defp translate_field_name("header"), do: "standardLicenseHeader"
  defp translate_field_name("name"), do: "name"
end