Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
The Formatting.jl package is licensed under the MIT "Expat" License:
The JuliaString/Format.jl package is licensed under the MIT "Expat" License,
and is based on the JuliaIO/Formatting.jl package, licensed as follows:

> Copyright (c) 2014: Dahua Lin and contributors.
>
Expand Down
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
# Formatting
# Format

This package offers Python-style general formatting and c-style numerical formatting (for speed).

[![Build Status](https://travis-ci.org/JuliaLang/Formatting.jl.svg?branch=master)](https://travis-ci.org/JuliaLang/Formatting.jl)
[![Formatting](http://pkg.julialang.org/badges/Formatting_0.4.svg)](http://pkg.julialang.org/?pkg=Formatting&ver=0.4)
[![Formatting](http://pkg.julialang.org/badges/Formatting_0.5.svg)](http://pkg.julialang.org/?pkg=Formatting&ver=0.5)
[![Build Status](https://travis-ci.org/JuliaString/Format.jl.svg?branch=newmaster)](https://travis-ci.org/JuliaString/Format.jl)
[![Format](http://pkg.julialang.org/badges/Format_0.4.svg)](http://pkg.julialang.org/?pkg=Format&ver=0.4)
[![Format](http://pkg.julialang.org/badges/Format_0.5.svg)](http://pkg.julialang.org/?pkg=Format&ver=0.5)

---------------


## Getting Started

This package is pure Julia. Setting up this package is like setting up other Julia packages:
This package is pure Julia.
This package is not yet registered, so it needs to be cloned and checked out:

```julia
Pkg.add("Formatting")
Pkg.clone("https://github.com/JuliaString/Format.jl")
Pkg.checkout("Format", "newmaster")
```

To start using the package, you can simply write

```julia
using Formatting
using Format
```

This package depends on Julia of version 0.2 or above. It has no other dependencies. The package is MIT-licensed.
This package depends on Julia of version 0.4 or above. It has no other dependencies. The package is MIT-licensed.


## Python-style Types and Functions
Expand Down Expand Up @@ -133,7 +135,7 @@ stripping trailing zeros, and mixed fractions.
### Usage and Implementation

The idea here is that the package compiles a function only once for each unique
format string within the `Formatting.*` name space, so repeated use is faster.
format string within the `Format.*` name space, so repeated use is faster.
Unrelated parts of a session using the same format string would reuse the same
function, avoiding redundant compilation. To avoid the proliferation of
functions, we limit the usage to only 1 argument. Practical consideration
Expand All @@ -142,7 +144,7 @@ seems manageable.

Usage
```julia
using Formatting
using Format

fmt = "%10.3f"
s = sprintf1( fmt, 3.14159 ) # usage 1. Quite performant. Easiest to switch to.
Expand Down
8 changes: 6 additions & 2 deletions src/Formatting.jl → src/Format.jl
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
module Formatting
module Format

import Base.show

export
FormatSpec, FormatExpr,
printfmt, printfmtln, fmt, format,
printfmt, printfmtln, cfmt, format,
sprintf1, generate_formatter

export
fmt, fmt_default, fmt_default!

using Compat

include("cformat.jl" )
include("fmtspec.jl")
include("fmtcore.jl")
include("formatexpr.jl")
include("fmt.jl")

end # module
173 changes: 173 additions & 0 deletions src/fmt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@

# interface proposal by Tom Breloff (@tbreloff)... comments welcome
# This uses the more basic formatting based on FormatSpec and the cfmt method (formerly called fmt, which I repurposed)

# TODO: swap out FormatSpec for something that is able to use the "format" method, which has more options for units, prefixes, etc
# TODO: support rational numbers, autoscale, etc as in "format"

# --------------------------------------------------------------------------------------------------

# the DefaultSpec object is just something to hold onto the current FormatSpec.
# we keep the typechar around specically for the reset! function, to go back to the starting state

type DefaultSpec
typechar::Char
fspec::FormatSpec
DefaultSpec(c::Char) = new(c, FormatSpec(c))
end

const DEFAULT_FORMATTERS = Dict{DataType, DefaultSpec}()

# adds a new default formatter for this type
defaultSpec!{T}(::Type{T}, c::Char) = (DEFAULT_FORMATTERS[T] = DefaultSpec(c); nothing)

# note: types T and K will now both share K's default
defaultSpec!{T,K}(::Type{T}, ::Type{K}) = (DEFAULT_FORMATTERS[T] = DEFAULT_FORMATTERS[K]; nothing)

# seed it with some basic default formatters
for (t, c) in [(Integer,'d'), (FloatingPoint,'f'), (Char,'c'), (String,'s')]
defaultSpec!(t, c)
end

reset!{T}(::Type{T}) = (dspec = defaultSpec(T); dspec.fspec = FormatSpec(dspec.typechar); nothing)


# --------------------------------------------------------------------------------------------------


function addKWArgsFromSymbols(kwargs, syms::Symbol...)
d = Dict(kwargs)
for s in syms
if s == :ljust || s == :left
d[:align] = '<'
elseif s == :rjust || s == :right
d[:align] = '>'
elseif s == :commas
d[:tsep] = true
elseif s == :zpad || s == :zeropad
d[:zpad] = true
elseif s == :ipre || s == :prefix
d[:ipre] = true
end
end
d
end

# --------------------------------------------------------------------------------------------------

# methods to get the current default objects
# note: if you want to set a default for an abstract type (i.e. FloatingPoint) you'll need to extend this method like here:
defaultSpec{T<:Integer}(::Type{T}) = DEFAULT_FORMATTERS[Integer]
defaultSpec{T<:FloatingPoint}(::Type{T}) = DEFAULT_FORMATTERS[FloatingPoint]
defaultSpec{T<:String}(::Type{T}) = DEFAULT_FORMATTERS[String]
function defaultSpec{T}(::Type{T})
get(DEFAULT_FORMATTERS, T) do
error("Missing default spec for type $T... call default!(T, c): $DEFAULT_FORMATTERS")
end
end
defaultSpec(x) = defaultSpec(typeof(x))

fmt_default{T}(::Type{T}) = defaultSpec(T).fspec
fmt_default(x) = defaultSpec(x).fspec



# first resets the fmt_default spec to the given arg, then continue by updating with args and kwargs
fmt_default!{T}(::Type{T}, c::Char, args...; kwargs...) = (defaultSpec!(T,c); fmt_default!(T, args...; kwargs...))
fmt_default!{T,K}(::Type{T}, ::Type{K}, args...; kwargs...) = (defaultSpec!(T,K); fmt_default!(T, args...; kwargs...))

# update the fmt_default for a specific type
function fmt_default!{T}(::Type{T}, syms::Symbol...; kwargs...)
if isempty(syms)

# if there are no arguments, reset to initial defaults
if isempty(kwargs)
reset!(T)
return
end

# otherwise update the spec
dspec = defaultSpec(T)
dspec.fspec = FormatSpec(dspec.fspec; kwargs...)

else
d = addKWArgsFromSymbols(kwargs, syms...)
fmt_default!(T; d...)
end
nothing
end

# update the fmt_default for all types
function fmt_default!(syms::Symbol...; kwargs...)
if isempty(syms)
for k in keys(DEFAULT_FORMATTERS)
fmt_default!(k; kwargs...)
end
else
d = addKWArgsFromSymbols(kwargs, syms...)
fmt_default!(; d...)
end
nothing
end


# --------------------------------------------------------------------------------------------------

# TODO: get rid of this entire hack by moving commas into cfmt

function optionalCommas(x::Real, s::String, fspec::FormatSpec)
dpos = findfirst(s, '.')
prevwidth = length(s)

if dpos == 0
s = addcommas(s)
else
s = string(addcommas(s[1:dpos-1]), '.', s[dpos+1:end])
end

# check for excess width from commas
w = length(s)
if fspec.width > 0 && w > fspec.width && w > prevwidth
# we may have made the string too wide with those commas... gotta fix it
s = strip(s)
n = fspec.width - length(s)
if fspec.align == '<' # left alignment
s = string(s, " "^n)
else
s = string(" "^n, s)
end
end

s
end
optionalCommas(x, s::String, fspec::FormatSpec) = s

# --------------------------------------------------------------------------------------------------


# TODO: do more caching to optimize repeated calls

# creates a new FormatSpec by overriding the defaults and passes it to cfmt
# 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 = isempty(kwargs) ? fmt_default(x) : FormatSpec(fmt_default(x); kwargs...)
s = cfmt(fspec, x)

# add the commas now... I was confused as to when this is done currently
if fspec.tsep
return optionalCommas(x, s, fspec)
end
s
end

# some helper method calls, which just convert to kwargs
fmt(x, prec::Int, args...; kwargs...) = fmt(x, args...; prec=prec, kwargs...)
fmt(x, prec::Int, width::Int, args...; kwargs...) = fmt(x, args...; prec=prec, width=width, kwargs...)

# integrate some symbol shorthands into the keyword args
# note: as above, this will generate relavent kwargs, so to format in a tight loop, you should probably update the fmt_default
function fmt(x, syms::Symbol...; kwargs...)
d = addKWArgsFromSymbols(kwargs, syms...)
fmt(x; d...)
end
17 changes: 15 additions & 2 deletions src/fmtspec.jl
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,19 @@ immutable FormatSpec
end
new(cls, typ, fill, align, sign, width, prec, ipre, zpad, tsep)
end

# copy constructor with overrides
function FormatSpec(spec::FormatSpec;
fill::Char=spec.fill,
align::Char=spec.align,
sign::Char=spec.sign,
width::Int=spec.width,
prec::Int=spec.prec,
ipre::Bool=spec.ipre,
zpad::Bool=spec.zpad,
tsep::Bool=spec.tsep)
new(spec.cls, spec.typ, fill, align, sign, width, prec, ipre, zpad, tsep)
end
end

function show(io::IO, fs::FormatSpec)
Expand Down Expand Up @@ -200,5 +213,5 @@ end

printfmt(fs::FormatSpec, x) = printfmt(STDOUT, fs, x)

fmt(fs::FormatSpec, x) = sprint(printfmt, fs, x)
fmt(spec::AbstractString, x) = fmt(FormatSpec(spec), x)
cfmt(fs::FormatSpec, x) = (buf = IOBuffer(); printfmt(buf, fs, x); bytestring(buf))
cfmt(spec::String, x) = cfmt(FormatSpec(spec), x)
2 changes: 1 addition & 1 deletion test/cformat.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Formatting
using Format
using Compat
using Base.Test

Expand Down
30 changes: 30 additions & 0 deletions test/fmt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Format
using Base.Test

# some basic functionality testing
x = 1234.56789

@test fmt(x) == "1234.567890"
@test fmt(x,2) == "1234.57"
@test fmt(x,3,10) == " 1234.568"
@test fmt(x,3,10,:left) == "1234.568 "
@test fmt(x,3,10,:ljust) == "1234.568 "
@test fmt(x,:commas) == "1,234.567890"

i = 1234567

@test fmt(i) == "1234567"
@test fmt(i,:commas) == "1,234,567"

fmt_default!(Int, :commas, width = 12)
@test fmt(i) == " 1,234,567"
@test fmt(x) == "1234.567890" # default hasn't changed

fmt_default!(:commas)
@test fmt(i) == " 1,234,567"
@test fmt(x) == "1,234.567890" # width hasn't changed, but added commas

fmt_default!(Int) # resets Integer defaults
@test fmt(i) == "1234567"
@test fmt(i,:commas) == "1,234,567"

2 changes: 1 addition & 1 deletion test/fmtspec.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# test format spec parsing

using Formatting
using Format
using Base.Test


Expand Down
2 changes: 1 addition & 1 deletion test/formatexpr.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Formatting
using Format
using Base.Test

# with positional arguments
Expand Down
2 changes: 1 addition & 1 deletion test/perf1.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# performance testing

using Formatting
using Format

# performance of format parsing

Expand Down
9 changes: 5 additions & 4 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Formatting
using Format
using Base.Test

include( "cformat.jl" )
include( "fmtspec.jl" )
include( "formatexpr.jl" )
# include( "cformat.jl" )
# include( "fmtspec.jl" )
# include( "formatexpr.jl" )
include( "fmt.jl" )