#!/usr/bin/env -S cmake -P
# -*- CMake -*-

set(CMATE "cmate")
set(CMATE_VER "0.0.0")
set(CMATE_CMDS "")
set(CMATE_DEPSFILE "deps.txt")
set(CMATE_PRJFILE "project.yaml")
set(CMATE_LINKFILE "link.yaml")
set(CMATE_GIT_HOST "GH")
set(CMATE_GH "https://github.com")
set(CMATE_GL "https://gitlab.com")
set(CMATE_BB "https://bitbucket.org")

cmake_policy(SET CMP0057 NEW)
cmake_policy(SET CMP0054 NEW)
cmake_policy(SET CMP0007 NEW)

###############################################################################
#
# Content of cmate/utilities.cmake
#
###############################################################################
function(cmate_die)
    list(JOIN ARGV " " MSGS)
    set(MSG "CMate: error: ${MSGS}")

    if(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.29)
        message("${MSG}")
        cmake_language(EXIT 1)
    else()
        message(FATAL_ERROR "${MSG}")
    endif()
endfunction()

function(cmate_msg)
    list(JOIN ARGV " " MSGS)
    message("CMate: ${MSGS}")
endfunction()

function(cmate_warn)
    list(JOIN ARGV " " MSGS)
    message(WARNING "CMate: ${MSGS}")
endfunction()

function(cmate_info)
    list(JOIN ARGV " " MSGS)

    if(CMATE_VERBOSE)
        cmate_msg(${MSGS})
    endif()
endfunction()

function(cmate_setg VAR VAL)
    if("$ENV{CMATE_SET_TRACE}")
        message("SET: ${VAR}=\"${VAL}\"")
    endif()

    set(${VAR} "${VAL}" CACHE INTERNAL "${VAR}")
endfunction()

function(cmate_unsetg VAR)
    unset(${VAR} CACHE)
endfunction()

function(cmate_appendg VAR VAL)
    if(${VAR})
        set(VAL "${${VAR}};${VAL}")
    endif()

    cmate_setg(${VAR} "${VAL}")
endfunction()

function(cmate_setgdir VAR VAL)
    cmate_setg(${VAR} "${VAL}")
    file(MAKE_DIRECTORY ${${VAR}})
endfunction()

function(cmate_sleep DURATION)
    execute_process(COMMAND ${CMAKE_COMMAND} -E sleep ${DURATION})
endfunction()

function(cmate_set_version)
    if("${CMATE_PROJECT.version}" STREQUAL "")
        cmate_warn("using default version: 0.1.0")
        cmate_setg(CMATE_PROJECT.version "0.1.0")
    endif()

    if("${CMATE_PROJECT.version}" MATCHES "^([^\\.]+)\\.([^\\.]+)\\.([^\\.]+)$")
        cmate_setg(CMATE_PROJECT.version_major ${CMAKE_MATCH_1})
        cmate_setg(CMATE_PROJECT.version_minor ${CMAKE_MATCH_2})
        cmate_setg(CMATE_PROJECT.version_patch ${CMAKE_MATCH_3})
    else()
        cmate_die("unable to parse version: ${CMATE_PROJECT.version}")
    endif()
endfunction()

macro(cmate_setv VAR VAL)
    if("${${VAR}}" STREQUAL "")
        set(${VAR} ${VAL})
    endif()
endmacro()

macro(cmate_setprop VAR PROP VAL)
    set(SINGLE "")
    set(MULTI "")
    cmake_parse_arguments(ARGS "PARENT_SCOPE" "${SINGLE}" "${MULTI}" ${ARGN})

    if(ARGS_PARENT_SCOPE)
        set("${VAR}.${PROP}" "${VAL}" PARENT_SCOPE)
    else()
        set("${VAR}.${PROP}" "${VAL}")
    endif()
endmacro()

macro(cmate_setprops PVAR VAR PROPS)
    set(SINGLE "")
    set(MULTI "")
    cmake_parse_arguments(ARGS "PARENT_SCOPE" "${SINGLE}" "${MULTI}" ${ARGN})

    if(ARGS_PARENT_SCOPE)
        foreach(PROP ${PROPS})
            set("${PVAR}.${PROP}" "${${VAR}.${PROP}}" PARENT_SCOPE)
        endforeach()
    else()
        foreach(PROP ${PROPS})
            set("${PVAR}.${PROP}" "${${VAR}.${PROP}}")
        endforeach()
    endif()
endmacro()

function(cmate_json_array_to_list JSON VAR)
    set(ITEMS "")
    string(JSON T ERROR_VARIABLE ERR TYPE ${JSON})

    if(T STREQUAL "ARRAY")
        string(JSON N LENGTH ${JSON})

        if(${N} GREATER_EQUAL 1)
            math(EXPR N "${N}-1")

            foreach(I RANGE ${N})
                string(JSON ITEM GET ${JSON} ${I})
                list(APPEND ITEMS ${ITEM})
            endforeach()
        endif()
    else()
        set(ITEMS "${JSON}")
    endif()

    set(${VAR} "${ITEMS}" PARENT_SCOPE)
endfunction()

function(cmate_json_get_array JSON KEY VAR)
    string(JSON VALUES ERROR_VARIABLE ERR GET ${JSON} ${KEY})
    set(ITEMS "")

    if (NOT ERR)
        cmate_json_array_to_list("${VALUES}" ITEMS)
    endif()

    set(${VAR} ${ITEMS} PARENT_SCOPE)
endfunction()

function(cmate_json_set_array JVAR JSON KEY VAR)
    set(ARRAY "[]")
    set(I 0)

    foreach(ITEM ${VAR})
        string(JSON ARRAY SET "${ARRAY}" "${I}" "\"${ITEM}\"")
        math(EXPR I "${I}+1")
    endforeach()

    string(JSON JSON SET ${JSON} ${KEY} ${ARRAY})
    set(${JVAR} ${JSON} PARENT_SCOPE)
endfunction()

function(cmate_json_get_str JSON KEY VAR DEF)
    string(JSON STR ERROR_VARIABLE ERR GET ${JSON} ${KEY})

    if(ERR)
        set(STR ${DEF})
    endif()

    set(${VAR} ${STR} PARENT_SCOPE)
endfunction()

function(cmate_json_set_val JVAR KEY VAL)
    string(JSON JSON SET ${${JVAR}} ${KEY} "${VAL}")
    set(${JVAR} ${JSON} PARENT_SCOPE)
endfunction()

function(cmate_json_set_str JVAR KEY VAL)
    cmate_json_set_val(${JVAR} ${KEY} "\"${VAL}\"")
    set(${JVAR} ${${JVAR}} PARENT_SCOPE)
endfunction()

function(cmate_split STR SEP VAR)
    set(VALUES "")

    while(STR MATCHES "^([^${SEP}]+)${SEP}(.+)$")
        list(APPEND VALUES "${CMAKE_MATCH_1}")
        set(STR "${CMAKE_MATCH_2}")
    endwhile()

    if(NOT STR STREQUAL "")
        list(APPEND VALUES "${STR}")
    endif()

    set(${VAR} "${VALUES}" PARENT_SCOPE)
endfunction()

function(cmate_split_lines STR VAR)
    set(VALUES "")
    set(SEP "\r\n")

    # REGEX MATCHALL can't match empty strings, so "manual" solution... Yeah...
    while(STR MATCHES "^([^${SEP}]*)[${SEP}](.*)$")
        if(CMAKE_MATCH_1 STREQUAL "")
            list(APPEND VALUES "${CMATE_EMPTY_LINE_MARKER}")
        else()
            list(APPEND VALUES "${CMAKE_MATCH_1}")
        endif()

        set(STR "${CMAKE_MATCH_2}")
    endwhile()

    if(NOT STR STREQUAL "")
        list(APPEND VALUES "${STR}")
    endif()

    set(${VAR} "${VALUES}" PARENT_SCOPE)
endfunction()

macro(cmate_split_conf_path PATH VAR)
    cmate_split("${PATH}" "\\." ${VAR})
endmacro()

function(cmate_conf_get PATH VAR)
    cmate_split_conf_path(${PATH} KEYS)

    if(${ARGC} GREATER 2)
        cmate_json_get_array("${ARGV2}" "${KEYS}" VALUE)
    else()
        cmate_json_get_array("${CMATE_CONF}" "${KEYS}" VALUE)
    endif()

    set(${VAR} "${VALUE}" PARENT_SCOPE)
endfunction()

function(cmate_conf_set_str PATH VAL)
    cmate_split_conf_path(${PATH} KEYS)
    cmate_json_set_str(CMATE_CONF "${KEYS}" "${VAL}")
    cmate_setg(CMATE_CONF "${CMATE_CONF}")
endfunction()

function(cmate_conf_load_version)
    set(VER "")

    cmate_conf_get("version_file" VFILE)

    if(VFILE AND EXISTS ${VFILE})
        # Allow file to be optional
        file(STRINGS ${VFILE} LINES)
        list(GET LINES 0 VER)
    else()
        cmate_conf_get("version" VER)
    endif()

    if("${VER}" STREQUAL "")
        cmate_die("project variable \"version\" or \"version_file\" no set")
    endif()

    cmate_setg(CMATE_PROJECT.version "${VER}")
endfunction()

function(cmate_load_conf FILE)
    set(PKGS "")

    if(NOT EXISTS ${FILE})
        if("${CMATE_${CMATE_UCMD}_ALLOW_NO_CONF}")
            return()
        else()
            cmate_die("configuration not found: ${FILE}")
        endif()
    endif()

    cmate_yaml_load(${FILE} CMATE_CONF)
    cmate_setg(CMATE_CONF "${CMATE_CONF}")

    foreach(VNAME "name" "namespace" "std")
        cmate_conf_get(${VNAME} VAL)

        if("${VAL}" STREQUAL "")
            cmate_die("project variable \"${VNAME}\" no set")
        else()
            cmate_setg(CMATE_PROJECT.${VNAME} "${VAL}")
        endif()
    endforeach()

    cmate_conf_load_version()
    cmate_set_version()
endfunction()

function(cmate_save_conf FILE)
    if(EXISTS ${FILE})
        cmate_die("${FILE} already exists")
    endif()

    cmate_yaml_save(${FILE} "${CMATE_CONF}")
endfunction()

function(cmate_project_varname NAME VAR)
    string(TOUPPER "${CMATE_PROJECT.name}_${NAME}" VNAME)
    string(REPLACE "-" "_" VNAME ${VNAME})
    set(${VAR} ${VNAME} PARENT_SCOPE)
endfunction()

function(cmate_join_escape_list LVAR OVAR)
    list(JOIN ${LVAR} "_semicolon_" ESCAPED)
    set(${OVAR} ${ESCAPED} PARENT_SCOPE)
endfunction()

macro(cmate_unescape_list LVAR)
    list(TRANSFORM ${LVAR} REPLACE "_semicolon_" "\\\;")
endmacro()

function(cmate_unquote STR VAR)
    set(VAL "")

    if(STR MATCHES "^\"((\\\\.|[^\"])*)?\"$")
        set(VAL "${CMAKE_MATCH_1}")
    elseif(STR MATCHES "^'([^']*(''[^']*)*)?'$")
        set(VAL "${CMAKE_MATCH_1}")
    else()
        set(VAL "${STR}")
    endif()

    set(${VAR} ${VAL} PARENT_SCOPE)
endfunction()

function(cmate_run_prog)
    set(OPTS QUIET)
    set(SINGLE DIR MSG ERR)
    set(MULTI CMD)
    cmake_parse_arguments(RUN "${OPTS}" "${SINGLE}" "${MULTI}" ${ARGN})

    cmate_unescape_list(RUN_CMD)

    if(RUN_MSG)
        cmate_msg(${RUN_MSG})
    endif()

    if(RUN_QUIET)
        list(APPEND EXEC_ARGS OUTPUT_QUIET)
    endif()

    execute_process(
        COMMAND ${RUN_CMD}
        WORKING_DIRECTORY "${RUN_DIR}"
        RESULTS_VARIABLE RC
        ${EXEC_ARGS}
    )

    if(RC)
        if(RUN_ERR)
            set(ERR "${RUN_ERR}")
        else()
            list(JOIN ARGV " " ERR)
            string(PREPEND ERR "command failed: ")
        endif()

        cmate_die("${ERR}")
    endif()
endfunction()

function(cmate_unique_dir PATH VAR)
    file(GLOB PATHS "${PATH}/*")

    foreach(PATH ${PATHS})
        if(IS_DIRECTORY ${PATH})
            list(APPEND ALL_DIRS ${PATH})
        endif()
    endforeach()

    list(LENGTH ALL_DIRS DIRS)

    if(DIRS EQUAL 0)
        cmate_die("no directories found in ${PATH}")
    elseif(DIRS GREATER 1)
        cmate_die("multiple directories found ${PATH}")
    endif()

    list(GET ALL_DIRS 0 DIR)
    set(${VAR} ${DIR} PARENT_SCOPE)
endfunction()

function(cmate_download URL FILE)
    if(CMATE_SIMULATE)
        cmate_msg("download ${URL} to ${FILE}")
        return()
    endif()

    set(WAIT_INTERVAL 5)
    set(MAX_RETRIES 10)
    set(RETRIES ${MAX_RETRIES})
    set(RC 1)

    while(RC)
        file(DOWNLOAD ${URL} ${FILE} STATUS ST)

        list(GET ST 0 RC)

        if(RC)
            if(RETRIES)
                math(EXPR RETRIES "${RETRIES} - 1")
                math(EXPR ATTEMPT "${MAX_RETRIES} - ${RETRIES}")
                cmate_warn(
                    "download of ${URL} failed"
                    " (attempt ${ATTEMPT} of ${MAX_RETRIES}"
                    ", retrying in ${WAIT_INTERVAL}s)"
                )
                cmate_sleep(${WAIT_INTERVAL})
            else()
                cmate_die("download of ${URL} failed: ${ST}")
            endif()
        endif()
    endwhile()
