packages feed

hprotoc-0.3.1: Text/ProtocolBuffers/ProtoCompile.hs

-- | This is the Main module for the command line program
module Main where

import qualified Data.Map as M
import Data.Version
import Language.Haskell.Pretty(prettyPrintStyleMode,Style(..),Mode(..),PPHsMode(..),PPLayout(..))
import System.Console.GetOpt
import System.Environment
import System.Directory
import System.FilePath

import Text.ProtocolBuffers.Reflections(ProtoInfo(..),DescriptorInfo(..),EnumInfo(..))

import Text.ProtocolBuffers.ProtoCompile.Gen(protoModule,descriptorModule,enumModule)
import Text.ProtocolBuffers.ProtoCompile.Resolve(loadProto)
import Text.ProtocolBuffers.ProtoCompile.MakeReflections(makeProtoInfo,serializeFDP)

-- | Version of protocol-buffers.
-- The version tags that I have used are ["unreleased"]
version :: Version
version = Version { versionBranch = [0,3,1]
                  , versionTags = [] }

data Options = Options { optPrefix :: String
                       , optTarget :: FilePath
                       , optInclude :: [FilePath]
                       , optProto :: FilePath
                       , optVerbose :: Bool
                       , optUnknownFields :: Bool }
  deriving Show

setPrefix,setTarget,setInclude,setProto :: String -> Options -> Options
setVerbose,setUnknown :: Options -> Options
setPrefix   s o = o { optPrefix = s }
setTarget   s o = o { optTarget = s }
setInclude  s o = o { optInclude = s : optInclude o }
setProto    s o = o { optProto = s }
setVerbose    o = o { optVerbose = True }
setUnknown    o = o { optUnknownFields = True }

data OptionAction = Mutate (Options->Options) | Run (Options->Options) | Switch Flag

data Flag = VersionInfo

optionList :: [OptDescr OptionAction]
optionList =
  [ Option ['I'] ["proto_path"] (ReqArg (Mutate . setInclude) "DIR")
               "directory from which to search for imported proto files (default is pwd); all DIR searched"
  , Option ['o'] ["haskell_out"] (ReqArg (Mutate . setTarget) "DIR")
               "directory to use are root of generated files (default is pwd); last flag"
  , Option ['p'] ["prefix"] (ReqArg (Mutate . setPrefix) "MODULE")
               "dotted haskell MODULE name to use as prefix (default is none); last flag used"
  , Option ['u'] ["unknown-fields"] (NoArg (Mutate setUnknown))
               "generated messages and groups all support unknown fields"
  , Option ['v'] ["verbose"] (NoArg (Mutate  setVerbose))
               "increase amount of printed information"
  , Option [] ["version"]  (NoArg (Switch VersionInfo))
               "print out version information"
  ]

usageMsg,versionInfo :: String
usageMsg = usageInfo "Usage: protoCompile [OPTION..] path-to-file.proto ..." optionList

versionInfo = unlines $
  [ "Welcome to protocol-buffers version "++showVersion version
  , "Copyright (c) 2008, Christopher Kuklewicz."
  , "Released under BSD3 style license, see LICENSE file for details."
  , "Some proto files, such as descriptor.proto and unittest*.proto"
  , "are from google's code and are under an Apache 2.0 license."
  , ""
  , "This program reads a .proto file and generates haskell code files."
  , "See http://code.google.com/apis/protocolbuffers/docs/overview.html for more."
  ]

processOptions :: [String] -> Either String [OptionAction]
processOptions argv =
    case getOpt (ReturnInOrder (Run . setProto)) optionList argv of
    (opts,_,[]) -> Right opts
    (_,_,errs) -> Left (unlines errs ++ usageMsg)

defaultOptions :: IO Options
defaultOptions = do
  pwd <- getCurrentDirectory
  return $ Options { optPrefix = "", optTarget = pwd, optInclude = [pwd], optProto = "", optVerbose = False, optUnknownFields = False }

main :: IO ()
main = do
  defs <- defaultOptions
  args <- getArgs
  case processOptions args of
    Left msg -> putStrLn msg
    Right todo -> process defs todo

process :: Options -> [OptionAction] -> IO ()
process options [] = if null (optProto options)
                       then do putStrLn "No proto file specified (or empty proto file)"
                               putStrLn ""
                               putStrLn usageMsg
                       else putStrLn "Processing complete, have a nice day."
process options (Mutate f:rest) = process (f options) rest
process options (Run f:rest) = let options' = f options
                            in run options' >> process options' rest
process _options (Switch VersionInfo:_) = putStrLn versionInfo
  
mkdirFor :: FilePath -> IO ()
mkdirFor p = createDirectoryIfMissing True (takeDirectory p)

style :: Style
style = Style PageMode 132 0.5

myMode :: PPHsMode
myMode = PPHsMode 2 2 2 2 4 2 True PPOffsideRule False True

run :: Options -> IO ()
run options = do
  print options
  protos <- loadProto (optInclude options) (optProto options)
  let (Just (fdp,_,names)) = M.lookup (optProto options) protos
      protoInfo = makeProtoInfo (optUnknownFields options) (optPrefix options) names fdp
  let produceMSG di = do
        let file = combine (optTarget options) . joinPath . descFilePath $ di
        print file
        mkdirFor file
        writeFile file (prettyPrintStyleMode style myMode (descriptorModule di))
      produceENM ei = do
        let file = combine (optTarget options) . joinPath . enumFilePath $ ei
        print file
        mkdirFor file
        writeFile file (prettyPrintStyleMode style myMode (enumModule ei))
  mapM_ produceMSG (messages protoInfo)
  mapM_ produceENM (enums protoInfo)

  let file = combine (optTarget options) . joinPath . protoFilePath $ protoInfo
  print file
  writeFile file (prettyPrintStyleMode style myMode (protoModule protoInfo (serializeFDP fdp)))