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