1{- data size display and parsing
2 -
3 - Copyright 2011 Joey Hess <id@joeyh.name>
4 -
5 - License: BSD-2-clause
6 -
7 -
8 - And now a rant:
9 -
10 - In the beginning, we had powers of two, and they were good.
11 -
12 - Disk drive manufacturers noticed that some powers of two were
13 - sorta close to some powers of ten, and that rounding down to the nearest
14 - power of ten allowed them to advertise their drives were bigger. This
15 - was sorta annoying.
16 -
17 - Then drives got big. Really, really big. This was good.
18 -
19 - Except that the small rounding error perpretrated by the drive
20 - manufacturers suffered the fate of a small error, and became a large
21 - error. This was bad.
22 -
23 - So, a committee was formed. And it arrived at a committee-like decision,
24 - which satisfied noone, confused everyone, and made the world an uglier
25 - place. As with all committees, this was meh.
26 -
27 - And the drive manufacturers happily continued selling drives that are
28 - increasingly smaller than you'd expect, if you don't count on your
29 - fingers. But that are increasingly too big for anyone to much notice.
30 - This caused me to need git-annex.
31 -
32 - Thus, I use units here that I loathe. Because if I didn't, people would
33 - be confused that their drives seem the wrong size, and other people would
34 - complain at me for not being standards compliant. And we call this
35 - progress?
36 -}
37
38module Utility.DataUnits (
39	dataUnits,
40	storageUnits,
41	memoryUnits,
42	bandwidthUnits,
43	oldSchoolUnits,
44	Unit(..),
45	ByteSize,
46
47	roughSize,
48	roughSize',
49	compareSizes,
50	readSize
51) where
52
53import Data.List
54import Data.Char
55
56import Utility.HumanNumber
57
58type ByteSize = Integer
59type Name = String
60type Abbrev = String
61data Unit = Unit ByteSize Abbrev Name
62	deriving (Ord, Show, Eq)
63
64dataUnits :: [Unit]
65dataUnits = storageUnits ++ memoryUnits
66
67{- Storage units are (stupidly) powers of ten. -}
68storageUnits :: [Unit]
69storageUnits =
70	[ Unit (p 8) "YB" "yottabyte"
71	, Unit (p 7) "ZB" "zettabyte"
72	, Unit (p 6) "EB" "exabyte"
73	, Unit (p 5) "PB" "petabyte"
74	, Unit (p 4) "TB" "terabyte"
75	, Unit (p 3) "GB" "gigabyte"
76	, Unit (p 2) "MB" "megabyte"
77	, Unit (p 1) "kB" "kilobyte" -- weird capitalization thanks to committe
78	, Unit (p 0) "B" "byte"
79	]
80  where
81	p :: Integer -> Integer
82	p n = 1000^n
83
84{- Memory units are (stupidly named) powers of 2. -}
85memoryUnits :: [Unit]
86memoryUnits =
87	[ Unit (p 8) "YiB" "yobibyte"
88	, Unit (p 7) "ZiB" "zebibyte"
89	, Unit (p 6) "EiB" "exbibyte"
90	, Unit (p 5) "PiB" "pebibyte"
91	, Unit (p 4) "TiB" "tebibyte"
92	, Unit (p 3) "GiB" "gibibyte"
93	, Unit (p 2) "MiB" "mebibyte"
94	, Unit (p 1) "KiB" "kibibyte"
95	, Unit (p 0) "B" "byte"
96	]
97  where
98	p :: Integer -> Integer
99	p n = 2^(n*10)
100
101{- Bandwidth units are only measured in bits if you're some crazy telco. -}
102bandwidthUnits :: [Unit]
103bandwidthUnits = error "stop trying to rip people off"
104
105{- Do you yearn for the days when men were men and megabytes were megabytes? -}
106oldSchoolUnits :: [Unit]
107oldSchoolUnits = zipWith (curry mingle) storageUnits memoryUnits
108  where
109	mingle (Unit _ a n, Unit s' _ _) = Unit s' a n
110
111{- approximate display of a particular number of bytes -}
112roughSize :: [Unit] -> Bool -> ByteSize -> String
113roughSize units short i = roughSize' units short 2 i
114
115roughSize' :: [Unit] -> Bool -> Int -> ByteSize -> String
116roughSize' units short precision i
117	| i < 0 = '-' : findUnit units' (negate i)
118	| otherwise = findUnit units' i
119  where
120	units' = sortBy (flip compare) units -- largest first
121
122	findUnit (u@(Unit s _ _):us) i'
123		| i' >= s = showUnit i' u
124		| otherwise = findUnit us i'
125	findUnit [] i' = showUnit i' (last units') -- bytes
126
127	showUnit x (Unit size abbrev name) = s ++ " " ++ unit
128	  where
129		v = (fromInteger x :: Double) / fromInteger size
130		s = showImprecise precision v
131		unit
132			| short = abbrev
133			| s == "1" = name
134			| otherwise = name ++ "s"
135
136{- displays comparison of two sizes -}
137compareSizes :: [Unit] -> Bool -> ByteSize -> ByteSize -> String
138compareSizes units abbrev old new
139	| old > new = roughSize units abbrev (old - new) ++ " smaller"
140	| old < new = roughSize units abbrev (new - old) ++ " larger"
141	| otherwise = "same"
142
143{- Parses strings like "10 kilobytes" or "0.5tb". -}
144readSize :: [Unit] -> String -> Maybe ByteSize
145readSize units input
146	| null parsednum || null parsedunit = Nothing
147	| otherwise = Just $ round $ number * fromIntegral multiplier
148  where
149	(number, rest) = head parsednum
150	multiplier = head parsedunit
151	unitname = takeWhile isAlpha $ dropWhile isSpace rest
152
153	parsednum = reads input :: [(Double, String)]
154	parsedunit = lookupUnit units unitname
155
156	lookupUnit _ [] = [1] -- no unit given, assume bytes
157	lookupUnit [] _ = []
158	lookupUnit (Unit s a n:us) v
159		| a ~~ v || n ~~ v = [s]
160		| plural n ~~ v || a ~~ byteabbrev v = [s]
161		| otherwise = lookupUnit us v
162
163	a ~~ b = map toLower a == map toLower b
164
165	plural n = n ++ "s"
166	byteabbrev a = a ++ "b"
167