packages feed

pixelated-avatar-generator-0.1.3: app/Main.hs

module Main where

import Console.Options
import Control.Concurrent.Async (mapConcurrently)
import Data.Char (isDigit)
import Data.List (nub)
import Data.Maybe (fromMaybe)
import Data.Monoid ((<>))
import Data.Version (makeVersion)
import Graphics.Avatars.Pixelated
import System.Random (randomIO)

main :: IO ()
main = defaultMain $ do
  -- Define program information
  programName "pixelated-avatar-generator"
  programDescription "Generates pixelated avatar images"
  programVersion $ makeVersion [0,1,2]

  -- Gather program flags and arguments
  flagS <- flagParam
    (FlagLong "scaling-factor" <> FlagDescription scalingFlagDescription)
    (FlagRequired scaleParser)
  firstFile <- argument "FILEPATH" Right
  tailFiles <- remainingArguments "FILEPATHS"

  -- Perform the functionality of the program
  action $ \toParam -> do
    let scalingFactor = fromMaybe defaultScalingFactor (toParam flagS)
    let paths = nub $ toParam firstFile : toParam tailFiles
    _ <- mapConcurrently (saveRandomAvatar scalingFactor) paths
    putStrLn $ createSuccessMessage paths

-- | The default avatar scaling factor that is used when the user does not
-- specify a custom scaling factor via the "--scaling-factor" flag.
defaultScalingFactor :: Int
defaultScalingFactor = 32

-- | The description of the "--scaling-factor" flag.
scalingFlagDescription :: String
scalingFlagDescription = unwords
  [ "Use a custom scaling factor. The scaling factor is multiplied by 8 to get"
  , "the dimensions of the image. For example, a scaling factor of 4 would"
  , "yield a 32x32px image. The default scaling factor is 32."
  ]

-- | Prints a success message noting the number of images created.
--
-- >>> createSuccessMessage ["1.png", "2.png"]
-- "Successfully created 2 avatars."
createSuccessMessage :: [FilePath] -> String
createSuccessMessage paths = message
  where
    message = unwords ["Successfully created", numberOfAvatars, "avatars."]
    numberOfAvatars = (show . length) paths

-- | Creates and saves a random avatar at the given filepath using the given
-- scaling factor.
saveRandomAvatar :: Int -> FilePath -> IO ()
saveRandomAvatar factor path = do
  seed <- show <$> (randomIO :: IO Double)
  saveCustomAvatar factor path seed

-- | Creates and saves an avatar at the given filepath using the given seed
-- string and the given scaling factor.
saveCustomAvatar :: Int -> FilePath -> String -> IO ()
saveCustomAvatar factor path string = do
  let seed = createSeed string
  let avatar = generateAvatar seed
  let avatarScaled = scaleAvatar factor avatar
  saveAvatar avatarScaled path

-- | Attempts to parse a scaling factor, which must be a non-positive integer
-- (1, 2, 3, ...).
--
-- >>> scaleParser "1"
-- Right 1
-- >>> scaleParser "0"
-- Left "The given String is not a positive integer"
-- >>> scaleParser "-1"
-- Left "The given String is not a positive integer"
scaleParser :: String -> Either String Int
scaleParser string
    | isPosInt string = Right ((read :: String -> Int) string)
    | otherwise       = Left "The given String is not a positive integer"
  where
    isPosInt s   = isNaturalNum s && isNotZero s
    isNaturalNum = all isDigit
    isNotZero s  = not $ all (== '0') s