<commit_before>/*!The Treasure Box Library
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Copyright (C) 2009-present, TBOOX Open Source Group.
 *
 * @author      ruki
 * @path        path.c
 * @ingroup     platform
 *
 */

/* //////////////////////////////////////////////////////////////////////////////////////
 * trace
 */
#define TB_TRACE_MODULE_NAME                "path"
#define TB_TRACE_MODULE_DEBUG               (0)

/* //////////////////////////////////////////////////////////////////////////////////////
 * includes
 */
#include "path.h"
#include "directory.h"
#include "../libc/libc.h"

/* //////////////////////////////////////////////////////////////////////////////////////
 * macros
 */

// the path separator
#if defined(TB_CONFIG_OS_WINDOWS) && !defined(TB_COMPILER_LIKE_UNIX)
#   define TB_PATH_SEPARATOR            '\\'
#else
#   define TB_PATH_SEPARATOR            '/'
#endif

// is path separator?
#if defined(TB_CONFIG_OS_WINDOWS) && !defined(TB_COMPILER_LIKE_UNIX)
#   define tb_path_is_sep(c)      ('/' == (c) || '\\' == (c))
#else
#   define tb_path_is_sep(c)      ('/' == (c))
#endif

/* //////////////////////////////////////////////////////////////////////////////////////
 * implementation
 */
#ifndef TB_CONFIG_MICRO_ENABLE
tb_size_t tb_path_translate(tb_char_t* path, tb_size_t size, tb_size_t maxn, tb_bool_t normalize)
{
    return tb_path_translate_to(path, size, path, maxn, normalize);
}

