diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2024-04-01 10:23:37 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2024-04-01 10:23:37 +0200 |
commit | 221a53ed3d18217d06f7d8e0bdf9ce315ca2413c (patch) | |
tree | 389225f308a7eaba4469be80513b768839854b13 /libbuild2/functions-string.cxx | |
parent | d28970114e5807f57ae339764ac05384ef163379 (diff) |
Add $string.replace() function
Diffstat (limited to 'libbuild2/functions-string.cxx')
-rw-r--r-- | libbuild2/functions-string.cxx | 132 |
1 files changed, 131 insertions, 1 deletions
diff --git a/libbuild2/functions-string.cxx b/libbuild2/functions-string.cxx index 367923f..0458724 100644 --- a/libbuild2/functions-string.cxx +++ b/libbuild2/functions-string.cxx @@ -8,6 +8,103 @@ using namespace std; namespace build2 { + static string + replace (string&& s, value&& fv, value&& tv, optional<names>&& fs) + { + bool ic (false), fo (false), lo (false); + if (fs) + { + for (name& f: *fs) + { + string s (convert<string> (move (f))); + + if (s == "icase") + ic = true; + else if (s == "first_only") + fo = true; + else if (s == "last_only") + lo = true; + else + throw invalid_argument ("invalid flag '" + s + '\''); + } + } + + string f (convert<string> (move (fv))); + string t (convert<string> (move (tv))); + + if (f.empty ()) + throw invalid_argument ("empty <from> substring"); + + if (!s.empty ()) + { + // Note that we don't cache s.size () since the string size will be + // changing as we are replacing. In fact, we may end up with an empty + // string after a replacement. + + size_t fn (f.size ()); + + // Look for the substring forward in the [p, n) range. + // + auto find = [&s, &f, fn, ic] (size_t p) -> size_t + { + for (size_t n (s.size ()); p != n; ++p) + { + if (n - p >= fn && + (ic + ? icasecmp (f, s.c_str () + p, fn) + : s.compare (p, fn, f)) == 0) + return p; + } + + return string::npos; + }; + + // Look for the substring backard in the [0, n) range. + // + auto rfind = [&s, &f, fn, ic] (size_t n) -> size_t + { + if (n >= fn) + { + n -= fn; // Don't consider characters out of range. + + for (size_t p (n);; ) + { + if ((ic + ? icasecmp (f, s.c_str () + p, fn) + : s.compare (p, fn, f)) == 0) + return p; + + if (--p == 0) + break; + } + } + + return string::npos; + }; + + if (fo || lo) + { + size_t p (lo ? rfind (s.size ()) : find (0)); + + if (fo && lo && p != string::npos) + { + if (p != find (0)) + p = string::npos; + } + + if (p != string::npos) + s.replace (p, fn, t); + } + else + { + for (size_t p (0); (p = find (0)) != string::npos; p += fn) + s.replace (p, fn, t); + } + } + + return s; + } + static size_t find_index (const strings& vs, value&& v, optional<names>&& fs) { @@ -32,7 +129,7 @@ namespace build2 })); return i != vs.end () ? i - vs.begin () : vs.size (); - }; + } void string_functions (function_map& m) @@ -76,6 +173,39 @@ namespace build2 convert<string> (move (y))) == 0; }; + // $string.replace(<untyped>, <from>, <to> [, <flags>]) + // $replace(<string>, <from>, <to> [, <flags>]) + // + // Replace occurences of substring <from> with <to> in a string. The + // <from> substring must not be empty. + // + // The following flags are supported: + // + // icase - compare ignoring case + // + // first_only - only replace the first match + // + // last_only - only replace the last match + // + // + // If both `first_only` and `last_only` flags are specified, then <from> + // is replaced only if it occurs in the string once. + // + // See also `$regex.replace()`. + // + f["replace"] += [](string s, value f, value t, optional<names> fs) + { + return replace (move (s), move (f), move (t), move (fs)); + }; + + f[".replace"] += [](names s, value f, value t, optional<names> fs) + { + return names { + name ( + replace ( + convert<string> (move (s)), move (f), move (t), move (fs)))}; + }; + // $string.trim(<untyped>) // $trim(<string>) // |