Fix credo issues

Fix credo issues

defmodule CoursePlannerWeb.SharedView do
  @moduledoc false
  use CoursePlannerWeb, :view

  alias CoursePlanner.Settings

  # helpers

  def path_exact_match(conn, path) do
    conn.request_path == path
  end

  def path_match(conn, path) do
    String.starts_with? conn.request_path, path
  end

  def get_gravatar_url(email, size \\ 100) do
    hash =
      :md5
      |> :crypto.hash(email)
      |> Base.encode16()
      |> String.downcase()

    "https://www.gravatar.com/avatar/#{hash}?d=mm&s=#{size}"
  end

  def display_user_name(user) do
    [user.name, user.family_name, user.nickname && "(#{user.nickname})"]
    |> Enum.filter(fn v -> String.length(to_string v) > 0 end)
    |> Enum.join(" ")
  end

  # form components

  def form_text(form, field, opts \\ []) do
    class = opts[:class] || ""
    label = opts[:label] || humanize(field)
    required = opts[:required] || nil
    error = error_message(form, field)

    value = Map.get form.data, field
    wrapper_class =
      if error do
        "is-invalid"
      else
        if required && String.length to_string value > 0 do
          "form-init"
        else
          ""
        end
      end

    render "form_text.html", form: form,
                             field: field,
                             label: label,
                             error: error,
                             wrapper_class: wrapper_class,
                             class: class,
                             required: required
  end

  def form_textarea(form, field, opts \\ []) do
    class = opts[:class] || ""
    label = opts[:label] || humanize(field)
    required = opts[:required] || nil
    error = error_message(form, field)

    value = Map.get form.data, field
    wrapper_class =
      if error do
        "is-invalid"
      else
        if required && String.length to_string value > 0 do
          "form-init"
        else
          ""
        end
      end

    rows = opts[:rows] || 3

    render "form_textarea.html", form: form,
                                 field: field,
                                 label: label,
                                 error: error,
                                 wrapper_class: wrapper_class,
                                 class: class,
                                 required: required,
                                 rows: rows
  end

  def form_password(form, field, opts \\ []) do
    class = opts[:class] || ""
    label = opts[:label] || humanize(field)
    required = opts[:required] || nil
    error = error_message(form, field)

    wrapper_class =
      if error do
        "is-invalid"
      else
        if required do "form-init" else "" end
      end

    render "form_password.html", form: form,
                                 field: field,
                                 label: label,
                                 error: error,
                                 wrapper_class: wrapper_class,
                                 class: class,
                                 required: required
  end

  def form_date(form, field, opts \\ []) do
    default = opts[:value] || Ecto.Date.utc()
    class = opts[:class] || ""
    label = opts[:label] || humanize(field)
    error = error_message(form, field)

    wrapper_class = if error do "is-invalid" else "" end

    render "form_date.html", form: form,
                             field: field,
                             label: label,
                             error: error,
                             wrapper_class: wrapper_class,
                             class: class,
                             default: default
  end

  def form_time(form, field, opts \\ []) do
    default = opts[:value] || %{hour: 0, min: 0}
    class = opts[:class] || ""
    label = opts[:label] || humanize(field)
    error = error_message(form, field)

    wrapper_class = if error do "is-invalid" else "" end

    render "form_time.html", form: form,
                             field: field,
                             label: label,
                             error: error,
                             wrapper_class: wrapper_class,
                             class: class,
                             default: default
  end

  def form_datetime(form, field, opts \\ []) do
    default = Map.merge(Ecto.DateTime.utc(), opts[:value] || %{})
    class = opts[:class] || ""
    label = opts[:label] || humanize(field)
    error = error_message(form, field)

    wrapper_class = if error do "is-invalid" else "" end

    render "form_datetime.html", form: form,
                                 field: field,
                                 label: label,
                                 error: error,
                                 wrapper_class: wrapper_class,
                                 class: class,
                                 default: default
  end

  def form_select(form, field, options, opts \\ []) do
    class = opts[:class] || ""
    label = opts[:label] || humanize(field)
    selected = opts[:selected] || nil
    error = error_message(form, field)

    wrapper_class = if error do "is-invalid" else "" end

    render "form_select.html", form: form,
                               field: field,
                               label: label,
                               error: error,
                               selected: selected,
                               options: options,
                               wrapper_class: wrapper_class,
                               class: class
  end

  def form_multiselect(form, field, options, opts \\ []) do
    class = opts[:class] || ""
    label = opts[:label] || humanize(field)
    selected = opts[:selected] || nil
    tooltip_text = opts[:tooltip_text] || "Add new item"
    error = error_message(form, field)

    multiselect_id = (Atom.to_string field) <> "__multiselect"
    button_id = (Atom.to_string field) <> "__add-button"

    wrapper_class = if error do "is-invalid" else "" end

    display_images = opts[:display_images] || false

    render "form_multiselect.html", form: form,
                                    field: field,
                                    label: label,
                                    error: error,
                                    selected: selected,
                                    options: options,
                                    tooltip_text: tooltip_text,
                                    wrapper_class: wrapper_class,
                                    class: class,
                                    display_images: display_images,
                                    button_id: button_id,
                                    multiselect_id: multiselect_id
  end

  def form_button(label, to, opts \\ []) do
    class = opts[:class] || ""
    render "form_button.html", label: label, to: to, class: class
  end

  def form_submit(form, label, opts \\ []) do
    class = opts[:class] || ""
    render "form_submit.html", form: form, label: label, class: class
  end

  def error_message(form, field) do
    import CoursePlannerWeb.ErrorHelpers, only: [translate_error: 1]
    case form.errors[field] do
      nil -> nil
      error -> translate_error(error)
    end
  end

  # card

  def card(title \\ nil, opts \\ [], [do: children]) do
    title_class =
      if opts[:highlighted_title] do
        "card__title--highlighted"
      else
        ""
      end

    render "card.html", title: title,
                        title_class: title_class,
                        children: children
  end

  def card_content(opts \\ [], [do: children]) do
    class =
      if opts[:vpadding] do
        "card__content--vpadding"
      else
        ""
      end

    render "card_content.html", children: children,
                                class: class
  end

  def card_actions([do: children]) do
    render "card_actions.html", children: children
  end

  # navbar

  def navbar(title, [do: children]) do
    default_uri = URI.parse("/images/logo.svg")
    logo = "PROGRAM_LOGO_URL" |> Settings.get_value(default_uri) |> URI.to_string()

    render "navbar.html", title: title, children: children,
                                        logo: logo
  end

  def navbar_separator do
    render "navbar_separator.html"
  end

  def navbar_item(label, conn, path) do
    classes = if path_match(conn, path) do
      "mdl-navigation__link mdl-navigation__link--current"
    else
      "mdl-navigation__link"
    end

    render "navbar_item.html", label: label, path: path, classes: classes
  end

  # show pages

  def user_list(users, opts \\ []) do
    empty_text = opts[:empty_text] || "There are no users here yet"
    render "user_list.html", users: users,
                             empty_text: empty_text
  end

  def user_bubble(user) do
    profile_picture = get_gravatar_url(user.email, 200)

    name = display_user_name(user)

    url = user_show_path(user)

    render "user_bubble.html", url: url,
                               profile_picture: profile_picture,
                               name: name
  end

  def class_list(classes, opts \\ []) do
    empty_text = opts[:empty_text] || "There are no classes here yet"
    show_attendances = opts[:show_attendances] || false

    render "class_list.html", classes: classes,
                              empty_text: empty_text,
                              show_attendances: show_attendances
  end

  def course_list(offered_courses, opts \\ []) do
    empty_text = opts[:empty_text] || "There are no courses here yet"

    render "course_list.html", offered_courses: offered_courses,
                               empty_text: empty_text
  end

  def task_list(tasks, opts \\ []) do
    empty_text = opts[:empty_text] || "There are no tasks here yet"

    render "task_list.html", tasks: tasks,
                             empty_text: empty_text
  end

  def holiday_list(holidays, opts \\ []) do
    empty_text = opts[:empty_text] || "There are no holidays here yet"

    render "holiday_list.html", holidays: holidays,
                                empty_text: empty_text
  end
