packages feed

hscuid (empty) → 1.0.0

raw patch · 5 files changed

+210/−0 lines, 5 filesdep +Win32dep +basedep +containerssetup-changed

Dependencies added: Win32, base, containers, formatting, hostname, hscuid, random, text, time, transformers, unix

Files

+ LICENSE view
@@ -0,0 +1,21 @@+The MIT License (MIT)++Copyright (c) 2015 Daniel Buckmaster++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hscuid.cabal view
@@ -0,0 +1,43 @@+-- Initial hscuid.cabal generated by cabal init.  For further +-- documentation, see http://haskell.org/cabal/users-guide/++name:                hscuid+version:             1.0.0+synopsis:            Collision-resistant IDs+description:         CUIDs are designed for horizontal scalability and collision+                     resistance. Read more about them at <https://usecuid.org>.+homepage:            https://github.com/eightyeight/hscuid+license:             MIT+license-file:        LICENSE+author:              Daniel Buckmaster+maintainer:          dan.buckmaster@gmail.com+-- copyright:           +category:            Web+build-type:          Simple+cabal-version:       >=1.8++library+  hs-source-dirs:      lib+  exposed-modules:     Web.Cuid+  build-depends:       base ==4.*,+                       formatting ==6.*,+                       time ==1.*,+                       random ==1.*,+                       transformers ==0.4.*,+                       hostname ==1.*,+                       text ==1.*+  if os(windows)+    build-depends:     Win32 ==2.*+    extra-libraries:   kernel32+  else+    build-depends:     unix ==2.*+  ghc-options:         -Wall++test-suite hscuid-test+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             Test.hs+  build-depends:       base ==4.*,+                       containers ==0.5.*,+                       hscuid+  ghc-options:         -Wall -rtsopts -O2
+ lib/Web/Cuid.hs view
@@ -0,0 +1,116 @@+{-# LANGUAGE CPP, ForeignFunctionInterface #-}++{-|+Stability: stable+Portability: portable++You can generate a new CUID inside any IO-enabled monad using this module's+one exported function:++>>> cuid <- newCuid+>>> print cuid+"ciaafthr00000qhpm0jp81gry"+-}+module Web.Cuid (+    Cuid, newCuid+) where++import Control.Monad (liftM)+import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Char (ord)+import Data.Monoid (mconcat, (<>))+import Data.IORef (IORef, newIORef, atomicModifyIORef')+import Data.String (fromString)+import Data.Text (Text)+import Data.Time.Clock.POSIX (getPOSIXTime)+import Formatting (Format, base, fitRight, sformat, left, (%.))+import Network.HostName (getHostName)+import System.IO.Unsafe (unsafePerformIO)+import System.Random (randomRIO)++#if defined(mingw32_HOST_OS)+import System.Win32 (ProcessId, failIfZero)+#else+import System.Posix.Process (getProcessID)+#endif++-- | 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 = concatM [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 (sformat number . millis) getPOSIXTime++    -- To avoid collisions on the same machine, add a global counter to each ID.+    count = liftM (sformat numberPadded) (postIncrement counter)++    -- To avoid collisions between separate machines, generate a 'fingerprint'+    -- from details which are hopefully unique to this machine - PID and hostname.+    fingerprint = do+        pid <- getPid+        hostname <- getHostName+        let hostSum = 36 + length hostname + sum (map ord hostname)+            twoOfNum = sformat (fitRight 2 %. number)+        return (twoOfNum pid <> twoOfNum hostSum)++    -- And some randomness for good measure. Note that System.Random is not a+    -- source of crypto-strength randomness.+    random = liftM (sformat numberPadded) (randomRIO (0, maxValue))++    -- Evaluate IO actions and concatenate their results.+    concatM actions = liftM mconcat (liftIO $ sequence actions)++    -- POSIX time library gives the result in fractional seconds.+    millis posix = round (posix * 1000)++-- CUID calls for a globally incrementing counter per machine. This is ugly,+-- but it satisfies the requirement.+counter :: IORef Int+counter = unsafePerformIO (newIORef 0)+-- Don't want two different counters being created because of inlining.+-- For more info: https://wiki.haskell.org/Top_level_mutable_state+{-# NOINLINE counter #-}++-- Increment the counter, and return the value before it was incremented.+postIncrement :: MonadIO m => IORef Int -> m Int+postIncrement c = liftIO (atomicModifyIORef' c incrementAndWrap) where+    incrementAndWrap count = (succ count `mod` maxValue, count)++-- These constants are to do with number formatting.+formatBase, blockSize, maxValue :: Int+formatBase = 36+blockSize = 4+maxValue = formatBase ^ blockSize++-- Number formatters for converting to the correct base and padding.+number, numberPadded :: Format Text (Int -> Text)+(number, numberPadded) = (toBase, toBlockSize %. toBase) where+    toBlockSize = left blockSize '0'+    toBase = base formatBase++-- Get the ID of the current process. This function has a platform-specific+-- implementation. Fun times.+getPid :: MonadIO m => m Int++#if defined(mingw32_HOST_OS)++foreign import stdcall unsafe "windows.h GetCurrentProcessId"+    c_GetCurrentProcessId :: IO ProcessId++getCurrentProcessId :: IO ProcessId+getCurrentProcessId = failIfZero "GetCurrentProcessId" c_GetCurrentProcessId++getPid = liftM fromIntegral (liftIO getCurrentProcessId)++#else++getPid = liftM fromIntegral (liftIO getProcessID)++#endif
+ test/Test.hs view
@@ -0,0 +1,28 @@+module Main where++import Data.Set (empty, insert, size)+import Control.Monad (foldM)+import System.Exit (ExitCode(..), exitWith)++import Web.Cuid (newCuid)++main :: IO ()+main = do+    result <- runCollisionTest 1200000+    case result of+        Nothing -> exitWith ExitSuccess+        Just n -> do+            print ("Number of collisions: " ++ show n)+            exitWith (ExitFailure 1)++runCollisionTest :: Int -> IO (Maybe Int)+runCollisionTest numberOfCuids = do+    let accumulate set _i = do+            cuid <- newCuid+            return $! insert cuid set+    -- Generate a set containing a bunch of generated CUIDs.+    set <- foldM accumulate empty [0..numberOfCuids-1]+    -- If every element was unique, the set will have the same size as the input.+    return $ if size set == numberOfCuids+        then Nothing+        else Just (numberOfCuids - size set)