{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE MultiWayIf                #-}
{-# LANGUAGE OverloadedStrings         #-}
{-# LANGUAGE ScopedTypeVariables       #-}
-- Copyright 2022 United States Government as represented by the Administrator
-- of the National Aeronautics and Space Administration. All Rights Reserved.
--
-- Disclaimers
--
-- Licensed under the Apache License, Version 2.0 (the "License"); you may
-- not use this file except in compliance with the License. You may obtain a
-- copy of the License at
--
--      https://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-- License for the specific language governing permissions and limitations
-- under the License.
--
-- | Shared functions across multiple backends.
module Command.Common
    ( InputFile(..)
    , combineInputFiles
    , parseInputFile
    , parseVariablesFile
    , parseRequirementsListFile
    , openVarDBFiles
    , openVarDBFilesWithDefault
    , parseTemplateVarsFile
    , checkArguments
    , specExtractExternalVariables
    , specExtractHandlers
    , processResult
    , cannotCopyTemplate
    , makeLeftE
    , locateTemplateDir
    )
  where

-- External imports
import qualified Control.Exception      as E
import           Control.Monad.Except   (ExceptT (..), runExceptT, throwError)
import           Control.Monad.IO.Class (liftIO)
import           Data.Aeson             (Value (Null, Object), eitherDecode,
                                         eitherDecodeFileStrict, object)
import           System.FilePath        ((</>))

-- External imports: auxiliary
import Data.ByteString.Extra as B (safeReadFile)
import Data.String.Extra     (sanitizeLCIdentifier, sanitizeUCIdentifier)

-- External imports: ogma
import Data.OgmaSpec (Requirement (..), Spec (..), externalVariableName,
                      externalVariables, requirementName, requirementResultType,
                      requirements)

-- Internal imports: VariableDBs
import Command.VariableDB (VariableDB, emptyVariableDB, mergeVariableDB)

-- Internal imports: auxiliary
import Command.Errors      (ErrorTriplet(..), ErrorCode)
import Command.Result      (Result (..))
import Data.Diagram        (Diagram)
import Data.Diagram.Parser (DiagramFormat (..), readDiagram)
import Data.Either.Extra   (makeLeft)
import Data.ExprPair       (ExprPair (..), ExprPairT (..))
import Data.Location       (Location (..))
import Data.Spec.Parser    (readInputFile)
import Paths_ogma_core     (getDataDir)

-- | File containing information to be processed by Ogma (e.g., specification,
-- diagram).
data InputFile a = InputFileDiagram Diagram
                 | InputFileSpec    (Spec a)

-- | Merge a list of input files into a smaller list of input files.
--
-- PRE: If there is more than one input file, all input files are specs.
combineInputFiles :: [InputFile a] -> [InputFile a]
combineInputFiles :: forall a. [InputFile a] -> [InputFile a]
combineInputFiles []  = []
combineInputFiles [InputFile a
x] = [InputFile a
x]
combineInputFiles [InputFile a]
xs
    | (InputFile a -> Bool) -> [InputFile a] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any InputFile a -> Bool
forall a. InputFile a -> Bool
isInputDiagram [InputFile a]
xs
    = []

    | Bool
otherwise
    = [ Spec a -> InputFile a
forall a. Spec a -> InputFile a
InputFileSpec (Spec a -> InputFile a) -> Spec a -> InputFile a
forall a b. (a -> b) -> a -> b
$ (Spec a -> Spec a -> Spec a) -> [Spec a] -> Spec a
forall a. (a -> a -> a) -> [a] -> a
forall (t :: * -> *) a. Foldable t => (a -> a -> a) -> t a -> a
foldr1 Spec a -> Spec a -> Spec a
forall a. Spec a -> Spec a -> Spec a
mergeSpecs ([Spec a] -> Spec a) -> [Spec a] -> Spec a
forall a b. (a -> b) -> a -> b
$ (InputFile a -> Spec a) -> [InputFile a] -> [Spec a]
forall a b. (a -> b) -> [a] -> [b]
map InputFile a -> Spec a
forall a. InputFile a -> Spec a
getSpec [InputFile a]
xs ]

  where

    -- True if the given argument is a diagram.
    isInputDiagram :: InputFile a -> Bool
    isInputDiagram :: forall a. InputFile a -> Bool
isInputDiagram (InputFileDiagram Diagram
_) = Bool
True
    isInputDiagram InputFile a
_                    = Bool
False

    -- Merge two specifications.
    mergeSpecs :: Spec a -> Spec a -> Spec a
    mergeSpecs :: forall a. Spec a -> Spec a -> Spec a
mergeSpecs Spec a
s1 Spec a
s2 = Spec
      { internalVariables :: [InternalVariableDef]
internalVariables = Spec a -> [InternalVariableDef]
forall a. Spec a -> [InternalVariableDef]
internalVariables Spec a
s1 [InternalVariableDef]
-> [InternalVariableDef] -> [InternalVariableDef]
forall a. [a] -> [a] -> [a]
++ Spec a -> [InternalVariableDef]
forall a. Spec a -> [InternalVariableDef]
internalVariables Spec a
s2
      , externalVariables :: [ExternalVariableDef]
externalVariables = Spec a -> [ExternalVariableDef]
forall a. Spec a -> [ExternalVariableDef]
externalVariables Spec a
s2 [ExternalVariableDef]
-> [ExternalVariableDef] -> [ExternalVariableDef]
forall a. [a] -> [a] -> [a]
++ Spec a -> [ExternalVariableDef]
forall a. Spec a -> [ExternalVariableDef]
externalVariables Spec a
s2
      , requirements :: [Requirement a]
requirements      = Spec a -> [Requirement a]
forall a. Spec a -> [Requirement a]
requirements Spec a
s1 [Requirement a] -> [Requirement a] -> [Requirement a]
forall a. [a] -> [a] -> [a]
++ Spec a -> [Requirement a]
forall a. Spec a -> [Requirement a]
requirements Spec a
s2
      }

    -- Unsafely unwrap the spec in an input file.
    --
    -- PRE: The argument input file contains a spec.
    getSpec :: InputFile a -> Spec a
    getSpec :: forall a. InputFile a -> Spec a
getSpec (InputFileSpec Spec a
s) = Spec a
s
    getSpec InputFile a
_                 = [Char] -> Spec a
forall a. HasCallStack => [Char] -> a
error [Char]
"The input file provided is not a spec"

-- | Process input file, it contains a valid diagram or specification, and
-- return its abstract representation.
parseInputFile :: FilePath
               -> String
               -> String
               -> Maybe String
               -> ExprPairT a
               -> ExceptT ErrorTriplet IO (InputFile a)
parseInputFile :: forall a.
[Char]
-> [Char]
-> [Char]
-> Maybe [Char]
-> ExprPairT a
-> ExceptT ErrorTriplet IO (InputFile a)
parseInputFile [Char]
fp [Char]
formatName [Char]
propFormatName Maybe [Char]
propVia ExprPairT a
exprT
    | [Char] -> Bool
isDiagramFormat [Char]
formatName
    = Diagram -> InputFile a
forall a. Diagram -> InputFile a
InputFileDiagram (Diagram -> InputFile a)
-> ExceptT ErrorTriplet IO Diagram
-> ExceptT ErrorTriplet IO (InputFile a)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$>
        [Char]
-> DiagramFormat -> ExprPair -> ExceptT ErrorTriplet IO Diagram
readDiagram [Char]
fp DiagramFormat
diagramFormat (ExprPairT a -> ExprPair
forall a. ExprPairT a -> ExprPair
ExprPair ExprPairT a
exprT)

    | Bool
otherwise
    = Spec a -> InputFile a
forall a. Spec a -> InputFile a
InputFileSpec (Spec a -> InputFile a)
-> ExceptT ErrorTriplet IO (Spec a)
-> ExceptT ErrorTriplet IO (InputFile a)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$>
        [Char]
-> [Char]
-> [Char]
-> Maybe [Char]
-> ExprPairT a
-> ExceptT ErrorTriplet IO (Spec a)
forall a.
[Char]
-> [Char]
-> [Char]
-> Maybe [Char]
-> ExprPairT a
-> ExceptT ErrorTriplet IO (Spec a)
readInputFile [Char]
fp [Char]
formatName [Char]
propFormatName Maybe [Char]
propVia ExprPairT a
exprT

  where

    isDiagramFormat :: String -> Bool
    isDiagramFormat :: [Char] -> Bool
isDiagramFormat [Char]
fName = [Char]
fName [Char] -> [[Char]] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ [Char]
"dot", [Char]
"mermaid" ]

    diagramFormat :: DiagramFormat
    diagramFormat :: DiagramFormat
diagramFormat
      | [Char]
formatName [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char]
"dot"     = DiagramFormat
Dot
      | [Char]
formatName [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char]
"mermaid" = DiagramFormat
Mermaid
      | Bool
otherwise               = [Char] -> DiagramFormat
forall a. HasCallStack => [Char] -> a
error ([Char] -> DiagramFormat) -> [Char] -> DiagramFormat
forall a b. (a -> b) -> a -> b
$
         [Char]
"diagramFormat: Not a diagram format " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char] -> [Char]
forall a. Show a => a -> [Char]
show [Char]
formatName

-- | Process a variable selection file, if available, and return the variable
-- names.
parseVariablesFile :: Maybe FilePath
                   -> ExceptT ErrorTriplet IO (Maybe [String])
parseVariablesFile :: Maybe [Char] -> ExceptT ErrorTriplet IO (Maybe [[Char]])
parseVariablesFile Maybe [Char]
Nothing   = Maybe [[Char]] -> ExceptT ErrorTriplet IO (Maybe [[Char]])
forall a. a -> ExceptT ErrorTriplet IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe [[Char]]
forall a. Maybe a
Nothing
parseVariablesFile (Just [Char]
fp) = do
  -- Fail if the file cannot be opened.
  varNamesE <- IO (Either SomeException [[Char]])
-> ExceptT ErrorTriplet IO (Either SomeException [[Char]])
forall a. IO a -> ExceptT ErrorTriplet IO a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Either SomeException [[Char]])
 -> ExceptT ErrorTriplet IO (Either SomeException [[Char]]))
