From 13420763b410ddd4209985df076f865d94d49195 Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Sun, 31 Oct 2021 18:40:10 +0300 Subject: [PATCH 01/10] download hepers --- Project.toml | 1 + src/Dash.jl | 3 +- src/components_utils/_components_utils.jl | 1 + src/components_utils/express.jl | 105 ++++++++++++++++++++++ test/components_utils.jl | 53 +++++++++++ test/runtests.jl | 7 +- 6 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 src/components_utils/_components_utils.jl create mode 100644 src/components_utils/express.jl create mode 100644 test/components_utils.jl diff --git a/Project.toml b/Project.toml index 14afb67..d0950c8 100644 --- a/Project.toml +++ b/Project.toml @@ -4,6 +4,7 @@ authors = ["Chris Parmer ", "Alexandr Romanenko Base64.base64encode(src), + :filename => filename, + :type => type, + :base64 => true + ) +end + +function dcc_send_bytes(src::Function, filename; type = nothing) + io = IOBuffer() + src(io) + return dcc_send_bytes(take!(io), filename, type = type) +end + +""" + dcc_send_data(src::AbstractString, filename; type = nothing) + dcc_send_data(src::Function, filename; type = nothing) + +Convert string into the format expected by the Download component. +`src` is a string or function that takes a single argument - io and writes data to it + +# Examples + +Sending string content +```julia +text_data = "this is the test" +callback!(app, Output("download", "data"), Input("download-btn", "n_clicks"), prevent_initial_call = true) do n_clicks + return dcc_send_string(text_data, "text.txt") +end +``` + +Sending `DataFrame` in `CSV` format +```julia +using DataFrames, CSV +... +df = DataFrame(...) +callback!(app, Output("download", "data"), Input("download-btn", "n_clicks"), prevent_initial_call = true) do n_clicks + return dcc_send_string("df.csv") do io + CSV.write(io, df) + end +end +``` +""" +function dcc_send_string(src::AbstractString, filename; type = nothing) + + return Dict( + :content => src, + :filename => filename, + :type => type, + :base64 => false + ) +end + +function dcc_send_string(src::Function, filename; type = nothing) + io = IOBuffer() + src(io) + return dcc_send_string(String(take!(io)), filename, type = type) +end \ No newline at end of file diff --git a/test/components_utils.jl b/test/components_utils.jl new file mode 100644 index 0000000..5d1f620 --- /dev/null +++ b/test/components_utils.jl @@ -0,0 +1,53 @@ +using Test +using Dash +using Base64 +@testset "Send String" begin + data = "test string" + res = dcc_send_string(data, "test_file.csv"; type = "text/csv") + @test res[:content] == data + @test res[:filename] == "test_file.csv" + @test res[:type] == "text/csv" + @test !res[:base64] + + data2 = "test 2 string" + res = dcc_send_string("test_file.csv"; type = "text/csv") do io + write(io, data2) + end + @test res[:content] == data2 + @test res[:filename] == "test_file.csv" + @test res[:type] == "text/csv" + @test !res[:base64] +end +@testset "Send Bytes" begin + data = "test string" + res = dcc_send_bytes(Vector{UInt8}(data), "test_file.csv"; type = "text/csv") + @test res[:content] == Base64.base64encode(data) + @test res[:filename] == "test_file.csv" + @test res[:type] == "text/csv" + @test res[:base64] + + data2 = "test 2 string" + res = dcc_send_bytes("test_file.csv"; type = "text/csv") do io + write(io, data2) + end + @test res[:content] == Base64.base64encode(data2) + @test res[:filename] == "test_file.csv" + @test res[:type] == "text/csv" + @test res[:base64] +end +@testset "Send File" begin + + file = "assets/test.png" + res = dcc_send_file(file, nothing; type = "text/csv") + @test res[:content] == Base64.base64encode(read(file)) + @test res[:filename] == "test.png" + @test res[:type] == "text/csv" + @test res[:base64] + + file = "assets/test.png" + res = dcc_send_file(file, "ttt.jpeg"; type = "text/csv") + @test res[:content] == Base64.base64encode(read(file)) + @test res[:filename] == "ttt.jpeg" + @test res[:type] == "text/csv" + @test res[:base64] +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 9617715..cd077d7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ -include("env.jl") +#=include("env.jl") include("resources.jl") include("devtools.jl") include("dash_creation.jl") @@ -8,5 +8,6 @@ include("utils.jl") include("context.jl") include("core.jl") include("misc.jl") -include("callbacks.jl") -#include("dev.jl") +include("callbacks.jl")=# +include("components_utils.jl") +#include("dev.jl") \ No newline at end of file From 94ddb0a61be406e8fe7eacbbe2e0b42994fc977f Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Sun, 31 Oct 2021 18:42:30 +0300 Subject: [PATCH 02/10] download helpers --- test/runtests.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index cd077d7..896f71c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ -#=include("env.jl") +include("env.jl") include("resources.jl") include("devtools.jl") include("dash_creation.jl") @@ -8,6 +8,6 @@ include("utils.jl") include("context.jl") include("core.jl") include("misc.jl") -include("callbacks.jl")=# +include("callbacks.jl") include("components_utils.jl") #include("dev.jl") \ No newline at end of file From c17d4eea7b74cc4166edc456078d3d593e4a606e Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Tue, 2 Nov 2021 12:46:06 +0300 Subject: [PATCH 03/10] in work --- src/components_utils/_components_utils.jl | 3 +- src/components_utils/table_format.jl | 595 ++++++++++++++++++++++ test/runtests.jl | 5 +- test/table_format.jl | 84 +++ 4 files changed, 684 insertions(+), 3 deletions(-) create mode 100644 src/components_utils/table_format.jl create mode 100644 test/table_format.jl diff --git a/src/components_utils/_components_utils.jl b/src/components_utils/_components_utils.jl index 19a73bd..5bdf46a 100644 --- a/src/components_utils/_components_utils.jl +++ b/src/components_utils/_components_utils.jl @@ -1 +1,2 @@ -include("express.jl") \ No newline at end of file +include("express.jl") +include("table_format.jl") \ No newline at end of file diff --git a/src/components_utils/table_format.jl b/src/components_utils/table_format.jl new file mode 100644 index 0000000..b343182 --- /dev/null +++ b/src/components_utils/table_format.jl @@ -0,0 +1,595 @@ +module TableFormat + export Format + + struct NamedValue{Name, T} + value::T + NamedValue{Name}(value::T) where {Name, Keys, T} = new{Name, T}(value) + end + + struct TupleWithNamedValues{Name, Keys} + values::NamedTuple + TupleWithNamedValues{Name, Keys}(values) where {Name, Keys} = new{Name, Keys}(values) + end + + Base.getproperty(tp::TupleWithNamedValues, prop::Symbol) = getfield(tp, :values)[prop] + + function tuple_with_named_values(name::Symbol, t::NamedTuple{Names}) where {Names} + return TupleWithNamedValues{name, Names}((;zip(Names, NamedValue{name}.(values(t)))...)) + end + + function possible_values(::TupleWithNamedValues{Name, Keys}) where {Name, Keys} + return join(string.(string(Name), ".", string.(Keys)), ", ") + end + + const Align = tuple_with_named_values(:Align, ( + default = "", + left = "<", + right = ">", + center = "^", + right_sign = "=" + )) + + const Group = tuple_with_named_values(:Group, ( + no = "", + yes = "," + )) + + const Padding = tuple_with_named_values(:Padding, (no = "", yes = 0)) + + const Prefix = tuple_with_named_values(:Prefix, ( + yocto = 10 ^ -24, + zepto = 10 ^ -21, + atto = 10 ^ -18, + femto = 10 ^ -15, + pico = 10 ^ -12, + nano = 10 ^ -9, + micro = 10 ^ -6, + milli = 10 ^ -3, + none = nothing, + kilo = 10 ^ 3, + mega = 10 ^ 6, + giga = 10 ^ 9, + tera = 10 ^ 12, + peta = 10 ^ 15, + exa = 10 ^ 18, + zetta = 10 ^ 21, + yotta = 10 ^ 24 + )) + const Scheme = tuple_with_named_values(:Scheme, ( + default = "", + decimal = "r", + decimal_integer = "d", + decimal_or_exponent = "g", + decimal_si_prefix = "s", + exponent = "e", + fixed = "f", + percentage = "%", + percentage_rounded = "p", + binary = "b", + octal = "o", + lower_case_hex = "x", + upper_case_hex = "X", + unicode = "c", + )) + const Sign = tuple_with_named_values(:Sign, ( + default = "", + negative = "-", + positive = "+", + parantheses = "(", + space = " " + )) + const DSymbol = tuple_with_named_values(:DSymbol, ( + no = "", + yes = "\$", + binary = "#b", + octal = "#o", + hex = "#x" + )) + const Trim = tuple_with_named_values(:Trim, ( + no = "", + yes = "~" + )) + + abstract type FormatProperty end; + + struct NamedProperty{Name, Keys} <: FormatProperty + value::NamedValue{Name} + function NamedProperty{Name, Keys}(value::NamedValue{ValueName}) where {Name, ValueName, Keys} + Name != ValueName && throw(ArgumentError("expected value to be one of $(join(string.(string(Name), ".", string.(Keys)), ", "))")) + return new{Name, Keys}(value) + end + end + + property_by_tuple(::TupleWithNamedValues{Name, Keys}) where {Name, Keys} = NamedProperty{Name, Keys} + + get_value(p::NamedProperty) = p.value.value + + struct CharProperty <: FormatProperty + value::Char + CharProperty(value::Char) = new(value) + function CharProperty(value::String) + length(value) != 1 && throw(ArgumentError("expected char or string of length one")) + return new(value[1]) + end + CharProperty(value) = throw(ArgumentError("expected char or string of length one")) + end + + get_value(p::CharProperty) = p.value + + struct StringProperty <: FormatProperty + value::String + StringProperty(value::String) = new(value) + StringProperty(value) = throw(ArgumentError("expected value to be a string")) + end + + get_value(p::StringProperty) = p.value + + struct UIntOrNothingProperty <: FormatProperty + value::Union{UInt, Nothing} + UIntOrNothingProperty(v::Nothing) = new(v) + function UIntOrNothingProperty(v::Integer) + v < 0 && throw(ArgumentError("expected value to be non-negative")) + return new(v) + end + UIntOrNothingProperty(v) = throw(ArgumentError("expected value to be an integer")) + end + + get_value(p::UIntOrNothingProperty) = p.value + + mutable struct Format + locale + nully + prefix + specifier + function Format() + return new( + Dict(), + "", + Prefix.none, + Dict( + :align => Align.default, + :fill => "", + :group => Group.no, + :width => "", + :padding => Padding.no, + :precision => "", + :sign => Sign.default, + :symbol => DSymbol.no, + :trim => Trim.no, + :type => Scheme.default + ) + ) + end + end + + function check_named_value(t::TupleWithNamedValues{Name, Keys}, v::NamedValue{ValueName}) where {Name, ValueName, Keys} + Name != ValueName && throw(ArgumentError("expected value to be one of $(possible_values(t))")) + return true + end + function check_named_value(t::TupleWithNamedValues{Name, Keys}, v) where {Name, Keys} + throw(ArgumentError("expected value to be one of $(possible_values(t))")) + end + + check_char_value(v::Char) = v + function check_char_value(v::String) + length(v) != 1 && throw(ArgumentError("expected char or string of length one")) + return v[1] + end + function check_char_value(v) + throw(ArgumentError("expected char or string of length one")) + end + + function check_int_value(v::Integer) + v < 0 && throw(ArgumentError("expected value to be non-negative")) + end + + function check_int_value(v) + throw(ArgumentError("expected value to be Integer")) + end + + function align!(f::Format, value) + check_named_value(Align, value) + f.specifier[:align] = value + end + + function fill!(f::Format, value) + v = check_char_value(value) + f.specifier[:fill] = v + end + + + function group!(f::Format, value) + if value isa Bool + value = value ? Group.yes : Group.no + end + check_named_value(Group, value) + f.specifier[:group] = value + end + + function padding!(f::Format, value) + if value isa Bool + value = value ? Padding.yes : Padding.no + end + check_named_value(Padding, value) + f.specifier[:padding] = value + end + + function padding_width!(f::Format, value) + if isnothing(value) + f.specifier[:width] = "" + else + check_int_value(value) + f.specifier[:width] = value + end + end + + function precision!(f::Format, value) + if isnothing(value) + f.specifier[:precision] = "" + else + check_int_value(value) + f.specifier[:precision] = ".$value" + end + end + + function scheme!(f::Format, value) + check_named_value(Scheme, value) + f.specifier[:type] = value + end + + function sign!(f::Format, value) + check_named_value(Sign, value) + f.specifier[:sign] = value + end + + function symbol!(f::Format, value) + check_named_value(DSymbol, value) + f.specifier[:symbol] = value + end + + function trim!(f::Format, value) + if value isa Bool + value = value ? Trim.yes : Trim.no + end + check_named_value(Trim, value) + f.specifier[:trim] = value + end + + # Locale + function symbol_prefix!(f::Format, value::AbstractString) + if haskey(f.locale, :symbol) + f.locale[:symbol] = [value, ""] + else + f.locale[:symbol][1] = value + end + end + + function symbol_suffix!(f::Format, value::AbstractString) + if haskey(f.locale, :symbol) + f.locale[:symbol] = ["", value] + else + f.locale[:symbol][2] = value + end + end + + function decimal_delimiter!(f::Format, value) + v = check_char_value(value) + f.locale[:decimal] = v + end + + function group_delimiter!(f::Format, value) + v = check_char_value(value) + f.locale[:group] = v + end + + #=struct Format + align ::Union{typeof(Align), Nothing} + fill ::Union{Char, Nothing} + group ::Union{typeof(Group), Bool, Nothing} + padding ::Union{typeof(Padding), Bool, Nothing} + padding_width ::Union{Integer, Nothing} + precision ::Union{Integer, Nothing} + scheme ::Union{typeof(Scheme), } + sign, + symbol, + trim, + + #locale + symbol_prefix, + symbol_suffix, + decimal_delimiter, + group_delimiter, + groups, + + #Nully + nully, + #Prefix + si_prefix + function Format(; + locale = (), + nully = "", + prefix = Prefix.none, + align = Align.default, + fill = "", + group = Group.no, + width = "", + padding = Padding.no, + precision = "", + sign = Sign.default, + symbol = Symbol.no, + trim = Trim.no, + type = Scheme.default + ) + return new( + locale, + nully, + prefix, + ( + align = align, + fill = fill, + group = group, + width = width, + padding = padding, + precision = precision, + sign = sign, + symbol = symbol, + trim = trim, + type = type + ) + ) + end + end + + function format(; + align, + fill, + group, + padding, + padding_width, + precision, + scheme, + sign, + symbol, + trim, + + #locale + symbol_prefix, + symbol_suffix, + decimal_delimiter, + group_delimiter, + groups, + + #Nully + nully, + #Prefix + si_prefix + ) + end=# +end +#= +import collections + + + + +Trim = get_named_tuple("trim", {"no": "", "yes": "~"}) + + +class Format: + def __init__(self, **kwargs): + self._locale = {} + self._nully = "" + self._prefix = Prefix.none + self._specifier = { + "align": Align.default, + "fill": "", + "group": Group.no, + "width": "", + "padding": Padding.no, + "precision": "", + "sign": Sign.default, + "symbol": Symbol.no, + "trim": Trim.no, + "type": Scheme.default, + } + + valid_methods = [ + m for m in dir(self.__class__) if m[0] != "_" and m != "to_plotly_json" + ] + + for kw, val in kwargs.items(): + if kw not in valid_methods: + raise TypeError( + "{0} is not a format method. Expected one of".format(kw), + str(list(valid_methods)), + ) + + getattr(self, kw)(val) + + def _validate_char(self, value): + self._validate_string(value) + + if len(value) != 1: + raise ValueError("expected value to a string of length one") + + def _validate_non_negative_integer_or_none(self, value): + if value is None: + return + + if not isinstance(value, int): + raise TypeError("expected value to be an integer") + + if value < 0: + raise ValueError("expected value to be non-negative", str(value)) + + def _validate_named(self, value, named_values): + if value not in named_values: + raise TypeError("expected value to be one of", str(list(named_values))) + + def _validate_string(self, value): + if not isinstance(value, (str, u"".__class__)): + raise TypeError("expected value to be a string") + + # Specifier + def align(self, value): + self._validate_named(value, Align) + + self._specifier["align"] = value + return self + + def fill(self, value): + self._validate_char(value) + + self._specifier["fill"] = value + return self + + def group(self, value): + if isinstance(value, bool): + value = Group.yes if value else Group.no + + self._validate_named(value, Group) + + self._specifier["group"] = value + return self + + def padding(self, value): + if isinstance(value, bool): + value = Padding.yes if value else Padding.no + + self._validate_named(value, Padding) + + self._specifier["padding"] = value + return self + + def padding_width(self, value): + self._validate_non_negative_integer_or_none(value) + + self._specifier["width"] = value if value is not None else "" + return self + + def precision(self, value): + self._validate_non_negative_integer_or_none(value) + + self._specifier["precision"] = ".{0}".format(value) if value is not None else "" + return self + + def scheme(self, value): + self._validate_named(value, Scheme) + + self._specifier["type"] = value + return self + + def sign(self, value): + self._validate_named(value, Sign) + + self._specifier["sign"] = value + return self + + def symbol(self, value): + self._validate_named(value, Symbol) + + self._specifier["symbol"] = value + return self + + def trim(self, value): + if isinstance(value, bool): + value = Trim.yes if value else Trim.no + + self._validate_named(value, Trim) + + self._specifier["trim"] = value + return self + + # Locale + def symbol_prefix(self, value): + self._validate_string(value) + + if "symbol" not in self._locale: + self._locale["symbol"] = [value, ""] + else: + self._locale["symbol"][0] = value + + return self + + def symbol_suffix(self, value): + self._validate_string(value) + + if "symbol" not in self._locale: + self._locale["symbol"] = ["", value] + else: + self._locale["symbol"][1] = value + + return self + + def decimal_delimiter(self, value): + self._validate_char(value) + + self._locale["decimal"] = value + return self + + def group_delimiter(self, value): + self._validate_char(value) + + self._locale["group"] = value + return self + + def groups(self, groups): + groups = ( + groups + if isinstance(groups, list) + else [groups] + if isinstance(groups, int) + else None + ) + + if not isinstance(groups, list): + raise TypeError("expected groups to be an integer or a list of integers") + if len(groups) == 0: + raise ValueError( + "expected groups to be an integer or a list of " "one or more integers" + ) + + for group in groups: + if not isinstance(group, int): + raise TypeError("expected entry to be an integer") + + if group <= 0: + raise ValueError("expected entry to be a non-negative integer") + + self._locale["grouping"] = groups + return self + + # Nully + def nully(self, value): + self._nully = value + return self + + # Prefix + def si_prefix(self, value): + self._validate_named(value, Prefix) + + self._prefix = value + return self + + def to_plotly_json(self): + f = {} + f["locale"] = self._locale.copy() + f["nully"] = self._nully + f["prefix"] = self._prefix + aligned = self._specifier["align"] != Align.default + f["specifier"] = "{}{}{}{}{}{}{}{}{}{}".format( + self._specifier["fill"] if aligned else "", + self._specifier["align"], + self._specifier["sign"], + self._specifier["symbol"], + self._specifier["padding"], + self._specifier["width"], + self._specifier["group"], + self._specifier["precision"], + self._specifier["trim"], + self._specifier["type"], + ) + + return f + +=# \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 896f71c..dad16ae 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ -include("env.jl") +#=include("env.jl") include("resources.jl") include("devtools.jl") include("dash_creation.jl") @@ -9,5 +9,6 @@ include("context.jl") include("core.jl") include("misc.jl") include("callbacks.jl") -include("components_utils.jl") +include("components_utils.jl")=# +include("table_format.jl") #include("dev.jl") \ No newline at end of file diff --git a/test/table_format.jl b/test/table_format.jl new file mode 100644 index 0000000..7d7177e --- /dev/null +++ b/test/table_format.jl @@ -0,0 +1,84 @@ +using Test +using Dash +using Dash.TableFormat +@testset "named values" begin + @test TableFormat.Align.left.value == "<" + a = TableFormat.Align.default + @test a == TableFormat.Align.default + @test TableFormat.possible_values(TableFormat.Align) == "Align.default, Align.left, Align.right, Align.center, Align.right_sign" + @test a != TableFormat.Align.left + @test a != TableFormat.Scheme.default +end + +@testset "specifier" begin + f = Format() + + @test f.specifier[:align] == TableFormat.Align.default + TableFormat.align!(f, TableFormat.Align.left) + @test f.specifier[:align] == TableFormat.Align.left + @test_throws ArgumentError TableFormat.align!(f, TableFormat.Group.no) + + TableFormat.fill!(f, '-') + @test f.specifier[:fill] == '-' + + + TableFormat.group!(f, TableFormat.Group.yes) + @test f.specifier[:group] == TableFormat.Group.yes + + TableFormat.group!(f, true) + @test f.specifier[:group] == TableFormat.Group.yes + TableFormat.group!(f, false) + @test f.specifier[:group] == TableFormat.Group.no + @test_throws ArgumentError TableFormat.group!(f, "ffff") + + TableFormat.padding!(f, TableFormat.Padding.yes) + @test f.specifier[:padding] == TableFormat.Padding.yes + TableFormat.padding!(f, true) + @test f.specifier[:padding] == TableFormat.Padding.yes + TableFormat.padding!(f, false) + @test f.specifier[:padding] == TableFormat.Padding.no + @test_throws ArgumentError TableFormat.padding!(f, "ffff") + + TableFormat.padding_width!(f, 100) + @test f.specifier[:width] == 100 + TableFormat.padding_width!(f, nothing) + @test f.specifier[:width] == "" + @test_throws ArgumentError TableFormat.padding_width!(f, "ffff") + @test_throws ArgumentError TableFormat.padding_width!(f, -10) + + TableFormat.precision!(f, 3) + @test f.specifier[:precision] == ".3" + TableFormat.precision!(f, nothing) + @test f.specifier[:precision] == "" + @test_throws ArgumentError TableFormat.precision!(f, "ffff") + @test_throws ArgumentError TableFormat.precision!(f, -10) + + @test f.specifier[:type] == TableFormat.Scheme.default + TableFormat.scheme!(f, TableFormat.Scheme.decimal) + @test f.specifier[:type] == TableFormat.Scheme.decimal + @test_throws ArgumentError TableFormat.scheme!(f, "ccc") + + @test f.specifier[:sign] == TableFormat.Sign.default + TableFormat.sign!(f, TableFormat.Sign.negative) + @test f.specifier[:sign] == TableFormat.Sign.negative + @test_throws ArgumentError TableFormat.sign!(f, "ccc") + + @test f.specifier[:symbol] == TableFormat.DSymbol.no + TableFormat.symbol!(f, TableFormat.DSymbol.yes) + @test f.specifier[:symbol] == TableFormat.DSymbol.yes + @test_throws ArgumentError TableFormat.symbol!(f, "ccc") + + @test f.specifier[:trim] == TableFormat.Trim.no + TableFormat.trim!(f, TableFormat.Trim.yes) + @test f.specifier[:trim] == TableFormat.Trim.yes + TableFormat.trim!(f, false) + @test f.specifier[:trim] == TableFormat.Trim.no + @test_throws ArgumentError TableFormat.trim!(f, "ccc") + +end + +@testset "locale" begin + f = Format() + + +end \ No newline at end of file From ed884ac78944bcdabddc45a2952a6456c9e5503f Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Tue, 2 Nov 2021 19:46:31 +0300 Subject: [PATCH 04/10] in work --- src/components_utils/table_format.jl | 185 ++++++++++++++++----------- test/table_format.jl | 129 ++++++++++++++++--- 2 files changed, 220 insertions(+), 94 deletions(-) diff --git a/src/components_utils/table_format.jl b/src/components_utils/table_format.jl index b343182..e47484f 100644 --- a/src/components_utils/table_format.jl +++ b/src/components_utils/table_format.jl @@ -1,5 +1,5 @@ module TableFormat - export Format + export Format, Align, Group, Padding, Prefix, Scheme, Sign, DSymbol, Trim struct NamedValue{Name, T} value::T @@ -90,75 +90,59 @@ module TableFormat yes = "~" )) - abstract type FormatProperty end; - - struct NamedProperty{Name, Keys} <: FormatProperty - value::NamedValue{Name} - function NamedProperty{Name, Keys}(value::NamedValue{ValueName}) where {Name, ValueName, Keys} - Name != ValueName && throw(ArgumentError("expected value to be one of $(join(string.(string(Name), ".", string.(Keys)), ", "))")) - return new{Name, Keys}(value) - end - end - - property_by_tuple(::TupleWithNamedValues{Name, Keys}) where {Name, Keys} = NamedProperty{Name, Keys} - - get_value(p::NamedProperty) = p.value.value - - struct CharProperty <: FormatProperty - value::Char - CharProperty(value::Char) = new(value) - function CharProperty(value::String) - length(value) != 1 && throw(ArgumentError("expected char or string of length one")) - return new(value[1]) - end - CharProperty(value) = throw(ArgumentError("expected char or string of length one")) - end - - get_value(p::CharProperty) = p.value - - struct StringProperty <: FormatProperty - value::String - StringProperty(value::String) = new(value) - StringProperty(value) = throw(ArgumentError("expected value to be a string")) - end - - get_value(p::StringProperty) = p.value - - struct UIntOrNothingProperty <: FormatProperty - value::Union{UInt, Nothing} - UIntOrNothingProperty(v::Nothing) = new(v) - function UIntOrNothingProperty(v::Integer) - v < 0 && throw(ArgumentError("expected value to be non-negative")) - return new(v) - end - UIntOrNothingProperty(v) = throw(ArgumentError("expected value to be an integer")) - end - - get_value(p::UIntOrNothingProperty) = p.value - mutable struct Format locale nully prefix specifier - function Format() - return new( + function Format(; + align = Align.default, + fill = nothing, + group = Group.no, + padding = Padding.no, + padding_width = nothing, + precision = nothing, + scheme = Scheme.default, + sign = Sign.default, + symbol = DSymbol.no, + trim = Trim.no, + + symbol_prefix = nothing, + symbol_suffix = nothing, + decimal_delimiter = nothing, + group_delimiter = nothing, + groups = nothing, + + nully = nothing, + + si_prefix = Prefix.none + ) + result = new( Dict(), "", - Prefix.none, - Dict( - :align => Align.default, - :fill => "", - :group => Group.no, - :width => "", - :padding => Padding.no, - :precision => "", - :sign => Sign.default, - :symbol => DSymbol.no, - :trim => Trim.no, - :type => Scheme.default - ) + Prefix.none.value, + Dict{Symbol, Any}() ) + align!(result, align) + fill!(result, fill) + group!(result, group) + padding!(result, padding) + padding_width!(result, padding_width) + precision!(result, precision) + scheme!(result, scheme) + sign!(result, sign) + symbol!(result, symbol) + trim!(result, trim) + + !isnothing(symbol_prefix) && symbol_prefix!(result, symbol_prefix) + !isnothing(symbol_suffix) && symbol_suffix!(result, symbol_suffix) + !isnothing(decimal_delimiter) && decimal_delimiter!(result, decimal_delimiter) + !isnothing(group_delimiter) && group_delimiter!(result, group_delimiter) + !isnothing(groups) && groups!(result, groups) + !isnothing(nully) && nully!(result, nully) + !isnothing(si_prefix) && si_prefix!(result, si_prefix) + + return result end end @@ -170,10 +154,10 @@ module TableFormat throw(ArgumentError("expected value to be one of $(possible_values(t))")) end - check_char_value(v::Char) = v + check_char_value(v::Char) = string(v) function check_char_value(v::String) - length(v) != 1 && throw(ArgumentError("expected char or string of length one")) - return v[1] + length(v) > 1 && throw(ArgumentError("expected char or string of length one")) + return v end function check_char_value(v) throw(ArgumentError("expected char or string of length one")) @@ -189,10 +173,14 @@ module TableFormat function align!(f::Format, value) check_named_value(Align, value) - f.specifier[:align] = value + f.specifier[:align] = value.value end function fill!(f::Format, value) + if isnothing(value) + f.specifier[:fill] = "" + return + end v = check_char_value(value) f.specifier[:fill] = v end @@ -203,7 +191,7 @@ module TableFormat value = value ? Group.yes : Group.no end check_named_value(Group, value) - f.specifier[:group] = value + f.specifier[:group] = value.value end function padding!(f::Format, value) @@ -211,7 +199,7 @@ module TableFormat value = value ? Padding.yes : Padding.no end check_named_value(Padding, value) - f.specifier[:padding] = value + f.specifier[:padding] = value.value end function padding_width!(f::Format, value) @@ -234,17 +222,17 @@ module TableFormat function scheme!(f::Format, value) check_named_value(Scheme, value) - f.specifier[:type] = value + f.specifier[:type] = value.value end function sign!(f::Format, value) check_named_value(Sign, value) - f.specifier[:sign] = value + f.specifier[:sign] = value.value end function symbol!(f::Format, value) check_named_value(DSymbol, value) - f.specifier[:symbol] = value + f.specifier[:symbol] = value.value end function trim!(f::Format, value) @@ -252,12 +240,12 @@ module TableFormat value = value ? Trim.yes : Trim.no end check_named_value(Trim, value) - f.specifier[:trim] = value + f.specifier[:trim] = value.value end # Locale function symbol_prefix!(f::Format, value::AbstractString) - if haskey(f.locale, :symbol) + if !haskey(f.locale, :symbol) f.locale[:symbol] = [value, ""] else f.locale[:symbol][1] = value @@ -265,7 +253,7 @@ module TableFormat end function symbol_suffix!(f::Format, value::AbstractString) - if haskey(f.locale, :symbol) + if !haskey(f.locale, :symbol) f.locale[:symbol] = ["", value] else f.locale[:symbol][2] = value @@ -282,6 +270,54 @@ module TableFormat f.locale[:group] = v end + function groups!(f::Format, value::Union{Vector{<:Integer}, <:Integer}) + groups = value isa Integer ? [value] : value + isempty(groups) && throw(ArgumentError("groups cannot be empty")) + + for g in groups + g < 0 && throw(ArgumentError("group entry must be non-negative integer")) + end + f.locale[:grouping] = groups + end + + # Nully + function nully!(f::Format, value) + f.nully = value + end + + # Prefix + function si_prefix!(f::Format, value) + check_named_value(Prefix, value) + f.prefix = value.value + end + + JSON3.StructTypes.StructType(::Type{Format}) = JSON3.RawType() + + function JSON3.rawbytes(f::Format) + aligned = f.specifier[:align] != Align.default.value + fill = aligned ? f.specifier[:fill] : "" + spec_io = IOBuffer() + print(spec_io, + aligned ? f.specifier[:fill] : "", + f.specifier[:align], + f.specifier[:sign], + f.specifier[:symbol], + f.specifier[:padding], + f.specifier[:width], + f.specifier[:group], + f.specifier[:precision], + f.specifier[:trim], + f.specifier[:type] + ) + return JSON3.write( + locale = deepcopy(f.locale), + nully = f.nully, + prefix = f.prefix, + specifier = String(take!(spec_io)) + ) + end + + #=struct Format align ::Union{typeof(Align), Nothing} fill ::Union{Char, Nothing} @@ -567,7 +603,6 @@ class Format: # Prefix def si_prefix(self, value): self._validate_named(value, Prefix) - self._prefix = value return self diff --git a/test/table_format.jl b/test/table_format.jl index 7d7177e..d1f90c1 100644 --- a/test/table_format.jl +++ b/test/table_format.jl @@ -13,30 +13,33 @@ end @testset "specifier" begin f = Format() - @test f.specifier[:align] == TableFormat.Align.default + @test f.specifier[:align] == TableFormat.Align.default.value TableFormat.align!(f, TableFormat.Align.left) - @test f.specifier[:align] == TableFormat.Align.left + @test f.specifier[:align] == TableFormat.Align.left.value @test_throws ArgumentError TableFormat.align!(f, TableFormat.Group.no) TableFormat.fill!(f, '-') - @test f.specifier[:fill] == '-' + @test f.specifier[:fill] == "-" + TableFormat.fill!(f, "+") + @test f.specifier[:fill] == "+" + @test_throws ArgumentError TableFormat.fill!(f, "++") TableFormat.group!(f, TableFormat.Group.yes) - @test f.specifier[:group] == TableFormat.Group.yes + @test f.specifier[:group] == TableFormat.Group.yes.value TableFormat.group!(f, true) - @test f.specifier[:group] == TableFormat.Group.yes + @test f.specifier[:group] == TableFormat.Group.yes.value TableFormat.group!(f, false) - @test f.specifier[:group] == TableFormat.Group.no + @test f.specifier[:group] == TableFormat.Group.no.value @test_throws ArgumentError TableFormat.group!(f, "ffff") TableFormat.padding!(f, TableFormat.Padding.yes) - @test f.specifier[:padding] == TableFormat.Padding.yes + @test f.specifier[:padding] == TableFormat.Padding.yes.value TableFormat.padding!(f, true) - @test f.specifier[:padding] == TableFormat.Padding.yes + @test f.specifier[:padding] == TableFormat.Padding.yes.value TableFormat.padding!(f, false) - @test f.specifier[:padding] == TableFormat.Padding.no + @test f.specifier[:padding] == TableFormat.Padding.no.value @test_throws ArgumentError TableFormat.padding!(f, "ffff") TableFormat.padding_width!(f, 100) @@ -53,32 +56,120 @@ end @test_throws ArgumentError TableFormat.precision!(f, "ffff") @test_throws ArgumentError TableFormat.precision!(f, -10) - @test f.specifier[:type] == TableFormat.Scheme.default + @test f.specifier[:type] == TableFormat.Scheme.default.value TableFormat.scheme!(f, TableFormat.Scheme.decimal) - @test f.specifier[:type] == TableFormat.Scheme.decimal + @test f.specifier[:type] == TableFormat.Scheme.decimal.value @test_throws ArgumentError TableFormat.scheme!(f, "ccc") - @test f.specifier[:sign] == TableFormat.Sign.default + @test f.specifier[:sign] == TableFormat.Sign.default.value TableFormat.sign!(f, TableFormat.Sign.negative) - @test f.specifier[:sign] == TableFormat.Sign.negative + @test f.specifier[:sign] == TableFormat.Sign.negative.value @test_throws ArgumentError TableFormat.sign!(f, "ccc") - @test f.specifier[:symbol] == TableFormat.DSymbol.no + @test f.specifier[:symbol] == TableFormat.DSymbol.no.value TableFormat.symbol!(f, TableFormat.DSymbol.yes) - @test f.specifier[:symbol] == TableFormat.DSymbol.yes + @test f.specifier[:symbol] == TableFormat.DSymbol.yes.value @test_throws ArgumentError TableFormat.symbol!(f, "ccc") - @test f.specifier[:trim] == TableFormat.Trim.no + @test f.specifier[:trim] == TableFormat.Trim.no.value TableFormat.trim!(f, TableFormat.Trim.yes) - @test f.specifier[:trim] == TableFormat.Trim.yes + @test f.specifier[:trim] == TableFormat.Trim.yes.value TableFormat.trim!(f, false) - @test f.specifier[:trim] == TableFormat.Trim.no + @test f.specifier[:trim] == TableFormat.Trim.no.value @test_throws ArgumentError TableFormat.trim!(f, "ccc") end @testset "locale" begin f = Format() + @test isempty(f.locale) + TableFormat.symbol_prefix!(f, "lll") + @test haskey(f.locale, :symbol) + @test f.locale[:symbol][1] == "lll" + @test f.locale[:symbol][2] == "" + TableFormat.symbol_prefix!(f, "rrr") + @test f.locale[:symbol][1] == "rrr" + @test f.locale[:symbol][2] == "" - + + f = Format() + @test isempty(f.locale) + TableFormat.symbol_suffix!(f, "lll") + @test haskey(f.locale, :symbol) + @test f.locale[:symbol][1] == "" + @test f.locale[:symbol][2] == "lll" + TableFormat.symbol_suffix!(f, "rrr") + @test f.locale[:symbol][1] == "" + @test f.locale[:symbol][2] == "rrr" + + TableFormat.symbol_prefix!(f, "kkk") + @test f.locale[:symbol][1] == "kkk" + @test f.locale[:symbol][2] == "rrr" + + f = Format() + TableFormat.decimal_delimiter!(f, '|') + @test f.locale[:decimal] == "|" + TableFormat.group_delimiter!(f, ';') + @test f.locale[:group] == ";" + + TableFormat.groups!(f, [3,3,3]) + @test f.locale[:grouping] == [3,3,3] + + TableFormat.groups!(f, [5]) + @test f.locale[:grouping] == [5] + + @test_throws ArgumentError TableFormat.groups!(f, Int[]) + @test_throws ArgumentError TableFormat.groups!(f, Int[-1,2]) +end + +@testset "nully & prefix" begin + f = Format() + TableFormat.nully!(f, "gggg") + @test f.nully == "gggg" + TableFormat.si_prefix!(f, TableFormat.Prefix.femto) + @test f.prefix == TableFormat.Prefix.femto.value +end + +@testset "Format creation" begin + f = Format( + align = Align.left, + fill = "+", + group = true, + padding = Padding.yes, + padding_width = 10, + precision = 2, + scheme = Scheme.decimal, + sign = Sign.negative, + symbol = DSymbol.yes, + trim = Trim.yes, + + symbol_prefix = "tt", + symbol_suffix = "kk", + decimal_delimiter = ";", + group_delimiter = ",", + groups = [2, 2], + nully = "f", + si_prefix = Prefix.femto + ) + + @test f.specifier == Dict{Symbol, Any}( + :align => Align.left.value, + :fill => "+", + :group => Group.yes.value, + :padding => Padding.yes.value, + :width => 10, + :precision => ".2", + :type => Scheme.decimal.value, + :sign => Sign.negative.value, + :symbol => DSymbol.yes.value, + :trim => Trim.yes.value + ) + + @test f.nully == "f" + @test f.prefix == Prefix.femto.value + + @test f.locale[:symbol] == ["tt", "kk"] + @test f.locale[:decimal] == ";" + @test f.locale[:group] == "," + @test f.locale[:grouping] == [2,2] end \ No newline at end of file From 9c2b5f96631c8a5bd5f353c6ec9da1c6dbc4cff3 Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Tue, 2 Nov 2021 21:12:13 +0300 Subject: [PATCH 05/10] table_format --- src/components_utils/table_format.jl | 332 ++------------------------- test/runtests.jl | 4 +- test/table_format.jl | 2 + 3 files changed, 23 insertions(+), 315 deletions(-) diff --git a/src/components_utils/table_format.jl b/src/components_utils/table_format.jl index e47484f..b38b9da 100644 --- a/src/components_utils/table_format.jl +++ b/src/components_utils/table_format.jl @@ -1,4 +1,5 @@ module TableFormat + using JSON3 export Format, Align, Group, Padding, Prefix, Scheme, Sign, DSymbol, Trim struct NamedValue{Name, T} @@ -113,7 +114,7 @@ module TableFormat group_delimiter = nothing, groups = nothing, - nully = nothing, + nully = "", si_prefix = Prefix.none ) @@ -310,321 +311,26 @@ module TableFormat f.specifier[:type] ) return JSON3.write( - locale = deepcopy(f.locale), - nully = f.nully, - prefix = f.prefix, - specifier = String(take!(spec_io)) - ) - end - - - #=struct Format - align ::Union{typeof(Align), Nothing} - fill ::Union{Char, Nothing} - group ::Union{typeof(Group), Bool, Nothing} - padding ::Union{typeof(Padding), Bool, Nothing} - padding_width ::Union{Integer, Nothing} - precision ::Union{Integer, Nothing} - scheme ::Union{typeof(Scheme), } - sign, - symbol, - trim, - - #locale - symbol_prefix, - symbol_suffix, - decimal_delimiter, - group_delimiter, - groups, - - #Nully - nully, - #Prefix - si_prefix - function Format(; - locale = (), - nully = "", - prefix = Prefix.none, - align = Align.default, - fill = "", - group = Group.no, - width = "", - padding = Padding.no, - precision = "", - sign = Sign.default, - symbol = Symbol.no, - trim = Trim.no, - type = Scheme.default - ) - return new( - locale, - nully, - prefix, - ( - align = align, - fill = fill, - group = group, - width = width, - padding = padding, - precision = precision, - sign = sign, - symbol = symbol, - trim = trim, - type = type - ) + ( + locale = deepcopy(f.locale), + nully = f.nully, + prefix = f.prefix, + specifier = String(take!(spec_io)) ) - end + ) end - function format(; - align, - fill, - group, - padding, - padding_width, - precision, - scheme, - sign, - symbol, - trim, - - #locale - symbol_prefix, - symbol_suffix, - decimal_delimiter, - group_delimiter, - groups, - - #Nully - nully, - #Prefix - si_prefix + money(decimals, sign = Sign.default) = Format( + group=Group.yes, + precision=decimals, + scheme=Scheme.fixed, + sign=sign, + symbol=Symbol.yes ) - end=# -end -#= -import collections - - - - -Trim = get_named_tuple("trim", {"no": "", "yes": "~"}) - - -class Format: - def __init__(self, **kwargs): - self._locale = {} - self._nully = "" - self._prefix = Prefix.none - self._specifier = { - "align": Align.default, - "fill": "", - "group": Group.no, - "width": "", - "padding": Padding.no, - "precision": "", - "sign": Sign.default, - "symbol": Symbol.no, - "trim": Trim.no, - "type": Scheme.default, - } - - valid_methods = [ - m for m in dir(self.__class__) if m[0] != "_" and m != "to_plotly_json" - ] - - for kw, val in kwargs.items(): - if kw not in valid_methods: - raise TypeError( - "{0} is not a format method. Expected one of".format(kw), - str(list(valid_methods)), - ) - - getattr(self, kw)(val) - - def _validate_char(self, value): - self._validate_string(value) - - if len(value) != 1: - raise ValueError("expected value to a string of length one") - - def _validate_non_negative_integer_or_none(self, value): - if value is None: - return - - if not isinstance(value, int): - raise TypeError("expected value to be an integer") - - if value < 0: - raise ValueError("expected value to be non-negative", str(value)) - - def _validate_named(self, value, named_values): - if value not in named_values: - raise TypeError("expected value to be one of", str(list(named_values))) - - def _validate_string(self, value): - if not isinstance(value, (str, u"".__class__)): - raise TypeError("expected value to be a string") - - # Specifier - def align(self, value): - self._validate_named(value, Align) - - self._specifier["align"] = value - return self - - def fill(self, value): - self._validate_char(value) - - self._specifier["fill"] = value - return self - - def group(self, value): - if isinstance(value, bool): - value = Group.yes if value else Group.no - - self._validate_named(value, Group) - - self._specifier["group"] = value - return self - - def padding(self, value): - if isinstance(value, bool): - value = Padding.yes if value else Padding.no - self._validate_named(value, Padding) - self._specifier["padding"] = value - return self - - def padding_width(self, value): - self._validate_non_negative_integer_or_none(value) - - self._specifier["width"] = value if value is not None else "" - return self - - def precision(self, value): - self._validate_non_negative_integer_or_none(value) - - self._specifier["precision"] = ".{0}".format(value) if value is not None else "" - return self - - def scheme(self, value): - self._validate_named(value, Scheme) - - self._specifier["type"] = value - return self - - def sign(self, value): - self._validate_named(value, Sign) - - self._specifier["sign"] = value - return self - - def symbol(self, value): - self._validate_named(value, Symbol) - - self._specifier["symbol"] = value - return self - - def trim(self, value): - if isinstance(value, bool): - value = Trim.yes if value else Trim.no - - self._validate_named(value, Trim) - - self._specifier["trim"] = value - return self - - # Locale - def symbol_prefix(self, value): - self._validate_string(value) - - if "symbol" not in self._locale: - self._locale["symbol"] = [value, ""] - else: - self._locale["symbol"][0] = value - - return self - - def symbol_suffix(self, value): - self._validate_string(value) - - if "symbol" not in self._locale: - self._locale["symbol"] = ["", value] - else: - self._locale["symbol"][1] = value - - return self - - def decimal_delimiter(self, value): - self._validate_char(value) - - self._locale["decimal"] = value - return self - - def group_delimiter(self, value): - self._validate_char(value) - - self._locale["group"] = value - return self - - def groups(self, groups): - groups = ( - groups - if isinstance(groups, list) - else [groups] - if isinstance(groups, int) - else None - ) - - if not isinstance(groups, list): - raise TypeError("expected groups to be an integer or a list of integers") - if len(groups) == 0: - raise ValueError( - "expected groups to be an integer or a list of " "one or more integers" - ) - - for group in groups: - if not isinstance(group, int): - raise TypeError("expected entry to be an integer") - - if group <= 0: - raise ValueError("expected entry to be a non-negative integer") - - self._locale["grouping"] = groups - return self - - # Nully - def nully(self, value): - self._nully = value - return self - - # Prefix - def si_prefix(self, value): - self._validate_named(value, Prefix) - self._prefix = value - return self - - def to_plotly_json(self): - f = {} - f["locale"] = self._locale.copy() - f["nully"] = self._nully - f["prefix"] = self._prefix - aligned = self._specifier["align"] != Align.default - f["specifier"] = "{}{}{}{}{}{}{}{}{}{}".format( - self._specifier["fill"] if aligned else "", - self._specifier["align"], - self._specifier["sign"], - self._specifier["symbol"], - self._specifier["padding"], - self._specifier["width"], - self._specifier["group"], - self._specifier["precision"], - self._specifier["trim"], - self._specifier["type"], - ) - - return f - -=# \ No newline at end of file + function percentage(decimals, rounded::Bool=false) + scheme = rounded ? Scheme.percentage_rounded : Scheme.percentage + return Format(scheme = scheme, precision = decimals) + end +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index dad16ae..9dde565 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ -#=include("env.jl") +include("env.jl") include("resources.jl") include("devtools.jl") include("dash_creation.jl") @@ -9,6 +9,6 @@ include("context.jl") include("core.jl") include("misc.jl") include("callbacks.jl") -include("components_utils.jl")=# +include("components_utils.jl") include("table_format.jl") #include("dev.jl") \ No newline at end of file diff --git a/test/table_format.jl b/test/table_format.jl index d1f90c1..252ae48 100644 --- a/test/table_format.jl +++ b/test/table_format.jl @@ -1,6 +1,7 @@ using Test using Dash using Dash.TableFormat +import JSON3 @testset "named values" begin @test TableFormat.Align.left.value == "<" a = TableFormat.Align.default @@ -172,4 +173,5 @@ end @test f.locale[:decimal] == ";" @test f.locale[:group] == "," @test f.locale[:grouping] == [2,2] + end \ No newline at end of file From 8be6b2b6b7f07f8c002aaa1012b6c87828372e41 Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Wed, 3 Nov 2021 16:28:40 +0300 Subject: [PATCH 06/10] optimization --- src/handler/index_page.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handler/index_page.jl b/src/handler/index_page.jl index 039df6e..6e6620e 100644 --- a/src/handler/index_page.jl +++ b/src/handler/index_page.jl @@ -103,7 +103,7 @@ function favicon_html(app::DashApp, resources::ApplicationResources) favicon_url = if !isnothing(resources.favicon) asset_path(app, resources.favicon.path) else - "$(get_setting(app, :requests_pathname_prefix))_favicon.ico?v=$(build_info().dash_version)" + "$(get_setting(app, :requests_pathname_prefix))_favicon.ico?v=$(_metadata.dash["version"])" end return format_tag( "link", From dda4dd37ce1846dc89fef044ee55b6f12d955d22 Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Wed, 3 Nov 2021 16:56:45 +0300 Subject: [PATCH 07/10] Trigger ci From 925744e70fd34a31598efa596b6ac0a03380d15b Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Wed, 10 Nov 2021 15:03:40 +0300 Subject: [PATCH 08/10] change send_ functions signature --- src/components_utils/express.jl | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/components_utils/express.jl b/src/components_utils/express.jl index d6bc9ca..589c39d 100644 --- a/src/components_utils/express.jl +++ b/src/components_utils/express.jl @@ -17,9 +17,10 @@ end """ dcc_send_bytes(src::AbstractVector{UInt8}, filename; type = nothing) - dcc_send_bytes(src::Function, filename; type = nothing) + dcc_send_bytes(writer::Function, data, filename; type = nothing) Convert vector of bytes into the format expected by the Download component. +`writer` function must have signature `(io::IO, data)` # Examples @@ -37,9 +38,7 @@ using DataFrames, Arrow ... df = DataFrame(...) callback!(app, Output("download", "data"), Input("download-btn", "n_clicks"), prevent_initial_call = true) do n_clicks - return dcc_send_bytes("df.arr") do io - Arrow.write(io, df) - end + return dcc_send_bytes(Arrow.write, df, "df.arr") end ``` """ @@ -53,18 +52,18 @@ function dcc_send_bytes(src::AbstractVector{UInt8}, filename; type = nothing) ) end -function dcc_send_bytes(src::Function, filename; type = nothing) +function dcc_send_bytes(writer::Function, data, filename; type = nothing) io = IOBuffer() - src(io) + writer(io, data) return dcc_send_bytes(take!(io), filename, type = type) end """ dcc_send_data(src::AbstractString, filename; type = nothing) - dcc_send_data(src::Function, filename; type = nothing) + dcc_send_data(writer::Function, data, filename; type = nothing) Convert string into the format expected by the Download component. -`src` is a string or function that takes a single argument - io and writes data to it +`writer` function must have signature `(io::IO, data)` # Examples @@ -82,9 +81,7 @@ using DataFrames, CSV ... df = DataFrame(...) callback!(app, Output("download", "data"), Input("download-btn", "n_clicks"), prevent_initial_call = true) do n_clicks - return dcc_send_string("df.csv") do io - CSV.write(io, df) - end + return dcc_send_string(CSV.write, df, "df.csv") end ``` """ @@ -98,8 +95,8 @@ function dcc_send_string(src::AbstractString, filename; type = nothing) ) end -function dcc_send_string(src::Function, filename; type = nothing) +function dcc_send_string(writer::Function, data, filename; type = nothing) io = IOBuffer() - src(io) + writer(io, data) return dcc_send_string(String(take!(io)), filename, type = type) end \ No newline at end of file From 82cbd749ec55e9d4ca9973de2106fb2e6f84a05b Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Wed, 10 Nov 2021 16:12:09 +0300 Subject: [PATCH 09/10] fix --- test/components_utils.jl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/components_utils.jl b/test/components_utils.jl index 5d1f620..d908138 100644 --- a/test/components_utils.jl +++ b/test/components_utils.jl @@ -10,8 +10,8 @@ using Base64 @test !res[:base64] data2 = "test 2 string" - res = dcc_send_string("test_file.csv"; type = "text/csv") do io - write(io, data2) + res = dcc_send_string(data2, "test_file.csv"; type = "text/csv") do io, data + write(io, data) end @test res[:content] == data2 @test res[:filename] == "test_file.csv" @@ -27,9 +27,7 @@ end @test res[:base64] data2 = "test 2 string" - res = dcc_send_bytes("test_file.csv"; type = "text/csv") do io - write(io, data2) - end + res = dcc_send_bytes(write, data2, "test_file.csv"; type = "text/csv") @test res[:content] == Base64.base64encode(data2) @test res[:filename] == "test_file.csv" @test res[:type] == "text/csv" From cc7ebaf8c974b0938da413b5c70a72c2eea1c0ad Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Wed, 10 Nov 2021 20:58:33 +0300 Subject: [PATCH 10/10] fix --- src/components_utils/table_format.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components_utils/table_format.jl b/src/components_utils/table_format.jl index b38b9da..b2af952 100644 --- a/src/components_utils/table_format.jl +++ b/src/components_utils/table_format.jl @@ -312,7 +312,7 @@ module TableFormat ) return JSON3.write( ( - locale = deepcopy(f.locale), + locale = f.locale, nully = f.nully, prefix = f.prefix, specifier = String(take!(spec_io))