{-# LANGUAGE OverloadedLists #-}

-- | Library management.
--
--   Sample use:
--
--   @
--     -- Get libraries as listed in @.agda/libraries@ file.
--     libs <- getInstalledLibraries Nothing
--
--     -- Get the libraries (and immediate paths) relevant for @projectRoot@.
--     -- This involves locating and processing the @.agda-lib@ file for the project.
--     (libNames, includePaths) <-  getDefaultLibraries projectRoot True
--
--     -- Get include paths of depended-on libraries.
--     resolvedPaths <- libraryIncludePaths Nothing libs libNames
--
--     let allPaths = includePaths ++ resolvedPaths
--   @
--
module Agda.Interaction.Library
  ( findProjectRoot
  , getAgdaAppDir
  , getDefaultLibraries
  , getInstalledLibraries
  , getTrustedExecutables
  , libraryIncludePaths
  , getAgdaLibFiles'
  , getPrimitiveLibDir
  , LibName
  , OptionsPragma(..)
  , AgdaLibFile(..)
  , ExeName
  , LibM
  , mkLibM
  , LibWarning(..)
  , LibPositionInfo(..)
  , libraryWarningName
  , ProjectConfig(..)
  -- * Exported for testing
  , VersionView(..), versionView, unVersionView
  , findLib'
  ) where

import Control.Arrow          ( first , second )
import qualified Control.Exception as E
import Control.Monad          ( filterM, forM )
import Control.Monad.Except
import Control.Monad.State
import Control.Monad.Writer   ( Writer, runWriterT, tell )
import Control.Monad.IO.Class ( MonadIO(..) )

import Data.Char
import Data.Either
import Data.Function (on)
import Data.Map ( Map )
import qualified Data.Map as Map
import qualified Data.List as List
import qualified Data.Text as T

import System.Directory
import System.FilePath
import System.Environment
import System.IO.Error ( isPermissionError )

import Agda.Interaction.Library.Base
import Agda.Interaction.Library.Parse

import Agda.Utils.Environment
import Agda.Utils.FileName
import Agda.Utils.Functor ( (<&>) )
import Agda.Utils.IO ( catchIO )
import qualified Agda.Utils.IO.UTF8 as UTF8
import Agda.Utils.Lens
import Agda.Utils.List
import Agda.Utils.List1             ( List1, pattern (:|) )
import Agda.Utils.List2             ( List2 )
import qualified Agda.Utils.List1   as List1
import qualified Agda.Utils.List2   as List2
import Agda.Utils.Maybe
import Agda.Utils.Monad
import Agda.Syntax.Common.Pretty
import Agda.Utils.Singleton
import Agda.Utils.String ( trim )
import Agda.Utils.Tuple ( mapSndM )

import Agda.Version

-- Paths_Agda.hs is in $(BUILD_DIR)/build/autogen/.
import Paths_Agda ( getDataFileName )

------------------------------------------------------------------------
-- * Types and Monads
------------------------------------------------------------------------

-- | Library names are structured into the base name and a suffix of version
--   numbers, e.g. @mylib-1.2.3@.  The version suffix is optional.
data VersionView = VersionView
  { VersionView -> [Char]
vvBase    :: LibName
      -- ^ Actual library name.
  , VersionView -> [Integer]
vvNumbers :: [Integer]
      -- ^ Major version, minor version, subminor version, etc., all non-negative.
      --   Note: a priori, there is no reason why the version numbers should be @Int@s.
  } deriving (VersionView -> VersionView -> Bool
(VersionView -> VersionView -> Bool)
-> (VersionView -> VersionView -> Bool) -> Eq VersionView
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: VersionView -> VersionView -> Bool
== :: VersionView -> VersionView -> Bool
$c/= :: VersionView -> VersionView -> Bool
/= :: VersionView -> VersionView -> Bool
Eq, LineNumber -> VersionView -> ShowS
[VersionView] -> ShowS
VersionView -> [Char]
(LineNumber -> VersionView -> ShowS)
-> (VersionView -> [Char])
-> ([VersionView] -> ShowS)
-> Show VersionView
forall a.
(LineNumber -> a -> ShowS)
-> (a -> [Char]) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: LineNumber -> VersionView -> ShowS
showsPrec :: LineNumber -> VersionView -> ShowS
$cshow :: VersionView -> [Char]
show :: VersionView -> [Char]
$cshowList :: [VersionView] -> ShowS
showList :: [VersionView] -> ShowS
Show)

-- | Raise collected 'LibErrors' as exception.
--
mkLibM :: [AgdaLibFile] -> LibErrorIO a -> LibM a
mkLibM :: forall a. [AgdaLibFile] -> LibErrorIO a -> LibM a
mkLibM [AgdaLibFile]
libs LibErrorIO a
m = do
  (x, ews) <- WriterT [LibWarning] (StateT LibState IO) (a, LibErrWarns)
-> ExceptT
     LibErrors
     (WriterT [LibWarning] (StateT LibState IO))
     (a, LibErrWarns)
forall (m :: * -> *) a. Monad m => m a -> ExceptT LibErrors m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (WriterT [LibWarning] (StateT LibState IO) (a, LibErrWarns)
 -> ExceptT
      LibErrors
      (WriterT [LibWarning] (StateT LibState IO))
      (a, LibErrWarns))
-> WriterT [LibWarning] (StateT LibState IO) (a, LibErrWarns)
-> ExceptT
     LibErrors
     (WriterT [LibWarning] (StateT LibState IO))
     (a, LibErrWarns)
forall a b. (a -> b) -> a -> b
$ StateT LibState IO (a, LibErrWarns)
-> WriterT [LibWarning] (StateT LibState IO) (a, LibErrWarns)
forall (m :: * -> *) a. Monad m => m a -> WriterT [LibWarning] m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (StateT LibState IO (a, LibErrWarns)
 -> WriterT [LibWarning] (StateT LibState IO) (a, LibErrWarns))
-> StateT LibState IO (a, LibErrWarns)
-> WriterT [LibWarning] (StateT LibState IO) (a, LibErrWarns)
forall a b. (a -> b) -> a -> b
$ LibErrorIO a -> StateT LibState IO (a, LibErrWarns)
forall w (m :: * -> *) a. WriterT w m a -> m (a, w)
runWriterT LibErrorIO a
m
  let (errs, warns) = partitionEithers ews
  tell warns
  () <- List1.unlessNull errs \ List1 LibError
errs -> LibErrors
-> ExceptT LibErrors (WriterT [LibWarning] (StateT LibState IO)) ()
forall a.
LibErrors
-> ExceptT LibErrors (WriterT [LibWarning] (StateT LibState IO)) a
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (LibErrors
 -> ExceptT
      LibErrors (WriterT [LibWarning] (StateT LibState IO)) ())
-> LibErrors
-> ExceptT LibErrors (WriterT [LibWarning] (StateT LibState IO)) ()
forall a b. (a -> b) -> a -> b
$ [AgdaLibFile] -> List1 LibError -> LibErrors
LibErrors [AgdaLibFile]
libs List1 LibError
errs
  return x

------------------------------------------------------------------------
-- * Resources
------------------------------------------------------------------------

-- | Get the path to @~/.agda@ (system-specific).
--   Can be overwritten by the @AGDA_DIR@ environment variable.
--
--   (This is not to be confused with the directory for the data files
--   that Agda needs (e.g. the primitive modules).)
--
getAgdaAppDir :: IO FilePath
getAgdaAppDir :: IO [Char]
getAgdaAppDir = do
  -- System-specific command to build the path to ~/.agda (Unix) or %APPDATA%\agda (Win)
  let agdaDir :: IO [Char]
agdaDir = [Char] -> IO [Char]
getAppUserDataDirectory [Char]
"agda" IO [Char] -> ([Char] -> IO [Char]) -> IO [Char]
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \[Char]
legacyAgdaDir ->
        IO Bool -> IO [Char] -> IO [Char] -> IO [Char]
forall (m :: * -> *) a. Monad m => m Bool -> m a -> m a -> m a
ifM ([Char] -> IO Bool
doesDirectoryExist [Char]
legacyAgdaDir)
            ([Char] -> IO [Char]
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure [Char]
legacyAgdaDir)
            (XdgDirectory -> [Char] -> IO [Char]
getXdgDirectory XdgDirectory
XdgConfig [Char]
"agda")
  -- The default can be overwritten by setting the AGDA_DIR environment variable
  IO (Maybe [Char])
-> IO [Char] -> ([Char] -> IO [Char]) -> IO [Char]
forall (m :: * -> *) a b.
Monad m =>
m (Maybe a) -> m b -> (a -> m b) -> m b
caseMaybeM ([Char] -> IO (Maybe [Char])
lookupEnv [Char]
"AGDA_DIR") IO [Char]
agdaDir (([Char] -> IO [Char]) -> IO [Char])
-> ([Char] -> IO [Char]) -> IO [Char]
forall a b. (a -> b) -> a -> b
$ \ [Char]
dir ->
      IO Bool -> IO [Char] -> IO [Char] -> IO [Char]
forall (m :: * -> *) a. Monad m => m Bool -> m a -> m a -> m a
ifM ([Char] -> IO Bool
doesDirectoryExist [Char]
dir) ([Char] -> IO [Char]
canonicalizePath [Char]
dir) (IO [Char] -> IO [Char]) -> IO [Char] -> IO [Char]
forall a b. (a -> b) -> a -> b
$ do
        d <- IO [Char]
agdaDir
        putStrLn $ "Warning: Environment variable AGDA_DIR points to non-existing directory " ++ show dir ++ ", using " ++ show d ++ " instead."
        return d

-- | Returns the absolute default lib dir. This directory is used to
-- store the Primitive.agda file.
getPrimitiveLibDir :: IO FilePath
getPrimitiveLibDir :: IO [Char]
getPrimitiveLibDir = do
  libdir <- AbsolutePath -> [Char]
filePath (AbsolutePath -> [Char]) -> IO AbsolutePath -> IO [Char]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ([Char] -> IO AbsolutePath
absolute ([Char] -> IO AbsolutePath) -> IO [Char] -> IO AbsolutePath
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< [Char] -> IO [Char]
getDataFileName [Char]
"lib")
  ifM (doesDirectoryExist libdir)
      (return $ libdir </> "prim")
      (error $ "The lib directory " ++ libdir ++ " does not exist")

-- | The @~/.agda/libraries@ file lists the libraries Agda should know about.
--   The content of @libraries@ is a list of paths to @.agda-lib@ files.
--
--   Agda honors also version-specific @libraries@ files, e.g. @libraries-2.6.0@.
--
--   @defaultLibraryFiles@ gives a list of all @libraries@ files Agda should process
--   by default.  The first file in this list that exists is actually used.
--
defaultLibraryFiles :: List1 FilePath
defaultLibraryFiles :: NonEmpty [Char]
defaultLibraryFiles = ([Char]
"libraries-" [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char]
version) [Char] -> [[Char]] -> NonEmpty [Char]
forall a. a -> [a] -> NonEmpty a
:| [Char]
"libraries" [Char] -> [[Char]] -> [[Char]]
forall a. a -> [a] -> [a]
: []

-- | The @defaultsFile@ contains a list of library names relevant for each Agda project.
--
defaultsFile :: FilePath
defaultsFile :: [Char]
defaultsFile = [Char]
"defaults"

-- | The @~/.agda/executables@ file lists the executables Agda should know about.
--   The content of @executables@ is a list of paths to executables.
--
--   Agda honors also version-specific @executables@ files, e.g. @executables-2.6.0@.
--
--   @defaultExecutablesFiles@ gives a list of all @executables@ Agda should process
--   by default.  The first file in this list that exists is actually used.
--
defaultExecutableFiles :: List1 FilePath
defaultExecutableFiles :: NonEmpty [Char]
defaultExecutableFiles = ([Char]
"executables-" [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char]
version) [Char] -> [[Char]] -> NonEmpty [Char]
forall a. a -> [a] -> NonEmpty a
:| [Char]
"executables" [Char] -> [[Char]] -> [[Char]]
forall a. a -> [a] -> [a]
: []

------------------------------------------------------------------------
-- * Get the libraries for the current project
------------------------------------------------------------------------

-- | Find project root by looking for @.agda-lib@ files.
--
--   If there are none, look in the parent directories until one is found.

findProjectConfig
  :: FilePath                          -- ^ Candidate (init: the directory Agda was called in)
  -> LibM ProjectConfig                -- ^ Actual root and @.agda-lib@ files for this project
findProjectConfig :: [Char] -> LibM ProjectConfig
findProjectConfig [Char]
root = [AgdaLibFile] -> LibErrorIO ProjectConfig -> LibM ProjectConfig
forall a. [AgdaLibFile] -> LibErrorIO a -> LibM a
mkLibM [] (LibErrorIO ProjectConfig -> LibM ProjectConfig)
-> LibErrorIO ProjectConfig -> LibM ProjectConfig
forall a b. (a -> b) -> a -> b
$ [Char] -> LibErrorIO ProjectConfig
findProjectConfig' [Char]
root

findProjectConfig'
  :: FilePath                          -- ^ Candidate (init: the directory Agda was called in)
  -> LibErrorIO ProjectConfig          -- ^ Actual root and @.agda-lib@ files for this project
findProjectConfig' :: [Char] -> LibErrorIO ProjectConfig
findProjectConfig' [Char]
root = do
  [Char]
-> WriterT LibErrWarns (StateT LibState IO) (Maybe ProjectConfig)
forall (m :: * -> *).
(MonadState LibState m, MonadIO m) =>
[Char] -> m (Maybe ProjectConfig)
getCachedProjectConfig [Char]
root WriterT LibErrWarns (StateT LibState IO) (Maybe ProjectConfig)
-> (Maybe ProjectConfig -> LibErrorIO ProjectConfig)
-> LibErrorIO ProjectConfig
forall a b.
WriterT LibErrWarns (StateT LibState IO) a
-> (a -> WriterT LibErrWarns (StateT LibState IO) b)
-> WriterT LibErrWarns (StateT LibState IO) b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
    Just ProjectConfig
conf -> ProjectConfig -> LibErrorIO ProjectConfig
forall a. a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. Monad m => a -> m a
return ProjectConfig
conf
    Maybe ProjectConfig
Nothing   -> LibErrorIO ProjectConfig -> LibErrorIO ProjectConfig
handlePermissionException do
      libFiles <- IO [[Char]] -> WriterT LibErrWarns (StateT LibState IO) [[Char]]
forall a. IO a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO [[Char]] -> WriterT LibErrWarns (StateT LibState IO) [[Char]])
-> IO [[Char]] -> WriterT LibErrWarns (StateT LibState IO) [[Char]]
forall a b. (a -> b) -> a -> b
$ [Char] -> IO [[Char]]
getDirectoryContents [Char]
root IO [[Char]] -> ([[Char]] -> IO [[Char]]) -> IO [[Char]]
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>=
        ([Char] -> IO Bool) -> [[Char]] -> IO [[Char]]
