Skip to content

Commit f4c9b77

Browse files
authored
Merge pull request #65 from JuliaString/spj/fixtypemin
Add support for Python ^ (center justification), fix issue #63, and Formatting.jl#84
2 parents 5f0fb70 + d116e94 commit f4c9b77

File tree

6 files changed

+108
-49
lines changed

6 files changed

+108
-49
lines changed

src/Format.jl

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,6 @@ One can use ``pyfmt`` to format a single value into a string, or ``format`` to f
9191
9292
At this point, this package implements a subset of Python's formatting language (with slight modification). Here is a summary of the differences:
9393
94-
- ``g`` and ``G`` for floating point formatting have not been supported yet. Please use ``f``, ``e``, or ``E`` instead.
95-
96-
- The package currently provides default alignment, left alignment ``<`` and right alignment ``>``. Other form of alignment such as centered alignment ``^`` has not been supported yet.
97-
9894
- In terms of argument specification, it supports natural ordering (e.g. ``{} + {}``), explicit position (e.g. ``{1} + {2}``). It hasn't supported named arguments or fields extraction yet. Note that mixing these two modes is not allowed (e.g. ``{1} + {}``).
9995
10096
- The package provides support for filtering (for explicitly positioned arguments), such as ``{1|>lowercase}`` by allowing one to embed the ``|>`` operator, which the Python counter part does not support.

src/fmt.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ function _add_kwargs_from_symbols(kwargs, syms::Symbol...)
5757
d[:align] = '<'
5858
elseif s == :rjust || s == :right
5959
d[:align] = '>'
60+
elseif s == :center
61+
d[:align] = '^'
6062
elseif s == :commas
6163
d[:tsep] = true
6264
elseif s == :zpad || s == :zeropad
@@ -146,6 +148,7 @@ function _optional_commas(x::Real, s::AbstractString, fspec::FormatSpec)
146148
if fspec.width > 0 && w > fspec.width && w > prevwidth
147149
# we may have made the string too wide with those commas... gotta fix it
148150
# left or right alignment ('<' is left)
151+
# TODO: handle center alignment
149152
s = fspec.align == '<' ? rpad(strip(s), fspec.width) : lpad(strip(s), fspec.width)
150153
end
151154
s
@@ -165,6 +168,7 @@ Symbol | Meaning
165168
------------------|------------------------------------------
166169
:ljust or :left | Left justified, same as < for FormatSpec
167170
:rjust or :right | Right justified, same as > for FormatSpec
171+
:center | Center justified, same as ^ for FormatSpec
168172
:zpad or :zeropad | Pad with 0s on left
169173
:ipre or :prefix | Whether to prefix 0b, 0o, or 0x
170174
:commas | Add commas every 3 digits
@@ -195,6 +199,7 @@ function fmt(x; kwargs...)
195199
fspec = fmt_default(x)
196200
isempty(kwargs) || (fspec = FormatSpec(fspec; kwargs...))
197201
s = pyfmt(fspec, x)
202+
# TODO: allow other thousands separators besides comma
198203
# add the commas now... I was confused as to when this is done currently
199204
fspec.tsep ? _optional_commas(x, s, fspec) : s
200205
end

src/fmtcore.jl

Lines changed: 52 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,18 @@ end
1515
### print string or char
1616

1717
function _pfmt_s(out::IO, fs::FormatSpec, s::Union{AbstractString,AbstractChar})
18-
wid = fs.width
19-
slen = length(s)
20-
if wid <= slen
18+
pad = fs.width - length(s)
19+
if pad <= 0
2120
print(out, s)
2221
elseif fs.align == '<'
2322
print(out, s)
24-
_repprint(out, fs.fill, wid-slen)
23+
_repprint(out, fs.fill, pad)
24+
elseif fs.align == '^'
25+
_repprint(out, fs.fill, pad>>1)
26+
print(out, s)
27+
_repprint(out, fs.fill, (pad+1)>>1)
2528
else
26-
_repprint(out, fs.fill, wid-slen)
29+
_repprint(out, fs.fill, pad)
2730
print(out, s)
2831
end
2932
end
@@ -122,16 +125,20 @@ function _pfmt_imin(out::IO, fs::FormatSpec, x::Integer, op::Op) where {Op}
122125
end
123126

