{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} module Template ( Template , parseTemplate , instantiateTemplate ) where import Sos import Control.Monad.Except import Data.ByteString (ByteString) import Data.Monoid import Text.Megaparsec import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Lazy.Char8 as BSL import qualified Data.ByteString.Builder as BS -- A template is a string that may contain anti-quoted capture groups. -- For example, -- -- "gcc -c \1.c -o \1.c" -- -- will become -- -- [Right "gcc -c ", Left 1, Right ".c -o ", Left 1, Right ".c"] type Template = [Either Int ByteString] parseTemplate :: MonadError SosException m => ByteString -> m Template parseTemplate template = case runParser parser "" template of Left err -> throwError (SosCommandParseException template err) Right x -> pure x where parser :: Parsec ByteString Template parser = some (Right <$> textPart <|> Left <$> capturePart) where textPart :: Parsec ByteString ByteString textPart = BS.pack <$> some (satisfy (/= '\\')) capturePart :: Parsec ByteString Int capturePart = read <$> (char '\\' *> some digitChar) -- Instantiate a template with a list of captured variables, per their indices. -- -- For example, -- -- instantiateTemplate ["ONE", "TWO"] [Right "foo", Left 0, Right "bar", Left 1] == "fooONEbarTWO" -- instantiateTemplate :: forall m. MonadError SosException m => [ByteString] -> Template -> m String instantiateTemplate vars0 template0 = go 0 vars0 template0 where go :: Int -> [ByteString] -> Template -> m String go _ [] template = case flattenTemplate template of Left err -> throwError (SosCommandApplyException template0 vars0 err) Right x -> pure x go n (t:ts) template = go (n+1) ts (map f template) where f :: Either Int ByteString -> Either Int ByteString f (Left n') | n == n' = Right t | otherwise = Left n' f x = x -- Attempt to flatten a list of Rights to a single string. flattenTemplate :: Template -> Either String String flattenTemplate = go mempty where go :: BS.Builder -> Template -> Either String String go acc [] = Right (BSL.unpack (BS.toLazyByteString acc)) go acc (Right x : xs) = go (acc <> BS.byteString x) xs go _ (Left n : _) = Left ("uninstantiated template variable \\" <> show n)