-> IO (Either SomeException [[Char]])
-> ExceptT ErrorTriplet IO (Either SomeException [[Char]])
forall a b. (a -> b) -> a -> b
$ IO [[Char]] -> IO (Either SomeException [[Char]])
forall e a. Exception e => IO a -> IO (Either e a)
E.try (IO [[Char]] -> IO (Either SomeException [[Char]]))
-> IO [[Char]] -> IO (Either SomeException [[Char]])
forall a b. (a -> b) -> a -> b
$ [Char] -> [[Char]]
lines ([Char] -> [[Char]]) -> IO [Char] -> IO [[Char]]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Char] -> IO [Char]
readFile [Char]
fp
  case (varNamesE :: Either E.SomeException [String]) of
    Left SomeException
_         -> ErrorTriplet -> ExceptT ErrorTriplet IO (Maybe [[Char]])
forall a. ErrorTriplet -> ExceptT ErrorTriplet IO a
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (ErrorTriplet -> ExceptT ErrorTriplet IO (Maybe [[Char]]))
-> ErrorTriplet -> ExceptT ErrorTriplet IO (Maybe [[Char]])
forall a b. (a -> b) -> a -> b
$ [Char] -> ErrorTriplet
cannotOpenVarFile [Char]
fp
    Right [[Char]]
