Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving experience with format and parse. #9045

Merged
merged 7 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import project.Nothing.Nothing
import project.Panic.Panic
from project.Data.Boolean import Boolean, False, True
from project.Internal.Number_Builtins import all
from project.Widget_Helpers import make_number_format_selector

polyglot java import java.lang.Double
polyglot java import java.lang.Long
Expand Down Expand Up @@ -305,9 +306,10 @@ type Number
Convert the value 5000 to a string.

5000.format "#,##0"
@format make_number_format_selector
@locale Locale.default_widget
format : Text -> Locale -> Text
format self format locale=Locale.default =
format self format:Text="" locale:Locale=Locale.default =
symbols = DecimalFormatSymbols.new locale.java_locale
formatter = DecimalFormat.new format symbols
formatter.format self
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ type Date_Time_Formatter
Date.parse "1 Nov '95" "d MMM ''yy{2099}" == (Date.new 2095 11 01)
@locale Locale.default_widget
from_simple_pattern : Text -> Locale -> Date_Time_Formatter ! Date_Time_Format_Parse_Error
from_simple_pattern pattern:Text locale:Locale=Locale.default =
from_simple_pattern pattern:Text='' locale:Locale=Locale.default =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor thing: I see above in Numbers.format we have "" and here '', shall we try to 'standardize' which one we prefer for empty strings? Or does this depend on the context?

How does the text input widget work? Does it always use '? Does it support escapes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to move to:

  • Use single quotes by default.
  • Use double quotes if it would avoid escaping.
  • Use escaped single quotes.

No the text input doesn't do that it just uses escaped single quotes.

used_locale = if locale == Locale.default then Locale.us else locale
FormatterCache.SIMPLE_FORMAT.get_or_set (FormatterCacheKey.new pattern used_locale.java_locale) _->
parsed = Tokenizer.tokenize pattern |> Parser.parse_simple_date_pattern
Expand Down Expand Up @@ -350,7 +350,6 @@ type Date_Time_Formatter
"iso_time" -> "(ISO time) HH:mm[:ss[.f]]"
other -> other


## PRIVATE
Date_Time_Formatter.from (that:Text) (locale:Locale = Locale.default) =
Date_Time_Formatter.from_simple_pattern that locale
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ polyglot java import org.enso.base.Time_Utils
## PRIVATE
interpret : Locale -> Vector (Common_Nodes | Standard_Date_Patterns | ISO_Week_Year_Patterns | Time_Patterns | Time_Zone_Patterns) -> Boolean -> DateTimeFormatter
interpret locale nodes prepare_defaults=True =
builder = DateTimeFormatterBuilder.new
builder = DateTimeFormatterBuilder.new.parseCaseInsensitive
interpret_node node = case node of
Common_Nodes.Literal text ->
builder.appendLiteral text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,5 @@ make_single_choice values display=Display.Always =
make_option value = case value of
_ : Vector -> Choice.Option value.first value.second
_ : Text -> Choice.Option value value.pretty
_ : Choice -> value
Widget.Single_Choice (values.map make_option) Nothing display
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import project.Data.Locale.Locale
import project.Data.Numbers.Number
import project.Data.Time.Date.Date
import project.Data.Time.Date_Time.Date_Time
import project.Data.Time.Date_Time_Formatter.Date_Time_Formatter
import project.Data.Time.Time_Of_Day.Time_Of_Day
import project.Meta
import project.Metadata.Widget
from project.Data.Boolean import Boolean, False, True
from project.Metadata import make_single_choice
from project.Metadata.Choice import Option

## PRIVATE
Creates a Regex / Text Widget for search and replace.
Expand All @@ -23,7 +26,15 @@ make_delimiter_selector =
Creates a Single_Choice Widget for file read delimiters.
make_file_read_delimiter_selector : Widget
make_file_read_delimiter_selector =
make_single_choice [',', ';', '|', ['{tab}', "'\t'"], ['{space}', "' '"], ['{none}', "''"], '_', ['Custom', "'?'"]]
make_single_choice [',', ';', '|', ['{tab}', "'\t'"], ['{space}', "' '"], ['{none}', "''"], '_', ['Custom', "'?'"]]

## PRIVATE
Creates a Single_Choice Widget for formatting decimals.
make_number_format_selector : Widget
make_number_format_selector (value:Number=1234.564321) =
formats = ['0', '#,##0', '#,##0.00', '#,##0.00##', '#,##0.00;(#,##0.00)', '0.####', '0.######E0']
mapped_formats = formats.map f-> [f + " (e.g. " + (value.format f) + ")", f.pretty]
make_single_choice mapped_formats

