Skip to main content

src/internal/iconify.gleam

import gleam/http/request
import gleam/httpc.{FailedToConnect, InvalidUtf8Response, ResponseTimeout}
import gleam/io
import gleam/list
import gleam/result
import gleam/string
import internal/config.{type Configuration}
import internal/converter
import internal/module

const base_uri: String = "https://api.iconify.design/"

type IconifyError {
  HttpError(String, httpc.HttpError)
  InvalidResponse(String)
  ConversionError(String)
  ModuleError(module.ModuleError)
}

pub fn add(icon: String, config: Configuration) -> Nil {
  let #(set, name) =
    string.split_once(icon, "/")
    |> result.unwrap(#(config.iconset, icon))
  let module = string.replace(config.module, "*", normalize(set))
  let module = case string.ends_with(module, "/") {
    False -> module
    True -> module <> normalize(name)
  }
  let filename = config.root <> "src/" <> module <> ".gleam"

  io.print("- " <> set <> "/" <> name <> ": ")

  case do_add(set, name, filename) {
    Ok(change) -> io.println(module.change_to_string(change))
    Error(HttpError(url, InvalidUtf8Response)) ->
      io.println("Invalid response from " <> url)
    Error(HttpError(url, FailedToConnect(..))) ->
      io.println("Failed to connect to " <> url)
    Error(HttpError(url, ResponseTimeout)) -> io.println("Timeout from " <> url)
    Error(InvalidResponse(url)) -> io.println("Invalid response from " <> url)
    Error(ConversionError(_)) -> io.println("Couldn't convert SVG to lustre")
    Error(ModuleError(error)) -> io.println(module.error_to_string(error))
  }
}

fn do_add(
  set: String,
  name: String,
  filename: String,
) -> Result(module.ModuleChange, IconifyError) {
  download(set, name)
  |> result.try(convert)
  |> result.try(adjust(filename, name, _))
}

fn download(set: String, name: String) -> Result(String, IconifyError) {
  let url = base_uri <> set <> "/" <> name <> ".svg"
  let assert Ok(request) = request.to(url)

  request
  |> httpc.send()
  |> result.map_error(HttpError(url, _))
  |> result.try(fn(response) {
    case response.status {
      200 -> Ok(response.body)
      _ -> Error(InvalidResponse(url))
    }
  })
}

fn convert(svg: String) -> Result(String, IconifyError) {
  svg
  |> converter.to_lustre
  |> result.map_error(ConversionError)
}

fn adjust(
  filename: String,
  fn_name: String,
  fn_body: String,
) -> Result(module.ModuleChange, IconifyError) {
  module.adjust(filename, fn_name, fn_body)
  |> result.map_error(ModuleError)
}

fn normalize(filename: String) -> String {
  filename
  |> string.lowercase()
  |> string.split("")
  |> list.map(fn(char) {
    case char {
      "a"
      | "b"
      | "c"
      | "d"
      | "e"
      | "f"
      | "g"
      | "h"
      | "i"
      | "j"
      | "k"
      | "l"
      | "m"
      | "n"
      | "o"
      | "p"
      | "q"
      | "r"
      | "s"
      | "t"
      | "u"
      | "v"
      | "w"
      | "x"
      | "y"
      | "z" -> char
      _ -> "_"
    }
  })
  |> string.join("")
}