{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}

{-| Convert Dhall to YAML via JSON

    Since JSON is only a subset of YAML, the functionality offered here is more
    limited than what the @dhall-yaml@ package can offer.
-}
module Dhall.JSON.Yaml
  ( Options(..)
  , parseDocuments
  , parseQuoted
  , defaultOptions
  , dhallToYaml
  , jsonToYaml
  , generatedCodeNotice
  ) where

import Data.ByteString     (ByteString)
import Data.Text           (Text)
import Dhall.JSON          (Conversion (..), SpecialDoubleMode (..))
import Options.Applicative (Parser)

import qualified Data.Aeson
import qualified Data.Aeson.Yaml
import qualified Data.ByteString.Lazy
import qualified Data.Vector
import qualified Dhall
import qualified Dhall.JSON
import qualified Options.Applicative

data Options = Options
    { Options -> Bool
explain    :: Bool
    , Options -> Value -> Value
omission   :: Data.Aeson.Value -> Data.Aeson.Value
    , Options -> Bool
documents  :: Bool
    , Options -> Bool
quoted     :: Bool
    , Options -> Conversion
conversion :: Conversion
    , Options -> Maybe FilePath
file       :: Maybe FilePath
    , Options -> Maybe FilePath
output     :: Maybe FilePath
    , Options -> Bool
noEdit     :: Bool
    }

defaultOptions :: Options
defaultOptions :: Options
defaultOptions =
  Options :: Bool
-> (Value -> Value)
-> Bool
-> Bool
-> Conversion
-> Maybe FilePath
-> Maybe FilePath
-> Bool
-> Options
Options { explain :: Bool
explain = Bool
False
          , omission :: Value -> Value
omission = Value -> Value
forall a. a -> a
id
          , documents :: Bool
documents = Bool
False
          , quoted :: Bool
quoted = Bool
False
          , conversion :: Conversion
conversion = Conversion
Dhall.JSON.defaultConversion
          , file :: Maybe FilePath
file = Maybe FilePath
forall a. Maybe a
Nothing
          , output :: Maybe FilePath
output = Maybe FilePath
forall a. Maybe a
Nothing
          , noEdit :: Bool
noEdit = Bool
False
          }

parseDocuments :: Parser Bool
parseDocuments :: Parser Bool
parseDocuments =
  Mod FlagFields Bool -> Parser Bool
Options.Applicative.switch
            (   FilePath -> Mod FlagFields Bool
forall (f :: * -> *) a. HasName f => FilePath -> Mod f a
Options.Applicative.long FilePath
"documents"
            Mod FlagFields Bool -> Mod FlagFields Bool -> Mod FlagFields Bool
forall a. Semigroup a => a -> a -> a
<>  FilePath -> Mod FlagFields Bool
forall (f :: * -> *) a. FilePath -> Mod f a
Options.Applicative.help FilePath
"If given a Dhall list, output a document for every element.  Each document, including the first one, will be preceded by \"---\", even if there is only one document.  If not given a list, output a single document (as if it were a list of one element)"
            )

parseQuoted :: Parser Bool
parseQuoted :: Parser Bool
parseQuoted =
  Mod FlagFields Bool -> Parser Bool
Options.Applicative.switch
            (   FilePath -> Mod FlagFields Bool
forall (f :: * -> *) a. HasName f => FilePath -> Mod f a
Options.Applicative.long FilePath
"quoted"
            Mod FlagFields Bool -> Mod FlagFields Bool -> Mod FlagFields Bool
forall a. Semigroup a => a -> a -> a
<>  FilePath -> Mod FlagFields Bool
forall (f :: * -> *) a. FilePath -> Mod f a
Options.Applicative.help FilePath
"Prevent from generating not quoted scalars"
            )

{-| The notice added to the top of a generated file when enabling the
    @--generated-comment@
-}
generatedCodeNotice :: ByteString
generatedCodeNotice :: ByteString
generatedCodeNotice = ByteString
"# Code generated by dhall-to-yaml.  DO NOT EDIT.\n"