tb_size_t tb_path_translate_to(tb_char_t const* path, tb_size_t size, tb_char_t* data, tb_size_t maxn, tb_bool_t normalize)
{
    // check
    tb_assert_and_check_return_val(path, 0);

    // file://?
    tb_char_t home[TB_PATH_MAXN];
    tb_char_t const* p = path;
    if (!tb_strnicmp(p, "file:", 5)) p += 5;
    // is user directory?
    else if (path[0] == '~')
    {
        // get the home directory
        tb_size_t home_size = tb_directory_home(home, sizeof(home));
        tb_assert_and_check_return_val(home_size, 0);

        // check the path space
        tb_size_t path_size = size? size : tb_strlen(path);
        tb_assert_and_check_return_val(home_size + path_size - 1 < maxn, 0);

        /* move the path and ensure the enough space for the home directory
         *
         * @note maybe path and data are same address
         */
        tb_memcpy(home + home_size, path + 1, path_size - 1);
        home[home_size + path_size - 1] = '\0';
        size = home_size + path_size - 1;

        // switch home as path source
        path = home;
        p = home;
    }
    if (!size) size = tb_strlen(path);

    // copy root path
    tb_char_t* dst       = data;
    tb_char_t const* src = p;
#ifdef TB_CONFIG_OS_WINDOWS
    // e.g. c:/
    if (tb_isalpha(src[0]) && src[1] == ':')
    {
        *(dst++) = src[0];
        *(dst++) = ':';
        src += 2;
    }
    // UNC path, e.g. `\\wsl.localhost`
    else if (src[0] == '\\' && src[1] == '\\' && (tb_isalpha(src[2]) || tb_isdigit(src[2])))
    {
        *(dst++) = src[0];
        *(dst++) = src[1];
        src += 2;
    }
    // dos device path, e.g. `\\.\`, `\\?\`
    else if (src[0] == '\\' && src[1] == '\\' && (src[2] == '.' || src[2] == '?') && src[3] == '\\')
    {
        *(dst++) = src[0];
        *(dst++) = src[1];
        *(dst++) = src[2];
        *(dst++) = src[3];
        src += 4;
    }
#endif
    tb_char_t const* src_root = src;
    tb_char_t const* dst_root = dst;
    if (tb_path_is_sep(*src))
    {
        ++src;
        *(dst++) = TB_PATH_SEPARATOR;
    }

#define tb_path_is_end(__p)         (__p >= src_end || (*__p) == '\0')
#define tb_path_is_sep_or_end(__p)  (tb_path_is_end(__p) || tb_path_is_sep(*__p))
    tb_char_t const* src_end  = path + size;
    tb_long_t folder_depth    = 0;
    while (!tb_path_is_end(src))
    {
        // reduce repeat separators and "/./" => "/"
        while (tb_path_is_sep(*src) ||
            (normalize && &src[-1] >= src_root && tb_path_is_sep(src[-1]) && src[0] == '.' && tb_path_is_sep_or_end(&src[1])))
            ++src;

        if (tb_path_is_end(src))
            break;

        // reduce "foo/bar/../" => "foo"
        if (normalize && src[0] == '.' && src[1] == '.' && tb_path_is_sep_or_end(&src[2]))
        {
            if (folder_depth > 0)
            {
                while (--dst != dst_root && !tb_path_is_sep(dst[-1]));
                --folder_depth;
            }
            else if (&dst[-1] != dst_root || !tb_path_is_sep(dst[-1]))
            {
                /* "/foo/../.." => "/"
                 * "foo/../.." => "../"
                 */
                *(dst++) = '.';
                *(dst++) = '.';
                *(dst++) = TB_PATH_SEPARATOR;
            }
            src += 3;
        }
        else
        {
            while (!tb_path_is_sep_or_end(src))
                *(dst++) = *(src++);

            if (tb_path_is_sep(*src))
            {
                *(dst++) = TB_PATH_SEPARATOR;
                ++src;
                ++folder_depth;
            }
        }
    }
#undef tb_path_is_end
#undef tb_path_is_sep_or_end

    // remove the tail separator and not root: '/'
    while (dst > data + 1 && tb_path_is_sep(dst[-1]))
        --dst;
    *dst = '\0';
    tb_trace_d("translate: %s", data);
    return dst - data;
}
#endif
tb_bool_t tb_path_is_absolute(tb_char_t const* path)
{
    // check
    tb_assert_and_check_return_val(path, tb_false);

    // is absolute?
#ifdef TB_CONFIG_OS_WINDOWS
    return (    path[0] == '~'
            ||  (path[0] == '\\' && path[1] == '\\' && (tb_isalpha(path[2]) || tb_isdigit(path[2]))) // UNC path, e.g. `\\Server2\Share\Test\Foo.txt`
            ||  (path[0] == '\\' && path[1] == '\\' && (path[2] == '.' || path[2] == '?') && path[3] == '\\')  // dos device path, e.g. `\\.\`, `\\?\`
#   ifdef TB_COMPILER_LIKE_UNIX
            ||  path[0] == '/'
            ||  path[0] == '\\'
            ||  !tb_strnicmp(path, "file:", 5)
#   endif
            ||  (tb_isalpha(path[0]) && path[1] == ':'));
#else
    return (    path[0] == '/'
            ||  path[0] == '\\'
            ||  path[0] == '~'
            ||  !tb_strnicmp(path, "file:", 5));
#endif
}
tb_char_t const* tb_path_absolute(tb_char_t const* path, tb_char_t* data, tb_size_t maxn)
{
    return tb_path_absolute_to(tb_null, path, data, maxn);
}
tb_char_t const* tb_path_absolute_to(tb_char_t const* root, tb_char_t const* path, tb_char_t* data, tb_size_t maxn)
{
    // check
    tb_assert_and_check_return_val(path && data && maxn, tb_null);

    // trace
    tb_trace_d("path: %s", path);

    // empty path?
    tb_check_return_val(path[0], tb_null);<commit_msg>add comments<commit_after>/*!The Treasure Box Library
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Copyright (C) 2009-present, TBOOX Open Source Group.
 *
 * @author      ruki
 * @path        path.c
 * @ingroup     platform
 *
 */

/* //////////////////////////////////////////////////////////////////////////////////////
 * trace
 */
#define TB_TRACE_MODULE_NAME                "path"
#define TB_TRACE_MODULE_DEBUG               (0)

/* //////////////////////////////////////////////////////////////////////////////////////
 * includes
 */
#include "path.h"
#include "directory.h"
#include "../libc/libc.h"

/* //////////////////////////////////////////////////////////////////////////////////////
 * macros
 */

// the path separator
#if defined(TB_CONFIG_OS_WINDOWS) && !defined(TB_COMPILER_LIKE_UNIX)
#   define TB_PATH_SEPARATOR            '\\'
#else
#   define TB_PATH_SEPARATOR            '/'
#endif

// is path separator?
#if defined(TB_CONFIG_OS_WINDOWS) && !defined(TB_COMPILER_LIKE_UNIX)
#   define tb_path_is_sep(c)      ('/' == (c) || '\\' == (c))
#else
#   define tb_path_is_sep(c)      ('/' == (c))
#endif

/* //////////////////////////////////////////////////////////////////////////////////////
 * implementation
 */
#ifndef TB_CONFIG_MICRO_ENABLE
tb_size_t tb_path_translate(tb_char_t* path, tb_size_t size, tb_size_t maxn, tb_bool_t normalize)
{
    return tb_path_translate_to(path, size, path, maxn, normalize);
}

tb_size_t tb_path_translate_to(tb_char_t const* path, tb_size_t size, tb_char_t* data, tb_size_t maxn, tb_bool_t normalize)
{
    // check
    tb_assert_and_check_return_val(path, 0);

    // file://?
    tb_char_t home[TB_PATH_MAXN];
    tb_char_t const* p = path;
    if (!tb_strnicmp(p, "file:", 5)) p += 5;
    // is user directory?
    else if (path[0] == '~')
    {
        // get the home directory
        tb_size_t home_size = tb_directory_home(home, sizeof(home));
        tb_assert_and_check_return_val(home_size, 0);

        // check the path space
        tb_size_t path_size = size? size : tb_strlen(path);
        tb_assert_and_check_return_val(home_size + path_size - 1 < maxn, 0);

        /* move the path and ensure the enough space for the home directory
         *
         * @note maybe path and data are same address
         */
        tb_memcpy(home + home_size, path + 1, path_size - 1);
        home[home_size + path_size - 1] = '\0';
        size = home_size + path_size - 1;

        // switch home as path source
        path = home;
        p = home;
    }
    if (!size) size = tb_strlen(path);

    // copy root path
    tb_char_t* dst       = data;
    tb_char_t const* src = p;
#ifdef TB_CONFIG_OS_WINDOWS
    // e.g. c:/
    if (tb_isalpha(src[0]) && src[1] == ':')
    {
        *(dst++) = src[0];
        *(dst++) = ':';
        src += 2;
    }
    // UNC path, e.g. `\\wsl.localhost`
    else if (src[0] == '\\' && src[1] == '\\' && (tb_isalpha(src[2]) || tb_isdigit(src[2])))
    {
        *(dst++) = src[0];
        *(dst++) = src[1];
        src += 2;
    }
    // dos device path, e.g. `\\.\`, `\\?\`
    else if (src[0] == '\\' && src[1] == '\\' && (src[2] == '.' || src[2] == '?') && src[3] == '\\')
    {
        *(dst++) = src[0];
        *(dst++) = src[1];
        *(dst++) = src[2];
        *(dst++) = src[3];
        src += 4;
    }
#endif
    tb_char_t const* src_root = src;
    tb_char_t const* dst_root = dst;
    if (tb_path_is_sep(*src))
    {
        ++src;
        *(dst++) = TB_PATH_SEPARATOR;
    }

#define tb_path_is_end(__p)         (__p >= src_end || (*__p) == '\0')
#define tb_path_is_sep_or_end(__p)  (tb_path_is_end(__p) || tb_path_is_sep(*__p))
    tb_char_t const* src_end  = path + size;
    tb_long_t folder_depth    = 0;
    while (!tb_path_is_end(src))
    {
        // reduce repeat separators and "/./" => "/"
        while (tb_path_is_sep(*src) ||
            (normalize && &src[-1] >= src_root && tb_path_is_sep(src[-1]) && src[0] == '.' && tb_path_is_sep_or_end(&src[1])))
            ++src;

        if (tb_path_is_end(src))
            break;

        // reduce "foo/bar/../" => "foo"
        if (normalize && src[0] == '.' && src[1] == '.' && tb_path_is_sep_or_end(&src[2]))
        {
            if (folder_depth > 0)
            {
                while (--dst != dst_root && !tb_path_is_sep(dst[-1]));
                --folder_depth;
            }
            else if (&dst[-1] != dst_root || !tb_path_is_sep(dst[-1]))
            {
                /* "/foo/../.." => "/"
                 * "foo/../.." => "../"
                 */
                *(dst++) = '.';
                *(dst++) = '.';
                *(dst++) = TB_PATH_SEPARATOR;
            }
            src += 3;
        }
        else
        {
            while (!tb_path_is_sep_or_end(src))
                *(dst++) = *(src++);

            if (tb_path_is_sep(*src))
            {
                *(dst++) = TB_PATH_SEPARATOR;
                ++src;
                ++folder_depth;
            }
        }
    }
#undef tb_path_is_end
#undef tb_path_is_sep_or_end

    // remove the tail separator and not root: '/'
    while (dst > data + 1 && tb_path_is_sep(dst[-1]))
        --dst;
    *dst = '\0';
    tb_trace_d("translate: %s", data);
    return dst - data;
}
#endif
tb_bool_t tb_path_is_absolute(tb_char_t const* path)
{
    // check
    tb_assert_and_check_return_val(path, tb_false);

#ifdef TB_CONFIG_OS_WINDOWS
    // @see https://learn.microsoft.com/zh-cn/dotnet/standard/io/file-path-formats
    return (    path[0] == '~'
            ||  (path[0] == '\\' && path[1] == '\\' && (tb_isalpha(path[2]) || tb_isdigit(path[2]))) // UNC path, e.g. `\\Server2\Share\Test\Foo.txt`
            ||  (path[0] == '\\' && path[1] == '\\' && (path[2] == '.' || path[2] == '?') && path[3] == '\\')  // dos device path, e.g. `\\.\`, `\\?\`
#   ifdef TB_COMPILER_LIKE_UNIX
            ||  path[0] == '/'
            ||  path[0] == '\\'
            ||  !tb_strnicmp(path, "file:", 5)
#   endif
            ||  (tb_isalpha(path[0]) && path[1] == ':'));
#else
    return (    path[0] == '/'
            ||  path[0] == '\\'
            ||  path[0] == '~'
            ||  !tb_strnicmp(path, "file:", 5));
#endif
}
tb_char_t const* tb_path_absolute(tb_char_t const* path, tb_char_t* data, tb_size_t maxn)
{
    return tb_path_absolute_to(tb_null, path, data, maxn);
}
tb_char_t const* tb_path_absolute_to(tb_char_t const* root, tb_char_t const* path, tb_char_t* data, tb_size_t maxn)
{
    // check
    tb_assert_and_check_return_val(path && data && maxn, tb_null);

    // trace
    tb_trace_d("path: %s", path);

    // empty path?
    tb_check_return_val(path[0], tb_null);
{'fraction_non_alphanumeric': 0.21404565186948607, 'fraction_numerical': 0.011202912757316903, 'mean_word_length': 1.8127215439149271, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 19, 'https://': 1, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5076482', 'n_tokens_mistral': 5119, 'n_tokens_neox': 4958, 'n_words': 1046}