packages feed

rosmsg-bin-0.1.0.0: app/genhs/Template.hs

{-# LANGUAGE OverloadedStrings #-}
module Template (newTemplate) where

import Data.Text (Text, unpack, pack,
                  toUpper, replace, takeEnd)
import qualified Data.Text as T
import Data.Text.Lazy.IO as TL
import Data.Text.Lazy.Builder
import Data.Maybe (catMaybes)
import Control.Monad (forM)
import System.FilePath
import Data.Monoid

import Robotics.ROS.Msg.Parser
import Robotics.ROS.Msg.Types
import Robotics.ROS.Pkg

newTemplate :: FilePath -> Package -> [FilePath] -> IO FilePath
newTemplate dir pkg msgFiles = do
    deps <- forM msgFiles $ \f -> do
        let msgPath = joinPath [path pkg, "msg", f]
        Done _ msg <- parse rosmsg <$> TL.readFile msgPath 
        depPkgs    <- catMaybes <$> mapM package (pkgBuildDeps (meta pkg))
        deps       <- zip depPkgs <$> mapM pkgMessages depPkgs
        return $ concatMap (mkDeps msg) ((pkg, msgFiles) : deps)

    TL.writeFile tFileName $ toLazyText $ tBuilder deps
    return tFileName
  where
    tFileName     = joinPath [dir, unpack (pkgName (meta pkg)) ++ ".hsfiles"]
    tBuilder deps = cabalBuilder pkg msgFiles
                 <> mconcat (msgBuilder pkg <$> zip msgFiles deps)
                 <> stackBuilder pkg

upperFirst :: Text -> Text
upperFirst x  = toUpper (T.take 1 x) <> T.drop 1 x

-- List of custom type names in Message
customTypes :: MsgDefinition -> [Text]
customTypes = fmap pkgInTypeHook . catMaybes . fmap go
  where go (Variable (t, _))   = custom t
        go (Constant (t, _) _) = custom t
        custom (Custom t)                = Just t
        custom (Array (Custom t))        = Just t
        custom (FixedArray _ (Custom t)) = Just t
        custom  _ = Nothing
        pkgInTypeHook = last . T.split (== '/')

-- Associate used in message custom type with package
mkDeps :: MsgDefinition -> (Package, [FilePath]) -> [(Package, FilePath)]
mkDeps msg (pkg, files) = catMaybes (go <$> files)
  where go m | isCustomType m = Just (pkg, m) 
             | otherwise      = Nothing
        messageName    = pack . takeWhile (/= '.')
        isCustomType m = messageName m `elem` customTypes msg

cabalBuilder :: Package -> [FilePath] -> Builder
cabalBuilder pkg msgs =
       "{-# START_FILE " <> fromText (sanitize name) <> ".cabal #-}"
    <> "\nname:                " <> fromText (sanitize name)
    <> "\nversion:             0.1"
    <> "\nsynopsis:            Autogenerated " <> fromText name <> " ROS messages"
    <> "\ndescription:         " <> fromText description 
    <> "\nlicense:             BSD3"
    <> "\ncategory:            Robotics"
    <> "\nbuild-type:          Simple"
    <> "\ncabal-version:       >=1.10"
    <> "\n"
    <> "\nlibrary"
    <> "\n  hs-source-dirs:      src"
    <> "\n  default-language:    Haskell2010"
    <> "\n  build-depends:       base >= 4.7 && < 5"
    <> "\n                     , binary"
    <> "\n                     , pureMD5"
    <> "\n                     , bytestring"
    <> "\n                     , lens-family" 
    <> "\n                     , data-default" 
    <> mconcat (msgBuildDeps <$> pkgBuildDeps (meta pkg))
    <> "\n  exposed-modules:     "
    <> msgMainModule <> "." <> messageName (head msgs) 
    <> mconcat (msgModule <$> drop 1 msgs)
    <> "\n\n{-# START_FILE Setup.hs #-}"
    <> "\nimport Distribution.Simple"
    <> "\nmain = defaultMain\n"

  where name        = pkgName (meta pkg) 
        description = replace "\n" "" $ pkgDescription (meta pkg)
        sanitize    = replace "_" "-"
        messageName = fromString . takeWhile (/= '.')
        space       = "                     " 
        msgBuildDeps "message_generation" = "\n" <> space <> ", rosmsg"
        msgBuildDeps dep =
            case takeEnd 4 dep of
                "msgs" -> "\n" <> space <> ", " <> fromText (sanitize dep) 
                _      -> mempty
        msgMainModule = "Robotics.ROS.Msg." <> fromText (upperFirst name)
        msgModule m  = "\n" <> space <> ", " <> msgMainModule <> "."
                            <> messageName m

msgBuilder :: Package -> (FilePath, [(Package, FilePath)]) -> Builder
msgBuilder pkg (msgFile, deps) =
       "\n{-# START_FILE src/Robotics/ROS/Msg/" <> packageName pkg
                                                <> "/"
                                                <> messageName msgFile
                                                <> ".hs #-}"
    <> "\n{-# LANGUAGE DataKinds, KindSignatures, DeriveGeneric"
    <> "\n  , DeriveDataTypeable, QuasiQuotes, OverloadedStrings #-}"
    <> "\nmodule " <> msgMainModule <> "." <> messageName msgFile <> " where\n"
    <> "\nimport           Data.Default (Default(..))"
    <> "\nimport           Data.Typeable (Typeable)"
    <> "\nimport           GHC.Generics (Generic)"
    <> "\nimport           Data.Binary (Binary)"
    <> "\nimport           Data.Data (Data)"
    <> "\nimport qualified Data.ByteString as BS"
    <> "\nimport qualified Data.Word as W"
    <> "\nimport qualified Data.Int as I"
    <> "\nimport qualified Prelude as P"
    <> "\n"
    <> mconcat (msgImport <$> deps)
    <> "\n"
    <> "\nimport           Robotics.ROS.Msg.TH"
    <> "\nimport           Robotics.ROS.Msg"
    <> "\n"
    <> "\n[rosmsgFrom|" <> fromString (joinPath [path pkg, "msg", msgFile])
                        <> "|]\n"
  where packageName      = fromText . upperFirst . pkgName . meta
        messageName      = fromString . takeWhile (/= '.')
        msgMainModule    = "Robotics.ROS.Msg." <> packageName pkg
        msgImport (p, m) =
            "\nimport qualified Robotics.ROS.Msg."
                <> packageName p
                <> "."
                <> messageName m
                <> " as "
                <> messageName m

stackBuilder :: Package -> Builder
stackBuilder pkg =
    "\n{-# START_FILE stack.yaml #-}"
  <> "\nresolver: lts-6.16"
  <> "\npackages:"
  <> "\n- '.'"
  <> mconcat ((\p -> "\n- '../" <> fromText (sanitize p) <> "'") <$> deps)
  <> "\nextra-deps:"
  <> "\n- rosmsg-0.5.0.0"
  <> "\nflags: {}"
  <> "\nextra-package-dbs: []"
  where deps     = filter (elem "msgs" . T.tails) (pkgBuildDeps (meta pkg)) 
        sanitize = replace "_" "-"