Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions examples/string_replace.cf
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,25 @@ bundle agent main

# replace ambiguous order
"replace_ambiguous" string => string_replace("aaaaa", "aaa", "b");
"matches" slist => { "e", "r" };
"subs" slist => { "E", "R" };

"replace_list"
string => string_replace("Hello World", "@(matches)", "@(subs)", "lists");

"replace_list_json"
string => string_replace(
"Hello World", '["World", "o"]', '["Everyone", "0"]', "lists"
);

reports:
# in order, the above...
"replace_once = '$(replace_once)'";
"replace_several = '$(replace_several)'";
"replace_none = '$(replace_none)'";
"replace_ambiguous = '$(replace_ambiguous)'";
"replace_list = '$(replace_list)'";
"replace_list_json = '$(replace_list_json)'";
}

#+end_src
Expand All @@ -47,5 +59,7 @@ bundle agent main
#@ R: replace_several = 'Thos os a strong'
#@ R: replace_none = 'This is a string'
#@ R: replace_ambiguous = 'baa'
#@ R: replace_list = 'HEllo WoRld'
#@ R: replace_list_json = 'Hell0 Every0ne'
#@ ```
#+end_src
121 changes: 106 additions & 15 deletions libpromises/evalfunction.c
Original file line number Diff line number Diff line change
Expand Up @@ -8974,34 +8974,124 @@

/*********************************************************************/

static FnCallResult FnCallStringReplace(ARG_UNUSED EvalContext *ctx,
static FnCallResult FnCallStringReplace(EvalContext *ctx,
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
ARG_UNUSED Policy const *policy,
ARG_UNUSED FnCall const *fp,
Rlist const *finalargs)
const FnCall *fp,
const Rlist *finalargs)
{
assert(fp != NULL);
assert(finalargs != NULL);
if (finalargs->next == NULL || finalargs->next->next == NULL)
{
Log(LOG_LEVEL_WARNING,
"Incorrect number of arguments for function '%s'",
fp->name);
return FnFailure();

Check warning

Code scanning / CodeQL

Poorly documented large function Warning

Poorly documented function: fewer than 2% comments for a function of 107 lines.
}

char *string = RlistScalarValue(finalargs);
char *match = RlistScalarValue(finalargs->next);
char *substitute = RlistScalarValue(finalargs->next->next);
char *input_string = RlistScalarValue(finalargs);
char *ret = NULL;

char *ret = SearchAndReplace(string, match, substitute);
char *option = "strings";
if (finalargs->next->next->next != NULL)
{
option = RlistScalarValue(finalargs->next->next->next);
}

if (ret == NULL)
if (StringEqual(option, "strings"))
{
Log(LOG_LEVEL_WARNING,
"Failed to replace with function '%s', string: '%s', match: '%s', "
"substitute: '%s'",
fp->name, string, match, substitute);
char *match_string = RlistScalarValue(finalargs->next);
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
char *substitute_string = RlistScalarValue(finalargs->next->next);
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed

ret = SearchAndReplace(input_string, match_string, substitute_string);
if (ret == NULL)
{
Log(LOG_LEVEL_WARNING,
"Failed to replace with function '%s', string: '%s', "
"match: '%s', substitute: '%s'",
fp->name, input_string, match_string, substitute_string);
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
return FnFailure();
}
return FnReturnNoCopy(ret);
}

/* option == "lists" */
Comment thread
victormlg marked this conversation as resolved.
bool match_allocated = false;
JsonElement *match = VarNameOrInlineToJson(ctx, fp, finalargs->next, true, &match_allocated);

if (match == NULL)
{
Log(LOG_LEVEL_ERR, "Failed to replace with function '%s' in '%s': "
"Couldn't resolve match pattern '%s' to an array"
, fp->name, input_string, RlistScalarValue(finalargs->next));
return FnFailure();
}

if (JsonGetType(match) != JSON_TYPE_ARRAY)
{
Log(LOG_LEVEL_ERR, "Failed to replace with function '%s' in '%s': "
"Match pattern expected 'array' but got '%s'"
, fp->name, input_string, JsonGetTypeAsString(match));
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
goto cleanup_match;
}

bool substitute_allocated = false;
JsonElement *substitute = VarNameOrInlineToJson(ctx, fp, finalargs->next->next, true, &substitute_allocated);
if (substitute == NULL)
{
Log(LOG_LEVEL_ERR, "Failed to replace with function '%s' in '%s': "
"Couldn't resolve substitute pattern '%s' to an array"
, fp->name, input_string, RlistScalarValue(finalargs->next->next));
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
goto cleanup_match;
}

if (JsonGetType(substitute) != JSON_TYPE_ARRAY)
{
Log(LOG_LEVEL_ERR, "Failed to replace with function '%s' in '%s': "
"Substitute pattern expected 'array' but got '%s'"
, fp->name, input_string, JsonGetTypeAsString(substitute));
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
goto cleanup_substitute;
}

if (JsonLength(match) != JsonLength(substitute))
{
Log(LOG_LEVEL_ERR, "Failed to replace with function '%s' in '%s': "
"Match and substitute pattern lists must have equal lengths",
fp->name, input_string);
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
goto cleanup_substitute;
}

char *working = SafeStringDuplicate(input_string);
for (size_t i = 0; i < JsonLength(match); i++)
{
const char *m = JsonPrimitiveGetAsString(JsonAt(match, i));
const char *s = JsonPrimitiveGetAsString(JsonAt(substitute, i));

char *next = SearchAndReplace(working, m, s);
free(working);

if (next == NULL)
{
Log(LOG_LEVEL_WARNING,
"Failed to replace with function '%s', string: '%s', "
"match: '%s', substitute: '%s'",
fp->name, input_string, m, s);
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
working = NULL;
break;
}
working = next;
}
ret = working;

cleanup_substitute:
JsonDestroyMaybe(substitute, substitute_allocated);
cleanup_match:
JsonDestroyMaybe(match, match_allocated);

if (ret == NULL)
{
return FnFailure();
}
return FnReturnNoCopy(ret);
}

