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