end

defmodule CoursePlannerWeb.SharedView do
  @moduledoc false
  use CoursePlannerWeb, :view

  alias CoursePlanner.Settings

  # helpers

  def path_exact_match(conn, path) do
    conn.request_path == path
  end

  def path_match(conn, path) do
    String.starts_with? conn.request_path, path
  end

  def get_gravatar_url(email, size \\ 100) do
    hash =
      :md5
      |> :crypto.hash(email)
      |> Base.encode16()
      |> String.downcase()

    "https://www.gravatar.com/avatar/#{hash}?d=mm&s=#{size}"
  end

  def display_user_name(user) do
    [user.name, user.family_name, user.nickname && "(#{user.nickname})"]
    |> Enum.filter(fn v -> String.length(to_string v) > 0 end)
    |> Enum.join(" ")
  end

  # form components

  def form_text(form, field, opts \\ []) do
    class = opts[:class] || ""
    label = opts[:label] || humanize(field)
    required = opts[:required] || nil
    error = error_message(form, field)

    value = Map.get form.data, field
    wrapper_class =
      if error do
        "is-invalid"
      else
        if required && String.length to_string value > 0 do
          "form-init"
        else
          ""
        end
      end

    render "form_text.html", form: form,
                             field: field,
                             label: label,
                             error: error,
                             wrapper_class: wrapper_class,
                             class: class,
                             required: required
  end

  def form_textarea(form, field, opts \\ []) do
    class = opts[:class] || ""
    label = opts[:label] || humanize(field)
    required = opts[:required] || nil
    error = error_message(form, field)

    value = Map.get form.data, field
    wrapper_class =
      if error do
        "is-invalid"
      else
        if required && String.length to_string value > 0 do
          "form-init"
        else
          ""
        end
      end

    rows = opts[:rows] || 3

    render "form_textarea.html", form: form,
                                 field: field,
                                 label: label,
                                 error: error,
                                 wrapper_class: wrapper_class,
                                 class: class,
                                 required: required,
                                 rows: rows
  end

  def form_password(form, field, opts \\ []) do
    class = opts[:class] || ""
    label = opts[:label] || humanize(field)
    required = opts[:required] || nil
    error = error_message(form, field)

    wrapper_class =
      if error do
        "is-invalid"
      else
        if required do "form-init" else "" end
      end

    render "form_password.html", form: form,
                                 field: field,
                                 label: label,
                                 error: error,
                                 wrapper_class: wrapper_class,
                                 class: class,
                                 required: required
  end

  def form_date(form, field, opts \\ []) do
    default = opts[:value] || Ecto.Date.utc()
    class = opts[:class] || ""
    label = opts[:label] || humanize(field)
    error = error_message(form, field)

    wrapper_class = if error do "is-invalid" else "" end

    render "form_date.html", form: form,
                             field: field,
                             label: label,
                             error: error,
                             wrapper_class: wrapper_class,
                             class: class,
                             default: default
  end

  def form_time(form, field, opts \\ []) do
    default = opts[:value] || %{hour: 0, min: 0}
    class = opts[:class] || ""
    label = opts[:label] || humanize(field)
    error = error_message(form, field)

    wrapper_class = if error do "is-invalid" else "" end

    render "form_time.html", form: form,
                             field: field,
                             label: label,
                             error: error,
                             wrapper_class: wrapper_class,
                             class: class,
                             default: default
  end

  def form_datetime(form, field, opts \\ []) do
    default = Map.merge(Ecto.DateTime.utc(), opts[:value] || %{})
    class = opts[:class] || ""
    label = opts[:label] || humanize(field)
    error = error_message(form, field)

    wrapper_class = if error do "is-invalid" else "" end

    render "form_datetime.html", form: form,
                                 field: field,
                                 label: label,
                                 error: error,
                                 wrapper_class: wrapper_class,
                                 class: class,
                                 default: default
  end

  def form_select(form, field, options, opts \\ []) do
    class = opts[:class] || ""
    label = opts[:label] || humanize(field)
    selected = opts[:selected] || nil
    error = error_message(form, field)

    wrapper_class = if error do "is-invalid" else "" end

    render "form_select.html", form: form,
                               field: field,
                               label: label,
                               error: error,
                               selected: selected,
                               options: options,
                               wrapper_class: wrapper_class,
                               class: class
  end

  def form_multiselect(form, field, options, opts \\ []) do
    class = opts[:class] || ""
    label = opts[:label] || humanize(field)
    selected = opts[:selected] || nil
    tooltip_text = opts[:tooltip_text] || "Add new item"
    error = error_message(form, field)

    multiselect_id = (Atom.to_string field) <> "__multiselect"
    button_id = (Atom.to_string field) <> "__add-button"

    wrapper_class = if error do "is-invalid" else "" end

    display_images = opts[:display_images] || false

    render "form_multiselect.html", form: form,
                                    field: field,
                                    label: label,
                                    error: error,
                                    selected: selected,
                                    options: options,
                                    tooltip_text: tooltip_text,
                                    wrapper_class: wrapper_class,
                                    class: class,
                                    display_images: display_images,
                                    button_id: button_id,
                                    multiselect_id: multiselect_id
  end

  def form_button(label, to, opts \\ []) do
    class = opts[:class] || ""
    render "form_button.html", label: label, to: to, class: class
  end

  def form_submit(form, label, opts \\ []) do
    class = opts[:class] || ""
    render "form_submit.html", form: form, label: label, class: class
  end

  def error_message(form, field) do
    import CoursePlannerWeb.ErrorHelpers, only: [translate_error: 1]
    case form.errors[field] do
      nil -> nil
      error -> translate_error(error)
    end
  end

  # card

  def card(title \\ nil, opts \\ [], [do: children]) do
    title_class =
      if opts[:highlighted_title] do
        "card__title--highlighted"
      else
        ""
      end

    render "card.html", title: title,
                        title_class: title_class,
                        children: children
  end

  def card_content(opts \\ [], [do: children]) do
    class =
      if opts[:vpadding] do
        "card__content--vpadding"
      else
        ""
      end

    render "card_content.html", children: children,
                                class: class
  end

  def card_actions([do: children]) do
    render "card_actions.html", children: children
  end

  # navbar

  def navbar(title, [do: children]) do
    defaultURI = URI.parse("/images/logo.svg")
    logoURI = Settings.get_value("PROGRAM_LOGO_URL", defaultURI)
    logo = URI.to_string(logoURI)

    render "navbar.html", title: title, children: children,
                                        logo: logo
  end

  def navbar_separator do
    render "navbar_separator.html"
  end

  def navbar_item(label, conn, path) do
    classes = if path_match(conn, path) do
      "mdl-navigation__link mdl-navigation__link--current"
    else
      "mdl-navigation__link"
    end

    render "navbar_item.html", label: label, path: path, classes: classes
  end

  # show pages

  def user_list(users, opts \\ []) do
    empty_text = opts[:empty_text] || "There are no users here yet"
    render "user_list.html", users: users,
                             empty_text: empty_text
  end

  def user_bubble(user) do
    profile_picture = get_gravatar_url(user.email, 200)

    name = display_user_name(user)

    url = user_show_path(user)

    render "user_bubble.html", url: url,
                               profile_picture: profile_picture,
                               name: name
  end

  def class_list(classes, opts \\ []) do
    empty_text = opts[:empty_text] || "There are no classes here yet"
    show_attendances = opts[:show_attendances] || false

    render "class_list.html", classes: classes,
                              empty_text: empty_text,
                              show_attendances: show_attendances
  end

  def course_list(offered_courses, opts \\ []) do
    empty_text = opts[:empty_text] || "There are no courses here yet"

    render "course_list.html", offered_courses: offered_courses,
                               empty_text: empty_text
  end

  def task_list(tasks, opts \\ []) do
    empty_text = opts[:empty_text] || "There are no tasks here yet"

    render "task_list.html", tasks: tasks,
                             empty_text: empty_text
  end

  def holiday_list(holidays, opts \\ []) do
    empty_text = opts[:empty_text] || "There are no holidays here yet"

    render "holiday_list.html", holidays: holidays,
                                empty_text: empty_text
  end
