content: {-# LANGUAGE CPP #-}
  {-# LANGUAGE NamedFieldPuns #-}
  
  import qualified Data.Aeson           as J
  import qualified Data.ByteString      as S
  import qualified Data.ByteString.Lazy as L
  import qualified Data.Yaml            as Y
  #if !MIN_VERSION_base(4,11,0)
  import Data.Semigroup ( Semigroup(..) )
  #endif
  
  import Options.Applicative
    ( Parser, (<|>)
    , action, execParser, footerDoc, headerDoc, help, helper, hidden, info
    , long, metavar, short, strArgument, strOption, value
    )
  import Options.Applicative.Help.Pretty
    ( vcat, pretty )
  
  import System.Exit    ( die )
  
  import Common         ( dashToNothing, versionText, versionOption, numericVersionOption )
  
  data Options = Options
    { optInput  :: Maybe FilePath
        -- ^ 'Nothing' means @stdin@.
    , optOutput :: Maybe FilePath
        -- ^ 'Nothing' means @stdout@.
    }
  
  -- | Name of the executable.
  
  self :: String
  self = "json2yaml"
  
  -- | Parse options; handle parse errors, @--help@, @--version@.
  
  options :: IO Options
  options =
    execParser $
      info (helper <*> versionOption self <*> numericVersionOption <*> programOptions)
        $  headerDoc hdoc
        <> footerDoc fdoc
  
    where
    hdoc = Just $ vcat $ map pretty
      [ versionText self
      , "Convert JSON to YAML."
      ]
    fdoc = Just $ pretty $ concat
      [ "The old call pattern '"
      , self
      , " IN OUT' is also accepted, but deprecated."
      ]
  
    programOptions :: Parser Options
    programOptions = Options <$> oInput <*> oOutput
  
    oInput :: Parser (Maybe FilePath)
    oInput = dashToNothing <$> do
      strArgument
        $  metavar "IN"
        <> value "-"
        <> action "file"
        <> help "The input file containing the JSON document; use '-' for stdin (default)."
  
    oOutput :: Parser (Maybe FilePath)
    oOutput = dashToNothing <$> do
      oOutputExplicit <|> oOutputImplicit
  
    oOutputExplicit :: Parser FilePath
    oOutputExplicit =
      strOption
        $  long "output"
        <> short 'o'
        <> metavar "OUT"
        <> action "file"
        -- <> help "The file to hold the produced YAML document; use '-' for stdout (default)."
  
    oOutputImplicit :: Parser FilePath
    oOutputImplicit =
      strArgument
        $  metavar "OUT"
        <> value "-"
        <> action "file"
        <> hidden
        <> help "The file to hold the produced YAML document; use '-' for stdout (default)."
  
  -- | Exit with 'self'-stamped error message.
  
  abort :: String -> IO a
  abort msg = die $ concat [ self, ": ", msg ]
  
  main :: IO ()
  main = do
    Options{ optInput, optOutput } <- options
  
    -- Read JSON.
    mval <- J.decode <$> maybe L.getContents L.readFile optInput
  
    -- Write YAML.
    case mval of
      Nothing  -> abort "Invalid input JSON"
      Just val -> case optOutput of
        Nothing -> S.putStr $ Y.encode (val :: Y.Value)
        Just f  -> Y.encodeFile f val
  