Module: Ruby::Rego::Builtins::Strings
- Extended by:
- RegistryHelpers
- Defined in:
- lib/ruby/rego/builtins/strings.rb,
lib/ruby/rego/builtins/strings/trim.rb,
lib/ruby/rego/builtins/strings/split.rb,
lib/ruby/rego/builtins/strings/concat.rb,
lib/ruby/rego/builtins/strings/search.rb,
lib/ruby/rego/builtins/strings/helpers.rb,
lib/ruby/rego/builtins/strings/case_ops.rb,
lib/ruby/rego/builtins/strings/matching.rb,
lib/ruby/rego/builtins/strings/substring.rb,
lib/ruby/rego/builtins/strings/transform.rb,
lib/ruby/rego/builtins/strings/formatting.rb,
lib/ruby/rego/builtins/strings/trim_helpers.rb,
lib/ruby/rego/builtins/strings/number_helpers.rb
Overview
Built-in string helpers.
Constant Summary collapse
- STRING_FUNCTIONS =
{ "concat" => { arity: 2, handler: :concat }, "contains" => { arity: 2, handler: :contains }, "startswith" => { arity: 2, handler: :startswith }, "endswith" => { arity: 2, handler: :endswith }, "format_int" => { arity: 2, handler: :format_int }, "indexof" => { arity: 2, handler: :indexof }, "lower" => { arity: 1, handler: :lower }, "upper" => { arity: 1, handler: :upper }, "split" => { arity: 2, handler: :split }, "sprintf" => { arity: 2, handler: :sprintf }, "substring" => { arity: 3, handler: :substring }, "trim" => { arity: 2, handler: :trim }, "trim_left" => { arity: 2, handler: :trim_left }, "trim_right" => { arity: 2, handler: :trim_right }, "trim_space" => { arity: 1, handler: :trim_space }, "trim_prefix" => { arity: 2, handler: :trim_prefix }, "trim_suffix" => { arity: 2, handler: :trim_suffix }, "replace" => { arity: 3, handler: :replace }, "strings.reverse" => { arity: 1, handler: :strings_reverse }, "strings.count" => { arity: 2, handler: :strings_count }, "indexof_n" => { arity: 2, handler: :indexof_n }, "strings.replace_n" => { arity: 2, handler: :replace_n }, "strings.any_prefix_match" => { arity: 2, handler: :any_prefix_match }, "strings.any_suffix_match" => { arity: 2, handler: :any_suffix_match } }.freeze
- BASE_DIGITS =
%w[ 0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z ].freeze
Class Method Summary collapse
-
.any_prefix_match(search, base) ⇒ Ruby::Rego::BooleanValue
True if any string in
searchstarts with any string inbase, matching OPA’sstrings.any_prefix_match. -
.any_suffix_match(search, base) ⇒ Ruby::Rego::BooleanValue
True if any string in
searchends with any string inbase, matching OPA’sstrings.any_suffix_match. -
.concat(delimiter, array) ⇒ Ruby::Rego::StringValue
-
.contains(haystack, needle) ⇒ Ruby::Rego::BooleanValue
-
.endswith(string, suffix) ⇒ Ruby::Rego::BooleanValue
-
.format_int(number, base) ⇒ Ruby::Rego::StringValue
-
.indexof(haystack, needle) ⇒ Ruby::Rego::NumberValue
-
.indexof_n(string, search) ⇒ Ruby::Rego::ArrayValue
All start indices (by character) of
searchinstring, including overlapping matches (matching OPA: indexof_n(“aaa”,”aa”) => [0,1]). -
.lower(string) ⇒ Ruby::Rego::StringValue
-
.register! ⇒ Ruby::Rego::Builtins::BuiltinRegistry
-
.replace(string, old, replacement) ⇒ Ruby::Rego::StringValue
Replaces every non-overlapping literal occurrence of
oldwithnew(matching OPA’sreplace; the search is literal, not a regex). -
.replace_n(patterns, value) ⇒ Ruby::Rego::StringValue
Replaces occurrences of each key in
patternswith its value, matching OPA’sstrings.replace_n(modeled on Go’sstrings.Replacer): keys are applied in ascending sort order with a single left-to-right pass, replaced text is never rescanned, and on overlapping matches the earliest-sorted key wins. -
.split(string, delimiter) ⇒ Ruby::Rego::ArrayValue
-
.sprintf(format, args) ⇒ Ruby::Rego::StringValue
-
.startswith(string, prefix) ⇒ Ruby::Rego::BooleanValue
-
.strings_count(string, search) ⇒ Ruby::Rego::NumberValue
Counts non-overlapping occurrences of
searchinstring(OPA’s strings.count). -
.strings_reverse(string) ⇒ Ruby::Rego::StringValue
Reverses by Unicode codepoint (not grapheme cluster), matching OPA.
-
.substring(string, offset, length) ⇒ Ruby::Rego::StringValue
-
.trim(string, cutset) ⇒ Ruby::Rego::StringValue
-
.trim_left(string, cutset) ⇒ Ruby::Rego::StringValue
-
.trim_prefix(string, prefix) ⇒ Ruby::Rego::StringValue
-
.trim_right(string, cutset) ⇒ Ruby::Rego::StringValue
-
.trim_space(string) ⇒ Ruby::Rego::StringValue
-
.trim_suffix(string, suffix) ⇒ Ruby::Rego::StringValue
-
.upper(string) ⇒ Ruby::Rego::StringValue
Methods included from RegistryHelpers
Class Method Details
.any_prefix_match(search, base) ⇒ Ruby::Rego::BooleanValue
True if any string in search starts with any string in base, matching OPA’s
strings.any_prefix_match. Each argument may be a string, array, or set of
strings; an empty collection yields false, and an empty base string is a prefix
of everything.
16 17 18 19 20 |
# File 'lib/ruby/rego/builtins/strings/matching.rb', line 16 def self.any_prefix_match(search, base) searches = string_collection(search, name: "strings.any_prefix_match search") bases = string_collection(base, name: "strings.any_prefix_match base") BooleanValue.new(any_pair_match?(searches, bases) { |candidate, affix| candidate.start_with?(affix) }) end |
.any_suffix_match(search, base) ⇒ Ruby::Rego::BooleanValue
True if any string in search ends with any string in base, matching OPA’s
strings.any_suffix_match. Argument typing mirrors any_prefix_match.
28 29 30 31 32 |
# File 'lib/ruby/rego/builtins/strings/matching.rb', line 28 def self.any_suffix_match(search, base) searches = string_collection(search, name: "strings.any_suffix_match search") bases = string_collection(base, name: "strings.any_suffix_match base") BooleanValue.new(any_pair_match?(searches, bases) { |candidate, affix| candidate.end_with?(affix) }) end |
.concat(delimiter, array) ⇒ Ruby::Rego::StringValue
11 12 13 14 15 |
# File 'lib/ruby/rego/builtins/strings/concat.rb', line 11 def self.concat(delimiter, array) delimiter_string = string_value(delimiter, context: "concat delimiter") parts = string_array(array, name: "concat") StringValue.new(parts.join(delimiter_string)) end |
.contains(haystack, needle) ⇒ Ruby::Rego::BooleanValue
11 12 13 14 15 16 17 18 19 |
# File 'lib/ruby/rego/builtins/strings/search.rb', line 11 def self.contains(haystack, needle) haystack_text, needle_text = string_pair( haystack, needle, left_context: "contains haystack", right_context: "contains needle" ) BooleanValue.new(haystack_text.include?(needle_text)) end |
.endswith(string, suffix) ⇒ Ruby::Rego::BooleanValue
37 38 39 40 41 42 43 44 45 |
# File 'lib/ruby/rego/builtins/strings/search.rb', line 37 def self.endswith(string, suffix) string_text, suffix_text = string_pair( string, suffix, left_context: "endswith string", right_context: "endswith suffix" ) BooleanValue.new(string_text.end_with?(suffix_text)) end |
.format_int(number, base) ⇒ Ruby::Rego::StringValue
13 14 15 16 17 18 |
# File 'lib/ruby/rego/builtins/strings/formatting.rb', line 13 def self.format_int(number, base) number_value = NumericHelpers.integer_value(number, context: "format_int number") base_value = NumericHelpers.integer_value(base, context: "format_int base") ensure_base(base_value) StringValue.new(base_encode(number_value, base_value)) end |
.indexof(haystack, needle) ⇒ Ruby::Rego::NumberValue
50 51 52 53 54 55 56 57 58 59 |
# File 'lib/ruby/rego/builtins/strings/search.rb', line 50 def self.indexof(haystack, needle) haystack_text, needle_text = string_pair( haystack, needle, left_context: "indexof haystack", right_context: "indexof needle" ) index = haystack_text.index(needle_text) NumberValue.new(index || -1) end |
.indexof_n(string, search) ⇒ Ruby::Rego::ArrayValue
All start indices (by character) of search in string, including
overlapping matches (matching OPA: indexof_n(“aaa”,”aa”) => [0,1]).
An empty search yields an undefined result (matching OPA).
81 82 83 84 85 86 87 |
# File 'lib/ruby/rego/builtins/strings/search.rb', line 81 def self.indexof_n(string, search) haystack, needle = string_pair( string, search, left_context: "indexof_n string", right_context: "indexof_n search" ) raise_empty_search("indexof_n") if needle.empty? ArrayValue.new(match_indices(haystack, needle).map { |index| NumberValue.new(index) }) end |
.lower(string) ⇒ Ruby::Rego::StringValue
10 11 12 |
# File 'lib/ruby/rego/builtins/strings/case_ops.rb', line 10 def self.lower(string) StringValue.new(string_value(string, context: "lower").downcase) end |
.register! ⇒ Ruby::Rego::Builtins::BuiltinRegistry
55 56 57 58 59 60 61 |
# File 'lib/ruby/rego/builtins/strings.rb', line 55 def self.register! registry = BuiltinRegistry.instance register_configured_functions(registry, STRING_FUNCTIONS) registry end |
.replace(string, old, replacement) ⇒ Ruby::Rego::StringValue
Replaces every non-overlapping literal occurrence of old with new
(matching OPA’s replace; the search is literal, not a regex).
15 16 17 18 19 20 |
# File 'lib/ruby/rego/builtins/strings/transform.rb', line 15 def self.replace(string, old, replacement) text = string_value(string, context: "replace string") old_text = string_value(old, context: "replace substring") new_text = string_value(replacement, context: "replace value") StringValue.new(text.gsub(old_text) { new_text }) end |
.replace_n(patterns, value) ⇒ Ruby::Rego::StringValue
Replaces occurrences of each key in patterns with its value, matching OPA’s
strings.replace_n (modeled on Go’s strings.Replacer): keys are applied in
ascending sort order with a single left-to-right pass, replaced text is never
rescanned, and on overlapping matches the earliest-sorted key wins.
One deliberate divergence from OPA: scanning is by Unicode codepoint, not byte. This matters only for an empty (“”) key against multibyte text — OPA (byte-based) inserts the replacement between a character’s bytes, producing invalid UTF-8, whereas this inserts only at codepoint boundaries, keeping the output valid UTF-8.
43 44 45 46 47 |
# File 'lib/ruby/rego/builtins/strings/transform.rb', line 43 def self.replace_n(patterns, value) mapping = string_hash(patterns, name: "strings.replace_n patterns") text = string_value(value, context: "strings.replace_n value") StringValue.new(apply_replace_n(text, mapping)) end |
.split(string, delimiter) ⇒ Ruby::Rego::ArrayValue
11 12 13 14 15 |
# File 'lib/ruby/rego/builtins/strings/split.rb', line 11 def self.split(string, delimiter) string_text = string_value(string, context: "split string") delimiter_text = string_value(delimiter, context: "split delimiter") ArrayValue.new(string_text.split(delimiter_text, -1)) end |
.sprintf(format, args) ⇒ Ruby::Rego::StringValue
23 24 25 26 27 28 29 |
# File 'lib/ruby/rego/builtins/strings/formatting.rb', line 23 def self.sprintf(format, args) format_value = string_value(format, context: "sprintf format") values = sprintf_values(args) StringValue.new(Kernel.sprintf(format_value, *values)) rescue ArgumentError, ::TypeError => error raise_sprintf_error(error) end |
.startswith(string, prefix) ⇒ Ruby::Rego::BooleanValue
24 25 26 27 28 29 30 31 32 |
# File 'lib/ruby/rego/builtins/strings/search.rb', line 24 def self.startswith(string, prefix) string_text, prefix_text = string_pair( string, prefix, left_context: "startswith string", right_context: "startswith prefix" ) BooleanValue.new(string_text.start_with?(prefix_text)) end |
.strings_count(string, search) ⇒ Ruby::Rego::NumberValue
Counts non-overlapping occurrences of search in string (OPA’s
strings.count). An empty search counts as the codepoint count plus one.
67 68 69 70 71 72 |
# File 'lib/ruby/rego/builtins/strings/search.rb', line 67 def self.strings_count(string, search) haystack, needle = string_pair( string, search, left_context: "strings.count string", right_context: "strings.count search" ) NumberValue.new(haystack.scan(needle).size) end |
.strings_reverse(string) ⇒ Ruby::Rego::StringValue
Reverses by Unicode codepoint (not grapheme cluster), matching OPA.
26 27 28 |
# File 'lib/ruby/rego/builtins/strings/transform.rb', line 26 def self.strings_reverse(string) StringValue.new(string_value(string, context: "strings.reverse").reverse) end |
.substring(string, offset, length) ⇒ Ruby::Rego::StringValue
12 13 14 15 16 17 18 |
# File 'lib/ruby/rego/builtins/strings/substring.rb', line 12 def self.substring(string, offset, length) string_text = string_value(string, context: "substring string") offset_value = NumericHelpers.non_negative_integer(offset, context: "substring offset") length_value = NumericHelpers.non_negative_integer(length, context: "substring length") substring = string_text.slice(offset_value, length_value) || "" StringValue.new(substring) end |
.trim(string, cutset) ⇒ Ruby::Rego::StringValue
11 12 13 |
# File 'lib/ruby/rego/builtins/strings/trim.rb', line 11 def self.trim(string, cutset) trim_with_cutset(string, cutset, { mode: :both, name: "trim" }) end |
.trim_left(string, cutset) ⇒ Ruby::Rego::StringValue
18 19 20 |
# File 'lib/ruby/rego/builtins/strings/trim.rb', line 18 def self.trim_left(string, cutset) trim_with_cutset(string, cutset, { mode: :left, name: "trim_left" }) end |
.trim_prefix(string, prefix) ⇒ Ruby::Rego::StringValue
38 39 40 41 42 43 |
# File 'lib/ruby/rego/builtins/strings/trim.rb', line 38 def self.trim_prefix(string, prefix) text, prefix_text = string_pair( string, prefix, left_context: "trim_prefix string", right_context: "trim_prefix prefix" ) StringValue.new(text.delete_prefix(prefix_text)) end |
.trim_right(string, cutset) ⇒ Ruby::Rego::StringValue
25 26 27 |
# File 'lib/ruby/rego/builtins/strings/trim.rb', line 25 def self.trim_right(string, cutset) trim_with_cutset(string, cutset, { mode: :right, name: "trim_right" }) end |
.trim_space(string) ⇒ Ruby::Rego::StringValue
31 32 33 |
# File 'lib/ruby/rego/builtins/strings/trim.rb', line 31 def self.trim_space(string) StringValue.new(string_value(string, context: "trim_space").strip) end |
.trim_suffix(string, suffix) ⇒ Ruby::Rego::StringValue
48 49 50 51 52 53 |
# File 'lib/ruby/rego/builtins/strings/trim.rb', line 48 def self.trim_suffix(string, suffix) text, suffix_text = string_pair( string, suffix, left_context: "trim_suffix string", right_context: "trim_suffix suffix" ) StringValue.new(text.delete_suffix(suffix_text)) end |
.upper(string) ⇒ Ruby::Rego::StringValue
16 17 18 |
# File 'lib/ruby/rego/builtins/strings/case_ops.rb', line 16 def self.upper(string) StringValue.new(string_value(string, context: "upper").upcase) end |