124127
# printing
125-
wid = fs.width
126-
if wid <= xlen
128+
pad = fs.width - xlen
129+
if pad <= 0
127130
_pfmt_intmin(out, ip, 0, s)
128131
elseif fs.zpad
129-
_pfmt_intmin(out, ip, wid-xlen, s)
132+
_pfmt_intmin(out, ip, pad, s)
130133
elseif fs.align == '<'
131134
_pfmt_intmin(out, ip, 0, s)
132-
_repprint(out, fs.fill, wid-xlen)
135+
_repprint(out, fs.fill, pad)
136+
elseif fs.align == '^'
137+
_repprint(out, fs.fill, pad>>1)
138+
_pfmt_intmin(out, ip, 0, s)
139+
_repprint(out, fs.fill, (pad+1)>>1)
133140
else
134-
_repprint(out, fs.fill, wid-xlen)
141+
_repprint(out, fs.fill, pad)
135142
_pfmt_intmin(out, ip, 0, s)
136143
end
137144
end
@@ -153,16 +160,20 @@ function _pfmt_i(out::IO, fs::FormatSpec, x::Integer, op::Op) where {Op}
153160
end
154161

155162
# printing
156-
wid = fs.width
157-
if wid <= xlen
163+
pad = fs.width - xlen
164+
if pad <= 0
158165
_pfmt_int(out, sch, ip, 0, ax, op)
159166
elseif fs.zpad
160-
_pfmt_int(out, sch, ip, wid-xlen, ax, op)
167+
_pfmt_int(out, sch, ip, pad, ax, op)
161168
elseif fs.align == '<'
162169
_pfmt_int(out, sch, ip, 0, ax, op)
163-
_repprint(out, fs.fill, wid-xlen)
170+
_repprint(out, fs.fill, pad)
171+
elseif fs.align == '^'
172+
_repprint(out, fs.fill, pad>>1)
173+
_pfmt_int(out, sch, ip, 0, ax, op)
174+
_repprint(out, fs.fill, (pad+1)>>1)
164175
else
165-
_repprint(out, fs.fill, wid-xlen)
176+
_repprint(out, fs.fill, pad)
166177
_pfmt_int(out, sch, ip, 0, ax, op)
167178
end
168179
end
@@ -205,20 +216,21 @@ function _pfmt_f(out::IO, fs::FormatSpec, x::AbstractFloat)
205216
sch != '\0' && (xlen += 1)
206217

207218
# print
208-
wid = fs.width
209-
if wid <= xlen
219+
pad = fs.width - xlen
220+
if pad <= 0
210221
_pfmt_float(out, sch, 0, intv, decv, fs.prec)
211222
elseif fs.zpad
212-
_pfmt_float(out, sch, wid-xlen, intv, decv, fs.prec)
223+
_pfmt_float(out, sch, pad, intv, decv, fs.prec)
224+
elseif fs.align == '<'
225+
_pfmt_float(out, sch, 0, intv, decv, fs.prec)
226+
_repprint(out, fs.fill, pad)
227+
elseif fs.align == '^'
228+
_repprint(out, fs.fill, pad>>1)
229+
_pfmt_float(out, sch, 0, intv, decv, fs.prec)
230+
_repprint(out, fs.fill, (pad+1)>>1)
213231
else
214-
a = fs.align
215-
if a == '<'
216-
_pfmt_float(out, sch, 0, intv, decv, fs.prec)
217-
_repprint(out, fs.fill, wid-xlen)
218-
else
219-
_repprint(out, fs.fill, wid-xlen)
220-
_pfmt_float(out, sch, 0, intv, decv, fs.prec)
221-
end
232+
_repprint(out, fs.fill, pad)
233+
_pfmt_float(out, sch, 0, intv, decv, fs.prec)
222234
end
223235
end
224236

@@ -252,14 +264,14 @@ function _pfmt_e(out::IO, fs::FormatSpec, x::AbstractFloat)
252264
else
253265
rax = round(ax; sigdigits = fs.prec + 1)
254266
e = floor(Integer, log10(rax)) # exponent
255-
u = rax * exp10(-e) # significand
267+
u = round(rax * exp10(-e); sigdigits = fs.prec + 1) # significand
256268
i = 0
257269
v10 = 1
258270
while isinf(u)
259271
i += 1
260272
i > 18 && (u = 0.0; e = 0; break)
261273
v10 *= 10
262-
u = v10 * rax * exp(-e - i)
274+
u = round(v10 * rax * exp10(-e - i); sigdigits = fs.prec + 1)
263275
end
264276
end
265277

