packages feed

derive-has-field (empty) → 0.0.1.0

raw patch · 7 files changed

+233/−0 lines, 7 filesdep +basedep +derive-has-fielddep +hspec

Dependencies added: base, derive-has-field, hspec, template-haskell, th-abstraction

Files

+ LICENSE view
@@ -0,0 +1,19 @@+Copyright (c) 2023 Barry Moore <chiroptical@gmail.com>++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN+THE SOFTWARE.
+ README.md view
@@ -0,0 +1,37 @@+Derive HasField instances+===++The [`OverloadedRecordDot`][overloaded-record-dot] syntax is surprisingly nice.+I really enjoy writing code with this extension and I was originally hesistant.++[Persistent][persistent] has a really nice feature where it will automatically remove+prefixes from models. Given a model like,++```+BankAccount+  accountNumber String+```++You would normally reference this field as `bankAccountAccountNumber`. However,+with overloaded record dot you can write `bankAccount.accountNumber` which is+much nicer.++At work, I really wanted this for **every** record. With this library, I can+write,++```haskell+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TemplateHaskell #-}++import DeriveHasField++data BankAccount =+  BankAccount+    { bankAccountAccountNumber :: String+    }++deriveHasFieldWith (dropPrefix "bankAccount") ''BankAccount+```++[overloaded-record-dot]: https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/overloaded_record_dot.html+[persistent]: https://github.com/yesodweb/persistent
+ derive-has-field.cabal view
@@ -0,0 +1,68 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.35.2.+--+-- see: https://github.com/sol/hpack++name:           derive-has-field+version:        0.0.1.0+synopsis:       Derive HasField instances with Template Haskell+description:    A Template Haskell function to derive HasField instances to utilize OverloadedRecordDot more effectively.+homepage:       https://github.com/chiroptical/snail#readme+bug-reports:    https://github.com/chiroptical/snail/issues+author:         Barry Moore II+maintainer:     chiroptical@gmail.com+copyright:      Barry Moore II+license:        MIT+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md++source-repository head+  type: git+  location: https://github.com/chiroptical/snail++library+  exposed-modules:+      DeriveHasField+  other-modules:+      Paths_derive_has_field+  hs-source-dirs:+      src+  default-extensions:+      DeriveGeneric+      ImportQualifiedPost+      LambdaCase+      OverloadedStrings+      RecordWildCards+      TypeApplications+  build-depends:+      base >=4.7 && <5+    , template-haskell+    , th-abstraction >0.4 && <0.7+  default-language: Haskell2010++test-suite derive-has-field-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      DeriveHasFieldSpec+      Import+      Paths_derive_has_field+  hs-source-dirs:+      test+  default-extensions:+      DeriveGeneric+      ImportQualifiedPost+      LambdaCase+      OverloadedStrings+      RecordWildCards+      TypeApplications+  build-depends:+      base >=4.7 && <5+    , derive-has-field+    , hspec >=2.10.10 && <2.11+    , template-haskell+    , th-abstraction >0.4 && <0.7+  default-language: Haskell2010
+ src/DeriveHasField.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}++module DeriveHasField (+  module GHC.Records,+  deriveHasFieldWith,+)+where++import Control.Monad+import Data.Char (toLower)+import Data.Foldable as Foldable+import Data.Traversable (for)+import GHC.Records+import Language.Haskell.TH+import Language.Haskell.TH.Datatype++deriveHasFieldWith :: (String -> String) -> Name -> DecsQ+deriveHasFieldWith fieldModifier = makeDeriveHasField fieldModifier <=< reifyDatatype++makeDeriveHasField :: (String -> String) -> DatatypeInfo -> DecsQ+makeDeriveHasField fieldModifier datatypeInfo = do+  -- We do not support sum of product types+  constructorInfo <- case datatypeInfo.datatypeCons of+    [info] -> pure info+    _ -> fail "deriveHasField: only supports product types with a single data constructor"++  -- We only support data and newtype declarations+  when (datatypeInfo.datatypeVariant `Foldable.notElem` [Datatype, Newtype]) $+    fail "deriveHasField: only supports data and newtype"++  -- We only support data types with field names and concrete types+  let isConcreteType = \case+        ConT _ -> True+        AppT _ _ -> True+        _ -> False+  recordConstructorNames <- case constructorInfo.constructorVariant of+    RecordConstructor names -> pure names+    _ -> fail "deriveHasField: only supports constructors with field names"+  unless (Foldable.all isConcreteType constructorInfo.constructorFields) $+    fail "deriveHasField: only supports concrete field types"++  -- Build the instances+  let constructorNamesAndTypes :: [(Name, Type)]+      constructorNamesAndTypes = zip recordConstructorNames constructorInfo.constructorFields+  decs <- for constructorNamesAndTypes $ \(name, ty) ->+    let currentFieldName = nameBase name+        wantedFieldName = lowerFirst $ fieldModifier currentFieldName+        litTCurrentField = litT $ strTyLit currentFieldName+        litTFieldWanted = litT $ strTyLit wantedFieldName+        parentTypeConstructor = conT datatypeInfo.datatypeName+     in if currentFieldName == wantedFieldName+          then fail "deriveHasField: after applying fieldModifier, field didn't change"+          else+            [d|+              instance HasField $litTFieldWanted $parentTypeConstructor $(pure ty) where+                getField = $(appTypeE (varE $ mkName "getField") litTCurrentField)+              |]+  pure $ Foldable.concat decs++lowerFirst :: String -> String+lowerFirst = \case+  [] -> []+  (x : xs) -> toLower x : xs
+ test/DeriveHasFieldSpec.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE TemplateHaskell #-}++module DeriveHasFieldSpec where++import DeriveHasField+import Import+import Test.Hspec++data SomeType = SomeType+  { someTypeSomeField :: String+  , someTypeSomeOtherField :: Int+  , someTypeSomeMaybeField :: Maybe Int+  , someTypeSomeEitherField :: Either String Int+  }++deriveHasFieldWith (dropPrefix "someType") ''SomeType++someType :: SomeType+someType =+  SomeType+    { someTypeSomeField = "hello"+    , someTypeSomeOtherField = 0+    , someTypeSomeMaybeField = Just 0+    , someTypeSomeEitherField = Right 0+    }++spec :: Spec+spec = do+  describe "deriveHasField" $ do+    it "compiles and gets the right field" $ do+      someType.someField `shouldBe` "hello"+      someType.someOtherField `shouldBe` 0+      someType.someMaybeField `shouldBe` Just 0+      someType.someEitherField `shouldBe` Right 0
+ test/Import.hs view
@@ -0,0 +1,7 @@+module Import where++import Data.List (stripPrefix)+import Data.Maybe (fromMaybe)++dropPrefix :: String -> String -> String+dropPrefix prefix input = fromMaybe input $ stripPrefix prefix input
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}