cpsa-2.1.1: src/CPSA/Lib/Entry.hs
-- A common entry point for programs based on the CPSA library
-- Copyright (c) 2009 The MITRE Corporation
--
-- This program is free software: you can redistribute it and/or
-- modify it under the terms of the BSD License as published by the
-- University of California.
module CPSA.Lib.Entry (start, usage, abort, success, algOptions,
algInterp, filterOptions, filterInterp,
readSExpr, outputHandle, defaultMargin,
writeSExpr, writeLnSEexpr, cpsaVersion,
comment, writeComment) where
import Numeric
import System.IO
import System.IO.Error
import System.Environment
import System.Console.GetOpt
import System.Exit
import Data.Version
import Paths_cpsa
import CPSA.Lib.SExpr
import CPSA.Lib.Printer
-- Returns the input S-expression and an interpretation of the command
-- line options.
start :: [OptDescr a] -> ([a] -> IO b) -> IO (PosHandle, b)
start options interp =
do
argv <- getArgs
(flags, files) <- opts options argv
output <- interp flags
case files of
[file] -> -- Input from named file
do
input <- openFile file ReadMode
p <- posHandle file input
return (p, output)
[] -> -- Input from the standard input
do
p <- posHandle "" stdin
return (p, output)
_ ->
do
msg <- usage options ["too many input files\n"]
abort msg
opts :: [OptDescr a] -> [String] -> IO ([a], [String])
opts options argv =
case getOpt RequireOrder options argv of
(o, n, []) -> return (o, n)
(_, _, errs) ->
do
msg <- usage options errs
abort msg
usage :: [OptDescr a] -> [String] -> IO String -- Return usage string
usage options errs =
do
name <- getProgName
datadir <- getDataDir
let header = "Usage: " ++ name ++ " [OPTIONS] [FILE]"
let footer = "\nDocumentation directory: " ++ datadir
return (concat errs ++ usageInfo header options ++ footer)
readSExpr :: PosHandle -> IO (Maybe (SExpr Pos))
readSExpr p =
do
x <- try (load p)
case x of
Right x ->
return x
Left err ->
abort (ioeGetErrorString err)
-- Options set up for simple filters:
defaultMargin :: Int
defaultMargin = 72
-- Command line option flags
data Flag
= Help -- Help
| Info -- Version information
| Algebra String -- Algebra
| Algebras -- Show algebras
| Margin String -- Output line length
| Output String -- Output file name
deriving Show
algOptions :: String -> [OptDescr Flag]
algOptions defaultAlgebra =
[ Option ['o'] ["output"] (ReqArg Output "FILE") "output FILE",
Option ['m'] ["margin"] (ReqArg Margin "INT")
("set output margin (default " ++ show defaultMargin ++ ")"),
Option ['a'] ["algebra"] (ReqArg Algebra "STRING")
("algebra (default " ++ defaultAlgebra ++ ")"),
Option ['s'] ["show-algebras"] (NoArg Algebras) "show algebras",
Option ['h'] ["help"] (NoArg Help) "show help message",
Option ['v'] ["version"] (NoArg Info) "show version number" ]
data Params = Params
{ file :: Maybe FilePath, -- Nothing specifies standard output
alg :: String, -- Name of the algebra
margin :: Int } -- Output line length
deriving Show
-- Interpret option flags
algInterp :: String -> [String] -> [Flag] -> IO (Maybe FilePath, String, Int)
algInterp alg algs flags =
loop flags (Params { file = Nothing, alg = alg, margin = defaultMargin })
where
loop [] (Params { file = file, alg = alg, margin = margin}) =
return (file, alg, margin)
loop (Output name : flags) params
| file params == Nothing =
loop flags $ params { file = Just name }
loop (Margin value : flags) params =
case readDec value of
[(margin, "")] ->
loop flags $ params { margin = margin }
_ ->
do
msg <- usage (algOptions alg) ["Bad value for margin\n"]
abort msg
loop (Algebra name : flags) params
| elem name algs = loop flags $ params { alg = name }
| otherwise =
abort ("Algebra " ++ name ++ " not one of\n" ++ unlines algs)
loop (Algebras : _) _ =
success $ unlines algs
loop (Info : _) _ =
success cpsaVersion
loop (Help : _) _ =
do -- Show help then exit with success
msg <- usage (algOptions alg) []
success msg
loop _ _ =
do -- Show help then exit with failure
msg <- usage (algOptions alg) ["Bad option combination\n"]
abort msg
filterOptions :: [OptDescr Flag]
filterOptions =
[ Option ['o'] ["output"] (ReqArg Output "FILE") "output FILE",
Option ['m'] ["margin"] (ReqArg Margin "INT")
("set output margin (default " ++ show defaultMargin ++ ")"),
Option ['h'] ["help"] (NoArg Help) "show help message",
Option ['v'] ["version"] (NoArg Info) "show version number" ]
-- Interpret option flags
filterInterp :: [Flag] -> IO (Maybe FilePath, Int)
filterInterp flags =
loop flags Nothing defaultMargin
where
loop [] file margin =
return (file, margin)
loop (Output name : flags) Nothing margin =
loop flags (Just name) margin
loop (Margin value : flags) file _ =
case readDec value of
[(margin, "")] ->
loop flags file margin
_ ->
do
msg <- usage filterOptions ["Bad value for margin\n"]
abort msg
loop (Info : _) _ _ =
success cpsaVersion
loop (Help : _) _ _ =
do -- Show help then exit with success
msg <- usage filterOptions []
success msg
loop _ _ _ =
do -- Show help then exit with failure
msg <- usage filterOptions ["Bad option combination\n"]
abort msg
-- Contruct the output handle specified by the command line.
outputHandle :: Maybe FilePath -> IO (Handle)
outputHandle output =
case output of
Nothing -> return stdout
Just file -> openFile file WriteMode
abort :: String -> IO a -- Print message then exit with failure
abort msg =
do
hPutStrLn stderr msg
exitFailure
success :: String -> IO a -- Print message then exit with success
success msg =
do
hPutStrLn stderr msg
exitWith ExitSuccess
-- S-expression pretty print parameters
indent :: Int
indent = 2
writeSExpr :: Handle -> Int -> SExpr a -> IO ()
writeSExpr h margin sexpr =
hPutStrLn h (pp margin indent sexpr)
writeLnSEexpr :: Handle -> Int -> SExpr a -> IO ()
writeLnSEexpr h margin sexpr =
do
hPutStrLn h ""
writeSExpr h margin sexpr
cpsaVersion :: String
cpsaVersion = showString "CPSA " (showVersion version)
comment :: String -> SExpr ()
comment msg =
L () [S () "comment", Q () msg]
writeComment :: Handle -> Int -> String -> IO ()
writeComment h margin msg =
writeSExpr h margin (comment msg)