From 7513f1757afaebb734601c81a9fdeac640e991ad Mon Sep 17 00:00:00 2001 From: Adam Riley Date: Thu, 23 Apr 2026 09:37:38 +0100 Subject: [PATCH 1/6] Add trim methods --- .../Typed_DB_Column_Implementations.enso | 22 +++++ .../Typed_Column_Implementations.enso | 32 +++++++ .../src/Refined_Types/Text_Column.enso | 85 +++++++++++++++++++ 3 files changed, 139 insertions(+) diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Type_Refinements/Typed_DB_Column_Implementations.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Type_Refinements/Typed_DB_Column_Implementations.enso index ee907d4dfb93..1f7bc3c5ba22 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Type_Refinements/Typed_DB_Column_Implementations.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Type_Refinements/Typed_DB_Column_Implementations.enso @@ -188,6 +188,28 @@ type DB_Text_Column_Implementation new_name = (naming_helper column).function_name "pad_right" [column, length, with_pad] make_op column "PAD_RIGHT" [length, with_pad] new_name + trim (column : Column & DB_Column) (what : Column | Text | Any) (where : Location) = + Value_Type_Checks.is_text column + parsed_what = if what.is_a Column && what.value_type == Value_Type.Null then Nothing else what + new_name = (naming_helper column).function_name "trim" [column, what, where] + operation_name = case where of + Location.Left -> "LTRIM" + Location.Right -> "RTRIM" + Location.Both -> "TRIM" + make_op column operation_name [parsed_what] new_name + + trim_left (column : Column & DB_Column) (what : Column | Text | Any) = + Value_Type_Checks.is_text column + parsed_what = if what.is_a Column && what.value_type == Value_Type.Null then Nothing else what + new_name = (naming_helper column).function_name "trim_left" [column, what] + make_op column "LTRIM" [parsed_what] new_name + + trim_right (column : Column & DB_Column) (what : Column | Text | Any) = + Value_Type_Checks.is_text column + parsed_what = if what.is_a Column && what.value_type == Value_Type.Null then Nothing else what + new_name = (naming_helper column).function_name "trim_right" [column, what] + make_op column "RTRIM" [parsed_what] new_name + parse_json column on_problems = _ = [column, on_problems] Unimplemented.throw "parse_json is not implemented for database backends." diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Typed_Column_Implementations.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Typed_Column_Implementations.enso index 487e7fd3c8ea..952078ebe1eb 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Typed_Column_Implementations.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Typed_Column_Implementations.enso @@ -235,6 +235,24 @@ type In_Memory_Text_Column_Implementation new_name = naming_helper.function_name "pad_right" [this_column, length, with_pad] _apply_pad this_column length with_pad Location.Right new_name + trim (this_column : Column & In_Memory_Column) (what : Column | Text | Any) (where : Location) = + Value_Type_Checks.is_text this_column + parsed_what = if what.is_a Column && what.value_type == Value_Type.Null then "" else what + new_name = naming_helper.function_name "trim" [this_column, what, where] + _apply_trim this_column parsed_what where new_name + + trim_left (this_column : Column & In_Memory_Column) (what : Column | Text | Any) = + Value_Type_Checks.is_text this_column + parsed_what = if what.is_a Column && what.value_type == Value_Type.Null then "" else what + new_name = naming_helper.function_name "trim_left" [this_column, what] + _apply_trim this_column parsed_what Location.Left new_name + + trim_right (this_column : Column & In_Memory_Column) (what : Column | Text | Any) = + Value_Type_Checks.is_text this_column + parsed_what = if what.is_a Column && what.value_type == Value_Type.Null then "" else what + new_name = naming_helper.function_name "trim_right" [this_column, what] + _apply_trim this_column parsed_what Location.Right new_name + parse_json (column : Column & In_Memory_Column) (on_problems:Problem_Behavior) = Value_Type_Checks.is_text column new_name = naming_helper.function_name "parse_json" [column] @@ -298,3 +316,17 @@ private _apply_pad (this_column : Column & In_Memory_Column) (length : Column | _apply_binary_map this_column (text -> pad-> do_pad text length pad) with_pad new_name skip_nulls=False expected_result_type=Value_Type.Char else apply_unary_map this_column new_name (text -> do_pad text length with_pad) expected_result_type=Value_Type.Char nothing_unchanged=False + +private _apply_trim (this_column : Column & In_Memory_Column) (what : Column | Text | Any) location:Location new_name:Text = + do_trim text chars = + if text.is_nothing then Nothing else + if chars.is_nothing || chars.is_empty then text.trim location else text.trim location chars + + case what of + _ : Text -> + trim_fn = if what.is_empty then t-> t.trim location else + t-> t.trim location what + apply_unary_map this_column new_name trim_fn expected_result_type=Value_Type.Char + _ : Column -> + Value_Type_Checks.is_text what + _apply_binary_map this_column do_trim what new_name skip_nulls=False expected_result_type=Value_Type.Char diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Refined_Types/Text_Column.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Refined_Types/Text_Column.enso index a7cf39ca2a28..99b06ec6bd4a 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Refined_Types/Text_Column.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Refined_Types/Text_Column.enso @@ -288,6 +288,91 @@ type Text_Column pad_right self (length : Column | Integer | Nothing) (with_pad : Column | Text | Any = " ") -> Column & Text_Column = self.operations_implementation.pad_right self.column length with_pad + ## --- + group: Standard.Base.Text + icon: text + --- + Trims whitespace or specified characters from both ends of each element in the column. + + Arguments: + - what: The text to trim. Defaults to whitespace. + - where: The location to trim from (Left, Right, or Both). Defaults to Both. + + ## Examples + ### Trim whitespace from both ends of values in a column. + + ``` + import Standard.Examples + + example_trim = Examples.text_column_1.trim + ``` + + ### Trim a specific character from the left side only. + + ``` + import Standard.Examples + + example_trim_custom = Examples.text_column_1.trim "x" ..Left + ``` + trim self (what : Column | Text | Any = " ") (where : Location = ..Both) -> Column & Text_Column = + self.operations_implementation.trim self.column what where + + ## --- + group: Standard.Base.Text + icon: text + --- + Trims whitespace or specified characters from the start of each element in the column. + + Arguments: + - what: The text to trim. Defaults to whitespace. + + ## Examples + ### Trim whitespace from the start of values in a column. + + ``` + import Standard.Examples + + example_trim_left = Examples.text_column_1.trim_left + ``` + + ### Trim a specific character from the start. + + ``` + import Standard.Examples + + example_trim_left_custom = Examples.text_column_1.trim_left "x" + ``` + trim_left self (what : Column | Text | Any = " ") -> Column & Text_Column = + self.operations_implementation.trim_left self.column what + + ## --- + group: Standard.Base.Text + icon: text + --- + Trims whitespace or specified characters from the end of each element in the column. + + Arguments: + - what: The text to trim. Defaults to whitespace. + + ## Examples + ### Trim whitespace from the end of values in a column. + + ``` + import Standard.Examples + + example_trim_right = Examples.text_column_1.trim_right + ``` + + ### Trim a specific character from the end. + + ``` + import Standard.Examples + + example_trim_right_custom = Examples.text_column_1.trim_right "x" + ``` + trim_right self (what : Column | Text | Any = " ") -> Column & Text_Column = + self.operations_implementation.trim_right self.column what + ## --- group: Standard.Base.Conversions icon: convert From 458e4a0dcbfae8aff433b2b4aaf30053c375aaa4 Mon Sep 17 00:00:00 2001 From: Adam Riley Date: Tue, 28 Apr 2026 09:25:37 +0100 Subject: [PATCH 2/6] Missed import --- .../Type_Refinements/Typed_DB_Column_Implementations.enso | 1 + 1 file changed, 1 insertion(+) diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Type_Refinements/Typed_DB_Column_Implementations.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Type_Refinements/Typed_DB_Column_Implementations.enso index 1f7bc3c5ba22..70a854ef1a46 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Type_Refinements/Typed_DB_Column_Implementations.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/Type_Refinements/Typed_DB_Column_Implementations.enso @@ -7,6 +7,7 @@ import Standard.Base.Errors.Unimplemented.Unimplemented import Standard.Table.Column.Column import Standard.Table.Column.Rest_Of_String import Standard.Table.Internal.Value_Type_Checks +from Standard.Table import Value_Type import project.DB_Column.DB_Column import project.Internal.Helpers From c0fbda66d487556b1003eff008c83a210c7e8dcd Mon Sep 17 00:00:00 2001 From: Adam Riley Date: Tue, 28 Apr 2026 09:47:59 +0100 Subject: [PATCH 3/6] docs --- .../Table/0.0.0-dev/docs/api/Refined_Types/Text_Column.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/distribution/lib/Standard/Table/0.0.0-dev/docs/api/Refined_Types/Text_Column.md b/distribution/lib/Standard/Table/0.0.0-dev/docs/api/Refined_Types/Text_Column.md index 8c33f93e3b52..295486f9a3f4 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/docs/api/Refined_Types/Text_Column.md +++ b/distribution/lib/Standard/Table/0.0.0-dev/docs/api/Refined_Types/Text_Column.md @@ -15,4 +15,7 @@ - reverse self -> (Standard.Table.Column.Column&Standard.Table.Refined_Types.Text_Column.Text_Column) - right self n:(Standard.Table.Column.Column|Standard.Base.Data.Numbers.Integer|Standard.Base.Nothing.Nothing)= -> (Standard.Table.Column.Column&Standard.Table.Refined_Types.Text_Column.Text_Column) - to_case self case_option:Standard.Base.Data.Text.Case.Case= locale:Standard.Base.Data.Locale.Locale= -> (Standard.Table.Column.Column&Standard.Table.Refined_Types.Text_Column.Text_Column) + - trim self what:(Standard.Table.Column.Column|Standard.Base.Data.Text.Text|Standard.Base.Any.Any)= where:Standard.Base.Data.Text.Location.Location= -> (Standard.Table.Column.Column&Standard.Table.Refined_Types.Text_Column.Text_Column) + - trim_left self what:(Standard.Table.Column.Column|Standard.Base.Data.Text.Text|Standard.Base.Any.Any)= -> (Standard.Table.Column.Column&Standard.Table.Refined_Types.Text_Column.Text_Column) + - trim_right self what:(Standard.Table.Column.Column|Standard.Base.Data.Text.Text|Standard.Base.Any.Any)= -> (Standard.Table.Column.Column&Standard.Table.Refined_Types.Text_Column.Text_Column) - upper self -> (Standard.Table.Column.Column&Standard.Table.Refined_Types.Text_Column.Text_Column) From 14605a53b2a8cdd672120c43341c3e5b082f06fc Mon Sep 17 00:00:00 2001 From: Adam Riley Date: Tue, 28 Apr 2026 09:49:16 +0100 Subject: [PATCH 4/6] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1dcb5dfef17..8374ea3771c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ - [Hiding `Prim_Text_Helper` from the API][14979] - [Add reverse][14931] - [Support for Spatial aggregates and making lines and polygons.][14973] +- [Add text trim methods][14982] [14522]: https://github.com/enso-org/enso/pull/14522 [14476]: https://github.com/enso-org/enso/pull/14476 @@ -102,6 +103,7 @@ [14931]: https://github.com/enso-org/enso/pull/14931 [14979]: https://github.com/enso-org/enso/pull/14979 [14973]: https://github.com/enso-org/enso/pull/14973 +[14982]: https://github.com/enso-org/enso/pull/14982 #### Enso Language & Runtime From 1f275e961aa20b3120f19241765983b9a5ccd023 Mon Sep 17 00:00:00 2001 From: Adam Riley Date: Tue, 28 Apr 2026 14:49:11 +0100 Subject: [PATCH 5/6] Commit tests --- .../src/Refined_Types/Text_Column.enso | 6 +-- .../Column_Operations_Spec.enso | 48 +++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Refined_Types/Text_Column.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Refined_Types/Text_Column.enso index 99b06ec6bd4a..4545d475bbd7 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Refined_Types/Text_Column.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Refined_Types/Text_Column.enso @@ -314,7 +314,7 @@ type Text_Column example_trim_custom = Examples.text_column_1.trim "x" ..Left ``` - trim self (what : Column | Text | Any = " ") (where : Location = ..Both) -> Column & Text_Column = + trim self (what : Column | Text | Any = '') (where : Location = ..Both) -> Column & Text_Column = self.operations_implementation.trim self.column what where ## --- @@ -342,7 +342,7 @@ type Text_Column example_trim_left_custom = Examples.text_column_1.trim_left "x" ``` - trim_left self (what : Column | Text | Any = " ") -> Column & Text_Column = + trim_left self (what : Column | Text | Any = '') -> Column & Text_Column = self.operations_implementation.trim_left self.column what ## --- @@ -370,7 +370,7 @@ type Text_Column example_trim_right_custom = Examples.text_column_1.trim_right "x" ``` - trim_right self (what : Column | Text | Any = " ") -> Column & Text_Column = + trim_right self (what : Column | Text | Any = '') -> Column & Text_Column = self.operations_implementation.trim_right self.column what ## --- diff --git a/test/Table_Tests/src/Common_Table_Operations/Column_Operations_Spec.enso b/test/Table_Tests/src/Common_Table_Operations/Column_Operations_Spec.enso index f73426443345..b0578b1236d9 100644 --- a/test/Table_Tests/src/Common_Table_Operations/Column_Operations_Spec.enso +++ b/test/Table_Tests/src/Common_Table_Operations/Column_Operations_Spec.enso @@ -1800,6 +1800,54 @@ add_column_operation_specs suite_builder setup = Test.expect_panic Type_Error <| data.a.trim what=1 data.a.trim what=data.c . should_fail_with Invalid_Value_Type + group_builder.specify "trim_left should trim whitespace from the start by default" <| + with_mixed_columns_if_supported [["A", [" A ", ' \t\n\rA\r\n\t ', "xxxAxx"]]] t-> + a = t.at "A" + a.trim_left . to_vector . should_equal ["A ", 'A\r\n\t ', "xxxAxx"] + + group_builder.specify "trim_left should trim custom characters from the start" <| + data.a.trim_left what='x' . to_vector . should_equal [" A ", ' \t\n\rA\r\n\t ', "Axx"] + data.a.trim_left what=' ' . to_vector . should_equal ["A ", '\t\n\rA\r\n\t ', "xxxAxx"] + data.a.trim_left what=' \t' . to_vector . should_equal ["A ", '\n\rA\r\n\t ', "xxxAxx"] + data.a.trim_left what=data.b . to_vector . should_equal ["A ", '\n\rA\r\n\t ', "Axx"] + + group_builder.specify "trim_right should trim whitespace from the end by default" <| + with_mixed_columns_if_supported [["A", [" A ", ' \t\n\rA\r\n\t ', "xxxAxx"]]] t-> + a = t.at "A" + a.trim_right . to_vector . should_equal [" A", ' \t\n\rA', "xxxAxx"] + + group_builder.specify "trim_right should trim custom characters from the end" <| + data.a.trim_right what='x' . to_vector . should_equal [" A ", ' \t\n\rA\r\n\t ', "xxxA"] + data.a.trim_right what=' ' . to_vector . should_equal [" A", ' \t\n\rA\r\n\t', "xxxAxx"] + data.a.trim_right what=' \t' . to_vector . should_equal [" A", ' \t\n\rA\r\n', "xxxAxx"] + data.a.trim_right what=data.b . to_vector . should_equal [" A", ' \t\n\rA\r\n', "xxxA"] + + group_builder.specify "trim_left and trim_right should only allow Text columns as arguments" <| + Test.expect_panic Type_Error <| data.a.trim_left what=1 + Test.expect_panic Type_Error <| data.a.trim_right what=1 + data.a.trim_left what=data.c . should_fail_with Invalid_Value_Type + data.a.trim_right what=data.c . should_fail_with Invalid_Value_Type + + group_builder.specify "should handle Nothing values in trim operations" <| + with_mixed_columns_if_supported [["A", [" A ", Nothing, "xxxAxx"]]] t-> + a = t.at "A" + a.trim . to_vector . should_equal ["A", Nothing, "xxxAxx"] + a.trim_left . to_vector . should_equal ["A ", Nothing, "xxxAxx"] + a.trim_right . to_vector . should_equal [" A", Nothing, "xxxAxx"] + a.trim what='x' . to_vector . should_equal [" A ", Nothing, "A"] + a.trim_left what='x' . to_vector . should_equal [" A ", Nothing, "Axx"] + a.trim_right what='x' . to_vector . should_equal [" A ", Nothing, "xxxA"] + + group_builder.specify "should handle empty strings in trim operations" <| + with_mixed_columns_if_supported [["A", [" ", " ", ""]]] t-> + a = t.at "A" + a.trim . to_vector . should_equal ["", "", ""] + a.trim_left . to_vector . should_equal ["", "", ""] + a.trim_right . to_vector . should_equal ["", "", ""] + a.trim what='x' . to_vector . should_equal [" ", " ", ""] + a.trim_left what='x' . to_vector . should_equal [" ", " ", ""] + a.trim_right what='x' . to_vector . should_equal [" ", " ", ""] + suite_builder.group prefix+"(Column_Operations_Spec) Other Column Operations" group_builder-> table_builder = build_sorted_table table_builder=setup.light_table_builder group_builder.specify "is_in" <| From 8961494eff21fff098a4853fca382d63a6881ebf Mon Sep 17 00:00:00 2001 From: Adam Riley Date: Tue, 28 Apr 2026 19:41:37 +0100 Subject: [PATCH 6/6] Catch type errors --- .../Internal/Type_Refinements/Typed_Column_Implementations.enso | 2 ++ 1 file changed, 2 insertions(+) diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Typed_Column_Implementations.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Typed_Column_Implementations.enso index 952078ebe1eb..e6f1add874f9 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Typed_Column_Implementations.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Typed_Column_Implementations.enso @@ -330,3 +330,5 @@ private _apply_trim (this_column : Column & In_Memory_Column) (what : Column | T _ : Column -> Value_Type_Checks.is_text what _apply_binary_map this_column do_trim what new_name skip_nulls=False expected_result_type=Value_Type.Char + _ -> + what : Text