forall (m :: * -> *) a.
Applicative m =>
(a -> m Bool) -> [a] -> m [a]
filterM (\[Char]
file -> IO Bool -> IO Bool -> IO Bool
forall (m :: * -> *). Monad m => m Bool -> m Bool -> m Bool
and2M
          (Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Bool -> IO Bool) -> Bool -> IO Bool
forall a b. (a -> b) -> a -> b
$ ShowS
takeExtension [Char]
file [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char]
".agda-lib")
          ([Char] -> IO Bool
doesFileExist ([Char]
root [Char] -> ShowS
</> [Char]
file)))
      case libFiles of
        []     -> IO (Maybe [Char])
-> WriterT LibErrWarns (StateT LibState IO) (Maybe [Char])
forall a. IO a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO ([Char] -> IO (Maybe [Char])
upPath [Char]
root) WriterT LibErrWarns (StateT LibState IO) (Maybe [Char])
-> (Maybe [Char] -> LibErrorIO ProjectConfig)
-> LibErrorIO ProjectConfig
forall a b.
WriterT LibErrWarns (StateT LibState IO) a
-> (a -> WriterT LibErrWarns (StateT LibState IO) b)
-> WriterT LibErrWarns (StateT LibState IO) b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
          Just [Char]
up -> do
            conf <- [Char] -> LibErrorIO ProjectConfig
findProjectConfig' [Char]
up
            conf <- return $ case conf of
                  DefaultProjectConfig{} -> ProjectConfig
conf
                  ProjectConfig{LineNumber
[Char]
[[Char]]
configRoot :: [Char]
configAgdaLibFiles :: [[Char]]
configAbove :: LineNumber
configAbove :: ProjectConfig -> LineNumber
configAgdaLibFiles :: ProjectConfig -> [[Char]]
configRoot :: ProjectConfig -> [Char]
..}      ->
                    ProjectConfig{ configAbove :: LineNumber
configAbove = LineNumber
configAbove LineNumber -> LineNumber -> LineNumber
forall a. Num a => a -> a -> a
+ LineNumber
1
                                 , [Char]
[[Char]]
configRoot :: [Char]
configAgdaLibFiles :: [[Char]]
configAgdaLibFiles :: [[Char]]
configRoot :: [Char]
..
                                 }
            storeCachedProjectConfig root conf
            return conf
          Maybe [Char]
Nothing -> ProjectConfig -> LibErrorIO ProjectConfig
forall a. a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. Monad m => a -> m a
return ProjectConfig
DefaultProjectConfig
        [[Char]]
files -> do
          let conf :: ProjectConfig
conf = [Char] -> [[Char]] -> LineNumber -> ProjectConfig
ProjectConfig [Char]
root [[Char]]
files LineNumber
0
          [Char]
-> ProjectConfig -> WriterT LibErrWarns (StateT LibState IO) ()
forall (m :: * -> *).
(MonadState LibState m, MonadIO m) =>
[Char] -> ProjectConfig -> m ()
storeCachedProjectConfig [Char]
root ProjectConfig
conf
          ProjectConfig -> LibErrorIO ProjectConfig
