packages feed

piet-0.1: sources/Main.hs

module Main
	( main
	) where

import Control.Monad.Error
import Data.List
import Data.Version (showVersion)
import Language.Piet
import qualified Paths_piet as Paths (version)
import System.Console.GetOpt
import System.Environment
import System.Exit
import System.IO

-- | Main function.
main :: IO ()
main = do
	args	<- getArgs
	case parseArguments args of
		Left err	-> do
			putStrLn err
			printUsage
			exitWith $ ExitFailure 1
		Right opts	-> do
			actualMain opts
			exitWith ExitSuccess

-- | Main function without the option-parsing part.
actualMain :: Options -> IO ()
actualMain Help		= printUsage
actualMain Version	= printVersion
actualMain opts@Run { }	= case program opts of
	Nothing		-> do
		putStrLn "No program given"
		printUsage
	Just file	-> do
		loadresult	<- imgFromFile (codelSize opts) file
		case loadresult of
			Left err	-> do
				putStr "Unable to load image, imlib2 issued this error: "
				print err
				exitWith $ ExitFailure 2
			Right img	-> do
				let prog	= compile img

				hSetBuffering stdin  NoBuffering
				hSetBuffering stdout NoBuffering
				hFlush stdout
				
				response	<- runPietMonad readCallback
					printCallback
					(logCallback (verbosity opts))
					(interpret prog)

				case response of
					Left err	-> do
						putStrLn err
						exitWith $ ExitFailure 3
					Right _		-> return ()

-- | Print program usage to STDOUT.
printUsage :: IO ()
printUsage = getProgName >>= putStrLn . ((flip usageInfo) options) . versionise

-- | Print program name and version to STDOUT.
printVersion :: IO ()
printVersion = getProgName >>= putStrLn . versionise

-- | Append /-version/ to a program name, where /version/ is
-- the actual version of this cabal package.
versionise :: String -> String
versionise = (++ "-" ++ showVersion Paths.version)

-- | Callback function to read a 'PietNumber' (i. e. a parsed line) or a
-- 'PietChar' from STDIN.
readCallback :: PietType -> IO Int
readCallback PietNumber = getLine >>= maybe (readCallback PietNumber) return . readMaybe
readCallback PietChar   = fromEnum `liftM` getChar

-- | Callback to print a 'PietType'.
printCallback :: PietType -> Int -> IO ()
printCallback PietNumber = putStr . show
printCallback PietChar   = putChar . toEnum

-- | Callback to manage log messages.
logCallback :: LogLevel -> LogLevel -> String -> IO ()
logCallback threshold level msg = when (level >= threshold) $ do
	let prefixedMsg = show level ++ ' ' : msg
	putStrLn prefixedMsg

-- | Build 'Options' and - if specified - a program file to execute
-- from a list of 'String's (the arguments).
parseArguments :: [String] -> Either String Options
parseArguments args = do
	let (optCBs, remaining, errors)	= getOpt Permute options args

	unless (null errors) $ fail (unlines errors)
	opts	<- foldM (flip ($)) defaultOptions optCBs
	
	case remaining of
		[]	-> return opts
		[prog]	-> case opts of
			Run { }	-> return (opts { program = Just prog } )
			_	-> return opts
		_	-> fail "please specify exactly one program"

-- | Type bundling command line options.
data Options
	= Help		-- ^ Don't run anything, just print a help text
	| Version	-- ^ Don't run anything, print program name and version
	| Run
		{ program	:: Maybe FilePath	-- ^ Program to run
		, codelSize	:: Maybe Int		-- ^ Explicitly specify codel size
		, verbosity	:: LogLevel
		}	-- ^ Run a given program
	deriving (Show, Eq, Ord)

-- | The default 'Options'.
defaultOptions :: Options
defaultOptions = Run
	{ program	= Nothing
	, codelSize	= Nothing
	, verbosity	= Info
	}

-- | List of command line options accepted by this interpreter.
options :: [OptDescr (Options -> (Either String Options))]
options =
	[ Option ['h', '?'] ["help"]
		(NoArg (const (Right Help)))
		"print usage information and exit"
	, Option [] ["version"]
		(NoArg (const (Right Version)))
		"print version information and exit"
	, Option ['c'] ["codels"]
		(ReqArg (\arg opt -> maybe
			(Left "codel size must be a number")
			(\x -> case opt of
				Run { }	-> Right (opt { codelSize = Just x })
				_	-> Right opt)
			(readMaybe arg)) "LENGTH")
		"codel length (the codel size will be LENGTH^2)"
	, Option ['v'] ["verbose"]
		(NoArg (\opt -> case opt of
			Run { }	-> Right (opt { verbosity = Verbosed } )
			_	-> Right opt))
		"give verbosed output"
	, Option ['q'] ["quiet"]
		(NoArg (\opt -> case opt of
			Run { }	-> Right (opt { verbosity = Error } )
			_	-> Right opt))
		"suppress all but error messages"
	]

-- | Like 'read', but returns 'Nothing' instead of raising an 'error' on failure.
readMaybe :: Read a => String -> Maybe a
readMaybe str = find ((== "") . snd) (reads str) >>= return . fst