cabal-buck2-0.1.0.0: src/Distribution/Client/Buck2/Starlark.hs
-- | A minimal Starlark (BUCK file) pretty-printer.
--
-- This deliberately doesn't attempt to represent the whole Starlark
-- language: it covers exactly the subset (string/int/bool scalars, lists,
-- dicts, @load@ statements and top-level rule calls) that generated
-- @BUCK@\/@.bzl@ files need, so every generator in "Distribution.Client.Buck2"
-- builds output through the same renderer instead of hand-formatting
-- strings.
module Distribution.Client.Buck2.Starlark
( Value (..)
, str
, strList
, Call (..)
, call
, renderCall
, renderLoad
, renderBinding
, renderFile
) where
import Distribution.Client.Compat.Prelude
import Prelude ()
-- | A Starlark expression, restricted to what BUCK files actually need:
-- scalars and (recursively) lists and dicts of them.
data Value
= VStr String
| VInt Int
| VBool Bool
| VList [Value]
| VDict [(String, Value)]
str :: String -> Value
str = VStr
strList :: [String] -> Value
strList = VList . map VStr
-- | A single top-level rule invocation, e.g. @haskell_library(name = ..., ...)@.
data Call = Call
{ callFn :: String
, callArgs :: [(String, Value)]
}
call :: String -> [(String, Value)] -> Call
call = Call
indent :: Int -> String
indent n = replicate (4 * n) ' '
-- | Render like Python's @repr@ for a single-quoted string: this is what
-- both buck2/gen-haskell-prebuilt.py's generated output and every
-- hand-written @.bzl@ file in buck2/ already use.
renderStr :: String -> String
renderStr s = '\'' : concatMap escape s ++ "'"
where
escape '\\' = "\\\\"
escape '\'' = "\\'"
escape '\n' = "\\n"
escape '\t' = "\\t"
escape c = [c]
renderValue :: Int -> Value -> String
renderValue _ (VStr s) = renderStr s
renderValue _ (VInt n) = show n
renderValue _ (VBool b) = if b then "True" else "False"
renderValue _ (VList []) = "[]"
renderValue ind (VList xs) =
"[\n"
++ concat [indent (ind + 1) ++ renderValue (ind + 1) x ++ ",\n" | x <- xs]
++ indent ind
++ "]"
renderValue _ (VDict []) = "{}"
renderValue ind (VDict kvs) =
"{\n"
++ concat
[ indent (ind + 1) ++ renderStr k ++ ": " ++ renderValue (ind + 1) v ++ ",\n"
| (k, v) <- kvs
]
++ indent ind
++ "}"
-- | Render a rule call as a standalone top-level statement, one argument
-- per line (matching the style buck2/gen-haskell-prebuilt.py's own
-- generated BUCK file uses), terminated by a blank line.
renderCall :: Call -> String
renderCall (Call fn args) =
fn
++ "(\n"
++ concat
[ indent 1 ++ k ++ " = " ++ renderValue 1 v ++ ",\n"
| (k, v) <- args
]
++ ")\n"
-- | Render a top-level @NAME = value@ statement.
renderBinding :: String -> Value -> String
renderBinding name v = name ++ " = " ++ renderValue 0 v ++ "\n"
-- | Render a @load("target", "name1", "name2")@ statement.
renderLoad :: String -> [String] -> String
renderLoad target names =
"load(" ++ intercalate ", " (renderStr target : map renderStr names) ++ ")\n"
-- | Render a full generated file: a header comment, the accumulated
-- @load@ statements, then each rule call in turn, separated by blank
-- lines.
renderFile :: String -> [(String, [String])] -> [Call] -> String
renderFile header loads calls =
unlines (map ("# " ++) headerLines)
++ "\n"
++ concatMap (uncurry renderLoad) loads
++ "\n"
++ intercalate "\n" (map renderCall calls)
where
headerLines = lines header