packages feed

lentil-0.2.0.0: src/Lentil/Args.hs

-----------------------------------------------------------------------------
-- |
-- Module      :  Lentil.Query
-- Copyright   :  © 2015 Francesco Ariis
-- License     :  GPLv3 (see the LICENSE file)
--
-- Command line parsing
-----------------------------------------------------------------------------

module Lentil.Args where

import Lentil.Types
import Lentil.Query
import Lentil.Helpers

import Options.Applicative
import Data.Semigroup ( (<>) )
import qualified Data.Char  as C
import qualified Data.Maybe as M


-----------
-- TYPES --
-----------

-- TODO: disambiguation optparse-applicative [feature:intermediate]
-- TODO: help as lentil PATH [ PATH... ] [ OPTIONS ] [feature:intermediate]

data LOptions = LOptions { loInExcl   :: ([FilePath], [FilePath]),
                           loFormat   :: Format,
                           loFilters  :: [LFilter],
                           loSort     :: [LSort],
                           loAlias    :: [Alias],
                           loOutFile  :: Maybe FilePath }

type LFilter = [Issue] -> [Issue]
type ChType  = [LFilter] -> [Issue] -> [Issue] -- AND or OR chain
type LSort   = [Issue] -> [Issue]


lOpts :: Parser LOptions
lOpts = LOptions <$> inexcls <*> format <*> filters <*>
                     issort <*> aliases <*> outfile
    where inexcls = (,) <$> includes <*> many exclude
          filters = many $ foldl1 (<|>) [tag, notag, path,
                                         nopath, desc, nodesc]
          aliases = fmap M.catMaybes (many alias)


-------------
-- PARSERS --
-------------

-- argument "." gets replaced to "" (all files)
includes :: Parser [FilePath]
includes = map repf <$> some (argument str (metavar "PATH..."))
    where repf cs  = cs

exclude :: Parser FilePath
exclude = strOption ( short 'x'      <>
                      metavar "PATH" <>
                      help "file/directory to exclude" )

format :: Parser Format
format = option (str >>= parseFormat)
                    ( short 'f'      <>
                      metavar "TYPE" <>
                      value Pretty   <>
                      help "output format (pretty, tagpop, csv, comp, xml)" )
    where
          parseFormat :: String -> ReadM Format
          parseFormat s = let asl = map forTup (enumFrom minBound)
                          in maybe (rerr s "unrecognised format")
                                   return (lookup s asl)

          forTup f      = (map C.toLower $ show f, f)


alias :: Parser (Maybe Alias)
alias = option (str >>= parseAlias)
                   ( short 'a'       <>
                     metavar "ALIAS" <>
                     help ("extension alias (e.g.: -a cpp" ++ aliasSign ++
                          "d)") )
    where
          parseAlias :: String -> ReadM (Maybe Alias)
          parseAlias s = return (aliasp s)


outfile :: Parser (Maybe FilePath)
outfile = optional $ strOption
                    ( long "output"  <>
                      metavar "FILE" <>
                      help "output file (if not present, prints to stdout)" )



issort :: Parser [LSort]
issort = pure []

{-

    TODO: uncomment and implement sorting [feature:intermediate]

issort :: Parser [LSort]
issort = option (str >>= parseSorts)
                 ( long "sort"                       <>
                   short 's'                         <>
                   metavar "[!]ORDER[,[!]ORDER ...]" <>
                   value []                          <>
                   help "sort order, comma separated (path, desc, label). \
                        \'!' orders in descending fashion. Check manual for \
                        \examples" )
          -- TODO: sort parsing? [feature:intermediate]
    where parseSorts :: String -> ReadM [LSort]
          parseSorts cs = mapM parSort . words . replace ',' ' ' $ cs

          -- this parsort is broken [bug] [duct]
          parSort :: String -> ReadM LSort
          parSort "path"  = return (sortIssues iFile Asc)
          parSort "desc"  = return (sortIssues iDesc Asc)
          parSort "!path" = return (sortIssues iFile Desc)
          parSort "!desc" = return (sortIssues iDesc Desc)
          parSort _       = error "lol"

          replace :: Char -> Char -> String -> String
          replace a b cs = map (\c -> if c == a then b else c) cs

-}

-------------------
-- FILTER PARAMS --
-------------------

path :: Parser LFilter
path = option (filterFilepath <$> str)
                       ( short 'p'        <>
                         metavar "EXPR"   <>
                         help "filters for filepath matching EXPR" )

nopath :: Parser LFilter
nopath = option (negFilter . filterFilepath <$> str)
                         ( short 'P'        <>
                           metavar "EXPR"   <>
                           help "filters for filepath NOT matching EXPR" )

desc :: Parser LFilter
desc = option (filterDescription <$> str)
                       ( short 'd'        <>
                         metavar "EXPR"   <>
                         help "filters for description matching EXPR" )

nodesc :: Parser LFilter
nodesc = option (negFilter . filterDescription <$> str)
                       ( short 'D'        <>
                         metavar "EXPR"   <>
                         help "filters for description NOT matching EXPR" )

tag :: Parser LFilter
tag = option (filterTags <$> str)
                       ( short 't'       <>
                         metavar "EXPR"  <>
                         help "filter for tag matching EXPR" )

notag :: Parser LFilter
notag = option (negFilter . filterTags <$> str)
                       ( short 'T'       <>
                         metavar "EXPR"  <>
                         help "filter for tag NOT matching EXPR" )


-----------------
-- ANCILLARIES --
-----------------

rerr :: String -> String -> ReadM a
rerr var msg = readerError $ msg ++ " \"" ++ var ++ "\""