end


Refactor for cleaner method calls

Refactor for cleaner method calls

defmodule EspiDni.ArticleViewCountNotifier do

  import Ecto.Query
  alias EspiDni.{
    Repo,
    ViewCount,
    Article,
    ArticleSlackMessenger
  }
  require Logger

  @minimum_increase 10
  @increase_threshold_percentage 25

  def notify_if_spike(article, team) do
    latest_counts = last_two_counts(article)

    if view_count_spike?(latest_counts, team) do
      ArticleSlackMessenger.send_view_spike_message(article, count_increase(latest_counts))
    end
  end

  defp last_two_counts(article) do
    Repo.all(
      from view_count in ViewCount,
      select: sum(view_count.count),
      where: view_count.article_id == ^article.id,
      where: view_count.inserted_at > ago(2, "hour"),
      group_by: fragment("round(extract('epoch' from inserted_at) / 1800)"),
      order_by: fragment("round(extract('epoch' from inserted_at) / 1800) desc"),
      limit: 2
    )
  end

  defp view_count_spike?([current_count, previous_count], team) do
    difference              = count_increase([current_count, previous_count])
    percentage_increase     = (difference / previous_count * 100)
    min_difference          = team.min_view_count_increase || @minimum_increase
    min_percentage_increase = team.view_count_threshold || @increase_threshold_percentage

    (difference >= min_difference) && (percentage_increase >= min_percentage_increase)
  end

  # return false if there's not a list with two entries
  defp view_count_spike?(_, _team), do: false

  defp count_increase([current_count, previous_count]) do
    current_count - previous_count
  end

end

defmodule EspiDni.ArticleViewCountNotifier do

  require Logger
  import Ecto.Query
  alias EspiDni.{
    Repo,
    ViewCount,
    Article,
    ArticleSlackMessenger
  }

  @minimum_increase 10
  @increase_threshold_percentage 25

  def notify_if_spike(article, team) do
    latest_counts = last_two_counts(article)

    if view_count_spike?(latest_counts, team) do
      ArticleSlackMessenger.send_view_spike_message(article, count_increase(latest_counts))
    end
  end

  defp last_two_counts(article) do
    Repo.all(
      from view_count in ViewCount,
      select: sum(view_count.count),
      where: view_count.article_id == ^article.id,
      where: view_count.inserted_at > ago(2, "hour"),
      group_by: fragment("round(extract('epoch' from inserted_at) / 1800)"),
      order_by: fragment("round(extract('epoch' from inserted_at) / 1800) desc"),
      limit: 2
    )
  end

  defp view_count_spike?([current_count, previous_count], team) do
    difference              = count_increase([current_count, previous_count])
    percentage_increase     = (difference / previous_count * 100)
    min_difference          = team.min_view_count_increase || @minimum_increase
    min_percentage_increase = team.view_count_threshold || @increase_threshold_percentage

    (difference >= min_difference) && (percentage_increase >= min_percentage_increase)
  end

  # return false if there's not a list with two entries
  defp view_count_spike?(_, _team), do: false

  defp count_increase([current_count, previous_count]) do
    current_count - previous_count
  end

end


fix typos on hello pages

fix typos on hello pages

defmodule OnePagesWeb.PageView do
  use OnePagesWeb, :view

  def home_intro do
    content_tag :p do
      [
        gettext("""
          InfinityOne combines the benefits of full-featured team chat with an
          extensible communications platform, built on a highly scalable, highly
          reliable platform.
          """),
        tag(:br),
        gettext("""
          Integrate InfinityOne with your phone system to instantly see who is
          on the phone, and call a user or any posted phone number with a simple click.
          """)
      ]
    end
  end
end

defmodule OnePagesWeb.PageView do
  use OnePagesWeb, :view

  def home_intro do
    content_tag :p do
      [
        gettext("""
          InfinityOne combines the benefits of full-featured team chat with an
          extensible communications platform, build on a highly scalable, highly
          reliable platform.
          """),
        tag(:br),
        gettext("""
          Integrate InfinityOne with your phone system to instantly see who is
          on the phone, call a user, or any posted phone number with a simple click.
          """)
      ]
    end
  end
end


Add binary counterparts of cmd and get_env

I was also considering adding binary support to put_env, but it would bloat
the code in my opinion. Don't know what's our general policy for using binary vs
charlist

# Since Elixir does not (yet) support local macros,
# we need to wrap the step that extracts git information
# in this module.
defmodule System.GitCompiler do
  @moduledoc false

  defmacro generate do
    quote do
      @doc """
      Returns a tuple { Elixir version, commit sha-1, build date }
      """
      def build_info do
        { System.version,
          unquote(get_head_sha),
          unquote(get_date) }
      end
    end
  end

  defp get_head_sha do
    normalize :os.cmd 'git rev-parse HEAD'
  end

  defp get_date do
    normalize :os.cmd 'TZ=GMT date'
  end

  defp normalize(string) do
    Regex.replace_all %r(\n), to_binary(string), ""
  end
end

