master-plan/app/Main.hs
2017-08-09 19:22:29 -07:00

138 lines
6.0 KiB
Haskell

{-|
Module : Main
Description : Parses command line and dispatches to correct backend
Copyright : (c) Rodrigo Setti, 2017
License : MIT
Maintainer : rodrigosetti@gmail.com
Stability : experimental
Portability : POSIX
-}
{-# LANGUAGE UnicodeSyntax #-}
module Main (main) where
import Data.List (intercalate)
import qualified Data.List.NonEmpty as NE
import qualified Data.Map as M
import Data.Maybe (catMaybes, fromMaybe)
import Data.Semigroup ((<>))
import qualified Data.Text.IO as TIO
import qualified MasterPlan.Backend.Graph as BG
import qualified MasterPlan.Backend.Html as BH
import qualified MasterPlan.Backend.Identity as BI
import qualified MasterPlan.Backend.Text as BT
import MasterPlan.Data
import qualified MasterPlan.Parser as P
import Options.Applicative
import System.IO (hPutStr, stderr)
-- |Type output from the command line parser
data Opts = Opts { inputPath :: FilePath
, outputPath :: Maybe FilePath
, projFilter :: ProjFilter -- ^ filter to consider
, properties :: [ProjProperty] -- ^ which properties to consider
, prioritize :: Bool -- ^ order by priority
, renderMode :: RenderMode }
deriving (Show)
newtype ProjFilter = ProjFilter (ProjectBinding Bool)
noFilter ProjFilter
noFilter = ProjFilter $ const True
instance Show ProjFilter where
show _ = "ProjFilter"
data RenderMode = IdentityRenderMode | TextRenderMode | GraphRenderMode | HtmlMode
deriving (Eq, Show)
readEnum [(String, a)] ReadM a
readEnum mapping = maybeReader $ flip lookup mapping
-- |The command line parser
cmdParser Parser Opts
cmdParser = Opts <$> strOption ( long "input"
<> short 'i'
<> help "plan file to read from"
<> value "master.plan"
<> showDefault
<> metavar "FILENAME" )
<*> optional (strOption ( long "output"
<> short 'o'
<> help "output file name"
<> metavar "FILENAME" ))
<*> (filterParser <|> pure noFilter)
<*> (invertProps <$> many (option property ( long "hide"
<> help "hide a particular property"
<> metavar (intercalate "|" $ map fst propertyNames))))
<*> switch ( long "prioritize"
<> short 'p'
<> help "prioritize projects to minimize cost")
<*> option parseRenderMode ( long "mode"
<> short 'm'
<> help "render mode"
<> metavar (intercalate "|" $ map fst nameRenderModes))
where
propertyNames = map (\p -> (show p, p)) [minBound :: ProjProperty ..]
nameRenderModes = [ ("identity", IdentityRenderMode)
, ("text", TextRenderMode)
, ("graph", GraphRenderMode)
, ("html", HtmlMode) ]
property = readEnum propertyNames
parseRenderMode = readEnum nameRenderModes
invertProps [ProjProperty] [ProjProperty]
invertProps l = filter (`notElem` l) $ map snd propertyNames
filterParser Parser ProjFilter
filterParser = (ProjFilter . mkProgressFilter) <$> option auto ( long "progress-below"
<> help "only display projects which progress is < N%"
<> metavar "N" )
where
mkProgressFilter n (TaskProj _ _ _ p) = p*100 < n
mkProgressFilter _ _ = True
main IO ()
main = masterPlan =<< execParser opts
where
opts = info (cmdParser <**> helper)
( fullDesc
<> progDesc "See documentation on how to write project plan files"
<> header "master-plan - project management tool for hackers" )
filterBinding ProjectSystem ProjFilter ProjectBinding Maybe ProjectBinding
filterBinding sys (ProjFilter f) (ExpressionProj r p) = ExpressionProj r <$> filterProj p
where
filterProj (SumProj ps) = SumProj <$> filterProjs ps
filterProj (ProductProj ps) = ProductProj <$> filterProjs ps
filterProj (SequenceProj ps) = SequenceProj <$> filterProjs ps
filterProj rp@(RefProj n) = do b <- M.lookup n $ bindings sys
if f b then Just rp else Nothing
filterProjs ps = NE.nonEmpty (catMaybes $ NE.toList $ NE.map filterProj ps)
filterBinding _ _ b = Just b
masterPlan Opts IO ()
masterPlan opts =
do contents <- TIO.readFile filename
case P.runParser filename contents of
Left e -> hPutStr stderr e
Right sys@(ProjectSystem b) ->
render $ maybeOptimize $ ProjectSystem $ M.mapMaybe
(filterBinding sys $ projFilter opts) b
where
filename = inputPath opts
maybeOptimize = if prioritize opts then optimizeSys else id
outputToFileOrOut s = case outputPath opts of
Nothing -> TIO.putStr s
Just path -> TIO.writeFile path s
render sys =
case renderMode opts of
IdentityRenderMode -> outputToFileOrOut $ BI.render sys $ properties opts
TextRenderMode -> outputToFileOrOut $ BT.render sys $ properties opts
HtmlMode -> outputToFileOrOut $ BH.render sys $ properties opts
GraphRenderMode -> do let outfile = fromMaybe (filename ++ ".png") $ outputPath opts
BG.render outfile sys $ properties opts