packages feed

ulid 0.2.0.0 → 0.3.0.0

raw patch · 16 files changed

+987/−388 lines, 16 filesdep −crockfordnew-uploaderPVP ok

version bump matches the API change (PVP)

Dependencies removed: crockford

API changes (from Hackage documentation)

- Data.ULID.Crockford: decode :: Int -> ReadS Integer
- Data.ULID.Crockford: encode :: Int -> Integer -> String
+ Data.ULID.Base32: decode :: Integral i => Int -> Text -> [(i, Text)]
+ Data.ULID.Base32: decodeChar :: Integral i => Char -> Maybe i
+ Data.ULID.Base32: encode :: Integral i => Int -> i -> Text
+ Data.ULID.Base32: encodeChar :: Integral i => i -> Char
+ Data.ULID.Digits: digits :: Integral n => n -> n -> [n]
+ Data.ULID.Digits: unDigits :: Integral n => n -> [n] -> n
- Data.ULID: ulidFromInteger :: Integer -> ULID
+ Data.ULID: ulidFromInteger :: Integer -> Either Text ULID

Files

README.md view
@@ -1,31 +1,51 @@-# Ulid implementation in Haskell+# ULID Implementation in Haskell -Lexicographically sortable, 128-bit identifier with 48-bit timestamp and 80 random bits.-Canonically encoded as a 26 character string, as opposed to the 36 character UUID.+Lexicographically sortable, 128-bit identifier+with 48-bit timestamp and 80 random bits.+Canonically encoded as a 26 character string,+as opposed to the 36 character UUID. -Original implementation and spec: https://github.com/alizain/ulid/+Original implementation and spec: [github.com/alizain/ulid] +[github.com/alizain/ulid]: https://github.com/alizain/ulid/ ++```txt+ 01an4z07by   79ka1307sr9x4mv3++|----------| |----------------|+ Timestamp       Randomness+  48 bits         80 bits+```++ # Universally Unique Lexicographically Sortable Identifier  UUID can be suboptimal for many uses-cases because:  - It isn't the most character efficient way of encoding 128 bits of randomness-- UUID v1/v2 is impractical in many environments, as it requires access to a unique, stable MAC address-- UUID v3/v5 requires a unique seed and produces randomly distributed IDs, which can cause fragmentation in many data structures-- UUID v4 provides no other information than randomness which can cause fragmentation in many data structures+- UUID v1/v2 is impractical in many environments,+    as it requires access to a unique, stable MAC address+- UUID v3/v5 requires a unique seed and produces randomly distributed IDs,+    which can cause fragmentation in many data structures+- UUID v4 provides no other information than randomness,+    which can cause fragmentation in many data structures  Instead, herein is proposed ULID:  - 128-bit compatibility with UUID - 1.21e+24 unique ULIDs per millisecond-- Lexicographically sortable!-- Canonically encoded as a 26 character string, as opposed to the 36 character UUID-- Uses Crockford's base32 for better efficiency and readability (5 bits per character)+- Lexicographically sortable+- Canonically encoded as a 26 character string,+    as opposed to the 36 character UUID+- Uses [Douglas Crockford's base 32] for better efficiency and readability+    (5 bits per character) - Case insensitive - No special characters (URL safe) +[Douglas Crockford's base 32]: https://www.crockford.com/base32.html + ## Usage  A simple usage example:@@ -33,61 +53,67 @@ ````haskell module Main where -import           Data.ULID+import Data.ULID  main :: IO () main = do-    -- Derive a ULID using the current time and default random number generator-    ulid1 <- getULID-    print ulid1+  -- Derive a ULID using the current time and default random number generator+  ulid1 <- getULID+  print ulid1 -    -- Derive a ULID using a specified time and default random number generator-    ulid2 <- getULIDTime 1469918176.385 -- POSIX Time, specified to the millisecond-    print ulid2+  -- Derive a ULID using a specified time and default random number generator+  ulid2 <- getULIDTime 1469918176.385 -- POSIX Time, millisecond precision+  print ulid2 ```` -As per the spec, it is also possible to use a cryptographically-secure random number generator to contribute the randomness.  However, the programmer must manage the generator on their own. Example:+As per the spec, it is also possible to use a cryptographically-secure+random number generator to contribute the randomness.+However, the programmer must manage the generator on their own. +Example:  ````haskell module Main where -import           Data.ULID+import Data.ULID  import qualified Crypto.Random       as CR import qualified Data.ULID.Random    as UR import qualified Data.ULID.TimeStamp as TS  main :: IO ()-main = do     -    -- This default instantiation may not be sufficiently secure, see the docs -    -- https://hackage.haskell.org/package/crypto-api-0.13.2/docs/Crypto-Random.html-    g <- (CR.newGenIO :: IO CR.SystemRandom)+main = do+  -- This default instantiation may not be sufficiently secure.+  -- See the docs at+  -- hackage.haskell.org/package/crypto-api-0.13.2/docs/Crypto-Random.html+  g <- (CR.newGenIO :: IO CR.SystemRandom) -    -- Generate time stamp from current time-    t <- TS.getULIDTimeStamp-    -    let ulid3 = case UR.mkCryptoULIDRandom g of-            Left err        -> error $ show err-            Right (rnd, g2) -> ULID t rnd   -- use g2, etc, to continue generating secure ULIDs-    print ulid3-````+  -- Generate timestamp from current time+  t <- TS.getULIDTimeStamp +  let ulid3 = case UR.mkCryptoULIDRandom g of+          Left err        -> error $ show err+          -- use g2, …, to continue generating secure ULIDs+          Right (rnd, g2) -> ULID t rnd +  print ulid3+```` + ## Test Suite -```+```sh stack test ``` + ## Performance -```+```sh stack bench ``` -```+```txt Running 1 benchmarks... Benchmark ulid-bench: RUNNING... 217,868 op/s generate
app/Main.hs view
@@ -1,35 +1,35 @@-module Main where
-
-import           Data.ULID
-
--- These imports only needed for cryptographically secure ULID
-import qualified Crypto.Random       as CR
-import qualified Data.ULID.Random    as UR
-import qualified Data.ULID.TimeStamp as TS
-
-main :: IO ()
-main = do
-     -- Derive a ULID using the current time and default random number generator
-    ulid1 <- getULID
-    print ulid1
-
-    -- Derive a ULID using a specified time and default random number generator
-    ulid2 <- getULIDTime 1469918176.385 -- POSIX Time, specified to the millisecond
-    print ulid2
-
-
-    -- Below only for crypographically secure ULID example
-
-    -- This default instantiation may not be sufficiently secure, see the docs
-    -- https://hackage.haskell.org/package/crypto-api-0.13.2/docs/Crypto-Random.html
-    g <- (CR.newGenIO :: IO CR.SystemRandom)
-
-    -- Generate time stamp from current time
-    t <- TS.getULIDTimeStamp
-
-    let ulid3 = case UR.mkCryptoULIDRandom g of
-            Left err        -> error $ show err
-            Right (rnd, g2) -> ULID t rnd   -- use g2, etc, to continue generating secure ULIDs
-    print ulid3
-
-
+module Main where++import Data.ULID++-- These imports only needed for cryptographically secure ULID+import qualified Crypto.Random       as CR+import qualified Data.ULID.Random    as UR+import qualified Data.ULID.TimeStamp as TS+++main :: IO ()+main = do+  -- Derive a ULID using the current time and default random number generator+  ulid1 <- getULID+  print ulid1++  -- Derive a ULID using a specified time and default random number generator+  ulid2 <- getULIDTime 1469918176.385 -- ^ POSIX Time in milliseconds+  print ulid2++  -- Below only for cryptographically secure ULID example++  -- This default instantiation may not be sufficiently secure, see the docs+  -- https://hackage.haskell.org/package/crypto-api-0.13.3/docs/Crypto-Random.html+  g <- (CR.newGenIO :: IO CR.SystemRandom)++  -- Generate time stamp from current time+  t <- TS.getULIDTimeStamp++  let ulid3 = case UR.mkCryptoULIDRandom g of+        Left err        -> error $ show err+        -- Use g2, etc, to continue generating secure ULIDs+        Right (rnd, g2) -> ULID t rnd++  print ulid3
bench/Main.hs view
@@ -1,24 +1,28 @@-module Main where
-
-import           Data.ULID
-
-import           Control.DeepSeq
-import           Control.Monad            (replicateM)
-import qualified Data.Text                as T
-import qualified Data.Text.Format.Numbers as FN
-import qualified Data.Time.Clock.POSIX    as PX
-
-formatTN = T.unpack . (FN.prettyI (Just ','))
-
-main :: IO ()
-main = do
-    -- Run many iterations of getULID
-    let ops = 100000
-    begin <- PX.getPOSIXTime
-    ulids <- replicateM ops (getULID >>= return.force)
-    end <- PX.getPOSIXTime
-    let elapsed = end - begin
-    let opsPerSec = (fromIntegral ops) / (realToFrac elapsed) :: Double
-    putStr $ formatTN (round opsPerSec)
-    putStrLn " op/s generate"
-
+module Main where++import Data.ULID++import Control.DeepSeq+import Control.Monad (replicateM)+import Data.Text as T+import Data.Text.IO as T+import Data.Text.Format.Numbers as FN+import Data.Time.Clock.POSIX as PX+++formatTN :: Int -> Text+formatTN =+  FN.prettyI $ Just ','+++main :: IO ()+main = do+  -- Run many iterations of getULID+  let ops = 100000+  begin <- PX.getPOSIXTime+  ulids <- replicateM ops (getULID >>= return.force)+  end <- PX.getPOSIXTime+  let elapsed = end - begin+  let opsPerSec = (fromIntegral ops) / (realToFrac elapsed) :: Double+  T.putStr $ formatTN $ round opsPerSec+  T.putStrLn " op/s generate"
src/Data/Binary/Roll.hs view
@@ -1,28 +1,31 @@-module Data.Binary.Roll where
-
-import           Data.Binary
-import           Data.Bits
-import           Data.List   (foldl', unfoldr)
-
-
--- | unroll and produce an exact number of bytes (left-pad with 0 bytes)
-unroll :: Int -> Integer -> [Word8]
-unroll bytes val = replicate (bytes - length xs) 0 ++ xs
-    where xs = unroll' val
-
--- source: http://hackage.haskell.org/package/binary-0.8.5.1/docs/src/Data-Binary-Class.html#line-311
---
--- Fold and unfold an Integer to and from a list of its bytes
---
--- MOST SIGNIFICANT BYTE FIRST please (this is a change from the default Data.Binary impl)
-
-unroll' :: Integer -> [Word8]
-unroll' = reverse . unfoldr step
-  where
-    step 0 = Nothing
-    step i = Just (fromIntegral i, i `shiftR` 8)
-
-roll :: [Word8] -> Integer
-roll = foldl' unstep 0
-  where
-    unstep a b = a `shiftL` 8 .|. fromIntegral b
+module Data.Binary.Roll where++import Data.Binary+import Data.Bits+import Data.List (foldl', unfoldr)+++-- | Unroll and produce an exact number of bytes (left-pad with 0 bytes)+unroll :: Int -> Integer -> [Word8]+unroll bytes val = replicate (bytes - length xs) 0 ++ xs+    where xs = unroll' (abs val)+++-- | Source:+-- https://hackage.haskell.org/package/binary-0.8.5.1/docs/src/Data-Binary-Class.html#line-311+--+-- Fold and unfold an Integer to and from a list of its bytes+--+-- MOST SIGNIFICANT BYTE FIRST please+-- (this is a change from the default `Data.Binary` impl)+unroll' :: Integer -> [Word8]+unroll' = reverse . unfoldr step+  where+    step 0 = Nothing+    step i = Just (fromIntegral i, i `shiftR` 8)+++roll :: [Word8] -> Integer+roll = foldl' unstep 0+  where+    unstep a b = a `shiftL` 8 .|. fromIntegral b
src/Data/ULID.hs view
@@ -1,119 +1,144 @@-{- |
-Module      : Data.ULID
-Copyright   : (c) 2017 Steve Kollmansberger
-
-License     : BSD-style
-
-Maintainer  : steve@kolls.net
-Stability   : experimental
-Portability : portable
-
-This library implements the Universally Unique Lexicographically Sortable Identifier, as described at <https://github.com/alizain/ulid>.
-
-UUID can be suboptimal for many uses-cases because:
-
-* It isn't the most character efficient way of encoding 128 bits of randomness
-* UUID v1/v2 is impractical in many environments, as it requires access to a unique, stable MAC address
-* UUID v3/v5 requires a unique seed and produces randomly distributed IDs, which can cause fragmentation in many data structures
-* UUID v4 provides no other information than randomness which can cause fragmentation in many data structures
-
-Instead, herein is proposed ULID:
-
-* 128-bit compatibility with UUID
-* 1.21e+24 unique ULIDs per millisecond
-* Lexicographically sortable!
-* Canonically encoded as a 26 character string, as opposed to the 36 character UUID
-* Uses Crockford's base32 for better efficiency and readability (5 bits per character)
-* Case insensitive
-* No special characters (URL safe)
-
--}
-{-# LANGUAGE DeriveDataTypeable #-}
-module Data.ULID (
-    ULID(..),
-    getULIDTime,
-    getULID,
-    ulidToInteger,
-    ulidFromInteger
-) where
-
-import           Control.DeepSeq
-import           Data.Binary
-import qualified Data.ByteString.Lazy  as LBS
-import           Data.Data
-import           Data.Hashable
-import           Data.Monoid           ((<>))
-import           Data.Time.Clock.POSIX
-import           System.IO.Unsafe
-import qualified System.Random         as R
-
-import           Data.Binary.Roll
-import           Data.ULID.Random
-import           Data.ULID.TimeStamp
-
-data ULID = ULID {
-    timeStamp :: !ULIDTimeStamp,
-    random    :: !ULIDRandom
-    }
-    deriving (Eq, Typeable, Data)
-
--- | Derive a ULID using a specified time and default random number generator
-getULIDTime :: POSIXTime    -- ^ The specified UNIX time (seconds) to millisecond precision, e.g. 1469918176.385
-    -> IO ULID
-getULIDTime t = do
-    let t' = mkULIDTimeStamp t
-    r <- getULIDRandom
-    return $ ULID t' r
-
--- | Derive a ULID using the current time and default random number generator
-getULID :: IO ULID
-getULID = do
-    t <- getULIDTimeStamp
-    r <- getULIDRandom
-    return $ ULID t r
-
--- | Convert a ULID to its corresponding (at most) 128-bit Integer. Integer equivalents retain sortable trait (same sort order).
--- This could be useful for storing in a database using a smaller field than storing the Show'n string, but still human-readable unlike the Binary version.
-ulidToInteger :: ULID -> Integer
-ulidToInteger = roll.(LBS.unpack).encode
-
--- | Convert a ULID from its corresponding 128-bit Integer.
-ulidFromInteger :: Integer -- ^ The ULID's Integer equivalent, as generated by toInteger
-    -> ULID
-ulidFromInteger = decode.(LBS.pack).(unroll 16) -- 16 bytes = 128 bit
-
-instance Ord ULID where
-    compare (ULID ts1 _) (ULID ts2 _) = compare ts1 ts2
-
-instance Show ULID where
-    show (ULID ts bytes) = (show ts) ++ (show bytes)
-
-instance Read ULID where
-    readsPrec _ str = do
-        (ts, str2) <- reads str
-        (rn, str3) <- reads str2
-        return (ULID ts rn, str3)
-
-instance Binary ULID where
-    put (ULID ts bytes) = put ts <> put bytes
-    get = do
-        ts <- get
-        bytes <- get
-        return $ ULID ts bytes
-
--- Because of the strictness annotations, this shouldn't be needed and shouldn't do anything
--- I tested and confirmed this in the benchmark, but since I did the work to put it here
--- It's no harm to leave it in
-instance NFData ULID where
-    rnf (ULID ts bytes) = rnf ts `seq` (rnf bytes `seq` ())
-
-instance R.Random ULID where
-    randomR _ = R.random -- ignore range
-    random g = unsafePerformIO $ do
-        t <- getULIDTimeStamp
-        let (r, g') = mkULIDRandom g
-        return (ULID t r, g')
-    randomIO = getULID
-
-instance Hashable ULID where
-    hashWithSalt salt ulid = hashWithSalt salt (encode ulid)
+{- |+This library implements the+Universally Unique Lexicographically Sortable Identifier,+as described at https://github.com/alizain/ulid.++UUID can be suboptimal for many uses-cases because:++* It isn't the most character efficient way of encoding 128 bits of randomness+* UUID v1/v2 is impractical in many environments,+    as it requires access to a unique, stable MAC address+* UUID v3/v5 requires a unique seed and produces randomly distributed IDs,+    which can cause fragmentation in many data structures+* UUID v4 provides no other information than randomness,+    which can cause fragmentation in many data structures++Instead, herein is proposed ULID:++* 128-bit compatibility with UUID+* 1.21e+24 unique ULIDs per millisecond+* Lexicographically sortable!+* Canonically encoded as a 26 character text,+    as opposed to the 36 character UUID+* Uses Douglas Crockford's base32 for better efficiency and readability+    (5 bits per character)+* Case insensitive+* No special characters (URL safe)+-}++{-# LANGUAGE DeriveDataTypeable #-}+module Data.ULID (+    ULID(..),+    getULIDTime,+    getULID,+    ulidToInteger,+    ulidFromInteger+) where++import           Control.DeepSeq+import           Data.Binary+import qualified Data.ByteString.Lazy  as LBS+import           Data.Data+import           Data.Hashable+import           Data.Monoid           ((<>))+import           Data.Text as T+import           Data.Time.Clock.POSIX+import           System.IO.Unsafe+import qualified System.Random         as R++import           Data.Binary.Roll+import           Data.ULID.Random+import           Data.ULID.TimeStamp+++{- |+> t <- getULIDTimeStamp+> r <- getULIDRandom+> pure $ ULID t r+-}+data ULID = ULID+  { timeStamp :: !ULIDTimeStamp+  , random    :: !ULIDRandom+  }+  deriving (Eq, Typeable, Data)++instance Ord ULID where+    compare (ULID ts1 _) (ULID ts2 _) = compare ts1 ts2++instance Show ULID where+    show (ULID ts bytes) = (show ts) ++ (show bytes)++instance Read ULID where+    readsPrec _ str = do+        (ts, str2) <- reads str+        (rn, str3) <- reads str2+        return (ULID ts rn, str3)++instance Binary ULID where+    put (ULID ts bytes) = put ts <> put bytes+    get = do+        ts <- get+        bytes <- get+        return $ ULID ts bytes++-- | Because of the strictness annotations,+-- this shouldn't be needed and shouldn't do anything.+-- This is tested and confirmed in the benchmark,+-- but since the work to put it here has already been done+-- it's no harm to leave it in.+instance NFData ULID where+    rnf (ULID ts bytes) = rnf ts `seq` (rnf bytes `seq` ())++instance R.Random ULID where+    randomR _ = R.random -- ignore range+    random g = unsafePerformIO $ do+        t <- getULIDTimeStamp+        let (r, g') = mkULIDRandom g+        return (ULID t r, g')+    randomIO = getULID++instance Hashable ULID where+    hashWithSalt salt ulid = hashWithSalt salt (encode ulid)+++-- | Derive a ULID using a specified time and default random number generator+getULIDTime+  :: POSIXTime  -- ^ Specified UNIX time with millisecond precision+                --   (e.g. 1469918176.385)+  -> IO ULID+getULIDTime t = do+    let t' = mkULIDTimeStamp t+    r <- getULIDRandom+    return $ ULID t' r+++-- | Derive a ULID using the current time and default random number generator+getULID :: IO ULID+getULID = do+    t <- getULIDTimeStamp+    r <- getULIDRandom+    return $ ULID t r+++-- | Convert a ULID to its corresponding (at most) 128-bit Integer.+-- Integer equivalents retain sortable trait (same sort order).+-- This could be useful for storing in a database using a smaller field+-- than storing the shown `Text`,+-- but still human-readable unlike the Binary version.+ulidToInteger :: ULID -> Integer+ulidToInteger = roll.(LBS.unpack).encode+++-- | Convert a ULID from its corresponding 128-bit Integer.+ulidFromInteger+  :: Integer -- ^ The ULID's Integer equivalent, as generated by toInteger+  -> Either Text ULID+ulidFromInteger n+    | n < 0 = Left "Value must not be negative"+    | n > maxValidInteger = Left+        "Value must not be larger than the maximum safe Integer size (128 bits)"+    | otherwise = Right+        . decode . LBS.pack . (unroll 16) $ n  -- 16 bytes = 128 bit+  where+    maxValidInteger :: Integer+    maxValidInteger = (2 ^ 128) - 1
+ src/Data/ULID/Base32.hs view
@@ -0,0 +1,155 @@+-- | Partly adapted from https://hackage.haskell.org/package/crockford+module Data.ULID.Base32+  ( encode+  , encodeChar+  , decode+  , decodeChar+  )+where++import Data.Char+import Data.Maybe+import Text.Read+import Data.Text as T++import Data.ULID.Digits (digits, unDigits)+++-- | Decodes a Crockford base 32 encoded `Text` into an natural number,+-- if possible. Returns `Nothing` if the `Text` is not a valid encoded value.+decodePlain :: Integral i => Text -> Maybe i+decodePlain base32text = do+  numbers <- mapM decodeChar $ T.unpack base32text+  pure $ unDigits 32 numbers+++-- | Encodes an natural number into a Text,+-- using Douglas Crockford's base 32 encoding.+-- Returns `Nothing` if number is negative.+encodePlain :: Integral i => i -> Text+encodePlain =+  T.pack . fmap encodeChar . digits 32+++-- | Decode a character to its corresponding integer+decodeChar :: Integral i => Char -> Maybe i+decodeChar c = case Data.Char.toUpper c of+    '0' -> Just 0+    'O' -> Just 0+    '1' -> Just 1+    'I' -> Just 1+    'L' -> Just 1+    '2' -> Just 2+    '3' -> Just 3+    '4' -> Just 4+    '5' -> Just 5+    '6' -> Just 6+    '7' -> Just 7+    '8' -> Just 8+    '9' -> Just 9+    'A' -> Just 10+    'B' -> Just 11+    'C' -> Just 12+    'D' -> Just 13+    'E' -> Just 14+    'F' -> Just 15+    'G' -> Just 16+    'H' -> Just 17+    'J' -> Just 18+    'K' -> Just 19+    'M' -> Just 20+    'N' -> Just 21+    'P' -> Just 22+    'Q' -> Just 23+    'R' -> Just 24+    'S' -> Just 25+    'T' -> Just 26+    'V' -> Just 27+    'W' -> Just 28+    'X' -> Just 29+    'Y' -> Just 30+    'Z' -> Just 31+    _ -> Nothing+++-- | Encode an integer to its corresponding character+encodeChar :: Integral i => i -> Char+encodeChar i = case i of+    0  -> '0'+    1  -> '1'+    2  -> '2'+    3  -> '3'+    4  -> '4'+    5  -> '5'+    6  -> '6'+    7  -> '7'+    8  -> '8'+    9  -> '9'+    10 -> 'A'+    11 -> 'B'+    12 -> 'C'+    13 -> 'D'+    14 -> 'E'+    15 -> 'F'+    16 -> 'G'+    17 -> 'H'+    18 -> 'J'+    19 -> 'K'+    20 -> 'M'+    21 -> 'N'+    22 -> 'P'+    23 -> 'Q'+    24 -> 'R'+    25 -> 'S'+    26 -> 'T'+    27 -> 'V'+    28 -> 'W'+    29 -> 'X'+    30 -> 'Y'+    31 -> 'Z'+    _  -> '0'+++-- | Source: https://stackoverflow.com/a/29153602+-- The safety for m > length was removed, because that should never happen.+-- If it does, it should crash.+leftpad :: Int -> Text -> Text+leftpad m xs =+  T.replicate (m - T.length xs) "0" <> xs+++-- | Converts all negative numbers to 0+clampZero :: Integral i => i -> i+clampZero x =+  if x < 0+  then 0+  else x+++-- | >>> encode 5 123+-- "0003V"+--+-- | >>> encode (-5) (-123)+-- ""+encode+  :: Integral i+  => Int  -- ^ Overall length of resulting Text+  -> i  -- ^ Natural number to encode+  -> Text  -- ^ 0 padded, Douglas Crockford's base 32 encoded Text+encode width =+  (leftpad $ clampZero width) . encodePlain . clampZero+++-- | >>> decode 5 "0003V"+-- [(123,"")]+decode+  :: Integral i+  => Int  -- ^ Overall length of input Text+  -> Text  -- ^ Base 32 encoded Text+  -> [(i, Text)]  -- ^ List of possible parses+decode width str  | T.length str >= width   = let+                      (crock, remainder) = T.splitAt width str+                    in case decodePlain crock of+                        Nothing -> []+                        Just c  -> [(c, remainder)]+                  | otherwise             = []
− src/Data/ULID/Crockford.hs
@@ -1,20 +0,0 @@-module Data.ULID.Crockford (encode, decode) where
-
-import qualified Codec.Crockford as CR
-import           Text.Read
-
--- source: https://stackoverflow.com/a/29153602
--- I removed the safety for m > length because that should never happen
--- and if it does, I want it to crash!
-leftpad m xs = replicate (m - length xs) '0' ++ xs
-
-encode :: Int -> Integer -> String
-encode width = (leftpad width).(CR.encode)
-
-decode :: Int -> ReadS Integer
-decode width str  | length str >= width   = let
-                    (crock, remainder) = splitAt width str
-                    in case CR.decode crock of
-                        Nothing -> []
-                        Just c  -> [(c, remainder)]
-                  | otherwise             = []
+ src/Data/ULID/Digits.hs view
@@ -0,0 +1,54 @@+-- | Adapted from https://hackage.haskell.org/package/digits+module Data.ULID.Digits+  ( digits+  , unDigits+  )+where++import Data.Maybe (fromJust)+import Data.List (genericTake)+++-- | Returns the digits of a positive integer as a Maybe list, in reverse order+--   or Nothing if a zero or negative base is given+--   This is slightly more efficient than in forward order.+mDigitsRev :: Integral n+    => n         -- ^ The base to use.+    -> n         -- ^ The number to convert to digit form.+    -> Maybe [n] -- ^ Nothing or Just the digits of the number+                 --   in list form, in reverse.+mDigitsRev base i = if base < 1+                    then Nothing -- We do not support zero or negative bases+                    else Just $ dr base i+    where+      dr _ 0 = []+      dr b x = case base of+                1 -> genericTake x $ repeat 1+                _ -> let (rest, lastDigit) = quotRem x b+                     in lastDigit : dr b rest+++-- | Returns the digits of a positive integer as a list, in reverse order.+--   Throws an error if given a zero or negative base.+digitsRev :: Integral n+    => n   -- ^ The base to use.+    -> n   -- ^ The number to convert to digit from.+    -> [n] -- ^ The digits of the number in list from, in reverse.+digitsRev base = fromJust . mDigitsRev base+++-- | Returns the digits of a positive integer as a list.+--   Throws an error if given a zero or negative base.+digits :: Integral n+    => n   -- ^ The base to use (typically 10).+    -> n   -- ^ The number to convert to digit form.+    -> [n] -- ^ Either Nothing or the digits of the number in list form.+digits base = reverse . digitsRev base+++-- | Takes a list of digits, and converts them back into a positive integer.+unDigits :: Integral n+    => n   -- ^ The base to use.+    -> [n] -- ^ The digits of the number in list form.+    -> n   -- ^ The original number.+unDigits base = foldl (\ a b -> a * base + b) 0
src/Data/ULID/Random.hs view
@@ -1,58 +1,78 @@-{-# LANGUAGE DeriveDataTypeable #-}
-module Data.ULID.Random (
-    ULIDRandom,
-    mkCryptoULIDRandom,
-    mkULIDRandom,
-    getULIDRandom
-) where
-
-import           Control.DeepSeq
-import           Control.Monad
-import           Crypto.Random
-import           Data.Binary
-import           Data.Binary.Roll
-import qualified Data.ByteString     as BS
-import           Data.Data
-import           Data.Word
-import           System.Random
-
-
-import qualified Data.ULID.Crockford as CR
-
-
-newtype ULIDRandom = ULIDRandom BS.ByteString
-    deriving (Eq, Typeable, Data)
-
-numBytes = 10 -- 80 bits
-
--- | Generate a ULID Random based on a cryptographically secure random number generator.
--- | see: https://hackage.haskell.org/package/crypto-api-0.13.2/docs/Crypto-Random.html
-mkCryptoULIDRandom :: CryptoRandomGen g => g -> Either GenError (ULIDRandom, g)
-mkCryptoULIDRandom g = do
-    (b, g2) <- genBytes numBytes g
-    return (ULIDRandom b, g2)
-
--- | Generate a ULID Random based on a standard random number generator.
--- | see: https://hackage.haskell.org/package/random-1.1/docs/System-Random.html
-mkULIDRandom :: RandomGen g => g -> (ULIDRandom, g)
-mkULIDRandom g = let
-    (g1, g2) = split g
-    genbytes = (BS.pack) . take numBytes . randoms
-    in (ULIDRandom $ genbytes g, g2)
-
--- | Generate a ULID Random based on the global random number generator.
-getULIDRandom :: IO ULIDRandom
-getULIDRandom = fst <$> mkULIDRandom <$> newStdGen -- Note: the call to newStdGen splits the generator, so this is safe to call multiple times
-
-instance Show ULIDRandom where
-    show (ULIDRandom r) =  (CR.encode) 16.roll.(BS.unpack) $ r
-
-instance Read ULIDRandom where
-    readsPrec _ = map (\(c,r)->(ULIDRandom $ (BS.pack) $ unroll numBytes c, r)) . (CR.decode) 16
-
-instance Binary ULIDRandom where
-    put (ULIDRandom r) = mapM_ put (BS.unpack $ r)
-    get = ULIDRandom <$> (BS.pack) <$> replicateM numBytes get
-
-instance NFData ULIDRandom where
-    rnf (ULIDRandom r) = rnf r
+-- | Helper functions to generate the random part of an ULID+-- either with PRNGs or TRNGs.++{-# LANGUAGE DeriveDataTypeable #-}+module Data.ULID.Random (+    ULIDRandom,+    mkCryptoULIDRandom,+    mkULIDRandom,+    getULIDRandom+) where++import Control.DeepSeq+import Control.Monad+import Crypto.Random+import Data.Binary+import Data.Binary.Roll+import Data.ByteString as BS hiding (split, take)+import Data.Data+import Data.Maybe+import Data.Word+import Data.Text as T hiding (split, take)+import System.Random+++import qualified Data.ULID.Base32 as B32+++-- | Newtype wrapping a `ByteString`+newtype ULIDRandom = ULIDRandom BS.ByteString+    deriving (Eq, Typeable, Data)++instance Show ULIDRandom where+    show (ULIDRandom r) = T.unpack $ B32.encode 16.roll.(BS.unpack) $ r++instance Read ULIDRandom where+  readsPrec _ = fmap+    (\(int, rest) ->+        (ULIDRandom $ BS.pack $ unroll numBytes int, T.unpack rest))+    . (B32.decode $ 16)+    . T.pack++instance Binary ULIDRandom where+    put (ULIDRandom r) = mapM_ put (BS.unpack $ r)+    get = ULIDRandom <$> (BS.pack) <$> replicateM numBytes get++instance NFData ULIDRandom where+    rnf (ULIDRandom r) = rnf r+++numBytes = 10 -- 80 bits+++-- | Generate a `ULIDRandom` based on a cryptographically secure+-- random number generator.+-- See:+-- https://hackage.haskell.org/package/crypto-api-0.13.3/docs/Crypto-Random.html+mkCryptoULIDRandom :: CryptoRandomGen g => g -> Either GenError (ULIDRandom, g)+mkCryptoULIDRandom g = do+  (b, g2) <- genBytes numBytes g+  return (ULIDRandom b, g2)+++-- | Generate a `ULIDRandom` based on a standard random number generator.+-- See:+-- https://hackage.haskell.org/package/random-1.1/docs/System-Random.html+mkULIDRandom :: RandomGen g => g -> (ULIDRandom, g)+mkULIDRandom g = let+  (g1, g2) = split g+  genbytes = (BS.pack) . take numBytes . randoms+  in (ULIDRandom $ genbytes g, g2)+++-- | Generate a ULID Random based on the global random number generator.+getULIDRandom :: IO ULIDRandom+-- | Note: The call to `newStdGen` splits the generator,+-- so this is safe to call multiple times+getULIDRandom =+  fst <$> mkULIDRandom <$> newStdGen
src/Data/ULID/TimeStamp.hs view
@@ -1,44 +1,56 @@-{-# LANGUAGE DeriveDataTypeable #-}
-module Data.ULID.TimeStamp (
-    ULIDTimeStamp,
-    mkULIDTimeStamp,
-    getULIDTimeStamp
-) where
-
-import           Control.DeepSeq
-import           Control.Monad
-import           Data.Binary
-import           Data.Binary.Roll
-import           Data.Data
-import           Data.Time.Clock
-import           Data.Time.Clock.POSIX
-
-import qualified Data.ULID.Crockford   as CR
-
-numBytes = 6 -- 48 bits
-
--- UNIX time in milliseconds
-newtype ULIDTimeStamp = ULIDTimeStamp Integer
-    deriving (Eq, Ord, Typeable, Data)
-
--- | Generate a ULID Timestamp based on a specified time
-mkULIDTimeStamp :: POSIXTime -- ^ The specified UNIX time (seconds) to millisecond precision, e.g. 1469918176.385
-    -> ULIDTimeStamp
-mkULIDTimeStamp = ULIDTimeStamp . round . (*1000)
-
--- | Generate a ULID Timestamp based on current system UNIX time
-getULIDTimeStamp :: IO ULIDTimeStamp
-getULIDTimeStamp = mkULIDTimeStamp <$> getPOSIXTime
-
-instance Show ULIDTimeStamp where
-    show (ULIDTimeStamp i) = CR.encode 10 i
-
-instance Read ULIDTimeStamp where
-    readsPrec _ = map (\(c,r)->(ULIDTimeStamp c, r)) . (CR.decode) 10
-
-instance Binary ULIDTimeStamp where
-    put (ULIDTimeStamp i) = mapM_ put (unroll numBytes i)
-    get = ULIDTimeStamp <$> roll <$> replicateM numBytes get
-
-instance NFData ULIDTimeStamp where
-    rnf (ULIDTimeStamp i) = rnf i
+-- | Custom data type for timestamps (milliseconds since 1970)++{-# LANGUAGE DeriveDataTypeable #-}+module Data.ULID.TimeStamp (+    ULIDTimeStamp,+    mkULIDTimeStamp,+    getULIDTimeStamp+) where++import           Control.DeepSeq+import           Control.Monad+import           Data.Binary+import           Data.Binary.Roll+import           Data.Data+import           Data.Maybe+import           Data.Text as T+import           Data.Time.Clock+import           Data.Time.Clock.POSIX++import qualified Data.ULID.Base32 as B32+++numBytes = 6 -- 48 bits++-- | UNIX time in milliseconds+newtype ULIDTimeStamp = ULIDTimeStamp Integer+    deriving (Eq, Ord, Typeable, Data)++instance Show ULIDTimeStamp where+    show (ULIDTimeStamp i) = T.unpack $ B32.encode 10 i++instance Read ULIDTimeStamp where+    readsPrec _ = fmap+        (\(int, rest)->(ULIDTimeStamp int, T.unpack rest))+        . (B32.decode) 10+        . T.pack++instance Binary ULIDTimeStamp where+    put (ULIDTimeStamp i) = mapM_ put (unroll numBytes i)+    get = ULIDTimeStamp <$> roll <$> replicateM numBytes get++instance NFData ULIDTimeStamp where+    rnf (ULIDTimeStamp i) = rnf i+++-- | Generate a ULID Timestamp based on a specified time+mkULIDTimeStamp+  :: POSIXTime  -- ^ Specified UNIX time with millisecond precision+                -- (e.g. 1469918176.385)+  -> ULIDTimeStamp+mkULIDTimeStamp = ULIDTimeStamp . round . (*1000)+++-- | Generate a ULID Timestamp based on current system UNIX time+getULIDTimeStamp :: IO ULIDTimeStamp+getULIDTimeStamp = mkULIDTimeStamp <$> getPOSIXTime
+ test/Data/ULID/Base32Spec.hs view
@@ -0,0 +1,43 @@+module Data.ULID.Base32Spec where++import Data.ULID.Base32++import Test.Hspec+++-- | Known examples from+-- https://web.archive.org/web/20171219211624/crockfordbase32.codeplex.com+spec :: Spec+spec = do+  describe "encode" $ do+    it "compare to known examples" $ do+        encode 1 1 `shouldBe` "1"+        encode 2 194 `shouldBe` "62"+        encode 11 3838385658376483 `shouldBe` "3D2ZQ6TVC93"+        encode 13 18446744073709551615 `shouldBe` "FZZZZZZZZZZZZ"++    it "compare to known examples (padded)" $ do+        encode 3 1 `shouldBe` "001"+        encode 3 194 `shouldBe` "062"+        encode 15 3838385658376483 `shouldBe` "00003D2ZQ6TVC93"++  describe "decode" $ do+    it "compare to known examples" $ do+        decode 1 "1" `shouldBe` [(1, "")]+        decode 2 "62" `shouldBe` [(194, "")]+        decode 11 "3D2ZQ6TVC93" `shouldBe` [(3838385658376483, "")]+        decode 13 "FZZZZZZZZZZZZ" `shouldBe` [(18446744073709551615, "")]++    it "compare to known examples (padded)" $ do+        decode 3 "001" `shouldBe` [(1, "")]+        decode 3 "062" `shouldBe` [(194, "")]+        decode 15 "00003D2ZQ6TVC93" `shouldBe` [(3838385658376483, "")]++    it "gives remainder text" $ do+        decode 3 "001ABC" `shouldBe` [(1, "ABC")]+        decode 3 "062DEF" `shouldBe` [(194, "DEF")]+        decode 15 "00003D2ZQ6TVC93X1" `shouldBe` [(3838385658376483, "X1")]++    it "gives empty list if invalid" $ do+        decode 3 "U01ABC" `shouldBe` []+        decode 2 "!01DEF" `shouldBe` []
+ test/Data/ULID/RandomSpec.hs view
@@ -0,0 +1,35 @@+module Data.ULID.RandomSpec where++import           Data.Binary+import qualified Data.ByteString.Lazy as LBS++import           Data.ULID.Random++import           Test.Hspec+++spec :: Spec+spec = do+  describe "show/read" $ do+    it "has show/read symmetry" $ do+        a1 <- getULIDRandom+        a2 <- getULIDRandom+        a1 == a2 `shouldBe` False+        read (show a1) `shouldBe` a1+        read (show a2) `shouldBe` a2++    it "has correct show length" $ do+        a1 <- getULIDRandom+        length (show a1) `shouldBe` 16++  describe "encode/decode" $ do+    it "has correct binary length" $ do+        a1 <- getULIDRandom+        LBS.length (encode a1) `shouldBe` 10 -- 80 bit++    it "has encode/decode symmetry" $ do+        a1 <- getULIDRandom+        a2 <- getULIDRandom+        a1 == a2 `shouldBe` False+        decode (encode a1) `shouldBe` a1+        decode (encode a2) `shouldBe` a2
+ test/Data/ULID/TimeStampSpec.hs view
@@ -0,0 +1,46 @@+module Data.ULID.TimeStampSpec where++import           Data.Binary+import qualified Data.ByteString.Lazy as LBS++import           Data.ULID.TimeStamp++import           Test.Hspec+++spec :: Spec+spec = do+  describe "show/read" $ do+    it "works with a known value" $ do+        show (mkULIDTimeStamp 1469918176.385) `shouldBe` "01ARYZ6S41"++    it "has show/read symmetry" $ do+        let a1 = mkULIDTimeStamp 12345+        let a2 = mkULIDTimeStamp 54321+        a1 == a2 `shouldBe` False+        read (show a1) `shouldBe` a1+        read (show a2) `shouldBe` a2++    it "has correct show length" $ do+        let a1 = mkULIDTimeStamp 12345+        length (show a1) `shouldBe` 10++  describe "encode/decode" $ do+    it "has correct binary length" $ do+        let a1 = mkULIDTimeStamp 12345+        LBS.length (encode a1) `shouldBe` 6 -- 48 bit++    it "has encode/decode symmetry" $ do+        let a1 = mkULIDTimeStamp 12345+        let a2 = mkULIDTimeStamp 54321+        a1 == a2 `shouldBe` False+        decode (encode a1) `shouldBe` a1+        decode (encode a2) `shouldBe` a2++    it "encodes MSB first" $ do+        let a1 = mkULIDTimeStamp 12345+        let e1 = encode a1+        -- This works because the value is small,+        -- so the MSB for this value should be 0+        LBS.head e1 `shouldBe` 0+        LBS.last e1 `shouldNotBe` 0
+ test/Data/ULIDSpec.hs view
@@ -0,0 +1,177 @@+module Data.ULIDSpec where++import           Control.Concurrent+import           Control.Monad        (replicateM)+import           Data.Binary+import qualified Data.ByteString.Lazy as LBS+import           Data.Char+import           Data.Hashable+import           Data.List            (nub, sort)+import qualified System.Random        as R++import           Data.ULID++import           Test.Hspec+++spec :: Spec+spec = do+  describe "ulid capabilities" $ do+    it "binary length 128-bit" $ do+        a1 <- getULID+        LBS.length (encode a1) `shouldBe` 16 -- 128 bit++    it "is lexicographically sortable" $ do+        u1 <- getULID+        threadDelay 1000+        u2 <- getULID+        threadDelay 1000+        u3 <- getULID+        threadDelay 1000+        u4 <- getULID+        threadDelay 1000+        let l = [show u3, show u2, show u4, show u1]+        let l' = sort l+        l' `shouldBe` [show u1, show u2, show u3, show u4]++        -- make sure it works in internal representation too :)+        let ul = [u3, u2, u4, u1]+        let ul' = sort ul+        ul' `shouldBe` [u1, u2, u3, u4]++    it "is encoded as 26 character text" $ do+        u1 <- getULID+        length (show u1) `shouldBe` 26++    it "is case-insensitive" $ do+        u1 <- getULID+        let u2 = read (map toLower (show u1))+        let u3 = read (map toUpper (show u1))+        u1 `shouldBe` u2+        u1 `shouldBe` u3++    it "no special characters" $ do+        u1 <- getULID+        filter (not.isAlphaNum) (show u1) `shouldBe` []++  describe "ulid" $ do+    it "starts with 0 (at least for the foreseeable future)" $ do+        u1 <- getULID+        head (show u1) `shouldBe` '0'++    it "generates unique ulids in default configuration" $ do+        let ops = 1000+        ulids <- replicateM ops getULID+        -- Verify uniqueness+        let n' = length $ nub ulids+        n' `shouldBe` ops++  describe "encode/decode" $ do+    it "has encode/decode symmetry" $ do+        a1 <- getULID+        a2 <- getULID+        a1 == a2 `shouldBe` False+        decode (encode a1) `shouldBe` a1+        decode (encode a2) `shouldBe` a2+        encode a1 `shouldNotBe` encode a2++    it "encodes MSB first" $ do+        a1 <- getULIDTime 12345+        let e1 = encode a1+        -- This works because the time value is small,+        -- and time is sequences first,+        -- so the MSB for this value should be 0+        LBS.head e1 `shouldBe` 0+        LBS.last e1 `shouldNotBe` 0++  describe "random" $ do+    it "works in IO" $ do+        u1 <- (R.randomIO :: IO ULID)+        u2 <- (R.randomIO :: IO ULID)+        u1 `shouldNotBe` u2++    it "works with randomgen" $ do+        g <- R.getStdGen+        let (u1, g') = R.random g :: (ULID, R.StdGen)+        let (u2, _) = R.random g' :: (ULID, R.StdGen)+        u1 `shouldNotBe` u2++  describe "hash" $ do+    -- | The general contract of hashWithSalt is:+    -- If two values are equal according to the == method,+    -- then applying the hashWithSalt method on each of the two values+    -- must produce the same integer result+    -- if the same salt is used in each case.+    it "produces same hash for equal ulids" $ do+        u1 <- getULID+        let u2 = (read (show u1)) :: ULID+        let salt = 12345+        hashWithSalt salt u1 `shouldBe` hashWithSalt salt u2++    -- | It is not required that if two values are unequal+    -- according to the == method,+    -- then applying the hashWithSalt method on each of the two values+    -- must produce distinct integer results.+    -- However, the programmer should be aware+    -- that producing distinct integer results for unequal values+    -- may improve the performance of hashing-based data structures.+    it "produces different hash for nonequals ulids" $ do+        u1 <- getULID+        u2 <- getULID+        let salt = 12345+        -- this could rarely fail due to hash nature+        hashWithSalt salt u1 `shouldNotBe` hashWithSalt salt u2++    -- | This method can be used to compute different hash values+    -- for the same input by providing a different salt+    -- in each application of the method.+    -- This implies that any instance that defines `hashWithSalt` must+    -- make use of the salt in its implementation.+    it "produces different hash for equals ulids with different salt" $ do+        u1 <- getULID+        let u2 = (read (show u1)) :: ULID+        let salt = 12345+        hashWithSalt salt u1 `shouldBe` hashWithSalt salt u2+        let salt2 = 54321+        hashWithSalt salt u1 `shouldNotBe` hashWithSalt salt2 u2++  describe "to/from integer" $ do+      it "is sortable" $ do+            u1 <- getULID+            threadDelay 1000+            u2 <- getULID+            threadDelay 1000+            u3 <- getULID+            threadDelay 1000+            u4 <- getULID+            threadDelay 1000+            let+              l = [ ulidToInteger u3+                  , ulidToInteger u2+                  , ulidToInteger u4+                  , ulidToInteger u1+                  ]+              l' = sort l+            l' `shouldBe` [ ulidToInteger u1+                          , ulidToInteger u2+                          , ulidToInteger u3+                          , ulidToInteger u4+                          ]++      it "has to/from symmetry" $ do+            a1 <- getULID+            a2 <- getULID+            a1 == a2 `shouldBe` False+            ulidFromInteger (ulidToInteger a1) `shouldBe` (Right a1)+            ulidFromInteger (ulidToInteger a2) `shouldBe` (Right a2)+            ulidToInteger a1 `shouldNotBe` ulidToInteger a2++      it "handles out-of-range integer" $ do+            a1 <- getULID+            ulidFromInteger (negate (ulidToInteger a1))+              `shouldBe` Left "Value must not be negative"+            ulidFromInteger (-1)+              `shouldBe` Left "Value must not be negative"+            ulidFromInteger (2 ^ 128)+              `shouldBe` Left "Value must not be larger than \+                  \the maximum safe Integer size (128 bits)"
test/Spec.hs view
@@ -1,3 +1,3 @@ {-# OPTIONS_GHC -F -pgmF hspec-discover #-}--- see https://github.com/hspec/hspec-example/blob/master/strip.cabal+-- See https://github.com/hspec/hspec-example/blob/master/strip.cabal -- runhaskell -isrc -itest test/Spec.hs
ulid.cabal view
@@ -1,50 +1,64 @@ name:                ulid-version:             0.2.0.0-synopsis:            Implementation of ULID, lexicographically sortable unique identifiers-description:         Implementation of alizain's ULID identifier. Canonically encoded as a 26 character string, as opposed to the 36 character UUID. -                     Uses Crockford's base32 for better efficiency and readability (5 bits per character)-homepage:            https://github.com/steven777400/ulid+version:             0.3.0.0+synopsis:            Implementation of ULID - Universally Unique+                     Lexicographically Sortable Identifier+description:         Implementation of Alizain Feerasta's ULID specification.+                     A 26 character string identifier,+                     as opposed to the 36 character UUID string.+                     Uses Douglas Crockford's base 32 encoding+                     for better efficiency and readability+                     (5 bits per character).+homepage:            https://github.com/ad-si/ulid license:             BSD3 license-file:        LICENSE author:              Steve Kollmansberger-maintainer:          steve@kolls.net+maintainer:          ulid@ad-si.com copyright:           2017 Steve Kollmansberger-category:            Data+category:            Data, Codec, Database build-type:          Simple extra-source-files:  README.md cabal-version:       >=1.10 + library   hs-source-dirs:      src   exposed-modules:     Data.ULID-                    ,  Data.ULID.Crockford+                    ,  Data.ULID.Base32+                    ,  Data.ULID.Digits                     ,  Data.ULID.Random                     ,  Data.ULID.TimeStamp-  other-modules:       Data.Binary.Roll                  +  other-modules:       Data.Binary.Roll   build-depends:       base >= 4.7 && < 5-                    ,  crockford-                    ,  time-                    ,  crypto-api-                    ,  random-                    ,  bytestring                     ,  binary+                    ,  bytestring+                    ,  crypto-api                     ,  deepseq                     ,  hashable+                    ,  random+                    ,  text+                    ,  time   default-language:    Haskell2010+  default-extensions:  OverloadedStrings + executable ulid-exe   hs-source-dirs:      app   main-is:             Main.hs   ghc-options:         -threaded -rtsopts -with-rtsopts=-N   build-depends:       base                      , ulid-                     , crypto-api                     +                     , crypto-api   default-language:    Haskell2010 + test-suite ulid-test   type:                exitcode-stdio-1.0   hs-source-dirs:      test   main-is:             Spec.hs+  other-modules:       Data.ULID.Base32Spec+                     , Data.ULID.RandomSpec+                     , Data.ULID.TimeStampSpec+                     , Data.ULIDSpec   build-depends:       base                      , hspec                      , ulid@@ -54,18 +68,23 @@                      , hashable   ghc-options:         -threaded -rtsopts -with-rtsopts=-N   default-language:    Haskell2010+  default-extensions:  OverloadedStrings + benchmark ulid-bench-    type:                exitcode-stdio-1.0-    hs-source-dirs:      bench-    main-is:             Main.hs-    build-depends:       base-                      ,  ulid                     -                      ,  time-                      ,  text-                      ,  format-numbers-                      ,  deepseq+  type:                exitcode-stdio-1.0+  hs-source-dirs:      bench+  main-is:             Main.hs+  build-depends:       base+                    ,  ulid+                    ,  time+                    ,  text+                    ,  format-numbers+                    ,  deepseq+  default-language:    Haskell2010+  default-extensions:  OverloadedStrings + source-repository head   type:     git-  location: https://github.com/steven777400/ulid.git+  location: https://github.com/ad-si/ulid.git