defmodule System do
  @moduledoc """
  The System module provides access to some variables used or
  maintained by the VM and to functions that interact strongly
  with the VM or the host system.
  """

  require System.GitCompiler
  System.GitCompiler.generate

  @doc """
  Returns Elixir's version as binary.
  """
  def version, do: "0.9.0.dev"

  @doc """
  Returns the list of command-line arguments passed to the program.
  """
  def argv do
    Erlang.gen_server.call(:elixir_code_server, :argv)
  end

  @doc """
  Registers a function that will be invoked
  at the end of program execution. Useful for
  invoking a hook on scripted mode.

  The function must expect the exit status code
  as argument.
  """
  def at_exit(fun) when is_function(fun, 1) do
    server_call { :at_exit, fun }
  end

  @doc """
  Executes `command` in a command shell of the target OS, captures the standard
  output of the command and returns this result as a string.

  `command` can be an atom, a charlist or a binary.
  """
  def cmd(command) when is_binary(command) do
      cmd binary_to_list(command)
  end

  def cmd(command), do: :os.cmd command

  @doc """
  Returns a list of all environment variables. Each environment variable is
  given as a single string on the format "VarName=Value", where VarName is the
  name of the variable and Value its value.
  """
  def get_env, do: :os.getenv

  @doc """
  Returns the Value of the environment variable `varname`, or nil if the
  environment variable is undefined.
  """
  def get_env(varname) when is_binary(varname) do
    get_env binary_to_list(varname)
  end

  def get_env(varname) do
    case :os.getenv(varname) do
    match: false
      nil
    match: other
      other
    end
  end

  @doc """
  Returns the process identifier of the current Erlang emulator in the format
  most commonly used by the operating system environment.

  See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
  """
  def get_pid, do: :os.getpid

  @doc """
  Sets a new `value` for the environment variable `varname`. Both arguments are
  expected to be of type charlist.
  """
  def put_env(varname, value), do: :os.putenv varname, value

  @doc """
  Returns the current working directory as a binary.
  """
  def pwd, do: :filename.absname("")

  @doc """
  Get the stacktrace.
  """
  def stacktrace do
    filter_stacktrace Erlang.erlang.get_stacktrace
  end

  ## Helpers

  # Filter stacktrace by removing internal BOOTSTRAP calls.
  defp filter_stacktrace([{ Elixir.Builtin, :raise, _, _ }|t]), do: filter_stacktrace(t)
  defp filter_stacktrace([{ _mod, :BOOTSTRAP, _, _ }|t]), do: filter_stacktrace(t)
  defp filter_stacktrace([{ _mod, :BOOTSTRAP, _ }|t]), do: filter_stacktrace(t)
  defp filter_stacktrace([h|t]), do: [h|filter_stacktrace(t)]
  defp filter_stacktrace([]), do: []

  defp server_call(args) do
    Erlang.gen_server.call(:elixir_code_server, args)
  end
end

# Since Elixir does not (yet) support local macros,
# we need to wrap the step that extracts git information
# in this module.
defmodule System.GitCompiler do
  @moduledoc false

  defmacro generate do
    quote do
      @doc """
      Returns a tuple { Elixir version, commit sha-1, build date }
      """
      def build_info do
        { System.version,
          unquote(get_head_sha),
          unquote(get_date) }
      end
    end
  end

  defp get_head_sha do
    normalize :os.cmd 'git rev-parse HEAD'
  end

  defp get_date do
    normalize :os.cmd 'TZ=GMT date'
  end

  defp normalize(string) do
    Regex.replace_all %r(\n), to_binary(string), ""
  end
end

defmodule System do
  @moduledoc """
  The System module provides access to some variables used or
  maintained by the VM and to functions that interact strongly
  with the VM or the host system.
  """

  require System.GitCompiler
  System.GitCompiler.generate

  @doc """
  Returns Elixir's version as binary.
  """
  def version, do: "0.9.0.dev"

  @doc """
  Returns the list of command-line arguments passed to the program.
  """
  def argv do
    Erlang.gen_server.call(:elixir_code_server, :argv)
  end

  @doc """
  Registers a function that will be invoked
  at the end of program execution. Useful for
  invoking a hook on scripted mode.

  The function must expect the exit status code
  as argument.
  """
  def at_exit(fun) when is_function(fun, 1) do
    server_call { :at_exit, fun }
  end

  @doc """
  Executes `command` in a command shell of the target OS, captures the standard
  output of the command and returns this result as a string.

  `command` can be an atom or a charlist
  """
  def cmd(command), do: :os.cmd command

  @doc """
  Returns a list of all environment variables. Each environment variable is
  given as a single string on the format "VarName=Value", where VarName is the
  name of the variable and Value its value.
  """
  def get_env, do: :os.getenv

  @doc """
  Returns the Value of the environment variable `varname`, or nil if the
  environment variable is undefined.
  """
  def get_env(varname) do
    case :os.getenv(varname) do
    match: false
      nil
    match: other
      other
    end
  end

  @doc """
  Returns the process identifier of the current Erlang emulator in the format
  most commonly used by the operating system environment.

  See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
  """
  def get_pid, do: :os.getpid

  @doc """
  Sets a new `value` for the environment variable `varname`. Both arguments are
  expected to be of type charlist.
  """
  def put_env(varname, value), do: :os.putenv varname, value

  @doc """
  Returns the current working directory as a binary.
  """
  def pwd, do: :filename.absname("")

  @doc """
  Get the stacktrace.
  """
  def stacktrace do
    filter_stacktrace Erlang.erlang.get_stacktrace
  end

  ## Helpers

  # Filter stacktrace by removing internal BOOTSTRAP calls.
  defp filter_stacktrace([{ Elixir.Builtin, :raise, _, _ }|t]), do: filter_stacktrace(t)
  defp filter_stacktrace([{ _mod, :BOOTSTRAP, _, _ }|t]), do: filter_stacktrace(t)
  defp filter_stacktrace([{ _mod, :BOOTSTRAP, _ }|t]), do: filter_stacktrace(t)
  defp filter_stacktrace([h|t]), do: [h|filter_stacktrace(t)]
  defp filter_stacktrace([]), do: []

  defp server_call(args) do
    Erlang.gen_server.call(:elixir_code_server, args)
  end
end


Fix style errors

Fix style errors


Removed dummy data generator function

Removed dummy data generator function

defmodule GraphQL.Schema.Root do
  alias GraphQL.Schema
  alias GraphQL.Type.{ObjectType, List, ID}
  alias GraphQL.Schema.Content

  defmodule Query do
    def type do
      %ObjectType{
        name: "Root",
        description: "All the queries available to the client",
        fields: %{
          content: %{
            type: %List{ofType: Content},
            args: %{
              id: %{type: %ID{}},
            },
            description: "A piece of uploaded content",
            resolve: &Content.find/3
          }
        }
      }
    end
  end

  def schema do
    %Schema{
      query: Query
    }
  end
end

defmodule GraphQL.Schema.Root do
  alias GraphQL.Schema
  alias GraphQL.Type.{ObjectType, List, ID}
  alias GraphQL.Schema.Content

  defmodule Query do
    def type do
      %ObjectType{
        name: "Root",
        description: "All the queries available to the client",
        fields: %{
          content: %{
            type: %List{ofType: Content},
            args: %{
              id: %{type: %ID{}},
            },
            description: "A piece of uploaded content",
            resolve: &Content.find/3
          }
        }
      }
    end
  end

  def schema do
    %Schema{
      query: Query
    }
  end

  def make_content(id) do
    %{
      id: "#{id}",
      description: "Hello",
      type: "image",
      data: "some/image/url"
    }
  end
end


Adding some comments to the code as well as expanding and aligning some existing comments

