lib/modkit/path_tool.ex

defmodule Modkit.PathTool do
  def list_create_dirs(path) when is_binary(path) do
    list_create_dirs([path])
  end

  def list_create_dirs(paths) when is_list(paths) do
    paths
    |> Enum.reduce(MapSet.new(), &dir_to_create/2)
    |> MapSet.to_list()
  end

  defp dir_to_create(".", acc), do: acc

  defp dir_to_create(path, acc) do
    cond do
      File.dir?(path) ->
        acc

      File.regular?(path) ->
        raise ArgumentError, "cannot create directory #{path}, file exists"

      MapSet.member?(acc, path) ->
        acc

      :_ ->
        acc = dir_to_create(Path.dirname(path), acc)
        MapSet.put(acc, path)
    end
  end

  def to_snake(segment) when is_binary(segment) when is_atom(segment) do
    underscore(segment) |> no_double_underscores()
  end

  defp no_double_underscores(segment) do
    if String.contains?(segment, "__") do
      segment |> String.replace("__", "_") |> no_double_underscores()
    else
      segment
    end
  end

  # This is a copy of `Macro.underscore/1` but that does not add an underscore
  # after digits.
  defp underscore(atom) when is_atom(atom) do
    "Elixir." <> rest = Atom.to_string(atom)
    underscore(rest)
  end

  defp underscore(<<h, t::binary>>) do
    <<to_lower_char(h)>> <> do_underscore(t, h)
  end

  defp underscore("") do
    ""
  end

  defp do_underscore(<<h, t, rest::binary>>, _)
       when h >= ?A and h <= ?Z and not (t >= ?A and t <= ?Z) and not (t >= ?0 and t <= ?9) and
              t != ?. and t != ?_ do
    <<?_, to_lower_char(h), t>> <> do_underscore(rest, t)
  end

  defp do_underscore(<<h, t::binary>>, prev)
       when h >= ?A and h <= ?Z and not (prev >= ?A and prev <= ?Z) and
              not (prev >= ?0 and prev <= ?9) and prev != ?_ do
    <<?_, to_lower_char(h)>> <> do_underscore(t, h)
  end

  defp do_underscore(<<?., t::binary>>, _) do
    <<?/>> <> underscore(t)
  end

  defp do_underscore(<<h, t::binary>>, _) do
    <<to_lower_char(h)>> <> do_underscore(t, h)
  end

  defp do_underscore(<<>>, _) do
    <<>>
  end

  defp to_lower_char(char) when char >= ?A and char <= ?Z, do: char + 32
  defp to_lower_char(char), do: char
end