content: {-# LANGUAGE CPP #-}
  {-# LANGUAGE LambdaCase #-}
  {-# LANGUAGE NamedFieldPuns #-}
  
  import Data.Aeson           (encode, Value)
  import qualified Data.ByteString      as S (getContents)
  import qualified Data.ByteString.Lazy as L (putStr, writeFile)
  #if !MIN_VERSION_base(4,11,0)
  import Data.Semigroup       (Semigroup(..))
  #endif
  import Data.Yaml            (decodeFileEither, decodeEither')
  import System.Exit          (die)
  
  import Options.Applicative
    ( Parser
    , action, execParser, headerDoc, help, helper, info
    , long, metavar, short, strArgument, strOption, value
    )
  import Options.Applicative.Help.Pretty
    ( vcat, pretty )
  
  import Common
    ( versionText, versionOption, numericVersionOption, dashToNothing )
  
  data Options = Options
    { optInput :: Maybe FilePath
        -- ^ 'Nothing' means @stdin@.
    , optOutput :: Maybe FilePath
        -- ^ 'Nothing' means @stdout@.
    }
  
  -- | Name of the executable.
  
  self :: String
  self = "yaml2json"
  
  -- | Parse options; handle parse errors, @--help@, @--version@.
  
  options :: IO Options
  options =
    execParser $
      info (helper <*> versionOption self <*> numericVersionOption <*> programOptions)
           (headerDoc hdoc)
  
    where
    hdoc = Just $ vcat $ map pretty
      [ versionText self
      , "Convert YAML to JSON."
      ]
  
    programOptions = Options <$> oInput <*> oOutput
  
    oInput :: Parser (Maybe FilePath)
    oInput = dashToNothing <$> do
      strArgument
        $  metavar "IN"
        <> value "-"
        <> action "file"
        <> help "The input file containing the YAML document; use '-' for stdin (default)."
  
    oOutput :: Parser (Maybe FilePath)
    oOutput = dashToNothing <$> do
      strOption
        $  long "output"
        <> short 'o'
        <> value "-"
        <> metavar "OUT"
        <> action "file"
        <> help "The file to hold the produced JSON document; use '-' for stdout (default)."
  
  
  main :: IO ()
  main = do
    Options{ optInput, optOutput } <- options
  
    -- Input YAML.
    result <- case optInput of
      -- strict getContents will read in all of stdin at once
      Nothing -> decodeEither' <$> S.getContents
      Just f  -> decodeFileEither f
  
    -- Output JSON.
    case result of
      Left  err -> die $ show err
      Right val -> do
        let json = encode (val :: Value)
        maybe L.putStr L.writeFile optOutput json
  