defmodule Pragmatic.Windows do

  @doc """
  Take a long Windows path or a path with spaces in it and return the 8.3 version of that name

  The function will throw a runtime error if you attempt to invoke it on a path that doesn't exist. E. g.

  iex> Pragmatic.Windows.get_short_name("c:/nosuchpath")
  ** (RuntimeError) The specified directory "c:/nosuchpath" does not exist

  A runtime error will also be thrown if you attempt to run this function on a non-Windows OS.

  Otherwise, you want to pass a directory using forward slashes as the path separator (yes, this does work on Windows)

  iex> Pragmatic.Windows.get_short_name("c:/program files")
  "c:/PROGRA~1"

  iex> Pragmatic.Windows.get_short_name("c:/users")
  "c:/Users"

  """
  @spec get_short_name(Path.t)::Path.t
  def get_short_name(path) when is_binary(path) do
    if !({:win32, _} = :os.type), do: raise "This function can only be run on Windows"
    if !(File.exists?(path)), do: raise "The specified directory \"#{path}\" does not exist"
    # If the path contains spaces it needs to be surrouneded with quotes. Otherwise, do _not_ surround it with quotes.
    quoted_path = if path_contains_spaces?(path), do: "\"#{path}\"", else: path

    # NB for some reason I could not get System.cmd to work with this particular shell command.  If you can
    # get it working, please share your fix with me.
    # 2>nul suppresses a bogus error message.
    :os.cmd('cmd /c FOR %d IN (#{quoted_path}) DO %~sd 2>nul')
    |> to_string
    |> strip_newlines_from_string
    |> strip_all_chars_up_to(">")
    |> String.strip
    |> Path.split
    |> Path.join
  end

  @spec path_contains_spaces?(Path.t)::boolean
  defp path_contains_spaces?(path) when is_binary(path) do
    # NB: Currently there's no way I can figure out to tell if the path has a "\" in it (since "\" is an escape character
    # so you must pass the path as /dir/dir/dir (yes this does work on Windows).
    spaces_in_middle = ~r/\S+\s+\S+/
    initial_spaces = ~r/\s+.*/
    trailing_spaces = ~r/.*\s+/
    (String.match?(path,spaces_in_middle) or String.match?(path, initial_spaces) or String.match?(path,trailing_spaces))
  end

  @spec strip_newlines_from_string(String.t)::String.t
  defp strip_newlines_from_string(s) when is_binary(s) do
    windows_newline = "\r\n"
    if String.contains?(s,windows_newline), do: String.replace(s, windows_newline, ""), else: s
  end

  @spec get_position_of_char(String.t, String.t, Integer)::Integer
  defp get_position_of_char(s, char, cnt) do
    {g, tail} = String.next_grapheme(s)
    if g === char, do: cnt, else: get_position_of_char(tail, char, cnt+1)
  end

  @spec strip_all_chars_up_to(String.t, String.t)::String.t
  defp strip_all_chars_up_to(s, char) do
    if String.contains?(s,char) do
      index_of_char = get_position_of_char(s, char, 0)
      {_,short_name} = String.split_at(s,index_of_char+1) #Add one since this is 0 based.
      short_name
    end
  end
end

defmodule Pragmatic.Windows do

  @doc """
  Take a long Windows path or a path with spaces in it and return the 8.3 version of that name

  The function will throw a runtime error if you attempt to invoke it on a path that doesn't exist. E. g.

  iex> Pragmatic.Windows.get_short_name("c:/nosuchpath")
  ** (RuntimeError) The specified directory "c:/nosuchpath" does not exist

  A runtime error will also be thrown if you attempt to run this function on a non-Windows OS.

  Otherwise, you want to pass a directory using forward slashes as the path separator (yes, this does work on Windows)

  iex> Pragmatic.Windows.get_short_name("c:/program files")
  "c:/PROGRA~1"

  iex> Pragmatic.Windows.get_short_name("c:/users")
  "c:/Users"

  """
  @spec get_short_name(Path.t)::Path.t
  def get_short_name(path) when is_binary(path) do
    if !({:win32, _} = :os.type), do: raise "This function can only be run on Windows"
    if !(File.exists?(path)), do: raise "The specified directory \"#{path}\" does not exist"
    quoted_path = if path_contains_spaces?(path), do: "\"#{path}\"", else: path

    #NB for some reason I could not get System.cmd to work with this particular shell command.  If you can
    #get it working, please share your fix with me.
    :os.cmd('cmd /c FOR %d IN (#{quoted_path}) DO %~sd 2>nul')
    |> to_string
    |> strip_newlines_from_string
    |> strip_all_chars_up_to(">")
    |> String.strip
    |> Path.split
    |> Path.join
  end

  @spec path_contains_spaces?(Path.t)::boolean
  defp path_contains_spaces?(path) when is_binary(path) do
    #NB: Currently there's no way I can figure out to tell if the path has a "\" in it so you must pass the path
    #as /dir/dir/dir (yes this does work on Windows).
    spaces_in_middle = ~r/\S+\s+\S+/
    initial_spaces = ~r/\s+.*/
    trailing_spaces = ~r/.*\s+/
    (String.match?(path,spaces_in_middle) or String.match?(path, initial_spaces) or String.match?(path,trailing_spaces))
  end

  @spec strip_newlines_from_string(String.t)::String.t
  defp strip_newlines_from_string(s) when is_binary(s) do
    windows_newline = "\r\n"
    if String.contains?(s,windows_newline), do: String.replace(s, windows_newline, ""), else: s
  end

  @spec get_position_of_char(String.t, String.t, Integer)::Integer
  defp get_position_of_char(s, char, cnt) do
    {g, tail} = String.next_grapheme(s)
    if g === char, do: cnt, else: get_position_of_char(tail, char, cnt+1)
  end

  @spec strip_all_chars_up_to(String.t, String.t)::String.t
  defp strip_all_chars_up_to(s, char) do
    if String.contains?(s,char) do
      index_of_char = get_position_of_char(s, char, 0)
      {_,short_name} = String.split_at(s,index_of_char+1) #Add one since this is 0 based.
      short_name
    end
  end
end


Remove referance to accessing tuples.

Remove referance to accessing tuples.

import Kernel, except: [access: 2]

defprotocol Access do
  @moduledoc """
  The Access protocol is the underlying protocol invoked
  when the brackets syntax is used. For instance, `foo[bar]`
  is translated to `access foo, bar` which, by default,
  invokes `Access.access` protocol.

  This protocol is limited and is implemented only for the
  following built-in types: keywords, records, atoms and
  functions.
  """

  @only [List, Function, Record, Atom]

  @doc """
  Receives the element being accessed and the access item.
  """
  def access(element, qualifier)
end

defimpl Access, for: List do
  @doc """
  Access the given key in a keywords list.

  ## Examples

      keywords = [a: 1, b: 2]
      keywords[:a] #=> 1

  """

  def access(list, atom) when is_atom(atom) do
    atom_access(list, atom)
  end

  defp atom_access([{k, _}|_], key) when key < k, do: nil
  defp atom_access([{k, _}|d], key) when key > k, do: atom_access(d, key)
  defp atom_access([{_k, value}|_], _key),        do: value
  defp atom_access([], _),                        do: nil
end

defimpl Access, for: Atom do
  @doc """
  The access protocol can only be accessed by atoms
  at compilation time. If we reach this, we should raise
  an exception.
  """
  def access(atom, _) do
    raise "The access protocol can only be invoked for atoms at " <>
      "compilation time, tried to invoke it for #{inspect atom}"
  end
