packages feed

newt-0.0.3.0: src/Newt/CmdParsing.hs

{-# LANGUAGE DeriveDataTypeable #-}
module Newt.CmdParsing
    ( Config(..)
    , getConfig
    , debug
    , versionString
    ) where

import Data.List    ( partition )
import Data.Version ( showVersion, Version )
import System.Console.CmdArgs.Implicit
import System.Console.CmdArgs.Verbosity ( whenLoud, Verbosity(..), getVerbosity )
import Safe ( headMay, tailMay )
import System.Environment ( getArgs, withArgs )

import Newt.Utilities ( isValueArg )

getConfig :: Version -> IO Config
getConfig version = do rawConf <- do args <- getArgs
                                     -- if no arguments were specified, print help and exit:
                                     case args of
                                       [] -> withArgs ["--help"] $ cmdArgs $ config version
                                       _  -> cmdArgs (config version)
                       v <- getVerbosity
                       return $ relaxArgs rawConf { verb = v }

data Config = Config { source    :: Maybe FilePath
                     , dest      :: Maybe FilePath
                     , rawTable  :: [String]
                     , list      :: Bool
                     , inplace   :: Bool
                     , prefix    :: Maybe String
                     , suffix    :: Maybe String
                     , verb      :: Verbosity
                     } deriving (Show, Data, Typeable)

config :: Version -> Config
config version =
         Config { source   = def &= name "s"
                           &= help ("Template source location.  Default is to"++
                                    " read from stdin.")
                , dest     = def
                           &= help ("Destination location.  Default is to"++
                                    " write to stdout, but not all inputs"++
                                    "can be written to stdout.")
                , rawTable = def &= args -- the raw key=value pairs
                , list     = def
                           &= help ("List the set of keys in the input"++
                                    " template. This is mutually exclusive"++
                                    " with output to stdout.")
                , inplace  = def
                           &= help ("Populate the source template in-place,"++
                                    " making destructive changes. This is"++
                                    " mutually exclusive with output to stdout."++
                                    " inplace is not yet supported with directory "++
                                    " template sources.")
                , prefix = def
                           &= help "Specify a custom prefix for the tagged keys"
                           &= groupname customTags &= explicit &= name "prefix"
                           &= typ "\"<<<\""
                , suffix = def
                           &= help "Specify a custom suffix for the tagged keys"
                           &= groupname customTags &= explicit &= name "suffix"
                           &= typ "\">>>\""
                , verb = Normal
                } &= summary (versionString version) &= details detailsHeader &= program "newt" &= verbosity

customTags :: String
customTags = "\nCustomizing tag syntax"

versionString :: Version -> String
versionString version = "newt " ++ showVersion version

detailsHeader :: [String]
detailsHeader = [ "For example:"
                , ""
                , "  Transform in.cabal according to a set of key=value assignments:"
                , "  $ newt --source=in.cabal --dest=FooApp.cabal name=FooApp "++
                  "author=\"Your Name\""
                , ""
                , "  List the tagged keys in in.cabal:"
                , "  $ newt --source=in.cabal --list"
                , ""
                , "  List the tagged keys in in.cabal, using cat and stdin:"
                , "  $ cat in.cabal | newt --list"
                ]

-- | Relax the arguments a bit so -s --sourc / etc aren't strictly
-- necessary.
--
--  If --source is not specified, use the first non-value assignment
--  in args
--
--  if --dest is also not specified, use the first non-value
--  assignment in args for dest.
--
--  if neither --source or --dest is specified, use the first
--  non-value assignment for --source and the second for --dest.
relaxArgs :: Config -> Config
relaxArgs conf = let (newSrc, newDest, newTable) = findAltSourceDest (source conf) (dest conf) (partition isValueArg $ rawTable conf)
                 in conf { source = newSrc
                         , dest = newDest
                         , rawTable = newTable
                         }

findAltSourceDest :: Maybe FilePath -> Maybe FilePath -> ([String], [String]) -> (Maybe FilePath, Maybe FilePath, [String])
findAltSourceDest mbSrc mbDest (valueArgs, nonValueArgs) =
    case (mbSrc, mbDest) of
      (Nothing, Nothing) -> ( headMay nonValueArgs
                            , tailMay nonValueArgs >>= headMay
                            , valueArgs)
      (Just s,  Nothing) -> (Just s, headMay nonValueArgs, valueArgs)
      (Nothing, Just d ) -> (headMay nonValueArgs, Just d, valueArgs)
      (Just s,  Just d ) -> (Just s, Just d, valueArgs)

debug :: Config -> String -> IO ()
debug conf msg = case verb conf of
                   Loud -> putStrLn msg
                   _    -> return ()