cuddle-0.5.0.0: bin/Main.hs
{-# LANGUAGE LambdaCase #-}
module Main (main) where
import Codec.CBOR.Cuddle.CBOR.Gen (generateCBORTerm)
import Codec.CBOR.Cuddle.CBOR.Validator
import Codec.CBOR.Cuddle.CDDL (Name (..), sortCDDL)
import Codec.CBOR.Cuddle.CDDL.Prelude (prependPrelude)
import Codec.CBOR.Cuddle.CDDL.Resolve (
fullResolveCDDL,
)
import Codec.CBOR.Cuddle.Parser (pCDDL)
import Codec.CBOR.Cuddle.Pretty ()
import Codec.CBOR.FlatTerm (toFlatTerm)
import Codec.CBOR.Pretty (prettyHexEnc)
import Codec.CBOR.Term (encodeTerm)
import Codec.CBOR.Write (toStrictByteString)
import Data.ByteString.Base16 qualified as Base16
import Data.ByteString.Char8 qualified as BSC
import Data.Text qualified as T
import Data.Text.IO qualified as T
import Options.Applicative
import Prettyprinter (Pretty (pretty))
import Prettyprinter.Util (putDocW)
import System.Exit (exitFailure, exitSuccess)
import System.IO (hPutStrLn, stderr)
import System.Random (getStdGen)
import Text.Megaparsec (ParseErrorBundle, Parsec, errorBundlePretty, runParser)
data Opts = Opts Command String
data Command
= Format FormatOpts
| Validate ValidateOpts
| GenerateCBOR GenOpts
| ValidateCBOR ValidateCBOROpts
newtype ValidateOpts = ValidateOpts {vNoPrelude :: Bool}
pValidateOpts :: Parser ValidateOpts
pValidateOpts =
ValidateOpts
<$> switch
( long "no-prelude"
<> help "Do not include the CDDL prelude."
)
-- | Various formats for outputtting CBOR
data CBOROutputFormat
= AsCBOR
| AsPrettyCBOR
| AsTerm
| AsFlatTerm
pCBOROutputFormat :: ReadM CBOROutputFormat
pCBOROutputFormat = eitherReader $ \case
"cbor" -> Right AsCBOR
"pretty" -> Right AsPrettyCBOR
"term" -> Right AsTerm
"flat" -> Right AsFlatTerm
s -> Left s
data GenOpts = GenOpts
{ itemName :: T.Text
, outputFormat :: CBOROutputFormat
, outputTo :: Maybe String
, gNoPrelude :: Bool
}
pGenOpts :: Parser GenOpts
pGenOpts =
GenOpts
<$> strOption
( long "rule"
<> short 'r'
<> metavar "RULE"
<> help "Name of the CDDL rule to generate a CBOR term for"
)
<*> option
pCBOROutputFormat
( long "format"
<> short 'f'
<> help "Output format"
<> value AsCBOR
)
<*> optional
( strOption
( long "out-file"
<> short 'o'
<> help "Write to"
)
)
<*> switch
( long "no-prelude"
<> help "Do not include the CDDL prelude."
)
newtype FormatOpts = FormatOpts
{sort :: Bool}
pFormatOpts :: Parser FormatOpts
pFormatOpts =
FormatOpts
<$> switch
( long "sort-rules"
<> help "Sort the CDDL rule definitions before printing."
)
data ValidateCBOROpts = ValidateCBOROpts
{ vcItemName :: T.Text
, vcInput :: FilePath
, vcNoPrelude :: Bool
}
pValidateCBOROpts :: Parser ValidateCBOROpts
pValidateCBOROpts =
ValidateCBOROpts
<$> strOption
( long "rule"
<> short 'r'
<> metavar "RULE"
<> help "Name of the CDDL rule to validate this file with"
)
<*> strOption
( long "cbor"
<> short 'c'
<> help "CBOR file"
)
<*> switch
( long "no-prelude"
<> help "Do not include the CDDL prelude."
)
opts :: Parser Opts
opts =
Opts
<$> subparser
( command
"format"
( info
(Format <$> pFormatOpts <**> helper)
(progDesc "Format the provided CDDL file")
)
<> command
"validate"
( info
(Validate <$> pValidateOpts <**> helper)
(progDesc "Validate the provided CDDL file")
)
<> command
"gen"
( info
(GenerateCBOR <$> pGenOpts <**> helper)
(progDesc "Generate a CBOR term matching the schema")
)
<> command
"validate-cbor"
( info
(ValidateCBOR <$> pValidateCBOROpts <**> helper)
(progDesc "Validate a CBOR file against a schema")
)
)
<*> argument str (metavar "CDDL_FILE")
main :: IO ()
main = do
options <-
execParser $
info
(opts <**> helper)
( fullDesc
<> progDesc "Manipulate CDDL files"
<> header "cuddle"
)
run options
run :: Opts -> IO ()
run (Opts cmd cddlFile) = do
parseFromFile pCDDL cddlFile >>= \case
Left err -> do
putStrLnErr $ errorBundlePretty err
exitFailure
Right res ->
case cmd of
Format fOpts ->
let
defs
| sort fOpts = sortCDDL res
| otherwise = res
in
putDocW 80 $ pretty defs
Validate vOpts ->
let
res'
| vNoPrelude vOpts = res
| otherwise = prependPrelude res
in
case fullResolveCDDL res' of
Left err -> putStrLnErr (show err) >> exitFailure
Right _ -> exitSuccess
(GenerateCBOR gOpts) ->
let
res'
| gNoPrelude gOpts = res
| otherwise = prependPrelude res
in
case fullResolveCDDL res' of
Left err -> putStrLnErr (show err) >> exitFailure
Right mt -> do
stdGen <- getStdGen
let term = generateCBORTerm mt (Name (itemName gOpts) mempty) stdGen
in case outputFormat gOpts of
AsTerm -> print term
AsFlatTerm -> print $ toFlatTerm (encodeTerm term)
AsCBOR -> case outputTo gOpts of
Nothing -> BSC.putStrLn . Base16.encode . toStrictByteString $ encodeTerm term
Just out -> BSC.writeFile out $ toStrictByteString $ encodeTerm term
AsPrettyCBOR -> putStrLn . prettyHexEnc $ encodeTerm term
ValidateCBOR vcOpts ->
let
res'
| vcNoPrelude vcOpts = res
| otherwise = prependPrelude res
in
case fullResolveCDDL res' of
Left err -> putStrLnErr (show err) >> exitFailure
Right mt -> do
cbor <- BSC.readFile (vcInput vcOpts)
validateCBOR cbor (Name (vcItemName vcOpts) mempty) mt
putStrLnErr :: String -> IO ()
putStrLnErr = hPutStrLn stderr
parseFromFile ::
Parsec e T.Text a ->
String ->
IO (Either (ParseErrorBundle T.Text e) a)
parseFromFile p file = runParser p file <$> T.readFile file