Stability: stable
Portability: portable

You can generate a new CUID or slug inside any IO-enabled monad using 'newCuid'
and 'newSlug'.
module Web.Cuid (
    Cuid, newCuid,
    Slug, newSlug
) where

import Control.Monad (liftM)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.String (fromString)
import Data.Text (Text)

#if !MIN_VERSION_base(4,8,0)
import Data.Monoid (Monoid, mconcat)

import Web.Cuid.Internal

-- | Convenience type so that you don't have to import Text downstream. Note that
-- this is strict Text.
type Cuid = Text

-- | Generate a new random CUID.
newCuid :: MonadIO m => m Cuid
newCuid = concatResults [c, time, count, fingerprint, random, random] where
    -- The CUID starts with a letter so it's usable in HTML element IDs.
    c = return (fromString "c")

    -- The second chunk is the timestamp. Note that this means it is possible
    -- to determine the time a particular CUID was created.
    time = liftM formatNumber getTimestamp

    -- To avoid collisions in the same process, add a global counter to each ID.
    count = liftM formatPadded getNextCount

    -- To avoid collisions between separate machines, generate a 'fingerprint'
    -- from details which are hopefully unique to this machine - PID and hostname.
    fingerprint = return myFingerprint

    -- And some actual randomness for good measure.
    random = liftM formatPadded getRandomValue

-- | A Slug is not a Cuid. But it is also a strict Text.
type Slug = Text

-- | A slug is a shorter piece of text generated using some of the same
-- techniques as CUIDs.
newSlug :: MonadIO m => m Slug
newSlug = concatResults [time, count, fingerprint, random] where
    time = liftM formatShort getTimestamp
    count = liftM formatPadded getNextCount
    random = liftM formatShort getRandomValue
    fingerprint = return myFingerprint

-- Evaluate IO actions and concatenate their results.
concatResults :: (MonadIO m, Monoid a) => [IO a] -> m a
concatResults actions = liftM mconcat (liftIO $ sequence actions)