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