Skip to content

Commit da1a179

Browse files
committed
Add Number and String formatting, closes #1, closes #3
1 parent 058b0db commit da1a179

File tree

5 files changed

+246
-132
lines changed

5 files changed

+246
-132
lines changed

README.md

+12-3
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,21 @@ Type-safe, printf-style formatting for PureScript.
44

55
## Usage
66
``` purs
7-
> formatInt (width 6) 123
7+
> format (width 6) 123
88
" 123"
99
10-
> formatInt (signed <> width 6) 123
10+
> format (signed <> width 6) 123
1111
" +123"
1212
13-
> formatInt (width 6 <> fillZeros) (-123)
13+
> format (zeroFill <> width 6) (-123)
1414
"-00123"
15+
16+
> format (width 8 <> precision 3) pi
17+
" 3.142"
18+
19+
> format (width 8 <> precision 3) 10.0
20+
" 10.000"
21+
22+
> format (width 8) "foo"
23+
" foo"
1524
```

bower.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
"authors": [
44
"David Peter <[email protected]>"
55
],
6+
"repository": {
7+
"type": "git",
8+
"url": "git://github.com/sharkdp/purescript-format.git"
9+
},
610
"license": "MIT",
711
"ignore": [
812
"**/.*",
@@ -12,7 +16,8 @@
1216
],
1317
"dependencies": {
1418
"purescript-strings": "^0.7.1",
15-
"purescript-arrays": "^0.4.5"
19+
"purescript-arrays": "^0.4.5",
20+
"purescript-math": "^0.2.0"
1621
},
1722
"devDependencies": {
1823
"purescript-test-unit": "^4.1.0"

src/Text/Format.purs

+140-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,152 @@
1+
-- | A module to format strings and numbers in a way similar to `printf` in
2+
-- | C-style languages.
3+
14
module Text.Format
2-
( padLeft
5+
( Properties()
6+
, width
7+
, zeroFill
8+
, signed
9+
, precision
10+
, class Format
11+
, format
312
) where
413

514
import Prelude
615

7-
import Data.String (length, fromCharArray)
16+
import Control.Alt ((<|>))
817
import Data.Array (replicate)
18+
import Data.Int as Int
19+
import Data.Maybe (Maybe(..), fromMaybe)
20+
import Data.Monoid (class Monoid)
21+
import Data.String (length, fromCharArray, dropWhile)
22+
import Math (round, pow, abs)
923

1024
-- | Pad a string on the left up to a given maximum length. The padding
1125
-- | character can be specified.
1226
padLeft :: Char -> Int -> String -> String
1327
padLeft c len str = prefix <> str
1428
where prefix = fromCharArray (replicate (len - length str) c)
29+
30+
type PropertiesRecord =
31+
{ width :: Maybe Int
32+
, padChar :: Maybe Char
33+
, signed :: Maybe Boolean
34+
, precision :: Maybe Int
35+
}
36+
37+
default :: PropertiesRecord
38+
default =
39+
{ width: Nothing
40+
, padChar: Nothing
41+
, signed: Nothing
42+
, precision: Nothing
43+
}
44+
45+
data Properties = Properties PropertiesRecord
46+
47+
instance eqProperties :: Eq Properties where
48+
eq (Properties rec1) (Properties rec2) =
49+
rec1.width == rec2.width &&
50+
rec1.padChar == rec2.padChar &&
51+
rec1.signed == rec2.signed &&
52+
rec1.precision == rec2.precision
53+
54+
instance semigroupProperties :: Semigroup Properties where
55+
append (Properties rec1) (Properties rec2) = Properties rec
56+
where
57+
-- These are combined such that options to the right take precedence:
58+
-- width 3 <> width 4 == width 4
59+
rec = { width: rec2.width <|> rec1.width
60+
, padChar: rec2.padChar <|> rec1.padChar
61+
, signed: rec2.signed <|> rec1.signed
62+
, precision: rec2.precision <|> rec1.precision
63+
}
64+
65+
instance monoidProperties :: Monoid Properties where
66+
mempty = Properties default
67+
68+
-- | The minium width of the output.
69+
width :: Int -> Properties
70+
width n = Properties (default { width = Just n })
71+
72+
-- | Fill the free space with zeros instead of spaces.
73+
zeroFill :: Properties
74+
zeroFill = Properties (default { padChar = Just '0' })
75+
76+
-- | Explicitely show a '+' sign for positive numbers. Gets ignored for
77+
-- | non-numeric types.
78+
signed :: Properties
79+
signed = Properties (default { signed = Just true })
80+
81+
-- | Number of decimal places. Gets ignored for non-numeric types.
82+
precision :: Int -> Properties
83+
precision digits = Properties (default { precision = Just digits })
84+
85+
-- | A class for types that can be formatted using the specified properties.
86+
class Format a where
87+
format :: Properties -> a -> String
88+
89+
instance formatString :: Format String where
90+
format (Properties rec) str =
91+
case rec.width of
92+
Just len -> padLeft padChar len str
93+
Nothing -> str
94+
where
95+
padChar = fromMaybe ' ' rec.padChar
96+
97+
instance formatInt :: Format Int where
98+
format prop@(Properties rec) num | fromMaybe 0 rec.precision > 0 =
99+
format prop (Int.toNumber num)
100+
101+
format (Properties rec) num =
102+
case rec.width of
103+
Just len ->
104+
if padChar == ' '
105+
then
106+
padLeft padChar len (numSgn <> show numAbs)
107+
else
108+
numSgn <> padLeft padChar (len - length numSgn) (show numAbs)
109+
Nothing -> numSgn <> show numAbs
110+
111+
where
112+
isSigned = fromMaybe false rec.signed
113+
padChar = fromMaybe ' ' rec.padChar
114+
isPositive = num > 0
115+
numAbs = if isPositive then num else (-num)
116+
numSgn = if isPositive
117+
then (if isSigned then "+" else "")
118+
else "-"
119+
120+
instance formatNumber :: Format Number where
121+
-- Format as an integer if the precision is set to 0
122+
format prop@(Properties rec) num | rec.precision == Just 0 =
123+
format prop (Int.round num)
124+
125+
format (Properties rec) num' =
126+
case rec.width of
127+
Just len ->
128+
if padChar == ' '
129+
then
130+
padLeft padChar len (numSgn <> numAbsStr)
131+
else
132+
numSgn <> padLeft padChar (len - length numSgn) numAbsStr
133+
Nothing -> numSgn <> numAbsStr
134+
135+
where
136+
num = case rec.precision of
137+
Nothing -> num'
138+
Just digits ->
139+
let f = 10.0 `pow` Int.toNumber digits
140+
in round (f * num') / f
141+
isSigned = fromMaybe false rec.signed
142+
padChar = fromMaybe ' ' rec.padChar
143+
isPositive = num > 0.0
144+
numAbsStr' = show (abs num)
145+
numAbsStr = case rec.precision of
146+
Nothing -> numAbsStr'
147+
Just p -> numAbsStr' <> paddedZeros p
148+
paddedZeros p = let d = length (dropWhile (_ /= '.') numAbsStr') - 1
149+
in fromCharArray (replicate (p - d) '0')
150+
numSgn = if isPositive
151+
then (if isSigned then "+" else "")
152+
else "-"

src/Text/Format/Number.purs

-85
This file was deleted.

0 commit comments

Comments
 (0)