end

defimpl Access, for: Function do
  @doc """
  The Access protocol for functions simply invokes
  the function passing the item as argument. This
  is useful because it allows a function to be
  passed as argument in places a dict would also fit.
  """
  def access(function, item) do
    function.(item)
  end
end

import Kernel, except: [access: 2]

defprotocol Access do
  @moduledoc """
  The Access protocol is the underlying protocol invoked
  when the brackets syntax is used. For instance, `foo[bar]`
  is translated to `access foo, bar` which, by default,
  invokes `Access.access` protocol.

  This protocol is limited and is implemented only for the
  following built-in types: keywords, tuples, atoms and
  functions.
  """

  @only [List, Function, Record, Atom]

  @doc """
  Receives the element being accessed and the access item.
  """
  def access(element, qualifier)
end

defimpl Access, for: List do
  @doc """
  Access the given key in a keywords list.

  ## Examples

      keywords = [a: 1, b: 2]
      keywords[:a] #=> 1

  """

  def access(list, atom) when is_atom(atom) do
    atom_access(list, atom)
  end

  defp atom_access([{k, _}|_], key) when key < k, do: nil
  defp atom_access([{k, _}|d], key) when key > k, do: atom_access(d, key)
  defp atom_access([{_k, value}|_], _key),        do: value
  defp atom_access([], _),                        do: nil
end

defimpl Access, for: Atom do
  @doc """
  The access protocol can only be accessed by atoms
  at compilation time. If we reach this, we should raise
  an exception.
  """
  def access(atom, _) do
    raise "The access protocol can only be invoked for atoms at " <>
      "compilation time, tried to invoke it for #{inspect atom}"
  end
end

defimpl Access, for: Function do
  @doc """
  The Access protocol for functions simply invokes
  the function passing the item as argument. This
  is useful because it allows a function to be
  passed as argument in places a dict would also fit.
  """
  def access(function, item) do
    function.(item)
  end
end


