cpsa-2.2.1: src/CPSA/Diff/Main.hs
-- Compares CPSA output files S-expression by S-expression, and
-- prints the first skeleton that differs.
-- Copyright (c) 2010 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 Main (main) where
import Numeric
import System.IO
import System.Environment
import System.Console.GetOpt
import System.Exit
import Paths_cpsa
import CPSA.Lib.CPSA
import CPSA.Lib.Entry
main :: IO ()
main =
do
argv <- getArgs
(flags, files) <- opts options argv
params <- interp flags
case files of
[oldName, newName] ->
do
oldFile <- openFile oldName ReadMode
old <- posHandle oldName oldFile
newFile <- openFile newName ReadMode
new <- posHandle newName newFile
go params old new
_ ->
do
msg <- use options []
abort msg
go :: Params -> PosHandle -> PosHandle -> IO ()
go params old new =
do
o <- readSkel old
n <- readSkel new
case (o, n) of
(Nothing, Nothing) -> return ()
(Just o, Just n) | o == n -> go params old new
_ -> showDiff params o n
showDiff :: Params -> Maybe (SExpr Pos) -> Maybe (SExpr Pos) -> IO ()
showDiff params o n =
do
out <- outputHandle (file params)
case o of
Just o ->
do
hPutStrLn out ("<<< " ++ show (annotation o))
writeSkel params out o
hPutStrLn out "<<<"
_ ->
return ()
case n of
Just n ->
do
hPutStrLn out (">>> " ++ show (annotation n))
writeSkel params out n
hPutStrLn out ">>>"
_ ->
return ()
hClose out
exitFailure
writeSkel :: Params -> Handle -> SExpr a -> IO ()
writeSkel params h sexpr =
do
hPutStrLn h $ pp (margin params) indent sexpr
-- Read the next skeleton
readSkel :: PosHandle -> IO (Maybe (SExpr Pos))
readSkel p =
do
input <- gentlyReadSExpr p
case input of
Nothing -> return input
Just x ->
case x of
L _ (S _ "defskeleton" : _) ->
return input
_ ->
readSkel p
-- Runtime parameters
data Params = Params
{ file :: Maybe FilePath, -- Nothing specifies standard output
margin :: Int } -- Output line length
deriving Show
indent :: Int
indent = optIndent defaultOptions
-- Command line option flags
data Flag
= Help -- Help
| Info -- Version information
| Margin String -- Output line length
| Output String -- Output file name
deriving Show
defaultMargin :: Int
defaultMargin = optMargin defaultOptions
options :: [OptDescr Flag]
options =
[ 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" ]
opts :: [OptDescr a] -> [String] -> IO ([a], [String])
opts options argv =
case getOpt RequireOrder options argv of
(o, n, []) -> return (o, n)
(_, _, errs) ->
do
msg <- use options errs
abort msg
use :: [OptDescr a] -> [String] -> IO String -- Return usage string
use options errs =
do
name <- getProgName
datadir <- getDataDir
let header = "Usage: " ++ name ++ " [OPTIONS] OLD-FILE NEW-FILE"
let footer = "\nDocumentation directory: " ++ datadir
return (concat errs ++ usageInfo header options ++ footer)
-- Interpret option flags
interp :: [Flag] -> IO Params
interp flags =
loop flags (Params { file = Nothing, -- By default, no output file
margin = defaultMargin })
where
loop [] params = return params
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 <- use options ["Bad value for margin\n"]
abort msg
loop (Info : _) _ =
success cpsaVersion
loop (Help : _) _ =
do -- Show help then exit with success
msg <- use options []
success msg
loop _ _ =
do -- Show help then exit with failure
msg <- use options ["Bad option combination\n"]
abort msg