-----------------------------------------------------------------------------
-- |
-- Module : Windll
-- Copyright : (c) Tamar Christina 2009 - 2010
-- License : BSD3
--
-- Maintainer : tamar@zhox.com
-- Stability : experimental
-- Portability : portable
--
-- A module to handle commandline arguments and parsing
-- for the main program.
--
-----------------------------------------------------------------------------
module WinDll.CmdArgs.Hs2lib where
import WinDll.Session.Hs2lib
import WinDll.Version.Hs2lib
import WinDll.Utils.Feedback
import WinDll.Structs.Structures
import WinDll.Utils.Pragma
import System.Directory
import System.Environment
import System.FilePath
import System.Console.GetOpt
import System.Exit
import Data.Maybe
import Data.Char
import Data.List
import Control.Monad
-- | just renaming it to something that might make more sense to read in this module
type Config = Session
-- | Default configuration set for both the client and server.
defaultConfig :: Mode -> Config
defaultConfig Windows = newSession
defaultConfig Unix = let session = newSession
in session { call = CCall }
-- | The parse mode for processArgs
type Mode = Platform
-- | Read all the pragmas that contain options and then update the session with the new flags
enablePragmas :: Exec ()
enablePragmas =
do session <- get
inform _normal "Enabling commandline pragmas..."
let prgmas = (pragmas.workingset) session
cmds = getPragmas (upper_name ++ "_OPTS") prgmas
args = mainFile session : concatMap (\(Pragma _ x)->x) cmds
mode = platform session
session' <- processConversionPragmas prgmas session
(result,errs) <- if (null args) then return (session',[]) else
liftIO $ do (result,errs) <- processArgs session' mode args
if (not.null) errs then putStrLn " Error(s):" else do return ()
mapM_ (\i -> putStrLn (" -> unrecognized input '"++i++"')")) errs
return (result,errs)
if null errs
then put result
else die "Error processing dynamic options"
-- | Process args tries to parse the commandline arguments for either the client or the server.
-- 1 = The session to initialize things with
-- 2 = Mode to parse for
-- 3 = arguments to parse
-- return = Parsed config or error message
processArgs :: Config -> Mode -> [String] -> IO (Config, [String])
processArgs cfg mode argv=
case getOpt Permute opt argv of
(o,n,[]) -> process (foldl (flip id) cfg o) n opt
(_,_,errs) -> ioError (userError (concat errs ++ usageInfo header opt))
where header = "Usage: " ++ name ++ " [OPTION...]"
name = if mode == Windows then exename ++ ".exe" else exename
opt = optDescr --if mode == Windows then optDescrClient else optDescrServer
process a r opt =
case Help `elem` opts of
True -> ioError (userError $ usageInfo header opt)
False -> case Version `elem` opts of
True -> putStrLn versionmsg >> exitSuccess
False -> case r of
(x:[]) -> do path <- canonicalizePath (addTrailingPathSeparator (tempDIR a))
base <- getCurrentDirectory
abs_path <- guessPath x
let dir = takeDirectory (normalise x)
bin = combine base dir
return (a { mainFile = x
, platform = mode
, tempDIR = path
, absPath = abs_path
, baseDir = base
},[])
_ -> ioError (userError $ usageInfo header opt)
where opts = options a
-- | Create a boolean flag
boolFlag :: String -> String -> (a->Bool->a) -> String -> [OptDescr (a->a)]
boolFlag short long update descr = [ template (update' True) short long descr
, template (update' False) "" ("no"++long) [] ]
where
template value short long descr = Option short [long] (NoArg value) descr
update' = flip update
-- | Collection of options available
optDescr = optVerbose
: optCall
: optNamespace
: optHackageBaseURL
: optOutDir
: optTempDir
: optOutFile
: optPlatform
: optVersion
: optHelp
: optLibmain
: optIncludes
: optGhc
: boolFlag "W" "warnings" (\cfg new -> cfg { warnings = new }) "print all warnings (Default True)"
++ boolFlag "E" "warnings-as-errors" (\cfg new -> cfg { warnings_as_errors = new
, warnings = new || warnings cfg }) "treat all warnings as errors (Default False)"
++ boolFlag "T" "keep-temp-files" (\cfg new -> cfg { keep_temps = new }) "keep all the temporary files generated by the pre-processor (Default False)"
++ boolFlag "" "c#" (\cfg new -> cfg { csharp = new }) "enable C# file output"
++ boolFlag "" "cpp" (\cfg new -> cfg { cpp = new }) "enable C/C++ file output (default True)"
++ boolFlag "M" "lib" (\cfg new -> cfg { mvcpp = new }) "create a .LIB file for use with the Microsoft Visual C++ compiler. NOTE: This requires the %VSBIN% (case sensitive) environment variable to be set."
++ boolFlag "d" "incl-def" (\cfg new -> cfg { incDef = new }) "Copy the generated .DEF file also to the specified output folder."
++ boolFlag "" "link-dyn" (\cfg new -> cfg { link_dynamic = new }) ("If you have the dynamic version of all your libs installed you can use this flag. (this includes having installed " ++ exename ++ " using -dynamic)")
++ boolFlag "" "debug" (\cfg new -> cfg { debugging = new }) ("Enable debugging support which enabled memory profiling.")
++ boolFlag "" "preserve-comments" (\cfg new -> cfg { pres_comment = new }) "Enabling this allows you to preserve the haddock description comments of your functions in the generated C _FFI file (Default True)"
++ boolFlag "" "preserve-foreigns" (\cfg new -> cfg { include_foreigns = new }) ("Enabling this will preserve and re-export any foreign declarations you already have declared in modules. NOTE: The Naming Convention must be the same as the generated functions. " ++ exename ++ " finds. (Default True)")
++ boolFlag "" "no-dllmain" (\cfg new -> cfg { dllmanual = new }) "Enabling this will allow you to manual initialise the RTS. Dllmain will no longer make the call to HsInit(). -L and --libmain will be ignored. (Default True)"
++ boolFlag "" "threaded" (\cfg new -> cfg { threaded = new
, dllmanual = new || dllmanual cfg }) "Enable the threaded RTS. This implies --no-dllmain. (Default False)"
-- | Collection of simple actions
optVerbose = Option ['v'] ["verbose","verbosity" ] (OptArg (\value cfg -> cfg { verbosity = fread (verbosity cfg) value }) "") "set the verbosity level. Requires a number between 0 (off) and 3"
optCall = Option ['C'] ["convention" ] (ReqArg (\value cfg -> cfg { call = cread StdCall value }) "") "set the calling convention used in FFI (Default stdcall)"
optNamespace = Option ['n'] ["name" ] (ReqArg (\value cfg -> cfg { namespace = correct value }) "") ("set the name to use in every generated code. (Default \"" ++ exename ++ "\")")
optHackageBaseURL = Option ['h'] ["hackage-base-url" ] (ReqArg (\value cfg -> cfg { hackage_base_url = value }) "") "set the Hackage base url which will be used when doing code discovery"
optOutDir = Option [ ] ["outdir","output-directory"] (ReqArg (\value cfg -> cfg { outputDIR = value }) "") "set the output directory of the pre-processor (Default current folder)"
optTempDir = Option [ ] ["temp","temp-directory" ] (ReqArg (\value cfg -> cfg { tempDIR = value }) "") "set the temporary directory of the pre-processor (Default system temp)"
optOutFile = Option ['o'] ["out","outFile" ] (ReqArg (\value cfg -> cfg { outputFile = value }) "") "set the name of the output file (Default derived from input and platform)"
optPlatform = Option ['P'] ["platform" ] (ReqArg (\value cfg -> cfg { platform = cread Windows value }) "") "set the platform to compile for (Defaul current system)"
optIncludes = Option ['I'] ["includes" ] (ReqArg (\value cfg -> cfg { includes = value : (includes cfg) }) "file") "include extra files in the pipeline. (external definitions)"
optHelp = Option ['?'] ["h","help" ] (NoArg (\cfg -> cfg {options = Help:(options cfg)}) ) "Show this help screen"
optVersion = Option [ ] ["version" ] (NoArg (\cfg -> cfg {options = Version:(options cfg)}) ) "Show program version information"
optLibmain = Option ['L'] ["libmain" ] (ReqArg (\value cfg -> cfg { dllmain = value }) "") "set the dllmain file to be used by when compiling the library"
optIncForeign = Option [ ] ["native-symbols" ] (ReqArg (\value cfg -> cfg { native_symbols = value : (native_symbols cfg) }) "") "include the .DEF file specified here into the final generated Exports table."
optGhc = Option [ ] ["opt-ghc" ] (ReqArg (\value cfg -> cfg { optGHC = value }) "\"\"") "options to pass along unmodified to ghc during main compilation."
fread :: Int -> Maybe String -> Int
fread c q = let p = maybe 1 (read' 1) q
in if (p + c) > 3 || (c + p) < 0 then 1 else (c+p)
cread c q = let p = reads q
hasValue = not $ null p
value = if hasValue then fst $ head p else c
in value
-- | Get the read in namespace name into a useable format
correct :: String -> String
correct [] = []
correct (x:xs) = toUpper x : filter isAlphaNum xs
-- | Because read can fail for parsing stuff other than strings.
-- this function is used to either accept the change. or use the default
read' :: Int -> String -> Int
read' def str = let r = reads str
in if null r then def else (fst.head) r
-- | The main parse function to call. It takes a mode as parameters and takes a pointer
-- to a function to call should it all succeed.
goArgs :: Mode -> (Config -> IO ()) -> IO ()
goArgs mode call =
do args <- getArgs
(result,errs) <- processArgs (defaultConfig mode) mode args
if (not.null) errs then putStrLn " Errors:" else do return ()
mapM_ (\i -> putStrLn (" -> unrecognized input '"++i++"')")) errs
if null errs then call result else do return ()