packages feed

hackport-0.7.3.0: exe/Main.hs

{-# LANGUAGE ApplicativeDo #-}

module Main (main) where

import Control.Applicative.Combinators
import qualified Control.Applicative.Combinators.NonEmpty as CNE
import Control.Monad
import Data.Bitraversable
import Data.Monoid (Endo(..))
import qualified Options.Applicative as Opt
import qualified Options.Applicative.Help as Opt
import qualified Text.Parsec as P
import qualified Text.Parsec.String as P
import qualified Distribution.Verbosity as V

import Status.Types (StatusDirection(..))

import Hackport.Env
import Hackport.Command.List
import Hackport.Command.MakeEbuild
import Hackport.Command.Update
import Hackport.Command.Status
import Hackport.Command.Merge

import Data.Version (showVersion)
import Distribution.Pretty (prettyShow)
import Distribution.Simple.Utils (cabalVersion)

import qualified Paths_cabal_install
import qualified Paths_hackport

main :: IO ()
main = join $ Opt.customExecParser prefs globalParser
  where
    prefs :: Opt.ParserPrefs
    prefs = Opt.prefs
      $  Opt.showHelpOnEmpty
      <> Opt.showHelpOnError
      <> Opt.noBacktrack
      <> Opt.helpLongEquals

-----------------------------------------------------------------------
-- Global
-----------------------------------------------------------------------

globalParser :: Opt.ParserInfo (IO ())
globalParser = Opt.info (Opt.helper <*> parser) infoMod
  where
    infoMod :: Opt.InfoMod (IO ())
    infoMod = Opt.progDesc
      $  "HackPort is an .ebuild generator from .cabal files "
      ++ "with hackage index support"

    parser :: Opt.Parser (IO ())
    parser =  versionParser
          <|> numericVersionParser
          <|> mainParser
      where
        versionParser :: Opt.Parser (IO ())
        versionParser = Opt.flag' printVersion
          $  Opt.short 'V'
          <> Opt.long "version"
          <> Opt.help "Print version information"
          where
            printVersion = putStrLn
              $  "hackport version "
              ++ showVersion Paths_hackport.version
              ++ "\nusing cabal-install "
              ++ showVersion Paths_cabal_install.version
              ++ " and the Cabal library version "
              ++ prettyShow cabalVersion

        numericVersionParser :: Opt.Parser (IO ())
        numericVersionParser = Opt.flag' printNumericVersion
          $  Opt.long "numeric-version"
          <> Opt.help "Print just the version number"
          where
            printNumericVersion = putStrLn $ showVersion Paths_hackport.version

        mainParser :: Opt.Parser (IO ())
        mainParser = do
          v <-  silentFlag
            <|> verbosityOption
            <|> (`appEndo` V.normal) . mconcat <$> many verbosityFlag

          op <- optional $ Opt.strOption
            $  Opt.short 'p'
            <> Opt.long "overlay-path"
            <> Opt.metavar "PATH"
            <> helpMulti
              [ "Override search path list where .hackport/ lives"
              , "(default list: ['.', paludis-ovls or emerge-ovls])"
              ]

          pp <- optional $ Opt.strOption
            $  Opt.long "portage-path"
            <> Opt.metavar "PATH"
            <> Opt.help "Override path to your portage tree"

          mode <- Opt.hsubparser
            $  subcmd "list"        listAction       listParser
            <> subcmd "make-ebuild" makeEbuildAction makeEbuildParser
            <> subcmd "update"      updateAction     updateParser
            <> subcmd "status"      statusAction     statusParser
            <> subcmd "merge"       mergeAction      mergeParser

          pure $ mode GlobalEnv
            { globalVerbosity = v
            , globalPathToOverlay = op
            , globalPathToPortage = pp
            }

          where
            subcmd
              :: String
              -> Env env ()
              -> Opt.ParserInfo env
              -> Opt.Mod Opt.CommandFields (GlobalEnv -> IO ())
            subcmd name env = Opt.command name . fmap (runEnv env)

            silentFlag :: Opt.Parser V.Verbosity
            silentFlag = Opt.flag' V.silent
                $  Opt.long "silent"
                <> Opt.help "Use the \"silent\" verbosity level"

            verbosityString :: P.Parser V.Verbosity
            verbosityString = choice
              [ V.silent <$ (P.try (P.string "0") <|> P.string "silent")
              , V.normal <$ (P.try (P.string "1") <|> P.string "normal")
              , V.verbose <$ (P.try (P.string "2") <|> P.string "verbose")
              , V.deafening <$ (P.try (P.string "3") <|> P.string "deafening")
              ] <* P.eof

            verbosityFlag :: Opt.Parser (Endo V.Verbosity)
            verbosityFlag = Opt.flag' (Endo V.moreVerbose)
              $  Opt.short 'v'
              <> Opt.help "Increase verbosity level"


            verbosityOption :: Opt.Parser V.Verbosity
            verbosityOption = Opt.option readm
                $  Opt.long "verbosity"
                <> helpMulti
                  [ "Set verbosity level "
                  , "(0-3 or (silent,normal,verbose,deafening))"
                  ]
              where
                readm :: Opt.ReadM V.Verbosity
                readm = Opt.eitherReader
                  $ either (Left . err) Right
                  . P.runParser verbosityString () "command line option"

                err :: P.ParseError -> String
                err _ = ($ "") $ Opt.displayS $ Opt.renderCompact $ Opt.extractChunk $ Opt.vsepChunks
                  [ Opt.stringChunk "Takes a number (0-3) or one of the following values:"
                  , Opt.tabulate =<< traverse (bitraverse Opt.stringChunk Opt.stringChunk)
                      [ ("silent", "No output")
                      , ("normal", "Default verbosity")
                      , ("verbose", "Increased verbosity")
                      , ("deafening", "Maximum verbosity")
                      ]
                  ]



-----------------------------------------------------------------------
-- List
-----------------------------------------------------------------------

listParser :: Opt.ParserInfo ListEnv
listParser = Opt.info parser infoMod
  where
    parser :: Opt.Parser ListEnv
    parser = do
      ps <- many $ Opt.strArgument $ Opt.metavar "PACKAGE"
      pure $ ListEnv ps

    infoMod :: Opt.InfoMod ListEnv
    infoMod = Opt.progDesc "List package versions matching pattern"

-----------------------------------------------------------------------
-- Make Ebuild
-----------------------------------------------------------------------

makeEbuildParser :: Opt.ParserInfo MakeEbuildEnv
makeEbuildParser = Opt.info parser infoMod
  where
    parser :: Opt.Parser MakeEbuildEnv
    parser = do
      cat <- Opt.strArgument $ Opt.metavar "<CATEGORY>"
      cabalFiles <- CNE.some $ Opt.strArgument $ Opt.metavar "[EBUILD FILE]"
      flags <- optional cabalFlagsParser
      notOnHackage <- Opt.switch
        $  Opt.long "not-on-hackage"
        <> Opt.help "Skip writing the hackage remote-id to metadata.xml"

      pure $ MakeEbuildEnv cat cabalFiles flags (not notOnHackage)

    infoMod :: Opt.InfoMod MakeEbuildEnv
    infoMod = Opt.progDesc "Make an ebuild from a .cabal file"

-----------------------------------------------------------------------
-- Update
-----------------------------------------------------------------------

updateParser :: Opt.ParserInfo ()
updateParser = Opt.info parser infoMod
  where
    parser :: Opt.Parser ()
    parser = pure ()

    infoMod :: Opt.InfoMod ()
    infoMod = Opt.progDesc "Update the local package database"

-----------------------------------------------------------------------
-- Status
-----------------------------------------------------------------------

statusParser :: Opt.ParserInfo StatusEnv
statusParser = Opt.info parser infoMod
  where
    parser :: Opt.Parser StatusEnv
    parser = do
      dir <- choice
        [ Opt.flag' OverlayToPortage
            $  Opt.long "to-portage"
            <> Opt.help "Print only packages likely to be interesting to move to the portage tree."
        , Opt.flag' HackageToOverlay
            $  Opt.long "from-hackage"
            <> Opt.help "Print only packages likely to be interesting to move from hackage tree."
        , pure PortagePlusOverlay
        ]

      pkgs <- many $ Opt.strArgument $ Opt.metavar "PACKAGE"

      pure StatusEnv
        { statusDirection = dir
        , statusPackages = pkgs
        }

    infoMod :: Opt.InfoMod StatusEnv
    infoMod = Opt.progDescDoc $ docMulti Opt.sep
      [ "Show up-to-date status against other repos"
      , "(hackage, ::gentoo)"
      ]

-----------------------------------------------------------------------
-- Merge
-----------------------------------------------------------------------

mergeParser :: Opt.ParserInfo MergeEnv
mergeParser = Opt.info parser infoMod
  where
    parser :: Opt.Parser MergeEnv
    parser = do
      flags <- optional $ cabalFlagsParser
      pkg <- Opt.strArgument $ Opt.metavar "PACKAGE"

      pure $ MergeEnv flags pkg

    infoMod :: Opt.InfoMod MergeEnv
    infoMod = Opt.progDesc "Make an ebuild out of hackage package"

-----------------------------------------------------------------------
-- Misc
-----------------------------------------------------------------------

helpMulti :: [String] -> Opt.Mod f a
helpMulti = Opt.helpDoc . docMulti Opt.sep

docMulti :: ([Opt.Doc] -> Opt.Doc) -> [String] -> Maybe Opt.Doc
docMulti f = Opt.unChunk . fmap f . traverse Opt.paragraph

cabalFlagsParser :: Opt.Parser String
cabalFlagsParser = Opt.strOption
        $  Opt.short 'f'
        <> Opt.long "flags"
        <> Opt.metavar "cabal_flags"
        <> helpMulti
          [ "Set cabal flags to certain state. Example:"
          , "--flags=-all_extensions"
          ]