packages feed

elm-bridge 0.1.0.0 → 0.2.1.0

raw patch · 16 files changed

+1108/−210 lines, 16 filesdep +QuickCheckdep +aesondep +containersdep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: QuickCheck, aeson, containers, text

Dependency ranges changed: base

API changes (from Hackage documentation)

- Elm.Derive: DeriveOpts :: (String -> String) -> (String -> String) -> DeriveOpts
- Elm.Derive: data DeriveOpts
- Elm.Derive: defaultOpts :: DeriveOpts
- Elm.Derive: do_constrModifier :: DeriveOpts -> String -> String
- Elm.Derive: do_fieldModifier :: DeriveOpts -> String -> String
- Elm.TyRender: instance ElmRenderable EAlias
- Elm.TyRender: instance ElmRenderable EPrimAlias
- Elm.TyRender: instance ElmRenderable ESum
- Elm.TyRender: instance ElmRenderable ETCon
- Elm.TyRender: instance ElmRenderable ETVar
- Elm.TyRender: instance ElmRenderable EType
- Elm.TyRender: instance ElmRenderable ETypeDef
- Elm.TyRender: instance ElmRenderable ETypeName
- Elm.TyRep: ea_fields :: EAlias -> [(String, EType)]
- Elm.TyRep: ea_name :: EAlias -> ETypeName
- Elm.TyRep: epa_name :: EPrimAlias -> ETypeName
- Elm.TyRep: epa_type :: EPrimAlias -> EType
- Elm.TyRep: es_name :: ESum -> ETypeName
- Elm.TyRep: es_options :: ESum -> [(String, [EType])]
- Elm.TyRep: et_args :: ETypeName -> [ETVar]
- Elm.TyRep: et_name :: ETypeName -> String
- Elm.TyRep: instance Eq EAlias
- Elm.TyRep: instance Eq EPrimAlias
- Elm.TyRep: instance Eq ESum
- Elm.TyRep: instance Eq ETCon
- Elm.TyRep: instance Eq ETVar
- Elm.TyRep: instance Eq EType
- Elm.TyRep: instance Eq ETypeDef
- Elm.TyRep: instance Eq ETypeName
- Elm.TyRep: instance Ord EAlias
- Elm.TyRep: instance Ord EPrimAlias
- Elm.TyRep: instance Ord ESum
- Elm.TyRep: instance Ord ETCon
- Elm.TyRep: instance Ord ETVar
- Elm.TyRep: instance Ord EType
- Elm.TyRep: instance Ord ETypeName
- Elm.TyRep: instance Show EAlias
- Elm.TyRep: instance Show EPrimAlias
- Elm.TyRep: instance Show ESum
- Elm.TyRep: instance Show ETCon
- Elm.TyRep: instance Show ETVar
- Elm.TyRep: instance Show EType
- Elm.TyRep: instance Show ETypeDef
- Elm.TyRep: instance Show ETypeName
- Elm.TyRep: tc_name :: ETCon -> String
- Elm.TyRep: tv_name :: ETVar -> String
+ Elm.Derive: ObjectWithSingleField :: SumEncoding
+ Elm.Derive: Options :: (String -> String) -> (String -> String) -> Bool -> Bool -> SumEncoding -> Options
+ Elm.Derive: TaggedObject :: String -> String -> SumEncoding
+ Elm.Derive: TwoElemArray :: SumEncoding
+ Elm.Derive: [allNullaryToStringTag] :: Options -> Bool
+ Elm.Derive: [constructorTagModifier] :: Options -> String -> String
+ Elm.Derive: [contentsFieldName] :: SumEncoding -> String
+ Elm.Derive: [fieldLabelModifier] :: Options -> String -> String
+ Elm.Derive: [omitNothingFields] :: Options -> Bool
+ Elm.Derive: [sumEncoding] :: Options -> SumEncoding
+ Elm.Derive: [tagFieldName] :: SumEncoding -> String
+ Elm.Derive: data Options :: *
+ Elm.Derive: data SumEncoding :: *
+ Elm.Derive: defaultOptions :: Options
+ Elm.Derive: defaultOptionsDropLower :: Int -> Options
+ Elm.Derive: deriveBoth :: Options -> Name -> Q [Dec]
+ Elm.Json: instance GHC.Classes.Eq Elm.Json.MaybeHandling
+ Elm.Module: defaultAlterations :: ETypeDef -> ETypeDef
+ Elm.Module: makeModuleContent :: [DefineElm] -> String
+ Elm.Module: makeModuleContentWithAlterations :: (ETypeDef -> ETypeDef) -> [DefineElm] -> String
+ Elm.Module: newtypeAliases :: [String] -> ETypeDef -> ETypeDef
+ Elm.Module: recAlterType :: (EType -> EType) -> ETypeDef -> ETypeDef
+ Elm.TyRender: instance Elm.TyRender.ElmRenderable Elm.TyRep.EAlias
+ Elm.TyRender: instance Elm.TyRender.ElmRenderable Elm.TyRep.EPrimAlias
+ Elm.TyRender: instance Elm.TyRender.ElmRenderable Elm.TyRep.ESum
+ Elm.TyRender: instance Elm.TyRender.ElmRenderable Elm.TyRep.ETCon
+ Elm.TyRender: instance Elm.TyRender.ElmRenderable Elm.TyRep.ETVar
+ Elm.TyRender: instance Elm.TyRender.ElmRenderable Elm.TyRep.EType
+ Elm.TyRender: instance Elm.TyRender.ElmRenderable Elm.TyRep.ETypeDef
+ Elm.TyRender: instance Elm.TyRender.ElmRenderable Elm.TyRep.ETypeName
+ Elm.TyRep: SumEncoding' :: SumEncoding -> SumEncoding'
+ Elm.TyRep: [ea_fields] :: EAlias -> [(String, EType)]
+ Elm.TyRep: [ea_name] :: EAlias -> ETypeName
+ Elm.TyRep: [ea_newtype] :: EAlias -> Bool
+ Elm.TyRep: [ea_omit_null] :: EAlias -> Bool
+ Elm.TyRep: [epa_name] :: EPrimAlias -> ETypeName
+ Elm.TyRep: [epa_type] :: EPrimAlias -> EType
+ Elm.TyRep: [es_name] :: ESum -> ETypeName
+ Elm.TyRep: [es_omit_null] :: ESum -> Bool
+ Elm.TyRep: [es_options] :: ESum -> [(String, Either [(String, EType)] [EType])]
+ Elm.TyRep: [es_type] :: ESum -> SumEncoding'
+ Elm.TyRep: [es_unary_strings] :: ESum -> Bool
+ Elm.TyRep: [et_args] :: ETypeName -> [ETVar]
+ Elm.TyRep: [et_name] :: ETypeName -> String
+ Elm.TyRep: [tc_name] :: ETCon -> String
+ Elm.TyRep: [tv_name] :: ETVar -> String
+ Elm.TyRep: defSumEncoding :: SumEncoding'
+ Elm.TyRep: instance GHC.Classes.Eq Elm.TyRep.EAlias
+ Elm.TyRep: instance GHC.Classes.Eq Elm.TyRep.EPrimAlias
+ Elm.TyRep: instance GHC.Classes.Eq Elm.TyRep.ESum
+ Elm.TyRep: instance GHC.Classes.Eq Elm.TyRep.ETCon
+ Elm.TyRep: instance GHC.Classes.Eq Elm.TyRep.ETVar
+ Elm.TyRep: instance GHC.Classes.Eq Elm.TyRep.EType
+ Elm.TyRep: instance GHC.Classes.Eq Elm.TyRep.ETypeDef
+ Elm.TyRep: instance GHC.Classes.Eq Elm.TyRep.ETypeName
+ Elm.TyRep: instance GHC.Classes.Eq Elm.TyRep.SumEncoding'
+ Elm.TyRep: instance GHC.Classes.Ord Elm.TyRep.EAlias
+ Elm.TyRep: instance GHC.Classes.Ord Elm.TyRep.EPrimAlias
+ Elm.TyRep: instance GHC.Classes.Ord Elm.TyRep.ESum
+ Elm.TyRep: instance GHC.Classes.Ord Elm.TyRep.ETCon
+ Elm.TyRep: instance GHC.Classes.Ord Elm.TyRep.ETVar
+ Elm.TyRep: instance GHC.Classes.Ord Elm.TyRep.EType
+ Elm.TyRep: instance GHC.Classes.Ord Elm.TyRep.ETypeName
+ Elm.TyRep: instance GHC.Classes.Ord Elm.TyRep.SumEncoding'
+ Elm.TyRep: instance GHC.Show.Show Elm.TyRep.EAlias
+ Elm.TyRep: instance GHC.Show.Show Elm.TyRep.EPrimAlias
+ Elm.TyRep: instance GHC.Show.Show Elm.TyRep.ESum
+ Elm.TyRep: instance GHC.Show.Show Elm.TyRep.ETCon
+ Elm.TyRep: instance GHC.Show.Show Elm.TyRep.ETVar
+ Elm.TyRep: instance GHC.Show.Show Elm.TyRep.EType
+ Elm.TyRep: instance GHC.Show.Show Elm.TyRep.ETypeDef
+ Elm.TyRep: instance GHC.Show.Show Elm.TyRep.ETypeName
+ Elm.TyRep: instance GHC.Show.Show Elm.TyRep.SumEncoding'
+ Elm.TyRep: newtype SumEncoding'
- Elm.Derive: deriveElmDef :: DeriveOpts -> Name -> Q [Dec]
+ Elm.Derive: deriveElmDef :: Options -> Name -> Q [Dec]
- Elm.TyRep: EAlias :: ETypeName -> [(String, EType)] -> EAlias
+ Elm.TyRep: EAlias :: ETypeName -> [(String, EType)] -> Bool -> Bool -> EAlias
- Elm.TyRep: ESum :: ETypeName -> [(String, [EType])] -> ESum
+ Elm.TyRep: ESum :: ETypeName -> [(String, Either [(String, EType)] [EType])] -> SumEncoding' -> Bool -> Bool -> ESum

Files

