packages feed

haskell-updater-1.5.0.0: src/Distribution/Gentoo/CmdLine/Types.hs

{- |
   Module      : Distribution.Gentoo.CmdLine.Types

   Types representing command-line options
 -}

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}

module Distribution.Gentoo.CmdLine.Types
    (
      -- * main type
      CmdLineArgs(..)
    , defCmdLineArgs
      -- * sub types
    , BuildTarget(..)
    , CustomTarget
    , RunMode(..)
      -- * Multiple-choice arguments
    , CmdlineOpt(..)
      -- ** getter functions
    , argString
    , argDescription
      -- ** utility functions
    , argHelp
    , fromCmdline
    ) where

import           Data.Char             (toLower)
import qualified Data.List             as L
import qualified Data.List.NonEmpty    as NE
import           Data.Proxy

import Distribution.Gentoo.PkgManager.Types
import Distribution.Gentoo.Types
import Output

-- | Represents possible options given on the command line
data CmdLineArgs = CmdLineArgs
    { cmdLinePkgManager :: PkgManager -- ^ @--package-manager@ or @--custom-pm@
    , cmdLinePretend :: Bool -- ^ @--pretend@
    , cmdLineNoDeep :: Bool -- ^ @--no-deep@
    , cmdLineVersion :: Bool -- ^ @--version@
    , cmdLineAction :: WithCmd -- ^ @--action@
    , cmdLineTargets :: Maybe (NE.NonEmpty (Either CustomTarget BuildTarget)) -- ^ @--target@
    , cmdLineMode :: RunMode -- ^ @--mode@
    , cmdLineVerbosity :: Verbosity -- ^ @--verbose@ or @--quiet@
    , cmdLineHelp :: Bool -- ^ @--help@

      -- This would be better off as another BuildTarget option, but then we
      -- would lose the cool CmdlineOpt automagic
    , cmdLineWorldFull :: Bool -- ^ @--world-full@
    } deriving (Show, Eq, Ord)

defCmdLineArgs :: PkgManager -> CmdLineArgs
defCmdLineArgs defPM = CmdLineArgs
    { cmdLinePkgManager = defPM
    , cmdLinePretend = False
    , cmdLineNoDeep = False
    , cmdLineVersion = False
    , cmdLineAction = PrintAndRun
    , cmdLineTargets = Nothing
    , cmdLineMode = BasicMode
    , cmdLineVerbosity = Normal
    , cmdLineHelp = False
    , cmdLineWorldFull = False
    }

data BuildTarget
    = OnlyInvalid -- ^ Default
    | AllInstalled -- ^ Rebuild every Haskell package
    | WorldTarget -- ^ Target @@world@ portage set
    | PreservedRebuild -- ^ Append @@preserved-rebuild@ set
    deriving (Eq, Ord, Show, Read, Enum, Bounded)

type CustomTarget = String

data RunMode
    = BasicMode -- ^ @--mode=basic@
    | ListMode -- ^ @--mode=list@
    | ReinstallAtomsMode -- ^ @--mode=reinstall-atoms@ (portage only)
    deriving (Show, Eq, Ord, Enum, Bounded)

-- | A class for multiple-choice options selected by an argument on the command
--   line
class (Eq a, Enum a, Bounded a) => CmdlineOpt a where
    -- | Define the short name and an optional description for a constructor
    argInfo :: a -> (String, Maybe String)
    -- | Define the short name for the multiple-choice argument as a whole
    --
    --   e.g. @"action"@
    optName :: Proxy a -> String
    -- | Define the description for the multiple-choice argument as a whole
    --
    --   e.g. @"Specify whether to run the PM command or just print it"@
    optDescription :: Proxy a -> String
    -- | Define the default constructor for the multiple-choice argument
    --
    --   e.g. 'PrintAndRun'
    optDefault :: Proxy a -> a

instance CmdlineOpt WithCmd where
    argInfo PrintAndRun = ("print-and-run", Nothing)
    argInfo PrintOnly = ("print", Nothing)
    argInfo RunOnly = ("run", Nothing)

    optName _ = "action"
    optDescription _ =
        "Specify whether to run the PM command or just print it"
    optDefault _ = PrintAndRun

instance CmdlineOpt BuildTarget where
    argInfo OnlyInvalid = ("invalid", Just "broken Haskell packages")
    argInfo AllInstalled = ("all", Just "all installed Haskell packages")
    argInfo WorldTarget =
        ( "world"
        , Just $ "@world set (only valid with portage package\n"
              ++ "manager and reinstall-atoms mode)"
        )
    argInfo PreservedRebuild =
        ( "preserved-rebuild"
        , Just $ "Append @preserved-rebuild set (only valid with\n"
              ++ "portage package manager and basic mode)"
        )

    optName _ = "target"
    optDescription _ =
        "Choose the type of packages for the PM to target.\n\
        \May be given multiple times in reinstall-atoms mode."
    optDefault _ = OnlyInvalid

instance CmdlineOpt RunMode where
    argInfo BasicMode = ("basic", Just "classic haskell-updater behavior")
    argInfo ListMode =
        ( "list"
        , Just $ "just print a list of packages for rebuild,\n"
              ++ "one package per line"
        )
    argInfo ReinstallAtomsMode =
        ( "reinstall-atoms"
        , Just $ "experimental portage invocation using\n"
              ++ "--reinstall-atoms (may be more useful in\n"
              ++ "some situations)" )

    optName _ = "mode"
    optDescription _ =
        "Mode of operation for haskell-updater"
    optDefault _ = BasicMode

argString :: CmdlineOpt a => a -> String
argString = fst . argInfo

argDescription :: CmdlineOpt a => a -> Maybe String
argDescription = snd . argInfo

argHelp :: forall a. CmdlineOpt a => Proxy a -> String
argHelp _ = unlines $ [mainDesc] ++ (args >>= argLine)
  where
    mainDesc = optDescription (Proxy @a)
    argLine a = case (L.lookup a argFields, argDescription a) of
        (Nothing, _) -> []
        (Just s, Nothing) ->  [s]
        (Just s, Just d) -> case lines d of
            (l:ls) -> [paddedFst s l] ++ (paddedRest <$> ls)
            _ -> []
    paddedFst s d =
        s ++ replicate (padMax - length s) ' ' ++ " : " ++ d
    paddedRest d = replicate (padMax + 3) ' ' ++ d
    padMax = maximum $ length . snd <$> argFields
    argFields = (\a -> (a, showArg a)) <$> args
    showArg a = " * " ++ argString a ++ showDef a
    showDef a
        | optDefault (Proxy @a) == a = " (default)"
        | otherwise = ""
    args = [minBound :: a .. maxBound]

fromCmdline
    :: forall a. CmdlineOpt a
    => (a -> CmdLineArgs -> CmdLineArgs)
    -> String
    -> CmdLineArgs
    -> Either String CmdLineArgs
fromCmdline update s rm =
    case L.find (\a -> argString a == lowerS) args of
        Nothing -> Left $ "Unknown " ++ name ++ ": " ++ lowerS
        Just a -> Right $ update a rm
  where
    lowerS = map toLower s
    name = optName $ Proxy @a
    args = [minBound :: a .. maxBound]