on master help sign in
Initial commit
commit2ddecd053b555e21b0b1ad153e08151fa994a0d2 authoriko <ilyakooo0@gmail.com> dateFri 22 Jul 2022, 19:18 (4 years ago)
8 files changed+513
.vscode/settings.json+3 LICENSE+30 README.md+162 example.yaml+7 package.yaml+59 src/Elm.hs+60 src/Main.hs+188 stack.yaml+4
.vscode/settings.jsonnew file+3
@@ -0,0 +1,3 @@
1+{
2+ "editor.formatOnSave": true
3+}
LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright Ilia Kostiuchenko (c) 2022
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Author name here nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
README.mdnew file+162
@@ -0,0 +1,162 @@
1+# Elm PS Bridge
2+
3+A very rough tool to generate compatible ELm and PS type definitions from a common YAML definition. I intend to use them to communicate through Elm ports.
4+
5+## Example
6+
7+```bash
8+elm-ps-bridge --input example.yaml --elmOutput Bridge.elm --psOutput Bridge.purs
9+```
10+
11+### `example.yaml`
12+
13+```yaml
14+Subscription:
15+- DownloadPasswords
16+- UpdatePassword:
17+ foo: Int
18+ id: Int
19+Command:
20+- Hello
21+```
22+
23+
24+### `Bridge.purs`
25+
26+```purescript
27+-- File auto generated by purescript-bridge! --
28+module Bridge where
29+
30+import Data.Generic.Rep (class Generic)
31+import Data.Lens (Iso', Lens', Prism', lens, prism')
32+import Data.Lens.Iso.Newtype (_Newtype)
33+import Data.Lens.Record (prop)
34+import Data.Maybe (Maybe(..))
35+import Data.Newtype (class Newtype)
36+import Data.Symbol (SProxy(SProxy))
37+
38+import Prelude
39+
40+data Command =
41+ Hello
42+
43+derive instance genericCommand :: Generic Command _
44+derive instance newtypeCommand :: Newtype Command _
45+derive instance eqCommand :: Eq Command
46+derive instance ordCommand :: Ord Command
47+
48+--------------------------------------------------------------------------------
49+_Hello :: Prism' Command Unit
50+_Hello = prism' (\_ -> Hello) f
51+ where
52+ f Hello = Just unit
53+
54+--------------------------------------------------------------------------------
55+newtype Subscription_UpdatePassword =
56+ Subscription_UpdatePassword {
57+ foo :: Int
58+ , id :: Int
59+ }
60+
61+derive instance genericSubscription_UpdatePassword :: Generic Subscription_UpdatePassword _
62+derive instance newtypeSubscription_UpdatePassword :: Newtype Subscription_UpdatePassword _
63+derive instance eqSubscription_UpdatePassword :: Eq Subscription_UpdatePassword
64+derive instance ordSubscription_UpdatePassword :: Ord Subscription_UpdatePassword
65+
66+--------------------------------------------------------------------------------
67+_Subscription_UpdatePassword :: Iso' Subscription_UpdatePassword { foo :: Int, id :: Int}
68+_Subscription_UpdatePassword = _Newtype
69+
70+--------------------------------------------------------------------------------
71+data Subscription =
72+ DownloadPasswords
73+ | UpdatePassword Subscription_UpdatePassword
74+
75+derive instance genericSubscription :: Generic Subscription _
76+derive instance newtypeSubscription :: Newtype Subscription _
77+derive instance eqSubscription :: Eq Subscription
78+derive instance ordSubscription :: Ord Subscription
79+
80+--------------------------------------------------------------------------------
81+_DownloadPasswords :: Prism' Subscription Unit
82+_DownloadPasswords = prism' (\_ -> DownloadPasswords) f
83+ where
84+ f DownloadPasswords = Just unit
85+ f _ = Nothing
86+
87+_UpdatePassword :: Prism' Subscription Subscription_UpdatePassword
88+_UpdatePassword = prism' UpdatePassword f
89+ where
90+ f (UpdatePassword a) = Just $ a
91+ f _ = Nothing
92+
93+--------------------------------------------------------------------------------
94+```
95+
96+### `Bridge.elm`
97+
98+```elm
99+module Bridge exposing(..)
100+
101+import Json.Decode
102+import Json.Encode exposing (Value)
103+-- The following module comes from bartavelle/json-helpers
104+import Json.Helpers exposing (..)
105+import Dict exposing (Dict)
106+import Set exposing (Set)
107+
108+
109+type Command =
110+ Hello
111+
112+jsonDecCommand : Json.Decode.Decoder ( Command )
113+jsonDecCommand =
114+ let jsonDecDictCommand = Dict.fromList [("Hello", Hello)]
115+ in decodeSumUnaries "Command" jsonDecDictCommand
116+
117+jsonEncCommand : Command -> Value
118+jsonEncCommand val =
119+ case val of
120+ Hello -> Json.Encode.string "Hello"
121+
122+
123+
124+type alias Subscription_UpdatePassword =
125+ { foo: Int
126+ , id: Int
127+ }
128+
129+jsonDecSubscription_UpdatePassword : Json.Decode.Decoder ( Subscription_UpdatePassword )
130+jsonDecSubscription_UpdatePassword =
131+ Json.Decode.succeed (\pfoo pid -> {foo = pfoo, id = pid})
132+ |> required "foo" (Json.Decode.int)
133+ |> required "id" (Json.Decode.int)
134+
135+jsonEncSubscription_UpdatePassword : Subscription_UpdatePassword -> Value
136+jsonEncSubscription_UpdatePassword val =
137+ Json.Encode.object
138+ [ ("foo", Json.Encode.int val.foo)
139+ , ("id", Json.Encode.int val.id)
140+ ]
141+
142+
143+
144+type Subscription =
145+ DownloadPasswords
146+ | UpdatePassword Subscription_UpdatePassword
147+
148+jsonDecSubscription : Json.Decode.Decoder ( Subscription )
149+jsonDecSubscription =
150+ let jsonDecDictSubscription = Dict.fromList
151+ [ ("DownloadPasswords", Json.Decode.lazy (\_ -> Json.Decode.succeed DownloadPasswords))
152+ , ("UpdatePassword", Json.Decode.lazy (\_ -> Json.Decode.map UpdatePassword (jsonDecSubscription_UpdatePassword)))
153+ ]
154+ in decodeSumObjectWithSingleField "Subscription" jsonDecDictSubscription
155+
156+jsonEncSubscription : Subscription -> Value
157+jsonEncSubscription val =
158+ let keyval v = case v of
159+ DownloadPasswords -> ("DownloadPasswords", encodeValue (Json.Encode.list identity []))
160+ UpdatePassword v1 -> ("UpdatePassword", encodeValue (jsonEncSubscription_UpdatePassword v1))
161+ in encodeSumObjectWithSingleField keyval val
162+```
example.yamlnew file+7
@@ -0,0 +1,7 @@
1+Subscription:
2+- DownloadPasswords
3+- UpdatePassword:
4+ foo: Int
5+ id: Int
6+Command:
7+- Hello
package.yamlnew file+59
@@ -0,0 +1,59 @@
1+name: elm-ps-bridge
2+license: MIT
3+
4+dependencies:
5+ - base
6+ - bytestring
7+ - yaml
8+ - aeson
9+ - vector
10+ - relude
11+ - optparse-generic
12+ - purescript-bridge
13+ - elm-bridge
14+ - mtl
15+
16+executable:
17+ main: Main.hs
18+ source-dirs: src
19+
20+default-extensions:
21+ - NoImplicitPrelude
22+ - AllowAmbiguousTypes
23+ - ApplicativeDo
24+ - BangPatterns
25+ - BlockArguments
26+ - ConstraintKinds
27+ - DataKinds
28+ - DefaultSignatures
29+ - DeriveAnyClass
30+ - DeriveFunctor
31+ - DeriveGeneric
32+ - DerivingVia
33+ - DuplicateRecordFields
34+ - FlexibleContexts
35+ - FlexibleInstances
36+ - FunctionalDependencies
37+ - GADTs
38+ - GeneralizedNewtypeDeriving
39+ - ImportQualifiedPost
40+ - LambdaCase
41+ - MonoLocalBinds
42+ - MultiParamTypeClasses
43+ - NamedFieldPuns
44+ - OverloadedLabels
45+ - OverloadedStrings
46+ - PartialTypeSignatures
47+ - PolyKinds
48+ - RankNTypes
49+ - RecordWildCards
50+ - RecursiveDo
51+ - ScopedTypeVariables
52+ - StandaloneDeriving
53+ - StandaloneKindSignatures
54+ - TupleSections
55+ - TypeApplications
56+ - TypeFamilies
57+ - TypeOperators
58+ - UndecidableInstances
59+ - ViewPatterns
src/Elm.hsnew file+60
@@ -0,0 +1,60 @@
1+module Elm (makeElmModuleFromETypeDef) where
2+
3+import Data.List
4+import Elm.Json
5+import Elm.Module hiding (makeElmModuleWithVersion, makeModuleContent, makeModuleContentWithAlterations)
6+import Elm.TyRender
7+import Elm.TyRep
8+import Elm.Versions
9+import Prelude
10+
11+-- | Creates an Elm module for the given version. This will use the default
12+-- type conversion rules (to -- convert @Vector@ to @List@, @HashMap a b@
13+-- to @List (a,b)@, etc.).
14+makeElmModuleWithVersion ::
15+ ElmVersion ->
16+ -- | Module name
17+ String ->
18+ -- | List of definitions to be included in the module
19+ [ETypeDef] ->
20+ String
21+makeElmModuleWithVersion elmVersion moduleName defs =
22+ unlines
23+ [ moduleHeader elmVersion moduleName,
24+ "",
25+ "import Json.Decode",
26+ "import Json.Encode exposing (Value)",
27+ "-- The following module comes from bartavelle/json-helpers",
28+ "import Json.Helpers exposing (..)",
29+ "import Dict exposing (Dict)",
30+ "import Set exposing (Set)",
31+ "",
32+ ""
33+ ]
34+ ++ makeModuleContent defs
35+
36+-- | Creates an Elm module. This will use the default type conversion rules (to
37+-- convert @Vector@ to @List@, @HashMap a b@ to @List (a,b)@, etc.).
38+--
39+-- default to 0.19
40+makeElmModuleFromETypeDef ::
41+ -- | Module name
42+ String ->
43+ -- | List of definitions to be included in the module
44+ [ETypeDef] ->
45+ String
46+makeElmModuleFromETypeDef = makeElmModuleWithVersion Elm0p19
47+
48+-- | Generates the content of a module. You will be responsible for
49+-- including the required Elm headers. This uses the default type
50+-- conversion rules.
51+makeModuleContent :: [ETypeDef] -> String
52+makeModuleContent = makeModuleContentWithAlterations defaultAlterations
53+
54+-- | Generates the content of a module, using custom type conversion rules.
55+makeModuleContentWithAlterations :: (ETypeDef -> ETypeDef) -> [ETypeDef] -> String
56+makeModuleContentWithAlterations alt = intercalate "\n\n" . map mkDef
57+ where
58+ mkDef typeDef =
59+ let def = alt typeDef
60+ in renderElm def ++ "\n" ++ jsonParserForDef def ++ "\n" ++ jsonSerForDef def ++ "\n"
src/Main.hsnew file+188
@@ -0,0 +1,188 @@
1+module Main (main) where
2+
3+import Control.Monad.Writer.Strict
4+import Data.Aeson hiding (Options)
5+import Data.Aeson.Key qualified as Key
6+import Data.Aeson.KeyMap qualified as KeyMap
7+import Data.Aeson.Types (Parser)
8+import Data.Map.Lazy qualified as M
9+import Data.Vector (Vector)
10+import Data.Vector qualified as V
11+import Data.Yaml as Yaml
12+import Elm (makeElmModuleFromETypeDef)
13+import Elm.TyRep
14+import Language.PureScript.Bridge hiding (SumType)
15+import Language.PureScript.Bridge qualified as PS
16+import Language.PureScript.Bridge.CodeGenSwitches qualified as PSS
17+import Language.PureScript.Bridge.PSTypes
18+import Options.Generic
19+import Relude
20+
21+main :: IO ()
22+main = do
23+ Options {input, elmOutput, psOutput} <- getRecord "elm-ps-bridge"
24+ bridge :: Map Text Datatype <- Yaml.decodeFileThrow input
25+ writeFile elmOutput $ elm bridge
26+ writeFileText psOutput $ ps bridge
27+
28+data Datatype
29+ = SumDatatype SumType
30+ | ProductType (Map Text Datatype)
31+ | BoolType
32+ | IntType
33+ | FloatType
34+ | StringType
35+ deriving stock (Show)
36+
37+newtype SumType = SumType (Map Text (Maybe Datatype))
38+ deriving stock (Show)
39+
40+parseSumType :: Vector Value -> Parser SumType
41+parseSumType = fmap (SumType . fold) . traverse parseConstructor . V.toList
42+ where
43+ parseConstructor (String s) = pure $ one (s, Nothing)
44+ parseConstructor (Object o) | [(Key.toText -> k, v)] <- KeyMap.toList o = one . (k,) . Just <$> parseJSON v
45+ parseConstructor (Object _) = fail "Sum types can only have one name per constructor"
46+ parseConstructor _ = fail "Sum Bad sum type ;("
47+
48+instance FromJSON Datatype where
49+ parseJSON (Array a) = SumDatatype <$> parseSumType a
50+ parseJSON (Object o) = ProductType <$> traverse parseJSON (M.mapKeysMonotonic Key.toText $ KeyMap.toMap o)
51+ parseJSON (String "Bool") = pure BoolType
52+ parseJSON (String "Int") = pure IntType
53+ parseJSON (String "Float") = pure FloatType
54+ parseJSON (String "String") = pure StringType
55+ parseJSON _ = fail "Bad datatype ;("
56+
57+data Options = Options
58+ { input :: FilePath,
59+ elmOutput :: FilePath,
60+ psOutput :: FilePath
61+ }
62+ deriving stock (Generic, Show)
63+ deriving anyclass (ParseRecord)
64+
65+elm :: Map Text Datatype -> String
66+elm datattypes =
67+ let (_, types) = runWriter $ traverse (uncurry elmType) $ M.toList datattypes
68+ in makeElmModuleFromETypeDef "Bridge" types
69+
70+elmType :: Text -> Datatype -> Writer [ETypeDef] EType
71+elmType name BoolType = pure $ ETyCon $ ETCon "Bool"
72+elmType name IntType = pure $ ETyCon $ ETCon "Int"
73+elmType name FloatType = pure $ ETyCon $ ETCon "Float"
74+elmType name StringType = pure $ ETyCon $ ETCon "String"
75+elmType name (ProductType fields) = do
76+ fields' <- traverse (\(k, v) -> (toString k,) <$> elmType (name <> "_" <> k) v) $ M.toList fields
77+ tell
78+ [ ETypeAlias
79+ EAlias
80+ { ea_name =
81+ ETypeName
82+ { et_name = toString name,
83+ et_args = []
84+ },
85+ ea_fields = fields',
86+ ea_omit_null = False,
87+ ea_newtype = False,
88+ ea_unwrap_unary = False
89+ }
90+ ]
91+ pure $ ETyCon $ ETCon $ toString name
92+elmType name (SumDatatype (SumType sums)) = do
93+ sums' <- traverse (\(k, v) -> (toString k,) <$> traverse (elmType (name <> "_" <> k)) v) $ M.toList sums
94+ tell
95+ [ ETypeSum
96+ ESum
97+ { es_name =
98+ ETypeName
99+ { et_name = toString name,
100+ et_args = []
101+ },
102+ es_constructors =
103+ sums' <&> \(name, value) ->
104+ STC
105+ { _stcName = name,
106+ _stcEncoded = name,
107+ _stcFields = Anonymous $ maybeToList value
108+ },
109+ es_omit_null = False,
110+ es_unary_strings = True,
111+ es_type = defSumEncoding
112+ }
113+ ]
114+ pure $ ETyCon $ ETCon $ toString name
115+
116+psType :: Text -> Datatype -> Writer [PS.SumType PureScript] (PS.TypeInfo PureScript)
117+psType _ BoolType = pure psBool
118+psType _ IntType = pure psInt
119+psType _ FloatType = pure psNumber
120+psType _ StringType = pure psString
121+psType name (SumDatatype (SumType sums)) = do
122+ sums' <- traverse (\(k, v) -> (k,) <$> traverse (psType (name <> "_" <> k)) v) $ M.toList sums
123+ let ti =
124+ TypeInfo
125+ { _typePackage = "bridge",
126+ _typeModule = "Bridge",
127+ _typeName = name,
128+ _typeParameters = []
129+ }
130+ tell
131+ [ PS.SumType
132+ ti
133+ ( sums' <&> \(name, value) ->
134+ DataConstructor
135+ { _sigConstructor = name,
136+ _sigValues = Left $ maybeToList value
137+ }
138+ )
139+ allInstances
140+ ]
141+ pure ti
142+psType name (ProductType fields) = do
143+ fields' <- traverse (\(k, v) -> (k,) <$> psType (name <> "_" <> k) v) $ M.toList fields
144+ let ti =
145+ TypeInfo
146+ { _typePackage = "bridge",
147+ _typeModule = "Bridge",
148+ _typeName = name,
149+ _typeParameters = []
150+ }
151+ tell
152+ [ PS.SumType
153+ ti
154+ [ DataConstructor
155+ { _sigConstructor = name,
156+ _sigValues =
157+ Right $
158+ fields' <&> \(name, value) ->
159+ RecordEntry
160+ { _recLabel = name,
161+ _recValue = value
162+ }
163+ }
164+ ]
165+ allInstances
166+ ]
167+ pure ti
168+
169+allInstances :: [PS.Instance]
170+allInstances =
171+ [ PS.Encode,
172+ PS.Decode,
173+ PS.Generic,
174+ PS.Newtype,
175+ PS.Eq,
176+ PS.Ord
177+ ]
178+
179+ps :: Map Text Datatype -> Text
180+ps datattypes =
181+ let (_, types) = runWriter $ traverse (uncurry psType) $ M.toList datattypes
182+ in moduleToText
183+ PSS.defaultSettings
184+ PSModule
185+ { psModuleName = "Bridge",
186+ psImportLines = mempty,
187+ psTypes = types
188+ }
stack.yamlnew file+4
@@ -0,0 +1,4 @@
1+resolver: lts-19.16
2+
3+packages:
4+- .