content: {-# LANGUAGE TemplateHaskell #-}
  module Distribution.Dev.CabalInstall
         ( findOnPath
         , program
         , getFeatures
         , CabalFeatures
         , needsQuotes
         , hasOnlyDependencies
         , configDir
         , CabalCommand(..)
         , Option(..)
         , OptionName(..)
         , ArgType(..)
         , matchLongOption
         , commandToString
         , stringToCommand
         , allCommands
         , commandOptions
         , supportsLongOption
         , supportedOptions
         , getUserConfig
         )
  where
  
  import Data.Maybe ( fromMaybe )
  import Control.Applicative ( (<$>), pure )
  import System.FilePath ( (</>) )
  import System.Environment ( getEnvironment )
  import Distribution.Version ( Version(..), withinRange
                              , earlierVersion, orLaterVersion )
  import Distribution.Verbosity ( Verbosity )
  import Distribution.Simple.Program ( Program( programFindVersion
                                              )
                                     , ConfiguredProgram
                                     , emptyProgramConfiguration
                                     , findProgramVersion
                                     , programLocation
                                     , programVersion
                                     , requireProgram
                                     , getProgramOutput
                                     , simpleProgram
                                     )
  import Distribution.Simple.Utils ( debug )
  import Distribution.Text ( display, simpleParse )
  
  import System.Directory ( getAppUserDataDirectory )
  
  import Distribution.Dev.InterrogateCabalInstall
      ( Option(..), OptionName(..), ArgType(..) )
  import Distribution.Dev.TH.DeriveCabalCommands
      ( deriveCabalCommands )
  
  -- XXX This is duplicated in Setup.hs
  -- |Definition of the cabal-install program
  program :: Maybe String -> Program
  program p =
      (simpleProgram $ fromMaybe "cabal" p) { programFindVersion =
                                    findProgramVersion "--numeric-version" id
                              }
  
  -- |Find cabal-install on the user's PATH
  findOnPath :: Verbosity -> Maybe FilePath -> IO ConfiguredProgram
  findOnPath v ci = do
    (cabal, _) <- requireProgram v (program ci) emptyProgramConfiguration
    debug v $ concat [ "Using cabal-install "
                     , maybe "(unknown version)" display $ programVersion cabal
                     , " at "
                     , show (programLocation cabal)
                     ]
    return cabal
  
  -- |Parse the Cabal library version from the output of cabal --version
  parseVersionOutput :: String -> Either String Version
  parseVersionOutput str =
      case lines str of
        []      -> Left "No version string provided."
        [_]     -> Left "Could not find Cabal version line."
        (_:ln:_) -> case simpleParse ((words ln)!!2) of
                     Just v  -> Right v
                     Nothing -> Left $ err ln
          where err line = "Could not parse Cabal verison.\n"
                           ++ "(simpleParse "++show line++")"
  
  -- |The information necessary to properly invoke cabal-install
  data CabalFeatures = CabalFeatures { cfLibVersion :: Version
                                     , cfExeVersion :: Version
                                     }
  
  mkVer :: [Int] -> Version
  mkVer l = Version l []
  
  -- |Extract the features of this cabal-install executable
  getFeatures :: Verbosity -> ConfiguredProgram ->
                 IO (Either String CabalFeatures)
  getFeatures v cabal = do
    case programVersion cabal of
      Nothing -> return $ Left "Failed to find cabal-install version"
      Just exeVer -> do
        verRes <- parseVersionOutput <$> getProgramOutput v cabal ["--version"]
        case verRes of
          Left err -> return $ Left $ "Detecting cabal-install's Cabal: " ++ err
          Right libVer -> return $ Right $
                          CabalFeatures { cfLibVersion = libVer
                                        , cfExeVersion = exeVer
                                        }
  
  -- |Does the cabal-install configuration file use quoted paths in the
  -- install-dirs section?
  needsQuotes :: CabalFeatures -> Bool
  needsQuotes = (`withinRange` earlierVersion (mkVer [1,10])) . cfLibVersion
  
  -- |Does this cabal-install executable support the --dependencies-only
  -- flag to install?
  hasOnlyDependencies :: CabalFeatures -> Bool
  hasOnlyDependencies =
    (`withinRange` orLaterVersion (mkVer [0, 10])) . cfExeVersion
  
  $(deriveCabalCommands)
  
  supportsLongOption :: CabalCommand -> String -> Bool
  supportsLongOption cc s = any ((`matchLongOption` s) . optionName) $ supportedOptions cc
  
  optionName :: Option -> OptionName
  optionName (Option n _) = n
  
  supportedOptions :: CabalCommand -> [Option]
  supportedOptions cc = commonOptions ++ commandOptions cc
  
  matchLongOption :: OptionName -> String -> Bool
  matchLongOption (Short _) = const False
  matchLongOption (LongOption s) = (== s)
  
  commonOptions :: [Option]
  commonOptions = [Option (LongOption "config-file") Req]
  
  -- |What is the configuration directory for this cabal-install executable?
  
  -- XXX: This needs to do something different for certain platforms for
  -- new versions of cabal-install (look at the tickets on creswick's
  -- cabal-dev repo)
  configDir :: CabalFeatures -> IO FilePath
  configDir _ = getAppUserDataDirectory "cabal"
  
  getUserConfig :: CabalFeatures -> IO FilePath
  getUserConfig cf = do
    env <- lookup "CABAL_CONFIG" <$> getEnvironment
    case env of
      Nothing -> (</> "config") <$> configDir cf
      Just f  -> pure f
  
  