Tune Text.trim, fix for Text.split (#10445)

- Rename `Location.Start` to `Location.Left`.
- Rename `Location.End` to `Location.Right`.
- Use auto-scoping for `Location`.
- Tune widgets for `Text.trim`.
- Correct signature of `Text.split`.
- Adjist `generateLocallyUniqueIdent` to not fail on bad signature.
This commit is contained in:
James Dunkerley 2024-07-04 23:24:56 +01:00 committed by GitHub
parent d46e4ac609
commit 0661f17d1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 91 additions and 88 deletions

View File

@ -8,10 +8,13 @@
#### Enso Enso Standard Library
- [Reamed `Data.list_directory` to `Data.list`. Removed list support from read
- [Renamed `Data.list_directory` to `Data.list`. Removed list support from read
methods.][10434]
- [Renamed `Location.Start` to `Location.Left` and `Location.End` to
`Location.Right`.][10445]
[10434]: https://github.com/enso-org/enso/pull/10434
[10445]: https://github.com/enso-org/enso/pull/10445
# Enso 2024.2

View File

@ -240,8 +240,9 @@ export const { injectFn: useGraphStore, provideFn: provideGraphStore } = createC
// FIXME: This implementation is not robust in the context of a synchronized document,
// as the same name can likely be assigned by multiple clients.
// Consider implementing a mechanism to repair the document in case of name clashes.
const identPrefix = prefix && isIdentifier(prefix + 1) ? prefix : FALLBACK_BINDING_PREFIX
for (let i = 1; ; i++) {
const ident = (prefix ?? FALLBACK_BINDING_PREFIX) + i
const ident = identPrefix + i
assert(isIdentifier(ident))
if (!db.identifierUsed(ident) && !ignore.has(ident)) return ident
}

View File

@ -1,11 +1,11 @@
import project.Any.Any
import project.Data.Array.Array
import project.Data.Array_Proxy.Array_Proxy
import project.Data.Decimal.Decimal
import project.Data.Json.JS_Object
import project.Data.Json.Json
import project.Data.Locale.Locale
import project.Data.Map.Map
import project.Data.Decimal.Decimal
import project.Data.Numbers.Float
import project.Data.Numbers.Integer
import project.Data.Numbers.Number

View File

@ -39,7 +39,7 @@ from project.Data.Json import Invalid_JSON, JS_Object, Json
from project.Data.Numbers import Float, Integer, Number, Number_Parse_Error
from project.Data.Range.Extensions import all
from project.Data.Text.Text_Cleanse import Cleansable_Text, Text_Cleanse
from project.Metadata import Display, Widget
from project.Metadata import Display, make_single_choice, Widget
from project.Widget_Helpers import make_data_cleanse_vector_selector, make_date_format_selector, make_date_time_format_selector, make_delimiter_selector, make_regex_text_widget, make_time_format_selector
polyglot java import com.ibm.icu.lang.UCharacter
@ -382,7 +382,7 @@ Text.to_regex self case_insensitive=False = Regex.compile self case_insensitive
'azbzczdzezfzg'.split ['b', 'zez'] == ['az', 'zczd', 'fzg']
@delimiter make_delimiter_selector
Text.split : Text | Vector Text -> Case_Sensitivity -> Boolean -> Vector Text | Illegal_Argument
Text.split : Text | Vector Text -> Case_Sensitivity -> Boolean -> Vector Text ! Illegal_Argument
Text.split self delimiter="," case_sensitivity=Case_Sensitivity.Sensitive use_regex=False =
delimiter_is_empty = case delimiter of
_ : Text -> delimiter.is_empty
@ -1181,10 +1181,12 @@ Text.to_case self case_option:Case=..Lower locale:Locale=Locale.default = case c
"HELLO".pad 9 "AB" == "HELLOABAB"
"HELLO".pad 8 "AB" == "HELLOABA"
"HELLO".pad 8 "AB" Location.Start == "BABHELLO"
"HELLO".pad 8 "AB" ..Left == "BABHELLO"
@length (Widget.Numeric_Input display=Display.Always)
@with_pad (Widget.Text_Input display=Display.Always)
@at (make_single_choice [["Left", "..Left"], ["Right", "..Right"]])
Text.pad : Integer -> Text -> Location -> Text
Text.pad self length=0 with_pad=' ' at=Location.End = case at of
Text.pad self length:Integer=0 with_pad:Text=' ' at:Location=..Right = case at of
Location.Both -> Error.throw (Illegal_Argument.Error "`Location.Both` cannot be used with `pad`.")
_ ->
with_pad_length = with_pad.length
@ -1194,9 +1196,9 @@ Text.pad self length=0 with_pad=' ' at=Location.End = case at of
full_repetitions = pad_size.div with_pad_length
remainder = pad_size % with_pad_length
case at of
Location.Start ->
Location.Left ->
with_pad.take (Index_Sub_Range.Last remainder) + with_pad.repeat full_repetitions + self
Location.End ->
Location.Right ->
self + with_pad.repeat full_repetitions + with_pad.take (Index_Sub_Range.First remainder)
## GROUP Text
@ -1215,42 +1217,40 @@ Text.pad self length=0 with_pad=' ' at=Location.End = case at of
Trimming whitespace from a string.
" Hello! ".trim == "Hello!"
" Hello! ".trim Location.Start == "Hello! "
" Hello! ".trim Location.End == " Hello!"
" Hello! ".trim ..Left == "Hello! "
" Hello! ".trim ..Right == " Hello!"
> Example
Trimming a specific set of letters from a string.
"ABC123".trim Location.Start "ABC" == "123"
"ABBA123".trim Location.Start "ABC" == "123"
"ABC123".trim ..Left "ABC" == "123"
"ABBA123".trim ..Left "ABC" == "123"
@where (make_single_choice [["Both", "..Both"], ["Left", "..Left"], ["Right", "..Right"]])
@what (Widget.Text_Input display=Display.Always)
Text.trim : Location -> (Text | (Text -> Boolean)) -> Text
Text.trim self where=Location.Both what=_.is_whitespace =
Text.trim self where:Location=..Both what=_.is_whitespace =
predicate = case what of
_ : Text -> what.contains _
_ -> what
break_iterator = BreakIterator.getCharacterInstance
break_iterator.setText self
start_index = case where of
Location.End -> 0
_ ->
loop current next =
if next < 0 then current else
case predicate (Text_Utils.substring self current next) of
True ->
@Tail_Call loop next break_iterator.next
False -> current
loop 0 break_iterator.next
end_index = case where of
Location.Start -> Text_Utils.char_length self
_ ->
loop current prev =
if prev < 0 then current else
case predicate (Text_Utils.substring self prev current) of
True ->
@Tail_Call loop prev break_iterator.previous
False -> current
current = break_iterator.last
loop current break_iterator.previous
start_index = if where == Location.Right then 0 else
loop current next =
if next < 0 then current else
case predicate (Text_Utils.substring self current next) of
True ->
@Tail_Call loop next break_iterator.next
False -> current
loop 0 break_iterator.next
end_index = if where == Location.Left then Text_Utils.char_length self else
loop current prev =
if prev < 0 then current else
case predicate (Text_Utils.substring self prev current) of
True ->
@Tail_Call loop prev break_iterator.previous
False -> current
current = break_iterator.last
loop current break_iterator.previous
if start_index >= end_index then "" else
Text_Utils.substring self start_index end_index

View File

@ -1,9 +1,9 @@
type Location
## Indicates the beginning of a text.
Start
Left
## Indicates the end of a text.
End
Right
## Indicates both the beginning and end of a text.
Both

View File

@ -189,7 +189,7 @@ type Regex
input = "abcdefghij"
texts = pattern.split input
texts . should_equal ["abcdefghij"]
split : Text -> Boolean -> Vector Text | Type_Error
split : Text -> Boolean -> Vector Text ! Type_Error
split self (input : Text) (only_first : Boolean = False) =
Vector.build builder->
it = Match_Iterator.new self input

View File

@ -6,11 +6,10 @@ import project.Data.Text.Text
import project.Data.Vector.Vector
import project.Meta
import project.Nothing.Nothing
from project.Data.Boolean import Boolean, False, True
from project.Data.Json.Extensions import all
from project.Data.Range.Extensions import all
from project.Data.Boolean import Boolean, False, True
type Display
## Parameter is always shown on the collapsed view.
Always

View File

@ -1373,16 +1373,15 @@ type DB_Column
- what: A `Text` (or text `DB_Column`) containing characters that should be
removed. By default, spaces, tabs, returns and new lines are removed.
trim : Location -> DB_Column | Text -> DB_Column
trim self where=Location.Both what='' =
Value_Type.expect_text self <|
Value_Type.expect_text what <|
new_name = self.naming_helper.function_name "trim" [self]
operator = case where of
Location.Both -> "TRIM"
Location.Start -> "LTRIM"
Location.End -> "RTRIM"
if self.connection.dialect.is_supported operator then self.make_binary_op operator what new_name else
Error.throw (Unsupported_Database_Operation.Error ("`DB_Column.trim "+where.to_text+"` is not supported by this connection."))
trim self where:Location=..Both what='' =
Value_Type.expect_text self <| Value_Type.expect_text what <|
new_name = self.naming_helper.function_name "trim" [self]
operator = case where of
Location.Both -> "TRIM"
Location.Left -> "LTRIM"
Location.Right -> "RTRIM"
if self.connection.dialect.is_supported operator then self.make_binary_op operator what new_name else
Error.throw (Unsupported_Database_Operation.Error ("`DB_Column.trim "+where.to_text+"` is not supported by this connection."))
## GROUP Standard.Base.Text
ICON column_add

View File

@ -337,7 +337,7 @@ profile_sql_if_enabled (jdbc_connection : JDBC_Connection) (~query_text : Text)
result.if_not_error <|
padded_query = query_text.to_display_text.pad length=80
millis = duration.total_milliseconds
millis_text = (millis.to_text.pad length=5 with_pad=' ' at=Location.Start) + ' ms'
millis_text = (millis.to_text.pad length=5 with_pad=' ' at=..Left) + ' ms'
# E.g. [SQLite] SELECT * FROM "test" ... --> 12 ms
log_line = "[" + db_id + "] " + padded_query + " --> " + millis_text + '\n'

View File

@ -287,7 +287,7 @@ type Postgres_Connection
self.data_link_setup.save_as_data_link destination on_existing_file
## PRIVATE
Converts this value to a JSON serializable object.
Converts this value to a JSON serializable object.
to_js_object : JS_Object
to_js_object self =
JS_Object.from_pairs [["type", "Postgres_Connection"], ["links", self.connection.tables.at "Name" . to_vector]]

View File

@ -1402,7 +1402,7 @@ type Column
- what: A `Text` (or text `Column`) containing characters that should be
removed. By default, all whitespace is removed.
trim : Location -> Column | Text -> Column
trim self where=Location.Both what='' = Value_Type.expect_text self <|
trim self where:Location=..Both what='' = Value_Type.expect_text self <|
new_name = naming_helper.function_name "trim" [self]
trim_get = wrap_text_argument_as_value_provider what

View File

@ -74,7 +74,7 @@ type Column_Operation
## Removes the specified characters, by default any whitespace, from the
start, the end, or both ends of the input.
Trim (input : Column_Ref|Expression|Text) (where:Location = Location.Both) (what:Text|Column_Ref = "")
Trim (input : Column_Ref|Expression|Text) (where:Location=..Both) (what:Text|Column_Ref = "")
## PRIVATE
Simple_Expression.from (that:Column_Operation) =

View File

@ -178,7 +178,7 @@ type Simple_Calculation
## Removes the specified characters, by default any whitespace, from the
start, the end, or both ends of the input.
Trim (where:Location = ..Both) (what:Column_Ref|Expression|Text = "")
Trim (where:Location=..Both) (what:Column_Ref|Expression|Text = "")
## Takes the first characters from the input column.
Text_Left (length : Column_Ref|Expression|Integer = 1)

View File

@ -189,7 +189,7 @@ progress_width = 70
print_progress current_progress total_count status_text =
total_count_as_text = total_count.to_text
counter_width = total_count_as_text.length
current_progress_as_text = current_progress.to_text.pad counter_width at=Location.Start
current_progress_as_text = current_progress.to_text.pad counter_width at=..Left
line = " ("+ current_progress_as_text + " / " + total_count_as_text + ") " + status_text
truncated_line = if line.length <= progress_width then line else
line.take (progress_width - 3) + '...'

View File

@ -7,9 +7,8 @@ from Standard.Table import Column, Table
import project.Id.Id
import project.Table as Table_Visualization
from project.Text import get_lazy_visualization_text_window
from project.Table.Visualization import make_json_for_value
from project.Text import get_lazy_visualization_text_window
## PRIVATE
Specifies that the builtin Table visualization should be used for any type,

View File

@ -24,7 +24,7 @@ error_preprocessor x =
message = err.to_display_text
stack_trace = x.get_stack_trace_text.if_nothing "" . split '\n'
truncated_message = Helpers.truncate message
full_message = truncated_message + if stack_trace.length > 0 then " (" + stack_trace.at 0 . trim +")" else ""
full_message = truncated_message + if stack_trace.length > 0 then " (" + stack_trace.at 0 . trim + ")" else ""
JS_Object.from_pairs [['kind', 'Dataflow'], ['message', full_message]] . to_json
if result.is_error then result.catch else ok

View File

@ -2,7 +2,7 @@ from Standard.Base import all
import Standard.Base.Data.Vector.Builder
import Standard.Table.Row.Row
from Standard.Table import Column, Table, Excel_Workbook
from Standard.Table import Column, Excel_Workbook, Table
import Standard.Database.DB_Column.DB_Column
import Standard.Database.DB_Table.DB_Table
@ -157,6 +157,8 @@ make_json_for_table dataframe all_rows_count include_index_col =
pairs = [header, value_type, data, all_rows, has_index_col, ["type", "Table"]]
JS_Object.from_pairs pairs
## PRIVATE
Create JSON serialization of values.
make_json_for_other : Any -> JS_Object
make_json_for_other x =
js_value = x.to_js_object

View File

@ -2703,7 +2703,7 @@ class RuntimeVisualizationsTest extends AnyFlatSpec with Matchers {
data
}
val stringified = new String(data)
stringified shouldEqual """{"kind":"Dataflow","message":"The List is empty. (at <enso> Main.main(Enso_Test.Test.Main:7:5-40)"}"""
stringified shouldEqual """{"kind":"Dataflow","message":"The List is empty. (at <enso> Main.main(Enso_Test.Test.Main:7:5-40))"}"""
}
it should "run visualization default preprocessor" in withContext() {

View File

@ -1039,7 +1039,7 @@ add_specs suite_builder =
"Hello World!".pad 15 . should_equal "Hello World! "
"HELLO".pad 9 "AB" . should_equal "HELLOABAB"
"HELLO".pad 8 "AB" . should_equal "HELLOABA"
"HELLO".pad 8 "AB" Location.Start . should_equal "BABHELLO"
"HELLO".pad 8 "AB" ..Left . should_equal "BABHELLO"
"".pad 4 . should_equal " "
"A".pad 3 "" . should_fail_with Illegal_Argument
"ABCDE".pad 3 "" . should_fail_with Illegal_Argument
@ -1056,8 +1056,8 @@ add_specs suite_builder =
'XX'.pad 5 'y\u{301}y' . should_equal 'XXy\u{301}yy\u{301}'
'XX'.pad 4 'yy\u{301}Z' . should_equal 'XXyy\u{301}'
'🚀'.pad 3 'B' Location.End . should_equal '🚀BB'
'🚀'.pad 3 'B' Location.Start . should_equal 'BB🚀'
'🚀'.pad 3 'B' ..Right . should_equal '🚀BB'
'🚀'.pad 3 'B' ..Left . should_equal 'BB🚀'
## It is technically possible to use a combining diacritical mark as
the padding, then the actual length of the text will not increase
@ -1067,19 +1067,19 @@ add_specs suite_builder =
group_builder.specify "should allow to trim a text" <|
" Hello! ".trim . should_equal "Hello!"
" Hello! ".trim Location.Start . should_equal "Hello! "
" Hello! ".trim Location.End . should_equal " Hello!"
"ABC123".trim Location.Start "ABC" . should_equal "123"
"ABBA123".trim Location.Start "ABC" . should_equal "123"
"ABCZ-]".trim Location.Both "[A-Z]" . should_equal "BC"
" Hello! ".trim ..Left . should_equal "Hello! "
" Hello! ".trim ..Right . should_equal " Hello!"
"ABC123".trim ..Left "ABC" . should_equal "123"
"ABBA123".trim ..Left "ABC" . should_equal "123"
"ABCZ-]".trim ..Both "[A-Z]" . should_equal "BC"
" ".trim . should_equal ""
" Hello World! ".trim . should_equal "Hello World!"
" Hello World! ".trim Location.Start . should_equal "Hello World! "
" Hello World! ".trim Location.End . should_equal " Hello World!"
"ABCD".trim Location.Start "ABCDEF" . should_equal ""
"ABCD".trim Location.End "ABCDEF" . should_equal ""
"ABCD".trim Location.Both "ABCDEF" . should_equal ""
" Hello World! ".trim ..Left . should_equal "Hello World! "
" Hello World! ".trim ..Right . should_equal " Hello World!"
"ABCD".trim ..Left "ABCDEF" . should_equal ""
"ABCD".trim ..Right "ABCDEF" . should_equal ""
"ABCD".trim ..Both "ABCDEF" . should_equal ""
"".trim . should_equal ""
"A".trim . should_equal "A"
@ -1087,12 +1087,12 @@ add_specs suite_builder =
' A\u{301} \n '.trim . should_equal 'A\u{301}'
"🚧".trim . should_equal "🚧"
" 🚧 🚧 ".trim . should_equal "🚧 🚧"
" 🚧 🚧 ".trim Location.End . should_equal " 🚧 🚧"
" 🚧 🚧 ".trim ..Right . should_equal " 🚧 🚧"
"ABCD".trim Location.Start (_ -> True) . should_equal ""
"ABCD".trim Location.Both (_ -> True) . should_equal ""
"ABCD".trim Location.Both (_ -> False) . should_equal "ABCD"
"123AB98".trim Location.Both _.is_digit . should_equal "AB"
"ABCD".trim ..Left (_ -> True) . should_equal ""
"ABCD".trim ..Both (_ -> True) . should_equal ""
"ABCD".trim ..Both (_ -> False) . should_equal "ABCD"
"123AB98".trim ..Both _.is_digit . should_equal "AB"
' \t\n\r'.trim . should_equal ''
'\t\t Test\nFoo\r\n'.trim . should_equal 'Test\nFoo'

View File

@ -1471,13 +1471,13 @@ add_specs suite_builder setup =
with_mixed_columns_if_supported [["A", [" A ", ' \t\n\rA\r\n\t ', "xxxAxx"]]] t->
a = t.at "A"
a.trim . to_vector . should_equal ["A", "A", "xxxAxx"]
a.trim Location.Start . to_vector . should_equal ["A ", 'A\r\n\t ', "xxxAxx"]
a.trim Location.End . to_vector . should_equal [" A", ' \t\n\rA', "xxxAxx"]
a.trim ..Left . to_vector . should_equal ["A ", 'A\r\n\t ', "xxxAxx"]
a.trim ..Right . to_vector . should_equal [" A", ' \t\n\rA', "xxxAxx"]
group_builder.specify "should trim custom characters" <|
data.a.trim what='x' . to_vector . should_equal [" A ", ' \t\n\rA\r\n\t ', "A"]
data.a.trim what='x' Location.Start . to_vector . should_equal [" A ", ' \t\n\rA\r\n\t ', "Axx"]
data.a.trim what='x' Location.End . to_vector . should_equal [" A ", ' \t\n\rA\r\n\t ', "xxxA"]
data.a.trim what='x' ..Left . to_vector . should_equal [" A ", ' \t\n\rA\r\n\t ', "Axx"]
data.a.trim what='x' ..Right . to_vector . should_equal [" A ", ' \t\n\rA\r\n\t ', "xxxA"]
data.a.trim what=' ' . to_vector . should_equal ["A", '\t\n\rA\r\n\t', "xxxAxx"]
data.a.trim what=' \t' . to_vector . should_equal ["A", '\n\rA\r\n', "xxxAxx"]
data.a.trim what=' \r' . to_vector . should_equal ["A", '\t\n\rA\r\n\t', "xxxAxx"]

View File

@ -172,10 +172,10 @@ add_specs suite_builder setup =
t = table_builder [["A", [" a ", "b"]], ["B", ["c", " d "]]]
t.set (Simple_Expression.Simple_Expr (Column_Ref.Name "A") Simple_Calculation.Trim) "Z" . at "Z" . to_vector . should_equal ["a", "b"]
t.set (Simple_Expression.Simple_Expr (Column_Ref.Name "A") (Simple_Calculation.Trim Location.End)) "Z" . at "Z" . to_vector . should_equal [" a", "b"]
t.set (Simple_Expression.Simple_Expr (Column_Ref.Name "A") (Simple_Calculation.Trim Location.Start)) "Z" . at "Z" . to_vector . should_equal ["a ", "b"]
t.set (Simple_Expression.Simple_Expr (Column_Ref.Name "A") (Simple_Calculation.Trim Location.Both "abc")) "Z" . at "Z" . to_vector . should_equal [" a ", ""]
t.set (Simple_Expression.Simple_Expr "bb aaaa" (Simple_Calculation.Trim Location.Both (Column_Ref.Name "A"))) "Z" . at "Z" . to_vector . should_equal ["bb", " aaaa"]
t.set (Simple_Expression.Simple_Expr (Column_Ref.Name "A") (Simple_Calculation.Trim ..Right)) "Z" . at "Z" . to_vector . should_equal [" a", "b"]
t.set (Simple_Expression.Simple_Expr (Column_Ref.Name "A") (Simple_Calculation.Trim ..Left)) "Z" . at "Z" . to_vector . should_equal ["a ", "b"]
t.set (Simple_Expression.Simple_Expr (Column_Ref.Name "A") (Simple_Calculation.Trim ..Both "abc")) "Z" . at "Z" . to_vector . should_equal [" a ", ""]
t.set (Simple_Expression.Simple_Expr "bb aaaa" (Simple_Calculation.Trim ..Both (Column_Ref.Name "A"))) "Z" . at "Z" . to_vector . should_equal ["bb", " aaaa"]
t.set (Simple_Expression.Simple_Expr (Column_Ref.Name "A") (Simple_Calculation.Add (Column_Ref.Name "B"))) "Z" . at "Z" . to_vector . should_equal [" a c", "b d "]
t.set (Simple_Expression.Simple_Expr "prefix_" (Simple_Calculation.Add (Column_Ref.Name "B"))) "Z" . at "Z" . to_vector . should_equal ["prefix_c", "prefix_ d "]