varNames -> Maybe [[Char]] -> ExceptT ErrorTriplet IO (Maybe [[Char]])
forall a. a -> ExceptT ErrorTriplet IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe [[Char]] -> ExceptT ErrorTriplet IO (Maybe [[Char]]))
-> Maybe [[Char]] -> ExceptT ErrorTriplet IO (Maybe [[Char]])
forall a b. (a -> b) -> a -> b
$ [[Char]] -> Maybe [[Char]]
forall a. a -> Maybe a
Just [[Char]]
varNames

-- | Process a requirements / handlers list file, if available, and return the
-- handler names.
parseRequirementsListFile :: Maybe FilePath
                          -> ExceptT ErrorTriplet IO (Maybe [String])
parseRequirementsListFile :: Maybe [Char] -> ExceptT ErrorTriplet IO (Maybe [[Char]])
parseRequirementsListFile Maybe [Char]
Nothing   = Maybe [[Char]] -> ExceptT ErrorTriplet IO (Maybe [[Char]])
forall a. a -> ExceptT ErrorTriplet IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe [[Char]]
forall a. Maybe a
Nothing
parseRequirementsListFile (Just [Char]
fp) =
  IO (Either ErrorTriplet (Maybe [[Char]]))
-> ExceptT ErrorTriplet IO (Maybe [[Char]])
forall e (m :: * -> *) a. m (Either e a) -> ExceptT e m a
ExceptT (IO (Either ErrorTriplet (Maybe [[Char]]))
 -> ExceptT ErrorTriplet IO (Maybe [[Char]]))