## PRIVATE
Creates a Single_Choice Widget for parsing dates.
Expand All @@ -34,8 +45,10 @@ make_date_format_selector (date:Date=(Date.new 2012 3 14)) =
formats = ['d/M/yyyy', 'dd/MM/yyyy', 'd-MMM-yy', 'd MMMM yyyy', 'M/d/yyyy', 'MM/dd/yyyy', 'MMMM d, yyyy', 'yyyy-MM'].map f->
[f + " (e.g. " + date.format f + ")", f.pretty]
custom_locale_format =
format = Date_Time_Formatter.from 'd MMMM yyyy' locale=Locale.france
['d MMMM yyyy - with custom Locale (e.g. ' + date.format format + ')', "(Date_Time_Formatter.from 'd MMMM yyyy' locale=Locale.france)"]
format = Date_Time_Formatter.from_simple_pattern 'd MMMM yyyy' locale=Locale.france
label = 'd MMMM yyyy - with custom Locale (e.g. ' + date.format format + ')'
code = "(Date_Time_Formatter.from_simple_pattern 'd MMMM yyyy' locale=Locale.france)"
Option label code [["pattern", make_single_choice formats], ["locale", Locale.default_widget]]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't these nested widgets be set automatically?

Or if we set the 'toplevel' widget, than all inner ones must also be set manually to work?

Asking mostly to understand and use the correct practice myself when adding any widgets.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes the top level ones will be set automatically (when a bug is fixed).
However in this case I am deliberately adding the inner ones so I can be consistent with outer one and be value type determining what is added.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, I see! I did not guess that.

I think a comment like 'Overriding the widget for patterns to ensure that the provided formats are the same as on the top level' could clarify this then

