function (add_formatter name type)
  if (type STREQUAL "check")
  elseif (type STREQUAL "reformat")
  else ()
    message(FATAL_ERROR
      "The formatter type of ${name} must be either `check` or `reformat`.")
  endif ()

  set_property(GLOBAL APPEND
    PROPERTY "formatter_${name}_type" "${type}")

  set(tools_exist TRUE)
  foreach (tool IN LISTS ARGN)
    find_program("TOOL_${tool}"
      NAMES "${tool}"
      DOC   "The path to the ${tool} formatting tool")
    if (NOT TOOL_${tool})
      set(tools_exist FALSE)
    endif ()
  endforeach ()

  if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/format.${name}")
    set(script "${CMAKE_CURRENT_BINARY_DIR}/format.${name}")
  else()
    set(script "${CMAKE_CURRENT_SOURCE_DIR}/format.${name}")
  endif()

  set_property(GLOBAL APPEND
    PROPERTY formatters "${name}")
  set_property(GLOBAL APPEND
    PROPERTY "formatter_${name}_tools" "${tools_exist}")
  set_property(GLOBAL APPEND
    PROPERTY "formatter_${name}_script" "${script}")
  set_property(GLOBAL
    PROPERTY "formatter_${name}_exclude_from_all" "0")

  option("ENABLE_${name}" "Install the ${tool} formatter script" "${tools_exist}")

  if (ENABLE_${name})
    message(STATUS "Enabling the ${name} formatter.")
    if (NOT tools_exist)
      message(WARNING
        "The ${name} formatter is enabled, but required tools do not exist in PATH.")
    endif ()
    install_formatter("${name}")
  else ()
    message(STATUS "Disabling the ${name} formatter.")
  endif ()
endfunction ()

function (install_formatter name)
  get_property(script GLOBAL PROPERTY "formatter_${name}_script")
  install(
    PROGRAMS    "${script}"
    DESTINATION "bin"
    COMPONENT   "formatters")
  if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/${name}.yaml.in")
    set(yaml_in "${CMAKE_CURRENT_BINARY_DIR}/${name}.yaml.in")
  else()
    set(yaml_in "${CMAKE_CURRENT_SOURCE_DIR}/${name}.yaml.in")
  endif()
  configure_file(
    "${yaml_in}"
    "${CMAKE_CURRENT_BINARY_DIR}/${name}.yaml"
    @ONLY)
  install(
    FILES       "${CMAKE_CURRENT_BINARY_DIR}/${name}.yaml"
    DESTINATION "share/formatters/config"
    COMPONENT   "examples")
endfunction ()

add_subdirectory(c)
add_subdirectory(generic)
add_subdirectory(python)
add_subdirectory(rust)

get_property(formatters GLOBAL
  PROPERTY formatters)
if (formatters)
  set(formatters_build)
  set(formatters_install)
  foreach (formatter IN LISTS formatters)
    if (NOT ENABLE_${formatter})
      continue ()
    endif ()

    # Skip explicitly excluded formatters.
    get_property(formatter_exclude_from_all GLOBAL
      PROPERTY "formatter_${formatter}_exclude_from_all")
    if (formatter_exclude_from_all)
      continue ()
    endif ()

    # Do not reformat with check-only tools.
    get_property(formatter_type GLOBAL
      PROPERTY "formatter_${formatter}_type")
    if (NOT formatter_type STREQUAL "reformat")
      continue ()
    endif ()

    get_property("formatter_script" GLOBAL
      PROPERTY "formatter_${formatter}_script")
    string(APPEND formatters_build "format_all '${formatter}' '${formatter_script}'\n")
    string(APPEND formatters_install "format_all '${formatter}' 'format.${formatter}'\n")
  endforeach ()

  set(formatter_calls "${formatters_build}")
  configure_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/format.all.in"
    "${CMAKE_BINARY_DIR}/bin/format.all"
    @ONLY)

  set(formatter_calls "${formatters_install}")
  configure_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/format.all.in"
    "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/format.all"
    @ONLY)
  install(
    PROGRAMS    "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/format.all"
    DESTINATION "bin"
    COMPONENT   "formatters")
endif ()