-> IO (Either ErrorTriplet (Maybe [[Char]]))
-> ExceptT ErrorTriplet IO (Maybe [[Char]])
forall a b. (a -> b) -> a -> b
$ ErrorTriplet
-> Either SomeException (Maybe [[Char]])
-> Either ErrorTriplet (Maybe [[Char]])
forall c b. c -> Either SomeException b -> Either c b
makeLeftE ([Char] -> ErrorTriplet
cannotOpenHandlersFile [Char]
fp) (Either SomeException (Maybe [[Char]])
 -> Either ErrorTriplet (Maybe [[Char]]))
-> IO (Either SomeException (Maybe [[Char]]))
-> IO (Either ErrorTriplet (Maybe [[Char]]))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$>
    (IO (Maybe [[Char]]) -> IO (Either SomeException (Maybe [[Char]]))
forall e a. Exception e => IO a -> IO (Either e a)
E.try (IO (Maybe [[Char]]) -> IO (Either SomeException (Maybe [[Char]])))
-> IO (Maybe [[Char]])
-> IO (Either SomeException (Maybe [[Char]]))
forall a b. (a -> b) -> a -> b
$ [[Char]] -> Maybe [[Char]]
forall a. a -> Maybe a
Just ([[Char]] -> Maybe [[Char]])
-> ([Char] -> [[Char]]) -> [Char] -> Maybe [[Char]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> [[Char]]
lines ([Char] -> Maybe [[Char]]) -> IO [Char] -> IO (Maybe [[Char]])
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Char] -> IO [Char]
readFile [Char]
fp)

-- | Read a list of variable DBs.
openVarDBFiles :: VariableDB
               -> [FilePath]
               -> ExceptT ErrorTriplet IO VariableDB
openVarDBFiles :: VariableDB -> [[Char]] -> ExceptT ErrorTriplet IO VariableDB
openVarDBFiles VariableDB
acc []     = VariableDB -> ExceptT ErrorTriplet IO VariableDB
forall a. a -> ExceptT ErrorTriplet IO a
forall (m :: * -> *) a. Monad m => a -> m a
return VariableDB
acc
openVarDBFiles VariableDB
acc ([Char]
x:[[Char]]
xs) = do
    file <- Maybe [Char] -> ExceptT ErrorTriplet IO VariableDB
parseVarDBFile ([Char] -> Maybe [Char]
forall a. a -> Maybe a
Just [Char]
x)
    acc' <- mergeVariableDB acc file
    openVarDBFiles acc' xs

  where

    -- Process a variable database file, if available.
    parseVarDBFile :: Maybe FilePath
                   -> ExceptT ErrorTriplet IO VariableDB
    parseVarDBFile :: Maybe [Char] -> ExceptT ErrorTriplet IO VariableDB
parseVarDBFile Maybe [Char]
Nothing   = VariableDB -> ExceptT ErrorTriplet IO VariableDB
forall a. a -> ExceptT ErrorTriplet IO a
forall (m :: * -> *) a. Monad m => a -> m a
return VariableDB
emptyVariableDB
    parseVarDBFile (Just [Char]
fn) =
      IO (Either ErrorTriplet VariableDB)
-> ExceptT ErrorTriplet IO VariableDB
forall e (m :: * -> *) a. m (Either e a) -> ExceptT e m a
ExceptT (IO (Either ErrorTriplet VariableDB)
 -> ExceptT ErrorTriplet IO VariableDB)
-> IO (Either ErrorTriplet VariableDB)
-> ExceptT ErrorTriplet IO VariableDB
forall a b. (a -> b) -> a -> b
$ ErrorTriplet
-> Either [Char] VariableDB -> Either ErrorTriplet VariableDB
forall c a b. c -> Either a b -> Either c b
makeLeft ([Char] -> ErrorTriplet
cannotOpenDB [Char]
fn) (Either [Char] VariableDB -> Either ErrorTriplet VariableDB)
-> IO (Either [Char] VariableDB)
-> IO (Either ErrorTriplet VariableDB)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$>
        [Char] -> IO (Either [Char] VariableDB)
