packages feed

precis-0.3.1: src/Main.hs

{-# OPTIONS -Wall #-}

--------------------------------------------------------------------------------
-- |
-- Module      :  Main
-- Copyright   :  (c) Stephen Tetley 2010
-- License     :  LGPL (depends on CppHs)
--
-- Maintainer  :  Stephen Tetley <stephen.tetley@gmail.com>
-- Stability   :  highly unstable
-- Portability :  to be determined.
--
--
--------------------------------------------------------------------------------

module Main where

import CPP
import Precis.CabalPackage
import Precis.Datatypes
import Precis.HsSrcUtils
import Precis.ModuleProperties
import Precis.PPShowS
import Precis.Properties

import Language.Haskell.Exts ( Module )         -- package: haskell-src-exts

import System.Environment
import System.Exit
import System.Console.GetOpt

-- | REMEMBER TO CHANGE THIS!
-- *****************************************************************************
version_number :: String
version_number = "0.3.0"

header :: String
header = "Usage: precis <new_cabal_file> <old_cabal_file>"

help_message :: String
help_message = unlines $  
    [ "Summarize the API differences between two revisions of a" 
    , "Cabal package."
    ]

data Flag = Usage
          | Version
  deriving (Eq, Show)

options :: [OptDescr Flag]
options =
    [ Option ['h'] ["help"]     (NoArg Usage)        help_message
    , Option ['v'] ["version"]  (NoArg Version)      "show version"
    ]

main :: IO ()
main = do { args <- getArgs
          ; let (opts, nonopts, errs) = getOpt Permute options args
          ; main2 opts nonopts errs
          }
  
main2 :: [Flag] -> [FilePath] -> [String] -> IO ()
main2 opts _           _ 
  | Usage       `elem` opts = precisExit $ usageInfo header options
  | Version     `elem` opts = precisExit $ 
                                "precis version " ++ version_number

main2 _    [newc,oldc] []   = runCompare newc oldc
main2 _    _           errs = 
    precisExitFail 1 (concat errs ++ usageInfo header options)

precisExit :: String -> IO ()
precisExit s = putStrLn s >> exitWith ExitSuccess

precisExitFail :: Int -> String -> IO ()
precisExitFail i s = putStrLn s >> exitWith (ExitFailure i)


runCompare :: FilePath -> FilePath -> IO ()
runCompare new_cabal_file old_cabal_file = do 
    ans1 <- extractPrecis new_cabal_file known_extensions
    ans2 <- extractPrecis old_cabal_file known_extensions

    case (ans1,ans2) of
      (Right new_cp, Right old_cp) -> sk new_cp old_cp
      (Left err, _)                -> precisExitFail 2 $ cabalFileErrorMsg err
      (_, Left err)                -> precisExitFail 2 $ cabalFileErrorMsg err
  where
   sk new_cp old_cp = do 
      { printPackageNameAndVersions new_cp old_cp
      ; printModuleCountSummary new_cp old_cp
      ; compareExposedModules (exposedModulesProp new_cp) 
                              (exposedModulesProp old_cp)
      }


printPackageNameAndVersions :: CabalPrecis -> CabalPrecis -> IO ()
printPackageNameAndVersions new_cp old_cp = putShowSLine $ vsep
    [ repeatChar 50 '-'
    , packageNames    (package_name new_cp)    (package_name old_cp) 
    , repeatChar 50 '-'
    , packageVersions (package_version new_cp) (package_version old_cp)
    , newline
    ]
  where
    packageNames a b | a == b    = text a
                     | otherwise = text a <+> text "*** Warning: comparing to " 
                                          <+> text b
    packageVersions a b = text "Version:" <+> text a 
                       <+> text "compared to Version:" 
                       <+> text b

printModuleCountSummary :: CabalPrecis -> CabalPrecis -> IO ()
printModuleCountSummary new_cp old_cp = putShowSLine $ vsep
    [ text "Exposed modules:"
    , summarizeAddedRemoved "file" "files" id expos
    , text "Internal modules:"
    , summarizeAddedRemoved "file" "files" id privs
    , newline
    ]
  where
    new_pm        = packageModulesProp new_cp
    old_pm        = packageModulesProp old_cp
    (expos,privs) = diffPackageModulesProps new_pm old_pm
                                  

compareExposedModules :: ExposedModulesProp -> ExposedModulesProp -> IO ()
compareExposedModules new_expos old_expos = 
   mapM_ compareSrcFileEdit $ diffExposedModulesProps new_expos old_expos  

compareSrcFileEdit :: Edit SourceFile -> IO ()
compareSrcFileEdit (Conflict a b) = compareSourceFiles a b
compareSrcFileEdit _              = return ()


compareSourceFiles :: SourceFile -> SourceFile -> IO ()
compareSourceFiles new_sf old_sf = do 
  putShowSLine $ newline <> text (module_name new_sf) 
  new_ans <- fullParseModule new_sf
  old_ans <- fullParseModule old_sf
  case (new_ans, old_ans) of 
    (Right new_modu, Right old_modu) -> compareModules new_modu old_modu
    (Left err,_)                     -> putStrLn $ moduleParseErrorMsg err
    (_, Left err)                    -> putStrLn $ moduleParseErrorMsg err


compareModules :: Module -> Module -> IO ()
compareModules new_modu old_modu =
    compareExports   new_modu old_modu  >>
    compareDataDecls new_modu old_modu  >>
    compareTypeSigs  new_modu old_modu  >>
    compareInstances new_modu old_modu          


compareExports :: Module -> Module -> IO ()
compareExports new_modu old_modu = putShowSLine $ vsep
    [ text "Explicit exports:"
    , summarizeAddedConflictRemoved "export" "exports" txt expos
    ]
  where
    new_expos = exportsProp new_modu
    old_expos = exportsProp old_modu
    expos     = diffExportsProps new_expos old_expos

    txt (ModuleExport s)   = s
    txt (DataOrClass  _ r) = r
    txt (Variable     s)   = s 

compareInstances :: Module -> Module -> IO ()
compareInstances new_modu old_modu = putShowSLine $ vsep
    [ text "Instances:"
    , summarizeAddedConflictRemoved "instance" "instances" txt expos
    ]
  where
    new_insts = instancesProp new_modu
    old_insts = instancesProp old_modu
    expos     = diffInstancesProps new_insts old_insts

    txt (InstanceDecl _ _ r)   = r


compareDataDecls :: Module -> Module -> IO ()
compareDataDecls new_modu old_modu = putShowSLine $ vsep
    [ text "Exported data type decls:"
    , summarizeAddedConflictRemoved "data type" "data types" datatype_rep ddecls
    ]
  where
    new_ddecls = dataDeclsProp new_modu
    old_ddecls = dataDeclsProp old_modu
    ddecls     = diffDataDeclsProps new_ddecls old_ddecls


compareTypeSigs :: Module -> Module -> IO ()
compareTypeSigs new_modu old_modu = putShowSLine $ vsep
    [ text "Exported type sigs:"
    , summarizeAddedConflictRemoved "type signature" 
                                    "type signatures" ppty tysigs
    ]
  where
    new_tysigs = typeSigsProp new_modu
    old_tysigs = typeSigsProp old_modu
    tysigs     = diffTypeSigsProps new_tysigs old_tysigs

    ppty (TypeSigDecl n r) = n ++ " :: " ++ r

-- | macro-expand and parse
--
fullParseModule :: SourceFile -> IO (Either ModuleParseError Module)
fullParseModule (UnresolvedFile name) = 
    return $ Left $ ERR_MODULE_FILE_MISSING name
fullParseModule (SourceFile _ file_name) = do
    mx_src <- preprocessFile precisCpphsOptions file_name
    return $ readModule mx_src