{-| Convert a piece of Text carrying a Dhall inscription to an equivalent YAML ByteString
-}
dhallToYaml
  :: Options
  -> Maybe FilePath  -- ^ The source file path. If no path is given, imports
                     -- are resolved relative to the current directory.
  -> Text  -- ^ Input text.
  -> IO ByteString
dhallToYaml :: Options -> Maybe FilePath -> Text -> IO ByteString
dhallToYaml Options{Bool
Maybe FilePath
Conversion
Value -> Value
noEdit :: Bool
output :: Maybe FilePath
file :: Maybe FilePath
conversion :: Conversion
quoted :: Bool
documents :: Bool
omission :: Value -> Value
explain :: Bool
noEdit :: Options -> Bool
output :: Options -> Maybe FilePath
file :: Options -> Maybe FilePath
conversion :: Options -> Conversion
quoted :: Options -> Bool
documents :: Options -> Bool
omission :: Options -> Value -> Value
explain :: Options -> Bool
..} Maybe FilePath
mFilePath Text
code = do

  let explaining :: IO a -> IO a
explaining = if Bool
explain then IO a -> IO a
forall a. IO a -> IO a
Dhall.detailed else IO a -> IO a
forall a. a -> a
id

  Value
json <- Value -> Value
omission (Value -> Value) -> IO Value -> IO Value
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO Value -> IO Value
forall a. IO a -> IO a
explaining (Conversion
-> SpecialDoubleMode -> Maybe FilePath -> Text -> IO Value
Dhall.JSON.codeToValue Conversion
conversion SpecialDoubleMode
UseYAMLEncoding Maybe FilePath
mFilePath Text
code)

  let header :: ByteString
header =
          if Bool
noEdit
          then ByteString
generatedCodeNotice
          else ByteString
forall a. Monoid a => a
mempty

  ByteString -> IO ByteString
forall (m :: * -> *) a. Monad m => a -> m a
return (ByteString -> IO ByteString) -> ByteString -> IO ByteString
forall a b. (a -> b) -> a -> b
$ ByteString
header ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Value -> Bool -> Bool -> ByteString
jsonToYaml Value
json Bool
documents Bool
quoted

-- | Transform json representation into yaml
jsonToYaml
    :: Data.Aeson.Value
    -> Bool
    -> Bool
    -> ByteString
jsonToYaml :: Value -> Bool -> Bool -> ByteString
jsonToYaml Value
json Bool
documents Bool
quoted =
  ByteString -> ByteString
Data.ByteString.Lazy.toStrict (ByteString -> ByteString) -> ByteString -> ByteString
forall a b. (a -> b) -> a -> b
$ case (Bool
documents, Value
json) of
    (Bool
True, Data.Aeson.Array Array
elems)
      -> (if Bool
quoted
            then [Value] -> ByteString
forall a. ToJSON a => [a] -> ByteString
Data.Aeson.Yaml.encodeQuotedDocuments
            else [Value] -> ByteString
forall a. ToJSON a => [a] -> ByteString
Data.Aeson.Yaml.encodeDocuments
         ) (Array -> [Value]
forall a. Vector a -> [a]
Data.Vector.toList Array
elems)
    (Bool
True, Value
value)
      -> (if Bool
quoted
            then [Value] -> ByteString
forall a. ToJSON a => [a] -> ByteString
Data.Aeson.Yaml.encodeQuotedDocuments
            else [Value] -> ByteString
forall a. ToJSON a => [a] -> ByteString
Data.Aeson.Yaml.encodeDocuments
         ) [ Value
value ]
    (Bool, Value)
_ -> (if Bool
quoted
            then Value -> ByteString
forall a. ToJSON a => a -> ByteString
Data.Aeson.Yaml.encodeQuoted
            else Value -> ByteString
forall a. ToJSON a => a -> ByteString
Data.Aeson.Yaml.encode
         ) Value
json