# zhengrr
# 2016-10-08 – 2021-10-12
# Unlicense

#[=======================================================================[.rst:
rrAuxSourceDirectory
--------------------

提供若干命令，以便利搜集源文件。
#]=======================================================================]

cmake_minimum_required(VERSION 3.20)
cmake_policy(VERSION 3.20)

include_guard()  # 3.10

if(NOT COMMAND rr_check_cmake_name OR
   NOT COMMAND rr_check_directory OR
   NOT COMMAND rr_check_fext_name OR
   NOT COMMAND rr_check_no_argn)
  include("${CMAKE_CURRENT_LIST_DIR}/rrCheck.cmake")
endif()


#[=======================================================================[.rst:
.. command:: rr_aux_source_directory

  搜集目录中的源文件。

  该命令受 ``aux_source_directory`` 命令的启发，提供更精细的控制和更多的功能。

  .. code-block:: cmake

    rr_aux_source_directory(
      <directory> <variable>
      [RECURES]
      [NAME_MATCHES    <regex>...]
      [NAME_CLASHES    <regex>...]
      [EXTENSIONS      <extension>...]
      [GROUP_PREFIX    <group-prefix>]
      [GROUP_FLAT |
       GROUP_TREE      <group-root>]
      [PROPERTIES      {<property-key> <property-value>}...]
      [RETURN_RELATIVE <base-directory>]
      [RETURN_GENEX    <generator-expression-condition>)

  ``RECURES``
    递归子文件夹。

  ``NAME_MATCHES``、``NAME_CLASHES``、``EXTENSIONS``
    目标源码的名称（``NAME_WLE``）白名单、黑名单以及后缀。

  ``GROUP_PREFIX``
    分组前缀。

  ``GROUP_FLAT``、``GROUP_TREE``
    平坦分组或树形分组。

  ``PROPERTIES``
    源码属性。

  ``RETURN_RELATIVE``
    返回相对路径。

  ``RETURN_GENEX``
    返回生成器表达式。

  参见：

  - `aux_source_directory <https://cmake.org/cmake/help/latest/command/aux_source_directory.html>`_
  - `file § Filesystem <https://cmake.org/cmake/help/latest/command/file.html#filesystem>`_
#]=======================================================================]
function(rr_aux_source_directory ARG_DIRECTORY ARG_VARIABLE)
  set(zOptKws    GROUP_FLAT
                 RECURSE)
  set(zOneValKws GROUP_PREFIX
                 GROUP_TREE
                 RETURN_GENEX
                 RETURN_RELATIVE)
  set(zMutValKws EXTENSIONS
                 NAME_CLASHES
                 NAME_MATCHES
                 PROPERTIES)
  cmake_parse_arguments(PARSE_ARGV 2 "ARG" "${zOptKws}" "${zOneValKws}" "${zMutValKws}")
  rr_check_no_argn("${ARG_UNPARSED_ARGUMENTS}" FATAL_ERROR)

  # 搜集源文件
  cmake_path(ABSOLUTE_PATH   ARG_DIRECTORY
                             NORMALIZE
             OUTPUT_VARIABLE pDirectory)  # 3.20
  rr_check_directory("${pDirectory}" FATAL_ERROR)

  rr_check_cmake_name("${ARG_VARIABLE}" AUTHOR_WARNING)

  if(ARG_RECURSE)
    set(oRecurse GLOB_RECURSE)
  else()
    set(oRecurse GLOB)
  endif()

  unset(zNameMatches)
  if(DEFINED ARG_NAME_MATCHES)
    set(zNameMatches ${ARG_NAME_MATCHES})
    list(REMOVE_DUPLICATES zNameMatches)
  endif()

  unset(zNameClashes)
  if(DEFINED ARG_NAME_CLASHES)
    set(zNameClashes ${ARG_NAME_CLASHES})
    list(REMOVE_DUPLICATES zNameClashes)
  endif()

  set(zExtensions ${ARG_EXTENSIONS})
  list(REMOVE_DUPLICATES zExtensions)
  foreach(sExt IN LISTS zExtensions)
    rr_check_fext_name("${sExt}" FATAL_ERROR)
  endforeach()

  set(zResults)
  foreach(sExt IN LISTS zExtensions)
    file(${oRecurse} zFiles CONFIGURE_DEPENDS "${pDirectory}/*${sExt}")  # 3.12

    foreach(pFile IN LISTS zFiles)
      if(DEFINED zNameMatches OR DEFINED zNameClashes)
        get_filename_component(sName "${pFile}" NAME_WLE)

        if(DEFINED zNameMatches)
          set(bPass FALSE)
          foreach(rMatch IN LISTS zNameMatches)
            if(sName MATCHES "${rMatch}")
              set(bPass TRUE)
              break()
            endif()
          endforeach()
          if(NOT bPass)
            continue()
          endif()
        endif()

        if(DEFINED zNameClashes)
          set(bPass TRUE)
          foreach(rClash IN LISTS zNameClashes)
            if(sName MATCHES "${rClash}")
              set(bPass FALSE)
              break()
            endif()
          endforeach()
          if(NOT bPass)
            continue()
          endif()
        endif()
      endif()

      list(APPEND zResults "${pFile}")
    endforeach()
  
  endforeach()
  list(REMOVE_DUPLICATES zResults)

  # 分组
  if(DEFINED ARG_GROUP_PREFIX)
    set(sGroupPrefix "${ARG_GROUP_PREFIX}")
  else()
    set(sGroupPrefix "/")
  endif()

  if(ARG_GROUP_FLAT)
    source_group("${sGroupPrefix}" FILES ${zResults})
  else()
    if(DEFINED ARG_GROUP_TREE)
      cmake_path(ABSOLUTE_PATH   ARG_GROUP_TREE
                                 NORMALIZE
                 OUTPUT_VARIABLE pGroupRoot)  # 3.20
      rr_check_directory("${pGroupRoot}" WARNING_ERROR)
    else()
      set(pGroupRoot "${pDirectory}")
    endif()
    source_group(TREE "${pGroupRoot}" PREFIX "${sGroupPrefix}" FILES ${zResults})
  endif()

  # 属性
  if(DEFINED ARG_PROPERTIES)
    if("${ARG_PROPERTIES}" STREQUAL "")
      message(AUTHOR_WARNING "Keyword PROPERTIES is used, but without value, ignored.")
    else()
      set_source_files_properties(${zResults} PROPERTIES ${ARG_PROPERTIES})
    endif()
  endif()

  # 返回相对路径
  if(DEFINED ARG_RETURN_RELATIVE)
    cmake_path(ABSOLUTE_PATH   ARG_RETURN_RELATIVE
                               NORMALIZE
               OUTPUT_VARIABLE pBaseDirectory)  # 3.20
    rr_check_directory("${pBaseDirectory}" FATAL_ERROR)

    set(zTmpResults)
    foreach(pAbsPath IN LISTS zResults)
      cmake_path(RELATIVE_PATH   pAbsPath
                 BASE_DIRECTORY  "${pBaseDirectory}"
                 OUTPUT_VARIABLE pRelPath)  # 3.20
      list(APPEND zTmpResults "${pRelPath}")
    endforeach()
    set(zResults ${zTmpResults})
  endif()

  # 返回生成器表达式
  if(DEFINED ARG_RETURN_GENEX)
    set(zTmpResults)
    foreach(pPath IN LISTS zResults)
      # string(REPLACE ">" "$<ANGLE-R>"   pPath "${pPath}")
      # string(REPLACE "," "$<COMMA>"     pPath "${pPath}")
      # string(REPLACE ":" "$<SEMICOLON>" pPath "${pPath}")
      set(pPath "$<${ARG_RETURN_GENEX}:${pPath}>")
      list(APPEND zTmpResults "${pPath}")
    endforeach()
    set(zResults ${zTmpResults})
  endif()

  set("${ARG_VARIABLE}" ${zResults} PARENT_SCOPE)
endfunction()


#[=======================================================================[.rst:
.. command:: rr_aux_source_directory_wcon

  依据惯例，搜集目录中的源文件。

  该命令接受与 :command:`rr_aux_source_directory` 命令类似的参数，并依据惯例进行更多配置（``wcon``，with convention）：

  .. code-block:: cmake

    rr_aux_source_directory_wcon(
      <name> <argument-of-"rr_aux_source_directory"-command>...
      [C] [CXX] [MFC] [QT])

  参见：

  - :command:`rr_aux_source_directory`
  - :command:`rr_check_cmake_name`
#]=======================================================================]
function(rr_aux_source_directory_wcon ARG_DIRECTORY ARG_VARIABLE)
  set(zOptKws    C
                 CXX
                 MFC
                 QT)
  set(zOneValKws)
  set(zMutValKws EXTENSIONS)
  cmake_parse_arguments(PARSE_ARGV 2 "ARG" "${zOptKws}" "${zOneValKws}" "${zMutValKws}")

  set(zExtensions ${ARG_EXTENSIONS})
  get_cmake_property(zLangs ENABLED_LANGUAGES)

  # https://www.doxygen.nl/manual/starting.html
  if(ON)
    list(APPEND zExtensions ".clang-format"  # 代码风格配置文件
                            ".dox"           # Doxygen 文档文件
                            ".in"            # CMake 配置输入文件
                            ".md"            # Markdown 文档文件
                            ".txt")          # 文本文件
  endif()
  if(ARG_C OR C IN_LIST zLangs)
    list(APPEND zExtensions ".h"    # 头文件
                            ".c"    # 源文件
                            ".inl"  # 内联实现文件
                            ".i")   # 预处理生成文件
  endif()
  # https://stackoverflow.com/questions/5171502/
  if(ARG_CXX OR CXX IN_LIST zLangs)
    list(APPEND zExtensions ".H"    ".h"    ".h++"  ".hh"   ".hp"   ".hpp"  ".hxx"  ".inc"  # 头文件
                            ".C"            ".c++"  ".cc"   ".cp"   ".cpp"  ".cxx"          # 源文件
                                                                    ".ipp"          ".inl"  # 内联实现文件
                                            ".t++"          ".tp"   ".tpp"  ".txx"  ".tpl"  # 内联实现文件（模板）
                                                                            ".ixx"          # 模块接口文件
                                            ".c++m"                 ".cppm" ".cxxm"         # 模块接口文件
                                                    ".ii")                                  # 预处理生成文件
  endif()
  if(ARG_MFC)
    list(APPEND zExtensions ".h"    ".cpp"
                            ".rc"   ".rc2"  ".bmp"  ".cur"  ".ico")
  endif()
  if(ARG_QT)
    list(APPEND zExtensions ".h"    ".cpp"
                            ".qrc"  ".ui"   ".qml"  ".ts")
  endif()

  list(REMOVE_DUPLICATES zExtensions)

  rr_aux_source_directory("${ARG_DIRECTORY}" zResults EXTENSIONS ${zExtensions} ${ARG_UNPARSED_ARGUMENTS})
  set("${ARG_VARIABLE}" ${zResults} PARENT_SCOPE)
endfunction()
