cuddle-1.1.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 (..), fromRules, sortCDDL)
import Codec.CBOR.Cuddle.CDDL.CTree (CTreeRoot)
import Codec.CBOR.Cuddle.CDDL.Postlude (appendPostlude)
import Codec.CBOR.Cuddle.CDDL.Resolve (
fullResolveCDDL,
)
import Codec.CBOR.Cuddle.IndexMappable (IndexMappable (..), mapCDDLDropExt)
import Codec.CBOR.Cuddle.Parser (pCDDL)
import Codec.CBOR.Cuddle.Pretty (PrettyStage)
import Codec.CBOR.FlatTerm (toFlatTerm)
import Codec.CBOR.Pretty (prettyHexEnc)
import Codec.CBOR.Term (encodeTerm)
import Codec.CBOR.Write (toStrictByteString)
import Data.ByteString qualified as BS
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 (
LayoutOptions (..),
PageWidth (..),
Pretty (pretty),
defaultLayoutOptions,
layoutPretty,
)
import Prettyprinter.Render.Text qualified as PT
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 = fromRules $ sortCDDL res
| otherwise = res
layoutOptions = defaultLayoutOptions {layoutPageWidth = AvailablePerLine 80 1}
formattedText =
PT.renderStrict . layoutPretty layoutOptions . pretty $
mapIndex @_ @_ @PrettyStage defs
strippedText = T.unlines . fmap (T.dropWhileEnd (== ' ')) $ T.lines formattedText
in
T.putStr strippedText
Validate vOpts ->
let
cddl
| vNoPrelude vOpts = res
| otherwise = appendPostlude res
in
case fullResolveCDDL $ mapCDDLDropExt cddl of
Left err -> putStrLnErr (show err) >> exitFailure
Right _ -> exitSuccess
GenerateCBOR gOpts ->
let
cddl
| gNoPrelude gOpts = res
| otherwise = appendPostlude res
in
case fullResolveCDDL $ mapCDDLDropExt cddl of
Left err -> putStrLnErr (show err) >> exitFailure
Right mt -> do
stdGen <- getStdGen
let term = generateCBORTerm mt (Name $ itemName gOpts) 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
cddl
| vcNoPrelude vcOpts = res
| otherwise = res
in
case fullResolveCDDL $ mapCDDLDropExt cddl of
Left err -> putStrLnErr (show err) >> exitFailure
Right mt -> do
cbor <- BSC.readFile (vcInput vcOpts)
runValidateCBOR cbor (Name $ vcItemName vcOpts) (mapIndex 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
runValidateCBOR :: BS.ByteString -> Name -> CTreeRoot ValidatorStage -> IO ()
runValidateCBOR bs rule cddl =
case validateCBOR bs rule cddl of
ok@(CBORTermResult _ (Valid _)) -> do
putStrLn $ "Valid " ++ show ok
exitSuccess
err -> do
hPutStrLn stderr $ "Invalid " ++ show err
exitFailure