forall a. a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. Monad m => a -> m a
return ProjectConfig
conf

  where
    -- Andreas, 2024-06-26, issue #7331:
    -- In case of missing permission we terminate our search for the project file
    -- with the default value.
    handlePermissionException :: LibErrorIO ProjectConfig -> LibErrorIO ProjectConfig
    handlePermissionException :: LibErrorIO ProjectConfig -> LibErrorIO ProjectConfig
handlePermissionException = (LibErrorIO ProjectConfig
 -> (IOError -> LibErrorIO ProjectConfig)
 -> LibErrorIO ProjectConfig)
-> (IOError -> LibErrorIO ProjectConfig)
-> LibErrorIO ProjectConfig
-> LibErrorIO ProjectConfig
forall a b c. (a -> b -> c) -> b -> a -> c
flip LibErrorIO ProjectConfig
-> (IOError -> LibErrorIO ProjectConfig)
-> LibErrorIO ProjectConfig
forall e a.
Exception e =>
WriterT LibErrWarns (StateT LibState IO) a
-> (e -> WriterT LibErrWarns (StateT LibState IO) a)
-> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) e a.
(CatchIO m, Exception e) =>
m a -> (e -> m a) -> m a
catchIO \ IOError
e ->
      if IOError -> Bool
isPermissionError IOError
e then ProjectConfig -> LibErrorIO ProjectConfig
forall a. a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. Monad m => a -> m a
return ProjectConfig
DefaultProjectConfig else IO ProjectConfig -> LibErrorIO ProjectConfig
forall a. IO a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO ProjectConfig -> LibErrorIO ProjectConfig)
-> IO ProjectConfig -> LibErrorIO ProjectConfig
forall a b. (a -> b) -> a -> b
$ IOError -> IO ProjectConfig
forall e a. (HasCallStack, Exception e) => e -> IO a
E.throwIO IOError
e

    -- Note that "going up" one directory is OS dependent
    -- if the directory is a symlink.
    --
    -- Quoting from https://hackage.haskell.org/package/directory-1.3.6.1/docs/System-Directory.html#v:canonicalizePath :
    --
    --   Note that on Windows parent directories .. are always fully
    --   expanded before the symbolic links, as consistent with the
    --   rest of the Windows API (such as GetFullPathName). In
    --   contrast, on POSIX systems parent directories .. are
    --   expanded alongside symbolic links from left to right. To
    --   put this more concretely: if L is a symbolic link for R/P,
    --   then on Windows L\.. refers to ., whereas on other
    --   operating systems L/.. refers to R.
    upPath :: FilePath -> IO (Maybe FilePath)
    upPath :: [Char] -> IO (Maybe [Char])
upPath [Char]
root = do
      up <- [Char] -> IO [Char]
canonicalizePath ([Char] -> IO [Char]) -> [Char] -> IO [Char]
forall a b. (a -> b) -> a -> b
$ [Char]
root [Char] -> ShowS
</> [Char]
".."
      if up == root then return Nothing else return $ Just up


-- | Get project root

findProjectRoot :: FilePath -> LibM (Maybe FilePath)
findProjectRoot :: [Char] -> LibM (Maybe [Char])
findProjectRoot [Char]
root = [Char] -> LibM ProjectConfig
findProjectConfig [Char]
root LibM ProjectConfig
-> (ProjectConfig -> Maybe [Char]) -> LibM (Maybe [Char])
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \case
  ProjectConfig [Char]
p [[Char]]
_ LineNumber
_  -> [Char] -> Maybe [Char]
forall a. a -> Maybe a
Just [Char]
p
  ProjectConfig
DefaultProjectConfig -> Maybe [Char]
forall a. Maybe a
Nothing


-- | Get the contents of @.agda-lib@ files in the given project root.
getAgdaLibFiles' :: FilePath -> LibErrorIO [AgdaLibFile]
getAgdaLibFiles' :: [Char] -> LibErrorIO [AgdaLibFile]
getAgdaLibFiles' [Char]
path = [Char] -> LibErrorIO ProjectConfig
findProjectConfig' [Char]
path LibErrorIO ProjectConfig
-> (ProjectConfig -> LibErrorIO [AgdaLibFile])
-> LibErrorIO [AgdaLibFile]
forall a b.
WriterT LibErrWarns (StateT LibState IO) a
-> (a -> WriterT LibErrWarns (StateT LibState IO) b)
-> WriterT LibErrWarns (StateT LibState IO) b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
  ProjectConfig
DefaultProjectConfig          -> [AgdaLibFile] -> LibErrorIO [AgdaLibFile]
forall a. a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. Monad m => a -> m a
return []
  ProjectConfig [Char]
root [[Char]]
libs LineNumber
above ->
    (AgdaLibFile -> AgdaLibFile) -> [AgdaLibFile] -> [AgdaLibFile]
forall a b. (a -> b) -> [a] -> [b]
map (Lens' AgdaLibFile LineNumber -> LensSet AgdaLibFile LineNumber
forall o i. Lens' o i -> LensSet o i
set (LineNumber -> f LineNumber) -> AgdaLibFile -> f AgdaLibFile
Lens' AgdaLibFile LineNumber
libAbove LineNumber
above) ([AgdaLibFile] -> [AgdaLibFile])
-> LibErrorIO [AgdaLibFile] -> LibErrorIO [AgdaLibFile]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$>
    Maybe LibrariesFile
-> [(LineNumber, [Char])] -> LibErrorIO [AgdaLibFile]
parseLibFiles Maybe LibrariesFile
forall a. Maybe a
Nothing (([Char] -> (LineNumber, [Char]))
-> [[Char]] -> [(LineNumber, [Char])]
forall a b. (a -> b) -> [a] -> [b]
map ((LineNumber
0,) ([Char] -> (LineNumber, [Char]))
-> ShowS -> [Char] -> (LineNumber, [Char])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([Char]
root [Char] -> ShowS
</>)) [[Char]]
libs)

-- | Get dependencies and include paths for given project root:
--
--   Look for @.agda-lib@ files according to 'findAgdaLibFiles'.
--   If none are found, use default dependencies (according to @defaults@ file)
--   and current directory (project root).
--
getDefaultLibraries
  :: FilePath  -- ^ Project root.
  -> Bool      -- ^ Use @defaults@ if no @.agda-lib@ file exists for this project?
  -> LibM ([LibName], [FilePath])  -- ^ The returned @LibName@s are all non-empty strings.
getDefaultLibraries :: [Char] -> Bool -> LibM ([[Char]], [[Char]])
getDefaultLibraries [Char]
root Bool
optDefaultLibs = [AgdaLibFile]
-> LibErrorIO ([[Char]], [[Char]]) -> LibM ([[Char]], [[Char]])
forall a. [AgdaLibFile] -> LibErrorIO a -> LibM a
mkLibM [] (LibErrorIO ([[Char]], [[Char]]) -> LibM ([[Char]], [[Char]]))
-> LibErrorIO ([[Char]], [[Char]]) -> LibM ([[Char]], [[Char]])
forall a b. (a -> b) -> a -> b
$ do
  libs <- [Char] -> LibErrorIO [AgdaLibFile]
getAgdaLibFiles' [Char]
root
  if null libs
    then (,[]) <$> if optDefaultLibs then (libNameForCurrentDir :) <$> readDefaultsFile else return []
    else return $ libsAndPaths libs
  where
    libsAndPaths :: t AgdaLibFile -> ([[Char]], [[Char]])
libsAndPaths t AgdaLibFile
ls = ( (AgdaLibFile -> [[Char]]) -> t AgdaLibFile -> [[Char]]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap AgdaLibFile -> [[Char]]
_libDepends t AgdaLibFile
ls
                      , ShowS -> [[Char]] -> [[Char]]
forall b a. Ord b => (a -> b) -> [a] -> [a]
nubOn ShowS
forall a. a -> a
id ((AgdaLibFile -> [[Char]]) -> t AgdaLibFile -> [[Char]]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap AgdaLibFile -> [[Char]]
_libIncludes t AgdaLibFile
ls)
                      )

-- | Return list of libraries to be used by default.
--
--   None if the @defaults@ file does not exist.
--
readDefaultsFile :: LibErrorIO [LibName]
readDefaultsFile :: WriterT LibErrWarns (StateT LibState IO) [[Char]]
readDefaultsFile = do
    agdaDir <- IO [Char] -> WriterT LibErrWarns (StateT LibState IO) [Char]
forall a. IO a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO [Char]
getAgdaAppDir
    let file = [Char]
agdaDir [Char] -> ShowS
</> [Char]
defaultsFile
    ifNotM (liftIO $ doesFileExist file) (return []) $ {-else-} do
      ls <- liftIO $ map snd . stripCommentLines <$> UTF8.readFile file
      return $ concatMap splitCommas ls
  WriterT LibErrWarns (StateT LibState IO) [[Char]]
-> (IOError -> WriterT LibErrWarns (StateT LibState IO) [[Char]])
-> WriterT LibErrWarns (StateT LibState IO) [[Char]]
forall e a.
Exception e =>
WriterT LibErrWarns (StateT LibState IO) a
-> (e -> WriterT LibErrWarns (StateT LibState IO) a)
-> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) e a.
(CatchIO m, Exception e) =>
m a -> (e -> m a) -> m a
`catchIO` \ IOError
e -> do
    List1 LibError' -> WriterT LibErrWarns (StateT LibState IO) ()
