{-| Stability: unstable Portability: portable Contains low-level number-formatting functions for CUIDs. -} module Web.Cuid.Internal.Formatting ( blockSize, formatBase, formatNumber, formatPadded, formatShort ) where import Data.Text (Text) import qualified Data.Text as T -- | These constants are to do with the desired output formatting of numbers in -- the CUID. formatBase, blockSize :: Int formatBase = 36 blockSize = 4 -- | Expresses the given number as a list of digits in the given base. The -- "digits" are just integers. The first element of the list is the most -- significant digit and the last element is the least significant. -- -- > digitsInBase 10 1234 = [1, 2, 3, 4] -- > digitsInBase 16 1234 = [4, 13, 2] -- "0x4D2" in the usual notation digitsInBase :: Integral a => a -> a -> [a] digitsInBase base' n' = go base' n' [] where go base n accum | n == 0 = if null accum then [0] else accum | n < base = [n] ++ accum | otherwise = go base q ([r] ++ accum) where (q, r) = quotRem n base -- | Converts a single integer to a textual digit. The integer can be any value -- between 0 and 35, inclusive; the numbers 10 through 35 are converted to the -- lowercase letters "a" through "z". numberToDigit :: Integral a => a -> Char numberToDigit n_ | n < 0 = error "numberToDigit: input is negative" | n < 10 = toEnum (n + 48) | n < 36 = toEnum (n + 97 - 10) | otherwise = error "numberToDigit: input is too large" where n = fromIntegral n_ -- | Returns the textual representation of the given integer in the given base. -- -- > formatInBase 10 999 = "999" -- > formatInBase 16 999 = "3e7" -- > formatInBase 36 999 = "rr" formatInBase :: Integral a => a -> a -> Text formatInBase base n = T.pack $ map numberToDigit $ digitsInBase base n -- | Returns the textual representation of the given integer in the given base, -- padded on the left with zeroes so that the string is /at least/ the given -- number of digits long. -- -- > formatPaddedInBase 10 3 1 = "001" -- > formatPaddedInBase 10 3 12 = "012" -- > formatPaddedInBase 10 3 123 = "123" -- > formatPaddedInBase 10 3 1234 = "1234" formatPaddedInBase :: Integral a => a -> a -> a -> Text formatPaddedInBase base d n = T.justifyRight digits '0' $ formatInBase base n where digits = fromIntegral d -- | Returns the textual representation of the first (i.e. most significant) two -- digits of the given integer in the given base. If the number is only one -- digit long then the resulting Text will be only one character long. -- -- > formatShortInBase 10 8 = "8" -- > formatShortInBase 10 86 = "86" -- > formatShortInBase 10 867 = "86" formatShortInBase :: Integral a => a -> a -> Text formatShortInBase base n = T.take 2 $ formatInBase base n -- | 'formatInBase' specialized to use the application's value of 'formatBase'. formatNumber :: Int -> Text formatNumber = formatInBase formatBase -- | 'formatPaddedInBase' specialized to use the application's values of -- 'formatBase' and 'blockSize'. formatPadded :: Int -> Text formatPadded = formatPaddedInBase formatBase blockSize -- | 'formatShortInBase' specialized to use the application's value of -- 'formatBase'. formatShort :: Int -> Text formatShort = formatShortInBase formatBase