orgstat-0.0.3: src/OrgStat/Util.hs
{-# LANGUAGE RankNTypes #-}
-- | Different utilities
module OrgStat.Util
( dropLowerOptions
, dropEnd
, addLocalTime
, fromJustM
, parseColour
, hashColour
, (??~)
) where
import Control.Lens (ASetter, ix)
import Data.Aeson.TH (defaultOptions)
import Data.Aeson.Types (Options, fieldLabelModifier)
import Data.Char (isLower, toLower)
import Data.Colour (Colour)
import Data.Colour.CIE (luminance)
import Data.Colour.SRGB (RGB (..), sRGB24, toSRGBBounded)
import Data.Hashable (hashWithSalt)
import Data.List (nub)
import Data.List ((!!))
import Data.Time (LocalTime (..), addUTCTime, localTimeToUTC, utc,
utcToLocalTime)
import Universum
-- | JSON/Yaml TH modifier. Each field of type "aoeuKek" turns into
-- "kek". Placed here because it can't be defined near json TH
-- deriving (ghc restriction).
dropLowerOptions :: Options
dropLowerOptions =
defaultOptions
{ fieldLabelModifier = \x -> (dropWhile isLower x) & ix 0 %~ toLower
}
fromJustM :: Monad m => m b -> m (Maybe b) -> m b
fromJustM e m = maybe e pure =<< m
-- | Drops n items from the end.
dropEnd :: Int -> [x] -> [x]
dropEnd n xs = take (length xs - n) xs
-- | Same as 'addUTCTime', but for local
addLocalTime :: (Integral n) => n -> LocalTime -> LocalTime
addLocalTime n a =
utcToLocalTime utc $ fromIntegral n `addUTCTime` localTimeToUTC utc a
-- | Parses colour from format '#rrggbb' or just 'rrggbb'
parseColour :: forall s a. (ToString s, Floating a, Ord a) => s -> Maybe (Colour a)
parseColour (toString -> s) = toColour $ dropWhile (== '#') s
where
toColour [r1,r2,g1,g2,b1,b2] = do
r <- toWord8 r1 r2
g <- toWord8 g1 g2
b <- toWord8 b1 b2
pure $ sRGB24 r g b
toColour _ = Nothing
toWord8 a b = readMaybe $ "0x"++[a,b]
-- | Generates a colour given salt and anything hashable. Doesn't return
-- too dark or too light colors.
hashColour :: (Hashable a) => Int -> a -> Colour Double
hashColour salt item = colours !! (hashWithSalt salt item `mod` length colours)
where
broken = error "Util#hashColour is broken"
range = [-5,0,5]
colours = filter (\c -> luminance c < 0.8 && luminance c > 0.06) mutate
ac :: Word8 -> Integer -> Word8
ac a b = fromIntegral $ (fromIntegral a) + b `mod` 255
mutate = nub $ concatMap
(\(r,g,b) -> [sRGB24 (r `ac` i) (g `ac` j) (b `ac` k)
| i <- range, j <- range, k <- range])
coloursBasic
coloursBasic =
map (toTriple . fromMaybe broken . parseColour @[Char] @Double) popularColours
toTriple c = let RGB{..} = toSRGBBounded c in (channelRed, channelGreen, channelBlue)
popularColours :: [[Char]]
popularColours = ["00FF00","01FFFE","FFA6FE","FFDB66","006401","010067","95003A","007DB5","FF00F6","FFEEE8","774D00","90FB92","0076FF","D5FF00","FF937E","6A826C","FF029D","FE8900","7A4782","7E2DD2","85A900","FF0056","A42400","00AE7E","683D3B","BDC6FF","263400","BDD393","00B917","9E008E","001544","C28C9F","FF74A3","01D0FF","004754","E56FFE","788231","0E4CA1","91D0CB","BE9970","968AE8","BB8800","43002C","DEFF74","00FFC6","FFE502","620E00","008F9C","98FF52","7544B1","B500FF","00FF78","FF6E41","005F39","6B6882","5FAD4E","A75740","A5FFD2","FFB167","009BFF","E85EBE"];
-- | Maybe setter that does nothing on Nothing.
(??~) :: ASetter s s a b -> Maybe b -> s -> s
(??~) _ Nothing = identity
(??~) l (Just k) = l .~ k