valiant-cli-0.1.0.0: src/Valiant/CLI/SqlMetadata.hs
module Valiant.CLI.SqlMetadata
( SqlMetadata (..)
, parseSqlMetadata
, defaultMetadata
) where
import Data.List qualified as List
import Data.Text (Text)
import Data.Text qualified as T
-- | Optional metadata parsed from @-- valiant:@ comments in SQL files.
data SqlMetadata = SqlMetadata
{ smName :: Maybe Text
-- ^ Override the generated binding name (@-- valiant:name getUserById@).
, smResult :: Maybe Text
-- ^ Use a named result type (@-- valiant:result User@).
, smSingle :: Bool
-- ^ Expect exactly one row (@-- valiant:single@). If set, the generated
-- binding returns @r@ instead of @Maybe r@ or @[r]@.
}
deriving stock (Show, Eq)
defaultMetadata :: SqlMetadata
defaultMetadata =
SqlMetadata
{ smName = Nothing
, smResult = Nothing
, smSingle = False
}
-- | Parse @-- valiant:@ directives from SQL file content.
-- Only lines starting with @-- valiant:@ (with optional leading whitespace)
-- are recognised. The directives must appear before the first SQL keyword.
parseSqlMetadata :: Text -> SqlMetadata
parseSqlMetadata = List.foldl' applyDirective defaultMetadata . extractDirectives
extractDirectives :: Text -> [(Text, Text)]
extractDirectives sql =
[ parseDirective line
| line <- T.lines sql
, isDirective line
]
isDirective :: Text -> Bool
isDirective line =
let stripped = T.stripStart line
in T.isPrefixOf "-- valiant:" stripped
parseDirective :: Text -> (Text, Text)
parseDirective line =
let stripped = T.stripStart line
-- Remove "-- valiant:" prefix
afterPrefix = T.drop 11 stripped -- length "-- valiant:" == 11
(key, rest) = T.break (== ' ') (T.strip afterPrefix)
in (T.toLower key, T.strip rest)
applyDirective :: SqlMetadata -> (Text, Text) -> SqlMetadata
applyDirective meta (key, value) = case key of
"name" | not (T.null value) -> meta {smName = Just value}
"result" | not (T.null value) -> meta {smResult = Just value}
"single" -> meta {smSingle = True}
_ -> meta