{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Main where import Control.Applicative import Control.Exception import Data.Aeson import Data.Aeson.Diff import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Lazy as BSL import Data.Monoid import qualified Data.Text.IO as T import Options.Applicative import Options.Applicative.Types import System.IO type File = Maybe FilePath -- | Command-line options. data Options = Options { optionJSON :: Bool , optionOut :: File , optionFrom :: File , optionTo :: File } data Configuration = Configuration { cfgJSON :: Bool , cfgOut :: Handle , cfgFrom :: Handle , cfgTo :: Handle } optionParser :: Parser Options optionParser = Options <$> switch ( long "json" <> short 'j' <> help "Output patch in JSON." ) <*> option fileP ( long "output" <> short 'o' <> metavar "OUTPUT" <> value Nothing ) <*> argument fileP ( metavar "FROM" ) <*> argument fileP ( metavar "TO" ) where fileP = do s <- readerAsk return $ case s of "-" -> Nothing _ -> Just s jsonFile :: Handle -> IO Value jsonFile fp = do s <- BS.hGetContents fp case decode (BSL.fromStrict s)of Nothing -> error "Could not parse as JSON" Just v -> return v run :: Options -> IO () run opt = bracket (load opt) close process where openr :: Maybe FilePath -> IO Handle openr Nothing = return stdin openr (Just p) = openFile p ReadMode openw :: Maybe FilePath -> IO Handle openw Nothing = return stdout openw (Just p) = openFile p WriteMode load :: Options -> IO Configuration load Options{..} = Configuration <$> pure optionJSON <*> openw optionOut <*> openr optionFrom <*> openr optionTo close :: Configuration -> IO () close Configuration{..} = do hClose cfgOut hClose cfgFrom hClose cfgTo process :: Configuration -> IO () process Configuration{..} = do json_from <- jsonFile cfgFrom json_to <- jsonFile cfgTo let p = diff json_from json_to if cfgJSON then BS.hPutStrLn cfgOut . BSL.toStrict . encode $ p else T.hPutStrLn cfgOut . formatPatch $ p main :: IO () main = execParser opts >>= run where opts = info (helper <*> optionParser) ( fullDesc <> progDesc "Generate a patch between two JSON documents.")