forall a. FromJSON a => [Char] -> IO (Either [Char] a)
eitherDecodeFileStrict [Char]
fn

-- | Read a list of variable DBs, as well as the default variable DB.
openVarDBFilesWithDefault :: [FilePath]
                          -> ExceptT ErrorTriplet IO VariableDB
openVarDBFilesWithDefault :: [[Char]] -> ExceptT ErrorTriplet IO VariableDB
openVarDBFilesWithDefault [[Char]]
files = do
  dataDir <- IO [Char] -> ExceptT ErrorTriplet IO [Char]
forall a. IO a -> ExceptT ErrorTriplet IO a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO [Char]
getDataDir
  let defaultDB = [Char]
dataDir [Char] -> [Char] -> [Char]
</> [Char]
"data" [Char] -> [Char] -> [Char]
</> [Char]
"variable-db.json"
  openVarDBFiles emptyVariableDB (files ++ [defaultDB])

-- | Process a JSON file with additional template variables to make available
-- during template expansion.
parseTemplateVarsFile :: Maybe FilePath
                      -> ExceptT ErrorTriplet IO Value
parseTemplateVarsFile :: Maybe [Char] -> ExceptT ErrorTriplet IO Value
parseTemplateVarsFile Maybe [Char]
Nothing   = Value -> ExceptT ErrorTriplet IO Value
forall a. a -> ExceptT ErrorTriplet IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Value -> ExceptT ErrorTriplet IO Value)
-> Value -> ExceptT ErrorTriplet IO Value
forall a b. (a -> b) -> a -> b
$ [Pair] -> Value
object []
parseTemplateVarsFile (Just [Char]
fp) = do
  content <- IO (Either [Char] ByteString)
-> ExceptT ErrorTriplet IO (Either [Char] ByteString)
forall a. IO a -> ExceptT ErrorTriplet IO a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Either [Char] ByteString)
 -> ExceptT ErrorTriplet IO (Either [Char] ByteString))
-> IO (Either [Char] ByteString)
-> ExceptT ErrorTriplet IO (Either [Char] ByteString)
forall a b. (a -> b) -> a -> b
$ [Char] -> IO (Either [Char] ByteString)
B.safeReadFile [Char]
fp
  let value = ByteString -> Either [Char] Value
forall a. FromJSON a => ByteString -> Either [Char] a
eitherDecode (ByteString -> Either [Char] Value)
-> Either [Char] ByteString -> Either [Char] Value
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Either [Char] ByteString
content
  case value of
    Right x :: Value
x@(Object Object
_) -> Value -> ExceptT ErrorTriplet IO Value
forall a. a -> ExceptT ErrorTriplet IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Value
x
    Right x :: Value
x@Value
Null       -> Value -> ExceptT ErrorTriplet IO Value
forall a. a -> ExceptT ErrorTriplet IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Value
x
    Right Value
_            -> ErrorTriplet -> ExceptT ErrorTriplet IO Value
forall a. ErrorTriplet -> ExceptT ErrorTriplet IO a
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError ([Char] -> ErrorTriplet
cannotReadObjectTemplateVars [Char]
fp)
    Either [Char] Value
_                  -> ErrorTriplet -> ExceptT ErrorTriplet IO Value
forall a. ErrorTriplet -> ExceptT ErrorTriplet IO a
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError ([Char] -> ErrorTriplet
cannotOpenTemplateVars [Char]
fp)

-- | Check that the arguments provided are sufficient to operate.
--
-- The backend provides several modes of operation, which are selected
-- by providing different arguments to the `ros` command.
--
-- When an input specification file is provided, the variables and requirements
-- defined in it are used unless variables or handlers files are provided, in
-- which case the latter take priority.
--
-- If an input file is not provided, then the user must provide BOTH a variable
-- list, and a list of handlers.
checkArguments :: Maybe (InputFile a)
               -> Maybe [String]
               -> Maybe [String]
               -> Either ErrorTriplet ()