+ CHANGELOG.md view
@@ -0,0 +1,11 @@+# v0.2.1++## New features++ * The template Haskell derivation functions now take `aeson` `Option` type instead of a custom type.+ This change makes it easier to synchronize the Haskell and Elm code.+ * The generated Elm code can be personalized. Helpers functions assist in converting type names, and defining which type will be newtyped.++## Notes++ * The generated Elm code depends on the [bartavelle/json-helpers](http://package.elm-lang.org/packages/bartavelle/json-helpers/1.1.0/) package.
README.md view
@@ -11,6 +11,8 @@  Building the bridge from [Haskell](http://haskell.org) to [Elm](http://elm-lang.org) and back. Define types once, use on both sides and enjoy easy (de)serialisation. Cheers! +Note that the [bartavelle/json-helpers](http://package.elm-lang.org/packages/bartavelle/json-helpers/latest/) package, with version >= 1.1.0, is expected by the generated Elm modules.+ ## Usage  ```haskell@@ -26,7 +28,7 @@    , f_blablub :: Int    } deriving (Show, Eq) -deriveElmDef defaultOpts ''Foo+deriveBoth defaultOptions ''Foo  main :: IO () main =@@ -44,6 +46,7 @@ import Json.Decode import Json.Decode exposing ((:=)) import Json.Encode+import Json.Helpers exposing (..)   type alias Foo  =@@ -51,11 +54,13 @@    , f_blablub: Int    } -jsonDecFoo  =+jsonDecFoo : Json.Decode.Decoder ( Foo )+jsonDecFoo =    ("f_name" := Json.Decode.string) `Json.Decode.andThen` \pf_name ->    ("f_blablub" := Json.Decode.int) `Json.Decode.andThen` \pf_blablub ->    Json.Decode.succeed {f_name = pf_name, f_blablub = pf_blablub} +jsonEncFoo : Foo -> Value jsonEncFoo  val =    Json.Encode.object    [ ("f_name", Json.Encode.string val.f_name)
elm-bridge.cabal view
@@ -1,12 +1,12 @@ name:                elm-bridge-version:             0.1.0.0+version:             0.2.1.0 synopsis:            Derive Elm types from Haskell types description:         Building the bridge from Haskell to Elm and back. Define types once,                      use on both sides and enjoy easy (de)serialisation. Cheers!-homepage:            http://github.com/agrafix/derive-elm+homepage:            http://github.com/agrafix/elm-bridge license:             BSD3 license-file:        LICENSE-author:              Alexander Thiemann <mail@athiemann.net>+author:              Alexander Thiemann <mail@athiemann.net>, Simon Marechal <bartavelle@gmail.com> maintainer:          Alexander Thiemann <mail@athiemann.net> copyright:           (c) 2015 Alexander Thiemann category:            Web, Compiler, Language@@ -15,20 +15,39 @@  extra-source-files:     README.md+    CHANGELOG.md     examples/*.hs + library   hs-source-dirs:      src+  ghc-options:         -Wall   exposed-modules:                        Elm.Derive                        Elm.Json                        Elm.Module                        Elm.TyRender                        Elm.TyRep+  other-modules:       Elm.Utils   build-depends:       base >= 4.7 && < 5,-                       template-haskell+                       template-haskell,+-- There is a known bug in aeson 0.10.0.0 that is triggered by the derivation+-- function in some cases: https://github.com/bos/aeson/issues/293+                       aeson  == 0.9.*   default-language:    Haskell2010 +test-suite end-to-end-tests+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             EndToEnd.hs+  build-depends:       base,+                       elm-bridge,+                       aeson,+                       containers,+                       QuickCheck,+                       text+  default-language:    Haskell2010+ test-suite derive-elm-tests   type:                exitcode-stdio-1.0   hs-source-dirs:      test@@ -37,14 +56,15 @@                        Elm.DeriveSpec                        Elm.TyRenderSpec                        Elm.JsonSpec-                       Elm.TestHelpers                        Elm.ModuleSpec   build-depends:                        base,                        hspec >= 2.0,-                       elm-bridge+                       elm-bridge,+                       aeson,+                       containers   default-language:    Haskell2010  source-repository head   type:     git-  location: https://github.com/agrafix/derive-elm+  location: https://github.com/agrafix/elm-bridge
examples/Example1.hs view
@@ -11,7 +11,7 @@    , f_blablub :: Int    } deriving (Show, Eq) -deriveElmDef defaultOpts ''Foo+deriveBoth defaultOptions ''Foo  main :: IO () main =
src/Elm/Derive.hs view
@@ -1,62 +1,91 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TupleSections #-}+{-| This module should be used to derive the Elm instance alongside the+ JSON ones. The prefered usage is to convert statements such as :++> $(deriveJSON defaultOptions{fieldLabelModifier = drop 4, constructorTagModifier = map toLower} ''D)++ into:++> $(deriveBoth defaultOptions{fieldLabelModifier = drop 4, constructorTagModifier = map toLower} ''D)++ Which will derive both the @aeson@ and @elm-bridge@ instances at the same+ time.+-}+ module Elm.Derive-    ( deriveElmDef, defaultOpts, DeriveOpts(..) )+    ( -- * Options+      A.Options(..)+    , A.SumEncoding(..)+    , defaultOptions+    , defaultOptionsDropLower+      -- * Template haskell functions+    , deriveElmDef+    , deriveBoth+    ) where  import Elm.TyRep  import Control.Monad+import Data.Aeson.TH (deriveJSON, SumEncoding(..))+import qualified Data.Aeson.TH as A import Language.Haskell.TH import Language.Haskell.TH.Syntax+import Data.Char (toLower)+import Control.Applicative+import Prelude -data DeriveOpts-   = DeriveOpts-   { do_fieldModifier :: String -> String-   , do_constrModifier :: String -> String-   }+-- | Note that This default set of options is distinct from that in+-- the @aeson@ package.+defaultOptions :: A.Options+defaultOptions = A.Options { A.sumEncoding             = A.ObjectWithSingleField+                           , A.fieldLabelModifier      = id+                           , A.constructorTagModifier  = id+                           , A.allNullaryToStringTag   = True+                           , A.omitNothingFields       = False+                           } -defaultOpts :: DeriveOpts-defaultOpts =-    DeriveOpts-    { do_fieldModifier = id-    , do_constrModifier = id-    }+{-| This generates a default set of options. The parameter represents the+number of characters that must be dropped from the Haskell field names.+The first letter of the field is then converted to lowercase, ie: -isConcreteType :: Type -> Bool-isConcreteType ty =-    case ty of-      AppT l r ->-          isConcreteType l-      ListT -> True+> data Foo = Foo { _fooBarQux :: Int }+> $(deriveBoth (defaultOptionsDropLower 4) ''Foo) -conCompiler :: String -> String-conCompiler s =-    case s of-      "Double" -> "Float"-      "Text" -> "String"-      "Vector" -> "List"-      _ -> s+Will be encoded as: +> {"barQux"=12}+-}+defaultOptionsDropLower :: Int -> A.Options+defaultOptionsDropLower n = defaultOptions { A.fieldLabelModifier = lower . drop n }+    where+        lower "" = ""+        lower (x:xs) = toLower x : xs+ compileType :: Type -> Q Exp compileType ty =     case ty of       ListT -> [|ETyCon (ETCon "List")|]       TupleT i -> [|ETyTuple i|]-      ConT name ->-          let n = conCompiler $ nameBase name-          in [|ETyCon (ETCon n)|]       VarT name ->           let n = nameBase name           in [|ETyVar (ETVar n)|]-      SigT ty _ ->-          compileType ty-      AppT a b ->-          let a1 = compileType a-              b1 = compileType b-          in [|ETyApp $a1 $b1|]+      SigT ty' _ ->+          compileType ty'+      AppT a b -> [|ETyApp $(compileType a) $(compileType b)|]+      ConT name ->+          let n = nameBase name+          in  [|ETyCon (ETCon n)|]       _ -> fail $ "Unsupported type: " ++ show ty +optSumType :: SumEncoding -> Q Exp+optSumType se =+    case se of+        TwoElemArray -> [|SumEncoding' TwoElemArray|]+        ObjectWithSingleField -> [|SumEncoding' ObjectWithSingleField|]+        TaggedObject tn cn -> [|SumEncoding' (TaggedObject tn cn)|]  runDerive :: Name -> [TyVarBndr] -> (Q Exp -> Q Exp) -> Q [Dec] runDerive name vars mkBody =@@ -89,58 +118,71 @@                 PlainTV tv -> tv                 KindedTV tv _ -> tv -deriveAlias :: DeriveOpts -> Name -> [TyVarBndr] -> Con -> Q [Dec]-deriveAlias opts name vars c =-    case c of-      RecC _ conFields ->-          let fields = listE $ map mkField conFields-          in runDerive name vars $ \typeName ->-                [|ETypeAlias (EAlias $typeName $fields)|]-      _ ->-          fail "Can only derive records like C { v :: Int, w :: a }"+deriveAlias :: A.Options -> Name -> [TyVarBndr] -> [VarStrictType] -> Q [Dec]+deriveAlias opts name vars conFields =+        runDerive name vars $ \typeName ->+                [|ETypeAlias (EAlias $typeName $fields omitNothing False)|] -- default to no newtype     where+      fields = listE $ map mkField conFields+      omitNothing = A.omitNothingFields opts       mkField :: VarStrictType -> Q Exp       mkField (fname, _, ftype) =           [|(fldName, $fldType)|]           where-            fldName = do_fieldModifier opts $ nameBase fname+            fldName = A.fieldLabelModifier opts $ nameBase fname             fldType = compileType ftype -deriveSum :: DeriveOpts -> Name -> [TyVarBndr] -> [Con] -> Q [Dec]+deriveSum :: A.Options -> Name -> [TyVarBndr] -> [Con] -> Q [Dec] deriveSum opts name vars constrs =     runDerive name vars $ \typeName ->-        [|ETypeSum (ESum $typeName $sumOpts)|]+        [|ETypeSum (ESum $typeName $sumOpts $sumEncOpts omitNothing allNullary)|]     where-      sumOpts =-          listE $ map mkOpt constrs+      allNullary = A.allNullaryToStringTag opts+      sumEncOpts = optSumType (A.sumEncoding opts)+      omitNothing = A.omitNothingFields opts+      sumOpts = listE $ map mkOpt constrs       mkOpt :: Con -> Q Exp       mkOpt c =-          case c of-            NormalC name args ->-                let n = do_constrModifier opts $ nameBase name+        let modifyName = A.constructorTagModifier opts . nameBase+        in case c of+            NormalC name' args ->+                let n = modifyName name'                     tyArgs = listE $ map (\(_, ty) -> compileType ty) args-                in [|(n, $tyArgs)|]-            _ ->-                fail "Can only derive sum types with options like C Int a"+                in [|(n, Right $tyArgs)|]+            RecC name' args ->+                let n = modifyName name'+                    tyArgs = listE $ map (\(nm, _, ty) -> let nm' = A.fieldLabelModifier opts $ nameBase nm+                                                          in  [|(nm', $(compileType ty))|]) args+                in [|(n, Left $tyArgs)|]+            _ -> fail ("Can't derive this sum: " ++ show c) -deriveSynonym :: DeriveOpts -> Name -> [TyVarBndr] -> Type -> Q [Dec]-deriveSynonym opts name vars otherT =+deriveSynonym :: A.Options -> Name -> [TyVarBndr] -> Type -> Q [Dec]+deriveSynonym _ name vars otherT =     runDerive name vars $ \typeName ->         [|ETypePrimAlias (EPrimAlias $typeName $otherType)|]     where       otherType = compileType otherT -deriveElmDef :: DeriveOpts -> Name -> Q [Dec]+-- | Equivalent to running both 'deriveJSON' and 'deriveElmDef' with the+-- same options, so as to ensure the code on the Haskell and Elm size is+-- synchronized.+deriveBoth :: A.Options -> Name -> Q [Dec]+deriveBoth o n = (++) <$> deriveElmDef o n <*> deriveJSON o n++-- | Just derive the @elm-bridge@ definitions for generating the+-- serialization/deserialization code. It must be kept synchronized with+-- the Haskell code manually.+deriveElmDef :: A.Options -> Name -> Q [Dec] deriveElmDef opts name =     do TyConI tyCon <- reify name        case tyCon of          DataD _ _ tyVars constrs _ ->              case constrs of                [] -> fail "Can not derive empty data decls"-               [x] -> deriveAlias opts name tyVars x+               [RecC _ conFields] -> deriveAlias opts name tyVars conFields                _ -> deriveSum opts name tyVars constrs-         NewtypeD _ _ tyVars constr _ ->-             deriveAlias opts name tyVars constr+         NewtypeD _ _ tyVars (RecC _ conFields) _ ->+             deriveAlias opts name tyVars conFields          TySynD _ vars otherTy ->              deriveSynonym opts name vars otherTy-         _ -> fail "Oops, can only derive data and newtype"+         _ -> fail ("Oops, can only derive data and newtype, not this: " ++ show tyCon)
src/Elm/Json.hs view
@@ -1,6 +1,10 @@ {- | This module implements a generator for JSON serialisers and parsers of arbitrary elm types.-Please note: It's still very hacky and might not work for all possible elm types yet.++It is highly recommended to either only use the functions of "Elm.Module", or to use the functions in this module+after having modified the 'ETypeDef' arguments with functions such as 'defaultAlterations'.++The reason is that Elm types might have an equivalent on the Haskell side and should be converted (ie. 'Text' -> 'String', 'Vector' -> 'List'). -} module Elm.Json     ( jsonParserForDef@@ -9,13 +13,25 @@ where  import Data.List-import Data.Maybe+import Data.Either (isLeft)+import Data.Aeson.Types (SumEncoding(..))  import Elm.TyRep+import Elm.Utils --- | Compile a JSON parser for an Elm type+data MaybeHandling = Root | Leaf+                   deriving Eq+ jsonParserForType :: EType -> String-jsonParserForType ty =+jsonParserForType = jsonParserForType' Leaf++isOption :: EType -> Bool+isOption (ETyApp (ETyCon (ETCon "Maybe")) _) = True+isOption _ = False++-- | Compile a JSON parser for an Elm type+jsonParserForType' :: MaybeHandling -> EType -> String+jsonParserForType' mh ty =     case ty of       ETyVar (ETVar v) -> "localDecoder_" ++ v       ETyCon (ETCon "Int") -> "Json.Decode.int"@@ -24,7 +40,12 @@       ETyCon (ETCon "Bool") -> "Json.Decode.bool"       ETyCon (ETCon c) -> "jsonDec" ++ c       ETyApp (ETyCon (ETCon "List")) t' -> "Json.Decode.list (" ++ jsonParserForType t' ++ ")"-      ETyApp (ETyCon (ETCon "Maybe")) t' -> "Json.Decode.maybe (" ++ jsonParserForType t' ++ ")"+      ETyApp (ETyCon (ETCon "Maybe")) t' -> if mh == Root+                                                then jsonParserForType t'+                                                else "Json.Decode.maybe (" ++ jsonParserForType t' ++ ")"+      ETyApp (ETyCon (ETCon "Set")) t' -> "decodeSet (" ++ jsonParserForType t' ++ ")"+      ETyApp (ETyApp (ETyCon (ETCon "Dict")) (ETyCon (ETCon "String")) ) value -> "Json.Decode.dict (" ++ jsonParserForType value ++ ")"+      ETyApp (ETyApp (ETyCon (ETCon "Dict")) key) value -> "decodeMap (" ++ jsonParserForType key ++ ") (" ++ jsonParserForType value ++ ")"       _ ->           case unpackTupleType ty of             [] -> error $ "This should never happen. Failed to unpackTupleType: " ++ show ty@@ -32,11 +53,7 @@                 case unpackToplevelConstr x of                   (y : ys) ->                       jsonParserForType y ++ " "-                      ++ unwords (catMaybes $ map (\t' ->-                                                 case t' of-                                                   ETyVar _ -> Just $ "(" ++ jsonParserForType t' ++ ")"-                                                   _ -> Nothing-                                            ) ys)+                      ++ unwords (map (\t' -> "(" ++ jsonParserForType t' ++ ")" ) ys)                   _ -> error $ "Do suitable json parser found for " ++ show ty             xs ->                 let tupleLen = length xs@@ -44,37 +61,96 @@                 in "Json.Decode.tuple" ++ show tupleLen ++ " (" ++ commas ++ ") "                     ++ unwords (map (\t' -> "(" ++ jsonParserForType t' ++ ")") xs) +parseRecords :: Maybe ETypeName -> [(String, EType)] -> [String]+parseRecords newtyped fields = map mkField fields ++ ["   Json.Decode.succeed " ++ mkNewtype ("{" ++ intercalate ", " (map (\(fldName, _) -> fixReserved fldName ++ " = p" ++ fldName) fields) ++ "}")]+    where+        mkNewtype x = case newtyped of+                          Nothing -> x+                          Just nm -> "(" ++ et_name nm ++ " " ++ x ++ ")"+        mkField (fldName, fldType) =+           let (fldStart, fldEnd, mh) = if isOption fldType+                                            then ("(Json.Decode.maybe ", ")", Root)+                                            else ("", "", Leaf)+           in   "   " ++ fldStart ++ "(\"" ++ fldName ++ "\" := "+                      ++ jsonParserForType' mh fldType+                      ++ fldEnd+                      ++ ") `Json.Decode.andThen` \\p" ++ fldName ++ " ->"++-- | Checks that all the arguments to the ESum are unary values+allUnaries :: Bool -> [(String, Either [(String, EType)] [EType])] -> Maybe [String]+allUnaries False = const Nothing+allUnaries True  = mapM isUnary+    where+        isUnary (x, Right args) = if null args then Just x else Nothing+        isUnary _ = Nothing+ -- | Compile a JSON parser for an Elm type definition jsonParserForDef :: ETypeDef -> String jsonParserForDef etd =     case etd of-      ETypePrimAlias (EPrimAlias name ty) ->-          makeName name ++  " = " ++ jsonParserForType ty ++ "\n"-      ETypeAlias (EAlias name fields) ->-          makeName name ++ " = \n"-          ++ intercalate "\n" (map (\(fldName, fldType) -> "   (\"" ++ fldName ++ "\" := "-                                    ++ jsonParserForType fldType-                                    ++ ") `Json.Decode.andThen` \\p" ++ fldName ++ " -> ") fields)-          ++ "\n   Json.Decode.succeed {" ++ intercalate ", " (map (\(fldName, _) -> fldName ++ " = p" ++ fldName) fields) ++ "}\n"-      ETypeSum (ESum name opts) ->-          makeName name ++ " = \n"-          ++ "   Json.Decode.oneOf \n   [ "-          ++ intercalate "\n   , " (map mkOpt opts) ++ "\n"-          ++ "   ]\n"+      ETypePrimAlias (EPrimAlias name ty) -> unlines+          [ decoderType name+          , makeName name ++  " ="+          , jsonParserForType ty+          ]+      ETypeAlias (EAlias name fields _ newtyping) -> unlines+          ( decoderType name+          : (makeName name ++ " =")+          : parseRecords (if newtyping then Just name else Nothing) fields+          )+      ETypeSum (ESum name opts (SumEncoding' encodingType) _ unarystring) ->+            decoderType name ++ "\n" +++            makeName name ++ " =" +++                case allUnaries unarystring opts of+                    Just names -> " " ++ deriveUnaries names+                    Nothing    -> "\n" ++ encodingDictionnary ++ isObjectSet ++ "\n    in  " ++ declLine ++ "\n"           where-            mkOpt (name, args) =-                let argLen = length args-                in "(\"" ++ name ++ "\" := Json.tuple" ++ show argLen ++ " " ++ name ++ " "-                   ++ unwords (map (\t' -> "(" ++ jsonParserForType t' ++ ")") args)-                   ++ ")"+            tab n s = replicate n ' ' ++ s+            typename = et_name name+            declLine = case encodingType of+                           ObjectWithSingleField -> unwords [ "decodeSumObjectWithSingleField ", show typename, dictName]+                           TwoElemArray          -> unwords [ "decodeSumTwoElemArray ", show typename, dictName ]+                           TaggedObject tg el    -> unwords [ "decodeSumTaggedObject", show typename, show tg, show el, dictName, isObjectSetName ]+            dictName = "jsonDecDict" ++ typename+            isObjectSetName = "jsonDecObjectSet" ++ typename+            deriveUnaries strs = unlines+                [ "decodeSumUnaries " ++ show typename ++ " " ++ dictName+                , dictName ++ " = Dict.fromList [" ++ intercalate ", " (map (\s -> "(" ++ show s ++ ", " ++ cap s ++ ")") strs ) ++ "]"+                ]+            encodingDictionnary = tab 4 "let " ++ dictName ++ " = Dict.fromList\n" ++ tab 12 "[ " ++ intercalate ("\n" ++ replicate 12 ' ' ++ ", ") (map dictEntry opts) ++ "\n" ++ tab 12 "]"+            isObjectSet = case encodingType of+                              TaggedObject _ _ -> "\n" ++ tab 8 (isObjectSetName ++ " = " ++ "Set.fromList [" ++ intercalate ", " (map (show . fst) $ filter (isLeft . snd) opts) ++ "]")+                              _ -> ""+            dictEntry (oname, args) = "(" ++ show oname ++ ", " ++ mkDecoder oname args ++ ")"+            mkDecoder oname (Left args)  =  "Json.Decode.map "+                                         ++ cap oname+                                         ++ " ("+                                         ++ unwords (parseRecords Nothing args)+                                         ++ ")"+            mkDecoder oname (Right args) = unwords ( decodeFunction+                                                   : cap oname+                                                   : map (\t' -> "(" ++ jsonParserForType t' ++ ")") args+                                                   )+                where decodeFunction = case length args of+                                           0 -> "Json.Decode.succeed"+                                           1 -> "Json.Decode.map"+                                           n -> "Json.Decode.tuple" ++ show n     where-      makeName name =-           "jsonDec" ++ et_name name ++ " "-           ++ unwords (map (\tv -> "localDecoder_" ++ tv_name tv) $ et_args name)+      funcname name = "jsonDec" ++ et_name name+      prependTypes str = map (\tv -> str ++ tv_name tv) . et_args+      decoderType name = funcname name ++ " : " ++ intercalate " -> " (prependTypes "Json.Decode.Decoder " name ++ [decoderTypeEnd name])+      decoderTypeEnd name = unwords ("Json.Decode.Decoder" : "(" : et_name name : map tv_name (et_args name) ++ [")"])+      makeName name = unwords (funcname name : prependTypes "localDecoder_" name) --- | Compile a JSON serializer for an Elm type+{-| Compile a JSON serializer for an Elm type.++The 'omitNothingFields' option is currently not implemented!+-} jsonSerForType :: EType -> String-jsonSerForType ty =+jsonSerForType = jsonSerForType' False++jsonSerForType' :: Bool -> EType -> String+jsonSerForType' omitnull ty =     case ty of       ETyVar (ETVar v) -> "localEncoder_" ++ v       ETyCon (ETCon "Int") -> "Json.Encode.int"@@ -82,8 +158,12 @@       ETyCon (ETCon "String") -> "Json.Encode.string"       ETyCon (ETCon "Bool") -> "Json.Encode.bool"       ETyCon (ETCon c) -> "jsonEnc" ++ c-      ETyApp (ETyCon (ETCon "List")) t' -> "(Json.Encode.list << map " ++ jsonSerForType t' ++ ")"-      ETyApp (ETyCon (ETCon "Maybe")) t' -> "(\v -> case v of Just val -> " ++ jsonSerForType t' ++ " val Nothing -> Json.Encode.null)"+      ETyApp (ETyCon (ETCon "List")) t' -> "(Json.Encode.list << List.map " ++ jsonSerForType t' ++ ")"+      ETyApp (ETyCon (ETCon "Maybe")) t' -> if omitnull+                                                then jsonSerForType t'+                                                else "(maybeEncode (" ++ jsonSerForType t' ++ "))"+      ETyApp (ETyCon (ETCon "Set")) t' -> "(encodeSet " ++ jsonSerForType t' ++ ")"+      ETyApp (ETyApp (ETyCon (ETCon "Dict")) key) value -> "encodeMap (" ++ jsonSerForType key ++ ") (" ++ jsonSerForType value ++ ")"       _ ->           case unpackTupleType ty of             [] -> error $ "This should never happen. Failed to unpackTupleType: " ++ show ty@@ -91,43 +171,64 @@                 case unpackToplevelConstr x of                   (y : ys) ->                       "(" ++ jsonSerForType y ++ " "-                      ++ unwords (catMaybes $ map (\t' ->-                                                 case t' of-                                                   ETyVar _ -> Just $ "(" ++ jsonSerForType t' ++ ")"-                                                   _ -> Nothing-                                            ) ys) ++ ")"+                      ++ unwords (map (\t' -> "(" ++ jsonSerForType t' ++ ")") ys)+                      ++ ")"                   _ -> error $ "Do suitable json serialiser found for " ++ show ty             xs ->-                let tupleLen = length xs-                    tupleArgsV = zip xs [1..]+                let tupleArgsV = zip xs ([1..] :: [Int])                     tupleArgs =-                        unwords $ map (\(_, v) -> "v" ++ show v) tupleArgsV-                in "(\\" ++ tupleArgs ++ " -> [" ++  intercalate "," (map (\(t', idx) -> "(" ++ jsonSerForType t' ++ ") v" ++ show idx) tupleArgsV) ++ "]"+                        intercalate "," $ map (\(_, v) -> "v" ++ show v) tupleArgsV+                in "(\\(" ++ tupleArgs ++ ") -> Json.Encode.list [" ++  intercalate "," (map (\(t', idx) -> "(" ++ jsonSerForType t' ++ ") v" ++ show idx) tupleArgsV) ++ "])" + -- | Compile a JSON serializer for an Elm type definition+-- TODO: handle the omit null case jsonSerForDef :: ETypeDef -> String jsonSerForDef etd =     case etd of       ETypePrimAlias (EPrimAlias name ty) ->-          makeName name ++  " = " ++ jsonSerForType ty ++ " val\n"-      ETypeAlias (EAlias name fields) ->-          makeName name ++ " = \n   Json.Encode.object\n   ["-          ++ intercalate "\n   ," (map (\(fldName, fldType) -> " (\"" ++ fldName ++ "\", " ++ jsonSerForType fldType ++ " val." ++ fldName ++ ")") fields)+          makeName name False ++  " = " ++ jsonSerForType ty ++ " val\n"+      ETypeAlias (EAlias name fields _ newtyping) ->+          makeName name newtyping ++ " =\n   Json.Encode.object\n   ["+          ++ intercalate "\n   ," (map (\(fldName, fldType) -> " (\"" ++ fldName ++ "\", " ++ jsonSerForType fldType ++ " val." ++ fixReserved fldName ++ ")") fields)           ++ "\n   ]\n"-      ETypeSum (ESum name opts) ->-          makeName name ++ " = \n"-          ++ "   case val of\n   "-          ++ intercalate "\n   " (map mkOpt opts) ++ "\n"+      ETypeSum (ESum name opts (SumEncoding' se) _ unarystring) ->+        case allUnaries unarystring opts of+            Nothing -> defaultEncoding+            Just strs -> unaryEncoding strs           where-            mkOpt (name, args) =-                let namedArgs = zip args [1..]-                    argList = unwords $ map (\(_, i) -> "v" ++ show i ) namedArgs-                    mkArg :: (EType, Int) -> String-                    mkArg (arg, idx) =-                        jsonSerForType arg ++ " v" ++ show idx-                in "   " ++ name ++ " " ++ argList ++ " -> [" ++ intercalate ", " (map mkArg namedArgs) ++ "]"+              encodeFunction = case se of+                                   ObjectWithSingleField -> "encodeSumObjectWithSingleField"+                                   TwoElemArray -> "encodeSumTwoElementArray"+                                   TaggedObject k c -> unwords ["encodeSumTaggedObject", show k, show c]+              defaultEncoding = unlines (+                ( makeName name False ++ " =")+                : "    let keyval v = case v of"+                :  (map (replicate 12 ' ' ++) (map mkcase opts))+                ++ [ "    " ++ unwords ["in", encodeFunction, "keyval", "val"] ]+                )+              unaryEncoding names = unlines (+                [ makeName name False ++ " ="+                , "    case val of"+                ] ++ map (\n -> replicate 8 ' ' ++ cap n ++ " -> Json.Encode.string " ++ show n) names+                )+              mkcase :: (String, Either [(String, EType)] [EType]) -> String+              mkcase (oname, Right args) = replicate 8 ' ' ++ cap oname ++ " " ++ argList args ++ " -> (" ++ show oname ++ ", " ++ mkEncodeList args ++ ")"+              mkcase (oname, Left args) = replicate 8 ' ' ++ cap oname ++ " vs -> (" ++ show oname ++ ", " ++ mkEncodeObject args ++ ")"+              argList a = unwords $ map (\i -> "v" ++ show i ) [1 .. length a]+              numargs :: (a -> String) -> [a] -> String+              numargs f = intercalate ", " . zipWith (\n a -> f a ++ " v" ++ show n)  ([1..] :: [Int])+              mkEncodeObject args = "encodeObject [" ++ intercalate ", " (map (\(n,t) -> "(" ++ show n ++ ", " ++ jsonSerForType t ++ " vs." ++ fixReserved n ++ ")") args) ++ "]"+              mkEncodeList [arg] = "encodeValue (" ++ jsonSerForType arg ++ " v1)"+              mkEncodeList args =  "encodeValue (Json.Encode.list [" ++ numargs jsonSerForType args ++ "])"     where-      makeName name =-           "jsonEnc" ++ et_name name ++ " "+      fname name = "jsonEnc" ++ et_name name+      makeType name = fname name ++ " : " ++ intercalate " -> " (map (mkLocalEncoder . tv_name) (et_args name) ++ [unwords (et_name name : map tv_name (et_args name)) , "Value"])+      mkLocalEncoder n = "(" ++ n ++ " -> Value)"+      makeName name newtyping =+           makeType name ++ "\n"+           ++ fname name ++ " "            ++ unwords (map (\tv -> "localEncoder_" ++ tv_name tv) $ et_args name)-           ++ " val"+           ++ if newtyping+                  then " (" ++ et_name name ++ " val)"+                  else " val"
src/Elm/Module.hs view
@@ -1,9 +1,14 @@ {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE ScopedTypeVariables #-}+{-|+Functions in this module are used to generate Elm modules. Note that the generated modules depend on the @bartavelle/json-helpers@ package.++-} module Elm.Module where  import Data.Proxy import Data.List+import Control.Arrow (second, (+++))  import Elm.TyRep import Elm.TyRender@@ -13,16 +18,94 @@ data DefineElm    = forall a. IsElmDefinition a => DefineElm (Proxy a) --- | Compile an Elm module-makeElmModule :: String -> [DefineElm] -> String-makeElmModule moduleName defs =-    "module " ++ moduleName ++ " where \n\n"-    ++ "import Json.Decode\n"-    ++ "import Json.Decode exposing ((:=))\n"-    ++ "import Json.Encode\n"-    ++ "\n\n"-    ++ intercalate "\n\n" (map mkDef defs)+-- | Creates an Elm module. This will use the default type conversion rules (to+-- convert @Vector@ to @List@, @HashMap a b@ to @List (a,b)@, etc.).+makeElmModule :: String -- ^ Module name+              -> [DefineElm] -- ^ List of definitions to be included in the module+              -> String+makeElmModule moduleName defs = unlines (+    [ "module " ++ moduleName ++ " where"+    , ""+    , "import Json.Decode"+    , "import Json.Decode exposing ((:=))"+    , "import Json.Encode exposing (Value)"+    , "-- The following module comes from bartavelle/json-helpers"+    , "import Json.Helpers exposing (..)"+    , ""+    , ""+    ]) ++ makeModuleContent defs+++-- | Generates the content of a module. You will be responsible for+-- including the required Elm headers. This uses the default type+-- conversion rules.+makeModuleContent :: [DefineElm] -> String+makeModuleContent = makeModuleContentWithAlterations defaultAlterations++-- | Generates the content of a module, using custom type conversion rules.+makeModuleContentWithAlterations :: (ETypeDef -> ETypeDef) -> [DefineElm] -> String+makeModuleContentWithAlterations alt = intercalate "\n\n" . map mkDef     where       mkDef (DefineElm proxy) =-          let def = compileElmDef proxy+          let def = alt (compileElmDef proxy)           in renderElm def ++ "\n" ++ jsonParserForDef def ++ "\n" ++ jsonSerForDef def ++ "\n"++{-| A helper function that will recursively traverse type definitions and let you convert types.++> myAlteration : ETypeDef -> ETypeDef+> myAlteration = recAlterType $ \t -> case t of+>                   ETyCon (ETCon "Integer") -> ETyCon (ETCon "Int")+>                   ETyCon (ETCon "Text")    -> ETyCon (ETCon "String")+>                   _                        -> t++-}+recAlterType :: (EType -> EType) -> ETypeDef -> ETypeDef+recAlterType f td = case td of+                     ETypeAlias a -> ETypeAlias (a { ea_fields = map (second f') (ea_fields a) })+                     ETypePrimAlias (EPrimAlias n t) -> ETypePrimAlias (EPrimAlias n (f' t))+                     ETypeSum s -> ETypeSum (s { es_options = map (second (map (second f') +++ map f')) (es_options s) })+    where+        f' (ETyApp a b) = f (ETyApp (f' a) (f' b))+        f' x = f x++-- | Given a list of type names, will @newtype@ all the matching type+-- definitions.+newtypeAliases :: [String] -> ETypeDef -> ETypeDef+newtypeAliases nts (ETypeAlias e) = ETypeAlias $ if et_name (ea_name e) `elem` nts+                                                     then e { ea_newtype = True }+                                                     else e+newtypeAliases _ x = x++{-| A default set of type conversion rules:++ * @HashSet a@, @Set a@ -> if @a@ is comparable, then @Set a@, else @List a@+ * @HashMap String v@, @Map String v@ -> @Dict String v@+ * @HashMap k v@, @Map k v@ -> @List (k, v)@+ * @Integer@ -> @Int@+ * @Text@ -> @String@+ * @Vector@ -> @List@+ * @Double@ -> @Float@+-}+defaultAlterations :: ETypeDef -> ETypeDef+defaultAlterations = recAlterType $ \t -> case t of+                                  ETyApp (ETyCon (ETCon "HashSet")) s             -> checkSet s+                                  ETyApp (ETyCon (ETCon "Set")) s                 -> checkSet s+                                  ETyApp (ETyApp (ETyCon (ETCon "HashMap")) k) v  -> checkMap k v+                                  ETyApp (ETyApp (ETyCon (ETCon "THashMap")) k) v -> checkMap k v+                                  ETyApp (ETyApp (ETyCon (ETCon "Map")) k) v      -> checkMap k v+                                  ETyCon (ETCon "Integer")                        -> ETyCon (ETCon "Int")+                                  ETyCon (ETCon "Text")                           -> ETyCon (ETCon "String")+                                  ETyCon (ETCon "Vector")                         -> ETyCon (ETCon "List")+                                  ETyCon (ETCon "Double")                         -> ETyCon (ETCon "Float")+                                  _                                               -> t+    where+        isString (ETyCon (ETCon "String")) = True+        isString _ = False+        isComparable (ETyCon (ETCon n)) = n `elem` ["String", "Int"]+        isComparable _ = False -- TODO check what Elm actually uses+        tc = ETyCon . ETCon+        checkMap k v | isString k = ETyApp (ETyApp (tc "Dict") k) v+                     | otherwise  = ETyApp (tc "List") (ETyApp (ETyApp (ETyTuple 2) k) v)+        checkSet s | isComparable s = ETyApp (ETyCon (ETCon "Set")) s+                   | otherwise = ETyApp (ETyCon (ETCon "List")) s+
src/Elm/TyRender.hs view
@@ -1,6 +1,8 @@+{-| This module should not usually be imported. -} module Elm.TyRender where  import Elm.TyRep+import Elm.Utils  import Data.List @@ -20,12 +22,14 @@           [t] -> renderSingleTy t           xs -> "(" ++ intercalate ", " (map renderSingleTy xs) ++ ")"         where-          renderSingleTy ty =-              case ty of+          renderApp (ETyApp l r) = renderApp l ++ " " ++ renderElm r+          renderApp x = renderElm x+          renderSingleTy typ =+              case typ of                 ETyVar v -> renderElm v                 ETyCon c -> renderElm c-                ETyTuple n -> error "Library Bug: This should never happen!"-                ETyApp l r -> "(" ++ renderElm l ++ " " ++ renderElm r ++ ")"+                ETyTuple _ -> error "Library Bug: This should never happen!"+                ETyApp l r -> "(" ++ renderApp l ++ " " ++ renderElm r ++ ")"  instance ElmRenderable ETCon where     renderElm = tc_name@@ -38,19 +42,23 @@         et_name tyName ++ " " ++ unwords (map renderElm $ et_args tyName)  instance ElmRenderable EAlias where-    renderElm alias =-        "type alias " ++ renderElm (ea_name alias) ++ " = \n   { "-        ++ intercalate "\n   , " (map (\(fld, ty) -> fld ++ ": " ++ renderElm ty) (ea_fields alias))-        ++ "\n   }\n"+    renderElm alias = (if ea_newtype alias then withnewtype else nonewtype) ++ body+        where+            withnewtype = "type " ++ renderElm (ea_name alias) ++ " = " ++ et_name (ea_name alias)+            nonewtype = "type alias " ++ renderElm (ea_name alias) ++ " ="+            body = "\n   { "+                ++ intercalate "\n   , " (map (\(fld, ty) -> fixReserved fld ++ ": " ++ renderElm ty) (ea_fields alias))+                ++ "\n   }\n"  instance ElmRenderable ESum where     renderElm s =-        "type " ++ renderElm (es_name s) ++ " = \n    "+        "type " ++ renderElm (es_name s) ++ " =\n    "         ++ intercalate "\n    | " (map mkOpt (es_options s))         ++ "\n"         where-          mkOpt (name, types) =-              name ++ " " ++ unwords (map renderElm types)+          mkOpt (name, Left types) = cap name ++ " {" ++ intercalate ", " (map (\(fld, ty) -> fixReserved fld ++ ": " ++ renderElm ty) types) ++ "}"+          mkOpt (name, Right types) =+              cap name ++ " " ++ unwords (map renderElm types)  instance ElmRenderable EPrimAlias where     renderElm pa =
src/Elm/TyRep.hs view
@@ -1,17 +1,24 @@+{-| This module defines how the derived Haskell data types are represented.+- It is useful for writing type conversion rules.+-} module Elm.TyRep where  import Data.List import Data.Proxy -class IsElmDefinition a where-    compileElmDef :: Proxy a -> ETypeDef+import Data.Aeson.Types (SumEncoding(..))+import Data.Monoid ((<>))+import Data.Maybe (fromMaybe) +-- | Type definition, including constructors. data ETypeDef    = ETypeAlias EAlias    | ETypePrimAlias EPrimAlias    | ETypeSum ESum      deriving (Show, Eq) +-- | Type construction : type variables, type constructors, tuples and type+-- application. data EType    = ETyVar ETVar    | ETyCon ETCon@@ -19,16 +26,29 @@    | ETyTuple Int    deriving (Show, Eq, Ord) +{-| Type constructor:++> ETCon "Int"+-} data ETCon    = ETCon    { tc_name :: String    } deriving (Show, Eq, Ord) +{-| Type variable:++> ETVar "a"+-} data ETVar    = ETVar    { tv_name :: String    } deriving (Show, Eq, Ord) ++{-| Type name:++> ETypeName "Map" [ETVar "k", ETVar "v"]+-} data ETypeName    = ETypeName    { et_name :: String@@ -45,25 +65,29 @@    = EAlias    { ea_name :: ETypeName    , ea_fields :: [(String, EType)]+   , ea_omit_null :: Bool+   , ea_newtype   :: Bool    } deriving (Show, Eq, Ord)  data ESum    = ESum-   { es_name :: ETypeName-   , es_options :: [(String, [EType])]+   { es_name          :: ETypeName+   , es_options       :: [(String, Either [(String, EType)] [EType])]+   , es_type          :: SumEncoding'+   , es_omit_null     :: Bool+   , es_unary_strings :: Bool    } deriving (Show, Eq, Ord) +-- | Transforms tuple types in a list of types. Otherwise returns+-- a singleton list with the original type. unpackTupleType :: EType -> [EType]-unpackTupleType t =-    unfoldr (\ty ->-                 case ty of-                   Just (ETyApp (ETyApp (ETyTuple i) r) r') ->-                       Just (r, Just r')-                   Just t ->-                       Just (t, Nothing)-                   Nothing ->-                       Nothing-            ) (Just t)+unpackTupleType et = fromMaybe [et] (extract et)+    where+        extract :: EType -> Maybe [EType]+        extract ty = case ty of+                         ETyApp (ETyTuple _) t -> return [t]+                         ETyApp app@(ETyApp _ _) t -> fmap (++ [t]) (extract app)+                         _ -> Nothing  unpackToplevelConstr :: EType -> [EType] unpackToplevelConstr t =@@ -77,3 +101,36 @@                     Just (r, Just l)                 _ ->                     Just (t', Nothing)++class IsElmDefinition a where+    compileElmDef :: Proxy a -> ETypeDef++newtype SumEncoding' = SumEncoding' SumEncoding++instance Show SumEncoding' where+    show (SumEncoding' se) = case se of+                                 TaggedObject n f -> "TaggedObject " ++ show n ++ " " ++ show f+                                 ObjectWithSingleField -> "ObjectWithSingleField"+                                 TwoElemArray -> "TwoElemArray"++instance Eq SumEncoding' where+    SumEncoding' a == SumEncoding' b = case (a,b) of+                                           (TaggedObject a1 b1, TaggedObject a2 b2) -> a1 == a2 && b1 == b2+                                           (ObjectWithSingleField, ObjectWithSingleField) -> True+                                           (TwoElemArray, TwoElemArray) -> True+                                           _ -> False++instance Ord SumEncoding' where+    compare (SumEncoding' a) (SumEncoding' b) =+       case (a,b) of+          (TaggedObject a1 b1, TaggedObject a2 b2) -> compare a1 a2 <> compare b1 b2+          (TaggedObject _ _, _) -> LT+          (_, TaggedObject _ _) -> GT+          (ObjectWithSingleField, ObjectWithSingleField) -> EQ+          (ObjectWithSingleField, _) -> LT+          (_, ObjectWithSingleField) -> GT+          (TwoElemArray, TwoElemArray) -> EQ++defSumEncoding :: SumEncoding'+defSumEncoding = SumEncoding' ObjectWithSingleField+
+ src/Elm/Utils.hs view
@@ -0,0 +1,22 @@+module Elm.Utils where++import Data.Char (toUpper)++cap :: String -> String+cap "" = ""+cap (x:xs) = toUpper x : xs++fixReserved :: String -> String+fixReserved x | x `elem` reservedWords = x ++ "_"+              | otherwise = x+    where+        reservedWords = [ "if", "then", "else"+                        , "case", "of"+                        , "let", "in"+                        , "type"+                        , "module", "where"+                        , "import", "as", "hiding", "exposing"+                        , "port", "export", "foreign"+                        , "perform"+                        , "deriving"+                        ]
test/Elm/DeriveSpec.hs view
@@ -6,6 +6,7 @@  import Data.Proxy import Test.Hspec+import Data.Char (toLower)  data Foo    = Foo@@ -21,14 +22,45 @@    , b_list :: [Bool]    } deriving (Show, Eq) +data Change a = Change { _before :: a, _after :: a }++data Baz a = Baz1 { _fOo :: Int, _qux :: a }+           | Baz2 { _bar :: Int, _sTr :: String }+           | Zob a++data Qux a = Qux1 { _quxfoo :: Int, _quxqux :: a }+           | Qux2 { _quxbar :: Int, _quxstr :: String }++data Test a = Test { _t1 :: Change Int+                   , _t2 :: Change a+                   }+ data SomeOpts a    = Okay Int    | NotOkay a -deriveElmDef defaultOpts ''Foo-deriveElmDef defaultOpts ''Bar-deriveElmDef defaultOpts ''SomeOpts+deriveElmDef defaultOptions ''Foo+deriveElmDef defaultOptions ''Bar+deriveElmDef defaultOptions ''SomeOpts+deriveElmDef defaultOptions { fieldLabelModifier = drop 1 . map toLower } ''Baz+deriveElmDef defaultOptions { fieldLabelModifier = drop 1 . map toLower } ''Test+deriveElmDef defaultOptions { fieldLabelModifier = drop 4 . map toLower, sumEncoding = TaggedObject "key" "value" } ''Qux +testElm :: ETypeDef+testElm = ETypeAlias $ EAlias+    { ea_name =+        ETypeName+        { et_name = "Test"+        , et_args = [ETVar {tv_name = "a"}]+        }+    , ea_fields =+        [ ("t1",ETyApp (ETyCon (ETCon {tc_name = "Change"})) (ETyCon (ETCon {tc_name = "Int"})))+        , ("t2",ETyApp (ETyCon (ETCon {tc_name = "Change"})) (ETyVar (ETVar {tv_name = "a"})))+        ]+    , ea_omit_null = False+    , ea_newtype = False+    }+ fooElm :: ETypeDef fooElm =     ETypeAlias $@@ -40,6 +72,8 @@           }     , ea_fields =         [("f_name",ETyCon (ETCon {tc_name = "String"})),("f_blablub",ETyCon (ETCon {tc_name = "Int"}))]+    , ea_omit_null = False+    , ea_newtype = False     }  barElm :: ETypeDef@@ -57,8 +91,35 @@         , ("b_tuple",ETyApp (ETyApp (ETyTuple 2) (ETyCon (ETCon {tc_name = "Int"}))) (ETyCon (ETCon {tc_name = "String"})))         , ("b_list",ETyApp (ETyCon (ETCon {tc_name = "List"})) (ETyCon (ETCon {tc_name = "Bool"})))         ]+    , ea_omit_null = False+    , ea_newtype = False     } +bazElm :: ETypeDef+bazElm = ETypeSum $ ESum+    { es_name = ETypeName {et_name = "Baz", et_args = [ETVar {tv_name = "a"}]}+    , es_options =+        [ ("Baz1",Left [("foo",ETyCon (ETCon {tc_name = "Int"})), ("qux",ETyVar (ETVar {tv_name = "a"}))])+        , ("Baz2",Left [("bar",ETyCon (ETCon {tc_name = "Int"})), ("str",ETyCon (ETCon {tc_name = "String"}))])+        , ("Zob",Right [ETyVar (ETVar {tv_name = "a"})])+        ]+    , es_type = SumEncoding' ObjectWithSingleField+    , es_omit_null = False+    , es_unary_strings = True+    }++quxElm :: ETypeDef+quxElm = ETypeSum $ ESum+    { es_name = ETypeName {et_name = "Qux", et_args = [ETVar {tv_name = "a"}]}+    , es_options =+        [ ("Qux1",Left [("foo",ETyCon (ETCon {tc_name = "Int"})), ("qux",ETyVar (ETVar {tv_name = "a"}))])+        , ("Qux2",Left [("bar",ETyCon (ETCon {tc_name = "Int"})), ("str",ETyCon (ETCon {tc_name = "String"}))])+        ]+    , es_type = SumEncoding' $ TaggedObject "key" "value"+    , es_omit_null = False+    , es_unary_strings = True+    }+ someOptsElm :: ETypeDef someOptsElm =     ETypeSum $@@ -69,9 +130,12 @@           , et_args = [ETVar {tv_name = "a"}]           }     , es_options =-        [ ("Okay",[ETyCon (ETCon {tc_name = "Int"})])-        , ("NotOkay",[ETyVar (ETVar {tv_name = "a"})])+        [ ("Okay", Right [ETyCon (ETCon {tc_name = "Int"})])+        , ("NotOkay", Right [ETyVar (ETVar {tv_name = "a"})])         ]+    , es_type = defSumEncoding+    , es_omit_null = False+    , es_unary_strings = True     }  spec :: Spec@@ -81,3 +145,6 @@     do compileElmDef (Proxy :: Proxy Foo) `shouldBe` fooElm        compileElmDef (Proxy :: Proxy (Bar a)) `shouldBe` barElm        compileElmDef (Proxy :: Proxy (SomeOpts a)) `shouldBe` someOptsElm+       compileElmDef (Proxy :: Proxy (Baz a)) `shouldBe` bazElm+       compileElmDef (Proxy :: Proxy (Qux a)) `shouldBe` quxElm+       compileElmDef (Proxy :: Proxy (Test a)) `shouldBe` testElm
test/Elm/JsonSpec.hs view
@@ -5,10 +5,11 @@ import Elm.TyRep import Elm.Json -import Elm.TestHelpers- import Data.Proxy import Test.Hspec+import Data.Char (toLower)+import Data.Aeson.Types (SumEncoding(..))+import qualified Data.Map.Strict as M  data Foo    = Foo@@ -28,39 +29,189 @@    = Okay Int    | NotOkay a -$(deriveElmDef (fieldDropOpts 2) ''Foo)-$(deriveElmDef (fieldDropOpts 2) ''Bar)-$(deriveElmDef defaultOpts ''SomeOpts)+data UnaryA = UnaryA1 | UnaryA2+data UnaryB = UnaryB1 | UnaryB2 +data Change a = Change { _before :: a, _after :: a }++data Baz a = Baz1 { _foo :: Int, _qux :: M.Map Int a }+           | Baz2 { _bar :: Maybe Int, _str :: String }+           | Testing (Baz a)++data TestComp a = TestComp { _t1 :: Change Int+                           , _t2 :: Change a+                           }++-- TODO+data Qux a = Qux1 { _quxfoo :: Int, _quxqux :: a }+           | Qux2 Int (M.Map Int a)++$(deriveElmDef (defaultOptionsDropLower 2) ''Foo)+$(deriveElmDef (defaultOptionsDropLower 2) ''Bar)+$(deriveElmDef (defaultOptionsDropLower 1) ''TestComp)+$(deriveElmDef defaultOptions ''SomeOpts)+$(deriveElmDef defaultOptions{ allNullaryToStringTag = False } ''UnaryA)+$(deriveElmDef defaultOptions{ allNullaryToStringTag = True  } ''UnaryB)+$(deriveElmDef defaultOptions { fieldLabelModifier = drop 1 . map toLower } ''Baz)+$(deriveElmDef defaultOptions { fieldLabelModifier = drop 4 . map toLower, sumEncoding = TaggedObject "tag" "value" } ''Qux)+ fooSer :: String-fooSer = "jsonEncFoo  val = \n   Json.Encode.object\n   [ (\"name\", Json.Encode.string val.name)\n   , (\"blablub\", Json.Encode.int val.blablub)\n   ]\n"+fooSer = "jsonEncFoo : Foo -> Value\njsonEncFoo  val =\n   Json.Encode.object\n   [ (\"name\", Json.Encode.string val.name)\n   , (\"blablub\", Json.Encode.int val.blablub)\n   ]\n"  fooParse :: String-fooParse = "jsonDecFoo  = \n   (\"name\" := Json.Decode.string) `Json.Decode.andThen` \\pname -> \n   (\"blablub\" := Json.Decode.int) `Json.Decode.andThen` \\pblablub -> \n   Json.Decode.succeed {name = pname, blablub = pblablub}\n"+fooParse = unlines+    [ "jsonDecFoo : Json.Decode.Decoder ( Foo )"+    , "jsonDecFoo ="+    , "   (\"name\" := Json.Decode.string) `Json.Decode.andThen` \\pname ->"+    , "   (\"blablub\" := Json.Decode.int) `Json.Decode.andThen` \\pblablub ->"+    , "   Json.Decode.succeed {name = pname, blablub = pblablub}"+    ]  barSer :: String-barSer = "jsonEncBar localEncoder_a val = \n   Json.Encode.object\n   [ (\"name\", localEncoder_a val.name)\n   , (\"blablub\", Json.Encode.int val.blablub)\n   , (\"tuple\", (\\v1 v2 -> [(Json.Encode.int) v1,(Json.Encode.string) v2] val.tuple)\n   , (\"list\", (Json.Encode.list << map Json.Encode.bool) val.list)\n   ]\n"+barSer = unlines+    [ "jsonEncBar : (a -> Value) -> Bar a -> Value"+    , "jsonEncBar localEncoder_a val ="+    , "   Json.Encode.object"+    , "   [ (\"name\", localEncoder_a val.name)"+    , "   , (\"blablub\", Json.Encode.int val.blablub)"+    , "   , (\"tuple\", (\\(v1,v2) -> Json.Encode.list [(Json.Encode.int) v1,(Json.Encode.string) v2]) val.tuple)"+    , "   , (\"list\", (Json.Encode.list << List.map Json.Encode.bool) val.list)"+    , "   ]"+    ] +bazSer :: String+bazSer = unlines+    [ "jsonEncBaz : (a -> Value) -> Baz a -> Value"+    , "jsonEncBaz localEncoder_a val ="+    , "    let keyval v = case v of"+    , "                    Baz1 vs -> (\"Baz1\", encodeObject [(\"foo\", Json.Encode.int vs.foo), (\"qux\", (jsonEncMap (Json.Encode.int) (localEncoder_a)) vs.qux)])"+    , "                    Baz2 vs -> (\"Baz2\", encodeObject [(\"bar\", (maybeEncode (Json.Encode.int)) vs.bar), (\"str\", Json.Encode.string vs.str)])"+    , "                    Testing v1 -> (\"Testing\", encodeValue ((jsonEncBaz (localEncoder_a)) v1))"+    , "    in encodeSumObjectWithSingleField keyval val"+    ]+ barParse :: String-barParse = "jsonDecBar localDecoder_a = \n   (\"name\" := localDecoder_a) `Json.Decode.andThen` \\pname -> \n   (\"blablub\" := Json.Decode.int) `Json.Decode.andThen` \\pblablub -> \n   (\"tuple\" := Json.Decode.tuple2 (,) (Json.Decode.int) (Json.Decode.string)) `Json.Decode.andThen` \\ptuple -> \n   (\"list\" := Json.Decode.list (Json.Decode.bool)) `Json.Decode.andThen` \\plist -> \n   Json.Decode.succeed {name = pname, blablub = pblablub, tuple = ptuple, list = plist}\n"+barParse = unlines+    [ "jsonDecBar : Json.Decode.Decoder a -> Json.Decode.Decoder ( Bar a )"+    , "jsonDecBar localDecoder_a ="+    , "   (\"name\" := localDecoder_a) `Json.Decode.andThen` \\pname ->"+    , "   (\"blablub\" := Json.Decode.int) `Json.Decode.andThen` \\pblablub ->"+    , "   (\"tuple\" := Json.Decode.tuple2 (,) (Json.Decode.int) (Json.Decode.string)) `Json.Decode.andThen` \\ptuple ->"+    , "   (\"list\" := Json.Decode.list (Json.Decode.bool)) `Json.Decode.andThen` \\plist ->"+    , "   Json.Decode.succeed {name = pname, blablub = pblablub, tuple = ptuple, list = plist}"+    ] +bazParse :: String+bazParse = unlines+    [ "jsonDecBaz : Json.Decode.Decoder a -> Json.Decode.Decoder ( Baz a )"+    , "jsonDecBaz localDecoder_a ="+    , "    let jsonDecDictBaz = Dict.fromList"+    , "            [ (\"Baz1\", Json.Decode.map Baz1 (   (\"foo\" := Json.Decode.int) `Json.Decode.andThen` \\pfoo ->    (\"qux\" := jsonDecMap (Json.Decode.int) (localDecoder_a)) `Json.Decode.andThen` \\pqux ->    Json.Decode.succeed {foo = pfoo, qux = pqux}))"+    , "            , (\"Baz2\", Json.Decode.map Baz2 (   (Json.Decode.maybe (\"bar\" := Json.Decode.int)) `Json.Decode.andThen` \\pbar ->    (\"str\" := Json.Decode.string) `Json.Decode.andThen` \\pstr ->    Json.Decode.succeed {bar = pbar, str = pstr}))"+    , "            , (\"Testing\", Json.Decode.map Testing (jsonDecBaz (localDecoder_a)))"+    , "            ]"+    , "    in  decodeSumObjectWithSingleField  \"Baz\" jsonDecDictBaz"+    ]++quxParse :: String+quxParse = unlines+    [ "jsonDecQux localDecoder_a ="+    , "   "+    ]+ someOptsParse :: String-someOptsParse = "jsonDecSomeOpts localDecoder_a = \n   Json.Decode.oneOf \n   [ (\"Okay\" := Json.tuple1 Okay (Json.Decode.int))\n   , (\"NotOkay\" := Json.tuple1 NotOkay (localDecoder_a))\n   ]\n"+someOptsParse = unlines+    [ "jsonDecSomeOpts : Json.Decode.Decoder a -> Json.Decode.Decoder ( SomeOpts a )"+    , "jsonDecSomeOpts localDecoder_a ="+    , "    let jsonDecDictSomeOpts = Dict.fromList"+    , "            [ (\"Okay\", Json.Decode.map Okay (Json.Decode.int))"+    , "            , (\"NotOkay\", Json.Decode.map NotOkay (localDecoder_a))"+    , "            ]"+    , "    in  decodeSumObjectWithSingleField  \"SomeOpts\" jsonDecDictSomeOpts"+    ]  someOptsSer :: String-someOptsSer = "jsonEncSomeOpts localEncoder_a val = \n   case val of\n      Okay v1 -> [Json.Encode.int v1]\n      NotOkay v1 -> [localEncoder_a v1]\n"+someOptsSer = unlines+    [ "jsonEncSomeOpts : (a -> Value) -> SomeOpts a -> Value"+    , "jsonEncSomeOpts localEncoder_a val ="+    , "    let keyval v = case v of"+    , "                    Okay v1 -> (\"Okay\", encodeValue (Json.Encode.int v1))"+    , "                    NotOkay v1 -> (\"NotOkay\", encodeValue (localEncoder_a v1))"+    , "    in encodeSumObjectWithSingleField keyval val"+    ] +test1Parse :: String+test1Parse = unlines+    [ "jsonDecTestComp : Json.Decode.Decoder a -> Json.Decode.Decoder ( TestComp a )"+    , "jsonDecTestComp localDecoder_a ="+    , "   (\"t1\" := jsonDecChange (Json.Decode.int)) `Json.Decode.andThen` \\pt1 ->"+    , "   (\"t2\" := jsonDecChange (localDecoder_a)) `Json.Decode.andThen` \\pt2 ->"+    , "   Json.Decode.succeed {t1 = pt1, t2 = pt2}"+    ]++unaryAParse :: String+unaryAParse = unlines+    [ "jsonDecUnaryA : Json.Decode.Decoder ( UnaryA )"+    , "jsonDecUnaryA ="+    , "    let jsonDecDictUnaryA = Dict.fromList"+    , "            [ (\"UnaryA1\", Json.Decode.succeed UnaryA1)"+    , "            , (\"UnaryA2\", Json.Decode.succeed UnaryA2)"+    , "            ]"+    , "    in  decodeSumObjectWithSingleField  \"UnaryA\" jsonDecDictUnaryA"+    ]++unaryBParse :: String+unaryBParse = unlines+    [ "jsonDecUnaryB : Json.Decode.Decoder ( UnaryB )"+    , "jsonDecUnaryB = decodeSumUnaries \"UnaryB\" jsonDecDictUnaryB"+    , "jsonDecDictUnaryB = Dict.fromList [(\"UnaryB1\", UnaryB1), (\"UnaryB2\", UnaryB2)]"+    ]++unaryASer :: String+unaryASer = unlines+    [ "jsonEncUnaryA : UnaryA -> Value"+    , "jsonEncUnaryA  val ="+    , "    let keyval v = case v of"+    , "                    UnaryA1  -> (\"UnaryA1\", encodeValue (Json.Encode.list []))"+    , "                    UnaryA2  -> (\"UnaryA2\", encodeValue (Json.Encode.list []))"+    , "    in encodeSumObjectWithSingleField keyval val"+    ]++unaryBSer :: String+unaryBSer = unlines+    [ "jsonEncUnaryB : UnaryB -> Value"+    , "jsonEncUnaryB  val ="+    , "    case val of"+    , "        UnaryB1 -> Json.Encode.string \"UnaryB1\""+    , "        UnaryB2 -> Json.Encode.string \"UnaryB2\""+    ]+ spec :: Spec spec =     describe "json serialisation" $     do let rFoo = compileElmDef (Proxy :: Proxy Foo)            rBar = compileElmDef (Proxy :: Proxy (Bar a))+           rBaz = compileElmDef (Proxy :: Proxy (Baz a))+           rQux = compileElmDef (Proxy :: Proxy (Qux a))+           rTest1 = compileElmDef (Proxy :: Proxy (TestComp a))            rSomeOpts = compileElmDef (Proxy :: Proxy (SomeOpts a))-       it "should produce the correct ser code" $-          do jsonSerForDef rFoo `shouldBe` fooSer+           rUnaryA = compileElmDef (Proxy :: Proxy UnaryA)+           rUnaryB = compileElmDef (Proxy :: Proxy UnaryB)+       it "should produce the correct ser code" $ do+             jsonSerForDef rFoo `shouldBe` fooSer              jsonSerForDef rBar `shouldBe` barSer              jsonSerForDef rSomeOpts `shouldBe` someOptsSer-       it "should produce the correct parse code" $-          do jsonParserForDef rFoo `shouldBe` fooParse+             jsonSerForDef rBaz `shouldBe` bazSer+       it "should produce the correct ser code for unary unions" $ do+             jsonSerForDef rUnaryA `shouldBe` unaryASer+             jsonSerForDef rUnaryB `shouldBe` unaryBSer+       it "should produce the correct parse code for aliases" $ do+             jsonParserForDef rFoo `shouldBe` fooParse              jsonParserForDef rBar `shouldBe` barParse+       it "should produce the correct parse code generic sum types" $ do+             jsonParserForDef rBaz `shouldBe` bazParse              jsonParserForDef rSomeOpts `shouldBe` someOptsParse+             jsonParserForDef rTest1 `shouldBe` test1Parse+       it "should produce the correct parse code for unary unions" $ do+             jsonParserForDef rUnaryA `shouldBe` unaryAParse+             jsonParserForDef rUnaryB `shouldBe` unaryBParse
test/Elm/ModuleSpec.hs view
@@ -4,7 +4,6 @@ import Elm.Derive import Elm.Module -import Elm.TestHelpers  import Data.Proxy import Test.Hspec@@ -17,15 +16,87 @@    , b_list :: [Bool]    } deriving (Show, Eq) -$(deriveElmDef (fieldDropOpts 2) ''Bar)+data Qux a = Qux1 Int String+           | Qux2 { _qux2a :: Int, _qux2test :: a }+           deriving (Show, Eq) +$(deriveElmDef (defaultOptionsDropLower 2) ''Bar)+$(deriveElmDef (defaultOptionsDropLower 5) ''Qux)+ moduleCode :: String-moduleCode = "module Foo where \n\nimport Json.Decode\nimport Json.Decode exposing ((:=))\nimport Json.Encode\n\n\ntype alias Bar a = \n   { name: a\n   , blablub: Int\n   , tuple: (Int, String)\n   , list: (List Bool)\n   }\n\njsonDecBar localDecoder_a = \n   (\"name\" := localDecoder_a) `Json.Decode.andThen` \\pname -> \n   (\"blablub\" := Json.Decode.int) `Json.Decode.andThen` \\pblablub -> \n   (\"tuple\" := Json.Decode.tuple2 (,) (Json.Decode.int) (Json.Decode.string)) `Json.Decode.andThen` \\ptuple -> \n   (\"list\" := Json.Decode.list (Json.Decode.bool)) `Json.Decode.andThen` \\plist -> \n   Json.Decode.succeed {name = pname, blablub = pblablub, tuple = ptuple, list = plist}\n\njsonEncBar localEncoder_a val = \n   Json.Encode.object\n   [ (\"name\", localEncoder_a val.name)\n   , (\"blablub\", Json.Encode.int val.blablub)\n   , (\"tuple\", (\\v1 v2 -> [(Json.Encode.int) v1,(Json.Encode.string) v2] val.tuple)\n   , (\"list\", (Json.Encode.list << map Json.Encode.bool) val.list)\n   ]\n\n"+moduleCode = unlines+    [ "module Foo where"+    , ""+    , "import Json.Decode"+    , "import Json.Decode exposing ((:=))"+    , "import Json.Encode exposing (Value)"+    , "-- The following module comes from bartavelle/json-helpers"+    , "import Json.Helpers exposing (..)"+    , ""+    , ""+    , "type alias Bar a ="+    , "   { name: a"+    , "   , blablub: Int"+    , "   , tuple: (Int, String)"+    , "   , list: (List Bool)"+    , "   }"+    , ""+    , "jsonDecBar : Json.Decode.Decoder a -> Json.Decode.Decoder ( Bar a )"+    , "jsonDecBar localDecoder_a ="+    , "   (\"name\" := localDecoder_a) `Json.Decode.andThen` \\pname ->"+    , "   (\"blablub\" := Json.Decode.int) `Json.Decode.andThen` \\pblablub ->"+    , "   (\"tuple\" := Json.Decode.tuple2 (,) (Json.Decode.int) (Json.Decode.string)) `Json.Decode.andThen` \\ptuple ->"+    , "   (\"list\" := Json.Decode.list (Json.Decode.bool)) `Json.Decode.andThen` \\plist ->"+    , "   Json.Decode.succeed {name = pname, blablub = pblablub, tuple = ptuple, list = plist}"+    , ""+    , "jsonEncBar : (a -> Value) -> Bar a -> Value"+    , "jsonEncBar localEncoder_a val ="+    , "   Json.Encode.object"+    , "   [ (\"name\", localEncoder_a val.name)"+    , "   , (\"blablub\", Json.Encode.int val.blablub)"+    , "   , (\"tuple\", (\\(v1,v2) -> Json.Encode.list [(Json.Encode.int) v1,(Json.Encode.string) v2]) val.tuple)"+    , "   , (\"list\", (Json.Encode.list << List.map Json.Encode.bool) val.list)"+    , "   ]"+    , ""+    ] +moduleCode' :: String+moduleCode' = unlines+    [ "module Qux where"+    , ""+    , "import Json.Decode"+    , "import Json.Decode exposing ((:=))"+    , "import Json.Encode exposing (Value)"+    , "-- The following module comes from bartavelle/json-helpers"+    , "import Json.Helpers exposing (..)"+    , ""+    , ""+    , "type Qux a ="+    , "    Qux1 Int String"+    , "    | Qux2 {a: Int, test: a}"+    , ""+    , "jsonDecQux : Json.Decode.Decoder a -> Json.Decode.Decoder ( Qux a )"+    , "jsonDecQux localDecoder_a ="+    , "    let jsonDecDictQux = Dict.fromList"+    , "            [ (\"Qux1\", Json.Decode.tuple2 Qux1 (Json.Decode.int) (Json.Decode.string))"+    , "            , (\"Qux2\", Json.Decode.map Qux2 (   (\"a\" := Json.Decode.int) `Json.Decode.andThen` \\pa ->    (\"test\" := localDecoder_a) `Json.Decode.andThen` \\ptest ->    Json.Decode.succeed {a = pa, test = ptest}))"+    , "            ]"+    , "    in  decodeSumObjectWithSingleField  \"Qux\" jsonDecDictQux"+    , ""+    , "jsonEncQux : (a -> Value) -> Qux a -> Value"+    , "jsonEncQux localEncoder_a val ="+    , "    let keyval v = case v of"+    , "                    Qux1 v1 v2 -> (\"Qux1\", encodeValue (Json.Encode.list [Json.Encode.int v1, Json.Encode.string v2]))"+    , "                    Qux2 vs -> (\"Qux2\", encodeObject [(\"a\", Json.Encode.int vs.a), (\"test\", localEncoder_a vs.test)])"+    , "    in encodeSumObjectWithSingleField keyval val"+    , ""+    ]+ spec :: Spec spec =     describe "makeElmModule" $     it "should produce the correct code" $-       do let modu =-                 makeElmModule "Foo" [DefineElm (Proxy :: Proxy (Bar a))]+       do let modu = makeElmModule "Foo" [DefineElm (Proxy :: Proxy (Bar a))]+          let modu' = makeElmModule "Qux" [DefineElm (Proxy :: Proxy (Qux a))]           modu `shouldBe` moduleCode+          modu' `shouldBe` moduleCode'
− test/Elm/TestHelpers.hs
@@ -1,9 +0,0 @@-module Elm.TestHelpers where--import Elm.Derive--fieldDropOpts :: Int -> DeriveOpts-fieldDropOpts i =-    defaultOpts-    { do_fieldModifier = drop i-    }
test/Elm/TyRenderSpec.hs view
@@ -5,8 +5,6 @@ import Elm.TyRep import Elm.TyRender -import Elm.TestHelpers- import Data.Proxy import Test.Hspec @@ -28,18 +26,18 @@    = Okay Int    | NotOkay a -$(deriveElmDef (fieldDropOpts 2) ''Foo)-$(deriveElmDef (fieldDropOpts 2) ''Bar)-$(deriveElmDef defaultOpts ''SomeOpts)+$(deriveElmDef (defaultOptionsDropLower 2) ''Foo)+$(deriveElmDef (defaultOptionsDropLower 2) ''Bar)+$(deriveElmDef defaultOptions ''SomeOpts)  fooCode :: String-fooCode = "type alias Foo  = \n   { name: String\n   , blablub: Int\n   }\n"+fooCode = "type alias Foo  =\n   { name: String\n   , blablub: Int\n   }\n"  barCode :: String-barCode = "type alias Bar a = \n   { name: a\n   , blablub: Int\n   , tuple: (Int, String)\n   , list: (List Bool)\n   }\n"+barCode = "type alias Bar a =\n   { name: a\n   , blablub: Int\n   , tuple: (Int, String)\n   , list: (List Bool)\n   }\n"  someOptsCode :: String-someOptsCode = "type SomeOpts a = \n    Okay Int\n    | NotOkay a\n"+someOptsCode = "type SomeOpts a =\n    Okay Int\n    | NotOkay a\n"  spec :: Spec spec =
+ test/EndToEnd.hs view
@@ -0,0 +1,271 @@+{-# LANGUAGE TemplateHaskell #-}+module Main where++import Elm.Derive+import Elm.Module+import Data.Proxy+import Data.Aeson hiding (defaultOptions)+import Data.Aeson.Types (SumEncoding(..))+import Test.QuickCheck.Arbitrary+import Test.QuickCheck.Gen (sample', oneof, Gen)+import qualified Data.Text as T+import Control.Applicative+import System.Environment+import Data.Char (toLower)+import Prelude++data Record1 a = Record1 { _r1foo :: Int, _r1bar :: Maybe Int, _r1baz :: a, _r1qux :: Maybe a } deriving Show+data Record2 a = Record2 { _r2foo :: Int, _r2bar :: Maybe Int, _r2baz :: a, _r2qux :: Maybe a } deriving Show++data Sum01 a = Sum01A a | Sum01B (Maybe a) | Sum01C a a | Sum01D { _s01foo :: a } | Sum01E { _s01bar :: Int, _s01baz :: Int } deriving Show+data Sum02 a = Sum02A a | Sum02B (Maybe a) | Sum02C a a | Sum02D { _s02foo :: a } | Sum02E { _s02bar :: Int, _s02baz :: Int } deriving Show+data Sum03 a = Sum03A a | Sum03B (Maybe a) | Sum03C a a | Sum03D { _s03foo :: a } | Sum03E { _s03bar :: Int, _s03baz :: Int } deriving Show+data Sum04 a = Sum04A a | Sum04B (Maybe a) | Sum04C a a | Sum04D { _s04foo :: a } | Sum04E { _s04bar :: Int, _s04baz :: Int } deriving Show+data Sum05 a = Sum05A a | Sum05B (Maybe a) | Sum05C a a | Sum05D { _s05foo :: a } | Sum05E { _s05bar :: Int, _s05baz :: Int } deriving Show+data Sum06 a = Sum06A a | Sum06B (Maybe a) | Sum06C a a | Sum06D { _s06foo :: a } | Sum06E { _s06bar :: Int, _s06baz :: Int } deriving Show+data Sum07 a = Sum07A a | Sum07B (Maybe a) | Sum07C a a | Sum07D { _s07foo :: a } | Sum07E { _s07bar :: Int, _s07baz :: Int } deriving Show+data Sum08 a = Sum08A a | Sum08B (Maybe a) | Sum08C a a | Sum08D { _s08foo :: a } | Sum08E { _s08bar :: Int, _s08baz :: Int } deriving Show+data Sum09 a = Sum09A a | Sum09B (Maybe a) | Sum09C a a | Sum09D { _s09foo :: a } | Sum09E { _s09bar :: Int, _s09baz :: Int } deriving Show+data Sum10 a = Sum10A a | Sum10B (Maybe a) | Sum10C a a | Sum10D { _s10foo :: a } | Sum10E { _s10bar :: Int, _s10baz :: Int } deriving Show+data Sum11 a = Sum11A a | Sum11B (Maybe a) | Sum11C a a | Sum11D { _s11foo :: a } | Sum11E { _s11bar :: Int, _s11baz :: Int } deriving Show+data Sum12 a = Sum12A a | Sum12B (Maybe a) | Sum12C a a | Sum12D { _s12foo :: a } | Sum12E { _s12bar :: Int, _s12baz :: Int } deriving Show++$(deriveBoth defaultOptions{ fieldLabelModifier = drop 3, omitNothingFields = False } ''Record1)+$(deriveBoth defaultOptions{ fieldLabelModifier = drop 3, omitNothingFields = True  } ''Record2)++$(deriveBoth defaultOptions{ fieldLabelModifier = drop 4, omitNothingFields = False, allNullaryToStringTag = False, sumEncoding = TaggedObject "tag" "content" } ''Sum01)+$(deriveBoth defaultOptions{ fieldLabelModifier = drop 4, omitNothingFields = True , allNullaryToStringTag = False, sumEncoding = TaggedObject "tag" "content" } ''Sum02)+$(deriveBoth defaultOptions{ fieldLabelModifier = drop 4, omitNothingFields = False, allNullaryToStringTag = True , sumEncoding = TaggedObject "tag" "content" } ''Sum03)+$(deriveBoth defaultOptions{ fieldLabelModifier = drop 4, omitNothingFields = True , allNullaryToStringTag = True , sumEncoding = TaggedObject "tag" "content" } ''Sum04)++$(deriveBoth defaultOptions{ fieldLabelModifier = drop 4, omitNothingFields = False, allNullaryToStringTag = False, sumEncoding = ObjectWithSingleField } ''Sum05)+$(deriveBoth defaultOptions{ fieldLabelModifier = drop 4, omitNothingFields = True , allNullaryToStringTag = False, sumEncoding = ObjectWithSingleField } ''Sum06)+$(deriveBoth defaultOptions{ fieldLabelModifier = drop 4, omitNothingFields = False, allNullaryToStringTag = True , sumEncoding = ObjectWithSingleField } ''Sum07)+$(deriveBoth defaultOptions{ fieldLabelModifier = drop 4, omitNothingFields = True , allNullaryToStringTag = True , sumEncoding = ObjectWithSingleField } ''Sum08)++$(deriveBoth defaultOptions{ fieldLabelModifier = drop 4, omitNothingFields = False, allNullaryToStringTag = False, sumEncoding = TwoElemArray } ''Sum09)+$(deriveBoth defaultOptions{ fieldLabelModifier = drop 4, omitNothingFields = True , allNullaryToStringTag = False, sumEncoding = TwoElemArray } ''Sum10)+$(deriveBoth defaultOptions{ fieldLabelModifier = drop 4, omitNothingFields = False, allNullaryToStringTag = True , sumEncoding = TwoElemArray } ''Sum11)+$(deriveBoth defaultOptions{ fieldLabelModifier = drop 4, omitNothingFields = True , allNullaryToStringTag = True , sumEncoding = TwoElemArray } ''Sum12)++instance Arbitrary a => Arbitrary (Record1 a) where+    arbitrary = Record1 <$> arbitrary <*> fmap Just arbitrary <*> arbitrary <*> fmap Just arbitrary+instance Arbitrary a => Arbitrary (Record2 a) where+    arbitrary = Record2 <$> arbitrary <*> fmap Just arbitrary <*> arbitrary <*> fmap Just arbitrary++arb :: Arbitrary a => (a -> b) -> (Maybe a -> b) -> (a -> a -> b) -> (a -> b) -> (Int -> Int -> b) -> Gen b+arb c1 c2 c3 c4 c5 = oneof+    [ c1 <$> arbitrary+    , c2 . Just <$> arbitrary+    , c3 <$> arbitrary <*> arbitrary+    , c4 <$> arbitrary+    , c5 <$> arbitrary <*> arbitrary+    ]++instance Arbitrary a => Arbitrary (Sum01 a) where arbitrary = arb Sum01A Sum01B Sum01C Sum01D Sum01E+instance Arbitrary a => Arbitrary (Sum02 a) where arbitrary = arb Sum02A Sum02B Sum02C Sum02D Sum02E+instance Arbitrary a => Arbitrary (Sum03 a) where arbitrary = arb Sum03A Sum03B Sum03C Sum03D Sum03E+instance Arbitrary a => Arbitrary (Sum04 a) where arbitrary = arb Sum04A Sum04B Sum04C Sum04D Sum04E+instance Arbitrary a => Arbitrary (Sum05 a) where arbitrary = arb Sum05A Sum05B Sum05C Sum05D Sum05E+instance Arbitrary a => Arbitrary (Sum06 a) where arbitrary = arb Sum06A Sum06B Sum06C Sum06D Sum06E+instance Arbitrary a => Arbitrary (Sum07 a) where arbitrary = arb Sum07A Sum07B Sum07C Sum07D Sum07E+instance Arbitrary a => Arbitrary (Sum08 a) where arbitrary = arb Sum08A Sum08B Sum08C Sum08D Sum08E+instance Arbitrary a => Arbitrary (Sum09 a) where arbitrary = arb Sum09A Sum09B Sum09C Sum09D Sum09E+instance Arbitrary a => Arbitrary (Sum10 a) where arbitrary = arb Sum10A Sum10B Sum10C Sum10D Sum10E+instance Arbitrary a => Arbitrary (Sum11 a) where arbitrary = arb Sum11A Sum11B Sum11C Sum11D Sum11E+instance Arbitrary a => Arbitrary (Sum12 a) where arbitrary = arb Sum12A Sum12B Sum12C Sum12D Sum12E++elmModuleContent :: String+elmModuleContent = unlines+    [ "-- This module requires the following packages:"+    , "-- * deadfoxygrandpa/elm-test"+    , "-- * bartavelle/json-helpers"+    , "module MyTests where"+    , ""+    , "import Dict exposing (Dict)"+    , "import Set exposing (Set)"+    , "import Json.Decode exposing ((:=), Value)"+    , "import Json.Encode"+    , "import Json.Helpers exposing (..)"+    , "import ElmTest exposing (..)"+    , "import Graphics.Element exposing (Element)"+    , "import String"+    , ""+    , "main : Element"+    , "main = elementRunner <| suite \"Testing\" [ sumEncode, sumDecode, recordDecode, recordEncode ]"+    , ""+    , "recordDecode : Test"+    , "recordDecode = suite \"Record decoding checks\""+    , "              [ recordDecode1"+    , "              , recordDecode2"+    , "              ]"+    , ""+    , "recordEncode : Test"+    , "recordEncode = suite \"Record encoding checks\""+    , "              [ recordEncode1"+    , "              , recordEncode2"+    , "              ]"+    , ""+    , "sumDecode : Test"+    , "sumDecode = suite \"Sum decoding checks\""+    , "              [ sumDecode01"+    , "              , sumDecode02"+    , "              , sumDecode03"+    , "              , sumDecode04"+    , "              , sumDecode05"+    , "              , sumDecode06"+    , "              , sumDecode07"+    , "              , sumDecode08"+    , "              , sumDecode09"+    , "              , sumDecode10"+    , "              , sumDecode11"+    , "              , sumDecode12"+    , "              ]"+    , ""+    , "sumEncode : Test"+    , "sumEncode = suite \"Sum encoding checks\""+    , "              [ sumEncode01"+    , "              , sumEncode02"+    , "              , sumEncode03"+    , "              , sumEncode04"+    , "              , sumEncode05"+    , "              , sumEncode06"+    , "              , sumEncode07"+    , "              , sumEncode08"+    , "              , sumEncode09"+    , "              , sumEncode10"+    , "              , sumEncode11"+    , "              , sumEncode12"+    , "              ]"+    , ""+    , "-- this is done to prevent artificial differences due to object ordering, this won't work with Maybe's though :("+    , "assertEqualHack : String -> String -> Assertion"+    , "assertEqualHack a b ="+    , "    let remix = Json.Decode.decodeString Json.Decode.value"+    , "    in assertEqual (remix a) (remix b)"+    , ""+    , makeModuleContentWithAlterations (newtypeAliases ["Record1", "Record2"] . defaultAlterations)+        [ DefineElm (Proxy :: Proxy (Record1 a))+        , DefineElm (Proxy :: Proxy (Record2 a))+        , DefineElm (Proxy :: Proxy (Sum01 a))+        , DefineElm (Proxy :: Proxy (Sum02 a))+        , DefineElm (Proxy :: Proxy (Sum03 a))+        , DefineElm (Proxy :: Proxy (Sum04 a))+        , DefineElm (Proxy :: Proxy (Sum05 a))+        , DefineElm (Proxy :: Proxy (Sum06 a))+        , DefineElm (Proxy :: Proxy (Sum07 a))+        , DefineElm (Proxy :: Proxy (Sum08 a))+        , DefineElm (Proxy :: Proxy (Sum09 a))+        , DefineElm (Proxy :: Proxy (Sum10 a))+        , DefineElm (Proxy :: Proxy (Sum11 a))+        , DefineElm (Proxy :: Proxy (Sum12 a))+        ]+    ]++mkDecodeTest :: (Show a, ToJSON a) => String -> String -> String -> [a] -> String+mkDecodeTest pred prefix num elems = unlines (+    [ map toLower pred ++ "Decode" ++ num ++ " : Test"+    , map toLower pred ++ "Decode" ++ num ++ " = suite \"" ++ pred ++ " decode " ++ num ++ "\""+    ]+    ++ map mktest (zip ([1..] :: [Int]) elems)+    ++ ["  ]"]+    )+  where+      mktest (n,e) = pfix ++ "test \"" ++ show n ++ "\" (assertEqual (Json.Decode.decodeString (jsonDec" ++ pred ++ num ++ " Json.Decode.int) " ++ encoded ++ ") (Ok (" ++ pretty ++ ")))"+        where+            pretty = T.unpack $ T.replace (T.pack (prefix ++ num)) T.empty $ T.pack $ show e+            encoded = show (encode e)+            pfix = if n == 1 then "  [ " else "  , "++mkSumDecodeTest :: (Show a, ToJSON a) => String -> [a] -> String+mkSumDecodeTest = mkDecodeTest "Sum" "_s"++mkRecordDecodeTest :: (Show a, ToJSON a) => String -> [a] -> String+mkRecordDecodeTest = mkDecodeTest "Record" "_r"++mkEncodeTest :: (Show a, ToJSON a) => String -> String -> String -> [a] -> String+mkEncodeTest pred prefix num elems = unlines (+    [ map toLower pred ++ "Encode" ++ num ++ " : Test"+    , map toLower pred ++ "Encode" ++ num ++ " = suite \"" ++ pred ++ " encode " ++ num ++ "\""+    ]+    ++ map mktest (zip ([1..] :: [Int]) elems)+    ++ ["  ]"]+    )+  where+      mktest (n,e) = pfix ++ "test \"" ++ show n ++ "\" (assertEqualHack (Json.Encode.encode 0 (jsonEnc" ++ pred ++ num ++ " Json.Encode.int (" ++ pretty ++ "))) " ++ encoded ++ ")"+        where+            pretty = T.unpack $ T.replace (T.pack (prefix ++ num)) T.empty $ T.pack $ show e+            encoded = show (encode e)+            pfix = if n == 1 then "  [ " else "  , "++mkSumEncodeTest :: (Show a, ToJSON a) => String -> [a] -> String+mkSumEncodeTest = mkEncodeTest "Sum" "_s"++mkRecordEncodeTest :: (Show a, ToJSON a) => String -> [a] -> String+mkRecordEncodeTest = mkEncodeTest "Record" "_r"+{-+mkSumEncodeTest num elems = unlines (+    [ "sumEncode" ++ num ++ " : Test"+    , "sumEncode" ++ num ++ " = suite \"sum encode " ++ num ++ "\""+    ]+    ++ map mktest (zip ([1..] :: [Int]) elems)+    ++ ["  ]"]+    )+  where+      mktest (n,e) = prefix ++ "test \"" ++ show n ++ "\" (assertEqualHack (Json.Encode.encode 0 (jsonEncSum" ++ num ++ " Json.Encode.int (" ++ pretty ++ "))) " ++ encoded ++ ")"+        where+            pretty = T.unpack $ T.replace (T.pack ("_s" ++ num)) T.empty $ T.pack $ show e+            encoded = show (encode e)+            prefix = if n == 1 then "  [ " else "  , "+-}+main :: IO ()+main = do+    ss01 <- sample' arbitrary :: IO [Sum01 Int]+    ss02 <- sample' arbitrary :: IO [Sum02 Int]+    ss03 <- sample' arbitrary :: IO [Sum03 Int]+    ss04 <- sample' arbitrary :: IO [Sum04 Int]+    ss05 <- sample' arbitrary :: IO [Sum05 Int]+    ss06 <- sample' arbitrary :: IO [Sum06 Int]+    ss07 <- sample' arbitrary :: IO [Sum07 Int]+    ss08 <- sample' arbitrary :: IO [Sum08 Int]+    ss09 <- sample' arbitrary :: IO [Sum09 Int]+    ss10 <- sample' arbitrary :: IO [Sum10 Int]+    ss11 <- sample' arbitrary :: IO [Sum11 Int]+    ss12 <- sample' arbitrary :: IO [Sum12 Int]+    re01 <- sample' arbitrary :: IO [Record1 Int]+    re02 <- sample' arbitrary :: IO [Record2 Int]+    args <- getArgs+    case args of+        [] -> return ()+        (x:_) -> writeFile x $+               unlines [ elmModuleContent+                       , mkSumEncodeTest "01" ss01+                       , mkSumEncodeTest "02" ss02+                       , mkSumEncodeTest "03" ss03+                       , mkSumEncodeTest "04" ss04+                       , mkSumEncodeTest "05" ss05+                       , mkSumEncodeTest "06" ss06+                       , mkSumEncodeTest "07" ss07+                       , mkSumEncodeTest "08" ss08+                       , mkSumEncodeTest "09" ss09+                       , mkSumEncodeTest "10" ss10+                       , mkSumEncodeTest "11" ss11+                       , mkSumEncodeTest "12" ss12+                       , mkSumDecodeTest "01" ss01+                       , mkSumDecodeTest "02" ss02+                       , mkSumDecodeTest "03" ss03+                       , mkSumDecodeTest "04" ss04+                       , mkSumDecodeTest "05" ss05+                       , mkSumDecodeTest "06" ss06+                       , mkSumDecodeTest "07" ss07+                       , mkSumDecodeTest "08" ss08+                       , mkSumDecodeTest "09" ss09+                       , mkSumDecodeTest "10" ss10+                       , mkSumDecodeTest "11" ss11+                       , mkSumDecodeTest "12" ss12+                       , mkRecordDecodeTest "1" re01+                       , mkRecordDecodeTest "2" re02+                       , mkRecordEncodeTest "1" re01+                       , mkRecordEncodeTest "2" re02+                       ]+