{-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} -- | Contains checks for custom properties of the map json module Properties (checkProperty) where import Control.Monad (unless, when) import Data.Text (Text, isPrefixOf) import Tiled2 (Layer (layerProperties), Property (..), PropertyValue (..)) import Util (layerIsEmpty, prettyprint) import LintWriter (LintWriter, complain, dependsOn, forbid, info, suggest, warn) import Types (Dep (Link, Local, LocalMap, MapLink)) -- | the point of this module -- -- given a property, check if it is valid. It gets a reference -- to its own layer since sometimes the presense of one property -- implies the presence or absense of another. -- -- The tests in here are meant to comply with the informal spec -- at https://workadventu.re/map-building -- -- I've attempted to build the LintWriter monad in a way -- that should make this readable even to non-Haskellers checkProperty :: Layer -> Property -> LintWriter () checkProperty layer (Property name value) = case name of "jitsiRoom" -> do uselessEmptyLayer unwrapString $ \val -> do info $ "found jitsi room: " <> prettyprint val suggestPropertyValue "jitsiTrigger" "onaction" "jitsiTrigger" -> do isString unless (hasProperty "jitsiTriggerMessage" layer) $ suggest "set \"jitsiTriggerMessage\" to a custom message to overwrite the default \"press SPACE to enter in jitsi meet room\"" requireProperty "jitsiRoom" "jitsiTriggerMessage" -> do isString requireProperty "jitsiTrigger" "jitsiUrl" -> isForbidden "jitsiConfig" -> isForbidden "jitsiClientConfig" -> isForbidden "jitsiRoomAdminTag" -> isForbidden "playAudio" -> do uselessEmptyLayer unwrapLink $ \link -> dependsOn $ if "https://" `isPrefixOf` link then Link link else Local link "audioLoop" -> do isBool requireProperty "playAudio" "audioVolume" -> do isBool requireProperty "playAudio" "openWebsite" -> do uselessEmptyLayer suggestPropertyValue "openWebsiteTrigger" "onaction" unwrapLink $ \link -> dependsOn $ if "https://" `isPrefixOf` link then Link link else Local link "openWebsiteTrigger" -> do isString unless (hasProperty "openWebsiteTriggerMessage" layer) $ suggest "set \"openWebsiteTriggerMessage\" to a custom message to overwrite the generic \"press SPACE to open Website\"" requireProperty "openWebsite" "openWebsiteTriggerMessage" -> do isString requireProperty "openWebsiteTrigger" "openWebsitePolicy" -> do isString requireProperty "openWebsite" "openTab" -> do isString requireProperty "openWebsite" "url" -> isForbidden "allowApi" -> isForbidden "exitUrl" -> do forbidEmptyLayer unwrapLink $ \link -> dependsOn $ if "https://" `isPrefixOf` link then MapLink link else LocalMap link "startLayer" -> do forbidEmptyLayer unwrapBool $ \case True -> pure () False -> complain "startLayer must be set to true" "silent" -> do isBool uselessEmptyLayer _ -> complain $ "unknown property type " <> prettyprint name where -- | asserts that this property is a string, and unwraps it unwrapString f = case value of StrProp str -> f str _ -> complain $ "type mismatch in property " <> name <> "; should be of type string" -- | same as unwrapString, but also forbids http:// as prefix unwrapLink f = case value of StrProp str -> if "http://" `isPrefixOf` str then complain "cannot access content via http; either use https or include it locally instead." else f str _ -> complain $ "type mismatch in property " <> name <> "; should be of typ string" -- | asserts that this property is a boolean, and unwraps it unwrapBool f = case value of BoolProp b -> f b _ -> complain $ "type mismatch in property " <> name <> "; should be of type bool" -- | just asserts that this is a string isString = unwrapString (const $ pure ()) -- | just asserts that this is a boolean isBool = unwrapBool (const $ pure ()) -- | this property is forbidden and should not be used isForbidden = forbid $ "property " <> prettyprint name <> " should not be used" -- | this property can only be used on a layer that contains at least one tiles forbidEmptyLayer = when (layerIsEmpty layer) $ complain ("property " <> name <> " should not be set on an empty layer") -- | this layer is allowed, but also useless on a layer that contains no tiles uselessEmptyLayer = when (layerIsEmpty layer) $ warn ("property" <> name <> " was set on an empty layer and is thereby useless") -- | require some property in this layer requireProperty name = unless (hasProperty name layer) $ complain $ "property "<>prettyprint name<>" requires property "<>prettyprint name -- | suggest a certain value for some other property in this layer suggestPropertyValue :: Text -> Text -> LintWriter () suggestPropertyValue name value = unless (hasProperty name layer) $ suggest $ "set property " <> prettyprint name <> " to " <> prettyprint value -- | does this layer have the given property? hasProperty :: Text -> Layer -> Bool hasProperty name = any (\(Property name' _) -> name' == name) . layerProperties