<commit_before>/**
 * @file
 *
 * @brief Delegate implementation for the `directoryvalue` plugin
 *
 * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
 *
 */

#include <numeric>

#include <kdbassert.h>
#include <kdbease.h>
#include <kdblogger.h>

#include "directoryvalue_delegate.hpp"

using std::accumulate;
using std::ignore;
using std::make_pair;
using std::pair;
using std::range_error;
using std::string;
using std::tie;

// -- Functions ----------------------------------------------------------------------------------------------------------------------------

namespace elektra
{
using CppKey = kdb::Key;

/**
 * @brief This function splits the given keyset into directory leaves (marked with `DIRECTORY_POSTFIX`) and other keys.
 *
 * @param input The function searches for directory leaves in this key set.
 *
 * @return A pair of key sets, where the first key set contains all directory leaves and the second key set contains all other keys
 */
KeySetPair splitDirectoryLeavesOther (CppKeySet const & input)
{
	CppKeySet directoryLeaves;
	CppKeySet other;

	for (auto key : input)
	{
		if (key.getBaseName () == DIRECTORY_POSTFIX)
		{
			directoryLeaves.append (key);
		}
		else
		{
			other.append (key);
		}
	}
	return make_pair (directoryLeaves, other);
}

/**
 * @brief This function splits the given keyset into array leaves (first element of an array parent) and other keys.
 *
 * @param input The function searches for array leaves in this key set.
 *
 * @return A pair of key sets, where the first key set contains all array leaves and the second key set contains all other keys
 */
KeySetPair splitArrayLeavesOther (CppKeySet const & arrayParents, CppKeySet const & keys)
{
	bool isFirstElement = false;
	CppKeySet firstElements;
	CppKeySet others;

	for (auto key : keys)
	{
		bool const isArrayLeaf = isFirstElement && key.isString () && key.getStringSize () > arrayValuePrefixSize &&
					 strncmp (key.getString ().c_str (), ARRAY_VALUE_PREFIX, arrayValuePrefixSize) == 0;
		(isArrayLeaf ? firstElements : others).append (key);
		isFirstElement = arrayParents.lookup (key);
	}

	return make_pair (firstElements, others);
}

/**
 * @brief This function removes the basename (last level) from the given keys.
 *
 * @param keys This parameter contains the keys for which this function removes the basename.
 *
 * @return A copy of the input, where the last level of each key was removed
 */
CppKeySet removeBaseName (CppKeySet const & keys)
{
	CppKeySet directories;

	for (auto key : keys)
	{
		ELEKTRA_LOG_DEBUG ("Remove basename from “%s”: “%s”", key.getName ().c_str (),
				   key.getBinarySize () == 0 ? "NULL" : key.isBinary () ? "binary value!" : key.getString ().c_str ());
		CppKey directory = key.dup ();
		directory.delBaseName ();
		directories.append (directory);
	}
	return directories;
}

/**
 * @brief This function converts the given array leaves to directory keys.
 *
 * @param arrayLeaves This parameter contains array leaves (that start with `ARRAY_VALUE_PREFIX` and have the baseName = `#0`).
 *
 * @return A copy of `arrayLeaves`, where key values does not start with `ARRAY_VALUE_PREFIX` any more and the baseName of each key (`#0`)
 *         was removed
 */
CppKeySet convertArrayLeaves (CppKeySet const & arrayLeaves)
{
	CppKeySet directories = removeBaseName (arrayLeaves);
	for (auto key : directories)
	{
		ELEKTRA_LOG_DEBUG ("Convert array leaf “%s”", key.getName ().c_str ());
		if (key.getStringSize () == arrayValuePrefixSize + 1)
		{
			key.setBinary (0, 0);
			ELEKTRA_LOG_DEBUG ("Set value of “%s” to NULL", key.getName ().c_str ());
		}
		else
		{
			key.setString (key.getString ().substr (arrayValuePrefixSize + 1));
			ELEKTRA_LOG_DEBUG ("Set value of “%s” to “%s”", key.getName ().c_str (), key.getString ().c_str ());
		}
	}
	return directories;
}

/**
 * @brief This function returns a modified copy of `child`, where child is directly below `parent`.
 *
 * For example, if `child` has the name `user/parent/level1/level2/level3` and parent has the name `user/parent`, then the function will
 * return a key with the name `user/parent/level1`.
 *
 * @pre The key `child` has to be below `parent`.
 *
 * @param parent This parameter specifies a parent key of `child`.
 * @param keys This variable stores a child key of `parent`.
 *
 * @return A copy of `child` that is directly below `parent`
 */
CppKey convertToDirectChild (CppKey const & parent, CppKey const & child)
{
	ELEKTRA_ASSERT (child.isBelow (parent), "The key `child` is not located below `parent`");

	CppKey directChild = child.dup ();
	while (!directChild.isDirectBelow (parent))
	{
		keySetBaseName (*directChild, 0);
	}
	return directChild;
}

/**
 * @brief This function determines if the given key is an array parent.
 *
 * @param parent This parameter specifies a possible array parent.
 * @param keys This variable stores the key set of `parent`.
 *
 * @retval true If `parent` is the parent key of an array
 * @retval false Otherwise
 */
bool isArrayParent (CppKey const & parent, CppKeySet const & keys)
{
	for (auto const & key : keys)
	{
		if (!key.isBelow (parent)) continue;

		CppKey directChild = convertToDirectChild (parent, key);
		if (elektraArrayValidateName (*directChild) < 0) return false;
	}

	return true;
}<commit_msg>Directory Value: Make `isArrayParent` faster<commit_after>/**
 * @file
 *
 * @brief Delegate implementation for the `directoryvalue` plugin
 *
 * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
 *
 */

#include <numeric>

#include <kdbassert.h>
#include <kdbease.h>
#include <kdblogger.h>

#include "directoryvalue_delegate.hpp"

using std::accumulate;
using std::ignore;
using std::make_pair;
using std::pair;
using std::range_error;
using std::string;
using std::tie;

// -- Functions ----------------------------------------------------------------------------------------------------------------------------

namespace elektra
{
using CppKey = kdb::Key;

/**
 * @brief This function splits the given keyset into directory leaves (marked with `DIRECTORY_POSTFIX`) and other keys.
 *
 * @param input The function searches for directory leaves in this key set.
 *
 * @return A pair of key sets, where the first key set contains all directory leaves and the second key set contains all other keys
 */
KeySetPair splitDirectoryLeavesOther (CppKeySet const & input)
{
	CppKeySet directoryLeaves;
	CppKeySet other;

	for (auto key : input)
	{
		if (key.getBaseName () == DIRECTORY_POSTFIX)
		{
			directoryLeaves.append (key);
		}
		else
		{
			other.append (key);
		}
	}
	return make_pair (directoryLeaves, other);
}

/**
 * @brief This function splits the given keyset into array leaves (first element of an array parent) and other keys.
 *
 * @param input The function searches for array leaves in this key set.
 *
 * @return A pair of key sets, where the first key set contains all array leaves and the second key set contains all other keys
 */
KeySetPair splitArrayLeavesOther (CppKeySet const & arrayParents, CppKeySet const & keys)
{
	bool isFirstElement = false;
	CppKeySet firstElements;
	CppKeySet others;

	for (auto key : keys)
	{
		bool const isArrayLeaf = isFirstElement && key.isString () && key.getStringSize () > arrayValuePrefixSize &&
					 strncmp (key.getString ().c_str (), ARRAY_VALUE_PREFIX, arrayValuePrefixSize) == 0;
		(isArrayLeaf ? firstElements : others).append (key);
		isFirstElement = arrayParents.lookup (key);
	}

	return make_pair (firstElements, others);
}

/**
 * @brief This function removes the basename (last level) from the given keys.
 *
 * @param keys This parameter contains the keys for which this function removes the basename.
 *
 * @return A copy of the input, where the last level of each key was removed
 */
CppKeySet removeBaseName (CppKeySet const & keys)
{
	CppKeySet directories;

	for (auto key : keys)
	{
		ELEKTRA_LOG_DEBUG ("Remove basename from “%s”: “%s”", key.getName ().c_str (),
				   key.getBinarySize () == 0 ? "NULL" : key.isBinary () ? "binary value!" : key.getString ().c_str ());
		CppKey directory = key.dup ();
		directory.delBaseName ();
		directories.append (directory);
	}
	return directories;
}

/**
 * @brief This function converts the given array leaves to directory keys.
 *
 * @param arrayLeaves This parameter contains array leaves (that start with `ARRAY_VALUE_PREFIX` and have the baseName = `#0`).
 *
 * @return A copy of `arrayLeaves`, where key values does not start with `ARRAY_VALUE_PREFIX` any more and the baseName of each key (`#0`)
 *         was removed
 */
CppKeySet convertArrayLeaves (CppKeySet const & arrayLeaves)
{
	CppKeySet directories = removeBaseName (arrayLeaves);
	for (auto key : directories)
	{
		ELEKTRA_LOG_DEBUG ("Convert array leaf “%s”", key.getName ().c_str ());
		if (key.getStringSize () == arrayValuePrefixSize + 1)
		{
			key.setBinary (0, 0);
			ELEKTRA_LOG_DEBUG ("Set value of “%s” to NULL", key.getName ().c_str ());
		}
		else
		{
			key.setString (key.getString ().substr (arrayValuePrefixSize + 1));
			ELEKTRA_LOG_DEBUG ("Set value of “%s” to “%s”", key.getName ().c_str (), key.getString ().c_str ());
		}
	}
	return directories;
}

/**
 * @brief This function returns a modified copy of `child`, where child is directly below `parent`.
 *
 * For example, if `child` has the name `user/parent/level1/level2/level3` and parent has the name `user/parent`, then the function will
 * return a key with the name `user/parent/level1`.
 *
 * @pre The key `child` has to be below `parent`.
 *
 * @param parent This parameter specifies a parent key of `child`.
 * @param keys This variable stores a child key of `parent`.
 *
 * @return A copy of `child` that is directly below `parent`
 */
CppKey convertToDirectChild (CppKey const & parent, CppKey const & child)
{
	ELEKTRA_ASSERT (child.isBelow (parent), "The key `child` is not located below `parent`");

	CppKey directChild = child.dup ();
	while (!directChild.isDirectBelow (parent))
	{
		keySetBaseName (*directChild, 0);
	}
	return directChild;
}

/**
 * @brief This function checks if `element` is an array element of `parent`.
 *
 * @pre The key `child` must be below `parent`.
 *
 * @param parent This parameter specifies a parent key.
 * @param keys This variable stores a direct or indirect child of `parent`.
 *
 * @retval true If `element` is an array element
 * @retval false Otherwise
 */
bool inline isArrayElementOf (CppKey const & parent, CppKey const & child)
{
	char const * relative = elektraKeyGetRelativeName (*child, *parent);
	if (relative[0] != '#') return false;
	relative++; // Skip leading `#`
	size_t underscores = 0;
	while (relative[underscores] == '_')
	{
		underscores++;
	}
	relative = &relative[underscores]; // Skip underscores
	// Check index
	for (size_t digit = 0; digit <= underscores; digit++)
	{
		if (relative[digit] < '0' || relative[digit] > '9') return false;
	}
	relative = &relative[underscores + 1]; // Skip index
	// The next character has to be the separation char (`/`) or end of string
	if (relative[0] != '\0' && relative[0] != '/') return false;

	return true;
}

/**
 * @brief This function determines if the given key is an array parent.
 *
 * @param parent This parameter specifies a possible array parent.
 * @param keys This variable stores the key set of `parent`.
 *
 * @retval true If `parent` is the parent key of an array
 * @retval false Otherwise
 */
bool isArrayParent (CppKey const & parent, CppKeySet const & keys)
{
	for (auto const & key : keys)
	{
		if (!key.isBelow (parent)) continue;
		if (!isArrayElementOf (parent, key)) return false;
	}

	return true;
}
{'fraction_non_alphanumeric': 0.13477924089852827, 'fraction_numerical': 0.003098373353989156, 'mean_word_length': 3.6666666666666665, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 14, 'https://': 2, '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': '673200', 'n_tokens_mistral': 3684, 'n_tokens_neox': 3503, 'n_words': 1298}