dojang-0.1.0: src/Dojang/Syntax/EnvironmentPredicate/Writer.hs
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
module Dojang.Syntax.EnvironmentPredicate.Writer
( writeEnvironmentPredicate
)
where
import Dojang.Types.Environment (Architecture, OperatingSystem)
import Dojang.Types.EnvironmentPredicate (EnvironmentPredicate (..))
import Dojang.Types.MonikerName (MonikerName)
import Data.CaseInsensitive (CI (original))
import Data.Char (isAlpha, isAlphaNum, isControl)
import Data.List.NonEmpty (toList)
import Data.Text
( Text
, all
, concatMap
, elem
, intercalate
, justifyRight
, pack
, singleton
, uncons
)
import Numeric (showHex)
import Prelude hiding (all, concatMap, elem, reverse)
-- | Turns an 'EnvironmentPredicate' into a string that can be used in a
-- @dojang.toml@ file. The string can be parsed back into an
-- 'EnvironmentPredicate' using
-- 'Dojang.Syntax.EnvironmentPredicate.Parser.parseEnvironmentPredicate'.
writeEnvironmentPredicate :: EnvironmentPredicate -> Text
writeEnvironmentPredicate = fst . write
write
:: EnvironmentPredicate
-> (Text, Bool)
-- \^ @(when, can be split by &&)@
write Always = ("always", False)
write (OperatingSystem os) =
("os = " <> stringLiteral (original os.identifier), False)
write (Architecture arch) =
("arch = " <> stringLiteral (original arch.identifier), False)
write (KernelName kernel) =
("kernel = " <> stringLiteral (original kernel), False)
write (KernelRelease rel) =
("kernel-release = " <> stringLiteral (original rel), False)
write (KernelReleasePrefix prefix) =
("kernel-release ^= " <> stringLiteral (original prefix), False)
write (KernelReleaseSuffix suffix) =
("kernel-release $= " <> stringLiteral (original suffix), False)
write (Moniker moniker) =
("moniker = " <> stringLiteral (original moniker.name), False)
write (And predicates) =
case (excludeOses, excludeArchitectures, excludeMonikers) of
(_ : _, [], [])
| length excludeOses == length predicates ->
( "os not in "
<> strings [original os.identifier | os <- excludeOses]
, False
)
([], _ : _, [])
| length excludeArchitectures == length predicates ->
( "arch not in "
<> strings
[ original arch.identifier
| arch <- excludeArchitectures
]
, False
)
([], [], _ : _)
| length excludeMonikers == length predicates ->
( "moniker not in "
<> strings [original moniker.name | moniker <- excludeMonikers]
, False
)
_ ->
( intercalate
" && "
[ let (when', needParentheses) = write p
in if needParentheses then "(" <> when' <> ")" else when'
| p <- toList predicates
]
, False
)
where
excludeOses :: [OperatingSystem]
excludeOses = [os | Not (OperatingSystem os) <- toList predicates]
excludeArchitectures :: [Architecture]
excludeArchitectures = [arch | Not (Architecture arch) <- toList predicates]
excludeMonikers :: [MonikerName]
excludeMonikers = [moniker | Not (Moniker moniker) <- toList predicates]
write (Or predicates) =
case (oses, architectures, monikers) of
(_ : _, [], [])
| length oses == length predicates ->
("os in " <> strings [original os.identifier | os <- oses], False)
([], _ : _, [])
| length architectures == length predicates ->
( "arch in "
<> strings [original arch.identifier | arch <- architectures]
, False
)
([], [], _ : _)
| length monikers == length predicates ->
( "moniker in "
<> strings [original moniker.name | moniker <- monikers]
, False
)
_ -> (intercalate " || " (fst . write <$> toList predicates), True)
where
oses :: [OperatingSystem]
oses = [os | OperatingSystem os <- toList predicates]
architectures :: [Architecture]
architectures = [arch | Architecture arch <- toList predicates]
monikers :: [MonikerName]
monikers = [moniker | Moniker moniker <- toList predicates]
write (Not Always) = ("never", False)
write (Not (OperatingSystem os)) =
("os != " <> stringLiteral (original os.identifier), False)
write (Not (Architecture arch)) =
("arch != " <> stringLiteral (original arch.identifier), False)
write (Not (Moniker moniker)) =
("moniker != " <> stringLiteral (original moniker.name), False)
write (Not p) = ("!(" <> fst (write p) <> ")", False)
stringLiteral :: Text -> Text
stringLiteral v = case uncons v of
Nothing -> "\"\""
Just (c, cs) | isAlpha c && all isAlphaNum cs -> v
_ | '"' `elem` v && not ('\'' `elem` v) -> "'" <> escapeText False v <> "'"
_ -> "\"" <> escapeText True v <> "\""
where
escapeText :: Bool -> Text -> Text
escapeText doubleQuote = concatMap (escape doubleQuote)
escape :: Bool -> Char -> Text
escape True '"' = "\\\""
escape False '\'' = "\\'"
escape _ '\\' = "\\\\"
escape _ '\b' = "\\b"
escape _ '\f' = "\\f"
escape _ '\n' = "\\n"
escape _ '\r' = "\\r"
escape _ '\t' = "\\t"
escape _ '\v' = "\\v"
escape _ '\0' = "\\0"
escape _ c
| not (isControl c) && c < '\x80' = singleton c
| c <= '\xff' = "\\x" <> justifyRight 2 '0' (hex c)
| c <= '\xffff' = "\\u" <> justifyRight 4 '0' (hex c) -- cSpell: disable-line
| otherwise = "\\U" <> justifyRight 8 '0' (hex c)
where
hex :: Char -> Text
hex = pack . (`showHex` "") . fromEnum
strings :: [Text] -> Text
strings = ("(" <>) . (<> ")") . intercalate ", " . fmap stringLiteral