forall (m :: * -> *).
MonadWriter LibErrWarns m =>
List1 LibError' -> m ()
raiseErrors' [ IOError -> [Char] -> LibError'
ReadError IOError
e [Char]
"Failed to read defaults file." ]
    [[Char]] -> WriterT LibErrWarns (StateT LibState IO) [[Char]]
forall a. a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. Monad m => a -> m a
return []

------------------------------------------------------------------------
-- * Reading the installed libraries
------------------------------------------------------------------------

-- | Returns the path of the @libraries@ file which lists the libraries Agda knows about.
--
--   Note: file may not exist.
--
--   If the user specified an alternative @libraries@ file which does not exist,
--   an exception is thrown containing the name of this file.
getLibrariesFile
  :: (MonadIO m, MonadError FilePath m)
  => Maybe FilePath -- ^ Override the default @libraries@ file?
  -> m LibrariesFile
getLibrariesFile :: forall (m :: * -> *).
(MonadIO m, MonadError [Char] m) =>
Maybe [Char] -> m LibrariesFile
getLibrariesFile (Just [Char]
overrideLibFile) = do
  -- A user-specified override file must exist.
  m Bool -> m LibrariesFile -> m LibrariesFile -> m LibrariesFile
forall (m :: * -> *) a. Monad m => m Bool -> m a -> m a -> m a
ifM (IO Bool -> m Bool
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Bool -> m Bool) -> IO Bool -> m Bool
forall a b. (a -> b) -> a -> b
$ [Char] -> IO Bool
doesFileExist [Char]
overrideLibFile)
    {-then-} (LibrariesFile -> m LibrariesFile
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (LibrariesFile -> m LibrariesFile)
-> LibrariesFile -> m LibrariesFile
forall a b. (a -> b) -> a -> b
$ [Char] -> Bool -> LibrariesFile
LibrariesFile [Char]
overrideLibFile Bool
True)
    {-else-} ([Char] -> m LibrariesFile
forall a. [Char] -> m a
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError [Char]
overrideLibFile)
getLibrariesFile Maybe [Char]
Nothing = do
  agdaDir <- IO [Char] -> m [Char]
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO [Char] -> m [Char]) -> IO [Char] -> m [Char]
forall a b. (a -> b) -> a -> b
$ IO [Char]
getAgdaAppDir
  let defaults = ShowS -> NonEmpty [Char] -> NonEmpty [Char]
forall a b. (a -> b) -> NonEmpty a -> NonEmpty b
List1.map ([Char]
agdaDir [Char] -> ShowS
</>) NonEmpty [Char]
defaultLibraryFiles -- NB: very short list
  files <- liftIO $ filterM doesFileExist (List1.toList defaults)
  case files of
    [Char]
file : [[Char]]
_ -> LibrariesFile -> m LibrariesFile
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (LibrariesFile -> m LibrariesFile)
-> LibrariesFile -> m LibrariesFile
forall a b. (a -> b) -> a -> b
$ [Char] -> Bool -> LibrariesFile
LibrariesFile [Char]
file Bool
True
    []       -> LibrariesFile -> m LibrariesFile
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (LibrariesFile -> m LibrariesFile)
-> LibrariesFile -> m LibrariesFile
forall a b. (a -> b) -> a -> b
$ [Char] -> Bool -> LibrariesFile
LibrariesFile (NonEmpty [Char] -> [Char]
forall a. NonEmpty a -> a
List1.last NonEmpty [Char]
defaults) Bool
False -- doesn't exist, but that's ok

-- | Parse the descriptions of the libraries Agda knows about.
--
--   Returns none if there is no @libraries@ file.
--
getInstalledLibraries
  :: Maybe FilePath     -- ^ Override the default @libraries@ file?
  -> LibM [AgdaLibFile] -- ^ Content of library files.  (Might have empty @LibName@s.)
getInstalledLibraries :: Maybe [Char] -> LibM [AgdaLibFile]
getInstalledLibraries Maybe [Char]
overrideLibFile = [AgdaLibFile] -> LibErrorIO [AgdaLibFile] -> LibM [AgdaLibFile]
forall a. [AgdaLibFile] -> LibErrorIO a -> LibM a
mkLibM [] (LibErrorIO [AgdaLibFile] -> LibM [AgdaLibFile])
-> LibErrorIO [AgdaLibFile] -> LibM [AgdaLibFile]
forall a b. (a -> b) -> a -> b
$ do
    filem <- IO (Either [Char] LibrariesFile)
-> WriterT
     LibErrWarns (StateT LibState IO) (Either [Char] LibrariesFile)
forall a. IO a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Either [Char] LibrariesFile)
 -> WriterT
      LibErrWarns (StateT LibState IO) (Either [Char] LibrariesFile))
-> IO (Either [Char] LibrariesFile)
-> WriterT
     LibErrWarns (StateT LibState IO) (Either [Char] LibrariesFile)
forall a b. (a -> b) -> a -> b
$ ExceptT [Char] IO LibrariesFile -> IO (Either [Char] LibrariesFile)
forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT (ExceptT [Char] IO LibrariesFile
 -> IO (Either [Char] LibrariesFile))
-> ExceptT [Char] IO LibrariesFile
-> IO (Either [Char] LibrariesFile)
forall a b. (a -> b) -> a -> b
$ Maybe [Char] -> ExceptT [Char] IO LibrariesFile
forall (m :: * -> *).
(MonadIO m, MonadError [Char] m) =>
Maybe [Char] -> m LibrariesFile
getLibrariesFile Maybe [Char]
overrideLibFile
    case filem of
      Left [Char]
theOverrideLibFile -> do
        List1 LibError' -> WriterT LibErrWarns (StateT LibState IO) ()
forall (m :: * -> *).
MonadWriter LibErrWarns m =>
List1 LibError' -> m ()
raiseErrors' [ [Char] -> LibError'
LibrariesFileNotFound [Char]
theOverrideLibFile ]
        [AgdaLibFile] -> LibErrorIO [AgdaLibFile]
forall a. a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. Monad m => a -> m a
return []
      Right LibrariesFile
file -> do
        if Bool -> Bool
not (LibrariesFile -> Bool
lfExists LibrariesFile
file) then [AgdaLibFile] -> LibErrorIO [AgdaLibFile]
forall a. a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. Monad m => a -> m a
return [] else do
          ls    <- IO [(LineNumber, [Char])]
-> WriterT LibErrWarns (StateT LibState IO) [(LineNumber, [Char])]
forall a. IO a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO [(LineNumber, [Char])]
 -> WriterT LibErrWarns (StateT LibState IO) [(LineNumber, [Char])])
-> IO [(LineNumber, [Char])]
-> WriterT LibErrWarns (StateT LibState IO) [(LineNumber, [Char])]
forall a b. (a -> b) -> a -> b
$ [Char] -> [(LineNumber, [Char])]
stripCommentLines ([Char] -> [(LineNumber, [Char])])
-> IO [Char] -> IO [(LineNumber, [Char])]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Char] -> IO [Char]
UTF8.readFile (LibrariesFile -> [Char]
lfPath LibrariesFile
file)
          files <- liftIO $ sequence [ (i, ) <$> expandEnvironmentVariables s | (i, s) <- ls ]
          parseLibFiles (Just file) $ nubOn snd files
  LibErrorIO [AgdaLibFile]
-> (IOError -> LibErrorIO [AgdaLibFile])
-> LibErrorIO [AgdaLibFile]
forall e a.
Exception e =>
WriterT LibErrWarns (StateT LibState IO) a
-> (e -> WriterT LibErrWarns (StateT LibState IO) a)
-> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) e a.
(CatchIO m, Exception e) =>
m a -> (e -> m a) -> m a
`catchIO` \ IOError
e -> do
    List1 LibError' -> WriterT LibErrWarns (StateT LibState IO) ()
forall (m :: * -> *).
MonadWriter LibErrWarns m =>
List1 LibError' -> m ()
raiseErrors' [ IOError -> [Char] -> LibError'
ReadError IOError
e [Char]
"Failed to read installed libraries." ]
    [AgdaLibFile] -> LibErrorIO [AgdaLibFile]
forall a. a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. Monad m => a -> m a
return []

-- | Parse the given library files.
--
parseLibFiles
  :: Maybe LibrariesFile       -- ^ Name of @libraries@ file for error reporting.
  -> [(LineNumber, FilePath)]  -- ^ Library files paired with their line number in @libraries@.
  -> LibErrorIO [AgdaLibFile]  -- ^ Content of library files.  (Might have empty @LibName@s.)
parseLibFiles :: Maybe LibrariesFile
-> [(LineNumber, [Char])] -> LibErrorIO [AgdaLibFile]
parseLibFiles Maybe LibrariesFile
mlibFile [(LineNumber, [Char])]
files = do

  anns <- [(LineNumber, [Char])]