@@ -270,20 +282,21 @@ function _pfmt_e(out::IO, fs::FormatSpec, x::AbstractFloat)
270282

271283
# print
272284
ec = isuppercase(fs.typ) ? 'E' : 'e'
273-
wid = fs.width
274-
if wid <= xlen
285+
pad = fs.width - xlen
286+
if pad <= 0
275287
_pfmt_floate(out, sch, 0, u, fs.prec, e, ec)
276288
elseif fs.zpad
277-
_pfmt_floate(out, sch, wid-xlen, u, fs.prec, e, ec)
289+
_pfmt_floate(out, sch, pad, u, fs.prec, e, ec)
290+
elseif fs.align == '<'
291+
_pfmt_floate(out, sch, 0, u, fs.prec, e, ec)
292+
_repprint(out, fs.fill, pad)
293+
elseif fs.align == '^'
294+
_repprint(out, fs.fill, pad>>1)
295+
_pfmt_floate(out, sch, 0, u, fs.prec, e, ec)
296+
_repprint(out, fs.fill, (pad+1)>>1)
278297
else
279-
a = fs.align
280-
if a == '<'
281-
_pfmt_floate(out, sch, 0, u, fs.prec, e, ec)
282-
_repprint(out, fs.fill, wid-xlen)
283-
else
284-
_repprint(out, fs.fill, wid-xlen)
285-
_pfmt_floate(out, sch, 0, u, fs.prec, e, ec)
286-
end
298+
_repprint(out, fs.fill, pad)
299+
_pfmt_floate(out, sch, 0, u, fs.prec, e, ec)
287300
end
288301
end
289302

@@ -305,4 +318,3 @@ function _pfmt_specialf(out::IO, fs::FormatSpec, x::AbstractFloat)
305318
_pfmt_s(out, fs, "NaN")
306319
end
307320
end
308-

src/fmtspec.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#
55
# spec ::= [[fill]align][sign][#][0][width][,][.prec][type]
66
# fill ::= <any character>
7-
# align ::= '<' | '>'
7+
# align ::= '<' | '^' | '>'
88
# sign ::= '+' | '-' | ' '
99
# width ::= <integer>
1010
# prec ::= <integer>
@@ -84,7 +84,7 @@ end
8484

8585
## parse FormatSpec from a string
8686

87-
const _spec_regex = r"^(.?[<>])?([ +-])?(#)?(\d+)?(,)?(.\d+)?([bcdeEfFgGnosxX])?$"
87+
const _spec_regex = r"^(.?[<^>])?([ +-])?(#)?(\d+)?(,)?(.\d+)?([bcdeEfFgGnosxX])?$"
8888

8989
function FormatSpec(s::AbstractString)
9090
# default spec
@@ -180,8 +180,7 @@ function printfmt(io::IO, fs::FormatSpec, x)
180180
fx = float(x)
181181
if isfinite(fx)
182182
ty == 'f' || ty == 'F' ? _pfmt_f(io, fs, fx) :
183-
ty == 'e' || ty == 'E' ? _pfmt_e(io, fs, fx) :
184-
error("format for type g or G is not supported yet (use f or e instead).")
183+
ty == 'e' || ty == 'E' ? _pfmt_e(io, fs, fx) : _pfmt_g(io, fs, fx)
185184
else
186185
_pfmt_specialf(io, fs, fx)
187186
end

test/fmt.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ x = 1234.56789
77
@test fmt(x,10,3,:left) == "1234.568 "
88
@test fmt(x,10,3,:ljust) == "1234.568 "
99
@test fmt(x,10,3,:right) == " 1234.568"
10-
@test fmt(x,10,3,:lrjust) == " 1234.568"
10+
@test fmt(x,10,3,:rjust) == " 1234.568"
11+
@test fmt(x,10,3,:center) == " 1234.568 "
1112
@test fmt(x,10,3,:zpad) == "001234.568"
1213
@test fmt(x,10,3,:zeropad) == "001234.568"
1314
@test fmt(x,:commas) == "1,234.567890"

test/fmtspec.jl

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ end
4646
@test FormatSpec("<8d") == FormatSpec('d'; width=8, align='<')
4747
@test FormatSpec("#<8d") == FormatSpec('d'; width=8, fill='#', align='<')
4848
@test FormatSpec("⋆<8d") == FormatSpec('d'; width=8, fill='', align='<')
49+
@test FormatSpec("#^8d") == FormatSpec('d'; width=8, fill='#', align='^')
4950
@test FormatSpec("#8,d") == FormatSpec('d'; width=8, ipre=true, tsep=true)
5051
end
5152

