1- {-# LANGUAGE DataKinds #-}
2- {-# LANGUAGE GADTs #-}
3- {-# LANGUAGE LambdaCase #-}
4- {-# LANGUAGE OverloadedStrings #-}
5- {-# LANGUAGE RecordWildCards #-}
6- {-# LANGUAGE TypeFamilies #-}
7- {-# LANGUAGE UndecidableInstances #-}
1+ {-# LANGUAGE ConstraintKinds #-}
2+ {-# LANGUAGE DataKinds #-}
3+ {-# LANGUAGE FlexibleContexts #-}
4+ {-# LANGUAGE FlexibleInstances #-}
5+ {-# LANGUAGE GADTs #-}
6+ {-# LANGUAGE LambdaCase #-}
7+ {-# LANGUAGE MultiParamTypeClasses #-}
8+ {-# LANGUAGE OverloadedStrings #-}
9+ {-# LANGUAGE RankNTypes #-}
10+ {-# LANGUAGE RecordWildCards #-}
11+ {-# LANGUAGE ScopedTypeVariables #-}
12+ {-# LANGUAGE TypeFamilies #-}
13+ {-# LANGUAGE TypeOperators #-}
14+ {-# LANGUAGE UndecidableInstances #-}
15+
816
917module Ide.Plugin.Properties
1018 ( PropertyType (.. ),
@@ -14,8 +22,10 @@ module Ide.Plugin.Properties
1422 PropertyKey (.. ),
1523 SPropertyKey (.. ),
1624 KeyNameProxy (.. ),
25+ KeyNamePath (.. ),
1726 Properties ,
1827 HasProperty ,
28+ HasPropertyByPath ,
1929 emptyProperties ,
2030 defineNumberProperty ,
2131 defineIntegerProperty ,
@@ -24,14 +34,18 @@ module Ide.Plugin.Properties
2434 defineObjectProperty ,
2535 defineArrayProperty ,
2636 defineEnumProperty ,
37+ definePropertiesProperty ,
2738 toDefaultJSON ,
2839 toVSCodeExtensionSchema ,
2940 usePropertyEither ,
3041 useProperty ,
42+ usePropertyByPathEither ,
43+ usePropertyByPath ,
3144 (&) ,
3245 )
3346where
3447
48+ import Control.Arrow (first )
3549import qualified Data.Aeson as A
3650import qualified Data.Aeson.Types as A
3751import Data.Either (fromRight )
@@ -43,6 +57,7 @@ import qualified Data.Text as T
4357import GHC.OverloadedLabels (IsLabel (.. ))
4458import GHC.TypeLits
4559
60+
4661-- | Types properties may have
4762data PropertyType
4863 = TNumber
@@ -52,6 +67,7 @@ data PropertyType
5267 | TObject Type
5368 | TArray Type
5469 | TEnum Type
70+ | TProperties [PropertyKey ] -- ^ A typed TObject, defined in a recursive manner
5571
5672type family ToHsType (t :: PropertyType ) where
5773 ToHsType 'TNumber = Double -- in js, there are no distinct types for integers and floating-point values
@@ -61,13 +77,14 @@ type family ToHsType (t :: PropertyType) where
6177 ToHsType ('TObject a ) = a
6278 ToHsType ('TArray a ) = [a ]
6379 ToHsType ('TEnum a ) = a
80+ ToHsType ('TProperties _ ) = A. Object
6481
6582-- ---------------------------------------------------------------------
6683
6784-- | Metadata of a property
6885data MetaData (t :: PropertyType ) where
6986 MetaData ::
70- (IsTEnum t ~ 'False) =>
87+ (IsTEnum t ~ 'False, IsProperties t ~ 'False ) =>
7188 { defaultValue :: ToHsType t ,
7289 description :: T. Text
7390 } ->
@@ -80,6 +97,15 @@ data MetaData (t :: PropertyType) where
8097 enumDescriptions :: [T. Text ]
8198 } ->
8299 MetaData t
100+ PropertiesMetaData ::
101+ (t ~ TProperties rs ) =>
102+ {
103+ defaultValue :: ToHsType t
104+ , description :: T. Text
105+ , childrenProperties :: Properties rs
106+ } ->
107+ MetaData t
108+
83109
84110-- | Used at type level for name-type mapping in 'Properties'
85111data PropertyKey = PropertyKey Symbol PropertyType
@@ -93,6 +119,7 @@ data SPropertyKey (k :: PropertyKey) where
93119 SObject :: (A. ToJSON a , A. FromJSON a ) => Proxy a -> SPropertyKey ('PropertyKey s ('TObject a ))
94120 SArray :: (A. ToJSON a , A. FromJSON a ) => Proxy a -> SPropertyKey ('PropertyKey s ('TArray a ))
95121 SEnum :: (A. ToJSON a , A. FromJSON a , Eq a , Show a ) => Proxy a -> SPropertyKey ('PropertyKey s ('TEnum a ))
122+ SProperties :: SPropertyKey ('PropertyKey s ('TProperties pp ))
96123
97124-- | Existential wrapper of 'SPropertyKey', with an extra 'MetaData'
98125data SomePropertyKeyWithMetaData
@@ -116,12 +143,53 @@ data KeyNameProxy (s :: Symbol) = KnownSymbol s => KeyNameProxy
116143instance (KnownSymbol s' , s ~ s' ) => IsLabel s (KeyNameProxy s' ) where
117144 fromLabel = KeyNameProxy
118145
146+ data NonEmptyList a =
147+ a :| NonEmptyList a | NE a
148+
149+ -- | a path to a property in a json object
150+ data KeyNamePath (r :: NonEmptyList Symbol ) where
151+ SingleKey :: KeyNameProxy s -> KeyNamePath (NE s )
152+ ConsKeysPath :: KeyNameProxy s1 -> KeyNamePath ss -> KeyNamePath (s1 :| ss )
153+
154+ class ParsePropertyPath (rs :: [PropertyKey ]) (r :: NonEmptyList Symbol ) where
155+ usePropertyByPathEither :: KeyNamePath r -> Properties rs -> A. Object -> Either String (ToHsType (FindByKeyPath r rs ))
156+ useDefault :: KeyNamePath r -> Properties rs -> ToHsType (FindByKeyPath r rs )
157+ usePropertyByPath :: KeyNamePath r -> Properties rs -> A. Object -> ToHsType (FindByKeyPath r rs )
158+ usePropertyByPath p ps x = fromRight (useDefault p ps) $ usePropertyByPathEither p ps x
159+
160+ instance (HasProperty s k t r ) => ParsePropertyPath r (NE s ) where
161+ usePropertyByPathEither (SingleKey kn) sm x = parseProperty kn (find kn sm) x
162+ useDefault (SingleKey kn) sm = defaultValue metadata
163+ where (_, metadata) = find kn sm
164+
165+ instance ( ToHsType (FindByKeyPath ss r2 ) ~ ToHsType (FindByKeyPath (s :| ss ) r )
166+ ,HasProperty s ('PropertyKey s ('TProperties r2 )) t2 r
167+ , ParsePropertyPath r2 ss )
168+ => ParsePropertyPath r (s :| ss ) where
169+ usePropertyByPathEither (ConsKeysPath kn p) sm x = do
170+ let (key, meta) = find kn sm
171+ interMedia <- parseProperty kn (key, meta) x
172+ case meta of
173+ PropertiesMetaData {.. }
174+ -> usePropertyByPathEither p childrenProperties interMedia
175+ useDefault (ConsKeysPath kn p) sm = case find kn sm of
176+ (_, PropertiesMetaData {.. }) -> useDefault p childrenProperties
177+
119178-- ---------------------------------------------------------------------
120179
180+ type family IsProperties (t :: PropertyType ) :: Bool where
181+ IsProperties ('TProperties pp ) = 'True
182+ IsProperties _ = 'False
183+
121184type family IsTEnum (t :: PropertyType ) :: Bool where
122185 IsTEnum ('TEnum _ ) = 'True
123186 IsTEnum _ = 'False
124187
188+ type family FindByKeyPath (ne :: NonEmptyList Symbol ) (r :: [PropertyKey ]) :: PropertyType where
189+ FindByKeyPath (s :| xs ) ('PropertyKey s ('TProperties rs ) ': _ ) = FindByKeyPath xs rs
190+ FindByKeyPath (s :| xs ) (_ ': ys ) = FindByKeyPath (s :| xs ) ys
191+ FindByKeyPath (NE s ) ys = FindByKeyName s ys
192+
125193type family FindByKeyName (s :: Symbol ) (r :: [PropertyKey ]) :: PropertyType where
126194 FindByKeyName s ('PropertyKey s t ': _ ) = t
127195 FindByKeyName s (_ ': xs ) = FindByKeyName s xs
@@ -140,10 +208,13 @@ type family NotElem (s :: Symbol) (r :: [PropertyKey]) :: Constraint where
140208 NotElem s (_ ': xs ) = NotElem s xs
141209 NotElem s '[] = ()
142210
211+
143212-- | In row @r@, there is a 'PropertyKey' @k@, which has name @s@ and carries haskell type @t@
144- type HasProperty s k t r = (k ~ 'PropertyKey s t , Elem s r , FindByKeyName s r ~ t , KnownSymbol s , FindPropertyMeta s r t )
213+ type HasProperty s k t r = (k ~ 'PropertyKey s t , Elem s r , FindByKeyPath (NE s ) r ~ t , FindByKeyName s r ~ t , KnownSymbol s , FindPropertyMeta s r t )
214+ -- similar to HasProperty, but the path is given as a type-level list of symbols
215+ type HasPropertyByPath props path t = (t ~ FindByKeyPath path props , ParsePropertyPath props path )
145216class FindPropertyMeta (s :: Symbol ) (r :: [PropertyKey ]) t where
146- findSomePropertyKeyWithMetaData :: KeyNameProxy s -> Properties r -> (SPropertyKey ('PropertyKey s t ), MetaData t )
217+ findSomePropertyKeyWithMetaData :: KeyNameProxy s -> Properties r -> (SPropertyKey ('PropertyKey s t ), MetaData t )
147218instance (FindPropertyMetaIf (IsPropertySymbol symbol k ) symbol k ks t ) => FindPropertyMeta symbol (k : ks ) t where
148219 findSomePropertyKeyWithMetaData = findSomePropertyKeyWithMetaDataIf
149220class (bool ~ IsPropertySymbol symbol k ) => FindPropertyMetaIf bool symbol k ks t where
@@ -219,6 +290,7 @@ parseProperty ::
219290 A. Object ->
220291 Either String (ToHsType t )
221292parseProperty kn k x = case k of
293+ (SProperties , _) -> parseEither
222294 (SNumber , _) -> parseEither
223295 (SInteger , _) -> parseEither
224296 (SString , _) -> parseEither
@@ -338,6 +410,16 @@ defineEnumProperty ::
338410defineEnumProperty kn description enums defaultValue =
339411 insert kn (SEnum Proxy ) $ EnumMetaData defaultValue description (fst <$> enums) (snd <$> enums)
340412
413+ definePropertiesProperty ::
414+ (KnownSymbol s , NotElem s r ) =>
415+ KeyNameProxy s ->
416+ T. Text ->
417+ Properties childrenProps ->
418+ Properties r ->
419+ Properties ('PropertyKey s ('TProperties childrenProps ) : r )
420+ definePropertiesProperty kn description ps rs =
421+ insert kn SProperties (PropertiesMetaData mempty description ps) rs
422+
341423-- ---------------------------------------------------------------------
342424
343425-- | Converts a properties definition into kv pairs with default values from 'MetaData'
@@ -363,64 +445,74 @@ toDefaultJSON pr = case pr of
363445 fromString s A. .= defaultValue
364446 (SomePropertyKeyWithMetaData (SEnum _) EnumMetaData {.. }) ->
365447 fromString s A. .= defaultValue
448+ (SomePropertyKeyWithMetaData SProperties PropertiesMetaData {.. }) ->
449+ fromString s A. .= A. object (toDefaultJSON childrenProperties)
366450
367451-- | Converts a properties definition into kv pairs as vscode schema
368452toVSCodeExtensionSchema :: T. Text -> Properties r -> [A. Pair ]
369- toVSCodeExtensionSchema prefix ps = case ps of
453+ toVSCodeExtensionSchema prefix p = [fromString (T. unpack prefix <> fromString k) A. .= v | (k, v) <- toVSCodeExtensionSchema' p]
454+ toVSCodeExtensionSchema' :: Properties r -> [(String , A. Value )]
455+ toVSCodeExtensionSchema' ps = case ps of
370456 EmptyProperties -> []
371457 ConsProperties (keyNameProxy :: KeyNameProxy s ) (k :: SPropertyKey k ) (m :: MetaData t ) xs ->
372- fromString (T. unpack prefix <> symbolVal keyNameProxy) A. .= toEntry (SomePropertyKeyWithMetaData k m) : toVSCodeExtensionSchema prefix xs
458+ [(symbolVal keyNameProxy <> maybe " " ((<>) " ." ) k1, v)
459+ | (k1, v) <- toEntry (SomePropertyKeyWithMetaData k m) ]
460+ ++ toVSCodeExtensionSchema' xs
373461 where
374- toEntry :: SomePropertyKeyWithMetaData -> A. Value
462+ wrapEmpty :: A. Value -> [(Maybe String , A. Value )]
463+ wrapEmpty v = [(Nothing , v)]
464+ toEntry :: SomePropertyKeyWithMetaData -> [(Maybe String , A. Value )]
375465 toEntry = \ case
376466 (SomePropertyKeyWithMetaData SNumber MetaData {.. }) ->
377- A. object
467+ wrapEmpty $ A. object
378468 [ " type" A. .= A. String " number" ,
379469 " markdownDescription" A. .= description,
380470 " default" A. .= defaultValue,
381471 " scope" A. .= A. String " resource"
382472 ]
383473 (SomePropertyKeyWithMetaData SInteger MetaData {.. }) ->
384- A. object
474+ wrapEmpty $ A. object
385475 [ " type" A. .= A. String " integer" ,
386476 " markdownDescription" A. .= description,
387477 " default" A. .= defaultValue,
388478 " scope" A. .= A. String " resource"
389479 ]
390480 (SomePropertyKeyWithMetaData SString MetaData {.. }) ->
391- A. object
481+ wrapEmpty $ A. object
392482 [ " type" A. .= A. String " string" ,
393483 " markdownDescription" A. .= description,
394484 " default" A. .= defaultValue,
395485 " scope" A. .= A. String " resource"
396486 ]
397487 (SomePropertyKeyWithMetaData SBoolean MetaData {.. }) ->
398- A. object
488+ wrapEmpty $ A. object
399489 [ " type" A. .= A. String " boolean" ,
400490 " markdownDescription" A. .= description,
401491 " default" A. .= defaultValue,
402492 " scope" A. .= A. String " resource"
403493 ]
404494 (SomePropertyKeyWithMetaData (SObject _) MetaData {.. }) ->
405- A. object
495+ wrapEmpty $ A. object
406496 [ " type" A. .= A. String " object" ,
407497 " markdownDescription" A. .= description,
408498 " default" A. .= defaultValue,
409499 " scope" A. .= A. String " resource"
410500 ]
411501 (SomePropertyKeyWithMetaData (SArray _) MetaData {.. }) ->
412- A. object
502+ wrapEmpty $ A. object
413503 [ " type" A. .= A. String " array" ,
414504 " markdownDescription" A. .= description,
415505 " default" A. .= defaultValue,
416506 " scope" A. .= A. String " resource"
417507 ]
418508 (SomePropertyKeyWithMetaData (SEnum _) EnumMetaData {.. }) ->
419- A. object
509+ wrapEmpty $ A. object
420510 [ " type" A. .= A. String " string" ,
421511 " description" A. .= description,
422512 " enum" A. .= enumValues,
423513 " enumDescriptions" A. .= enumDescriptions,
424514 " default" A. .= defaultValue,
425515 " scope" A. .= A. String " resource"
426516 ]
517+ (SomePropertyKeyWithMetaData SProperties PropertiesMetaData {.. }) ->
518+ map (first Just ) $ toVSCodeExtensionSchema' childrenProperties
0 commit comments