checkArguments :: forall a.
Maybe (InputFile a)
-> Maybe [[Char]] -> Maybe [[Char]] -> Either ErrorTriplet ()
checkArguments Maybe (InputFile a)
Nothing Maybe [[Char]]
Nothing   Maybe [[Char]]
Nothing   = ErrorTriplet -> Either ErrorTriplet ()
forall a b. a -> Either a b
Left ErrorTriplet
wrongArguments
checkArguments Maybe (InputFile a)
Nothing Maybe [[Char]]
Nothing   Maybe [[Char]]
_         = ErrorTriplet -> Either ErrorTriplet ()
forall a b. a -> Either a b
Left ErrorTriplet
wrongArguments
checkArguments Maybe (InputFile a)
Nothing Maybe [[Char]]
_         Maybe [[Char]]
Nothing   = ErrorTriplet -> Either ErrorTriplet ()
forall a b. a -> Either a b
Left ErrorTriplet
wrongArguments
checkArguments Maybe (InputFile a)
_       (Just []) Maybe [[Char]]
_         = ErrorTriplet -> Either ErrorTriplet ()
forall a b. a -> Either a b
Left ErrorTriplet
wrongArguments
checkArguments Maybe (InputFile a)
_       Maybe [[Char]]
_         (Just []) = ErrorTriplet -> Either ErrorTriplet ()
forall a b. a -> Either a b
Left ErrorTriplet
wrongArguments
checkArguments Maybe (InputFile a)
_       Maybe [[Char]]
_         Maybe [[Char]]
_         = () -> Either ErrorTriplet ()
forall a b. b -> Either a b
Right ()

-- | Extract the variables from a specification, and sanitize them.
specExtractExternalVariables :: Maybe (Spec a) -> [String]
specExtractExternalVariables :: forall a. Maybe (Spec a) -> [[Char]]
specExtractExternalVariables Maybe (Spec a)
Nothing   = []
specExtractExternalVariables (Just Spec a
cs) = ([Char] -> [Char]) -> [[Char]] -> [[Char]]
forall a b. (a -> b) -> [a] -> [b]
map [Char] -> [Char]
sanitizeLCIdentifier
                                       ([[Char]] -> [[Char]]) -> [[Char]] -> [[Char]]
forall a b. (a -> b) -> a -> b
$ (ExternalVariableDef -> [Char])
-> [ExternalVariableDef] -> [[Char]]
forall a b. (a -> b) -> [a] -> [b]
map ExternalVariableDef -> [Char]
externalVariableName
                                       ([ExternalVariableDef] -> [[Char]])
-> [ExternalVariableDef] -> [[Char]]
forall a b. (a -> b) -> a -> b
$ Spec a -> [ExternalVariableDef]
forall a. Spec a -> [ExternalVariableDef]
externalVariables Spec a
cs

-- | Extract the requirements from a specification, and sanitize them to match
-- the names of the handlers used by Copilot.
specExtractHandlers :: Maybe (Spec a) -> [(String, Maybe String)]
specExtractHandlers :: forall a. Maybe (Spec a) -> [([Char], Maybe [Char])]
specExtractHandlers Maybe (Spec a)
Nothing   = []
specExtractHandlers (Just Spec a
cs) = (Requirement a -> ([Char], Maybe [Char]))
-> [Requirement a] -> [([Char], Maybe [Char])]
forall a b. (a -> b) -> [a] -> [b]
map Requirement a -> ([Char], Maybe [Char])
forall {a}. Requirement a -> ([Char], Maybe [Char])
extractReqData
                              ([Requirement a] -> [([Char], Maybe [Char])])
-> [Requirement a] -> [([Char], Maybe [Char])]
forall a b. (a -> b) -> a -> b
$ Spec a -> [Requirement a]
forall a. Spec a -> [Requirement a]
requirements Spec a
cs
  where
    extractReqData :: Requirement a -> ([Char], Maybe [Char])
extractReqData Requirement a
r =
      ([Char] -> [Char]
handlerNameF (Requirement a -> [Char]
forall a. Requirement a -> [Char]
requirementName Requirement a
r), Requirement a -> Maybe [Char]
forall a. Requirement a -> Maybe [Char]
requirementResultType Requirement a
r)

    handlerNameF :: [Char] -> [Char]