-> ((LineNumber, [Char])
    -> WriterT
         LibErrWarns
         (StateT LibState IO)
         (Either (Maybe LibPositionInfo, LibParseError) AgdaLibFile,
          [LibWarning]))
-> WriterT
     LibErrWarns
     (StateT LibState IO)
     [(Either (Maybe LibPositionInfo, LibParseError) AgdaLibFile,
       [LibWarning])]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
t a -> (a -> m b) -> m (t b)
forM [(LineNumber, [Char])]
files (((LineNumber, [Char])
  -> WriterT
       LibErrWarns
       (StateT LibState IO)
       (Either (Maybe LibPositionInfo, LibParseError) AgdaLibFile,
        [LibWarning]))
 -> WriterT
      LibErrWarns
      (StateT LibState IO)
      [(Either (Maybe LibPositionInfo, LibParseError) AgdaLibFile,
        [LibWarning])])
-> ((LineNumber, [Char])
    -> WriterT
         LibErrWarns
         (StateT LibState IO)
         (Either (Maybe LibPositionInfo, LibParseError) AgdaLibFile,
          [LibWarning]))
-> WriterT
     LibErrWarns
     (StateT LibState IO)
     [(Either (Maybe LibPositionInfo, LibParseError) AgdaLibFile,
       [LibWarning])]
forall a b. (a -> b) -> a -> b
$ \(LineNumber
ln, [Char]
file) -> do
    [Char]
-> WriterT LibErrWarns (StateT LibState IO) (Maybe AgdaLibFile)
forall (m :: * -> *).
(MonadState LibState m, MonadIO m) =>
[Char] -> m (Maybe AgdaLibFile)
getCachedAgdaLibFile [Char]
file WriterT LibErrWarns (StateT LibState IO) (Maybe AgdaLibFile)
-> (Maybe AgdaLibFile
    -> WriterT
         LibErrWarns
         (StateT LibState IO)
         (Either (Maybe LibPositionInfo, LibParseError) AgdaLibFile,
          [LibWarning]))
-> WriterT
     LibErrWarns
     (StateT LibState IO)
     (Either (Maybe LibPositionInfo, LibParseError) AgdaLibFile,
      [LibWarning])
forall a b.
WriterT LibErrWarns (StateT LibState IO) a
-> (a -> WriterT LibErrWarns (StateT LibState IO) b)
-> WriterT LibErrWarns (StateT LibState IO) b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
      Just AgdaLibFile
lib -> (Either (Maybe LibPositionInfo, LibParseError) AgdaLibFile,
 [LibWarning])
-> WriterT
     LibErrWarns
     (StateT LibState IO)
     (Either (Maybe LibPositionInfo, LibParseError) AgdaLibFile,
      [LibWarning])
forall a. a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. Monad m => a -> m a
return (AgdaLibFile
-> Either (Maybe LibPositionInfo, LibParseError) AgdaLibFile
forall a b. b -> Either a b
Right AgdaLibFile
lib, [])
      Maybe AgdaLibFile