Expand Down Expand Up @@ -11272,8 +11362,9 @@
static const FnCallArg STRING_REPLACE_ARGS[] =
{
{CF_ANYSTRING, CF_DATA_TYPE_STRING, "Source string"},
{CF_ANYSTRING, CF_DATA_TYPE_STRING, "String to replace"},
{CF_ANYSTRING, CF_DATA_TYPE_STRING, "Replacement string"},
{CF_ANYSTRING, CF_DATA_TYPE_STRING, "String or list of strings to replace"},
{CF_ANYSTRING, CF_DATA_TYPE_STRING, "Replacement string or list of strings"},
{"strings,lists", CF_DATA_TYPE_OPTION, "Whether to expect strings or list of strings"},
{NULL, CF_DATA_TYPE_NONE, NULL}
};

Expand Down Expand Up @@ -11873,7 +11964,7 @@
FnCallTypeNew("string_split", CF_DATA_TYPE_STRING_LIST, SPLITSTRING_ARGS, &FnCallStringSplit, "Convert a string in arg1 into a list of at most arg3 strings by splitting on a regular expression in arg2",
FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL, DEFAULT_ARGC),
FnCallTypeNew("string_replace", CF_DATA_TYPE_STRING, STRING_REPLACE_ARGS, &FnCallStringReplace, "Search through arg1, replacing occurences of arg2 with arg3.",
FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL, DEFAULT_ARGC),
FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL, ARGC(3, 4)),
FnCallTypeNew("string_trim", CF_DATA_TYPE_STRING, STRING_TRIM_ARGS, &FnCallStringTrim, "Trim whitespace from beginning and end of string",
FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL, DEFAULT_ARGC),
FnCallTypeNew("regex_replace", CF_DATA_TYPE_STRING, REGEX_REPLACE_ARGS, &FnCallRegReplace, "Replace occurrences of arg1 in arg2 with arg3, allowing backreferences. Perl-style options accepted in arg4.",
Expand Down
11 changes: 11 additions & 0 deletions tests/acceptance/01_vars/02_functions/string_replace.cf
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ bundle agent test
vars:
"test" string => "abcdefghij\t\n";
"test2" string => "(){}[].*?";
"match_list" slist => { "a", "b", "d" };
"sub_list" slist => { "b", "c", "e" };

# normal tests
"match_once" string => string_replace("abcd", "abc", "ABC");
Expand All @@ -35,6 +37,15 @@ bundle agent test
# empty cases
"empty" string => string_replace("", "abc", "ABC");
"empty2" string => string_replace("", "abc", "");

# list
"list_replace"
string => string_replace("abcd", "@(match_list)", "@(sub_list)", "lists");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try with function calls here; parsejson and expandrange for example.


"list_replace_inline_json"
string => string_replace(
"Hello World", '["World", "o"]', '["Everyone", "0"]', "lists"
);
}

#######################################################
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
{
"empty": "",
"empty2": "",
"list_replace": "ccce",
"list_replace_inline_json": "Hell0 Every0ne",
"match_list": [
"a",
"b",
"d"
],
"match_none": "abdc",
"match_none2": "abcdefghij\\t\\n",
"match_once": "ABCd",
Expand All @@ -12,6 +19,11 @@
"special": "1{}[].*?",
"special2": "()\\1[].*?",
"special3": "(){}[]\\2",
"sub_list": [
"b",
"c",
"e"
],
"test": "abcdefghij\\t\\n",
"test2": "(){}[].*?"
}
Loading