endfunction()

function(cmate_set_build_types DEBUGVAR RELEASEVAR DEFAULTS)
    if(CMATE_BUILD_TYPES)
        return()
    endif()

    set(TYPES "")

    if(NOT "${${DEBUGVAR}}" AND NOT "${${RELEASEVAR}}")
        set(TYPES ${DEFAULTS})
    else()
        foreach(TYPE "Debug" "Release")
            string(TOUPPER "${TYPE}VAR" TVAR)
            set(TVAR "${${TVAR}}")

            if("${${TVAR}}")
                list(APPEND TYPES "${TYPE}")
            endif()
        endforeach()
    endif()

    cmate_setg(CMATE_BUILD_TYPES "${TYPES}")
endfunction()

function(cmate_github_get_latest REPO PKG VAR)
    set(URL "https://github.com/${REPO}/releases/latest/download/${PKG}")

    set(FILE "${CMATE_DL_DIR}/${PKG}")

    if (NOT EXISTS ${FILE})
        cmate_download(${URL} ${FILE})
    endif()

    set(${VAR} ${FILE} PARENT_SCOPE)
endfunction()

function(cmate_check_ninja)
    if(CMATE_NO_NINJA)
        unset(CMATE_NINJA)
        return()
    endif()

    find_program(NINJA ninja)
    set(TDIR "${CMATE_TMP_DIR}/ninja")

    if(NOT NINJA)
        set(NOS "")
        set(NCMD "ninja")

        if(${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Linux")
            set(NOS "linux")
        elseif(${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Windows")
            set(NOS "win")
            set(NCMD "ninja.exe")
        elseif(${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Darwin")
            set(NOS "mac")
        else()
            cmate_die("Please install ninja: ${CMAKE_SYSTEM_NAME}")
        endif()

        if(NOT EXISTS "${CMATE_ENV_BIN_DIR}/${NCMD}")
            cmate_msg("getting ninja from github")
            cmate_github_get_latest("ninja-build/ninja" "ninja-${NOS}.zip" NZIP)

            file(REMOVE_RECURSE ${TDIR})
            file(ARCHIVE_EXTRACT INPUT ${NZIP} DESTINATION ${TDIR})
            file(COPY_FILE "${TDIR}/${NCMD}" "${CMATE_ENV_BIN_DIR}/${NCMD}")
            file(REMOVE_RECURSE ${TDIR})
            cmate_msg("ninja installed in ${CMATE_ENV_BIN_DIR}")
        endif()

        set(NINJA "${CMATE_ENV_BIN_DIR}/${NCMD}")
    endif()

    cmate_setg(CMATE_NINJA ${NINJA})
endfunction()
###############################################################################
#
# Content of cmate/yaml.cmake
#
###############################################################################
###############################################################################
#
# Simple YAML parser based on Perl's YAML::Tiny / Lua's tinyyaml
#
###############################################################################
function(cmate_yaml_count_indent LINE VAR)
    set(LEVEL 0)

    if(LINE MATCHES "^([ ]+)")
        string(LENGTH "${CMAKE_MATCH_1}" LEVEL)
    endif()

    set("${VAR}" ${LEVEL} PARENT_SCOPE)
endfunction()

function(cmate_yaml_unquote STR VAR)
    set(VAL "")

    if(STR MATCHES "^\"((\\\\.|[^\"])*)?\"$")
        set(VAL "${CMAKE_MATCH_1}")
    elseif(STR MATCHES "^'([^']*(''[^']*)*)?'$")
        set(VAL "${CMAKE_MATCH_1}")
    else()
        set(VAL "${STR}")
    endif()

    set(${VAR} ${VAL} PARENT_SCOPE)
endfunction()

function(cmate_yaml_parse_scalar)
    set(OPTS IS_KEY)
    set(SINGLE STR TO_VAR)
    set(MULTI "")
    cmake_parse_arguments(SCALAR "${OPTS}" "${SINGLE}" "${MULTI}" ${ARGN})

    set(VALUE "")

    # Trim whitespace and comments
    string(REGEX REPLACE "^[ ]+" "" SCALAR_STR ${SCALAR_STR})
    string(REGEX REPLACE "[ ]+$" "" SCALAR_STR ${SCALAR_STR})
    string(REGEX REPLACE "#.*$" "" STR ${SCALAR_STR})

    if("${SCALAR_STR}" STREQUAL "~")
        set(VALUE "null")
    else()
        cmate_yaml_unquote(${SCALAR_STR} VALUE)

        if(VALUE MATCHES "[^0-9]" AND NOT SCALAR_IS_KEY)
            set(VALUE "\"${VALUE}\"")
        endif()
    endif()

    set(${SCALAR_TO_VAR} "${VALUE}" PARENT_SCOPE)
endfunction()

function(cmate_yaml_parse_seq)
    set(OPTS "")
    set(SINGLE LINE INDENT PREFIX)
    set(MULTI LINES)
    cmake_parse_arguments(MY "${OPTS}" "${SINGLE}" "${MULTI}" ${ARGN})

    set(OBJ "[]")

    if(NOT "${MY_LINE}" STREQUAL "")
        message(FATAL_ERROR "parse_seq error: '${MY_LINE}'")
    endif()

    while(1)
        list(LENGTH MY_LINES LINECOUNT)

        if(${LINECOUNT} EQUAL 0)
            break()
        endif()

        list(GET MY_LINES 0 LINE)
        cmate_yaml_count_indent("${LINE}" LEVEL)

        if(LEVEL LESS MY_INDENT)
            # return seq
            break()
        elseif(LEVEL GREATER MY_INDENT)
            message(FATAL_ERROR "found bad identing on line: ${LINE}: ${LEVEL} > ${MY_INDENT}")
        endif()

        if(NOT LINE MATCHES "^([ ]*-[ ]+)(.*)")
            if(NOT LINE MATCHES "^([ ]*-$)(.*)")
                # return seq
                break()
            endif()
        endif()

        set(REST "${CMAKE_MATCH_2}")
        string(LENGTH "${CMAKE_MATCH_1}" INDENT2)

        if(REST MATCHES "^[^'\" ]*:[ ]*$" OR LINE MATCHES "^[^'\" ]*:[ ]+.")
            # Inline nested hash
            string(REPEAT " " ${INDENT2} PAD)
            list(POP_FRONT MY_LINES)
            list(PREPEND MY_LINES "${PAD}${REST}")

            cmate_yaml_parse_map(
                LINE ""
                LINES ${MY_LINES}
                INDENT ${INDENT2}
                PREFIX SUB
            )
            set(MY_LINES ${SUB_LINES})

            string(JSON POS LENGTH "${OBJ}")
            string(JSON OBJ SET ${OBJ} ${POS} "${SUB_JSON}")
        elseif(REST MATCHES "^-[ ]+")
            # Inline nested seq
            string(REPEAT " " ${INDENT2} PAD)
            list(POP_FRONT MY_LINES)
            list(PREPEND MY_LINES "${PAD}${REST}")

            cmate_yaml_parse_seq(
                LINE ""
                LINES ${MY_LINES}
                INDENT ${INDENT2}
                PREFIX SUB
            )
            set(MY_LINES ${SUB_LINES})

            string(JSON POS LENGTH "${OBJ}")
            string(JSON OBJ SET ${OBJ} ${POS} "${SUB_JSON}")
        elseif("${REST}" STREQUAL "")
            list(POP_FRONT MY_LINES)
            message(FATAL_ERROR "WHOA")
        elseif(NOT "${REST}" STREQUAL "")
            list(GET MY_LINES 0 NEXTLINE)
            cmate_yaml_count_indent("${NEXTLINE}" INDENT2)
            list(POP_FRONT MY_LINES)
            cmate_yaml_parse_scalar(STR "${REST}" TO_VAR VALUE)

            string(JSON POS LENGTH "${OBJ}")
            string(JSON OBJ SET ${OBJ} ${POS} ${VALUE})
        endif()
    endwhile()

    set("${MY_PREFIX}_LINES" ${MY_LINES} PARENT_SCOPE)
    set("${MY_PREFIX}_JSON" ${OBJ} PARENT_SCOPE)
endfunction()

function(cmate_yaml_parse_map)
    set(OPTS "")
    set(SINGLE LINE INDENT PREFIX)
    set(MULTI LINES)
    cmake_parse_arguments(MY "${OPTS}" "${SINGLE}" "${MULTI}" ${ARGN})

    set(OBJ "{}")

    if(NOT "${MY_LINE}" STREQUAL "")
        message(FATAL_ERROR "parse_map error: '${MY_LINE}'")
    endif()

    while(1)
        list(LENGTH MY_LINES LINECOUNT)

        if(${LINECOUNT} EQUAL 0)
            break()
        endif()

        list(GET MY_LINES 0 LINE)
        cmate_yaml_count_indent("${LINE}" LEVEL)

        if(LEVEL LESS MY_INDENT)
            # return map
            break()
        elseif(LEVEL GREATER MY_INDENT)
            message(FATAL_ERROR "found bad identing on line: ${LINE}: ${LEVEL} > ${MY_INDENT}")
        endif()

        if(${LINE} MATCHES "^([ ]*(.+):)")
            string(LENGTH "${CMAKE_MATCH_1}" TOSTRIP)
            cmate_yaml_parse_scalar(STR "${CMAKE_MATCH_2}" TO_VAR KEY IS_KEY 1)
            string(SUBSTRING ${LINE} ${TOSTRIP} -1 LINE)
        else()
            message(FATAL_ERROR "failed to classify line: ${LINE}")
        endif()

        if(NOT "${LINE}" STREQUAL "")
            # We have a value
            list(POP_FRONT MY_LINES)
            cmate_yaml_parse_scalar(STR "${LINE}" TO_VAR VALUE)
            string(JSON OBJ SET ${OBJ} ${KEY} ${VALUE})
        else()
            # Indent/sub map
            list(POP_FRONT MY_LINES)
            list(LENGTH MY_LINES LINECOUNT)

            if(LINECOUNT EQUAL 0)
                string(JSON OBJ SET ${OBJ} ${KEY} "null")
                break()
            endif()

            list(GET MY_LINES 0 LINE)
            cmate_yaml_count_indent("${LINE}" INDENT2)

            if("${LINE}" MATCHES "^[ ]*-")
                cmate_yaml_parse_seq(
                    LINE ""
                    LINES "${MY_LINES}"
                    INDENT ${INDENT2}
                    PREFIX SUB
                )
                set(MY_LINES ${SUB_LINES})

                string(JSON OBJ SET ${OBJ} ${KEY} "${SUB_JSON}")
            else()
                if(${MY_INDENT} GREATER_EQUAL ${INDENT2})
                    string(JSON OBJ SET ${OBJ} ${KEY} "null")
                else()
                    cmate_yaml_parse_map(
                        LINE ""
                        LINES "${MY_LINES}"
                        INDENT ${INDENT2}
                        PREFIX SUB
                    )
                    set(MY_LINES ${SUB_LINES})

                    string(JSON OBJ SET ${OBJ} ${KEY} "${SUB_JSON}")
                endif()
            endif()
        endif()
    endwhile()

    set("${MY_PREFIX}_LINES" ${MY_LINES} PARENT_SCOPE)
    set("${MY_PREFIX}_JSON" ${OBJ} PARENT_SCOPE)
endfunction()

function(cmate_yaml_parse_doc LINES VAR)
    while(LINES)
        list(GET LINES 0 LINE)

        if(LINE STREQUAL "---")
            list(POP_FRONT LINES)
            continue()
        elseif(LINE MATCHES "^[ ]*-")
            # Array
            cmate_yaml_parse_seq(
                LINE ""
                LINES ${LINES}
                INDENT 0
                PREFIX SUB
            )
            set(LINES ${SUB_LINES})
        elseif(LINE MATCHES "^[ ]*[^ ]")
            # Hash
            cmate_yaml_count_indent("${LINE}" LEVEL)
            cmate_yaml_parse_map(
                LINE ""
                LINES ${LINES}
                INDENT ${LEVEL}
                PREFIX SUB
            )
            set(LINES ${SUB_LINES})
        else()
            message(FATAL_ERROR "parse error")
        endif()
    endwhile()

    set(${VAR} "${SUB_JSON}" PARENT_SCOPE)
endfunction()

function(cmate_yaml_dump_scalar)
    set(OPTS IS_KEY)
    set(SINGLE STR TO_VAR)
    set(MULTI "")
    cmake_parse_arguments(SCALAR "${OPTS}" "${SINGLE}" "${MULTI}" ${ARGN})

    string(JSON T ERROR_VARIABLE ERR TYPE "${SCALAR_STR}")

    if(T STREQUAL "NULL")
        set(CONTENT "~")
    elseif("${SCALAR_STR}" STREQUAL "")
        set(CONTENT "''")
    elseif(T STREQUAL "NUMBER")
        if(SCALAR_IS_KEY)
            set(CONTENT "'${SCALAR_STR}'")
        else()
            set(CONTENT "${SCALAR_STR}")
        endif()
    elseif("${SCALAR_STR}" MATCHES "^[~!@#%&*|>?:,'\"`{} ]|^-+$|:$]")
        set(CONTENT "'${SCALAR_STR}'")
    else()
        set(CONTENT "${SCALAR_STR}")
    endif()

    set("${SCALAR_TO_VAR}" "${CONTENT}" PARENT_SCOPE)
endfunction()

function(cmate_yaml_dump_seq)
    set(OPTS "")
    set(SINGLE JSON TO_VAR INDENT)
    set(MULTI "")
    cmake_parse_arguments(SEQ "${OPTS}" "${SINGLE}" "${MULTI}" ${ARGN})

    string(REPEAT "  " ${SEQ_INDENT} INDENT)
    set(CONTENT "")

    cmate_json_array_to_list("${SEQ_JSON}" ITEMS)

    foreach(ITEM ${ITEMS})
        set(LINE "${INDENT}-")
        cmate_yaml_get_type(JSON "${ITEM}" VAR T)

        if(T STREQUAL "SCALAR")
            cmate_yaml_dump_scalar(STR "${ITEM}" TO_VAR SCALAR)
            string(APPEND LINE " ${SCALAR}")
            string(APPEND CONTENT "${LINE}\n")
        elseif(T STREQUAL "ARRAY")
            string(JSON N LENGTH ${ITEM})

            if(${N} GREATER 0)
                string(APPEND CONTENT "${LINE}\n")
                math(EXPR SINDENT "${INDENT}+1")
                cmate_yaml_dump_seq(JSON "${ITEM}" TO_VAR ARRAY INDENT ${SINDENT})
                string(APPEND CONTENT "${ARRAY}\n")
            else()
                string(APPEND CONTENT "[]\n")
            endif()
        elseif(T STREQUAL "OBJECT")
            string(JSON N LENGTH ${ITEM})

            if(${N} GREATER 0)
                string(APPEND CONTENT "${LINE}\n")
                math(EXPR SINDENT "${INDENT}+1")
                cmate_yaml_dump_seq(JSON "${ITEM}" TO_VAR HASH INDENT ${SINDENT})
                string(APPEND CONTENT "${HASH}\n")
            else()
                string(APPEND CONTENT "{}\n")
            endif()
        endif()
    endforeach()

    set(${SEQ_TO_VAR} "${CONTENT}" PARENT_SCOPE)
endfunction()

function(cmate_yaml_dump_map)
    set(OPTS "")
    set(SINGLE JSON TO_VAR INDENT)
    set(MULTI "")
    cmake_parse_arguments(MAP "${OPTS}" "${SINGLE}" "${MULTI}" ${ARGN})

    string(REPEAT "  " ${MAP_INDENT} INDENT)
    set(CONTENT "")

    string(JSON N LENGTH ${MAP_JSON})

    if(N EQUAL 0)
        return()
    else()
        math(EXPR COUNT "${N}-1")
    endif()

    foreach(I RANGE ${COUNT})
        string(JSON KEY MEMBER ${MAP_JSON} ${I})
        string(JSON ITEM GET ${MAP_JSON} "${KEY}")
        cmate_yaml_dump_scalar(STR "${KEY}" TO_VAR NAME IS_KEY 1)
        set(LINE "${INDENT}${KEY}:")

        cmate_yaml_get_type(JSON ${MAP_JSON} KEY "${KEY}" VAR T)

        if(T STREQUAL "SCALAR")
            cmate_yaml_dump_scalar(STR "${ITEM}" TO_VAR SCALAR)
            string(APPEND LINE " ${SCALAR}")
            string(APPEND CONTENT "${LINE}\n")
        elseif(T STREQUAL "ARRAY")
            string(JSON N LENGTH ${ITEM})

            if(${N} GREATER 0)
                string(APPEND CONTENT "${LINE}\n")
                math(EXPR SINDENT "${INDENT}+1")
                cmate_yaml_dump_seq(JSON "${ITEM}" TO_VAR ARRAY INDENT ${SINDENT})
                string(APPEND CONTENT "${ARRAY}\n")
            else()
                string(APPEND CONTENT "[]\n")
            endif()
        elseif(T STREQUAL "OBJECT")
            string(JSON N LENGTH ${ITEM})

            if(${N} GREATER 0)
                string(APPEND CONTENT "${LINE}\n")
                math(EXPR SINDENT "${INDENT}+1")
                cmate_yaml_dump_seq(JSON "${ITEM}" TO_VAR HASH INDENT ${SINDENT})
                string(APPEND CONTENT "${HASH}\n")
            else()
                string(APPEND CONTENT "{}\n")
            endif()
        endif()
    endforeach()

    set(${MAP_TO_VAR} "${CONTENT}" PARENT_SCOPE)
endfunction()

function(cmate_yaml_get_type)
    set(OPTS "")
    set(SINGLE JSON KEY VAR)
    set(MULTI "")
    cmake_parse_arguments(GT "${OPTS}" "${SINGLE}" "${MULTI}" ${ARGN})

    string(JSON T ERROR_VARIABLE ERR TYPE ${GT_JSON} ${GT_KEY})

    if(T STREQUAL "NULL" OR T STREQUAL "NUMBER" OR T STREQUAL "STRING" OR T STREQUAL "BOOLEAN")
        set(T "SCALAR")
    endif()

    set(${GT_VAR} ${T} PARENT_SCOPE)
endfunction()

function(cmate_yaml_dump JSON VAR)
    cmate_yaml_get_type(JSON "${JSON}" VAR T)
    set(DOC "---")

    set(INDENT 0)

    if(T STREQUAL "SCALAR")
        cmate_yaml_dump_scalar(STR "${JSON}" TO_VAR CONTENT)
    elseif(T STREQUAL "ARRAY")
        cmate_yaml_dump_seq(JSON "${JSON}" TO_VAR CONTENT INDENT ${INDENT})
    elseif(T STREQUAL "OBJECT")
        cmate_yaml_dump_map(JSON "${JSON}" TO_VAR CONTENT INDENT ${INDENT})
    endif()

    if(CONTENT)
        string(APPEND DOC "\n${CONTENT}")
    endif()

    set(${VAR} "${DOC}" PARENT_SCOPE)
endfunction()

function(cmate_yaml_load FILE VAR)
    set(LINES "")

    if(EXISTS ${FILE})
        file(STRINGS ${FILE} LINES)
    endif()

    cmate_yaml_parse_doc("${LINES}" JSON)

    set("${VAR}" "${JSON}" PARENT_SCOPE)
endfunction()

function(cmate_yaml_save FILE JSON)
    cmate_yaml_dump("${JSON}" YAML)
    file(WRITE ${FILE} ${YAML})
endfunction()
###############################################################################
#
# Content of cmate/args.cmake
#
###############################################################################
function(cmate_check_option OPT OPTS LABEL)
    list(FIND OPTS ${OPT} IDX)

    if (IDX LESS 0)
        cmate_die("unknown ${LABEL} option: ${OPT}")
    endif()
endfunction()

function(cmate_locate_cmate_arguments)
    set(FOUND OFF)

    foreach(POS RANGE ${CMAKE_ARGC})
        string(TOLOWER "${CMAKE_ARGV${POS}}" ARG)
        math(EXPR POS "${POS}+1")

        if (ARG MATCHES "${CMATE}$")
            # Script args follow us, POS already incremented
            set(FOUND ON)
            cmate_setg(CMATE_POS ${POS})
            break()
        endif()
    endforeach()

    if(NOT FOUND)
        # Should not happen if script has correct name (see CMATE at top)
        cmate_die("parse_argument")
    endif()
endfunction()

function(cmate_parse_arguments)
    cmate_locate_cmate_arguments()
    set(OPTS_LABEL "generic")
    set(OPTS ${CMATE_OPTIONS})

    while(CMATE_POS LESS ${CMAKE_ARGC})
        if ("${CMAKE_ARGV${CMATE_POS}}" MATCHES "^--?([A-Za-z0-9_-]+)(=(.+))?$")
            #cmate_check_option(${CMAKE_MATCH_1} "${OPTS}" ${OPTS_LABEL})
            set(OPT "CMATE")

            if(CMATE_CMD)
                string(APPEND OPT "_${CMATE_CMD}")
            endif()

            string(APPEND OPT "_${CMAKE_MATCH_1}")
            string(REPLACE "-" "_" OPT "${OPT}")
            string(TOUPPER ${OPT} OPT)

            if("${CMAKE_MATCH_3}" STREQUAL "")
                cmate_setg(${OPT} 1)
            else()
                cmate_setg(${OPT} "${CMAKE_MATCH_3}")
            endif()
        elseif("${CMATE_CMD}" STREQUAL "")
            set(CMATE_CMD "${CMAKE_ARGV${CMATE_POS}}")
            set(OPTS_LABEL ${CMATE_CMD})
            set(OPTS_VAR CMATE_${CMATE_CMD}_OPTIONS)
            string(TOUPPER "${OPTS_VAR}" OPTS_VAR)
            set(OPTS ${${OPTS_VAR}})
        else()
            list(APPEND CMATE_ARGS "${CMAKE_ARGV${CMATE_POS}}")
        endif()

        math(EXPR CMATE_POS "${CMATE_POS}+1")
    endwhile()

    list(LENGTH CMATE_ARGS CMATE_ARGC)

    cmate_setg(CMATE_CMD "${CMATE_CMD}")
    string(TOUPPER "${CMATE_CMD}" CMATE_UCMD)
    cmate_setg(CMATE_UCMD "${CMATE_UCMD}")
    cmate_setg(CMATE_ARGS "${CMATE_ARGS}")
    cmate_setg(CMATE_ARGC ${CMATE_ARGC})
    get_filename_component(CMATE_ENV "${CMATE_ENV}" REALPATH)
    cmate_setg(CMATE_ENV ${CMATE_ENV})
endfunction()
###############################################################################
#
# Content of cmate/target_deps.cmake
#
###############################################################################
function(cmate_load_link_deps FILE PREFIX)
    set(TOTAL 0)

    cmate_yaml_load(${FILE} LINK)

    foreach(TYPE "public" "private")
        cmate_conf_get("libs.${TYPE}" DEPS ${LINK})

        string(TOUPPER ${TYPE} UTYPE)
        set(${PREFIX}_${UTYPE}_DEPS ${DEPS} PARENT_SCOPE)
        list(LENGTH DEPS DEPS_COUNT)
        set(${PREFIX}_${UTYPE}_DEPS_COUNT ${DEPS_COUNT} PARENT_SCOPE)

        math(EXPR TOTAL "${TOTAL} + ${DEPS_COUNT}")
    endforeach()

    set(${PREFIX}_DEPS_COUNT ${TOTAL} PARENT_SCOPE)
endfunction()

function(cmate_target_link_deps NAME FILE VAR)
    cmate_load_link_deps(${FILE} TGT)

    if(${TGT_DEPS_COUNT} GREATER 0)
        set(TDEPS "\ntarget_link_libraries(\n    ${NAME}")

        foreach(TYPE PUBLIC PRIVATE)
            if(${TGT_${TYPE}_DEPS_COUNT} GREATER 0)
                string(APPEND TDEPS "\n    ${TYPE}")

                foreach(DEP ${TGT_${TYPE}_DEPS})
                    string(APPEND TDEPS "\n        ${DEP}")
                endforeach()
            endif()
        endforeach()

        string(APPEND TDEPS "\n)\n")
        set(${VAR} ${TDEPS} PARENT_SCOPE)
    endif()
endfunction()

function(cmate_target_name NAME TYPE VAR)
    string(TOLOWER "${CMATE_PROJECT.namespace}_${NAME}_${TYPE}" TBASE)
    string(REPLACE "-" "_" TBASE ${TBASE})
    set(${VAR} ${TBASE} PARENT_SCOPE)
endfunction()
###############################################################################
#
# Content of cmate/deps.cmake
#
###############################################################################
cmate_setg(
    CMATE_DEPS_PROPS
    "TYPE;NAME;URL;HOST;REPO;REF;ARGS;SRCDIR;EXTRACT_DIR;SOURCE_DIR;BUILD_DIR"
)

macro(cmate_deps_copy_dep TOV FROMV)
    set(SINGLE "")
    set(MULTI "")
    cmake_parse_arguments(ARGS "PARENT_SCOPE" "${SINGLE}" "${MULTI}" ${ARGN})

    if(ARGS_PARENT_SCOPE)
        cmate_setprops(${TOV} ${FROMV} "${CMATE_DEPS_PROPS}" PARENT_SCOPE)
    else()
        cmate_setprops(${TOV} ${FROMV} "${CMATE_DEPS_PROPS}")
    endif()
endmacro()

function(cmate_deps_get_dep_dir DEPV VAR)
    string(MD5 HASH "${${DEPV}.URL}")
    set(DIR "${CMATE_DL_DIR}/${HASH}")
    set("${VAR}" "${DIR}" PARENT_SCOPE)
endfunction()

function(cmate_deps_get_dep_cache_dir DEPV TYPE VAR)
    cmate_deps_get_dep_dir("${DEPV}" DIR)
    set("${VAR}" "${DIR}/${TYPE}" PARENT_SCOPE)
endfunction()

function(cmate_deps_get_state_file DEPV STATE VAR)
    cmate_deps_get_dep_cache_dir("${DEPV}" "state" DIR)
    set(${VAR} "${DIR}/.${STATE}" PARENT_SCOPE)
endfunction()

function(cmate_deps_set_state DEPV STATE)
    cmate_deps_get_dep_cache_dir("${DEPV}" "state" DIR)
    file(MAKE_DIRECTORY ${DIR})
    cmate_deps_get_state_file("${DEPV}" ${STATE} FILE)
    file(TOUCH ${FILE})
endfunction()

function(cmate_deps_check_repo URL)
    set(GIT_ARGS "ls-remote")
    cmate_run_prog(
        MSG "checking remote at ${URL}"
        ERR "invalid remote at ${URL}"
        CMD git ${GIT_ARGS} ${URL}
        QUIET
    )
endfunction()

function(cmate_deps_get_source_dir DEPV VAR)
    set(DIR "${${DEPV}.EXTRACT_DIR}")

    if(NOT "${${DEPV}.SRCDIR}" STREQUAL "")
        set(DIR "${${DEPV}.EXTRACT_DIR}/${${DEPV}.SRCDIR}")
    endif()

    set(${VAR} ${DIR} PARENT_SCOPE)
endfunction()

function(cmate_deps_get_repo DEPV)
    cmate_deps_copy_dep(DEP ${DEPV})

    set(HOST "${DEP.HOST}")

    if(HOST MATCHES "^\\$\\{(.+)\\}$")
        # Dereference variable
        set(HOST ${${CMAKE_MATCH_1}})
    endif()

    if(HOST STREQUAL "GH")
        set(HOST "https://github.com")
    elseif(TYPE STREQUAL "GL")
        set(HOST "https://gitlab.com")
    endif()

    set(URL "${HOST}/${DEP.REPO}.git")

    cmate_deps_check_repo(${URL})

    set(GIT_ARGS "clone")
    list(
        APPEND GIT_ARGS
        -c advice.detachedHead=false
        --depth 1
    )

    if("${DEP.REF}")
        list(APPEND GIT_ARGS --branch "${DEPV.REF}")
    endif()

    cmate_deps_get_dep_cache_dir(DEP "sources" SDIR)
    cmate_deps_get_dep_cache_dir(DEP "build" BDIR)
    cmate_deps_get_state_file(DEP "fetched" FETCHED)

    if(NOT IS_DIRECTORY ${SDIR} OR NOT EXISTS ${FETCHED})
        # Whatever the reason, we're (re-)fetching
        file(REMOVE_RECURSE ${SDIR})
        cmate_info("cloning ${URL} in ${SDIR}")
        cmate_run_prog(CMD git ${GIT_ARGS} ${URL} ${SDIR})
        cmate_deps_set_state(DEP "fetched")
    endif()

    cmate_setprop(DEP EXTRACT_DIR ${SDIR})
    cmate_deps_get_source_dir(DEP SOURCE_DIR)
    cmate_setprop(DEP EXTRACT_DIR ${SDIR})
    cmate_setprop(DEP SOURCE_DIR ${SOURCE_DIR})
    cmate_setprop(DEP BUILD_DIR ${BDIR})

    cmate_deps_copy_dep(${DEPV} DEP PARENT_SCOPE)
endfunction()

function(cmate_deps_get_url_filename DEPV VAR)
    if("${${DEPV}.URL}" MATCHES "/([^/]+)$")
        set(FILE ${CMAKE_MATCH_1})
    else()
        cmate_die("can't find filename from URL: ${${DEPV}.URL}")
    endif()

    set(${VAR} "${FILE}" PARENT_SCOPE)
endfunction()

function(cmate_deps_get_url DEPV)
    cmate_deps_copy_dep(DEP ${DEPV})

    cmate_deps_get_url_filename(${DEPV} DFILE)
    cmate_deps_get_state_file("${DEPV}" "fetched" FETCHED)
    cmate_deps_get_state_file("${DEPV}" "extracted" EXTRACTED)
    cmate_deps_get_dep_dir(${DEPV} DDIR)
    cmate_deps_get_dep_cache_dir(${DEPV} "sources" DSDIR)
    cmate_deps_get_dep_cache_dir(${DEPV} "build" BDIR)

    set(DFILE "${DDIR}/${DFILE}")

    if(NOT EXISTS ${DFILE})
        file(MAKE_DIRECTORY ${DDIR})
        cmate_info("downloading ${URL} in ${DDIR}")
        cmate_download("${${DEPV}.URL}" ${DFILE})
        cmate_deps_set_state("${DEPV}" "fetched")
    else()
    endif()

    if(NOT IS_DIRECTORY ${DSDIR} OR NOT EXISTS ${EXTRACTED})
        file(REMOVE_RECURSE ${DSDIR})
        cmate_info("extracting ${FILE}")
        file(
            ARCHIVE_EXTRACT
            INPUT ${DFILE}
            DESTINATION ${DSDIR}
        )
        cmate_deps_set_state("${DEPV}" "extracted")
    endif()

    cmate_unique_dir(${DSDIR} UDIR)
    cmate_setprop(${DEPV} EXTRACT_DIR ${UDIR})
    cmate_deps_get_source_dir(${DEPV} SOURCE_DIR)
    cmate_setprop(${DEPV} SOURCE_DIR ${SOURCE_DIR})
    cmate_setprop(${DEPV} BUILD_DIR ${BDIR})

    cmate_deps_copy_dep(${DEPV} DEP PARENT_SCOPE)
endfunction()

function(cmate_deps_dump_dep DEPV)
    foreach(PROP ${CMATE_DEPS_PROPS})
        cmate_msg("DEP.${PROP}=${${DEPV}.${PROP}}")
    endforeach()
endfunction()

function(cmate_deps_parse_spec SPEC VAR)
    if(SPEC MATCHES "^([a-z]+)://([^ /]+)/([^ ]+)$")
        message("1=${CMAKE_MATCH_1}")
        message("2=${CMAKE_MATCH_2}")
        message("3=${CMAKE_MATCH_3}")
        set(SCHEME ${CMAKE_MATCH_1})
        set(HOST ${CMAKE_MATCH_2})
        set(REPO ${CMAKE_MATCH_3})

        if(REPO MATCHES "(.+)\\.git(@([^ ]+))?$")
            # Full remote Git URL
            cmate_setprop(DEP TYPE "git")
            cmate_setprop(DEP REPO ${CMAKE_MATCH_1})
            cmate_setprop(DEP REF "${CMAKE_MATCH_3}")
            cmate_setprop(DEP URL "${SCHEME}://${HOST}/${REPO}.git")
        else()
            # Raw URL, find a name
            cmate_setprop(DEP URL ${SPEC})
            cmate_setprop(DEP TYPE "url")
            cmate_deps_get_url_filename(DEP DFILE)
            get_filename_component(NAME ${DFILE} NAME_WE)
            cmate_setprop(DEP NAME ${NAME})
        endif()
    elseif(SPEC MATCHES "^([A-Za-z0-9_]+)@([a-z]+://[^ ]+)$")
        # name@URL
        cmate_setprop(DEP TYPE "url")
        cmate_setprop(DEP NAME ${CMAKE_MATCH_1})
        cmate_setprop(DEP URL ${CMAKE_MATCH_2})
    elseif(SPEC MATCHES "^(([^: ]+):)?([^@ ]+)(@([^ ]+))?$")
        cmate_setprop(DEP TYPE "git")

        # GitHub/GitLab style project short ref
        if(CMAKE_MATCH_2)
            if(CMATE_${CMAKE_MATCH_2})
                cmate_setprop(DEP HOST ${CMATE_${CMAKE_MATCH_2}})
            else()
                cmate_die("unknown id: ${CMAKE_MATCH_2}")
            endif()
        else()
            cmate_setprop(DEP HOST ${CMATE_${CMATE_GIT_HOST}})
        endif()

        cmate_setprop(DEP REPO ${CMAKE_MATCH_3})
        cmate_setprop(DEP REF "${CMAKE_MATCH_5}")
        cmate_setprop(DEP URL "${DEP.HOST}/${DEP.REPO}.git")
        set("DEP.NAME" ${DEP.REPO})
        string(REGEX REPLACE "[^A-Za-z0-9_]" "_" "DEP.NAME" "${DEP.NAME}")
    else()
        cmate_die("unable to parse dependency: ${SPEC}")

    endif()

    cmate_setprops(${VAR} DEP "${CMATE_DEPS_PROPS}" PARENT_SCOPE)
endfunction()

function(cmate_deps_make_dep JSON_SPEC DEPV)
    string(JSON T ERROR_VARIABLE ERR TYPE ${JSON_SPEC})

    if(T STREQUAL "OBJECT")
        string(JSON DKEYS LENGTH ${JSON_SPEC})

        if(NOT DKEYS EQUAL 1)
            cmate_die("invalid dependency: expected a single key, got ${NKEYS}: ${JSON_SPEC}")
        endif()

        string(JSON SPEC MEMBER ${JSON_SPEC} 0)
        cmate_deps_parse_spec(${SPEC} DEP)
        cmate_json_get_array(${JSON_SPEC} "${SPEC};args" DEP.ARGS)
        cmate_json_get_array(${JSON_SPEC} "${SPEC};srcdir" DEP.SRCDIR)
    elseif(T STREQUAL "STRING")
        cmate_deps_parse_spec(${JSON_SPEC} DEP)
    else()
        cmate_die("invalid dependency: expected object or string, got ${JSON_SPEC}")
    endif()

    cmate_setprops(${DEPV} DEP "${CMATE_DEPS_PROPS}" PARENT_SCOPE)
endfunction()

function(cmate_deps_install_cmake_dep DEPV)
    cmate_deps_get_state_file(${DEPV} "configured" CONFIGURED)
    cmate_deps_get_state_file(${DEPV} "built" BUILT)
    cmate_deps_get_state_file(${DEPV} "installed" INSTALLED)

    if(NOT EXISTS ${CONFIGURED})
        cmate_msg("building with: ${${DEPV}.ARGS}")

        set(ARGS "")

        find_program(CMATE_CCACHE ccache)

        if(CMATE_CCACHE)
            list(APPEND ARGS "-DCMAKE_C_COMPILER_LAUNCHER=${CMATE_CCACHE}")
            list(APPEND ARGS "-DCMAKE_CXX_COMPILER_LAUNCHER=${CMATE_CCACHE}")
        endif()

        cmate_check_ninja()

        cmate_run_prog(
            CMD
                ${CMAKE_COMMAND}
                -DCMAKE_PREFIX_PATH=${CMATE_ENV_DIR}
                -DCMAKE_INSTALL_PREFIX=${CMATE_ENV_DIR}
                -DCMAKE_BUILD_TYPE=Release
                -DBUILD_TESTING=OFF
                -G Ninja
                ${ARGS}
                -S ${${DEPV}.SOURCE_DIR} -B ${${DEPV}.BUILD_DIR}
                ${${DEPV}.ARGS}
        )
        cmate_deps_set_state(${DEPV} "configured")
    endif()
    if(NOT EXISTS ${BUILT})
        cmate_run_prog(
            CMD
                ${CMAKE_COMMAND}
                --build ${${DEPV}.BUILD_DIR}
                --config Release
                --parallel
        )
        cmate_deps_set_state(${DEPV} "built")
    endif()
    if(NOT EXISTS ${INSTALLED})
        cmate_run_prog(
            CMD
                ${CMAKE_COMMAND}
                --install ${${DEPV}.BUILD_DIR}
                --config Release
        )
        cmate_deps_set_state(${DEPV} "installed")
    endif()
endfunction()

function(cmate_deps_install_meson_dep DEPV)
    cmate_deps_get_state_file(${DEPV} "configured" CONFIGURED)
    cmate_deps_get_state_file(${DEPV} "installed" INSTALLED)
    file(MAKE_DIRECTORY ${${DEPV}.BUILD_DIR})

    if(NOT EXISTS ${CONFIGURED})
        cmate_run_prog(
            DIR ${${DEPV}.BUILD_DIR}
            CMD
                meson
                --prefix=${CMATE_ENV_DIR}
                --pkg-config-path=${CMATE_ENV_DIR}
                --cmake-prefix-path=${CMATE_ENV_DIR}
                ${${DEPV}.ARGS}
                . ${${DEPV}.SOURCE_DIR}
        )
        cmate_deps_set_state(${DEPV} "configured")
    endif()
    if(NOT EXISTS ${INSTALLED})
        cmate_run_prog(meson install)
        cmate_deps_set_state(${DEPV} "installed")
    endif()
endfunction()

function(cmate_deps_install_autotools_dep DEPV)
    cmate_deps_get_state_file(${DEPV} "configured" CONFIGURED)
    cmate_deps_get_state_file(${DEPV} "installed" INSTALLED)
    file(MAKE_DIRECTORY ${${DEPV}.BUILD_DIR})

    if(NOT EXISTS ${CONFIGURED})
        cmate_run_prog(
            DIR ${${DEPV}.BUILD_DIR}
            CMD
                ${${DEPV}.SOURCE_DIR}/configure
                --prefix=${CMATE_ENV_DIR}
                ${${DEPV}.ARGS}
        )
        cmate_deps_set_state(${DEPV} "configured")
    endif()
    if(NOT EXISTS ${INSTALLED})
        cmate_run_prog(
            DIR ${${DEPV}.BUILD_DIR}
            CMD make install
        )
        cmate_deps_set_state(${DEPV} "installed")
    endif()
endfunction()

function(cmate_deps_install_makefile_dep DEPV)
    cmate_deps_get_state_file(${DEPV} "built" BUILT)
    cmate_deps_get_state_file(${DEPV} "installed" INSTALLED)
    file(MAKE_DIRECTORY ${${DEPV}.BUILD_DIR})

    if(NOT EXISTS ${BUILT})
        cmate_run_prog(
            DIR ${${DEPV}.SOURCE_DIR}
            CMD make
        )
        cmate_deps_set_state(${DEPV} "built")
    endif()
    if(NOT EXISTS ${INSTALLED})
        cmate_run_prog(
            DIR ${${DEPV}.SOURCE_DIR}
            CMD make prefix=${CMATE_ENV_DIR} install
        )
        cmate_deps_set_state(${DEPV} "installed")
    endif()
endfunction()

function(cmate_deps_install_dep DEPV)
    set(SDIR "${${DEPV}.SOURCE_DIR}")

    if(NOT IS_DIRECTORY "${SDIR}")
        cmate_die("invalid source directory: ${SDIR}")
    endif()

    if(EXISTS "${SDIR}/CMakeLists.txt")
        cmate_deps_install_cmake_dep(${DEPV})
    elseif(EXISTS "${SDIR}/meson.build")
        cmate_deps_install_meson_dep(${DEPV})
    elseif(EXISTS "${SDIR}/configure")
        cmate_deps_install_autotools_dep(${DEPV})
    elseif(EXISTS "${SDIR}/Makefile")
        cmate_deps_install_makefile_dep(${DEPV})
    else()
        cmate_die("don't know how to build in ${SDIR}")
    endif()
endfunction()

function(cmate_deps_get_dep DEPV)
    cmate_deps_copy_dep(DEP ${DEPV})

    if(NOT "${DEP.REPO}" STREQUAL "")
        cmate_msg("checking repository ${DEP.REPO}")
        cmate_deps_get_repo(DEP)
    elseif(NOT "${DEPV.URL}" STREQUAL "")
        cmate_msg("checking url ${DEP.URL}")
        cmate_deps_get_url(DEP)
    else()
        cmate_die("invalid dependency: $DEP")
    endif()

    cmate_deps_copy_dep(${DEPV} DEP PARENT_SCOPE)
endfunction()
###############################################################################
#
# Content of cmate/tmpl.cmake
#
###############################################################################
function(cmate_tmpl_process_includes FROM VAR)
    cmate_split_lines("${FROM}" LINES)
    set(CONTENT "")

    foreach(LINE ${LINES})
        if(LINE MATCHES "^%%include <(.+)>$")
            set(INC "${CMAKE_MATCH_1}")
            cmate_tmpl_load("${INC}" TMPL)
            string(APPEND CONTENT "${TMPL}")
        else()
            string(APPEND CONTENT "${LINE}\n")
        endif()
    endforeach()

    set(${VAR} "${CONTENT}" PARENT_SCOPE)
endfunction()

macro(cmate_tmpl_block_begin)
    if(NOT IN_BLOCK)
        set(IN_BLOCK TRUE)
        string(APPEND TMPL "string(APPEND RESULT [=[\n")
    endif()
endmacro()

macro(cmate_tmpl_block_end)
    if(IN_BLOCK)
        set(IN_BLOCK FALSE)
        string(APPEND TMPL "]=])\n")
    endif()
endmacro()

macro(cmate_tmpl_escape VAR)
    if(${VAR} MATCHES "%@([^@]+)@%")
        string(REPLACE "%@" "__CMATE_AT_BEGIN__" ${VAR} "${${VAR}}")
        string(REPLACE "@%" "__CMATE_AT_END__" ${VAR} "${${VAR}}")
    endif()
endmacro()

macro(cmate_tmpl_unescape VAR)
    string(REPLACE "__CMATE_AT_BEGIN__" "@" ${VAR} "${${VAR}}")
    string(REPLACE "__CMATE_AT_END__" "@" ${VAR} "${${VAR}}")
endmacro()

function(cmate_tmpl_eval FROM TO)
    set(IN_CM_BLOCK FALSE)
    set(IN_BLOCK FALSE)
    set(LINENUM 0)
    set(INLINES "")
    set(TMPL "")
    set(RESULT "")
    set(IT_BEGIN "%{ ")
    set(IT_END " }%")
    string(LENGTH "${IT_BEGIN}" IT_LEN)

    cmate_split_lines("${FROM}" LINES)

    foreach(LINE ${LINES})
        math(EXPR LINENUM "${LINENUM}+1")

        if(LINE MATCHES "^%{CMake}%")
            # Verbatim CMake block begin
            if(IN_CM_BLOCK)
                cmate_die("line ${LINENUM}: unclosed previous block")
            else()
                set(IN_CM_BLOCK TRUE)
            endif()

            continue()
        elseif(LINE MATCHES "^%{/CMake}%")
            # Verbatim CMake block begin
            if(NOT IN_CM_BLOCK)
                cmate_die("line ${LINENUM}: no previous opened block")
            else()
                set(IN_CM_BLOCK FALSE)
            endif()

            continue()
        elseif(IN_CM_BLOCK)
            string(APPEND TMPL "${LINE}\n")
            continue()
        elseif(LINE MATCHES "^%[ \t]*$")
            # Skip empty lines
            continue()
        elseif(LINE MATCHES "^%%")
            # Skip template comment lines
            continue()
        elseif(LINE MATCHES "^%#(.*)$")
            # Generate a CMake comment line
            cmate_tmpl_block_end()
            string(APPEND TMPL "string(APPEND RESULT \"#${CMAKE_MATCH_1}\\n\")\n")
            continue()
        elseif(NOT LINE MATCHES "^%[ \t]+")
            if(LINE MATCHES "%{[ ]+[^ ]+[ ]+}%")
                cmate_tmpl_block_end()

                while(LINE MATCHES "%{[ ]+[^ ]+[ ]+}%")
                    # Pure CMake joy :D
                    string(FIND "${LINE}" "${IT_BEGIN}" BEGIN)
                    string(FIND "${LINE}" "${IT_END}" END)
                    math(EXPR INLINE_BEGIN "${BEGIN}+${IT_LEN}")
                    math(EXPR INLINE_LEN "${END}-${BEGIN}-${IT_LEN}")
                    string(SUBSTRING "${LINE}" 0 ${BEGIN} BEFORE)
                    string(SUBSTRING "${LINE}" ${INLINE_BEGIN} ${INLINE_LEN} INLINE)
                    string(APPEND TMPL "string(APPEND RESULT [=[${BEFORE}]=])\n")
                    string(APPEND TMPL "string(APPEND RESULT \"${INLINE}\")\n")
                    math(EXPR RPOS "${END}+${IT_LEN}")
                    string(SUBSTRING "${LINE}" ${RPOS} -1 LINE)
                endwhile()
            elseif(LINE MATCHES "%@([^@]+)@%")
                cmate_tmpl_block_end()
                cmate_tmpl_escape(LINE)
            endif()

            cmate_tmpl_block_begin()
        else()
            cmate_tmpl_block_end()
            string(REGEX REPLACE "^% " "" LINE "${LINE}")
        endif()

        string(APPEND TMPL "${LINE}\n")
    endforeach()

    cmate_tmpl_block_end()
    cmake_language(EVAL CODE "${TMPL}")

    string(CONFIGURE "${RESULT}" RESULT @ONLY)

    cmate_tmpl_unescape(RESULT)

    set(${TO} "${RESULT}" PARENT_SCOPE)
endfunction()

function(cmate_tmpl_load FILE_OR_VAR VAR)
    set(TFILE "${CMATE_TMPL_DIR}/${FILE_OR_VAR}")
    string(TOUPPER "CMATE_${FILE_OR_VAR}" TVAR)
    string(REGEX REPLACE "[-/\\.]" "_" TVAR "${TVAR}")
    set(CONTENT "")

    if(NOT "${${TVAR}}" STREQUAL "")
        # In amalgamate mode, template is stored in a variable
        set(CONTENT "${${TVAR}}")
    elseif(EXISTS "${TFILE}")
        # In dev/filesystem mode, template is in a file
        file(STRINGS "${TFILE}" LINES)
        list(FILTER LINES EXCLUDE REGEX "^# -[*]-")
        list(JOIN LINES "\n" CONTENT)
    else()
        cmate_die("no template content for '${FILE_OR_VAR}'")
    endif()

    cmate_tmpl_process_includes("${CONTENT}" CONTENT)

    set(${VAR} "${CONTENT}" PARENT_SCOPE)
endfunction()

function(cmate_tmpl_process)
    set(OPTS APPEND)
    set(SINGLE FROM TO_FILE TO_VAR PRE)
    set(MULTI "")
    cmake_parse_arguments(TMPL "${OPTS}" "${SINGLE}" "${MULTI}" ${ARGN})

    if(NOT TMPL_FROM)
        cmate_die("missing template")
    endif()

    # TODO: handle conflicting FILE/VAR
    if(NOT TMPL_TO_FILE AND NOT TMPL_TO_VAR)
        # No output specified, assume file derived from TMPL_FROM
        get_filename_component(TMPL_TO_FILE "${TMPL_FROM}" NAME)
    endif()

    cmate_tmpl_load("${TMPL_FROM}" TMPL)
    cmate_tmpl_eval("${TMPL}" CONTENT)

    if(TMPL_TO_FILE)
        if(TMPL_APPEND)
            set(FILE_MODE "APPEND")
        else()
            set(FILE_MODE "WRITE")
        endif()

        file(${FILE_MODE} "${TMPL_TO_FILE}" "${CONTENT}")
        cmate_msg("wrote ${TMPL_TO_FILE}")
    elseif(TMPL_TO_VAR)
        if(TMPL_APPEND)
            set(VALUE "${TMPL_TO_VAR}")
        else()
            set(VALUE "")
        endif()

        string(APPEND VALUE "${CONTENT}")

        set(${TMPL_TO_VAR} "${VALUE}" PARENT_SCOPE)
    else()
        cmate_die("missing template destination")
    endif()
endfunction()
###############################################################################
#
# Content of cmate/commands/config.cmake
#
###############################################################################
list(APPEND CMATE_CMDS "config")
set(CMATE_CONFIG_SHORT_HELP "Local project configuration")
set(
    CMATE_CONFIG_HELP
    "
Usage: cmate config [COMMAND]

${CMATE_CONFIG_SHORT_HELP}

Commands:
  show       Dumps config as JSON"
)

function(cmate_config_show)
    cmate_msg(${CMATE_CONF})
endfunction()

function(cmate_config)
    if(CMATE_ARGC LESS 1)
        cmate_die("missing config command")
    endif()

    list(GET CMATE_ARGS 0 CMD)

    set(CMATE_CONFIG_COMMAND "cmate_config_${CMD}")

    if(COMMAND "${CMATE_CONFIG_COMMAND}")
        cmake_language(CALL ${CMATE_CONFIG_COMMAND})
    else()
        cmate_msg("unknown command: ${CMATE_CONFIG_COMMAND}")
    endif()
endfunction()
###############################################################################
#
# Content of cmate/commands/new.cmake
#
###############################################################################
set(CMATE_NEW_ALLOW_NO_CONF 1)
set(CMATE_NEW_DEFAULT_VERSION "0.1.0")
set(CMATE_NEW_DEFAULT_STD "20")

list(APPEND CMATE_CMDS "new")
list(
    APPEND
    CMATE_NEW_OPTIONS
    "name"
    "version"
    "namespace"
    "std"
)
set(CMATE_NEW_SHORT_HELP "Create new local project")
set(
    CMATE_NEW_HELP
    "
Usage: cmate new [OPTIONS]

${CMATE_NEW_SHORT_HELP}

Options:
  --name=NAME            Project name
  --version=SEMVER       Project version (default: ${CMATE_NEW_DEFAULT_VERSION})
  --namespace=NAMESPACE  Project C++ namespace (default: project name)
  --std=STD              Project C++ standard (default: ${CMATE_NEW_DEFAULT_STD})"
)

function(cmate_new_set_conf)
    set(OPTS "")
    set(SINGLE VAR KEY DEFAULT)
    set(MULTI "")
    cmake_parse_arguments(NEW "${OPTS}" "${SINGLE}" "${MULTI}" ${ARGN})

    if(${NEW_VAR})
        set(VAL "${${NEW_VAR}}")
    else()
        set(VAL "${NEW_DEFAULT}")
    endif()

    cmate_conf_set_str("${NEW_KEY}" "${VAL}")
endfunction()

function(cmate_new)
    if(NOT CMATE_NEW_NAME)
        cmate_die("missing project name")
    else()
        cmate_conf_set_str("name" "${CMATE_NEW_NAME}")
    endif()

    cmate_new_set_conf(KEY "version" VAR CMATE_NEW_VERSION DEFAULT "${CMATE_NEW_DEFAULT_VERSION}")
    cmate_new_set_conf(KEY "namespace" VAR CMATE_NEW_NAMESPACE DEFAULT "${CMATE_NEW_NAME}")
    cmate_new_set_conf(KEY "std" VAR CMATE_NEW_STD DEFAULT "${CMATE_NEW_DEFAULT_STD}")

    cmate_save_conf("${CMATE_PRJFILE}")
endfunction()
###############################################################################
#
# Content of cmate/commands/configure.cmake
#
###############################################################################
list(APPEND CMATE_CMDS "configure")
list(
    APPEND
    CMATE_CONFIGURE_OPTIONS
    "generate-only"
    "no-tests"
    "toolchain"
    "namespace"
    "version"
    "version-file"
    "source-pat"
    "header-pat"
)
set(CMATE_CONFIGURE_SHORT_HELP "Configure local project")
set(
    CMATE_CONFIGURE_HELP
    "
Usage: cmate configure [OPTIONS]

${CMATE_CONFIGURE_SHORT_HELP}

Options:
  --generate-only        Don't run CMake
  --no-tests             Don't build tests
  --toolchain=FILE       CMake toolchain file
  --version=SEMVER       CMake package version
  --version-file=FILE    CMake package version from FILE
  --version-file=FILE    CMake package version from FILE
  --source-pat=PATTERN   CMate targets source file glob pattern
                         (default: \$CACHE{CMATE_SOURCE_PAT})
  --header-pat=PATTERN   CMate targets header file glob pattern
                         (default: \$CACHE{CMATE_HEADER_PAT})"
)

cmate_setg(CMATE_CONFIGURE_GENERATE_ONLY 0)

function(cmate_configure_lib NAME TARGET SRC_BASE)
    if(${CMATE_DRY_RUN})
        cmate_msg("found library ${NAME}")
        return()
    endif()

    set(SDIR "${CMATE_ROOT_DIR}/${SRC_BASE}/${NAME}")
    set(CM_FILE "${SDIR}/CMakeLists.txt")
    set(LINK_FILE "${SDIR}/${CMATE_LINKFILE}")

    # Set target template variables
    set(T.NAME "${NAME}")
    set(T.TNAME "${TARGET}")
    string(TOUPPER ${TARGET} T.UTNAME)

    cmate_load_link_deps(${LINK_FILE} TARGET)
    cmate_tmpl_process(
        FROM "targets/lib/CMakeLists.txt.in"
        TO_VAR CONTENT
    )

    if(${CMATE_DUMP})
        message(${DEPS})
        message(${CONTENT})
    endif()

    file(WRITE ${CM_FILE} ${CONTENT})
endfunction()

function(cmate_configure_prog TYPE NAME TARGET SRC_BASE)
    string(TOUPPER ${TBASE} VBASE)

    if(${CMATE_DRY_RUN})
        cmate_msg("found ${TYPE} ${NAME} (${SRC_BASE}/${NAME})")
        return()
    endif()

    set(SDIR "${CMATE_ROOT_DIR}/${SRC_BASE}/${NAME}")
    set(CM_FILE "${SDIR}/CMakeLists.txt")
    set(LINK_FILE "${SDIR}/${CMATE_LINKFILE}")

    # Set target template variables
    set(T.NAME "${NAME}")
    set(T.TNAME "${TARGET}")

    if(${TYPE} STREQUAL "bin")
        set(T.TTYPE "executable")
    elseif(${TYPE} STREQUAL "test")
        set(T.TTYPE "test")
    else()
        cmate_die("invalid program type: ${TYPE}")
    endif()

    string(TOUPPER ${TARGET} T.UTNAME)

    cmate_load_link_deps(${LINK_FILE} TARGET)
    cmate_tmpl_process(
        FROM "targets/${TYPE}/CMakeLists.txt.in"
        TO_VAR CONTENT
    )

    if(${CMATE_DUMP})
        message(${DEPS})
        message(${CONTENT})
    endif()

    file(WRITE ${CM_FILE} ${CONTENT})
endfunction()

function(cmate_configure_bin NAME TBASE SRC_BASE)
    cmate_configure_prog("bin" ${NAME} ${TBASE} ${SRC_BASE})
endfunction()

function(cmate_configure_test NAME TBASE SRC_BASE)
    cmate_configure_prog("test" ${NAME} ${TBASE} ${SRC_BASE})
endfunction()

function(cmate_configure_cmake_package PKGDESC VAR)
    set(COMPS "")
    string(JSON T ERROR_VARIABLE ERR TYPE ${PKGDESC})

    if(T STREQUAL "OBJECT")
        string(JSON PKG MEMBER ${PKGDESC} 0)
        cmate_json_get_array(${PKGDESC} ${PKG} COMPS)
    else()
        set(PKG "${PKGDESC}")
    endif()

    set("${VAR}.PKG" ${PKG} PARENT_SCOPE)
    set("${VAR}.COMPS" ${COMPS} PARENT_SCOPE)

    list(LENGTH COMPS COMP_COUNT)
    set("${VAR}.COMP_COUNT" ${COMP_COUNT} PARENT_SCOPE)
endfunction()

function(cmate_configure_project_cmake_packages VAR)
    cmate_conf_get("packages.cmake" PKGS)

    list(LENGTH PKGS COUNT)
    set(PKGNAMES "")

    foreach(PKG ${PKGS})
        cmate_configure_cmake_package(${PKG} PC)
        list(APPEND PKGNAMES "${PC.PKG}")
        set("${VAR}.PKGS.${PC.PKG}.COMPS" "${PC.COMPS}" PARENT_SCOPE)
        set("${VAR}.PKGS.${PC.PKG}.COMP_COUNT" "${PC.COMP_COUNT}" PARENT_SCOPE)
    endforeach()

    set("${VAR}.PKGS" ${PKGNAMES} PARENT_SCOPE)

    list(LENGTH PKGNAMES PKG_COUNT)
    set("${VAR}.PKG_COUNT" ${PKG_COUNT} PARENT_SCOPE)
endfunction()

function(cmate_configure_project_pkgconfig_packages VAR)
    cmate_conf_get("packages.pkgconfig" PKGS)

    list(LENGTH PKGNAMES COUNT)
    set("${VAR}.PKGS" ${PKGNAMES} PARENT_SCOPE)

    list(LENGTH PKGNAMES PKG_COUNT)
    set("${VAR}.PKG_COUNT" ${PKG_COUNT} PARENT_SCOPE)
endfunction()

macro(cmate_configure_project_set_deps)
    # Prepare dependencies sources
    cmate_conf_get("deps" DEPS)

    foreach(SPEC ${DEPS})
        cmate_deps_make_dep(${SPEC} DEP)
        list(APPEND "P.DEPS" ${DEP.NAME})
        cmate_deps_copy_dep("P.DEPS.${DEP.NAME}" DEP)
    endforeach()

    # Prepare CMake/PkgConfig dependencies names/structure
    foreach(PLIST "cmake;CM" "pkgconfig;PC")
        list(GET PLIST 0 PTYPE)
        list(GET PLIST 1 PVAR)
        cmake_language(
            CALL "cmate_configure_project_${PTYPE}_packages"
            "P.${PVAR}"
        )
    endforeach()
endmacro()

macro(cmate_configure_project_set_targets)
    # Libraries and binaries
    set(P.TARGETS.BIN "")
    set(P.TARGETS.LIB "")

    if(CMATE_BINS OR CMATE_LIBS)
        foreach(TYPE "LIB" "BIN")
            foreach(T ${CMATE_${TYPE}S})
                cmate_target_name(${T} ${TYPE} TNAME)
                list(APPEND P.TARGETS.${TYPE} ${TNAME})

                set(TDIR "src/${TYPE}/${T}")
                string(TOLOWER "${TDIR}" TDIR)

                set("P.TARGETS.${TYPE}.${TNAME}.SUBDIR" "${TDIR}")
                set("P.TARGETS.${TYPE}.${TNAME}.NAME" "${T}")
            endforeach()
        endforeach()
    else()
        cmate_die("no targets to configure")
    endif()

    list(APPEND P.TARGETS.INSTALL "${P.TARGETS.LIB}" "${P.TARGETS.BIN}")

    # Tests
    set(P.TARGETS.TEST "")

    if(CMATE_TESTS)
        set(TYPE "TEST")

        foreach(T ${CMATE_TESTS})
            cmate_target_name(${T} ${TYPE} TNAME)
            list(APPEND P.TARGETS.TEST ${TNAME})

            set(TDIR "src/${TYPE}/${T}")
            string(TOLOWER "${TDIR}" TDIR)

            set("P.TARGETS.${TYPE}.${TNAME}.SUBDIR" "${TDIR}")
        endforeach()
    endif()
endmacro()

function(cmate_configure_project_cmake_files)
    cmate_tmpl_process(
        FROM "cmake/config.cmake.in"
        TO_FILE "${CMATE_ROOT_DIR}/cmake/${P.NAME}-config.cmake.in"
    )
endfunction()

function(cmate_configure_project)
    if(${CMATE_DRY_RUN})
        return()
    endif()

    set(CM_FILE "${CMATE_ROOT_DIR}/CMakeLists.txt")
    set(CMATE_CMAKE_VER 3.12)

    cmate_configure_project_set_deps()
    cmate_configure_project_set_targets()

    # Auxiliary CMake files
    cmate_configure_project_cmake_files()

    cmate_tmpl_process(
        FROM "project/CMakeLists.txt.in"
        TO_FILE ${CM_FILE}
    )
endfunction()

function(cmate_configure_load_targets PREFIX)
    set(JSON "{}")
    set(TARGETS "")

    if(EXISTS ${CMATE_TARGETS_FILE})
        file(READ ${CMATE_TARGETS_FILE} JSON)
    endif()

    foreach(TYPE "LIB" "BIN" "TEST")
        string(TOLOWER "${TYPE}S" KEY)
        cmate_json_get_array(${JSON} ${KEY} LST)

        foreach(T ${LST})
            cmate_target_name(${T} ${TYPE} "TNAME")
            list(APPEND TARGETS "${TNAME}")
        endforeach()

        set("${PREFIX}_${TYPE}S" "${LST}" PARENT_SCOPE)
    endforeach()

    set("${PREFIX}_TARGETS" "${TARGETS}" PARENT_SCOPE)
endfunction()

function(cmate_configure_save_targets)
    set(JSON "{}")

    foreach(LST "LIBS" "BINS" "TESTS")
        string(TOLOWER "${LST}" KEY)
        cmate_json_set_array(JSON ${JSON} ${KEY} "${CMATE_${LST}}")
    endforeach()

    file(WRITE "${CMATE_TARGETS_FILE}" ${JSON})
endfunction()

function(cmate_configure_find_targets)
    file(GLOB LIB_INC_DIRS "${CMATE_ROOT_DIR}/include/*")
    set(TARGETS "")
    set(LIBS "")
    set(BINS "")
    set(TESTS "")

    # Libraries
    foreach(LIB_INC_DIR ${LIB_INC_DIRS})
        string(REPLACE "${CMATE_ROOT_DIR}/include/" "" NAME ${LIB_INC_DIR})
        cmate_target_name(${NAME} "lib" "TNAME")
        list(APPEND TARGETS ${TNAME})
        list(APPEND LIBS ${NAME})
    endforeach()

    # Binaries and tests
    foreach(TYPE bin test)
        file(GLOB SRC_DIRS "${CMATE_ROOT_DIR}/src/${TYPE}/*")
        set(TVAR "${TYPE}s")
        string(TOUPPER ${TVAR} TVAR)

        foreach(SRC_DIR ${SRC_DIRS})
            string(REPLACE "${CMATE_ROOT_DIR}/src/${TYPE}/" "" NAME ${SRC_DIR})
            cmate_target_name(${NAME} ${TYPE} "TNAME")
            list(APPEND TARGETS ${TNAME})
            list(APPEND ${TVAR} ${NAME})
        endforeach()
    endforeach()

    foreach(LST "TARGETS" "LIBS" "BINS" "TESTS")
        list(SORT ${LST})
        set(LVAR "CMATE_${LST}")
        cmate_setg(${LVAR} "${${LST}}")
    endforeach()
endfunction()

function(cmate_configure_clean)
    foreach(TYPE "BIN" "LIB" "TEST")
        if(NOT CMATE_${TYPE}S)
            continue()
        endif()

        foreach(T ${CMATE_${TYPE}S})
            set(TDIR "${CMATE_ROOT_DIR}/src/${TYPE}/${T}")
            string(TOLOWER "${TDIR}" TDIR)
            file(REMOVE "${TDIR}/CMakeLists.txt")
        endforeach()
    endforeach()

    file(REMOVE "${CMATE_ROOT_DIR}/CMakeLists.txt")
endfunction()

function(cmate_configure_needed VAR LIBS BINS TESTS)
    set(RES FALSE)

    foreach(LST "LIBS" "BINS" "TESTS")
        set(REFL ${CMATE_${LST}})
        list(SORT REFL)
        list(JOIN REFL "_" REFS)
        set(L ${${LST}})
        list(SORT L)
        list(JOIN L "_" S)

        if(NOT "${S}" STREQUAL "${REFS}")
            set(RES TRUE)
            break()
        endif()
    endforeach()

    set(${VAR} ${RES} PARENT_SCOPE)
endfunction()

function(cmate_configure_libraries)
    foreach(NAME ${CMATE_LIBS})
        cmate_target_name(${NAME} "lib" "TNAME")
        cmate_configure_lib(${NAME} ${TNAME} "src/lib")
    endforeach()
endfunction()

function(cmate_configure_binaries)
    foreach(TYPE "bin" "test")
        string(TOUPPER "CMATE_${TYPE}S" LNAME)

        if(NOT ${LNAME})
            continue()
        endif()

        foreach(NAME ${${LNAME}})
            cmate_target_name(${NAME} ${TYPE} "TNAME")
            cmake_language(
                CALL "cmate_configure_${TYPE}"
                ${NAME} ${TNAME} "src/${TYPE}"
            )
        endforeach()
    endforeach()
endfunction()

function(cmate_configure_cmake_files)
endfunction()

function(cmate_configure_generate)
    # Set CMate global template variables
    set(CM.HPAT "${CMATE_HEADER_PAT}")
    set(CM.SPAT "${CMATE_SOURCE_PAT}")

    # Set project level template variables
    set(P.NAME "${CMATE_PROJECT.name}")
    string(TOUPPER "${CMATE_PROJECT.name}" P.UNAME)
    set(P.VER "${CMATE_PROJECT.version}")
    set(P.VER_MAJOR "${CMATE_PROJECT.version_major}")
    set(P.VER_MINOR "${CMATE_PROJECT.version_minor}")
    set(P.VER_PATCH "${CMATE_PROJECT.version_patch}")
    set(P.NS "${CMATE_PROJECT.namespace}")
    set(P.STD "${CMATE_PROJECT.std}")

    # Targets
    cmate_configure_libraries()
    cmate_configure_binaries()

    # Top-level project
    cmate_configure_project()
endfunction()

function(cmate_configure_cmake_common_args VAR)
    set(ARGS "")

    if (EXISTS "${CMATE_ENV_DIR}")
        list(APPEND ARGS "-DCMAKE_PREFIX_PATH=${CMATE_ENV_DIR}")
    endif()

    list(APPEND ARGS "-DCMAKE_INSTALL_PREFIX=${CMATE_ROOT_DIR}/stage")

    find_program(CMATE_CCACHE ccache)

    if(CMATE_CCACHE)
        list(APPEND ARGS "-DCMAKE_C_COMPILER_LAUNCHER=${CMATE_CCACHE}")
        list(APPEND ARGS "-DCMAKE_CXX_COMPILER_LAUNCHER=${CMATE_CCACHE}")
    endif()

    cmate_project_varname("BUILD_TESTS" BUILD_TESTS)

    if(CMATE_CONFIGURE_NO_TESTS)
        list(APPEND ARGS "-D${BUILD_TESTS}=OFF")
        list(APPEND ARGS "-DBUILD_TESTING=OFF")
    else()
        list(APPEND ARGS "-D${BUILD_TESTS}=ON")
    endif()

    set(${VAR} ${ARGS} PARENT_SCOPE)
endfunction()

function(cmate_configure_run_cmake_multi)
    cmate_configure_cmake_common_args(ARGS)

    cmate_join_escape_list(CMATE_BUILD_TYPES TYPES)

    list(APPEND ARGS "-DCMAKE_CONFIGURATION_TYPES=${TYPES}")
    list(APPEND ARGS "-S" "${CMATE_ROOT_DIR}")
    list(APPEND ARGS "-B" "${CMATE_BUILD_DIR}")

    if(CMATE_NINJA)
        list(APPEND ARGS "-G" "Ninja Multi-Config")
    endif()

    cmate_run_prog(CMD ${CMAKE_COMMAND} ${ARGS})
endfunction()

function(cmate_configure_run_cmake TYPE)
    cmate_configure_cmake_common_args(ARGS)

    list(APPEND ARGS "-DCMAKE_BUILD_TYPE=${TYPE}")
    list(APPEND ARGS "-S" "${CMATE_ROOT_DIR}")
    list(APPEND ARGS "-B" "${CMATE_BUILD_DIR}/${TYPE}")

    if(CMATE_TOOLCHAIN)
        list(APPEND ARGS "--toolchain" "${CMATE_TOOLCHAIN}")
    endif()

    cmate_run_prog(CMD ${CMAKE_COMMAND} ${ARGS})
endfunction()

function(cmate_configure)
    cmate_configure_find_targets()
    cmate_configure_load_targets(PREV)
    cmate_configure_needed(
        NEEDED
        "${PREV_LIBS}" "${PREV_BINS}" "${PREV_TESTS}"
    )

    if(NOT NEEDED)
        return()
    endif()

    cmate_configure_generate()

    cmate_setg(CMATE_BUILD_TYPES "Debug;Release")

    if(NOT CMATE_CONFIGURE_GENERATE_ONLY)
        cmate_check_ninja()

        if(CMATE_NINJA OR WIN32)
            cmate_configure_run_cmake_multi()
        else()
            foreach(TYPE ${CMATE_BUILD_TYPES})
                cmate_configure_run_cmake(${TYPE})
            endforeach()
        endif()
    endif()

    cmate_configure_save_targets()
endfunction()
###############################################################################
#
# Content of cmate/commands/reconfigure.cmake
#
###############################################################################
list(APPEND CMATE_CMDS "reconfigure")
set(CMATE_RECONFIGURE_SHORT_HELP "Clean + configure")
set(
    CMATE_RECONFIGURE_HELP
    "
Usage: cmate reconfigure

${CMATE_RECONFIGURE_SHORT_HELP}"
)

function(cmate_reconfigure)
    cmate_clean()
    cmate_configure()
endfunction()
###############################################################################
#
# Content of cmate/commands/build.cmake
#
###############################################################################
list(APPEND CMATE_CMDS "build")
set(CMATE_BUILD_SHORT_HELP "Build local project")
set(
    CMATE_BUILD_HELP
    "
Usage: cmate build [OPTIONS]

${CMATE_BUILD_SHORT_HELP}

Options:
  --debug         Build in debug mode (default)
  --release       Build in release mode"
)

function(cmate_build)
    cmate_configure()
    cmate_unsetg(CMATE_BUILD_TYPES)

    cmate_set_build_types(
        CMATE_BUILD_DEBUG
        CMATE_BUILD_RELEASE
        "Debug"
    )

    foreach(TYPE ${CMATE_BUILD_TYPES})
        set(ARGS "")

        if (IS_DIRECTORY "${CMATE_BUILD_DIR}/${TYPE}")
            list(APPEND ARGS "--build" "${CMATE_BUILD_DIR}/${TYPE}")
        else()
            list(APPEND ARGS "--build" "${CMATE_BUILD_DIR}")
            list(APPEND ARGS "--config" "${TYPE}")
        endif()

        list(APPEND ARGS "--parallel")

        cmate_run_prog(CMD ${CMAKE_COMMAND} ${ARGS})
    endforeach()
endfunction()
###############################################################################
#
# Content of cmate/commands/rebuild.cmake
#
###############################################################################
list(APPEND CMATE_CMDS "rebuild")
set(CMATE_REBUILD_SHORT_HELP "Reconfigure + build")
set(
    CMATE_REBUILD_HELP
    "
Usage: cmate rebuild

${CMATE_REBUILD_SHORT_HELP}"
)

function(cmate_rebuild)
    cmate_reconfigure()
    cmate_build()
endfunction()
###############################################################################
#
# Content of cmate/commands/stage.cmake
#
###############################################################################
list(APPEND CMATE_CMDS "stage")
set(CMATE_STAGE_SHORT_HELP "Stage local project")
set(
    CMATE_STAGE_HELP
    "
Usage: cmate stage

${CMATE_STAGE_SHORT_HELP}

Options:
  --debug         Stage debug build
  --release       Stage release build"
)

function(cmate_stage)
    cmate_set_build_types(
        CMATE_STAGE_DEBUG
        CMATE_STAGE_RELEASE
        "Debug"
    )

    cmate_build()

    foreach(TYPE ${CMATE_BUILD_TYPES})
        set(ARGS "")

        if (IS_DIRECTORY "${CMATE_BUILD_DIR}/${TYPE}")
            list(APPEND ARGS "--install" "${CMATE_BUILD_DIR}/${TYPE}")
        else()
            list(APPEND ARGS "--install" "${CMATE_BUILD_DIR}")
            list(APPEND ARGS "--config" "${TYPE}")
        endif()

        cmate_run_prog(CMD ${CMAKE_COMMAND} ${ARGS})
    endforeach()
endfunction()
###############################################################################
#
# Content of cmate/commands/clean.cmake
#
###############################################################################
list(APPEND CMATE_CMDS "clean")
set(CMATE_CLEAN_SHORT_HELP "Clean local project")
set(
    CMATE_CLEAN_HELP
    "
Usage: cmate clean

${CMATE_CLEAN_SHORT_HELP}

Options:
  --purge       Remove everything: cenv, dependencies, ..."
)

function(cmate_clean)
    cmate_configure_find_targets()

    set(DIRS "BUILD" "STAGE" "STATE" "TMP")

    if(${CMATE_CLEAN_PURGE})
        list(APPEND DIRS "ENV" "DEPS")
        cmate_configure_clean()
    endif()

    foreach(DIR ${DIRS})
        set(DVAR "CMATE_${DIR}_DIR")

        if (IS_DIRECTORY ${${DVAR}})
            cmate_msg("cleaning: ${${DVAR}}")
            file(REMOVE_RECURSE ${${DVAR}})
        endif()
    endforeach()
endfunction()
###############################################################################
#
# Content of cmate/commands/add.cmake
#
###############################################################################
list(APPEND CMATE_CMDS "add")
set(CMATE_ADD_SHORT_HELP "Add a dependency to project.yaml")
set(
    CMATE_ADD_HELP
    "
Usage: cmate add URL

${CMATE_ADD_SHORT_HELP}"
)

function(cmate_add)
    cmate_conf_get("deps" DEPS)

    if(CMATE_ARGC EQUAL 0)
        cmate_die("missing URL")
    endif()

    list(GET CMATE_ARGS 0 URL)
    cmate_deps_parse(${URL} DEP)
    cmate_deps_dump_dep(DEP)
    # cmate_deps_get_dep(DEP)
endfunction()
###############################################################################
#
# Content of cmate/commands/install.cmake
#
###############################################################################
list(APPEND CMATE_CMDS "install")
set(CMATE_INSTALL_SHORT_HELP "Install dependencies listed in project.yaml")
set(
    CMATE_INSTALL_HELP
    "
Usage: cmate install

${CMATE_INSTALL_SHORT_HELP}"
)

function(cmate_install)
    cmate_conf_get("deps" DEPS)

    foreach(SPEC ${DEPS})
        cmate_deps_make_dep(${SPEC} DEP)
        cmate_deps_get_dep(DEP)
        cmate_deps_install_dep(DEP)
    endforeach()
endfunction()
###############################################################################
#
# Content of cmate/commands/help.cmake
#
###############################################################################
set(CMATE_HELP_HEADER "CMate v${CMATE_VER}")
list(APPEND CMATE_CMDS "help")

list(
    APPEND
    CMATE_OPTIONS
    "verbose"
    "cc"
)

function(cmate_build_help VAR)
    set(
        CMATE_HELP_PRE
    "
Usage: cmate [OPTIONS] COMMAND

Options:
  --verbose    Verbose operation
  --cc=ID      Compiler suite to use (overrides CMATE_CC)
               (e.g.: gcc, clang, gcc-10, clang-16, cl)
  --no-ninja   Don't use Ninja

Commands:
"
    )
    set(
        CMATE_HELP_POST
        "See 'cmate help <command>' to read about a specific subcommand."
    )
    set(LENGTH 0)
    string(APPEND HELP ${CMATE_HELP_PRE})

    foreach(CMD ${CMATE_CMDS})
        string(LENGTH "${CMD}" CL)

        if(CL GREATER ${LENGTH})
            set(LENGTH ${CL})
        endif()
    endforeach()

    foreach(CMD ${CMATE_CMDS})
        string(LENGTH "${CMD}" CL)
        math(EXPR PAD "${LENGTH}-${CL}")
        string(TOUPPER ${CMD} UCMD)
        set(CHVAR "CMATE_${UCMD}_SHORT_HELP")

        string(REPEAT " " ${PAD} CPAD)
        string(APPEND HELP "  ${CMD}${CPAD}    ${${CHVAR}}\n")
    endforeach()

    string(APPEND HELP "\n${CMATE_HELP_POST}")
    set(${VAR} ${HELP} PARENT_SCOPE)
endfunction()

function(cmate_help)
    set(HVAR "CMATE")
    if(CMATE_ARGC GREATER 0)
        # Sub command help
        list(GET CMATE_ARGS 0 HCMD)

        if(${HCMD} IN_LIST CMATE_CMDS)
            string(TOUPPER "${HCMD}" HCMD)
            string(APPEND HVAR "_${HCMD}_HELP")
            set(HELP ${${HVAR}})
        else()
            cmate_die("no such command: ${HCMD}")
        endif()
    else()
        # Global help
        cmate_build_help("HELP")
    endif()

    string(CONFIGURE ${HELP} HELP)

    message("${CMATE_HELP_HEADER}")
    message(${HELP})
endfunction()

##############################################################################
#
# Configuration functions
#
##############################################################################
function(cmate_set_defaults)
    cmate_setg(CMATE_CONF "{}")

    set(ME ${CMAKE_CURRENT_LIST_FILE})
    get_filename_component(MYDIR "${ME}" DIRECTORY)
    get_filename_component(MYDIR "${MYDIR}/.." REALPATH)

    cmate_setg(CMATE_TMPL_DIR "${MYDIR}/templates")

    get_filename_component(DIR "." ABSOLUTE)
    cmate_setg(CMATE_ROOT_DIR ${DIR})

    get_filename_component(DIR ".cenv" ABSOLUTE)
    cmate_setg(CMATE_ENV_DIR ${DIR})
    cmate_setgdir(CMATE_ENV_BIN_DIR "${DIR}/bin")
    cmate_setgdir(CMATE_DL_DIR "${CMATE_ENV_DIR}/downloads")

    get_filename_component(DIR ".cmate" ABSOLUTE)
    cmate_setg(CMATE_HOME_DIR ${DIR})
    cmate_setg(CMATE_STATE_DIR "${CMATE_HOME_DIR}/state")
    cmate_setg(CMATE_TOOLCHAINS_DIR "${CMATE_HOME_DIR}/toolchains")

    cmate_setg(CMATE_BUILD_DIR "${CMATE_ROOT_DIR}/build")
    cmate_setg(CMATE_TARGETS_FILE "${CMATE_BUILD_DIR}/cmate-targets.json")

    cmate_setg(CMATE_STAGE_DIR "${CMATE_ROOT_DIR}/stage")

    cmate_setg(CMATE_TMP_DIR "${CMATE_HOME_DIR}/tmp")

    cmate_setg(CMATE_HEADER_PAT "*.hpp")
    cmate_setg(CMATE_SOURCE_PAT "*.[ch]pp")

    cmate_setg(CMATE_EMPTY_LINE_MARKER "@CMATE_EMPTY_LINE@")
endfunction()

function(cmate_set_compilers)
    set(CC "$ENV{CMATE_CC}")

    if(CMATE_CC)
        set(CC "${CMATE_CC}")
    endif()

    if(CC)
        if(${CC} MATCHES "^gcc(.*)$")
            set(CXX "g++${CMAKE_MATCH_1}")
        elseif(${CC} MATCHES "^clang(.*)$")
            set(CXX "clang++${CMAKE_MATCH_1}")
        else()
            set(CXX "${CC}")
        endif()

        cmate_msg("using compilers CC=${CC} CXX=${CXX}")
        cmate_setg(CMAKE_C_COMPILER "${CC}")
        cmate_setg(CMAKE_CXX_COMPILER "${CXX}")
        set(ENV{CC} "${CC}")
        set(ENV{CXX} "${CXX}")
    endif()
endfunction()

##############################################################################
#
# Command processing
#
##############################################################################
function(cmate_process_cmd)
    if (CMATE_CMD STREQUAL "version")
        message(${CMATE_VERSION})
    elseif (CMATE_CMD)
        set(CMATE_COMMAND "cmate_${CMATE_CMD}")

        if(COMMAND "${CMATE_COMMAND}")
            cmake_language(CALL ${CMATE_COMMAND})
        else()
            cmate_msg("unknown command: ${CMATE_CMD}")
        endif()
    else()
        cmate_msg("no command")
    endif()
endfunction()


###############################################################################
#
# Template PROJECT_OPTIONS_TXT_IN
#
###############################################################################
set(
    CMATE_PROJECT_OPTIONS_TXT_IN
    [=[

%###
%#
%# Options
%#
%###
if(${@P.UNAME@_MAIN_PROJECT})
    set(@P.UNAME@_BUILD_TESTS_INIT ON)
    set(@P.UNAME@_EXPORT_COMPILE_INIT ON)
else()
    set(@P.UNAME@_BUILD_TESTS_INIT OFF)
    set(@P.UNAME@_EXPORT_COMPILE_INIT OFF)
endif()

option(
    @P.UNAME@_BUILD_TESTS
    "Build the unit tests."
    ${@P.UNAME@_BUILD_TESTS_INIT}
)

option(
    @P.UNAME@_EXPORT_COMPILE
    "Export compile commands."
    ${@P.UNAME@_EXPORT_COMPILE_INIT}
)

option(
    @P.UNAME@_FETCH_DEPS
    "Fetch dependencies via FetchContent."
    ${@P.UNAME@_FETCH_DEPS}
)
]=])

###############################################################################
#
# Template PROJECT_INSTALL_TXT_IN
#
###############################################################################
set(
    CMATE_PROJECT_INSTALL_TXT_IN
    [=[

%###
%#
%# Installation support
%#
%###
include(CMakePackageConfigHelpers)

configure_package_config_file(
  "cmake/@P.NAME@-config.cmake.in"
  "${PROJECT_BINARY_DIR}/@P.NAME@-config.cmake"
  INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/@P.NAME@"
  PATH_VARS CMAKE_INSTALL_INCLUDEDIR CMAKE_INSTALL_LIBDIR
)

write_basic_package_version_file(
  "${PROJECT_BINARY_DIR}/@P.NAME@-config-version.cmake"
  COMPATIBILITY AnyNewerVersion
)

install(
    TARGETS
% foreach(TARGET ${P.TARGETS.INSTALL})
        %{ ${TARGET} }%
% endforeach()
    EXPORT @P.NAME@-targets
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

install(
    EXPORT @P.NAME@-targets
    FILE @P.NAME@-targets.cmake
    NAMESPACE @P.NS@::
    DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/@P.NAME@"
)

install(
    FILES
        "${PROJECT_BINARY_DIR}/@P.NAME@-config.cmake"
        "${PROJECT_BINARY_DIR}/@P.NAME@-config-version.cmake"
    DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/@P.NAME@"
)

% foreach(TARGET ${P.TARGETS.LIB})

install(
    DIRECTORY "${PROJECT_SOURCE_DIR}/include/%{ ${P.TARGETS.LIB.${TARGET}.NAME} }%/"
    DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/@P.NS@
)
% endforeach()
]=])

###############################################################################
#
# Template PROJECT_CONFIGURATION_TXT_IN
#
###############################################################################
set(
    CMATE_PROJECT_CONFIGURATION_TXT_IN
    [=[

%###
%#
%# Configuration
%#
%###
include(GNUInstallDirs)

if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
    add_compile_definitions(_CRT_SECURE_NO_WARNINGS _SCL_SECURE_NO_WARNINGS)
endif()

if(@P.UNAME@_EXPORT_COMPILE)
    set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
endif()
]=])

###############################################################################
#
# Template PROJECT_PKG_PKGCONFIG_TXT_IN
#
###############################################################################
set(
    CMATE_PROJECT_PKG_PKGCONFIG_TXT_IN
    [=[
% if(${P.PC.PKG_COUNT} GREATER 0)

%###
%#
%# pkg-config dependencies
%#
%###
%     foreach(PKG ${P.PC.PKGS})
pkg_check_modules(%{ ${PKG} }% REQUIRED IMPORTED_TARGET %{ ${PKG} }%)
%     endforeach()
% endif()
]=])

###############################################################################
#
# Template PROJECT_TARGETS_TXT_IN
#
###############################################################################
set(
    CMATE_PROJECT_TARGETS_TXT_IN
    [=[

%###
%#
%# Targets
%#
%###
% foreach(TYPE "LIB" "BIN")
%     foreach(T ${P.TARGETS.${TYPE}})
add_subdirectory(%{ ${P.TARGETS.${TYPE}.${T}.SUBDIR} }%)
%     endforeach()
% endforeach()
%%
% if(${P.TARGETS.TEST})

if(@P.UNAME@_BUILD_TESTS)
    include(CTest)
    enable_testing()
% foreach(T ${P.TARGETS.TEST})
    add_subdirectory(%{ ${P.TARGETS.TEST.${T}.SUBDIR} }%)
% endforeach()
endif()
% endif()
]=])

###############################################################################
#
# Template PROJECT_PKG_CMAKE_TXT_IN
#
###############################################################################
set(
    CMATE_PROJECT_PKG_CMAKE_TXT_IN
    [=[
% if(${P.CM.PKG_COUNT} GREATER 0)

%###
%#
%# CMake dependencies
%#
%###
if(@P.UNAME@_FETCH_DEPS)
    include(FetchContent)
%     foreach(DEP ${P.DEPS})

    FetchContent_Declare(
        %{ ${P.DEPS.${DEP}.NAME} }%
%         if("${P.DEPS.${DEP}.TYPE}" STREQUAL "git")
        GIT_REPOSITORY %{ ${P.DEPS.${DEP}.URL} }%
        GIT_TAG %{ ${P.DEPS.${DEP}.REF} }%
%         elseif("${P.DEPS.${DEP}.TYPE}" STREQUAL "url")
        URL %{ ${P.DEPS.${DEP}.URL} }%
%         endif()
        OVERRIDE_FIND_PACKAGE
%         if(NOT "${P.DEPS.${DEP}.SRCDIR}" STREQUAL "")
        SOURCE_SUBDIR "%{ ${P.DEPS.${DEP}.SRCDIR} }%"
%         endif()
    )
%         if(NOT "${P.DEPS.${DEP}.ARGS}" STREQUAL "")

%             foreach(ARG ${P.DEPS.${DEP}.ARGS})
%                 if (ARG MATCHES "^-D([^=]+)=(([^=]+))$")
    set(%{ ${CMAKE_MATCH_1} }% "%{ ${CMAKE_MATCH_2} }%")
%                 endif()
%             endforeach()

%         endif()
    FetchContent_MakeAvailable(%{ ${P.DEPS.${DEP}.NAME} }%)
%     endforeach()
endif()

%     foreach(PKG ${P.CM.PKGS})
%         if(${P.CM.PKGS.${PKG}.COMP_COUNT} GREATER 0)
find_package(
    %{ ${PKG} }%
    CONFIG REQUIRED
    COMPONENTS
%         foreach(COMP ${P.CM.PKGS.${PKG}.COMPS})
        %{ ${COMP} }%
%         endforeach()
)
%         else()
find_package(%{ ${PKG} }% CONFIG REQUIRED)
%         endif()
%     endforeach()
% endif()
]=])

###############################################################################
#
# Template PROJECT_PROJECT_TXT_IN
#
###############################################################################
set(
    CMATE_PROJECT_PROJECT_TXT_IN
    [=[
%###
%#
%# Project
%# name and version
%#
%###
cmake_minimum_required(VERSION @CMATE_CMAKE_VER@ FATAL_ERROR)

project(@P.NAME@ VERSION @P.VER@ LANGUAGES C CXX)

%###
%#
%# Main project check
%#
%###
set(@P.NAME@_MAIN_PROJECT OFF)

if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
    set(@P.NAME@_MAIN_PROJECT ON)
endif()
]=])

###############################################################################
#
# Template PROJECT_CMAKELISTS_TXT_IN
#
###############################################################################
set(
    CMATE_PROJECT_CMAKELISTS_TXT_IN
    [=[
%%include <project/project.txt.in>
%%
%%include <project/options.txt.in>
%%
%%include <project/configuration.txt.in>
%%
%%include <project/pkg-cmake.txt.in>
%%include <project/pkg-pkgconfig.txt.in>
%%
%%include <project/targets.txt.in>
%%
%%include <project/install.txt.in>
]=])

###############################################################################
#
# Template CMAKE_CONFIG_CMAKE_IN
#
###############################################################################
set(
    CMATE_CMAKE_CONFIG_CMAKE_IN
    [=[
%@PACKAGE_INIT@%
include("${CMAKE_CURRENT_LIST_DIR}/@P.NAME@-targets.cmake")
]=])

###############################################################################
#
# Template TARGETS_LIB_CMAKELISTS_TXT_IN
#
###############################################################################
set(
    CMATE_TARGETS_LIB_CMAKELISTS_TXT_IN
    [=[
add_library(@T.TNAME@)
add_library(@P.NS@::@T.NAME@ ALIAS @T.TNAME@)

set(@T.UTNAME@_INC_DIR "${PROJECT_SOURCE_DIR}/include/@T.NAME@")
file(GLOB_RECURSE @T.UTNAME@_HEADERS "${@T.UTNAME@_INC_DIR}/@CM.HPAT@")
list(APPEND @T.UTNAME@_ALL_SOURCES ${@T.UTNAME@_HEADERS})

set(@T.UTNAME@_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
file(GLOB_RECURSE @T.UTNAME@_SOURCES "${@T.UTNAME@_SRC_DIR}/@CM.SPAT@")
list(APPEND @T.UTNAME@_ALL_SOURCES ${@T.UTNAME@_SOURCES})

target_sources(
    @T.TNAME@
    PRIVATE
        ${@T.UTNAME@_ALL_SOURCES}
)

target_include_directories(
    @T.TNAME@
    PUBLIC
        $<BUILD_INTERFACE:${@T.UTNAME@_INC_DIR}>
        $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/@P.NS@>
    PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}
)
%%
%%include <targets/link.txt.in>

set_target_properties(
    @T.TNAME@
    PROPERTIES
        CXX_STANDARD @P.STD@
        VERSION @P.VER@
        SOVERSION @P.VER_MAJOR@
        EXPORT_NAME @T.NAME@
        OUTPUT_NAME @P.NS@_@T.NAME@
)
]=])

###############################################################################
#
# Template TARGETS_BIN_CMAKELISTS_TXT_IN
#
###############################################################################
set(
    CMATE_TARGETS_BIN_CMAKELISTS_TXT_IN
    [=[
add_%{ ${T.TTYPE} }%(@T.TNAME@)

set(@T.UTNAME@_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
file(GLOB_RECURSE @T.UTNAME@_SOURCES "${@T.UTNAME@_SRC_DIR}/@CM.SPAT@")
list(APPEND @T.UTNAME@_ALL_SOURCES ${@T.UTNAME@_SOURCES})

target_sources(
    @T.TNAME@
    PRIVATE
        ${@T.UTNAME@_ALL_SOURCES}
)

target_include_directories(
    @T.TNAME@
    PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}
)
%%
%%include <targets/link.txt.in>

set_target_properties(
    @T.TNAME@
    PROPERTIES
        CXX_STANDARD @P.STD@
        OUTPUT_NAME @T.NAME@
)
]=])

###############################################################################
#
# Template TARGETS_LINK_TXT_IN
#
###############################################################################
set(
    CMATE_TARGETS_LINK_TXT_IN
    [=[
% if(${TARGET_DEPS_COUNT} GREATER 0)

target_link_libraries(
    @T.TNAME@
%     foreach(TYPE PUBLIC PRIVATE)
%         if(${TARGET_${TYPE}_DEPS_COUNT} GREATER 0)
    %{ ${TYPE} }%
%             foreach(DEP ${TARGET_${TYPE}_DEPS})
        %{ ${DEP} }%
%             endforeach()
%         endif()
%     endforeach()
)
% endif()
]=])

##############################################################################
#
# Main part
#
##############################################################################
if(CMAKE_SCRIPT_MODE_FILE)
    cmate_set_defaults()
    cmate_parse_arguments()
    cmate_set_compilers()
    cmate_load_conf("${CMATE_ROOT_DIR}/${CMATE_PRJFILE}")
    cmate_process_cmd()
endif()