handlerNameF = ([Char]
"handler" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++) ([Char] -> [Char]) -> ([Char] -> [Char]) -> [Char] -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> [Char]
sanitizeUCIdentifier

-- * Errors

-- | Process a computation that can fail with an error code, and turn it into a
-- computation that returns a 'Result'.
processResult :: Monad m => ExceptT ErrorTriplet m a -> m (Result ErrorCode)
processResult :: forall (m :: * -> *) a.
Monad m =>
ExceptT ErrorTriplet m a -> m (Result ErrorCode)
processResult ExceptT ErrorTriplet m a
m = do
  r <- ExceptT ErrorTriplet m a -> m (Either ErrorTriplet a)
forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT ExceptT ErrorTriplet m a
m
  case r of
    Left (ErrorTriplet ErrorCode
errorCode [Char]
msg Location
location)
      -> Result ErrorCode -> m (Result ErrorCode)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Result ErrorCode -> m (Result ErrorCode))
-> Result ErrorCode -> m (Result ErrorCode)
forall a b. (a -> b) -> a -> b
$ ErrorCode -> [Char] -> Location -> Result ErrorCode
forall a. a -> [Char] -> Location -> Result a
Error ErrorCode
errorCode [Char]
msg Location
location
    Either ErrorTriplet a
_ -> Result ErrorCode -> m (Result ErrorCode)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Result ErrorCode
forall a. Result a
Success

-- ** Error messages

-- | Exception handler to deal with the case in which the arguments
-- provided are incorrect.
wrongArguments :: ErrorTriplet
wrongArguments :: ErrorTriplet
wrongArguments =
    ErrorCode -> [Char] -> Location -> ErrorTriplet
ErrorTriplet ErrorCode
ecWrongArguments [Char]
msg Location
LocationNothing
  where
    msg :: [Char]
msg =
      [Char]