week_date_formats = ['YYYY-ww-d', 'YYYY-ww', 'ddd, YYYY-ww'].map f->
format = Date_Time_Formatter.from_iso_week_date_pattern f
["ISO Week-based Date: " + f + " (e.g. " + date.format format + ")", "("+fqn+".from_iso_week_date_pattern " + f.pretty + ")"]
Expand All @@ -59,8 +72,10 @@ make_date_time_format_selector (date_time:Date_Time=(Date_Time.new 2012 3 14 15
formats = ['yyyy-MM-dd HH:mm:ss.f', 'yyyy-MM-dd HH:mm:ss.f TT', 'd/M/yyyy h:mm a', 'dd/MM/yyyy HH:mm:ss', 'd-MMM-yy HH:mm:ss', 'd-MMM-yy h:mm:ss a', 'd MMMM yyyy h:mm a', 'M/d/yyyy h:mm:ss a', 'MM/dd/yyyy HH:mm:ss']
mapped_formats = formats.map f-> [f + " (e.g. " + date_time.format f + ")", f.pretty]
custom_locale_format =
format = Date_Time_Formatter.from 'd MMMM yyyy HH:mm' locale=Locale.france
['d MMMM yyyy HH:mm - with custom Locale (e.g. ' + date_time.format format + ')', "(Date_Time_Formatter.from 'd MMMM yyyy HH:mm' locale=Locale.france)"]
format = Date_Time_Formatter.from_simple_pattern 'd MMMM yyyy HH:mm' locale=Locale.france
label = 'd MMMM yyyy HH:mm - with custom Locale (e.g. ' + date_time.format format + ')'
code = "(Date_Time_Formatter.from_simple_pattern 'd MMMM yyyy HH:mm' locale=Locale.france)"
Option label code [["pattern", make_single_choice mapped_formats], ["locale", Locale.default_widget]]
week_date_formats = ['YYYY-ww-d HH:mm:ss.f'].map f->
format = Date_Time_Formatter.from_iso_week_date_pattern f
["ISO Week-based Date-Time: " + f + " (e.g. " + date_time.format format + ")", "(Date_Time_Formatter.from_iso_week_date_pattern " + f.pretty + ")"]
Expand All @@ -77,3 +92,23 @@ make_time_format_selector (time:Time_Of_Day=(Time_Of_Day.new 13 30 55 123)) =
[f + " (e.g. " + time.format f + ")", f.pretty]

make_single_choice ([iso_format] + formats)

## PRIVATE
Create a Single_Choice Widget for selecting a format value.
make_format_chooser : Boolean -> Boolean -> Boolean -> Boolean -> Boolean -> Widget
make_format_chooser include_number:Boolean=True include_date:Boolean=True include_date_time:Boolean=True include_time:Boolean=True include_boolean:Boolean=True =
numeric = if include_number.not then [] else
['0', '#,##0', '#,##0.00', '#,##0.00;(#,##0.00)'].map f-> [f + " (e.g. " + (1234.5678.format f) + ")", f.pretty]
date = if include_date.not then [] else
['d/M/yyyy', 'dd/MM/yyyy', 'M/d/yyyy', 'MM/dd/yyyy', 'd-MMM-yy', 'd MMMM yyyy'].map f-> [f + " (e.g. " + (Date.new 2012 3 14).format f + ")", f.pretty]
date_time = if include_date_time.not then [] else
['yyyy-MM-dd HH:mm:ss.f', 'yyyy-MM-dd HH:mm:ss.f TT', 'd/M/yyyy h:mm a', 'dd/MM/yyyy HH:mm:ss', 'MM/dd/yyyy HH:mm:ss'].map f-> [f + " (e.g. " + (Date_Time.new 2012 3 14 15 9 26 123).format f + ")", f.pretty]
custom_locale_format =
format = Date_Time_Formatter.from_simple_pattern 'd MMMM yyyy HH:mm' locale=Locale.france
label = 'd MMMM yyyy HH:mm - with custom Locale (e.g. ' + Date_Time.now.format format + ')'
code = "(Date_Time_Formatter.from_simple_pattern 'd MMMM yyyy HH:mm' locale=Locale.france)"
[Option label code [["pattern", make_single_choice (date+date_time)], ["locale", Locale.default_widget]]]
time = if include_time.not then [] else
['HH:mm[:ss]', 'h:mm[:ss] a'].map f-> [f + " (e.g. " + (Time_Of_Day.new 13 30 55 123).format f + ")", f.pretty]
boolean = if include_boolean.not then [] else ['Yes|No', '1|0'].map f-> [f + " (Boolean)", f.pretty]
make_single_choice (numeric + date + date_time + time + custom_locale_format + boolean)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Standard.Base.Errors.Common.Index_Out_Of_Bounds
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
import Standard.Base.Errors.Illegal_State.Illegal_State
import Standard.Base.Internal.Rounding_Helpers
from Standard.Base.Widget_Helpers import make_regex_text_widget
from Standard.Base.Widget_Helpers import make_format_chooser, make_regex_text_widget

import Standard.Table.Data.Column.Column as Materialized_Column
import Standard.Table.Data.Constants.Previous_Value
Expand Down Expand Up @@ -1648,8 +1648,8 @@ type DB_Column
can be used. For `Boolean`, it should be two values that represent true
and false, separated by a `|`. Alternatively, a `Data_Formatter` can be
passed to provide complete customisation of the formatting. If
`Nothing` is provided, the default formatting settings of the backend
will be used. `Nothing` is currently the only setting accepted by the
`""` is provided, the default formatting settings of the backend
will be used. `""` is currently the only setting accepted by the
Database backends.
- on_problems: Specifies how to handle if a problem occurs, raising as a
warning by default.
Expand All @@ -1665,26 +1665,24 @@ type DB_Column
does not support customization, an `Unsupported_Database_Operation`
error is reported.
@type (Widget_Helpers.parse_type_selector include_auto=False)
parse : Value_Type | Auto -> Text | Data_Formatter | Nothing -> Problem_Behavior -> DB_Column
parse self type format=Nothing on_problems=Report_Warning =
check_parameters =
if type == Auto then Error.throw (Unsupported_Database_Operation.Error "The `Auto` parse type is not supported by the Database backend. Either pick a specific type or materialize the table to memory using `.read`.") else
case format of
Nothing -> Nothing
_ -> Error.throw (Unsupported_Database_Operation.Error "Custom formatting is not supported by the Database backend. Please set the format to `Nothing` to indicate that the default Database settings can be used, or if custom formatting is needed, materialize the table to memory using `.read` first.")
check_parameters.if_not_error <|
Value_Type.expect_text self <|
## In the future we may have some specific logic, for example
allowing to support formatting settings. For now, the
Database parse just boils down to a simple CAST.
self.internal_do_cast type on_problems
@format (make_format_chooser include_number=False)
parse : Value_Type | Auto -> Text | Data_Formatter -> Problem_Behavior -> DB_Column
parse self type format:(Text|Data_Formatter)="" on_problems=Report_Warning =
if type == Auto then Error.throw (Unsupported_Database_Operation.Error "The `Auto` parse type is not supported by the Database backend. Either pick a specific type or materialize the table to memory using `.read`.") else
if format != "" then Error.throw (Unsupported_Database_Operation.Error "Custom formatting is not supported by the Database backend. Please set the format to `''` to use the default settings, or if custom formatting is needed, materialize the table to memory using `.read` first.") else
Value_Type.expect_text self <|
## In the future we may have some specific logic, for example
allowing to support formatting settings. For now, the
Database parse just boils down to a simple CAST.
self.internal_do_cast type on_problems

## GROUP Standard.Base.Conversions
ICON convert
Formatting values is not supported in database columns.
@locale Locale.default_widget
@format (self-> Widget_Helpers.make_format_chooser_for_type self.value_type)
format : Text | Date_Time_Formatter | DB_Column | Nothing -> Locale -> DB_Column ! Illegal_Argument
format self format:(Text | Date_Time_Formatter | DB_Column | Nothing)=Nothing locale=Locale.default =
format self format:(Text | Date_Time_Formatter | DB_Column)="" locale=Locale.default =
_ = [format, locale]
Error.throw <| Unsupported_Database_Operation.Error "`DB_Column.format` is not implemented yet for the Database backends."

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import Standard.Base.Errors.Unimplemented.Unimplemented
import Standard.Base.System.File.Generic.Writable_File.Writable_File
from Standard.Base.Metadata import make_single_choice
from Standard.Base.Runtime import assert
from Standard.Base.Widget_Helpers import make_delimiter_selector
from Standard.Base.Widget_Helpers import make_delimiter_selector, make_format_chooser

import Standard.Table.Data.Blank_Selector.Blank_Selector
import Standard.Table.Data.Calculations.Column_Operation.Column_Operation
Expand Down Expand Up @@ -2017,8 +2017,9 @@ type Table
table.parse "birthday" Value_Type.Date
@type (Widget_Helpers.parse_type_selector include_auto=False)
@columns Widget_Helpers.make_column_name_vector_selector
parse : Vector (Text | Integer | Regex) | Text | Integer | Regex -> Value_Type | Auto -> Text | Data_Formatter | Nothing -> Boolean -> Problem_Behavior -> Table
parse self columns=(self.columns . filter (c-> c.value_type.is_text) . map .name) type format=Nothing error_on_missing_columns=True on_problems=Report_Warning =
@format (make_format_chooser include_number=False)
parse : Vector (Text | Integer | Regex) | Text | Integer | Regex -> Value_Type | Auto -> Text | Data_Formatter -> Boolean -> Problem_Behavior -> Table
parse self columns=(self.columns . filter (c-> c.value_type.is_text) . map .name) type format:(Text|Data_Formatter)='' error_on_missing_columns=True on_problems=Report_Warning =
selected = self.columns_helper.select_columns columns Case_Sensitivity.Default reorder=False error_on_missing_columns=error_on_missing_columns on_problems=on_problems error_on_empty=False . map self.make_column
selected.fold self table-> column_to_parse->
new_column = column_to_parse.parse type format on_problems
Expand Down Expand Up @@ -2099,8 +2100,9 @@ type Table
table.format
@columns Widget_Helpers.make_column_name_vector_selector
@locale Locale.default_widget
format : Vector (Text | Integer | Regex) | Text | Integer | Regex -> Text | Date_Time_Formatter | DB_Column | Nothing -> Locale -> Boolean -> Problem_Behavior -> Table ! Date_Time_Format_Parse_Error | Illegal_Argument
format self columns format:(Text | Date_Time_Formatter | DB_Column | Nothing)=Nothing locale=Locale.default error_on_missing_columns=True on_problems=Report_Warning =
@format (make_format_chooser include_number=False)
format : Vector (Text | Integer | Regex) | Text | Integer | Regex -> Text | Date_Time_Formatter | DB_Column -> Locale -> Boolean -> Problem_Behavior -> Table ! Date_Time_Format_Parse_Error | Illegal_Argument
format self columns format:(Text | Date_Time_Formatter | DB_Column)="" locale=Locale.default error_on_missing_columns=True on_problems=Report_Warning =
_ = [columns, format, locale, error_on_missing_columns, on_problems]
Error.throw (Unsupported_Database_Operation.Error "Table.format is not implemented yet for the Database backends.")

Expand Down
Loading
Loading