@@ -66,8 +67,12 @@ end
6667
@test pyfmt("5s", "αβγ") == "αβγ "
6768
@test pyfmt(">5s", "abc") == " abc"
6869
@test pyfmt(">5s", "αβγ") == " αβγ"
70+
@test pyfmt("^5s", "abc") == " abc "
71+
@test pyfmt("^5s", "αβγ") == " αβγ "
6972
@test pyfmt("*>5s", "abc") == "**abc"
7073
@test pyfmt("⋆>5s", "αβγ") == "⋆⋆αβγ"
74+
@test pyfmt("*^5s", "abc") == "*abc*"
75+
@test pyfmt("⋆^5s", "αβγ") == "⋆αβγ⋆"
7176
@test pyfmt("*<5s", "abc") == "abc**"
7277
@test pyfmt("⋆<5s", "αβγ") == "αβγ⋆⋆"
7378
end
@@ -86,8 +91,12 @@ end
8691
@test pyfmt("3c", 'γ') == "γ "
8792
@test pyfmt(">3c", 'c') == " c"
8893
@test pyfmt(">3c", 'γ') == " γ"
94+
@test pyfmt("^3c", 'c') == " c "
95+
@test pyfmt("^3c", 'γ') == " γ "
8996
@test pyfmt("*>3c", 'c') == "**c"
9097
@test pyfmt("⋆>3c", 'γ') == "⋆⋆γ"
98+
@test pyfmt("*^3c", 'c') == "*c*"
99+
@test pyfmt("⋆^3c", 'γ') == "⋆γ⋆"
91100
@test pyfmt("*<3c", 'c') == "c**"
92101
@test pyfmt("⋆<3c", 'γ') == "γ⋆⋆"
93102
end
@@ -116,12 +125,17 @@ end
116125
@test pyfmt(" 6d", 123) == " 123"
117126
@test pyfmt("<6d", 123) == "123 "
118127
@test pyfmt(">6d", 123) == " 123"
128+
@test pyfmt("^6d", 123) == " 123 "
119129
@test pyfmt("*<6d", 123) == "123***"
120130
@test pyfmt("⋆<6d", 123) == "123⋆⋆⋆"
131+
@test pyfmt("*^6d", 123) == "*123**"
132+
@test pyfmt("⋆^6d", 123) == "⋆123⋆⋆"
121133
@test pyfmt("*>6d", 123) == "***123"
122134
@test pyfmt("⋆>6d", 123) == "⋆⋆⋆123"
123135
@test pyfmt("< 6d", 123) == " 123 "
124136
@test pyfmt("<+6d", 123) == "+123 "
137+
@test pyfmt("^ 6d", 123) == " 123 "
138+
@test pyfmt("^+6d", 123) == " +123 "
125139
@test pyfmt("> 6d", 123) == " 123"
126140
@test pyfmt(">+6d", 123) == " +123"
127141

@@ -130,7 +144,14 @@ end
130144
@test pyfmt(" d", -123) == "-123"
131145
@test pyfmt("06d", -123) == "-00123"
132146
@test pyfmt("<6d", -123) == "-123 "
147+
@test pyfmt("^6d", -123) == " -123 "
133148
@test pyfmt(">6d", -123) == " -123"
149+
150+
# Issue #110 (in Formatting.jl)
151+
f = FormatExpr("{:+d}")
152+
for T in (Int8, Int16, Int32, Int64, Int128)
153+
@test format(f, typemin(T)) == string(typemin(T))
154+
end
134155
end
135156

136157
@testset "Format floating point (f)" begin
@@ -150,22 +171,32 @@ end
150171

151172
@test pyfmt("8.2f", 8.376) == " 8.38"
152173
@test pyfmt("<8.2f", 8.376) == "8.38 "
174+
@test pyfmt("^8.2f", 8.376) == " 8.38 "
153175
@test pyfmt(">8.2f", 8.376) == " 8.38"
154176
@test pyfmt("8.2f", -8.376) == " -8.38"
155177
@test pyfmt("<8.2f", -8.376) == "-8.38 "
178+
@test pyfmt("^8.2f", -8.376) == " -8.38 "
156179
@test pyfmt(">8.2f", -8.376) == " -8.38"
157180
@test pyfmt(".0f", 8.376) == "8"
158181