"the arguments provided are insufficient: you must provide an input "
      [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"specification, or both a variables and a handlers file."

-- | Exception handler to deal with the case in which the variable DB cannot be
-- opened.
cannotOpenDB :: FilePath -> ErrorTriplet
cannotOpenDB :: [Char] -> ErrorTriplet
cannotOpenDB [Char]
file =
    ErrorCode -> [Char] -> Location -> ErrorTriplet
ErrorTriplet ErrorCode
ecCannotOpenDBFile [Char]
msg ([Char] -> Location
LocationFile [Char]
file)
  where
    msg :: [Char]
msg =
      [Char]
"cannot open variable DB file " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
file

-- | Exception handler to deal with the case in which the variable file
-- provided by the user cannot be opened.
cannotOpenVarFile :: FilePath -> ErrorTriplet
cannotOpenVarFile :: [Char] -> ErrorTriplet
cannotOpenVarFile [Char]
file =
    ErrorCode -> [Char] -> Location -> ErrorTriplet
ErrorTriplet ErrorCode
ecCannotOpenVarFile  [Char]
msg ([Char] -> Location
LocationFile [Char]
file)
  where
    msg :: [Char]
msg =
      [Char]
"cannot open variable list file " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
file

-- | Exception handler to deal with the case in which the handlers file cannot
-- be opened.
cannotOpenHandlersFile :: FilePath -> ErrorTriplet
cannotOpenHandlersFile :: [Char] -> ErrorTriplet
cannotOpenHandlersFile [Char]
file =
    ErrorCode -> [Char] -> Location -> ErrorTriplet
ErrorTriplet ErrorCode
ecCannotOpenHandlersFile [Char]
msg ([Char] -> Location
LocationFile [Char]
file)
  where
    msg :: [Char]
msg =
      [Char]
"cannot open handlers file " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
file

-- | Exception handler to deal with the case in which the template vars file
-- cannot be opened.
cannotOpenTemplateVars :: FilePath -> ErrorTriplet
cannotOpenTemplateVars :: [Char] -> ErrorTriplet
cannotOpenTemplateVars [Char]
file =
    ErrorCode -> [Char] -> Location -> ErrorTriplet
ErrorTriplet ErrorCode
ecCannotOpenTemplateVarsFile [Char]
msg ([Char] -> Location
LocationFile [Char]
file)
  where
    msg :: [Char]
msg =
      [Char]
"Cannot open file with additional template variables: " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
file

-- | Exception handler to deal with the case in which the template vars file
-- cannot be opened.
cannotReadObjectTemplateVars :: FilePath -> ErrorTriplet
cannotReadObjectTemplateVars :: [Char] -> ErrorTriplet
cannotReadObjectTemplateVars [Char]
file =
    ErrorCode -> [Char] -> Location -> ErrorTriplet
ErrorTriplet ErrorCode
ecCannotReadObjectTemplateVarsFile [Char]
msg ([Char] -> Location
LocationFile [Char]
file)
  where
    msg :: [Char]
msg =
      [Char]
"Cannot open file with additional template variables: " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
file

-- | Exception handler to deal with the case of files that cannot be
-- copied/generated due lack of space or permissions or some I/O error.
cannotCopyTemplate :: ErrorTriplet
cannotCopyTemplate :: ErrorTriplet
cannotCopyTemplate =
    ErrorCode -> [Char] -> Location -> ErrorTriplet
ErrorTriplet ErrorCode
ecCannotCopyTemplate [Char]
msg Location
LocationNothing
  where
    msg :: [Char]
msg =
      [Char]
"Generation failed during copy/write operation. Check that"
      [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
" there's free space in the disk and that you have the necessary"
      [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
" permissions to write in the destination directory."

-- ** Error codes

-- | Error: wrong arguments provided.
ecWrongArguments :: ErrorCode
ecWrongArguments :: ErrorCode
ecWrongArguments = ErrorCode
1

-- | Error: the variable DB provided by the user cannot be opened.
ecCannotOpenDBFile :: ErrorCode
ecCannotOpenDBFile :: ErrorCode
ecCannotOpenDBFile = ErrorCode
1

-- | Error: the variable file provided by the user cannot be opened.
ecCannotOpenVarFile :: ErrorCode
ecCannotOpenVarFile :: ErrorCode
ecCannotOpenVarFile = ErrorCode
1

-- | Error: the handlers file provided by the user cannot be opened.
ecCannotOpenHandlersFile :: ErrorCode
ecCannotOpenHandlersFile :: ErrorCode
ecCannotOpenHandlersFile = ErrorCode
1

-- | Error: the template vars file provided by the user cannot be opened.
ecCannotOpenTemplateVarsFile :: ErrorCode
ecCannotOpenTemplateVarsFile :: ErrorCode
ecCannotOpenTemplateVarsFile = ErrorCode
1

-- | Error: the template variables file passed does not contain a JSON object.
ecCannotReadObjectTemplateVarsFile :: ErrorCode
ecCannotReadObjectTemplateVarsFile :: ErrorCode
ecCannotReadObjectTemplateVarsFile = ErrorCode
1

-- | Error: the files cannot be copied/generated due lack of space or
-- permissions or some I/O error.
ecCannotCopyTemplate :: ErrorCode
ecCannotCopyTemplate :: ErrorCode
ecCannotCopyTemplate = ErrorCode
1

-- * Auxiliary Functions

-- | Return the path to the template directory.
locateTemplateDir :: Maybe FilePath
                  -> FilePath
                  -> ExceptT e IO FilePath
locateTemplateDir :: forall e. Maybe [Char] -> [Char] -> ExceptT e IO [Char]
locateTemplateDir Maybe [Char]
mTemplateDir [Char]
name =
  case Maybe [Char]
mTemplateDir of
    Just [Char]
x  -> [Char] -> ExceptT e IO [Char]
forall a. a -> ExceptT e IO a
forall (m :: * -> *) a. Monad m => a -> m a
return [Char]
x
    Maybe [Char]
Nothing -> IO [Char] -> ExceptT e IO [Char]
forall a. IO a -> ExceptT e IO a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO [Char] -> ExceptT e IO [Char])
-> IO [Char] -> ExceptT e IO [Char]
forall a b. (a -> b) -> a -> b
$ do
      dataDir <- IO [Char]
getDataDir
      return $ dataDir </> "templates" </> name

-- | Replace the left Exception in an Either.
makeLeftE :: c -> Either E.SomeException b -> Either c b
makeLeftE :: forall c b. c -> Either SomeException b -> Either c b
makeLeftE = c -> Either SomeException b -> Either c b
forall c a b. c -> Either a b -> Either c b
makeLeft