From 30bca167f78565361bf1b073dbe4d663b321b851 Mon Sep 17 00:00:00 2001 From: LagunaElectric <6085143+LagunaElectric@users.noreply.github.com> Date: Thu, 12 Sep 2024 01:34:19 -0400 Subject: [PATCH 1/6] add replace_one function for string and string_builder --- src/gleam/string.gleam | 25 +++++++++++++++++++++++++ src/gleam/string_builder.gleam | 10 ++++++++++ src/gleam_stdlib.erl | 6 +++++- src/gleam_stdlib.mjs | 4 ++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/gleam/string.gleam b/src/gleam/string.gleam index e530c8fa..57abbf48 100644 --- a/src/gleam/string.gleam +++ b/src/gleam/string.gleam @@ -103,6 +103,31 @@ pub fn replace( |> string_builder.to_string } +/// Creates a new `String` by replacing the first occurrence of a given substring. +/// +/// ## Examples +/// +/// ```gleam +/// replace("www.example.com", each: ".", with: "-") +/// // -> "www-example.com" +/// ``` +/// +/// ```gleam +/// replace("a,b,c,d,e", each: ",", with: "/") +/// // -> "a/b,c,d,e" +/// ``` +/// +pub fn replace_one( + in string: String, + one_of pattern: String, + with substitute: String, +) -> String { + string + |> string_builder.from_string + |> string_builder.replace_one(one_of: pattern, with: substitute) + |> string_builder.to_string +} + /// Creates a new `String` with all the graphemes in the input `String` converted to /// lowercase. /// diff --git a/src/gleam/string_builder.gleam b/src/gleam/string_builder.gleam index 24445a20..f4afa420 100644 --- a/src/gleam/string_builder.gleam +++ b/src/gleam/string_builder.gleam @@ -205,6 +205,16 @@ pub fn replace( with substitute: String, ) -> StringBuilder +/// Replaces the first instance of a pattern with a given string substitute. +/// +@external(erlang, "gleam_stdlib", "string_replace_one") +@external(javascript, "../gleam_stdlib.mjs", "string_replace_one") +pub fn replace_one( + in builder: StringBuilder, + one_of pattern: String, + with substitute: String, +) -> StringBuilder + /// Compares two builders to determine if they have the same textual content. /// /// Comparing two iodata using the `==` operator may return `False` even if they diff --git a/src/gleam_stdlib.erl b/src/gleam_stdlib.erl index 1fc15b2d..0fd407e6 100644 --- a/src/gleam_stdlib.erl +++ b/src/gleam_stdlib.erl @@ -14,7 +14,8 @@ decode_tuple5/1, decode_tuple6/1, tuple_get/2, classify_dynamic/1, print/1, println/1, print_error/1, println_error/1, inspect/1, float_to_string/1, int_from_base_string/2, utf_codepoint_list_to_string/1, contains_string/2, - crop_string/2, base16_decode/1, string_replace/3, regex_replace/3, slice/3, bit_array_to_int_and_size/1 + crop_string/2, base16_decode/1, string_replace/3, regex_replace/3, slice/3, + bit_array_to_int_and_size/1, string_replace_one/3 ]). %% Taken from OTP's uri_string module @@ -553,6 +554,9 @@ base16_decode(String) -> string_replace(String, Pattern, Replacement) -> string:replace(String, Pattern, Replacement, all). +string_replace_one(String, Pattern, Replacement) -> + string:replace(String, Pattern, Replacement). + slice(String, Index, Length) -> case string:slice(String, Index, Length) of X when is_binary(X) -> X; diff --git a/src/gleam_stdlib.mjs b/src/gleam_stdlib.mjs index 50ebb46f..66747679 100644 --- a/src/gleam_stdlib.mjs +++ b/src/gleam_stdlib.mjs @@ -127,6 +127,10 @@ export function string_replace(string, target, substitute) { ); } +export function string_replace_one(string, target, substitute) { + return string.replace(target, substitute); +} + export function string_reverse(string) { return [...string].reverse().join(""); } From 2976f08a24acbd0cc4ab16760e35b7d89c3d491a Mon Sep 17 00:00:00 2001 From: LagunaElectric <6085143+LagunaElectric@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:42:16 -0400 Subject: [PATCH 2/6] add replace_one for regex --- src/gleam/regex.gleam | 24 ++++++++++++++++++++++++ src/gleam_stdlib.erl | 5 ++++- src/gleam_stdlib.mjs | 7 +++++++ test/gleam/regex_test.gleam | 12 ++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/gleam/regex.gleam b/src/gleam/regex.gleam index d1f8ef0a..0948c285 100644 --- a/src/gleam/regex.gleam +++ b/src/gleam/regex.gleam @@ -214,3 +214,27 @@ pub fn replace( in string: String, with substitute: String, ) -> String + +/// Creates a new `String` by replacing the first substring that matches the regular +/// expression. +/// +/// ## Examples +/// +/// ```gleam +/// let assert Ok(re) = regex.from_string("^https://") +/// replace(one_of: re, in: "https://example.com", with: "www.") +/// // -> "www.example.com" +/// ``` +/// +/// ```gleam +/// let assert Ok(re) = regex.from_string("[, +-]") +/// replace(one_of: re, in: "a,b-c d+e", with: "/") +/// // -> "a/b-c d+e" +/// ``` +@external(erlang, "gleam_stdlib", "regex_replace_one") +@external(javascript, "../gleam_stdlib.mjs", "regex_replace_one") +pub fn replace_one( + one_of pattern: Regex, + in string: String, + with substitute: String, +) -> String diff --git a/src/gleam_stdlib.erl b/src/gleam_stdlib.erl index 0fd407e6..f18f57b1 100644 --- a/src/gleam_stdlib.erl +++ b/src/gleam_stdlib.erl @@ -15,7 +15,7 @@ println/1, print_error/1, println_error/1, inspect/1, float_to_string/1, int_from_base_string/2, utf_codepoint_list_to_string/1, contains_string/2, crop_string/2, base16_decode/1, string_replace/3, regex_replace/3, slice/3, - bit_array_to_int_and_size/1, string_replace_one/3 + bit_array_to_int_and_size/1, string_replace_one/3, regex_replace_one/3 ]). %% Taken from OTP's uri_string module @@ -267,6 +267,9 @@ regex_scan(Regex, String) -> regex_replace(Regex, Subject, Replacement) -> re:replace(Subject, Regex, Replacement, [global, {return, binary}]). +regex_replace_one(Regex, Subject, Replacement) -> + re:replace(Subject, Regex, Replacement, [{return, binary}]). + base_decode64(S) -> try {ok, base64:decode(S)} catch error:_ -> {error, nil} diff --git a/src/gleam_stdlib.mjs b/src/gleam_stdlib.mjs index 66747679..1cf07000 100644 --- a/src/gleam_stdlib.mjs +++ b/src/gleam_stdlib.mjs @@ -455,6 +455,13 @@ export function regex_replace(regex, original_string, replacement) { return original_string.replaceAll(regex, replacement); } +export function regex_replace_one(regex, original_string, replacement) { + // Forcibly strip the g flag from the regex, if it's present + let flags = regex.toString().split("/").pop().replace("g", ""); + let match = new RegExp(regex, flags); + return original_string.replace(match, replacement); +} + export function new_map() { return Dict.new(); } diff --git a/test/gleam/regex_test.gleam b/test/gleam/regex_test.gleam index e5175779..19ec9755 100644 --- a/test/gleam/regex_test.gleam +++ b/test/gleam/regex_test.gleam @@ -185,3 +185,15 @@ pub fn replace_3_test() { regex.replace(re, "🐈🐈 are great!", "🐕") |> should.equal("🐕🐕 are great!") } + +pub fn replace_one_test() { + let assert Ok(re) = regex.from_string("🐈") + regex.replace_one(in: "🐈🐈 are great!", one_of: re, with: "🐕") + |> should.equal("🐕🐈 are great!") +} + +pub fn replace_one_of_many_test() { + let assert Ok(re) = regex.from_string("[, +-]") + regex.replace_one(one_of: re, in: "a,b-c d+e", with: "/") + |> should.equal("a/b-c d+e") +} From 38318980f42d12a24bada77ea21fe387540bb741 Mon Sep 17 00:00:00 2001 From: LagunaElectric <6085143+LagunaElectric@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:54:35 -0400 Subject: [PATCH 3/6] rename replace_one to replace_first --- src/gleam/regex.gleam | 6 +++--- src/gleam/string.gleam | 4 ++-- src/gleam/string_builder.gleam | 6 +++--- src/gleam_stdlib.erl | 6 +++--- src/gleam_stdlib.mjs | 4 ++-- test/gleam/regex_test.gleam | 8 ++++---- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/gleam/regex.gleam b/src/gleam/regex.gleam index 0948c285..8b8f7470 100644 --- a/src/gleam/regex.gleam +++ b/src/gleam/regex.gleam @@ -231,9 +231,9 @@ pub fn replace( /// replace(one_of: re, in: "a,b-c d+e", with: "/") /// // -> "a/b-c d+e" /// ``` -@external(erlang, "gleam_stdlib", "regex_replace_one") -@external(javascript, "../gleam_stdlib.mjs", "regex_replace_one") -pub fn replace_one( +@external(erlang, "gleam_stdlib", "regex_replace_first") +@external(javascript, "../gleam_stdlib.mjs", "regex_replace_first") +pub fn replace_first( one_of pattern: Regex, in string: String, with substitute: String, diff --git a/src/gleam/string.gleam b/src/gleam/string.gleam index 57abbf48..17d722b5 100644 --- a/src/gleam/string.gleam +++ b/src/gleam/string.gleam @@ -117,14 +117,14 @@ pub fn replace( /// // -> "a/b,c,d,e" /// ``` /// -pub fn replace_one( +pub fn replace_first( in string: String, one_of pattern: String, with substitute: String, ) -> String { string |> string_builder.from_string - |> string_builder.replace_one(one_of: pattern, with: substitute) + |> string_builder.replace_first(one_of: pattern, with: substitute) |> string_builder.to_string } diff --git a/src/gleam/string_builder.gleam b/src/gleam/string_builder.gleam index f4afa420..8c538f2f 100644 --- a/src/gleam/string_builder.gleam +++ b/src/gleam/string_builder.gleam @@ -207,9 +207,9 @@ pub fn replace( /// Replaces the first instance of a pattern with a given string substitute. /// -@external(erlang, "gleam_stdlib", "string_replace_one") -@external(javascript, "../gleam_stdlib.mjs", "string_replace_one") -pub fn replace_one( +@external(erlang, "gleam_stdlib", "string_replace_first") +@external(javascript, "../gleam_stdlib.mjs", "string_replace_first") +pub fn replace_first( in builder: StringBuilder, one_of pattern: String, with substitute: String, diff --git a/src/gleam_stdlib.erl b/src/gleam_stdlib.erl index f18f57b1..df33c7cf 100644 --- a/src/gleam_stdlib.erl +++ b/src/gleam_stdlib.erl @@ -15,7 +15,7 @@ println/1, print_error/1, println_error/1, inspect/1, float_to_string/1, int_from_base_string/2, utf_codepoint_list_to_string/1, contains_string/2, crop_string/2, base16_decode/1, string_replace/3, regex_replace/3, slice/3, - bit_array_to_int_and_size/1, string_replace_one/3, regex_replace_one/3 + bit_array_to_int_and_size/1, string_replace_first/3, regex_replace_first/3 ]). %% Taken from OTP's uri_string module @@ -267,7 +267,7 @@ regex_scan(Regex, String) -> regex_replace(Regex, Subject, Replacement) -> re:replace(Subject, Regex, Replacement, [global, {return, binary}]). -regex_replace_one(Regex, Subject, Replacement) -> +regex_replace_first(Regex, Subject, Replacement) -> re:replace(Subject, Regex, Replacement, [{return, binary}]). base_decode64(S) -> @@ -557,7 +557,7 @@ base16_decode(String) -> string_replace(String, Pattern, Replacement) -> string:replace(String, Pattern, Replacement, all). -string_replace_one(String, Pattern, Replacement) -> +string_replace_first(String, Pattern, Replacement) -> string:replace(String, Pattern, Replacement). slice(String, Index, Length) -> diff --git a/src/gleam_stdlib.mjs b/src/gleam_stdlib.mjs index 1cf07000..0ee2370e 100644 --- a/src/gleam_stdlib.mjs +++ b/src/gleam_stdlib.mjs @@ -127,7 +127,7 @@ export function string_replace(string, target, substitute) { ); } -export function string_replace_one(string, target, substitute) { +export function string_replace_first(string, target, substitute) { return string.replace(target, substitute); } @@ -455,7 +455,7 @@ export function regex_replace(regex, original_string, replacement) { return original_string.replaceAll(regex, replacement); } -export function regex_replace_one(regex, original_string, replacement) { +export function regex_replace_first(regex, original_string, replacement) { // Forcibly strip the g flag from the regex, if it's present let flags = regex.toString().split("/").pop().replace("g", ""); let match = new RegExp(regex, flags); diff --git a/test/gleam/regex_test.gleam b/test/gleam/regex_test.gleam index 19ec9755..136ecd3d 100644 --- a/test/gleam/regex_test.gleam +++ b/test/gleam/regex_test.gleam @@ -186,14 +186,14 @@ pub fn replace_3_test() { |> should.equal("🐕🐕 are great!") } -pub fn replace_one_test() { +pub fn replace_first_test() { let assert Ok(re) = regex.from_string("🐈") - regex.replace_one(in: "🐈🐈 are great!", one_of: re, with: "🐕") + regex.replace_first(in: "🐈🐈 are great!", one_of: re, with: "🐕") |> should.equal("🐕🐈 are great!") } -pub fn replace_one_of_many_test() { +pub fn replace_first_of_many_test() { let assert Ok(re) = regex.from_string("[, +-]") - regex.replace_one(one_of: re, in: "a,b-c d+e", with: "/") + regex.replace_first(one_of: re, in: "a,b-c d+e", with: "/") |> should.equal("a/b-c d+e") } From 5e36083da56470a00739a6d4a5c676f8a0f10b78 Mon Sep 17 00:00:00 2001 From: LagunaElectric <6085143+LagunaElectric@users.noreply.github.com> Date: Thu, 12 Sep 2024 19:46:03 -0400 Subject: [PATCH 4/6] add test for string.replace_first --- test/gleam/string_test.gleam | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/gleam/string_test.gleam b/test/gleam/string_test.gleam index 6d3031e4..1ea15c2b 100644 --- a/test/gleam/string_test.gleam +++ b/test/gleam/string_test.gleam @@ -92,6 +92,12 @@ pub fn replace_test() { |> should.equal("Gleam++Erlang++Elixir") } +pub fn replace_first_test() { + "Gleam,Erlang,Elixir" + |> string.replace_first(",", "++") + |> should.equal("Gleam++Erlang,Elixir") +} + pub fn append_test() { "Test" |> string.append(" Me") From 13cf9f42a5d2fa02c53e3f0d5c0ef0465c973a86 Mon Sep 17 00:00:00 2001 From: LagunaElectric <6085143+LagunaElectric@users.noreply.github.com> Date: Fri, 13 Sep 2024 01:18:04 -0400 Subject: [PATCH 5/6] add a couple more tests for good measure --- test/gleam/string_test.gleam | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/gleam/string_test.gleam b/test/gleam/string_test.gleam index 1ea15c2b..ac311fee 100644 --- a/test/gleam/string_test.gleam +++ b/test/gleam/string_test.gleam @@ -92,12 +92,22 @@ pub fn replace_test() { |> should.equal("Gleam++Erlang++Elixir") } -pub fn replace_first_test() { +pub fn replace_first_0_test() { "Gleam,Erlang,Elixir" |> string.replace_first(",", "++") |> should.equal("Gleam++Erlang,Elixir") } +pub fn replace_first_1_test() { + string.replace_first(in: "🐈🐈 are great!", one_of: "🐈", with: "🐕") + |> should.equal("🐕🐈 are great!") +} + +pub fn replace_first_2_test() { + string.replace_first(one_of: ",", in: "a,b,c,d,e", with: "/") + |> should.equal("a/b,c,d,e") +} + pub fn append_test() { "Test" |> string.append(" Me") From 8c12160c8eb8528d822b47934fee094df6a4861a Mon Sep 17 00:00:00 2001 From: LagunaElectric <6085143+LagunaElectric@users.noreply.github.com> Date: Fri, 13 Sep 2024 12:11:58 -0400 Subject: [PATCH 6/6] update docstrings --- src/gleam/regex.gleam | 4 ++-- src/gleam/string.gleam | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gleam/regex.gleam b/src/gleam/regex.gleam index 8b8f7470..a849ed68 100644 --- a/src/gleam/regex.gleam +++ b/src/gleam/regex.gleam @@ -222,13 +222,13 @@ pub fn replace( /// /// ```gleam /// let assert Ok(re) = regex.from_string("^https://") -/// replace(one_of: re, in: "https://example.com", with: "www.") +/// replace_first(one_of: re, in: "https://example.com", with: "www.") /// // -> "www.example.com" /// ``` /// /// ```gleam /// let assert Ok(re) = regex.from_string("[, +-]") -/// replace(one_of: re, in: "a,b-c d+e", with: "/") +/// replace_first(one_of: re, in: "a,b-c d+e", with: "/") /// // -> "a/b-c d+e" /// ``` @external(erlang, "gleam_stdlib", "regex_replace_first") diff --git a/src/gleam/string.gleam b/src/gleam/string.gleam index 17d722b5..e6d69f5e 100644 --- a/src/gleam/string.gleam +++ b/src/gleam/string.gleam @@ -108,12 +108,12 @@ pub fn replace( /// ## Examples /// /// ```gleam -/// replace("www.example.com", each: ".", with: "-") +/// replace_first("www.example.com", each: ".", with: "-") /// // -> "www-example.com" /// ``` /// /// ```gleam -/// replace("a,b,c,d,e", each: ",", with: "/") +/// replace_first("a,b,c,d,e", each: ",", with: "/") /// // -> "a/b,c,d,e" /// ``` ///