diff --git a/changelogs/fragments/65051-regex-replace-multiline.yaml b/changelogs/fragments/65051-regex-replace-multiline.yaml new file mode 100644 index 00000000000..3ce017cffdb --- /dev/null +++ b/changelogs/fragments/65051-regex-replace-multiline.yaml @@ -0,0 +1,2 @@ +minor_changes: +- regexp_replace filter - add multiline support for regex_replace filter (https://github.com/ansible/ansible/issues/61985) diff --git a/docs/docsite/rst/user_guide/playbooks_filters.rst b/docs/docsite/rst/user_guide/playbooks_filters.rst index 967d5185c6c..bb53b154ed3 100644 --- a/docs/docsite/rst/user_guide/playbooks_filters.rst +++ b/docs/docsite/rst/user_guide/playbooks_filters.rst @@ -1396,6 +1396,9 @@ To replace text in a string with regex, use the "regex_replace" filter:: # convert "localhost:80" to "localhost" {{ 'localhost:80' | regex_replace(':80') }} + # change a multiline string + {{ var | regex_replace('^', '#CommentThis#', multiline=True) }} + .. note:: If you want to match the whole string and you are using ``*`` make sure to always wraparound your regular expression with the start/end anchors. For example ``^(.*)$`` will always match only one result, while ``(.*)`` on some Python versions will match the whole string and an empty string at the end, which means it will make two replacements:: diff --git a/lib/ansible/plugins/filter/core.py b/lib/ansible/plugins/filter/core.py index d2ae68a2441..52721e53808 100644 --- a/lib/ansible/plugins/filter/core.py +++ b/lib/ansible/plugins/filter/core.py @@ -123,15 +123,16 @@ def fileglob(pathname): return [g for g in glob.glob(pathname) if os.path.isfile(g)] -def regex_replace(value='', pattern='', replacement='', ignorecase=False): +def regex_replace(value='', pattern='', replacement='', ignorecase=False, multiline=False): ''' Perform a `re.sub` returning a string ''' value = to_text(value, errors='surrogate_or_strict', nonstring='simplerepr') + flags = 0 if ignorecase: - flags = re.I - else: - flags = 0 + flags |= re.I + if multiline: + flags |= re.M _re = re.compile(pattern, flags=flags) return _re.sub(replacement, value) diff --git a/test/integration/targets/filter_core/files/foo.txt b/test/integration/targets/filter_core/files/foo.txt index a5839cee958..55fba527902 100644 --- a/test/integration/targets/filter_core/files/foo.txt +++ b/test/integration/targets/filter_core/files/foo.txt @@ -61,6 +61,9 @@ TODO: realpath follows symlinks. There isn't a test for this just now. TODO: add tests for set theory operations like union regex_replace = bar +# Check regex_replace with multiline +#bar +#bart regex_search = 0001 regex_findall = "['car', 'tar', 'bar']" regex_escape = \^f\.\*o\(\.\*\)\$ diff --git a/test/integration/targets/filter_core/templates/foo.j2 b/test/integration/targets/filter_core/templates/foo.j2 index 55893cc1321..9d895fdcedc 100644 --- a/test/integration/targets/filter_core/templates/foo.j2 +++ b/test/integration/targets/filter_core/templates/foo.j2 @@ -55,6 +55,8 @@ TODO: realpath follows symlinks. There isn't a test for this just now. TODO: add tests for set theory operations like union regex_replace = {{ 'foo' | regex_replace('^foo', 'bar') }} +# Check regex_replace with multiline +{{ '#foo\n#foot' | regex_replace('^#foo', '#bar', multiline=True) }} regex_search = {{ 'test_value_0001' | regex_search('([0-9]+)$')}} regex_findall = "{{ 'car\ntar\nfoo\nbar\n' | regex_findall('^.ar$', multiline=True) }}" regex_escape = {{ '^f.*o(.*)$' | regex_escape() }}