module Travis.Meta (
preprocessIO
, preprocess
, preprocessYaml
, Env
, parseEnv
, interpolateEnv
) where
import Control.Lens
import Control.Monad hiding (sequence)
import Data.Aeson.Types
import Data.Aeson.Lens
import Data.ByteString as BS
import Data.Char
import Data.Maybe
import Data.Monoid
import Data.Text as T
import Data.Traversable
import Data.Yaml
import Prelude hiding (sequence)
import Text.Regex.Applicative
type Env = [(Text, Text)]
parseEnv :: Text -> Either String Env
parseEnv = traverse (f . T.splitOn "=") . T.words
where f [k, n] = Right (k, n)
f _ = Left "Cannot parse"
interpolationRe :: (String -> Maybe String) -> RE Char (Maybe String)
interpolationRe l = comb <$> many (interpolationChar l)
where comb :: [Maybe [a]] -> Maybe [a]
comb = fmap Prelude.concat . sequence
interpolationChar :: (String -> Maybe String) -> RE Char (Maybe String)
interpolationChar l = var <|> other
where var = l <$ sym '$' <*> many (psym isAlpha)
other = (\x -> Just [x]) <$> anySym
interpolateEnv :: Env -> Text -> Either String Text
interpolateEnv env = hoist . fmap T.pack . join . match (interpolationRe l) . T.unpack
where l = fmap T.unpack . flip lookup env . T.pack
hoist :: Maybe a -> Either String a
hoist Nothing = Left "error in interpolation"
hoist (Just a) = Right a
preprocessYaml :: Value -> Either String Value
preprocessYaml v = do
assertNoMatrixInclude v
matrixInclude <- buildMatrixInclude v
let v' = v & _Object . at "env" .~ Nothing
& _Object . at "addons" .~ Nothing
& _Object . at "matrix" ?~ (fromMaybe (Object mempty) (v ^? key "matrix"))
& key "matrix" . _Object . at "include" ?~ matrixInclude
return v'
buildMatrixInclude :: Value -> Either String Value
buildMatrixInclude v = toJSON <$> mk `traverse` envs
where addons = v ^? key "addons"
envs = v ^.. key "env" . values . _String
mk env = do env' <- parseEnv env
addons' <- traverseOf (deep _String) (interpolateEnv env') `traverse` addons
case addons' of
Just addons'' -> return $ object [ "env" Data.Yaml..= env, "addons" Data.Yaml..= addons'' ]
Nothing -> return $ object [ "env" Data.Yaml..= env ]
assertNoMatrixInclude :: Value -> Either String ()
assertNoMatrixInclude v =
case v ^? key "matrix" . key "include" of
Nothing -> Right ()
Just v' -> Left $ "matrix.include specified: " ++ show v'
preprocess :: ByteString -> Either String ByteString
preprocess = fmap encode . preprocessYaml <=< decodeEither
preprocessIO :: FilePath -> FilePath -> IO ()
preprocessIO source target = do
contents <- BS.readFile source
case preprocess contents of
Left err -> error err
Right bs -> BS.writeFile target bs