Nothing  -> do
        (e, ws) <- IO (Either LibParseError AgdaLibFile, [LibWarning'])
-> WriterT
     LibErrWarns
     (StateT LibState IO)
     (Either LibParseError AgdaLibFile, [LibWarning'])
forall a. IO a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Either LibParseError AgdaLibFile, [LibWarning'])
 -> WriterT
      LibErrWarns
      (StateT LibState IO)
      (Either LibParseError AgdaLibFile, [LibWarning']))
-> IO (Either LibParseError AgdaLibFile, [LibWarning'])
-> WriterT
     LibErrWarns
     (StateT LibState IO)
     (Either LibParseError AgdaLibFile, [LibWarning'])
forall a b. (a -> b) -> a -> b
$ P AgdaLibFile -> (Either LibParseError AgdaLibFile, [LibWarning'])
forall a. P a -> (Either LibParseError a, [LibWarning'])
runP (P AgdaLibFile
 -> (Either LibParseError AgdaLibFile, [LibWarning']))
-> IO (P AgdaLibFile)
-> IO (Either LibParseError AgdaLibFile, [LibWarning'])
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Char] -> IO (P AgdaLibFile)
parseLibFile [Char]
file
        let pos = Maybe [Char] -> LineNumber -> [Char] -> LibPositionInfo
LibPositionInfo (LibrariesFile -> [Char]
lfPath (LibrariesFile -> [Char]) -> Maybe LibrariesFile -> Maybe [Char]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe LibrariesFile
mlibFile) LineNumber
ln [Char]
file
            ws' = (LibWarning' -> LibWarning) -> [LibWarning'] -> [LibWarning]
forall a b. (a -> b) -> [a] -> [b]
map (Maybe LibPositionInfo -> LibWarning' -> LibWarning
LibWarning (LibPositionInfo -> Maybe LibPositionInfo
forall a. a -> Maybe a
Just LibPositionInfo
pos)) [LibWarning']
ws
        case e of
          Left LibParseError
err -> do
            (Either (Maybe LibPositionInfo, LibParseError) AgdaLibFile,
 [LibWarning])
-> WriterT
     LibErrWarns
     (StateT LibState IO)
     (Either (Maybe LibPositionInfo, LibParseError) AgdaLibFile,
      [LibWarning])
forall a. a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. Monad m => a -> m a
return ((Maybe LibPositionInfo, LibParseError)
-> Either (Maybe LibPositionInfo, LibParseError) AgdaLibFile
forall a b. a -> Either a b
Left (LibPositionInfo -> Maybe LibPositionInfo
forall a. a -> Maybe a
Just LibPositionInfo
pos, LibParseError
err), [LibWarning]
ws')
          Right AgdaLibFile
lib -> do
            [Char]
-> AgdaLibFile -> WriterT LibErrWarns (StateT LibState IO) ()
forall (m :: * -> *).
(MonadState LibState m, MonadIO m) =>
[Char] -> AgdaLibFile -> m ()
storeCachedAgdaLibFile [Char]
file AgdaLibFile
lib
            (Either (Maybe LibPositionInfo, LibParseError) AgdaLibFile,
 [LibWarning])
-> WriterT
     LibErrWarns
     (StateT LibState IO)
     (Either (Maybe LibPositionInfo, LibParseError) AgdaLibFile,
      [LibWarning])
forall a. a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. Monad m => a -> m a
return (AgdaLibFile
-> Either (Maybe LibPositionInfo, LibParseError) AgdaLibFile
forall a b. b -> Either a b
Right AgdaLibFile
lib, [LibWarning]
ws')

  let (xs, warns) = unzip anns
      (errs, als) = partitionEithers xs

  List1.unlessNull (concat warns) warnings
  List1.unlessNull errs $ \ List1 (Maybe LibPositionInfo, LibParseError)
errs1 ->
    List1 LibError -> WriterT LibErrWarns (StateT LibState IO) ()
forall (m :: * -> *).
MonadWriter LibErrWarns m =>
List1 LibError -> m ()
raiseErrors (List1 LibError -> WriterT LibErrWarns (StateT LibState IO) ())
-> List1 LibError -> WriterT LibErrWarns (StateT LibState IO) ()
forall a b. (a -> b) -> a -> b
$ ((Maybe LibPositionInfo, LibParseError) -> LibError)
-> List1 (Maybe LibPositionInfo, LibParseError) -> List1 LibError
forall a b. (a -> b) -> NonEmpty a -> NonEmpty b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\ (Maybe LibPositionInfo
mc, LibParseError
err) -> Maybe LibPositionInfo -> LibError' -> LibError
LibError Maybe LibPositionInfo
mc (LibError' -> LibError) -> LibError' -> LibError
forall a b. (a -> b) -> a -> b
$ LibParseError -> LibError'
LibParseError LibParseError
err) List1 (Maybe LibPositionInfo, LibParseError)
errs1

  return $ nubOn _libFile als

-- | Remove trailing white space and line comments.
--
stripCommentLines :: String -> [(LineNumber, String)]
stripCommentLines :: [Char] -> [(LineNumber, [Char])]
stripCommentLines = ((LineNumber, [Char]) -> [(LineNumber, [Char])])
-> [(LineNumber, [Char])] -> [(LineNumber, [Char])]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (LineNumber, [Char]) -> [(LineNumber, [Char])]
forall {a}. (a, [Char]) -> [(a, [Char])]
strip ([(LineNumber, [Char])] -> [(LineNumber, [Char])])
-> ([Char] -> [(LineNumber, [Char])])
-> [Char]
-> [(LineNumber, [Char])]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [LineNumber] -> [[Char]] -> [(LineNumber, [Char])]
forall a b. [a] -> [b] -> [(a, b)]
zip [LineNumber
Item [LineNumber]
1..] ([[Char]] -> [(LineNumber, [Char])])
-> ([Char] -> [[Char]]) -> [Char] -> [(LineNumber, [Char])]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> [[Char]]
lines
  where
    strip :: (a, [Char]) -> [(a, [Char])]
strip (a
i, [Char]
s) = [ (a
i, [Char]
s') | Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ [Char] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Char]
s' ]
      where s' :: [Char]
s' = ShowS
trimLineComment [Char]
s

-- | Returns the path of the @executables@ file which lists the trusted executables Agda knows about.
--
--   Note: file may not exist.
--
getExecutablesFile
  :: IO ExecutablesFile
getExecutablesFile :: IO ExecutablesFile
getExecutablesFile = do
  agdaDir <- IO [Char]
getAgdaAppDir
  let defaults = ShowS -> NonEmpty [Char] -> NonEmpty [Char]
forall a b. (a -> b) -> NonEmpty a -> NonEmpty b
List1.map ([Char]
agdaDir [Char] -> ShowS
</>) NonEmpty [Char]
defaultExecutableFiles  -- NB: very short list
  files <- filterM doesFileExist (List1.toList defaults)
  case files of
    [Char]
file : [[Char]]
_ -> ExecutablesFile -> IO ExecutablesFile
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (ExecutablesFile -> IO ExecutablesFile)
-> ExecutablesFile -> IO ExecutablesFile
forall a b. (a -> b) -> a -> b
$ [Char] -> Bool -> ExecutablesFile
ExecutablesFile [Char]
file Bool
True
    []       -> ExecutablesFile -> IO ExecutablesFile
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (ExecutablesFile -> IO ExecutablesFile)
-> ExecutablesFile -> IO ExecutablesFile
forall a b. (a -> b) -> a -> b
$ [Char] -> Bool -> ExecutablesFile
ExecutablesFile (NonEmpty [Char] -> [Char]
forall a. NonEmpty a -> a
List1.last NonEmpty [Char]
defaults) Bool
False -- doesn't exist, but that's ok

-- | Return the trusted executables Agda knows about.
--
--   Returns none if there is no @executables@ file.
--
getTrustedExecutables
  :: LibM (Map ExeName FilePath)  -- ^ Content of @executables@ files.
getTrustedExecutables :: LibM (Map ExeName [Char])
getTrustedExecutables = [AgdaLibFile]
-> LibErrorIO (Map ExeName [Char]) -> LibM (Map ExeName [Char])
forall a. [AgdaLibFile] -> LibErrorIO a -> LibM a
mkLibM [] (LibErrorIO (Map ExeName [Char]) -> LibM (Map ExeName [Char]))
-> LibErrorIO (Map ExeName [Char]) -> LibM (Map ExeName [Char])
forall a b. (a -> b) -> a -> b
$ do
    file <- IO ExecutablesFile
-> WriterT LibErrWarns (StateT LibState IO) ExecutablesFile
forall a. IO a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO ExecutablesFile
getExecutablesFile
    if not (efExists file) then return Map.empty else do
      es    <- liftIO $ stripCommentLines <$> UTF8.readFile (efPath file)
      lines <- liftIO $ mapM (mapSndM expandEnvironmentVariables) es
      parseExecutablesFile file lines
  LibErrorIO (Map ExeName [Char])
-> (IOError -> LibErrorIO (Map ExeName [Char]))
-> LibErrorIO (Map ExeName [Char])
forall e a.
Exception e =>
WriterT LibErrWarns (StateT LibState IO) a
-> (e -> WriterT LibErrWarns (StateT LibState IO) a)
-> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) e a.
(CatchIO m, Exception e) =>
m a -> (e -> m a) -> m a
`catchIO` \ IOError
e -> do
    List1 LibError' -> WriterT LibErrWarns (StateT LibState IO) ()
forall (m :: * -> *).
MonadWriter LibErrWarns m =>
List1 LibError' -> m ()
raiseErrors' [ IOError -> [Char] -> LibError'
ReadError IOError
e [Char]
"Failed to read trusted executables." ]
    Map ExeName [Char] -> LibErrorIO (Map ExeName [Char])
forall a. a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. Monad m => a -> m a
return Map ExeName [Char]
forall k a. Map k a
Map.empty

-- | Parse the @executables@ file.
--
parseExecutablesFile
  :: ExecutablesFile
  -> [(LineNumber, FilePath)]
  -> LibErrorIO (Map ExeName FilePath)
parseExecutablesFile :: ExecutablesFile
-> [(LineNumber, [Char])] -> LibErrorIO (Map ExeName [Char])
parseExecutablesFile ExecutablesFile
ef [(LineNumber, [Char])]
files = do
  executables <- [(LineNumber, [Char])]
-> ((LineNumber, [Char])
    -> WriterT
         LibErrWarns (StateT LibState IO) (ExeName, (LineNumber, [Char])))
-> WriterT
     LibErrWarns (StateT LibState IO) [(ExeName, (LineNumber, [Char]))]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
t a -> (a -> m b) -> m (t b)
forM [(LineNumber, [Char])]
files (((LineNumber, [Char])
  -> WriterT
       LibErrWarns (StateT LibState IO) (ExeName, (LineNumber, [Char])))
 -> WriterT
      LibErrWarns (StateT LibState IO) [(ExeName, (LineNumber, [Char]))])
-> ((LineNumber, [Char])
    -> WriterT
         LibErrWarns (StateT LibState IO) (ExeName, (LineNumber, [Char])))
-> WriterT
     LibErrWarns (StateT LibState IO) [(ExeName, (LineNumber, [Char]))]
forall a b. (a -> b) -> a -> b
$ \(LineNumber
ln, [Char]
fp) -> do
    -- Compute canonical executable name and absolute filepath.
    let strExeName :: [Char]
strExeName  = ShowS
takeFileName [Char]
fp
    let strExeName' :: [Char]
strExeName' = [Char] -> Maybe [Char] -> [Char]
forall a. a -> Maybe a -> a
fromMaybe [Char]
strExeName (Maybe [Char] -> [Char]) -> Maybe [Char] -> [Char]
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char] -> Maybe [Char]
stripExtension [Char]
exeExtension [Char]
strExeName
    let txtExeName :: ExeName
txtExeName  = [Char] -> ExeName
T.pack [Char]
strExeName'
    exePath <- IO [Char] -> WriterT LibErrWarns (StateT LibState IO) [Char]
forall a. IO a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO [Char] -> WriterT LibErrWarns (StateT LibState IO) [Char])
-> IO [Char] -> WriterT LibErrWarns (StateT LibState IO) [Char]
forall a b. (a -> b) -> a -> b
$ [Char] -> IO [Char]
makeAbsolute [Char]
fp
    return (txtExeName, (ln, exePath))

  -- Create a map from executable names to their location(s).
  let exeMap1 :: Map ExeName (List1 (LineNumber, FilePath))
      exeMap1 = (List1 (LineNumber, [Char])
 -> List1 (LineNumber, [Char]) -> List1 (LineNumber, [Char]))
-> [(ExeName, List1 (LineNumber, [Char]))]
-> Map ExeName (List1 (LineNumber, [Char]))
forall k a. Ord k => (a -> a -> a) -> [(k, a)] -> Map k a
Map.fromListWith List1 (LineNumber, [Char])
-> List1 (LineNumber, [Char]) -> List1 (LineNumber, [Char])
forall a. Semigroup a => a -> a -> a
(<>) ([(ExeName, List1 (LineNumber, [Char]))]
 -> Map ExeName (List1 (LineNumber, [Char])))
-> [(ExeName, List1 (LineNumber, [Char]))]
-> Map ExeName (List1 (LineNumber, [Char]))
forall a b. (a -> b) -> a -> b
$ ((ExeName, (LineNumber, [Char]))
 -> (ExeName, List1 (LineNumber, [Char])))
-> [(ExeName, (LineNumber, [Char]))]
-> [(ExeName, List1 (LineNumber, [Char]))]
forall a b. (a -> b) -> [a] -> [b]
map (((LineNumber, [Char]) -> List1 (LineNumber, [Char]))
-> (ExeName, (LineNumber, [Char]))
-> (ExeName, List1 (LineNumber, [Char]))
forall b c d. (b -> c) -> (d, b) -> (d, c)
forall (a :: * -> * -> *) b c d.
Arrow a =>
a b c -> a (d, b) (d, c)
second (LineNumber, [Char]) -> List1 (LineNumber, [Char])
forall el coll. Singleton el coll => el -> coll
singleton) ([(ExeName, (LineNumber, [Char]))]
 -> [(ExeName, List1 (LineNumber, [Char]))])
-> [(ExeName, (LineNumber, [Char]))]
-> [(ExeName, List1 (LineNumber, [Char]))]
forall a b. (a -> b) -> a -> b
$ [(ExeName, (LineNumber, [Char]))]
-> [(ExeName, (LineNumber, [Char]))]
forall a. [a] -> [a]
reverse [(ExeName, (LineNumber, [Char]))]
executables

  -- Separate non-ambiguous from ambiguous mappings.
  let (exeMap, duplicates) = Map.mapEither List2.fromList1Either exeMap1

  -- Report ambiguous mappings with line numbers.
  List1.unlessNull (Map.toList duplicates) $ \ List1 (ExeName, List2 (LineNumber, [Char]))
duplicates1 ->
    List1 LibError' -> WriterT LibErrWarns (StateT LibState IO) ()
forall (m :: * -> *).
MonadWriter LibErrWarns m =>
List1 LibError' -> m ()
raiseErrors' (List1 LibError' -> WriterT LibErrWarns (StateT LibState IO) ())
-> List1 LibError' -> WriterT LibErrWarns (StateT LibState IO) ()
forall a b. (a -> b) -> a -> b
$ ((ExeName, List2 (LineNumber, [Char])) -> LibError')
-> List1 (ExeName, List2 (LineNumber, [Char])) -> List1 LibError'
forall a b. (a -> b) -> NonEmpty a -> NonEmpty b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((ExeName -> List2 (LineNumber, [Char]) -> LibError')
-> (ExeName, List2 (LineNumber, [Char])) -> LibError'
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry ((ExeName -> List2 (LineNumber, [Char]) -> LibError')
 -> (ExeName, List2 (LineNumber, [Char])) -> LibError')
-> (ExeName -> List2 (LineNumber, [Char]) -> LibError')
-> (ExeName, List2 (LineNumber, [Char]))
-> LibError'
forall a b. (a -> b) -> a -> b
$ [Char] -> ExeName -> List2 (LineNumber, [Char]) -> LibError'
DuplicateExecutable ([Char] -> ExeName -> List2 (LineNumber, [Char]) -> LibError')
-> [Char] -> ExeName -> List2 (LineNumber, [Char]) -> LibError'
forall a b. (a -> b) -> a -> b
$ ExecutablesFile -> [Char]
efPath ExecutablesFile
ef) List1 (ExeName, List2 (LineNumber, [Char]))
duplicates1

  -- Return non-ambiguous mappings without line numbers.
  return $ fmap snd exeMap

------------------------------------------------------------------------
-- * Resolving library names to include pathes
------------------------------------------------------------------------

-- | Get all include pathes for a list of libraries to use.
libraryIncludePaths
  :: Maybe FilePath  -- ^ @libraries@ file (error reporting only).
  -> [AgdaLibFile]   -- ^ Libraries Agda knows about.
  -> [LibName]       -- ^ (Non-empty) library names to be resolved to (lists of) pathes.
  -> LibM [FilePath] -- ^ Resolved pathes (no duplicates).  Contains "." if @[LibName]@ does.
libraryIncludePaths :: Maybe [Char] -> [AgdaLibFile] -> [[Char]] -> LibM [[Char]]
libraryIncludePaths Maybe [Char]
overrideLibFile [AgdaLibFile]
libs [[Char]]
xs0 = [AgdaLibFile]
-> WriterT LibErrWarns (StateT LibState IO) [[Char]]
-> LibM [[Char]]
forall a. [AgdaLibFile] -> LibErrorIO a -> LibM a
mkLibM [AgdaLibFile]
libs (WriterT LibErrWarns (StateT LibState IO) [[Char]]
 -> LibM [[Char]])
-> WriterT LibErrWarns (StateT LibState IO) [[Char]]
-> LibM [[Char]]
forall a b. (a -> b) -> a -> b
$ do
    efile <- IO (Either [Char] LibrariesFile)
-> WriterT
     LibErrWarns (StateT LibState IO) (Either [Char] LibrariesFile)
forall a. IO a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Either [Char] LibrariesFile)
 -> WriterT
      LibErrWarns (StateT LibState IO) (Either [Char] LibrariesFile))
-> IO (Either [Char] LibrariesFile)
-> WriterT
     LibErrWarns (StateT LibState IO) (Either [Char] LibrariesFile)
forall a b. (a -> b) -> a -> b
$ ExceptT [Char] IO LibrariesFile -> IO (Either [Char] LibrariesFile)
forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT (ExceptT [Char] IO LibrariesFile
 -> IO (Either [Char] LibrariesFile))
-> ExceptT [Char] IO LibrariesFile
-> IO (Either [Char] LibrariesFile)
forall a b. (a -> b) -> a -> b
$ Maybe [Char] -> ExceptT [Char] IO LibrariesFile
forall (m :: * -> *).
(MonadIO m, MonadError [Char] m) =>
Maybe [Char] -> m LibrariesFile
getLibrariesFile Maybe [Char]
overrideLibFile
    case efile of
      Left [Char]
theOverrideLibFile -> do
        List1 LibError' -> WriterT LibErrWarns (StateT LibState IO) ()
forall (m :: * -> *).
MonadWriter LibErrWarns m =>
List1 LibError' -> m ()
raiseErrors' [ [Char] -> LibError'
LibrariesFileNotFound [Char]
theOverrideLibFile ]
        [[Char]] -> WriterT LibErrWarns (StateT LibState IO) [[Char]]
forall a. a -> WriterT LibErrWarns (StateT LibState IO) a
forall (m :: * -> *) a. Monad m => a -> m a
return []
      Right LibrariesFile
file -> Writer LibErrWarns [[Char]]
-> WriterT LibErrWarns (StateT LibState IO) [[Char]]
forall w (m :: * -> *) a.
(Monoid w, Monad m) =>
Writer w a -> WriterT w m a
embedWriter (Writer LibErrWarns [[Char]]
 -> WriterT LibErrWarns (StateT LibState IO) [[Char]])
-> Writer LibErrWarns [[Char]]
-> WriterT LibErrWarns (StateT LibState IO) [[Char]]
forall a b. (a -> b) -> a -> b
$ ([[Char]]
dot [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++) ([[Char]] -> [[Char]])
-> ([AgdaLibFile] -> [[Char]]) -> [AgdaLibFile] -> [[Char]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [AgdaLibFile] -> [[Char]]
incs ([AgdaLibFile] -> [[Char]])
-> WriterT LibErrWarns Identity [AgdaLibFile]
-> Writer LibErrWarns [[Char]]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> LibrariesFile
-> [[Char]]
-> [[Char]]
-> WriterT LibErrWarns Identity [AgdaLibFile]
find LibrariesFile
file [] [[Char]]
xs
  where
    ([[Char]]
dots, [[Char]]
xs) = ([Char] -> Bool) -> [[Char]] -> ([[Char]], [[Char]])
forall a. (a -> Bool) -> [a] -> ([a], [a])
List.partition ([Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char]
libNameForCurrentDir) ([[Char]] -> ([[Char]], [[Char]]))
-> [[Char]] -> ([[Char]], [[Char]])
forall a b. (a -> b) -> a -> b
$ ShowS -> [[Char]] -> [[Char]]
forall a b. (a -> b) -> [a] -> [b]
map ShowS
trim [[Char]]
xs0
    incs :: [AgdaLibFile] -> [[Char]]
incs       = ShowS -> [[Char]] -> [[Char]]
forall b a. Ord b => (a -> b) -> [a] -> [a]
nubOn ShowS
forall a. a -> a
id ([[Char]] -> [[Char]])
-> ([AgdaLibFile] -> [[Char]]) -> [AgdaLibFile] -> [[Char]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (AgdaLibFile -> [[Char]]) -> [AgdaLibFile] -> [[Char]]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap AgdaLibFile -> [[Char]]
_libIncludes
    dot :: [[Char]]
dot        = [ [Char]
"." | Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ [[Char]] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [[Char]]
dots ]

    -- Due to library dependencies, the work list may grow temporarily.
    find
      :: LibrariesFile  -- Only for error reporting.
      -> [LibName]      -- Already resolved libraries.
      -> [LibName]      -- Work list: libraries left to be resolved.
      -> Writer LibErrWarns [AgdaLibFile]
    find :: LibrariesFile
-> [[Char]]
-> [[Char]]
-> WriterT LibErrWarns Identity [AgdaLibFile]
find LibrariesFile
_ [[Char]]
_ [] = [AgdaLibFile] -> WriterT LibErrWarns Identity [AgdaLibFile]
forall a. a -> WriterT LibErrWarns Identity a
forall (f :: * -> *) a. Applicative f => a -> f a
pure []
    find LibrariesFile
file [[Char]]
visited ([Char]
x : [[Char]]
xs)
      | [Char]
x [Char] -> [[Char]] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]]
visited = LibrariesFile
-> [[Char]]
-> [[Char]]
-> WriterT LibErrWarns Identity [AgdaLibFile]
find LibrariesFile
file [[Char]]
visited [[Char]]
xs
      | Bool
otherwise = do
          -- May or may not find the library
          ml <- case [Char] -> [AgdaLibFile] -> [AgdaLibFile]
findLib [Char]
x [AgdaLibFile]
libs of
            [Item [AgdaLibFile]
l] -> Maybe AgdaLibFile
-> WriterT LibErrWarns Identity (Maybe AgdaLibFile)
forall a. a -> WriterT LibErrWarns Identity a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (AgdaLibFile -> Maybe AgdaLibFile
forall a. a -> Maybe a
Just Item [AgdaLibFile]
AgdaLibFile
l)
            []  -> Maybe AgdaLibFile
forall a. Maybe a
Nothing Maybe AgdaLibFile
-> WriterT LibErrWarns Identity ()
-> WriterT LibErrWarns Identity (Maybe AgdaLibFile)
forall a b.
a
-> WriterT LibErrWarns Identity b -> WriterT LibErrWarns Identity a
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ List1 LibError' -> WriterT LibErrWarns Identity ()
forall (m :: * -> *).
MonadWriter LibErrWarns m =>
List1 LibError' -> m ()
raiseErrors' [LibrariesFile -> [Char] -> LibError'
LibNotFound LibrariesFile
file [Char]
x]
            [AgdaLibFile]
ls  -> Maybe AgdaLibFile
forall a. Maybe a
Nothing Maybe AgdaLibFile
-> WriterT LibErrWarns Identity ()
-> WriterT LibErrWarns Identity (Maybe AgdaLibFile)
forall a b.
a
-> WriterT LibErrWarns Identity b -> WriterT LibErrWarns Identity a
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ List1 LibError' -> WriterT LibErrWarns Identity ()
forall (m :: * -> *).
MonadWriter LibErrWarns m =>
List1 LibError' -> m ()
raiseErrors' [[Char] -> [AgdaLibFile] -> LibError'
AmbiguousLib [Char]
x [AgdaLibFile]
ls]
          -- If it is found, add its dependencies to work list
          let xs' = (AgdaLibFile -> [[Char]]) -> Maybe AgdaLibFile -> [[Char]]
forall m a. Monoid m => (a -> m) -> Maybe a -> m
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap AgdaLibFile -> [[Char]]
_libDepends Maybe AgdaLibFile
ml [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ [[Char]]
xs
          mcons ml <$> find file (x : visited) xs'

-- | @findLib x libs@ retrieves the matches for @x@ from list @libs@.
--
--   1. Case @x@ is unversioned:
--      If @x@ is contained in @libs@, then that match is returned.
--      Otherwise, the matches with the highest version number are returned.
--
--   2. Case @x@ is versioned: the matches with the highest version number are returned.
--
--   Examples, see 'findLib''.
--
findLib :: LibName -> [AgdaLibFile] -> [AgdaLibFile]
findLib :: [Char] -> [AgdaLibFile] -> [AgdaLibFile]
findLib = (AgdaLibFile -> [Char]) -> [Char] -> [AgdaLibFile] -> [AgdaLibFile]
forall a. (a -> [Char]) -> [Char] -> [a] -> [a]
findLib' AgdaLibFile -> [Char]
_libName

-- | Generalized version of 'findLib' for testing.
--
--   > findLib' id "a"   [ "a-1", "a-02", "a-2", "b" ] == [ "a-02", "a-2" ]
--
--   > findLib' id "a"   [ "a", "a-1", "a-01", "a-2", "b" ] == [ "a" ]
--   > findLib' id "a-1" [ "a", "a-1", "a-01", "a-2", "b" ] == [ "a-1", "a-01" ]
--   > findLib' id "a-2" [ "a", "a-1", "a-01", "a-2", "b" ] == [ "a-2" ]
--   > findLib' id "c"   [ "a", "a-1", "a-01", "a-2", "b" ] == []
--
findLib' :: (a -> LibName) -> LibName -> [a] -> [a]
findLib' :: forall a. (a -> [Char]) -> [Char] -> [a] -> [a]
findLib' a -> [Char]
libName [Char]
x [a]
libs =
  case [a]
ls of
    -- Take the first and all exact matches (modulo leading zeros in version numbers).
    a
l : [a]
ls' -> a
l a -> [a] -> [a]
forall a. a -> [a] -> [a]
: (a -> Bool) -> [a] -> [a]
forall a. (a -> Bool) -> [a] -> [a]
takeWhile ((([Char], Bool, [Integer]) -> ([Char], Bool, [Integer]) -> Bool
forall a. Eq a => a -> a -> Bool
(==) (([Char], Bool, [Integer]) -> ([Char], Bool, [Integer]) -> Bool)
-> (a -> ([Char], Bool, [Integer])) -> a -> a -> Bool
forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
`on` a -> ([Char], Bool, [Integer])
versionMeasure) a
l) [a]
ls'
    []      -> []
  where
    -- @LibName@s that match @x@, sorted descendingly.
    -- The unversioned LibName, if any, will come first.
    ls :: [a]
ls = (a -> a -> Ordering) -> [a] -> [a]
forall a. (a -> a -> Ordering) -> [a] -> [a]
List.sortBy ((([Char], Bool, [Integer])
 -> ([Char], Bool, [Integer]) -> Ordering)
-> ([Char], Bool, [Integer])
-> ([Char], Bool, [Integer])
-> Ordering
forall a b c. (a -> b -> c) -> b -> a -> c
flip ([Char], Bool, [Integer]) -> ([Char], Bool, [Integer]) -> Ordering
forall a. Ord a => a -> a -> Ordering
compare (([Char], Bool, [Integer])
 -> ([Char], Bool, [Integer]) -> Ordering)
-> (a -> ([Char], Bool, [Integer])) -> a -> a -> Ordering
forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
`on` a -> ([Char], Bool, [Integer])
versionMeasure)
                     [ a
l | a
l <- [a]
libs, [Char]
x [Char] -> [Char] -> Bool
`hasMatch` a -> [Char]
libName a
l ]

    -- foo > foo-2.2 > foo-2.0.1 > foo-2 > foo-1.0
    versionMeasure :: a -> ([Char], Bool, [Integer])
versionMeasure a
l = ([Char]
rx, [Integer] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Integer]
vs, [Integer]
vs)
      where
        VersionView [Char]
rx [Integer]
vs = [Char] -> VersionView
versionView (a -> [Char]
libName a
l)

-- | @x `hasMatch` y@ if @x@ and @y@ have the same @vvBase@ and
--   either @x@ has no version qualifier or the versions also match.
hasMatch :: LibName -> LibName -> Bool
hasMatch :: [Char] -> [Char] -> Bool
hasMatch [Char]
x [Char]
y = [Char]
rx [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char]
ry Bool -> Bool -> Bool
&& ([Integer]
vx [Integer] -> [Integer] -> Bool
forall a. Eq a => a -> a -> Bool
== [Integer]
vy Bool -> Bool -> Bool
|| [Integer] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Integer]
vx)
  where
    VersionView [Char]
rx [Integer]
vx = [Char] -> VersionView
versionView [Char]
x
    VersionView [Char]
ry [Integer]
vy = [Char] -> VersionView
versionView [Char]
y

-- | Split a library name into basename and a list of version numbers.
--
--   > versionView "foo-1.2.3"    == VersionView "foo" [1, 2, 3]
--   > versionView "foo-01.002.3" == VersionView "foo" [1, 2, 3]
--
--   Note that because of leading zeros, @versionView@ is not injective.
--   (@unVersionView . versionView@ would produce a normal form.)
versionView :: LibName -> VersionView
versionView :: [Char] -> VersionView
versionView [Char]
s =
  case (Char -> Bool) -> [Char] -> ([Char], [Char])
forall a. (a -> Bool) -> [a] -> ([a], [a])
span (\ Char
c -> Char -> Bool
isDigit Char
c Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'.') (ShowS
forall a. [a] -> [a]
reverse [Char]
s) of
    ([Char]
v, Char
'-' : [Char]
x) | [[Char]] -> Bool
forall {t :: * -> *} {t :: * -> *} {a}.
(IsList (t (t a)), Foldable t, Foldable t) =>
t (t a) -> Bool
valid [[Char]]
vs ->
      [Char] -> [Integer] -> VersionView
VersionView (ShowS
forall a. [a] -> [a]
reverse [Char]
x) ([Integer] -> VersionView) -> [Integer] -> VersionView
forall a b. (a -> b) -> a -> b
$ [Integer] -> [Integer]
forall a. [a] -> [a]
reverse ([Integer] -> [Integer]) -> [Integer] -> [Integer]
forall a b. (a -> b) -> a -> b
$ ([Char] -> Integer) -> [[Char]] -> [Integer]
forall a b. (a -> b) -> [a] -> [b]
map ([Char] -> Integer
forall a. Read a => [Char] -> a
read ([Char] -> Integer) -> ShowS -> [Char] -> Integer
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ShowS
forall a. [a] -> [a]
reverse) [[Char]]
vs
      where vs :: [[Char]]
vs = (Char -> Bool) -> [Char] -> [[Char]]
forall a. (a -> Bool) -> [a] -> [[a]]
chopWhen (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'.') [Char]
v
            valid :: t (t a) -> Bool
valid [] = Bool
False
            valid t (t a)
vs = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (t a -> Bool) -> t (t a) -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any t a -> Bool
forall a. t a -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null t (t a)
vs
    ([Char], [Char])
_ -> [Char] -> [Integer] -> VersionView
VersionView [Char]
s []

-- | Print a @VersionView@, inverse of @versionView@ (modulo leading zeros).
unVersionView :: VersionView -> LibName
unVersionView :: VersionView -> [Char]
unVersionView = \case
  VersionView [Char]
base [] -> [Char]
base
  VersionView [Char]
base [Integer]
vs -> [Char]
base [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char]
"-" [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char] -> [[Char]] -> [Char]
forall a. [a] -> [[a]] -> [a]
List.intercalate [Char]
"." ((Integer -> [Char]) -> [Integer] -> [[Char]]
forall a b. (a -> b) -> [a] -> [b]
map Integer -> [Char]
forall a. Show a => a -> [Char]
show [Integer]
vs)