Clarify app env usage (#11627)

Clarify app env usage (#11627)

defmodule Config do
  @moduledoc ~S"""
  A simple keyword-based configuration API.

  ## Example

  This module is most commonly used to define application configuration,
  typically in `config/config.exs`:

      import Config

      config :some_app,
        key1: "value1",
        key2: "value2"

      import_config "#{config_env()}.exs"

  `import Config` will import the functions `config/2`, `config/3`
  `config_env/0`, `config_target/0`, and `import_config/1`
  to help you manage your configuration.

  `config/2` and `config/3` are used to define key-value configuration
  for a given application. Once Mix starts, it will automatically
  evaluate the configuration file and persist the configuration above
  into `:some_app`'s application environment, which can be accessed in
  as follows:

      "value1" = Application.fetch_env!(:some_app, :key1)

  Finally, the line `import_config "#{config_env()}.exs"` will import
  other config files based on the current configuration environment,
  such as `config/dev.exs` and `config/test.exs`.

  `Config` also provides a low-level API for evaluating and reading
  configuration, under the `Config.Reader` module.

  > **Important:** if you are writing a library to be used by other developers,
  > it is generally recommended to avoid the application environment, as the
  > application environment is effectively a global storage. Also note that
  > the `config/config.exs` of a library is not evaluated when the library is
  > used as a dependency, as configuration is always meant to configure the
  > current project. For more information, read our [library guidelines](library-guidelines.md).

  ## Migrating from `use Mix.Config`

  The `Config` module in Elixir was introduced in v1.9 as a replacement to
  `Mix.Config`, which was specific to Mix and has been deprecated.

  You can leverage `Config` instead of `Mix.Config` in three steps. The first
  step is to replace `use Mix.Config` at the top of your config files by
  `import Config`.

  The second is to make sure your `import_config/1` calls do not have a
  wildcard character. If so, you need to perform the wildcard lookup
  manually. For example, if you did:

      import_config "../apps/*/config/config.exs"

  It has to be replaced by:

      for config <- "../apps/*/config/config.exs" |> Path.expand(__DIR__) |> Path.wildcard() do
        import_config config
      end

  The last step is to replace all `Mix.env()` calls in the config files with `config_env()`.

  Keep in mind you must also avoid using `Mix.env()` inside your project files.
  To check the environment at _runtime_, you may add a configuration key:

      # config.exs
      ...
      config :my_app, env: config_env()

  Then, in other scripts and modules, you may get the environment with
  `Application.fetch_env!/2`:

      # router.exs
      ...
      if Application.fetch_env!(:my_app, :env) == :prod do
        ...
      end

  The only files where you may acccess functions from the `Mix` module are
  the `mix.exs` file and inside custom Mix tasks, which always within the
  `Mix.Tasks` namespace.

  ## config/runtime.exs

  For runtime configuration, you can use the `config/runtime.exs` file.
  It is executed right before applications start in both Mix and releases
  (assembled with `mix release`).
  """

  @opts_key {__MODULE__, :opts}
  @config_key {__MODULE__, :config}
  @imports_key {__MODULE__, :imports}

  defp get_opts!(), do: Process.get(@opts_key) || raise_improper_use!()
  defp put_opts(value), do: Process.put(@opts_key, value)
  defp delete_opts(), do: Process.delete(@opts_key)

  defp get_config!(), do: Process.get(@config_key) || raise_improper_use!()
  defp put_config(value), do: Process.put(@config_key, value)
  defp delete_config(), do: Process.delete(@config_key)

  defp get_imports!(), do: Process.get(@imports_key) || raise_improper_use!()
  defp put_imports(value), do: Process.put(@imports_key, value)
  defp delete_imports(), do: Process.delete(@imports_key)

  defp raise_improper_use!() do
    raise "could not set configuration via Config. " <>
            "This usually means you are trying to execute a configuration file " <>
            "directly, instead of reading it with Config.Reader"
  end

  @doc """
  Configures the given `root_key`.

  Keyword lists are always deep-merged.

  ## Examples

  The given `opts` are merged into the existing configuration
  for the given `root_key`. Conflicting keys are overridden by the
  ones specified in `opts`, unless they are keywords, which are
  deep merged recursively. For example, the application configuration
  below

      config :logger,
        level: :warn,
        backends: [:console]

      config :logger,
        level: :info,
        truncate: 1024

  will have a final configuration for `:logger` of:

      [level: :info, backends: [:console], truncate: 1024]

  """
  @doc since: "1.9.0"
  def config(root_key, opts) when is_atom(root_key) and is_list(opts) do
    unless Keyword.keyword?(opts) do
      raise ArgumentError, "config/2 expected a keyword list, got: #{inspect(opts)}"
    end

    get_config!()
    |> __merge__([{root_key, opts}])
    |> put_config()
  end

  @doc """
  Configures the given `key` for the given `root_key`.

  Keyword lists are always deep merged.

  ## Examples

  The given `opts` are merged into the existing values for `key`
  in the given `root_key`. Conflicting keys are overridden by the
  ones specified in `opts`, unless they are keywords, which are
  deep merged recursively. For example, the application configuration
  below

      config :ecto, Repo,
        log_level: :warn,
        adapter: Ecto.Adapters.Postgres,
        metadata: [read_only: true]

      config :ecto, Repo,
        log_level: :info,
        pool_size: 10,
        metadata: [replica: true]

  will have a final value of the configuration for the `Repo`
  key in the `:ecto` application of:

      Application.get_env(:ecto, Repo)
      #=> [
      #=>   log_level: :info,
      #=>   pool_size: 10,
      #=>   adapter: Ecto.Adapters.Postgres,
      #=>   metadata: [read_only: true, replica: true]
      #=> ]

  """
  @doc since: "1.9.0"
  def config(root_key, key, opts) when is_atom(root_key) and is_atom(key) do
    get_config!()
    |> __merge__([{root_key, [{key, opts}]}])
    |> put_config()
  end

  @doc """
  Returns the environment this configuration file is executed on.

  In Mix projects this function returns the environment this configuration
  file is executed on. In releases, the environment when `mix release` ran.

  This is most often used to execute conditional code:

      if config_env() == :prod do
        config :my_app, :debug, false
      end

  """
  @doc since: "1.11.0"
  defmacro config_env() do
    quote do
      Config.__env__!()
    end
  end

  @doc false
  @spec __env__!() :: atom()
  def __env__!() do
    elem(get_opts!(), 0) || raise "no :env key was given to this configuration file"
  end

  @doc """
  Returns the target this configuration file is executed on.

  This is most often used to execute conditional code:

      if config_target() == :host do
        config :my_app, :debug, false
      end

  """
  @doc since: "1.11.0"
  defmacro config_target() do
    quote do
      Config.__target__!()
    end
  end

  @doc false
  @spec __target__!() :: atom()
  def __target__!() do
    elem(get_opts!(), 1) || raise "no :target key was given to this configuration file"
  end

  @doc ~S"""
  Imports configuration from the given file.

  In case the file doesn't exist, an error is raised.

  If file is a relative, it will be expanded relatively to the
  directory the current configuration file is in.

  ## Examples

  This is often used to emulate configuration across environments:

      import_config "#{config_env()}.exs"

  Note, however, some configuration files, such as `config/runtime.exs`
  does not support imports, as they are meant to be copied across
  systems.
  """
  @doc since: "1.9.0"
  defmacro import_config(file) do
    quote do
      Config.__import__!(Path.expand(unquote(file), __DIR__))
      :ok
    end
  end

  @doc false
  @spec __import__!(Path.t()) :: {term, Code.binding()}
  def __import__!(file) when is_binary(file) do
    import_config!(file, File.read!(file), true)
  end

  @doc false
  @spec __eval__!(Path.t(), binary(), keyword) :: {keyword, [Path.t()] | :disabled}
  def __eval__!(file, content, opts \\ []) when is_binary(file) and is_list(opts) do
    env = Keyword.get(opts, :env)
    target = Keyword.get(opts, :target)
    imports = Keyword.get(opts, :imports, [])

    previous_opts = put_opts({env, target})
    previous_config = put_config([])
    previous_imports = put_imports(imports)

    try do
      {eval_config, _} = import_config!(file, content, false)

      case get_config!() do
        [] when is_list(eval_config) ->
          {validate!(eval_config, file), get_imports!()}

        pdict_config ->
          {pdict_config, get_imports!()}
      end
    after
      if previous_opts, do: put_opts(previous_opts), else: delete_opts()
      if previous_config, do: put_config(previous_config), else: delete_config()
      if previous_imports, do: put_imports(previous_imports), else: delete_imports()
    end
  end

  defp import_config!(file, contents, raise_when_disabled?) do
    current_imports = get_imports!()

    cond do
      current_imports == :disabled ->
        if raise_when_disabled? do
          raise "import_config/1 is not enabled for this configuration file. " <>
                  "Some configuration files do not allow importing other files " <>
                  "as they are often copied to external systems"
        end

      file in current_imports ->
        raise ArgumentError,
              "attempting to load configuration #{Path.relative_to_cwd(file)} recursively"

      true ->
        put_imports([file | current_imports])
        :ok
    end

    # TODO: Emit a warning if Mix.env() is found in said files in Elixir v1.15.
    # Note this won't be a deprecation warning as it will always be emitted.
    Code.eval_string(contents, [], file: file)
  end

  @doc false
  def __merge__(config1, config2) when is_list(config1) and is_list(config2) do
    Keyword.merge(config1, config2, fn _, app1, app2 ->
      Keyword.merge(app1, app2, &deep_merge/3)
    end)
  end

  defp deep_merge(_key, value1, value2) do
    if Keyword.keyword?(value1) and Keyword.keyword?(value2) do
      Keyword.merge(value1, value2, &deep_merge/3)
    else
      value2
    end
  end

  defp validate!(config, file) do
    Enum.all?(config, fn
      {app, value} when is_atom(app) ->
        if Keyword.keyword?(value) do
          true
        else
          raise ArgumentError,
                "expected config for app #{inspect(app)} in #{Path.relative_to_cwd(file)} " <>
                  "to return keyword list, got: #{inspect(value)}"
        end

      _ ->
        false
    end)

    config
  end
end

defmodule Config do
  @moduledoc ~S"""
  A simple keyword-based configuration API.

  ## Example

  This module is most commonly used to define application configuration,
  typically in `config/config.exs`:

      import Config

      config :some_app,
        key1: "value1",
        key2: "value2"

      import_config "#{config_env()}.exs"

  `import Config` will import the functions `config/2`, `config/3`
  `config_env/0`, `config_target/0`, and `import_config/1`
  to help you manage your configuration.

  `config/2` and `config/3` are used to define key-value configuration
  for a given application. Once Mix starts, it will automatically
  evaluate the configuration file and persist the configuration above
  into `:some_app`'s application environment, which can be accessed in
  as follows:

      "value1" = Application.fetch_env!(:some_app, :key1)

  Finally, the line `import_config "#{config_env()}.exs"` will import
  other config files based on the current configuration environment,
  such as `config/dev.exs` and `config/test.exs`.

  `Config` also provides a low-level API for evaluating and reading
  configuration, under the `Config.Reader` module.

  > **Important:** if you are writing a library to be used by other developers,
  > it is generally recommended to avoid the application environment, as the
  > application environment is effectively a global storage. Also note that
  > the `config/config.exs` of a library is not evaluated when the library is
  > used as a dependency, as configuration is always meant to configure the
  > current project. For more information, read our [library guidelines](library-guidelines.md).

  ## Migrating from `use Mix.Config`

  The `Config` module in Elixir was introduced in v1.9 as a replacement to
  `Mix.Config`, which was specific to Mix and has been deprecated.

  You can leverage `Config` instead of `Mix.Config` in three steps. The first
  step is to replace `use Mix.Config` at the top of your config files by
  `import Config`.

  The second is to make sure your `import_config/1` calls do not have a
  wildcard character. If so, you need to perform the wildcard lookup
  manually. For example, if you did:

      import_config "../apps/*/config/config.exs"

  It has to be replaced by:

      for config <- "../apps/*/config/config.exs" |> Path.expand(__DIR__) |> Path.wildcard() do
        import_config config
      end

  The last step is to replace all `Mix.env()` calls by `config_env()`.

  ## config/runtime.exs

  For runtime configuration, you can use the `config/runtime.exs` file.
  It is executed right before applications start in both Mix and releases
  (assembled with `mix release`).
  """

  @opts_key {__MODULE__, :opts}
  @config_key {__MODULE__, :config}
  @imports_key {__MODULE__, :imports}

  defp get_opts!(), do: Process.get(@opts_key) || raise_improper_use!()
  defp put_opts(value), do: Process.put(@opts_key, value)
  defp delete_opts(), do: Process.delete(@opts_key)

  defp get_config!(), do: Process.get(@config_key) || raise_improper_use!()
  defp put_config(value), do: Process.put(@config_key, value)
  defp delete_config(), do: Process.delete(@config_key)

  defp get_imports!(), do: Process.get(@imports_key) || raise_improper_use!()
  defp put_imports(value), do: Process.put(@imports_key, value)
  defp delete_imports(), do: Process.delete(@imports_key)

  defp raise_improper_use!() do
    raise "could not set configuration via Config. " <>
            "This usually means you are trying to execute a configuration file " <>
            "directly, instead of reading it with Config.Reader"
  end

  @doc """
  Configures the given `root_key`.

  Keyword lists are always deep-merged.

  ## Examples

  The given `opts` are merged into the existing configuration
  for the given `root_key`. Conflicting keys are overridden by the
  ones specified in `opts`, unless they are keywords, which are
  deep merged recursively. For example, the application configuration
  below

      config :logger,
        level: :warn,
        backends: [:console]

      config :logger,
        level: :info,
        truncate: 1024

  will have a final configuration for `:logger` of:

      [level: :info, backends: [:console], truncate: 1024]

  """
  @doc since: "1.9.0"
  def config(root_key, opts) when is_atom(root_key) and is_list(opts) do
    unless Keyword.keyword?(opts) do
      raise ArgumentError, "config/2 expected a keyword list, got: #{inspect(opts)}"
    end

    get_config!()
    |> __merge__([{root_key, opts}])
    |> put_config()
  end

  @doc """
  Configures the given `key` for the given `root_key`.

  Keyword lists are always deep merged.

  ## Examples

  The given `opts` are merged into the existing values for `key`
  in the given `root_key`. Conflicting keys are overridden by the
  ones specified in `opts`, unless they are keywords, which are
  deep merged recursively. For example, the application configuration
  below

      config :ecto, Repo,
        log_level: :warn,
        adapter: Ecto.Adapters.Postgres,
        metadata: [read_only: true]

      config :ecto, Repo,
        log_level: :info,
        pool_size: 10,
        metadata: [replica: true]

  will have a final value of the configuration for the `Repo`
  key in the `:ecto` application of:

      Application.get_env(:ecto, Repo)
      #=> [
      #=>   log_level: :info,
      #=>   pool_size: 10,
      #=>   adapter: Ecto.Adapters.Postgres,
      #=>   metadata: [read_only: true, replica: true]
      #=> ]

  """
  @doc since: "1.9.0"
  def config(root_key, key, opts) when is_atom(root_key) and is_atom(key) do
    get_config!()
    |> __merge__([{root_key, [{key, opts}]}])
    |> put_config()
  end

  @doc """
  Returns the environment this configuration file is executed on.

  In Mix projects this function returns the environment this configuration
  file is executed on. In releases, the environment when `mix release` ran.

  This is most often used to execute conditional code:

      if config_env() == :prod do
        config :my_app, :debug, false
      end

  """
  @doc since: "1.11.0"
  defmacro config_env() do
    quote do
      Config.__env__!()
    end
  end

  @doc false
  @spec __env__!() :: atom()
  def __env__!() do
    elem(get_opts!(), 0) || raise "no :env key was given to this configuration file"
  end

  @doc """
  Returns the target this configuration file is executed on.

  This is most often used to execute conditional code:

      if config_target() == :host do
        config :my_app, :debug, false
      end

  """
  @doc since: "1.11.0"
  defmacro config_target() do
    quote do
      Config.__target__!()
    end
  end

  @doc false
  @spec __target__!() :: atom()
  def __target__!() do
    elem(get_opts!(), 1) || raise "no :target key was given to this configuration file"
  end

  @doc ~S"""
  Imports configuration from the given file.

  In case the file doesn't exist, an error is raised.

  If file is a relative, it will be expanded relatively to the
  directory the current configuration file is in.

  ## Examples

  This is often used to emulate configuration across environments:

      import_config "#{config_env()}.exs"

  Note, however, some configuration files, such as `config/runtime.exs`
  does not support imports, as they are meant to be copied across
  systems.
  """
  @doc since: "1.9.0"
  defmacro import_config(file) do
    quote do
      Config.__import__!(Path.expand(unquote(file), __DIR__))
      :ok
    end
  end

  @doc false
  @spec __import__!(Path.t()) :: {term, Code.binding()}
  def __import__!(file) when is_binary(file) do
    import_config!(file, File.read!(file), true)
  end

  @doc false
  @spec __eval__!(Path.t(), binary(), keyword) :: {keyword, [Path.t()] | :disabled}
  def __eval__!(file, content, opts \\ []) when is_binary(file) and is_list(opts) do
    env = Keyword.get(opts, :env)
    target = Keyword.get(opts, :target)
    imports = Keyword.get(opts, :imports, [])

    previous_opts = put_opts({env, target})
    previous_config = put_config([])
    previous_imports = put_imports(imports)

    try do
      {eval_config, _} = import_config!(file, content, false)

      case get_config!() do
        [] when is_list(eval_config) ->
          {validate!(eval_config, file), get_imports!()}

        pdict_config ->
          {pdict_config, get_imports!()}
      end
    after
      if previous_opts, do: put_opts(previous_opts), else: delete_opts()
      if previous_config, do: put_config(previous_config), else: delete_config()
      if previous_imports, do: put_imports(previous_imports), else: delete_imports()
    end
  end

  defp import_config!(file, contents, raise_when_disabled?) do
    current_imports = get_imports!()

    cond do
      current_imports == :disabled ->
        if raise_when_disabled? do
          raise "import_config/1 is not enabled for this configuration file. " <>
                  "Some configuration files do not allow importing other files " <>
                  "as they are often copied to external systems"
        end

      file in current_imports ->
        raise ArgumentError,
              "attempting to load configuration #{Path.relative_to_cwd(file)} recursively"

      true ->
        put_imports([file | current_imports])
        :ok
    end

    # TODO: Emit a warning if Mix.env() is found in said files in Elixir v1.15.
    # Note this won't be a deprecation warning as it will always be emitted.
    Code.eval_string(contents, [], file: file)
  end

  @doc false
  def __merge__(config1, config2) when is_list(config1) and is_list(config2) do
    Keyword.merge(config1, config2, fn _, app1, app2 ->
      Keyword.merge(app1, app2, &deep_merge/3)
    end)
  end

  defp deep_merge(_key, value1, value2) do
    if Keyword.keyword?(value1) and Keyword.keyword?(value2) do
      Keyword.merge(value1, value2, &deep_merge/3)
    else
      value2
    end
  end

  defp validate!(config, file) do
    Enum.all?(config, fn
      {app, value} when is_atom(app) ->
        if Keyword.keyword?(value) do
          true
        else
          raise ArgumentError,
                "expected config for app #{inspect(app)} in #{Path.relative_to_cwd(file)} " <>
                  "to return keyword list, got: #{inspect(value)}"
        end

      _ ->
        false
    end)

    config
  end
end