{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} module Main where import Data.Conduit ((.|)) import qualified Data.Conduit as C import qualified Data.Conduit.Combinators as CC import qualified GHC.Base import Protolude import qualified Data.List as L import Options.Applicative hiding ((<>)) import qualified System.FilePath.Glob as G import qualified JSONSchema.Draft4.Internal.Utils as Utils import JSONSchema.Draft4.SchemaGeneration (jsonToSchemaWithConfig, unifySchemas) import qualified JSONSchema.Draft4.SchemaGenerationConfig as SGC data Input = FileInput [FilePath] | StandardInput deriving (Show) data Options = Options { input :: Input , generationConfig :: SGC.SchemaGenerationConfig } deriving (Show) version :: Parser (a -> a) version = infoOption "JSON to Schema 0.1.0" ( long "version" <> short 'v' <> help "Prints version information." ) configParser :: Parser SGC.SchemaGenerationConfig configParser = SGC.SchemaGenerationConfig <$> switch ( long "type-arrays-as-tuples" <> help "When disabled (default), all objects in an array are assumed to have the \ \ same type and are unified accordingly. When enabled, arrays are considered to\ \ be tuples and each index will have its own type. For example, [14, false]\ \ is either an array that can take integer or boolean objects (disabled) or a tuple \ \ that is an integer in its first index and a boolean in its second.") <*> switch ( long "seal-object-properties" <> help "Toggles additionalProperties for each object instance in the JSON schema. \ \ When disabled (default), a JSON object instance that has more\ \ properties than specified in the schema will continue to validate. \ \ When enabled, a JSON object instance that has more properties \ \ than specified will no longer validate.") fileInputParser :: Parser Input fileInputParser = FileInput <$> some (strOption (long "path" <> short 'p' <> metavar "TARGET" <> help "Can be specified multiple times. Each path can be a glob.\ \ All files matching these paths will be read and their JSONs \ \ will be combined.")) standardInputParser :: Parser Input standardInputParser = flag' StandardInput ( long "stdin" <> help "Reads JSON from stdin instead of a path, e.g. cat file.json | jsons-to-schema --stdin") parser :: Parser Options parser = Options <$> (fileInputParser <|> standardInputParser) <*> configParser pInfo :: ParserInfo Options pInfo = info (parser <**> helper <**> version) ( fullDesc <> progDesc "A JSON Schema Draft 4 Generator from JSON instances." <> header "JSON to Schema") globPaths :: [GHC.Base.String] -> IO [FilePath] globPaths ss = (L.concat . fst) <$> G.globDir patterns "" where patterns = fmap G.compile ss getSource (FileInput fps) = do paths <- globPaths fps putText $ fromMaybe (panic "Could not find any files matching any of the globs provided") (if null paths then Nothing else Just "") return $ mapM_ CC.sourceFile paths getSource StandardInput = return $ mapM_ CC.sourceHandle [stdin] main :: IO () main = do options <- execParser pInfo let c = generationConfig options conduitSource <- getSource (input options) schema <- C.runConduitRes $ conduitSource .| CC.map (jsonToSchemaWithConfig c . Utils.parseValue) .| CC.foldl1 unifySchemas putText $ Utils.printSchema (fromMaybe (panic "Error unifying schemas.") schema)