182+
# Note: these act differently than Python, but it looks like Python might be wrong
183+
# in at least some of these cases (zero padding should be *after* the sign, IMO)
159184
@test pyfmt("<08.2f", 8.376) == "00008.38"
185+
@test pyfmt("^08.2f", 8.376) == "00008.38"
160186
@test pyfmt(">08.2f", 8.376) == "00008.38"
161187
@test pyfmt("<08.2f", -8.376) == "-0008.38"
188+
@test pyfmt("^08.2f", -8.376) == "-0008.38"
162189
@test pyfmt(">08.2f", -8.376) == "-0008.38"
163190
@test pyfmt("*<8.2f", 8.376) == "8.38****"
164191
@test pyfmt("⋆<8.2f", 8.376) == "8.38⋆⋆⋆⋆"
192+
@test pyfmt("*^8.2f", 8.376) == "**8.38**"
193+
@test pyfmt("⋆^8.2f", 8.376) == "⋆⋆8.38⋆⋆"
165194
@test pyfmt("*>8.2f", 8.376) == "****8.38"
166195
@test pyfmt("⋆>8.2f", 8.376) == "⋆⋆⋆⋆8.38"
167196
@test pyfmt("*<8.2f", -8.376) == "-8.38***"
168197
@test pyfmt("⋆<8.2f", -8.376) == "-8.38⋆⋆⋆"
198+
@test pyfmt("*^8.2f", -8.376) == "*-8.38**"
199+
@test pyfmt("⋆^8.2f", -8.376) == "⋆-8.38⋆⋆"
169200
@test pyfmt("*>8.2f", -8.376) == "***-8.38"
170201
@test pyfmt("⋆>8.2f", -8.376) == "⋆⋆⋆-8.38"
171202

@@ -190,9 +221,12 @@ end
190221
@test pyfmt("8e", 1234.5678) == "1.234568e+03"
191222

192223
@test pyfmt("<12.2e", 13.89) == "1.39e+01 "
224+
@test pyfmt("^12.2e", 13.89) == " 1.39e+01 "
193225
@test pyfmt(">12.2e", 13.89) == " 1.39e+01"
194226
@test pyfmt("*<12.2e", 13.89) == "1.39e+01****"
195227
@test pyfmt("⋆<12.2e", 13.89) == "1.39e+01⋆⋆⋆⋆"
228+
@test pyfmt("*^12.2e", 13.89) == "**1.39e+01**"
229+
@test pyfmt("⋆^12.2e", 13.89) == "⋆⋆1.39e+01⋆⋆"
196230
@test pyfmt("*>12.2e", 13.89) == "****1.39e+01"
197231
@test pyfmt("⋆>12.2e", 13.89) == "⋆⋆⋆⋆1.39e+01"
198232
@test pyfmt("012.2e", 13.89) == "00001.39e+01"
@@ -218,6 +252,14 @@ end
218252
@test pyfmt("10.2e", 9.999e99) == " 1.00e+100"
219253
@test pyfmt("11.2e", BigFloat("9.999e999")) == " 1.00e+1000"
220254
@test pyfmt("10.2e", -9.999e-100) == " -1.00e-99"
255+
256+
# issue #84 (from Formatting.jl)
257+
@test pyfmt("+11.3e", 1.0e-309) == "+1.000e-309"
258+
@test pyfmt("+11.3e", 1.0e-313) == "+1.000e-313"
259+
260+
# issue #108 (from Formatting.jl)
261+
@test pyfmt(".1e", 0.0003) == "3.0e-04"
262+
@test pyfmt(".1e", 0.0006) == "6.0e-04"
221263
end
222264

223265
@testset "Format special floating point value" begin
@@ -238,9 +280,13 @@ end
238280
@test pyfmt("e", -Inf32) == "-Inf"
239281

240282
@test pyfmt("<5f", Inf) == "Inf "
283+
@test pyfmt("^5f", Inf) == " Inf "
241284
@test pyfmt(">5f", Inf) == " Inf"
285+
242286
@test pyfmt("*<5f", Inf) == "Inf**"
243287
@test pyfmt("⋆<5f", Inf) == "Inf⋆⋆"
288+
@test pyfmt("*^5f", Inf) == "*Inf*"
289+
@test pyfmt("⋆^5f", Inf) == "⋆Inf⋆"
244290
@test pyfmt("*>5f", Inf) == "**Inf"
245291
@test pyfmt("⋆>5f", Inf) == "⋆⋆Inf"
246292
end

0 commit comments

Comments
 (0)