diff --git a/src/fmt.jl b/src/fmt.jl index 61df286..c0c6495 100644 --- a/src/fmt.jl +++ b/src/fmt.jl @@ -18,7 +18,7 @@ mutable struct DefaultSpec DefaultSpec(c::AbstractChar) = new(Char(c), FormatSpec(c)) end -const DEFAULT_FORMATTERS = Dict{DataType, DefaultSpec}() +const DEFAULT_FORMATTERS = Dict{Union{DataType, UnionAll}, DefaultSpec}() # adds a new default formatter for this type default_spec!(::Type{T}, c::AbstractChar) where {T} = @@ -29,7 +29,12 @@ default_spec!(::Type{T}, ::Type{K}) where {T,K} = (DEFAULT_FORMATTERS[T] = DEFAULT_FORMATTERS[K]; nothing) # seed it with some basic default formatters -for (t, c) in [(Integer,'d'), (AbstractFloat,'f'), (AbstractChar,'c'), (AbstractString,'s')] +ComplexInteger = Complex{T} where T<:Integer +ComplexFloat = Complex{T} where T<:AbstractFloat +ComplexRational = Complex{T} where T<:Rational +for (t, c) in [(Integer,'d'), (AbstractFloat,'f'), (AbstractChar,'c'), (AbstractString,'s'), + (ComplexInteger,'d'), (ComplexFloat,'f'), (Number,'S'), (AbstractIrrational,'S'), + (Rational,'S'), (ComplexRational,'S')] default_spec!(t, c) end @@ -62,10 +67,16 @@ 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: -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{<: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(::ComplexInteger) = DEFAULT_FORMATTERS[ComplexInteger] +default_spec(::ComplexFloat) = DEFAULT_FORMATTERS[ComplexFloat] +default_spec(::Rational) = DEFAULT_FORMATTERS[Rational] +default_spec(::ComplexRational) = DEFAULT_FORMATTERS[ComplexRational] default_spec(::Type{T}) where {T} = get(DEFAULT_FORMATTERS, T) do @@ -176,7 +187,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) @@ -200,3 +211,6 @@ function fmt(x, syms::Symbol...; kwargs...) d = _add_kwargs_from_symbols(kwargs, syms...) fmt(x; d...) end + +#fmt_default!(AbstractIrrational, 's', :right) +#fmt_default!(Number, '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..2e006d3 100644 --- a/src/fmtspec.jl +++ b/src/fmtspec.jl @@ -9,7 +9,7 @@ # width ::= # prec ::= # type ::= 'b' | 'c' | 'd' | 'e' | 'E' | 'f' | 'F' | 'g' | 'G' | -# 'n' | 'o' | 'x' | 'X' | 's' +# 'n' | 'o' | 'x' | 'X' | 's' | 'S' # # Please refer to http://docs.python.org/2/library/string.html#formatspec # for more details @@ -17,17 +17,18 @@ ## FormatSpec type -const _numtypchars = Set(['b', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 'x', 'X']) +const _numtypchars = Set(['b', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 'x', 'X', 'S']) _tycls(c::AbstractChar) = (c == 'd' || c == 'n' || c == 'b' || c == 'o' || c == 'x') ? 'i' : (c == 'e' || c == 'f' || c == 'g') ? 'f' : (c == 'c') ? 'c' : (c == 's') ? 's' : + (c == 'S') ? 'S' : error("Invalid type char $(c)") struct FormatSpec - cls::Char # category: 'i' | 'f' | 'c' | 's' + cls::Char # category: 'i' | 'f' | 'c' | 's' | 'S' typ::Char fill::Char align::Char @@ -84,7 +85,7 @@ end ## parse FormatSpec from a string -const _spec_regex = r"^(.?[<>])?([ +-])?(#)?(\d+)?(,)?(.\d+)?([bcdeEfFgGnosxX])?$" +const _spec_regex = r"^(.?[<>])?([ +-])?(#)?(\d+)?(,)?(.\d+)?([bcdeEfFgGnosxXS])?$" function FormatSpec(s::AbstractString) # default spec @@ -164,19 +165,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) : @@ -184,7 +196,7 @@ function printfmt(io::IO, fs::FormatSpec, x) else _pfmt_specialf(io, fs, fx) end - elseif cls == 's' + elseif cls == 's' || cls == 'S' _pfmt_s(io, fs, _srepr(x)) else # cls == 'c' _pfmt_s(io, fs, Char(x)) 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..b0c01da 100644 --- a/test/fmtspec.jl +++ b/test/fmtspec.jl @@ -234,3 +234,16 @@ 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("3S", 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("10S", 3//4) == " 3//4" + pyfmt("10.1f", 3//4) == " 0.8" +end