From d2dafff1215b2411dc63fb36bcaf7516f269cc1f Mon Sep 17 00:00:00 2001 From: Helmut Haensel Date: Tue, 17 Dec 2019 13:37:30 +0100 Subject: [PATCH 01/18] Allow for Complex and Rationals and user types --- src/fmt.jl | 12 +++++++++--- src/fmtcore.jl | 38 ++++++++++++++++++++++++++++++++++++++ src/fmtspec.jl | 15 +++++++++++++-- test/fmt.jl | 21 +++++++++++++++++++-- test/fmtspec.jl | 14 ++++++++++++++ 5 files changed, 93 insertions(+), 7 deletions(-) diff --git a/src/fmt.jl b/src/fmt.jl index 344b143..06d46c6 100644 --- a/src/fmt.jl +++ b/src/fmt.jl @@ -29,7 +29,7 @@ function DefaultSpec(c::AbstractChar, syms...; kwargs...) end end -const DEFAULT_FORMATTERS = Dict{DataType, DefaultSpec}() +const DEFAULT_FORMATTERS = Dict{Type{T} where T, DefaultSpec}() # adds a new default formatter for this type default_spec!(::Type{T}, c::AbstractChar) where {T} = @@ -79,6 +79,9 @@ default_spec(::Type{<:AbstractString}) = DEFAULT_FORMATTERS[AbstractString] default_spec(::Type{<:AbstractChar}) = DEFAULT_FORMATTERS[AbstractChar] default_spec(::Type{<:AbstractIrrational}) = DEFAULT_FORMATTERS[AbstractIrrational] default_spec(::Type{<:Number}) = DEFAULT_FORMATTERS[Number] +default_spec(::Complex{T} where T<:Integer) = DEFAULT_FORMATTERS[Complex{T} where T<:Integer] +default_spec(::Complex{T} where T<:AbstractFloat) = DEFAULT_FORMATTERS[Complex{T} where T<:AbstractFloat] +default_spec(::Complex{T} where T<:Rational) = DEFAULT_FORMATTERS[Complex{T} where T<:Rational] default_spec(::Type{T}) where {T} = get(DEFAULT_FORMATTERS, T) do @@ -189,7 +192,7 @@ function fmt end # TODO: do more caching to optimize repeated calls # creates a new FormatSpec by overriding the defaults and passes it to pyfmt -# note: adding kwargs is only appropriate for one-off formatting. +# note: adding kwargs is only appropriate for one-off formatting. # normally it will be much faster to change the fmt_default formatting as needed function fmt(x; kwargs...) fspec = fmt_default(x) @@ -220,9 +223,12 @@ end for (t, c) in [(Integer,'d'), (AbstractFloat,'f'), (AbstractChar,'c'), - (AbstractString,'s')] + (AbstractString,'s'), + (Complex{T} where T<:Integer, 'd' ), + (Complex{T} where T<:AbstractFloat, 'f' )] default_spec!(t, c) end default_spec!(Number, 's', :right) default_spec!(AbstractIrrational, 's', :right) +default_spec!(Complex{T} where T<:Rational, 's', :right) diff --git a/src/fmtcore.jl b/src/fmtcore.jl index 712b675..b7b6c64 100644 --- a/src/fmtcore.jl +++ b/src/fmtcore.jl @@ -1,4 +1,5 @@ # core formatting functions +export fmt_Number ### auxiliary functions @@ -262,3 +263,40 @@ function _pfmt_specialf(out::IO, fs::FormatSpec, x::AbstractFloat) end end +function _pfmt_Number_f(out::IO, fs::FormatSpec, x::Number, _pf::Function) + fsi = FormatSpec(fs, width = -1) + f = x::AbstractFloat->begin + io = IOBuffer() + _pf(io, fsi, x) + String(take!(io)) + end + s = fmt_Number(x, f) + _pfmt_s(out, fs, s) +end + +function _pfmt_Number_i(out::IO, fs::FormatSpec, x::Number, op::Op, _pf::Function) where {Op} + fsi = FormatSpec(fs, width = -1) + f = x::Integer->begin + io = IOBuffer() + _pf(io, fsi, x, op) + String(take!(io)) + end + s = fmt_Number(x, f) + _pfmt_s(out, fs, s) +end + +function _pfmt_i(out::IO, fs::FormatSpec, x::Number, op::Op) where {Op} + _pfmt_Number_i(out, fs, x, op, _pfmt_i) +end + +function _pfmt_f(out::IO, fs::FormatSpec, x::Number) + _pfmt_Number_f(out, fs, x, _pfmt_f) +end + +function _pfmt_e(out::IO, fs::FormatSpec, x::Number) + _pfmt_Number_f(out, fs, x, _pfmt_e) +end + +function fmt_Number(x::Complex, f::Function) + s = f(real(x)) * (imag(x) >= 0 ? " + " : " - ") * f(abs(imag(x))) * "im" +end diff --git a/src/fmtspec.jl b/src/fmtspec.jl index 95d4916..ac03d46 100644 --- a/src/fmtspec.jl +++ b/src/fmtspec.jl @@ -164,19 +164,30 @@ _srepr(x) = repr(x) _srepr(x::AbstractString) = x _srepr(x::AbstractChar) = string(x) _srepr(x::Enum) = string(x) +@static if VERSION < v"1.2.0-DEV" + _srepr(x::Irrational{sym}) where {sym} = string(sym) +end function printfmt(io::IO, fs::FormatSpec, x) cls = fs.cls ty = fs.typ if cls == 'i' - ix = Integer(x) + ix = x + try + ix = Integer(x) + catch + end ty == 'd' || ty == 'n' ? _pfmt_i(io, fs, ix, _Dec()) : ty == 'x' ? _pfmt_i(io, fs, ix, _Hex()) : ty == 'X' ? _pfmt_i(io, fs, ix, _HEX()) : ty == 'o' ? _pfmt_i(io, fs, ix, _Oct()) : _pfmt_i(io, fs, ix, _Bin()) elseif cls == 'f' - fx = float(x) + fx = x + try + fx = float(x) + catch + end if isfinite(fx) ty == 'f' || ty == 'F' ? _pfmt_f(io, fs, fx) : ty == 'e' || ty == 'E' ? _pfmt_e(io, fs, fx) : diff --git a/test/fmt.jl b/test/fmt.jl index 2c2f740..9fed6b4 100644 --- a/test/fmt.jl +++ b/test/fmt.jl @@ -20,8 +20,19 @@ i = 1234567 @test fmt(i) == "1234567" @test fmt(i,:commas) == "1,234,567" -@test_throws ErrorException fmt_default(Real) -@test_throws ErrorException fmt_default(Complex) +@test fmt(2 - 3im, 10) == " 2 - 3im" +@test fmt(pi - 3im, 15, 2) == " 3.14 - 3.00im" + +@test fmt(3//4, 10) == " 3//4" +@test fmt(1//2 + 6//2 * im, 15) == " 1//2 + 3//1*im" + +fmt_default!(Rational, 'f', prec = 2) +fmt_default!(Format.ComplexRational, 'f', prec = 2) + +@test fmt(3//4, 10, 2) == " 0.75" +@test fmt(3//4, 10, 1) == " 0.8" +@test fmt(1//2 + 6//2 * im, 23) == " 0.500000 + 3.000000im" +@test fmt(1//2 + 6//2 * im, 15, 2) == " 0.50 + 3.00im" fmt_default!(Int, :commas, width = 12) @test fmt(i) == " 1,234,567" @@ -41,3 +52,9 @@ fmt_default!(UInt16, 'd', :commas) fmt_default!(UInt32, UInt16, width=20) @test fmt(0xfffff) == " 1,048,575" +v = pi +@test fmt(v) == "π" +@test fmt(v; width=10) == " π" + +v = MathConstants.eulergamma +@test fmt(v, 10, 2) == " γ" diff --git a/test/fmtspec.jl b/test/fmtspec.jl index a1121b2..437f23d 100644 --- a/test/fmtspec.jl +++ b/test/fmtspec.jl @@ -234,3 +234,17 @@ end @test pyfmt("*>5f", Inf) == "**Inf" @test pyfmt("⋆>5f", Inf) == "⋆⋆Inf" end + +@testset "Format Symbols (S) for Irrationals" begin + @test pyfmt(">10s", pi) == " π" + @test pyfmt("10s", pi) == "π " + @test pyfmt("3", MathConstants.eulergamma) == " γ" + @test pyfmt("10.2f", MathConstants.eulergamma) == " 0.58" + @test pyfmt("<3s", MathConstants.e) == "ℯ " +end + +@testset "Format Symbols (S) for Irrationals" begin + pyfmt("10s", 3//4) == "3//4 " + pyfmt("10", 3//4) == " 3//4" + pyfmt("10.1f", 3//4) == " 0.8" +end From 3c883923b14fd0668e8df999a3db4a3c9ae5d578 Mon Sep 17 00:00:00 2001 From: Helmut Haensel Date: Tue, 17 Dec 2019 14:49:12 +0100 Subject: [PATCH 02/18] type conversion for formatting methods --- src/fmtcore.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/fmtcore.jl b/src/fmtcore.jl index b7b6c64..75d52f9 100644 --- a/src/fmtcore.jl +++ b/src/fmtcore.jl @@ -265,9 +265,10 @@ end function _pfmt_Number_f(out::IO, fs::FormatSpec, x::Number, _pf::Function) fsi = FormatSpec(fs, width = -1) - f = x::AbstractFloat->begin + f = x->begin + fx = AbstractFloat(x) # not float(x), this should error out, if conversion is not possible io = IOBuffer() - _pf(io, fsi, x) + _pf(io, fsi, fx) String(take!(io)) end s = fmt_Number(x, f) @@ -276,9 +277,10 @@ end function _pfmt_Number_i(out::IO, fs::FormatSpec, x::Number, op::Op, _pf::Function) where {Op} fsi = FormatSpec(fs, width = -1) - f = x::Integer->begin + f = x->begin + ix = Integer(x) io = IOBuffer() - _pf(io, fsi, x, op) + _pf(io, fsi, ix, op) String(take!(io)) end s = fmt_Number(x, f) From ed836bd46cb920d918b73599459c3030e192adea Mon Sep 17 00:00:00 2001 From: Helmut Haensel Date: Tue, 17 Dec 2019 15:04:19 +0100 Subject: [PATCH 03/18] printfmt returned true if suffix was empty --- src/formatexpr.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/formatexpr.jl b/src/formatexpr.jl index ef8cb45..ffd30ea 100644 --- a/src/formatexpr.jl +++ b/src/formatexpr.jl @@ -151,6 +151,7 @@ function printfmt(io::IO, fe::FormatExpr, args...) end end isempty(fe.suffix) || print(io, fe.suffix) + nothing end const StringOrFE = Union{AbstractString, FormatExpr} From 4a958eeaf231b561a29b8c64866bd6de60a73482 Mon Sep 17 00:00:00 2001 From: Helmut Haensel Date: Tue, 17 Dec 2019 22:18:40 +0100 Subject: [PATCH 04/18] correction in fmtspec and fmt test --- test/fmt.jl | 2 +- test/fmtspec.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fmt.jl b/test/fmt.jl index 9fed6b4..0876dd7 100644 --- a/test/fmt.jl +++ b/test/fmt.jl @@ -27,7 +27,7 @@ i = 1234567 @test fmt(1//2 + 6//2 * im, 15) == " 1//2 + 3//1*im" fmt_default!(Rational, 'f', prec = 2) -fmt_default!(Format.ComplexRational, 'f', prec = 2) +fmt_default!(Complex{T} where T<:Rational, 'f', prec = 2) @test fmt(3//4, 10, 2) == " 0.75" @test fmt(3//4, 10, 1) == " 0.8" diff --git a/test/fmtspec.jl b/test/fmtspec.jl index 437f23d..6789dba 100644 --- a/test/fmtspec.jl +++ b/test/fmtspec.jl @@ -238,7 +238,7 @@ end @testset "Format Symbols (S) for Irrationals" begin @test pyfmt(">10s", pi) == " π" @test pyfmt("10s", pi) == "π " - @test pyfmt("3", MathConstants.eulergamma) == " γ" + @test pyfmt("3", MathConstants.eulergamma) == "γ " @test pyfmt("10.2f", MathConstants.eulergamma) == " 0.58" @test pyfmt("<3s", MathConstants.e) == "ℯ " end From 7d436573d0f53ab91f7af9cbf03f2ffb71d27211 Mon Sep 17 00:00:00 2001 From: Helmut Haensel Date: Tue, 17 Dec 2019 22:53:32 +0100 Subject: [PATCH 05/18] Define constants for complex subtypes --- src/fmt.jl | 20 +++++++++++++------- test/fmt.jl | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/fmt.jl b/src/fmt.jl index 06d46c6..86e4e4a 100644 --- a/src/fmt.jl +++ b/src/fmt.jl @@ -73,15 +73,20 @@ end # methods to get the current default objects # note: if you want to set a default for an abstract type (i.e. AbstractFloat) # you'll need to extend this method like here: + +const ComplexInteger = Complex{<:Integer} +const ComplexFloat = Complex{<:AbstractFloat} +const ComplexRational = Complex{<:Rational} + default_spec(::Type{<:Integer}) = DEFAULT_FORMATTERS[Integer] default_spec(::Type{<:AbstractFloat}) = DEFAULT_FORMATTERS[AbstractFloat] default_spec(::Type{<:AbstractString}) = DEFAULT_FORMATTERS[AbstractString] default_spec(::Type{<:AbstractChar}) = DEFAULT_FORMATTERS[AbstractChar] default_spec(::Type{<:AbstractIrrational}) = DEFAULT_FORMATTERS[AbstractIrrational] default_spec(::Type{<:Number}) = DEFAULT_FORMATTERS[Number] -default_spec(::Complex{T} where T<:Integer) = DEFAULT_FORMATTERS[Complex{T} where T<:Integer] -default_spec(::Complex{T} where T<:AbstractFloat) = DEFAULT_FORMATTERS[Complex{T} where T<:AbstractFloat] -default_spec(::Complex{T} where T<:Rational) = DEFAULT_FORMATTERS[Complex{T} where T<:Rational] +default_spec(::ComplexInteger) = DEFAULT_FORMATTERS[ComplexInteger] +default_spec(::ComplexFloat) = DEFAULT_FORMATTERS[ComplexFloat] +default_spec(::ComplexRational) = DEFAULT_FORMATTERS[ComplexRational] default_spec(::Type{T}) where {T} = get(DEFAULT_FORMATTERS, T) do @@ -224,11 +229,12 @@ for (t, c) in [(Integer,'d'), (AbstractFloat,'f'), (AbstractChar,'c'), (AbstractString,'s'), - (Complex{T} where T<:Integer, 'd' ), - (Complex{T} where T<:AbstractFloat, 'f' )] + (ComplexInteger,'d'), + (ComplexFloat,'f')] default_spec!(t, c) end -default_spec!(Number, 's', :right) +default_spec!(Rational, 's', :right) default_spec!(AbstractIrrational, 's', :right) -default_spec!(Complex{T} where T<:Rational, 's', :right) +default_spec!(ComplexRational, 's', :right) +default_spec!(Number, 's', :right) diff --git a/test/fmt.jl b/test/fmt.jl index 0876dd7..9fed6b4 100644 --- a/test/fmt.jl +++ b/test/fmt.jl @@ -27,7 +27,7 @@ i = 1234567 @test fmt(1//2 + 6//2 * im, 15) == " 1//2 + 3//1*im" fmt_default!(Rational, 'f', prec = 2) -fmt_default!(Complex{T} where T<:Rational, 'f', prec = 2) +fmt_default!(Format.ComplexRational, 'f', prec = 2) @test fmt(3//4, 10, 2) == " 0.75" @test fmt(3//4, 10, 1) == " 0.8" From 874d042b48a967da1bfd205da6f51dca53fe753f Mon Sep 17 00:00:00 2001 From: Helmut Haensel Date: Tue, 17 Dec 2019 23:39:44 +0100 Subject: [PATCH 06/18] add Rationals and some code beautifying --- src/fmt.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fmt.jl b/src/fmt.jl index 86e4e4a..4c6cecf 100644 --- a/src/fmt.jl +++ b/src/fmt.jl @@ -29,7 +29,7 @@ function DefaultSpec(c::AbstractChar, syms...; kwargs...) end end -const DEFAULT_FORMATTERS = Dict{Type{T} where T, DefaultSpec}() +const DEFAULT_FORMATTERS = Dict{Type{<:Any}, DefaultSpec}() # adds a new default formatter for this type default_spec!(::Type{T}, c::AbstractChar) where {T} = @@ -83,6 +83,7 @@ default_spec(::Type{<:AbstractFloat}) = DEFAULT_FORMATTERS[AbstractFloat] default_spec(::Type{<:AbstractString}) = DEFAULT_FORMATTERS[AbstractString] default_spec(::Type{<:AbstractChar}) = DEFAULT_FORMATTERS[AbstractChar] default_spec(::Type{<:AbstractIrrational}) = DEFAULT_FORMATTERS[AbstractIrrational] +default_spec(::Type{<:Rational}) = DEFAULT_FORMATTERS[Rational] default_spec(::Type{<:Number}) = DEFAULT_FORMATTERS[Number] default_spec(::ComplexInteger) = DEFAULT_FORMATTERS[ComplexInteger] default_spec(::ComplexFloat) = DEFAULT_FORMATTERS[ComplexFloat] From cf67f99bebf80565280867c479b602278f05c516 Mon Sep 17 00:00:00 2001 From: Helmut Haensel Date: Wed, 18 Dec 2019 01:07:10 +0100 Subject: [PATCH 07/18] remove type instability --- src/fmtspec.jl | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/fmtspec.jl b/src/fmtspec.jl index ac03d46..c55fe8f 100644 --- a/src/fmtspec.jl +++ b/src/fmtspec.jl @@ -172,28 +172,34 @@ function printfmt(io::IO, fs::FormatSpec, x) cls = fs.cls ty = fs.typ if cls == 'i' - ix = x try ix = Integer(x) + ty == 'd' || ty == 'n' ? _pfmt_i(io, fs, ix, _Dec()) : + ty == 'x' ? _pfmt_i(io, fs, ix, _Hex()) : + ty == 'X' ? _pfmt_i(io, fs, ix, _HEX()) : + ty == 'o' ? _pfmt_i(io, fs, ix, _Oct()) : + _pfmt_i(io, fs, ix, _Bin()) catch + ty == 'd' || ty == 'n' ? _pfmt_i(io, fs, x, _Dec()) : + ty == 'x' ? _pfmt_i(io, fs, x, _Hex()) : + ty == 'X' ? _pfmt_i(io, fs, x, _HEX()) : + ty == 'o' ? _pfmt_i(io, fs, x, _Oct()) : + _pfmt_i(io, fs, x, _Bin()) end - ty == 'd' || ty == 'n' ? _pfmt_i(io, fs, ix, _Dec()) : - ty == 'x' ? _pfmt_i(io, fs, ix, _Hex()) : - ty == 'X' ? _pfmt_i(io, fs, ix, _HEX()) : - ty == 'o' ? _pfmt_i(io, fs, ix, _Oct()) : - _pfmt_i(io, fs, ix, _Bin()) elseif cls == 'f' - fx = x try fx = float(x) + if isfinite(fx) + ty == 'f' || ty == 'F' ? _pfmt_f(io, fs, fx) : + ty == 'e' || ty == 'E' ? _pfmt_e(io, fs, fx) : + error("format for type g or G is not supported yet (use f or e instead).") + else + _pfmt_specialf(io, fs, fx) + end catch - end - if isfinite(fx) - ty == 'f' || ty == 'F' ? _pfmt_f(io, fs, fx) : - ty == 'e' || ty == 'E' ? _pfmt_e(io, fs, fx) : + ty == 'f' || ty == 'F' ? _pfmt_f(io, fs, x) : + ty == 'e' || ty == 'E' ? _pfmt_e(io, fs, x) : error("format for type g or G is not supported yet (use f or e instead).") - else - _pfmt_specialf(io, fs, fx) end elseif cls == 's' _pfmt_s(io, fs, _srepr(x)) From 4479a8a88865287e7ce518eb539cb83b04897a4f Mon Sep 17 00:00:00 2001 From: Helmut Haensel Date: Wed, 18 Dec 2019 10:39:26 +0100 Subject: [PATCH 08/18] new try catch in printfmt --- src/fmtspec.jl | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/fmtspec.jl b/src/fmtspec.jl index c55fe8f..5f08a89 100644 --- a/src/fmtspec.jl +++ b/src/fmtspec.jl @@ -172,34 +172,35 @@ function printfmt(io::IO, fs::FormatSpec, x) cls = fs.cls ty = fs.typ if cls == 'i' + local ix try ix = Integer(x) - ty == 'd' || ty == 'n' ? _pfmt_i(io, fs, ix, _Dec()) : - ty == 'x' ? _pfmt_i(io, fs, ix, _Hex()) : - ty == 'X' ? _pfmt_i(io, fs, ix, _HEX()) : - ty == 'o' ? _pfmt_i(io, fs, ix, _Oct()) : - _pfmt_i(io, fs, ix, _Bin()) catch - ty == 'd' || ty == 'n' ? _pfmt_i(io, fs, x, _Dec()) : - ty == 'x' ? _pfmt_i(io, fs, x, _Hex()) : - ty == 'X' ? _pfmt_i(io, fs, x, _HEX()) : - ty == 'o' ? _pfmt_i(io, fs, x, _Oct()) : - _pfmt_i(io, fs, x, _Bin()) + ix = x end + ty == 'd' || ty == 'n' ? _pfmt_i(io, fs, ix, _Dec()) : + ty == 'x' ? _pfmt_i(io, fs, ix, _Hex()) : + ty == 'X' ? _pfmt_i(io, fs, ix, _HEX()) : + ty == 'o' ? _pfmt_i(io, fs, ix, _Oct()) : + _pfmt_i(io, fs, ix, _Bin()) elseif cls == 'f' + local fx, nospecialf try fx = float(x) - if isfinite(fx) - ty == 'f' || ty == 'F' ? _pfmt_f(io, fs, fx) : - ty == 'e' || ty == 'E' ? _pfmt_e(io, fs, fx) : - error("format for type g or G is not supported yet (use f or e instead).") - else - _pfmt_specialf(io, fs, fx) - end catch - ty == 'f' || ty == 'F' ? _pfmt_f(io, fs, x) : - ty == 'e' || ty == 'E' ? _pfmt_e(io, fs, x) : + fx = x + end + try + nospecialf = isfinite(fx) + catch + nospecialf = true + end + if nospecialf + ty == 'f' || ty == 'F' ? _pfmt_f(io, fs, fx) : + ty == 'e' || ty == 'E' ? _pfmt_e(io, fs, fx) : error("format for type g or G is not supported yet (use f or e instead).") + else + _pfmt_specialf(io, fs, fx) end elseif cls == 's' _pfmt_s(io, fs, _srepr(x)) From d07f241d15f14fea9190dccccb2124a83a479824 Mon Sep 17 00:00:00 2001 From: Helmut Haensel Date: Wed, 18 Dec 2019 12:27:01 +0100 Subject: [PATCH 09/18] adapt to master updates --- test/fmt.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/fmt.jl b/test/fmt.jl index 9fed6b4..41cc008 100644 --- a/test/fmt.jl +++ b/test/fmt.jl @@ -20,6 +20,9 @@ i = 1234567 @test fmt(i) == "1234567" @test fmt(i,:commas) == "1,234,567" +@test_throws ErrorException fmt_default(Real) +@test_throws ErrorException fmt_default(Complex) + @test fmt(2 - 3im, 10) == " 2 - 3im" @test fmt(pi - 3im, 15, 2) == " 3.14 - 3.00im" From 314a4fbd920ea655b1343013dae924d4b6666334 Mon Sep 17 00:00:00 2001 From: Helmut Haensel Date: Wed, 18 Dec 2019 12:32:09 +0100 Subject: [PATCH 10/18] change version to 1.2.0 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 3b658ed..4a7a436 100644 --- a/Project.toml +++ b/Project.toml @@ -23,7 +23,7 @@ keywords = ["Strings", "Formatting"] license = "MIT" name = "Format" uuid = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8" -version = "1.1.0" +version = "1.2.0" [deps] Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" From 38e386a024243a232fba4bcfc96c87b3ae9d230c Mon Sep 17 00:00:00 2001 From: Helmut Haensel Date: Thu, 19 Dec 2019 14:32:24 +0100 Subject: [PATCH 11/18] apply changes to `format()` --- src/cformat.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cformat.jl b/src/cformat.jl index 3b04b2a..9cbef51 100644 --- a/src/cformat.jl +++ b/src/cformat.jl @@ -117,6 +117,11 @@ function generate_format_string(; String(append!(s, _codeunits(conversion))) end +function format(x::T; kwargs...) where T<:Number + s = fmt_Number(x, x->format(AbstractFloat(x); kwargs..., width=-1)) + fmt(s, get(kwargs, :width, 0), get(kwargs, :leftjustified, false) ? :left : :right) +end + function format( x::T; width::Int=-1, precision::Int= -1, From 82b9b43c9b2295255e3a0f6e7d34085531bd184e Mon Sep 17 00:00:00 2001 From: ScottPJones Date: Tue, 17 Dec 2019 12:39:46 -0500 Subject: [PATCH 12/18] Update tests --- test/fmt.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/fmt.jl b/test/fmt.jl index 41cc008..35723ff 100644 --- a/test/fmt.jl +++ b/test/fmt.jl @@ -20,8 +20,9 @@ i = 1234567 @test fmt(i) == "1234567" @test fmt(i,:commas) == "1,234,567" -@test_throws ErrorException fmt_default(Real) -@test_throws ErrorException fmt_default(Complex) +# These are not handled +#@test_throws ErrorException fmt_default(Real) +#@test_throws ErrorException fmt_default(Complex) @test fmt(2 - 3im, 10) == " 2 - 3im" @test fmt(pi - 3im, 15, 2) == " 3.14 - 3.00im" From befc01c50e10fa1cfabf71f248839ebcdaeeaaf9 Mon Sep 17 00:00:00 2001 From: Helmut Haensel Date: Fri, 20 Dec 2019 02:00:06 +0100 Subject: [PATCH 13/18] apply changes to `cfmt()` and add tests --- src/cformat.jl | 12 +++++++++++- test/fmt.jl | 3 ++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/cformat.jl b/src/cformat.jl index 9cbef51..06b61f9 100644 --- a/src/cformat.jl +++ b/src/cformat.jl @@ -1,6 +1,16 @@ formatters = Dict{ ASCIIStr, Function }() -cfmt( fmt::ASCIIStr, x ) = m_eval(Expr(:call, generate_formatter( fmt ), x)) +cfmt( fmt::ASCIIStr, x::Union{<:AbstractString,<:Real} ) = m_eval(Expr(:call, generate_formatter( fmt ), x)) + +function cfmt( fmt_str::ASCIIStr, x::Number ) + #remove width information + new_fmt_str = replace(fmt_str, r"(%(\d+\$)?[\-\+#0' ]*)(\d+)?"=>s"\1") + s = fmt_Number(x, x->m_eval(Expr(:call, generate_formatter( new_fmt_str ), AbstractFloat(x)))) + # extract width information + m = match(r"%(\d+\$)?[\-\+#0' ]*(\d+)?", fmt_str) + width = m[2] == nothing ? 0 : parse(Int, m[2]) + fmt(s, width, occursin("-", fmt_str) ? :left : :right) +end function checkfmt(fmt) test = PF.parse( fmt ) diff --git a/test/fmt.jl b/test/fmt.jl index 35723ff..8686b58 100644 --- a/test/fmt.jl +++ b/test/fmt.jl @@ -20,9 +20,10 @@ i = 1234567 @test fmt(i) == "1234567" @test fmt(i,:commas) == "1,234,567" -# These are not handled +# All Number types are now handled, default is right aligned string #@test_throws ErrorException fmt_default(Real) #@test_throws ErrorException fmt_default(Complex) +fmt_default(Real) == fmt_default(Number) == FormatSpec('s', align = '>') @test fmt(2 - 3im, 10) == " 2 - 3im" @test fmt(pi - 3im, 15, 2) == " 3.14 - 3.00im" From ab0bdacce00db5ad1205cadf0bd654caed53b083 Mon Sep 17 00:00:00 2001 From: Helmut Haensel Date: Fri, 20 Dec 2019 02:15:42 +0100 Subject: [PATCH 14/18] resolve merge conflict --- test/fmt.jl | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/test/fmt.jl b/test/fmt.jl index 8686b58..80f46a0 100644 --- a/test/fmt.jl +++ b/test/fmt.jl @@ -20,24 +20,9 @@ i = 1234567 @test fmt(i) == "1234567" @test fmt(i,:commas) == "1,234,567" -# All Number types are now handled, default is right aligned string +# These are not handled #@test_throws ErrorException fmt_default(Real) #@test_throws ErrorException fmt_default(Complex) -fmt_default(Real) == fmt_default(Number) == FormatSpec('s', align = '>') - -@test fmt(2 - 3im, 10) == " 2 - 3im" -@test fmt(pi - 3im, 15, 2) == " 3.14 - 3.00im" - -@test fmt(3//4, 10) == " 3//4" -@test fmt(1//2 + 6//2 * im, 15) == " 1//2 + 3//1*im" - -fmt_default!(Rational, 'f', prec = 2) -fmt_default!(Format.ComplexRational, 'f', prec = 2) - -@test fmt(3//4, 10, 2) == " 0.75" -@test fmt(3//4, 10, 1) == " 0.8" -@test fmt(1//2 + 6//2 * im, 23) == " 0.500000 + 3.000000im" -@test fmt(1//2 + 6//2 * im, 15, 2) == " 0.50 + 3.00im" fmt_default!(Int, :commas, width = 12) @test fmt(i) == " 1,234,567" @@ -63,3 +48,19 @@ v = pi v = MathConstants.eulergamma @test fmt(v, 10, 2) == " γ" + +fmt_default(Real) == fmt_default(Number) == FormatSpec('s', align = '>') + +@test fmt(2 - 3im, 10) == " 2 - 3im" +@test fmt(pi - 3im, 15, 2) == " 3.14 - 3.00im" + +@test fmt(3//4, 10) == " 3//4" +@test fmt(1//2 + 6//2 * im, 15) == " 1//2 + 3//1*im" + +fmt_default!(Rational, 'f', prec = 2) +fmt_default!(Format.ComplexRational, 'f', prec = 2) + +@test fmt(3//4, 10, 2) == " 0.75" +@test fmt(3//4, 10, 1) == " 0.8" +@test fmt(1//2 + 6//2 * im, 23) == " 0.500000 + 3.000000im" +@test fmt(1//2 + 6//2 * im, 15, 2) == " 0.50 + 3.00im" From 75985563eaeadf1b55041e4928eca30d37acae2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helmut=20H=C3=A4nsel?= Date: Fri, 20 Dec 2019 12:42:51 +0100 Subject: [PATCH 15/18] separator handling for general Number types --- src/fmtcore.jl | 4 ++-- test/fmt.jl | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/fmtcore.jl b/src/fmtcore.jl index 75d52f9..5d79f7d 100644 --- a/src/fmtcore.jl +++ b/src/fmtcore.jl @@ -272,7 +272,7 @@ function _pfmt_Number_f(out::IO, fs::FormatSpec, x::Number, _pf::Function) String(take!(io)) end s = fmt_Number(x, f) - _pfmt_s(out, fs, s) + _pfmt_s(out, FormatSpec(fs, tsep = false), s) end function _pfmt_Number_i(out::IO, fs::FormatSpec, x::Number, op::Op, _pf::Function) where {Op} @@ -284,7 +284,7 @@ function _pfmt_Number_i(out::IO, fs::FormatSpec, x::Number, op::Op, _pf::Functio String(take!(io)) end s = fmt_Number(x, f) - _pfmt_s(out, fs, s) + _pfmt_s(out, FormatSpec(fs, tsep = false), s) end function _pfmt_i(out::IO, fs::FormatSpec, x::Number, op::Op) where {Op} diff --git a/test/fmt.jl b/test/fmt.jl index 80f46a0..d2450a5 100644 --- a/test/fmt.jl +++ b/test/fmt.jl @@ -49,11 +49,14 @@ v = pi v = MathConstants.eulergamma @test fmt(v, 10, 2) == " γ" -fmt_default(Real) == fmt_default(Number) == FormatSpec('s', align = '>') +reset!(Number) +reset!(Real) +@test fmt_default(Real) == fmt_default(Number) == FormatSpec('s', align = '>') @test fmt(2 - 3im, 10) == " 2 - 3im" @test fmt(pi - 3im, 15, 2) == " 3.14 - 3.00im" +reset!(Rational) @test fmt(3//4, 10) == " 3//4" @test fmt(1//2 + 6//2 * im, 15) == " 1//2 + 3//1*im" From 0233e95f7239df3016e8166a2729dcb17757068d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helmut=20H=C3=A4nsel?= Date: Fri, 20 Dec 2019 18:41:38 +0100 Subject: [PATCH 16/18] add cformat tests for complex numbers --- src/cformat.jl | 2 +- test/cformat.jl | 16 ++++++++++++++++ test/fmtspec.jl | 31 ++++++++++++++++++++++++++----- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/cformat.jl b/src/cformat.jl index 06b61f9..f8a178d 100644 --- a/src/cformat.jl +++ b/src/cformat.jl @@ -1,6 +1,6 @@ formatters = Dict{ ASCIIStr, Function }() -cfmt( fmt::ASCIIStr, x::Union{<:AbstractString,<:Real} ) = m_eval(Expr(:call, generate_formatter( fmt ), x)) +cfmt( fmt::ASCIIStr, x::Union{<:AbstractString,<:Real,<:Rational} ) = m_eval(Expr(:call, generate_formatter( fmt ), x)) function cfmt( fmt_str::ASCIIStr, x::Number ) #remove width information diff --git a/test/cformat.jl b/test/cformat.jl index e1d7f98..1ce0bb6 100644 --- a/test/cformat.jl +++ b/test/cformat.jl @@ -187,3 +187,19 @@ end @test format( 100, precision=2, suffix="%", conversion="f" ) == "100.00%" end +@testset "complex numbers" begin + c = 2 - 3.1im + @test cfmt("%20.0f", c) == " 2 - 3im" + @test cfmt("%20.1f", c) == " 2.0 - 3.1im" + @test cfmt("%20.2f", c) == " 2.00 - 3.10im" + @test cfmt("%20s", c) == " 2.0 - 3.1im" + @test cfmt("%20.1e", c) == " 2.0e+00 - 3.1e+00im" + @test cfmt("%-20.1e", c) == "2.0e+00 - 3.1e+00im " + + @test format(c, width=20) == " 2 - 3.1im" + @test format(c, width=20, precision=0) == " 2 - 3im" + @test format(c, width=20, precision=1) == " 2.0 - 3.1im" + @test format(c, width=20, precision=2) == " 2.00 - 3.10im" + @test format(c, width=20, precision=2, leftjustified=true) == "2.00 - 3.10im " + @test format(c, width=20, precision=1, conversion="e") == " 2.0e+00 - 3.1e+00im" +end diff --git a/test/fmtspec.jl b/test/fmtspec.jl index 6789dba..4bc4e54 100644 --- a/test/fmtspec.jl +++ b/test/fmtspec.jl @@ -235,7 +235,7 @@ end @test pyfmt("⋆>5f", Inf) == "⋆⋆Inf" end -@testset "Format Symbols (S) for Irrationals" begin +@testset "Format Irrationals" begin @test pyfmt(">10s", pi) == " π" @test pyfmt("10s", pi) == "π " @test pyfmt("3", MathConstants.eulergamma) == "γ " @@ -243,8 +243,29 @@ end @test pyfmt("<3s", MathConstants.e) == "ℯ " end -@testset "Format Symbols (S) for Irrationals" begin - pyfmt("10s", 3//4) == "3//4 " - pyfmt("10", 3//4) == " 3//4" - pyfmt("10.1f", 3//4) == " 0.8" +@testset "Format Rationals" begin + @test pyfmt("10s", 3//4) == "3//4 " + @test pyfmt("10", 3//4) == "3//4 " + @test pyfmt(">10", 3//4) == " 3//4" + @test pyfmt("10.1f", 3//4) == " 0.8" + @test pyfmt("10.1f", 3//4) == " 0.8" + @test pyfmt("10.1e", 3//4) == " 7.5e-01" +end + +@testset "Format Complex Numbers" begin + c = 2 - 3.1im + @test fmt(round(c), 20, 1) == " 2.0 - 3.0im" + @test fmt(c, 20, 1) == " 2.0 - 3.1im" + @test fmt(c, 20, 2) == " 2.00 - 3.10im" + @test fmt(c, 20) == "2.000000 - 3.100000im" + fmt_default!(Format.ComplexFloat, 'e') + @test fmt(c, 20, 1) == " 2.0e+00 - 3.1e+00im" + + @test format(c, width=20) == " 2 - 3.1im" + @test format(c, width=20, precision=0) == " 2 - 3im" + @test format(c, width=20, precision=1) == " 2.0 - 3.1im" + @test format(c, width=20, precision=2) == " 2.00 - 3.10im" + @test format(c, width=20, precision=2, leftjustified=true) == "2.00 - 3.10im " + @test format(c, width=20, precision=1, conversion="e") == " 2.0e+00 - 3.1e+00im" + fmt_default!(Format.ComplexFloat, 'f') end From 9e1d5534e10491a477bbfbee057c08e4b17867f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helmut=20H=C3=A4nsel?= Date: Sat, 21 Dec 2019 00:45:31 +0100 Subject: [PATCH 17/18] correct complex types and add tests --- src/fmt.jl | 12 ++++++------ test/fmt.jl | 6 +++--- test/fmtspec.jl | 2 ++ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/fmt.jl b/src/fmt.jl index 4c6cecf..80071ab 100644 --- a/src/fmt.jl +++ b/src/fmt.jl @@ -74,9 +74,9 @@ end # note: if you want to set a default for an abstract type (i.e. AbstractFloat) # you'll need to extend this method like here: -const ComplexInteger = Complex{<:Integer} -const ComplexFloat = Complex{<:AbstractFloat} -const ComplexRational = Complex{<:Rational} +const ComplexInteger = Complex{T} where T<:Integer +const ComplexFloat = Complex{T} where T<:AbstractFloat +const ComplexRational = Complex{T} where T<:Rational default_spec(::Type{<:Integer}) = DEFAULT_FORMATTERS[Integer] default_spec(::Type{<:AbstractFloat}) = DEFAULT_FORMATTERS[AbstractFloat] @@ -85,9 +85,9 @@ default_spec(::Type{<:AbstractChar}) = DEFAULT_FORMATTERS[AbstractChar] default_spec(::Type{<:AbstractIrrational}) = DEFAULT_FORMATTERS[AbstractIrrational] default_spec(::Type{<:Rational}) = DEFAULT_FORMATTERS[Rational] default_spec(::Type{<:Number}) = DEFAULT_FORMATTERS[Number] -default_spec(::ComplexInteger) = DEFAULT_FORMATTERS[ComplexInteger] -default_spec(::ComplexFloat) = DEFAULT_FORMATTERS[ComplexFloat] -default_spec(::ComplexRational) = DEFAULT_FORMATTERS[ComplexRational] +default_spec(::Type{<:ComplexInteger}) = DEFAULT_FORMATTERS[ComplexInteger] +default_spec(::Type{<:ComplexFloat}) = DEFAULT_FORMATTERS[ComplexFloat] +default_spec(::Type{<:ComplexRational}) = DEFAULT_FORMATTERS[ComplexRational] default_spec(::Type{T}) where {T} = get(DEFAULT_FORMATTERS, T) do diff --git a/test/fmt.jl b/test/fmt.jl index d2450a5..363950a 100644 --- a/test/fmt.jl +++ b/test/fmt.jl @@ -63,7 +63,7 @@ reset!(Rational) fmt_default!(Rational, 'f', prec = 2) fmt_default!(Format.ComplexRational, 'f', prec = 2) -@test fmt(3//4, 10, 2) == " 0.75" +@test fmt(3//4, 10) == " 0.75" @test fmt(3//4, 10, 1) == " 0.8" -@test fmt(1//2 + 6//2 * im, 23) == " 0.500000 + 3.000000im" -@test fmt(1//2 + 6//2 * im, 15, 2) == " 0.50 + 3.00im" +@test fmt(1//2 + 6//2 * im, 15) == " 0.50 + 3.00im" +@test fmt(1//2 + 6//2 * im, 15, 1) == " 0.5 + 3.0im" diff --git a/test/fmtspec.jl b/test/fmtspec.jl index 4bc4e54..6401b24 100644 --- a/test/fmtspec.jl +++ b/test/fmtspec.jl @@ -260,6 +260,8 @@ end @test fmt(c, 20) == "2.000000 - 3.100000im" fmt_default!(Format.ComplexFloat, 'e') @test fmt(c, 20, 1) == " 2.0e+00 - 3.1e+00im" + fmt_default!(Format.ComplexFloat, 'e', :left) + @test fmt(c, 20, 1) == "2.0e+00 - 3.1e+00im " @test format(c, width=20) == " 2 - 3.1im" @test format(c, width=20, precision=0) == " 2 - 3im" From 0eb2a8cd482cc0d8c6c098294a58ccf71e24b066 Mon Sep 17 00:00:00 2001 From: Helmut Haensel Date: Tue, 21 Jan 2020 16:15:25 +0100 Subject: [PATCH 18/18] include `Char` in the types of `cfmt` in order to match calls like `cfmt("%c",'A')` --- src/cformat.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cformat.jl b/src/cformat.jl index f8a178d..2a939d0 100644 --- a/src/cformat.jl +++ b/src/cformat.jl @@ -1,6 +1,6 @@ formatters = Dict{ ASCIIStr, Function }() -cfmt( fmt::ASCIIStr, x::Union{<:AbstractString,<:Real,<:Rational} ) = m_eval(Expr(:call, generate_formatter( fmt ), x)) +cfmt( fmt::ASCIIStr, x::Union{<:AbstractString,<:Real,<:Rational,<:Char} ) = m_eval(Expr(:call, generate_formatter( fmt ), x)) function cfmt( fmt_str::ASCIIStr, x::Number ) #remove width information