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

#include "directoryvalue.h"

#include <kdbassert.h>
#include <kdberrors.h>
#include <kdbhelper.h>
#include <kdblogger.h>

/**
 * @brief This function returns a key set containing the contract of this plugin.
 *
 * @return A contract describing the functionality of this plugin.
 */
static KeySet * directoryValueContract (void)
{
	return ksNew (30,
		      keyNew ("system/elektra/modules/directoryvalue", KEY_VALUE, "directoryvalue plugin waits for your orders", KEY_END),
		      keyNew ("system/elektra/modules/directoryvalue/exports", KEY_END),
		      keyNew ("system/elektra/modules/directoryvalue/exports/get", KEY_FUNC, elektraDirectoryvalueGet, KEY_END),
		      keyNew ("system/elektra/modules/directoryvalue/exports/set", KEY_FUNC, elektraDirectoryvalueSet, KEY_END),
#include ELEKTRA_README (directoryvalue)
		      keyNew ("system/elektra/modules/directoryvalue/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END);
}

/**
 * @brief Convert `key` to a leaf value and copy it to `output`.
 *
 * - If `key` is already a leaf value it will not be changed and directly copied to `output`.
 * - If `key` is a directory value the function creates a new leaf value storing the data of the old directory key. The function also
 *   creates another empty node with the name of the directory value in this case. Both of these key will be copied to `output`.
 *
 * @pre The parameters `output`, `next` and `parent` must not be `NULL`.
 *
 * @param output This parameter stores the values this function creates.
 * @param key This parameter stores the key this function operates on.
 * @param next This parameter stores the key directly after `key`.
 * @param parent This parameter stores the parent key of `keySet`. This function uses this key to emit error information.
 *
 * @retval ELEKTRA_PLUGIN_STATUS_NO_UPDATE if everything went fine and the function copied `key` without any modifications to `output`.
 * @retval ELEKTRA_PLUGIN_STATUS_SUCCESS if everything went fine and the function converted `key` to a leaf value.
 * @retval ELEKTRA_PLUGIN_STATUS_ERROR if the function was unable to convert `key`.
 */
static int addDirectoryValue (KeySet * output, Key const * const key, Key const * const next, Key * const parent)
{
	ELEKTRA_NOT_NULL (next);
	ELEKTRA_NOT_NULL (parent);
	ELEKTRA_NOT_NULL (output);

	if (!key)
	{
		ELEKTRA_LOG_DEBUG ("Key `key` is null");
		return ELEKTRA_PLUGIN_STATUS_NO_UPDATE;
	}
	if (!keyIsBelow (key, next))
	{
		ELEKTRA_LOG_DEBUG ("Key %s is a leaf value", keyName (key));
		ksAppendKey (output, keyDup (key));

		return ELEKTRA_PLUGIN_STATUS_NO_UPDATE;
	}

	Key * directoryKey = keyNew (keyName (key), KEY_END);
	Key * dataKey = keyDup (key);
	if (keyAddBaseName (dataKey, DIRECTORY_POSTFIX) < 0)
	{
		ELEKTRA_SET_ERRORF (ELEKTRA_ERROR_DIRECTORY_VALUE_APPEND, parent, "Could not append directory postfix to “%s”",
				    keyName (dataKey));
		return ELEKTRA_PLUGIN_STATUS_ERROR;
	}
	ELEKTRA_ASSERT (ksAppendKey (output, directoryKey) >= 0, "Could not append directory key");
	ELEKTRA_ASSERT (ksAppendKey (output, dataKey) >= 0, "Could not append data key");
	return ELEKTRA_PLUGIN_STATUS_SUCCESS;
}

/**
 * @brief Convert the directory value saved in `key` back to a directory key.
 *
 * @param output This parameter stores the key this function converts.
 * @param dirValues The function also stores the converted key in this key set.
 * @param key This parameter stores the current key this function operates on.
 * @param error The function uses this key to emit error information.
 *
 * @pre The parameters `output`, `dirValues`, `key` and `parent` must not be `NULL`.
 *
 * @retval ELEKTRA_PLUGIN_STATUS_SUCCESS if everything went fine
 * @retval ELEKTRA_PLUGIN_STATUS_ERROR if the function was unable to convert `key`
**/
static int convertToDirectoryKey (KeySet * output, KeySet * dirValues, Key * key, Key * parent)
{
	ELEKTRA_NOT_NULL (output);
	ELEKTRA_NOT_NULL (dirValues);
	ELEKTRA_NOT_NULL (key);
	ELEKTRA_NOT_NULL (parent);

	size_t directoryKeyLength = elektraStrLen (keyName (key)) - DIRECTORY_POSTFIX_LENGTH - 1;

	int errorNumber = errno;
	char * directoryName = strndup (keyName (key), directoryKeyLength);
	if (!directoryName)
	{
		errno = errorNumber;
		ELEKTRA_MALLOC_ERROR (parent, directoryKeyLength);
		return ELEKTRA_PLUGIN_STATUS_ERROR;
	}

	Key * directoryKey = keyDup (key);
	keySetName (directoryKey, directoryName);
	elektraFree (directoryName);
	ksAppendKey (dirValues, directoryKey);
	ksAppendKey (output, directoryKey);
	keyDel (key);
	return ELEKTRA_PLUGIN_STATUS_SUCCESS;
}

/**
 * @brief Check `key` for a directory value, modify it and copy it to output.
 *
 * - If key contains a directory value (marked by `DIRECTORY_POSTFIX`) it will be converted to a directory key. After that the function
 *   saves the key in `output` and `dirValue`.
 * - If `key` has the same name as the last converted directory value (stored in `dirValues`), then the function deletes the key.
 * - If `key` is a non-special key (none of the two cases above apply), then the function just appends the unmodified `key` to `output`.
 *
 * @pre The parameters `output`, `key`, `dirValues` and `parent` must not be `NULL`.
 *
 * @param output This parameter stores the key this function converts.
 * @param key This parameter stores the current key this function operates on.
 * @param dirValues This key set stores the last converted directory value, or nothing if this function was already invoked with the
 *                  empty version of the directory key as parameter.
 * @param error The function uses this key to emit error information.
 *
 * @retval ELEKTRA_PLUGIN_STATUS_NO_UPDATE if everything went fine and the function copied `key` without any modifications to `output`
 * @retval ELEKTRA_PLUGIN_STATUS_SUCCESS if everything went fine and the function either converted a directory value or did not add `key`
 *                                       to output
 * @retval ELEKTRA_PLUGIN_STATUS_ERROR if the function was unable to convert `key`
 */
static int removeDirectoryValue (KeySet * output, Key * key, KeySet * dirValues, Key * error)
{
	const char * baseName = keyBaseName (key);
	size_t baseNameLength = keyGetBaseNameSize (key);
	size_t minLengthBase = DIRECTORY_POSTFIX_LENGTH < baseNameLength ? DIRECTORY_POSTFIX_LENGTH : baseNameLength;

	if (baseNameLength == DIRECTORY_POSTFIX_LENGTH && strncmp (baseName, DIRECTORY_POSTFIX, minLengthBase) == 0)
	{
		ELEKTRA_LOG_DEBUG ("Convert leaf “%s” back to directory key", keyName (key));
		return convertToDirectoryKey (output, dirValues, key, error);
	}

	ELEKTRA_ASSERT (ksGetSize (dirValues) <= 1, "More than one recent directory key");

	Key * dirValue = ksPop (dirValues);<commit_msg>Directory Value: Do not compare string terminators<commit_after>/**
 * @file
 *
 * @brief Source for directoryvalue plugin
 *
 * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
 *
 */

#include "directoryvalue.h"

#include <kdbassert.h>
#include <kdberrors.h>
#include <kdbhelper.h>
#include <kdblogger.h>

/**
 * @brief This function returns a key set containing the contract of this plugin.
 *
 * @return A contract describing the functionality of this plugin.
 */
static KeySet * directoryValueContract (void)
{
	return ksNew (30,
		      keyNew ("system/elektra/modules/directoryvalue", KEY_VALUE, "directoryvalue plugin waits for your orders", KEY_END),
		      keyNew ("system/elektra/modules/directoryvalue/exports", KEY_END),
		      keyNew ("system/elektra/modules/directoryvalue/exports/get", KEY_FUNC, elektraDirectoryvalueGet, KEY_END),
		      keyNew ("system/elektra/modules/directoryvalue/exports/set", KEY_FUNC, elektraDirectoryvalueSet, KEY_END),
#include ELEKTRA_README (directoryvalue)
		      keyNew ("system/elektra/modules/directoryvalue/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END);
}

/**
 * @brief Convert `key` to a leaf value and copy it to `output`.
 *
 * - If `key` is already a leaf value it will not be changed and directly copied to `output`.
 * - If `key` is a directory value the function creates a new leaf value storing the data of the old directory key. The function also
 *   creates another empty node with the name of the directory value in this case. Both of these key will be copied to `output`.
 *
 * @pre The parameters `output`, `next` and `parent` must not be `NULL`.
 *
 * @param output This parameter stores the values this function creates.
 * @param key This parameter stores the key this function operates on.
 * @param next This parameter stores the key directly after `key`.
 * @param parent This parameter stores the parent key of `keySet`. This function uses this key to emit error information.
 *
 * @retval ELEKTRA_PLUGIN_STATUS_NO_UPDATE if everything went fine and the function copied `key` without any modifications to `output`.
 * @retval ELEKTRA_PLUGIN_STATUS_SUCCESS if everything went fine and the function converted `key` to a leaf value.
 * @retval ELEKTRA_PLUGIN_STATUS_ERROR if the function was unable to convert `key`.
 */
static int addDirectoryValue (KeySet * output, Key const * const key, Key const * const next, Key * const parent)
{
	ELEKTRA_NOT_NULL (next);
	ELEKTRA_NOT_NULL (parent);
	ELEKTRA_NOT_NULL (output);

	if (!key)
	{
		ELEKTRA_LOG_DEBUG ("Key `key` is null");
		return ELEKTRA_PLUGIN_STATUS_NO_UPDATE;
	}
	if (!keyIsBelow (key, next))
	{
		ELEKTRA_LOG_DEBUG ("Key %s is a leaf value", keyName (key));
		ksAppendKey (output, keyDup (key));

		return ELEKTRA_PLUGIN_STATUS_NO_UPDATE;
	}

	Key * directoryKey = keyNew (keyName (key), KEY_END);
	Key * dataKey = keyDup (key);
	if (keyAddBaseName (dataKey, DIRECTORY_POSTFIX) < 0)
	{
		ELEKTRA_SET_ERRORF (ELEKTRA_ERROR_DIRECTORY_VALUE_APPEND, parent, "Could not append directory postfix to “%s”",
				    keyName (dataKey));
		return ELEKTRA_PLUGIN_STATUS_ERROR;
	}
	ELEKTRA_ASSERT (ksAppendKey (output, directoryKey) >= 0, "Could not append directory key");
	ELEKTRA_ASSERT (ksAppendKey (output, dataKey) >= 0, "Could not append data key");
	return ELEKTRA_PLUGIN_STATUS_SUCCESS;
}

/**
 * @brief Convert the directory value saved in `key` back to a directory key.
 *
 * @param output This parameter stores the key this function converts.
 * @param dirValues The function also stores the converted key in this key set.
 * @param key This parameter stores the current key this function operates on.
 * @param error The function uses this key to emit error information.
 *
 * @pre The parameters `output`, `dirValues`, `key` and `parent` must not be `NULL`.
 *
 * @retval ELEKTRA_PLUGIN_STATUS_SUCCESS if everything went fine
 * @retval ELEKTRA_PLUGIN_STATUS_ERROR if the function was unable to convert `key`
**/
static int convertToDirectoryKey (KeySet * output, KeySet * dirValues, Key * key, Key * parent)
{
	ELEKTRA_NOT_NULL (output);
	ELEKTRA_NOT_NULL (dirValues);
	ELEKTRA_NOT_NULL (key);
	ELEKTRA_NOT_NULL (parent);

	size_t directoryKeyLength = elektraStrLen (keyName (key)) - DIRECTORY_POSTFIX_LENGTH - 1;

	int errorNumber = errno;
	char * directoryName = strndup (keyName (key), directoryKeyLength);
	if (!directoryName)
	{
		errno = errorNumber;
		ELEKTRA_MALLOC_ERROR (parent, directoryKeyLength);
		return ELEKTRA_PLUGIN_STATUS_ERROR;
	}

	Key * directoryKey = keyDup (key);
	keySetName (directoryKey, directoryName);
	elektraFree (directoryName);
	ksAppendKey (dirValues, directoryKey);
	ksAppendKey (output, directoryKey);
	keyDel (key);
	return ELEKTRA_PLUGIN_STATUS_SUCCESS;
}

/**
 * @brief Check `key` for a directory value, modify it and copy it to output.
 *
 * - If key contains a directory value (marked by `DIRECTORY_POSTFIX`) it will be converted to a directory key. After that the function
 *   saves the key in `output` and `dirValue`.
 * - If `key` has the same name as the last converted directory value (stored in `dirValues`), then the function deletes the key.
 * - If `key` is a non-special key (none of the two cases above apply), then the function just appends the unmodified `key` to `output`.
 *
 * @pre The parameters `output`, `key`, `dirValues` and `parent` must not be `NULL`.
 *
 * @param output This parameter stores the key this function converts.
 * @param key This parameter stores the current key this function operates on.
 * @param dirValues This key set stores the last converted directory value, or nothing if this function was already invoked with the
 *                  empty version of the directory key as parameter.
 * @param error The function uses this key to emit error information.
 *
 * @retval ELEKTRA_PLUGIN_STATUS_NO_UPDATE if everything went fine and the function copied `key` without any modifications to `output`
 * @retval ELEKTRA_PLUGIN_STATUS_SUCCESS if everything went fine and the function either converted a directory value or did not add `key`
 *                                       to output
 * @retval ELEKTRA_PLUGIN_STATUS_ERROR if the function was unable to convert `key`
 */
static int removeDirectoryValue (KeySet * output, Key * key, KeySet * dirValues, Key * error)
{
	const char * baseName = keyBaseName (key);
	size_t baseNameLength = keyGetBaseNameSize (key);
	size_t minLengthBase = (DIRECTORY_POSTFIX_LENGTH < baseNameLength ? DIRECTORY_POSTFIX_LENGTH : baseNameLength) - 1;

	if (strncmp (baseName, DIRECTORY_POSTFIX, minLengthBase) == 0 && baseNameLength == DIRECTORY_POSTFIX_LENGTH)
	{
		ELEKTRA_LOG_DEBUG ("Convert leaf “%s” back to directory key", keyName (key));
		return convertToDirectoryKey (output, dirValues, key, error);
	}

	ELEKTRA_ASSERT (ksGetSize (dirValues) <= 1, "More than one recent directory key");

	Key * dirValue = ksPop (dirValues);
{'fraction_non_alphanumeric': 0.08930772018847409, 'fraction_numerical': 0.0012323305545487496, 'mean_word_length': 4.5316760224538895, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 15, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2391356', 'n_tokens_mistral': 4255, 'n_tokens_neox': 4110, 'n_words': 1576}