packages feed

locators-0.3.0.5: lib/Data/Locator/Latin25.hs

{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Data.Locator.Latin25 (
    Latin25 (..),
    toLatin25,
    fromLatin25,
    hashStringToLatin25,
) where

import Prelude hiding (toInteger)

import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as S
import Numeric (showIntAtBase)

import Data.Locator.Common
import Data.Locator.Hashes (padWithZeros)

{- |
A symbol set with twenty-five visually distinct characters.

These are not protected against similar pronounciations; if you need to
read your identifiers /aloud/ use 'English16' instead.
-}

{-

    --  | Two       -- Obvious conflict with Z
    --  | Five      -- Obvious conflict with S
    --  | Six       -- Too close to G
    --  | Bravo     -- Too close to 8
    --  | Delta     -- Shape of D too close to O
    --  | Foxtrot   -- Excluded because too close to E
    --  | India     -- Too close to 1 and J
    --  | Oscar     -- Obvious conflict with 0
    --  | Quebec    -- The tail on Q is too easy to miss, thereby colliding with O/0
    --  | Romeo     -- Dropped in favour of P
    --  | Uniform   -- Too close to V

-}
data Latin25
    = -- | @\'0\'@ /0th/
      Zero'
    | -- | @\'1\'@ /1st/
      One'
    | -- | @\'3\'@ /2nd/
      Three'
    | -- | @\'4\'@ /3rd/
      Four'
    | -- | @\'7\'@ /4th/
      Seven'
    | -- | @\'8\'@ /5th/
      Eight'
    | -- | @\'9\'@ /6th/
      Nine'
    | -- | @\'A\'@ /7th/
      Alpha'
    | -- | @\'C\'@ /8th/
      Charlie'
    | -- | @\'E\'@ /9th/
      Echo'
    | -- | @\'G\'@ /10th/
      Golf'
    | -- | @\'H\'@ /11th/
      Hotel'
    | -- | @\'J\'@ /12th/
      Juliet'
    | -- | @\'K\'@ /13th/
      Kilo'
    | -- | @\'L\'@ /14th/
      Lima'
    | -- | @\'M\'@ /15th/
      Mike'
    | -- | @\'N\'@ /16th/
      November'
    | -- | @\'P\'@ /17th/
      Papa'
    | -- | @\'S\'@ /18th/
      Sierra'
    | -- | @\'T\'@ /19th/
      Tango'
    | -- | @\'V\'@ /20th/
      Victor'
    | -- | @\'W\'@ /21st/
      Whiskey'
    | -- | @\'X\'@ /22nd/
      XRay'
    | -- | @\'Y\'@ /23rd/
      Yankee'
    | -- | @\'Z\'@ /24th/
      Zulu'
    deriving (Eq, Ord, Enum, Bounded)

instance Locator Latin25 where
    locatorToDigit x =
        case x of
            Zero' -> '0'
            One' -> '1'
            Three' -> '3'
            Four' -> '4'
            Seven' -> '7'
            Eight' -> '8'
            Nine' -> '9'
            Alpha' -> 'A'
            Charlie' -> 'C'
            Echo' -> 'E'
            Golf' -> 'G'
            Hotel' -> 'H'
            Juliet' -> 'J'
            Kilo' -> 'K'
            Lima' -> 'L'
            Mike' -> 'M'
            November' -> 'N'
            Papa' -> 'P'
            Sierra' -> 'S'
            Tango' -> 'T'
            Victor' -> 'V'
            Whiskey' -> 'W'
            XRay' -> 'X'
            Yankee' -> 'Y'
            Zulu' -> 'Z'

    digitToLocator :: Char -> Latin25
    digitToLocator c =
        case c of
            '0' -> Zero'
            '1' -> One'
            '3' -> Three'
            '4' -> Four'
            '7' -> Seven'
            '8' -> Eight'
            '9' -> Nine'
            'A' -> Alpha'
            'C' -> Charlie'
            'E' -> Echo'
            'G' -> Golf'
            'H' -> Hotel'
            'J' -> Juliet'
            'K' -> Kilo'
            'L' -> Lima'
            'M' -> Mike'
            'N' -> November'
            'P' -> Papa'
            'S' -> Sierra'
            'T' -> Tango'
            'W' -> Whiskey'
            'V' -> Victor'
            'X' -> XRay'
            'Y' -> Yankee'
            'Z' -> Zulu'
            _ -> error "Illegal digit"

instance Show Latin25 where
    show x = [c]
      where
        c = locatorToDigit x

--

{- |
Given a number, convert it to a string in the Latin25 base 25 symbol
alphabet. This is useful for primary keys and object identifiers that you
need to scan for in log output, for example.
-}
toLatin25 :: Int -> String
toLatin25 x =
    showIntAtBase 25 (represent Zulu') x ""

--

-- | Given a number encoded in Locator16, convert it back to an integer.
fromLatin25 :: String -> Int
fromLatin25 ss =
    foldl (multiply Zulu') 0 ss

--

{- |
Take an arbitrary sequence of bytes, hash it with SHA1, then format as a
short @limit@-long Latin25 string.

>>> hashStringToLatin25 5 "You'll get used to it. Or, you'll have a psychotic episode"
SG8XP

17 characters is the widest hash you can request.
-}
hashStringToLatin25 :: Int -> ByteString -> ByteString
hashStringToLatin25 limit s'
    | limit > 17 = error "Can only request a maximum width of 17, sorry"
    | otherwise =
        let s = S.unpack s'
            n = digest s -- SHA1 hash
            r = mod n upperBound -- trim to specified number of base 25 chars
            x = toLatin25 r -- express in Latin25
            b' = S.pack (padWithZeros limit x)
         in b'
  where
    upperBound = 25 ^ limit