packages feed

thrift-compiler-0.3.0.0: Thrift/Compiler/OptParse.hs

{-
  Copyright (c) Meta Platforms, Inc. and affiliates.
  All rights reserved.

  This source code is licensed under the BSD-style license found in the
  LICENSE file in the root directory of this source tree.
-}

{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE MonadComprehensions #-}
{-# LANGUAGE CPP #-}
module Thrift.Compiler.OptParse
  ( optionsParser
  , ThriftLanguage
  , SomeOptions(..), SomeLangOpts(..)
  ) where

#if __GLASGOW_HASKELL__ <= 804
import Data.Monoid ( (<>) )
#endif
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Typeable
import Options.Applicative

import Thrift.Compiler.Options
import Thrift.Compiler.Plugin
import Thrift.Compiler.Plugins.Haskell
import Thrift.Compiler.Plugins.Linter

-- Types -----------------------------------------------------------------------

type ThriftLanguage l =
  ( Typecheckable l
  , Typeable l
  )

data SomeOptions = forall l. ThriftLanguage l => TheseOptions (Options l)

-- Hack because data families must be saturated
data SomeLangOpts = forall l. ThriftLanguage l => TheseLangOpts (LangOpts l)

-- Parsers ---------------------------------------------------------------------

optionsParser :: Parser SomeOptions
optionsParser = do
  optsPath <- strArgument $ mconcat
    [ metavar "INPUT"
    , help "Generate code for thrift file INPUT"
    ]
  optsOutPath <- strOption $ mconcat
    [ long "output-dir"
    , short 'o'
    , metavar "DIR"
    , help "Output generated files in DIR"
    , value "."
    ]
  optsIncludePath <- strOption $ mconcat
    [ long "include-dir"
    , short 'I'
    , metavar "DIR"
    , help "Base directory for includes DIR"
    , value "."
    ]
  optsThriftMade <- maybeStr
    [ long "record-genfiles"
    , metavar "GENFILES"
    , help "Output generated file to GENFILES"
    ]
  optsGenMode <- modeParser
  lo <- langOptsParser
  optsReqSymbols <- maybeList
    [ long "required-symbols"
    , help "List of symbols that should be generated"
    ]
  optsRecursive <- switch $ mconcat
    [ long "recursive"
    , short 'r'
    , help "Recursively generate dependencies"
    ]
  optsSingleOutput <- switch $ mconcat
    [ long "single-output"
    , help "When emitting JSON in recursive mode, dump output to a single file"
    ]
  optsLenient <- switch $ mconcat
    [ long "lenient"
    , help "Parse & Typecheck weird old thrift files, disables code generation"
    ]
  optsLenientStillGenCode <- switch $ mconcat
    [ long "lenient-still-gencode"
    , help "Do code generation for lenient runs"
    ]
  -- We can support parsing these options for compatibility, but they don't do
  -- anything
  _ <- switch (long "allow-64bit-consts")
  _ <- switch (long "allow-neg-keys")
  _ <- switch (long "allow-neg-enum-vals")
  _ <- maybeStr [long "templates"]
  _ <- maybeStr [long "extra-validation"]
  pure $ case lo of
    TheseLangOpts optsLangSpecific -> TheseOptions Options{..}

modeParser :: Parser Mode
modeParser =
  flag' Lint
  (mconcat
   [ long "lint"
   , help "Only invoke the typechecker, do not emit any code"
   ]) <|>
  flag' (EmitJSON WithoutLoc)
  (mconcat
   [ long "emit-json"
   , help "Dump the typechecked thrift AST as JSON in the output directory"
   ]) <|>
  flag' (EmitJSON WithLoc)
  (mconcat
   [ long "emit-json-loc"
   , help $ "Dump the typechecked thrift AST as JSON"
            <> " with Locations in the output directory"
   ]) <|>
  -- Default Mode is code generation
  pure EmitCode

langOptsParser :: Parser SomeLangOpts
langOptsParser =
  (TheseLangOpts <$> hsOptsParser) <|>
  pure (TheseLangOpts NoOpts)

hsOptsParser :: Parser (LangOpts Haskell)
hsOptsParser = do
  _ <- flag' () $ mconcat
    [ long "hs"
    , help "Target Haskell"
    ]
  hsoptsEnableHaddock <- switch $ mconcat
    [ long "enable-haddock"
    , help "Enable generating and building haddock"
    ]
  hsoptsUseInt <- switch $ mconcat
    [ long "use-int"
    , help $ "Use the Haskell type Int for the thrift type i64 " ++
             "(not recommended for 32-bit systems)"
    ]
  hsoptsUseHashMap <- switch $ mconcat
    [ long "use-hash-map"
    , help "Use the Haskell type HashMap for the thrift type map"
    ]
  hsoptsUseHashSet <- switch $ mconcat
    [ long "use-hash-set"
    , help "Use the Haskell type HashSet for the thrift type set"
    ]
  hsoptsDupNames <- switch $ mconcat
    [ long "duplicate-names"
    , help "Do not de-dup struct field names"
    ]
  hsoptsExtensions <- listOption
    [ long "extensions"
    , help "List of language extensions to turn on"
    , value []
    ]
  hsoptsGenPrefix <- strOption $ mconcat
    [ long "gen-prefix"
    , short 'g'
    , metavar "DIR"
    , help "Prefix for generated file paths, default 'gen-hs2'"
    , value "gen-hs2"
    ]
  hsoptsExtraHasFields <- switch $ mconcat
    [ long "extra-hasfields"
    , help "Generate HasField instances for unqualified struct names"
    ]

  return HsOpts{..}

-- Helpers ---------------------------------------------------------------------

maybeStr :: [Mod OptionFields FilePath] -> Parser (Maybe FilePath)
maybeStr = optional . strOption . mconcat

strToList :: String -> [Text]
strToList s | null s    = []
            | otherwise = Text.splitOn "," $ Text.pack s

listOption :: [Mod OptionFields String] -> Parser [Text]
listOption = fmap strToList . strOption . mconcat

maybeList :: [Mod OptionFields String] -> Parser (Maybe [Text])
maybeList = optional . listOption