diff --git a/.restyled.yaml b/.restyled.yaml index d5b7a3b..13d3c19 100644 --- a/.restyled.yaml +++ b/.restyled.yaml @@ -1,9 +1,6 @@ restylers: - - brittany: - include: - - '**/*.hs' - - '!src/Graphula/Class.hs' # CPP - - stylish-haskell + - "!stylish-haskell" + - fourmolu comments: false diff --git a/.stylish-haskell.yaml b/.stylish-haskell.yaml deleted file mode 100644 index f17b0e5..0000000 --- a/.stylish-haskell.yaml +++ /dev/null @@ -1,22 +0,0 @@ ---- -steps: - - simple_align: - cases: false - top_level_patterns: false - records: false - - imports: - align: none - list_align: after_alias - pad_module_names: false - long_list_align: new_line_multiline - empty_list_align: right_after - list_padding: 2 - separate_lists: false - space_surround: false - - language_pragmas: - style: vertical - align: false - remove_redundant: false - - trailing_whitespace: {} -columns: 80 -newline: native diff --git a/Setup.hs b/Setup.hs index 9a994af..e8ef27d 100644 --- a/Setup.hs +++ b/Setup.hs @@ -1,2 +1,3 @@ import Distribution.Simple + main = defaultMain diff --git a/brittany.yaml b/brittany.yaml deleted file mode 100644 index 4955113..0000000 --- a/brittany.yaml +++ /dev/null @@ -1,77 +0,0 @@ ---- -conf_debug: - dconf_roundtrip_exactprint_only: false - dconf_dump_bridoc_simpl_par: false - dconf_dump_ast_unknown: false - dconf_dump_bridoc_simpl_floating: false - dconf_dump_config: false - dconf_dump_bridoc_raw: false - dconf_dump_bridoc_final: false - dconf_dump_bridoc_simpl_alt: false - dconf_dump_bridoc_simpl_indent: false - dconf_dump_annotations: false - dconf_dump_bridoc_simpl_columns: false - dconf_dump_ast_full: false -conf_forward: - options_ghc: - - -XBangPatterns - - -XConstraintKinds - - -XDataKinds - - -XDeriveDataTypeable - - -XDeriveGeneric - - -XDoAndIfThenElse - - -XEmptyDataDecls - - -XFlexibleContexts - - -XFlexibleInstances - - -XFunctionalDependencies - - -XGADTs - - -XKindSignatures - - -XLambdaCase - - -XMultiParamTypeClasses - - -XMultiWayIf - - -XNamedFieldPuns - - -XNoImplicitPrelude - - -XNoMonomorphismRestriction - - -XOverloadedStrings - - -XPolyKinds - - -XQuasiQuotes - - -XRank2Types - - -XRecordWildCards - - -XScopedTypeVariables - - -XStandaloneDeriving - - -XTemplateHaskell - - -XTupleSections - - -XTypeApplications - - -XTypeFamilies - - -XTypeOperators - - -XViewPatterns -conf_errorHandling: - econf_ExactPrintFallback: ExactPrintFallbackModeInline - econf_Werror: false - econf_omit_output_valid_check: false - econf_produceOutputOnErrors: false -conf_preprocessor: - ppconf_CPPMode: CPPModeAbort - ppconf_hackAroundIncludes: false -conf_obfuscate: false -conf_roundtrip_exactprint_only: false -conf_version: 1 -conf_layout: - lconfig_reformatModulePreamble: true - lconfig_altChooser: - tag: AltChooserBoundedSearch - contents: 3 - lconfig_allowSingleLineExportList: false - lconfig_importColumn: 60 - lconfig_hangingTypeSignature: false - lconfig_importAsColumn: 50 - lconfig_alignmentLimit: 1 - lconfig_indentListSpecial: true - lconfig_indentAmount: 2 - lconfig_alignmentBreakOnMultiline: true - lconfig_cols: 80 - lconfig_indentPolicy: IndentPolicyLeft - lconfig_indentWhereSpecial: true - lconfig_columnAlignMode: - tag: ColumnAlignModeDisabled - contents: 0.7 diff --git a/fourmolu.yaml b/fourmolu.yaml new file mode 100644 index 0000000..ef571e8 --- /dev/null +++ b/fourmolu.yaml @@ -0,0 +1,15 @@ +indentation: 2 +column-limit: 80 # ignored until v12 / ghc-9.6 +function-arrows: leading +comma-style: leading # default +import-export-style: leading +indent-wheres: false # default +record-brace-space: true +newlines-between-decls: 1 # default +haddock-style: single-line +let-style: mixed +in-style: left-align +single-constraint-parens: never # ignored until v12 / ghc-9.6 +unicode: never # default +respectful: true # default +fixities: [] # default diff --git a/src/Graphula.hs b/src/Graphula.hs index 96693d8..4ba4c6b 100755 --- a/src/Graphula.hs +++ b/src/Graphula.hs @@ -72,64 +72,64 @@ -- $ 'ensure' -- $ not . courseIsArchived -- @ --- module Graphula - ( - -- * Basic usage - -- ** Model requirements - HasDependencies(..) - , Only(..) + ( -- * Basic usage + + -- ** Model requirements + HasDependencies (..) + , Only (..) , only - -- ** Defining the graph + -- ** Defining the graph , node , edit , ensure - -- ** Running the graph + -- ** Running the graph , GraphulaT , runGraphulaT - , GenerationFailure(..) + , GenerationFailure (..) - -- * Advanced usage - -- ** Non-serial keys - , KeySourceType(..) + -- * Advanced usage + + -- ** Non-serial keys + , KeySourceType (..) , nodeKeyed - -- ** Running with logging + -- ** Running with logging , GraphulaLoggedT , runGraphulaLoggedT , runGraphulaLoggedWithFileT - -- ** Running idempotently + -- ** Running idempotently , GraphulaIdempotentT , runGraphulaIdempotentT - -- * Useful synonymns - -- | - -- - -- When declaring your own functions that call 'node', these synonyms can help - -- with the constraint soup. - -- - -- > genSchoolWithTeacher - -- > :: GraphulaContext m '[School, Teacher] - -- > -> m (Entity Teacher) - -- > genSchoolWithTeacher = do - -- > school <- node @School () mempty - -- > node @Teacher (onlyKey school) mempty - -- + -- * Useful synonymns + + -- | + -- + -- When declaring your own functions that call 'node', these synonyms can help + -- with the constraint soup. + -- + -- > genSchoolWithTeacher + -- > :: GraphulaContext m '[School, Teacher] + -- > -> m (Entity Teacher) + -- > genSchoolWithTeacher = do + -- > school <- node @School () mempty + -- > node @Teacher (onlyKey school) mempty , GraphulaContext , GraphulaNode - -- * Lower-level details - -- | - -- - -- These exports are likely to be removed from this module in a future - -- version. If you are using them, consider importing from their own modules. - -- + -- * Lower-level details + + -- | + -- + -- These exports are likely to be removed from this module in a future + -- version. If you are using them, consider importing from their own modules. , MonadGraphula - , MonadGraphulaBackend(..) - , MonadGraphulaFrontend(..) + , MonadGraphulaBackend (..) + , MonadGraphulaFrontend (..) , NodeOptions , GenerateKey , NoConstraint @@ -162,8 +162,11 @@ import Graphula.NoConstraint import Graphula.Node import System.Random (randomIO) import Test.HUnit.Lang - (FailureReason(..), HUnitFailure(..), formatFailureReason) -import Test.QuickCheck (Arbitrary(..)) + ( FailureReason (..) + , HUnitFailure (..) + , formatFailureReason + ) +import Test.QuickCheck (Arbitrary (..)) import Test.QuickCheck.Random (QCGen, mkQCGen) import UnliftIO.Exception (catch, throwIO) @@ -179,10 +182,9 @@ import UnliftIO.Exception (catch, throwIO) -- node @C (a, b) $ edit $ \n -> -- n { cc = "spanish" } -- @ --- type family GraphulaContext (m :: Type -> Type) (ts :: [Type]) :: Constraint where - GraphulaContext m '[] = MonadGraphula m - GraphulaContext m (t ': ts) = (GraphulaNode m t, GraphulaContext m ts) + GraphulaContext m '[] = MonadGraphula m + GraphulaContext m (t ': ts) = (GraphulaNode m t, GraphulaContext m ts) data Args backend n m = Args { dbRunner :: RunDB backend n m @@ -191,9 +193,9 @@ data Args backend n m = Args newtype RunDB backend n m = RunDB (forall b. ReaderT backend n b -> m b) -newtype GraphulaT n m a = - GraphulaT { runGraphulaT' :: ReaderT (Args SqlBackend n m) m a } - deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader (Args SqlBackend n m)) +newtype GraphulaT n m a = GraphulaT {runGraphulaT' :: ReaderT (Args SqlBackend n m) m a} + deriving newtype + (Functor, Applicative, Monad, MonadIO, MonadReader (Args SqlBackend n m)) instance MonadTrans (GraphulaT n) where lift = GraphulaT . lift @@ -212,9 +214,10 @@ instance (MonadIO m, MonadIO n) => MonadGraphulaFrontend (GraphulaT n m) where insert mKey n = do RunDB runDB <- asks dbRunner lift . runDB $ case mKey of - Nothing -> insertUnique n >>= \case - Nothing -> pure Nothing - Just key -> getEntity key + Nothing -> + insertUnique n >>= \case + Nothing -> pure Nothing + Just key -> getEntity key Just key -> do existingKey <- get key whenNothing existingKey $ do @@ -232,9 +235,11 @@ whenNothing Nothing f = f whenNothing (Just _) _ = pure Nothing runGraphulaT - :: (MonadUnliftIO m) - => Maybe Int -- ^ Optional seed - -> (forall b . ReaderT SqlBackend n b -> m b) -- ^ Database runner + :: MonadUnliftIO m + => Maybe Int + -- ^ Optional seed + -> (forall b. ReaderT SqlBackend n b -> m b) + -- ^ Database runner -> GraphulaT n m a -> m a runGraphulaT mSeed runDB action = do @@ -250,11 +255,11 @@ rethrowHUnitWith :: MonadIO m => String -> HUnitFailure -> m a rethrowHUnitWith message (HUnitFailure l r) = throwIO . HUnitFailure l . Reason $ message ++ "\n\n" ++ formatFailureReason r -type GraphulaNode m a - = ( HasDependencies a - , Logging m a - , PersistEntityBackend a ~ SqlBackend - , PersistEntity a - , Typeable a - , Arbitrary a - ) +type GraphulaNode m a = + ( HasDependencies a + , Logging m a + , PersistEntityBackend a ~ SqlBackend + , PersistEntity a + , Typeable a + , Arbitrary a + ) diff --git a/src/Graphula/Arbitrary.hs b/src/Graphula/Arbitrary.hs index e9eb02a..878786e 100644 --- a/src/Graphula/Arbitrary.hs +++ b/src/Graphula/Arbitrary.hs @@ -17,7 +17,6 @@ import Test.QuickCheck.Gen (unGen) -- This is akin to 'Test.QuickCheck.generate', but utilizing graphula's -- generation. The size passed to the generator is always 30; if you want -- another size then you should explicitly use 'Test.QuickCheck.resize'. --- generate :: (MonadIO m, MonadGraphulaBackend m) => Gen a -> m a generate gen = do genRef <- askGen diff --git a/src/Graphula/Class.hs b/src/Graphula/Class.hs index ad25a52..199de82 100644 --- a/src/Graphula/Class.hs +++ b/src/Graphula/Class.hs @@ -1,6 +1,6 @@ {-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE CPP #-} +{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} @@ -13,21 +13,23 @@ -- | Internal type class(es) for Graphula-related behaviors module Graphula.Class ( MonadGraphula - , MonadGraphulaFrontend(..) - , MonadGraphulaBackend(..) + , MonadGraphulaFrontend (..) + , MonadGraphulaBackend (..) , GraphulaSafeToInsert ) where import Control.Monad.IO.Class (MonadIO) import Data.IORef (IORef) import Data.Kind (Constraint, Type) -import Database.Persist (Entity(..), Key, PersistEntity, PersistEntityBackend) +import Database.Persist (Entity (..), Key, PersistEntity, PersistEntityBackend) import Database.Persist.Sql (SqlBackend) import Test.QuickCheck.Random (QCGen) #if MIN_VERSION_persistent(2,14,0) import Database.Persist.Class.PersistEntity (SafeToInsert) #endif +{- FOURMOLU_DISABLE -} + -- | A class that provides backwards compatibility with @persistent-2.14@ -- -- If you are using that version or above, then this is a class alias for @@ -49,12 +51,18 @@ instance #endif GraphulaSafeToInsert a -type MonadGraphula m - = (Monad m, MonadIO m, MonadGraphulaBackend m, MonadGraphulaFrontend m) +{- FOURMOLU_ENABLE -} + +type MonadGraphula m = + (Monad m, MonadIO m, MonadGraphulaBackend m, MonadGraphulaFrontend m) class MonadGraphulaFrontend m where insert - :: (PersistEntityBackend a ~ SqlBackend, PersistEntity a, Monad m, GraphulaSafeToInsert a) + :: ( PersistEntityBackend a ~ SqlBackend + , PersistEntity a + , Monad m + , GraphulaSafeToInsert a + ) => Maybe (Key a) -> a -> m (Maybe (Entity a)) diff --git a/src/Graphula/Dependencies.hs b/src/Graphula/Dependencies.hs index a5afe6a..c62a457 100644 --- a/src/Graphula/Dependencies.hs +++ b/src/Graphula/Dependencies.hs @@ -18,12 +18,12 @@ {-# LANGUAGE UndecidableSuperClasses #-} module Graphula.Dependencies - ( HasDependencies(..) - , Only(..) + ( HasDependencies (..) + , Only (..) , only - -- * Non-serial keys - , KeySourceType(..) + -- * Non-serial keys + , KeySourceType (..) , GenerateKey , generateKey ) where @@ -31,14 +31,14 @@ module Graphula.Dependencies import Prelude import Data.Kind (Constraint) -import Data.Proxy (Proxy(..)) +import Data.Proxy (Proxy (..)) import Database.Persist (Key) import GHC.Generics (Generic) -import GHC.TypeLits (ErrorMessage(..), TypeError) +import GHC.TypeLits (ErrorMessage (..), TypeError) import Generics.Eot (Eot, HasEot, fromEot, toEot) import Graphula.Dependencies.Generic import Graphula.NoConstraint -import Test.QuickCheck.Arbitrary (Arbitrary(..)) +import Test.QuickCheck.Arbitrary (Arbitrary (..)) import Test.QuickCheck.Gen (Gen) class HasDependencies a where @@ -63,9 +63,9 @@ class HasDependencies a where -- instance 'HasDependencies' Course where -- type Dependencies Course = (SchoolId, TeacherId) -- @ - -- type Dependencies a - type instance Dependencies _a = () + + type Dependencies _a = () -- | Specify the method for resolving a node's key -- @@ -79,9 +79,9 @@ class HasDependencies a where -- -- Most types will use 'SourceDefault' or 'SourceArbitrary'. Only use -- 'SourceExternal' if the key for a value is always defined externally. - -- type KeySource a :: KeySourceType - type instance KeySource _a = 'SourceDefault + + type KeySource _a = 'SourceDefault -- | Assign values from the 'Dependencies' collection to a value -- @@ -91,15 +91,19 @@ class HasDependencies a where -- -- The default, 'Generic'-based implementation will assign values by the order -- of the fields in the model's type. - -- dependsOn :: a -> Dependencies a -> a default dependsOn - :: - ( HasEot a - , HasEot (Dependencies a) - , GHasDependencies (Proxy a) (Proxy (Dependencies a)) (Eot a) (Eot (Dependencies a)) - ) - => a -> Dependencies a -> a + :: ( HasEot a + , HasEot (Dependencies a) + , GHasDependencies + (Proxy a) + (Proxy (Dependencies a)) + (Eot a) + (Eot (Dependencies a)) + ) + => a + -> Dependencies a + -> a dependsOn a dependencies = fromEot $ genericDependsOn @@ -109,29 +113,32 @@ class HasDependencies a where (toEot dependencies) -- | For entities that only have singular 'Dependencies' -newtype Only a = Only { fromOnly :: a } +newtype Only a = Only {fromOnly :: a} deriving stock (Eq, Show, Ord, Generic, Functor, Foldable, Traversable) only :: a -> Only a only = Only data KeySourceType - = SourceDefault - -- ^ Generate keys using the database's @DEFAULT@ strategy - | SourceArbitrary - -- ^ Generate keys using the 'Arbitrary' instance for the 'Key' - | SourceExternal - -- ^ Always explicitly pass an external key - -- - -- See 'nodeKeyed'. - -- + = -- | Generate keys using the database's @DEFAULT@ strategy + SourceDefault + | -- | Generate keys using the 'Arbitrary' instance for the 'Key' + SourceArbitrary + | -- | Always explicitly pass an external key + -- + -- See 'nodeKeyed'. + SourceExternal -- | Abstract constraint that some @a@ can generate a key -- -- This is part of ensuring better error messages. --- -class (GenerateKeyInternal (KeySource a) a, KeyConstraint (KeySource a) a) => GenerateKey a -instance (GenerateKeyInternal (KeySource a) a, KeyConstraint (KeySource a) a) => GenerateKey a +class + (GenerateKeyInternal (KeySource a) a, KeyConstraint (KeySource a) a) => + GenerateKey a + +instance + (GenerateKeyInternal (KeySource a) a, KeyConstraint (KeySource a) a) + => GenerateKey a class GenerateKeyInternal (s :: KeySourceType) a where type KeyConstraint s a :: Constraint @@ -158,34 +165,37 @@ instance GenerateKeyInternal 'SourceArbitrary a where -- • Change ‘KeySource X’ to 'SourceDefault or 'SourceArbitrary -- @ -- -instance TypeError - ( 'Text "Cannot generate a value of type " - ':<>: Quote ('ShowType a) - ':<>: 'Text " using " - ':<>: Quote ('Text "node") - ':<>: 'Text " since" - ':$$: 'Text "" - ':$$: 'Text " instance HasDependencies " - ':<>: 'ShowType a - ':<>: 'Text " where" - ':$$: 'Text " " - ':<>: 'Text "type KeySource " - ':<>: 'ShowType a - ':<>: 'Text " = " - ':<>: 'ShowType 'SourceExternal - ':$$: 'Text "" - ':$$: 'Text "Possible fixes include:" - ':$$: 'Text "• Use " - ':<>: Quote ('Text "nodeKeyed") - ':<>: 'Text " instead of " - ':<>: Quote ('Text "node") - ':$$: 'Text "• Change " - ':<>: Quote ('Text "KeySource " ':<>: 'ShowType a) - ':<>: 'Text " to " - ':<>: 'Text "'SourceDefault" - ':<>: 'Text " or " - ':<>: 'Text "'SourceArbitrary" - ) => GenerateKeyInternal 'SourceExternal a where +instance + TypeError + ( 'Text "Cannot generate a value of type " + ':<>: Quote ('ShowType a) + ':<>: 'Text " using " + ':<>: Quote ('Text "node") + ':<>: 'Text " since" + ':$$: 'Text "" + ':$$: 'Text " instance HasDependencies " + ':<>: 'ShowType a + ':<>: 'Text " where" + ':$$: 'Text " " + ':<>: 'Text "type KeySource " + ':<>: 'ShowType a + ':<>: 'Text " = " + ':<>: 'ShowType 'SourceExternal + ':$$: 'Text "" + ':$$: 'Text "Possible fixes include:" + ':$$: 'Text "• Use " + ':<>: Quote ('Text "nodeKeyed") + ':<>: 'Text " instead of " + ':<>: Quote ('Text "node") + ':$$: 'Text "• Change " + ':<>: Quote ('Text "KeySource " ':<>: 'ShowType a) + ':<>: 'Text " to " + ':<>: 'Text "'SourceDefault" + ':<>: 'Text " or " + ':<>: 'Text "'SourceArbitrary" + ) + => GenerateKeyInternal 'SourceExternal a + where type KeyConstraint 'SourceExternal a = NoConstraint a generateKey = error "unreachable" diff --git a/src/Graphula/Dependencies/Generic.hs b/src/Graphula/Dependencies/Generic.hs index c326def..ec0c509 100644 --- a/src/Graphula/Dependencies/Generic.hs +++ b/src/Graphula/Dependencies/Generic.hs @@ -11,12 +11,12 @@ -- | Machinery for the 'Generic'-based 'HasDependencies' instance module Graphula.Dependencies.Generic - ( GHasDependencies(..) + ( GHasDependencies (..) ) where import Data.Kind (Type) -import GHC.TypeLits (ErrorMessage(..), TypeError) -import Generics.Eot (Proxy(..), Void) +import GHC.TypeLits (ErrorMessage (..), TypeError) +import Generics.Eot (Proxy (..), Void) data Match t = NoMatch t @@ -24,31 +24,37 @@ data Match t type family DependenciesTypeInstance nodeTy depsTy where DependenciesTypeInstance nodeTy depsTy = - 'Text "‘type Dependencies " ':<>: 'ShowType nodeTy ':<>: - 'Text " = " ':<>: 'ShowType depsTy ':<>: 'Text "’" + 'Text "‘type Dependencies " + ':<>: 'ShowType nodeTy + ':<>: 'Text " = " + ':<>: 'ShowType depsTy + ':<>: 'Text "’" -- Walk through the fields of our node and match them up with fields from the dependencies. type family FindMatches nodeTy depsTy as ds :: [Match Type] where -- Excess dependencies FindMatches nodeTy depsTy () (d, _ds) = TypeError - ( 'Text "Excess dependency ‘" ':<>: 'ShowType d ':<>: - 'Text "’ in " ':$$: DependenciesTypeInstance nodeTy depsTy ':$$: - 'Text "Ordering of dependencies must match their occurrence in the target type ‘" ':<>: - 'ShowType nodeTy ':<>: 'Text "’" + ( 'Text "Excess dependency ‘" + ':<>: 'ShowType d + ':<>: 'Text "’ in " + ':$$: DependenciesTypeInstance nodeTy depsTy + ':$$: 'Text + "Ordering of dependencies must match their occurrence in the target type ‘" + ':<>: 'ShowType nodeTy + ':<>: 'Text "’" ) - -- No more fields or dependencies left FindMatches _nodeTy _depsTy () () = '[] - -- Fields left, but no more dependencies - FindMatches nodeTy depsTy (a, as) () = 'NoMatch a ': FindMatches nodeTy depsTy as () - + FindMatches nodeTy depsTy (a, as) () = + 'NoMatch a ': FindMatches nodeTy depsTy as () -- Field matches dependency, keep going - FindMatches nodeTy depsTy (a, as) (a, ds) = 'Match a ': FindMatches nodeTy depsTy as ds - + FindMatches nodeTy depsTy (a, as) (a, ds) = + 'Match a ': FindMatches nodeTy depsTy as ds -- Field does not match dependency, keep going - FindMatches nodeTy depsTy (a, as) (d, ds) = 'NoMatch a ': FindMatches nodeTy depsTy as (d, ds) + FindMatches nodeTy depsTy (a, as) (d, ds) = + 'NoMatch a ': FindMatches nodeTy depsTy as (d, ds) class GHasDependencies nodeTyProxy depsTyProxy node deps where genericDependsOn :: nodeTyProxy -> depsTyProxy -> node -> deps -> node @@ -65,11 +71,12 @@ instance {-# OVERLAPPING #-} GHasDependencies (Proxy nodeTy) (Proxy depsTy) Void -- to a datatype with no constructors instance {-# OVERLAPPABLE #-} - ( TypeError - ( 'Text "A datatype with no constructors can't use the dependencies in" ':$$: - DependenciesTypeInstance nodeTy depsTy + TypeError + ( 'Text "A datatype with no constructors can't use the dependencies in" + ':$$: DependenciesTypeInstance nodeTy depsTy ) - ) => GHasDependencies (Proxy nodeTy) (Proxy depsTy) Void (Either deps rest) where + => GHasDependencies (Proxy nodeTy) (Proxy depsTy) Void (Either deps rest) + where genericDependsOn _ _ _ _ = error "Impossible" -- This instance head only matches EoT representations of @@ -77,7 +84,13 @@ instance instance ( FindMatches nodeTy depsTy node deps ~ fields , GHasDependenciesRecursive (Proxy fields) node deps - ) => GHasDependencies (Proxy nodeTy) (Proxy depsTy) (Either node Void) (Either deps Void) where + ) + => GHasDependencies + (Proxy nodeTy) + (Proxy depsTy) + (Either node Void) + (Either deps Void) + where genericDependsOn _ _ (Left node) (Left deps) = Left (genericDependsOnRecursive (Proxy :: Proxy fields) node deps) genericDependsOn _ _ _ _ = error "Impossible" -- EoT never generates an actual `Right (x :: Void)` here @@ -85,52 +98,72 @@ instance -- This instance matches a sum type as both node and dependencies. -- We use this to report an error to the user. instance - ( TypeError - ( 'Text "Cannot automatically find dependencies for sum type in" ':$$: - DependenciesTypeInstance nodeTy depsTy + TypeError + ( 'Text "Cannot automatically find dependencies for sum type in" + ':$$: DependenciesTypeInstance nodeTy depsTy ) - ) => GHasDependencies (Proxy nodeTy) (Proxy depsTy) (Either left (Either right rest)) (Either deps Void) where + => GHasDependencies + (Proxy nodeTy) + (Proxy depsTy) + (Either left (Either right rest)) + (Either deps Void) + where genericDependsOn _ _ _ _ = error "Impossible" -- This instance matches a sum type as the node. -- This is also an error. instance - ( TypeError - ( 'Text "Cannot automatically use a sum type as dependencies in" ':$$: - DependenciesTypeInstance nodeTy depsTy + TypeError + ( 'Text "Cannot automatically use a sum type as dependencies in" + ':$$: DependenciesTypeInstance nodeTy depsTy ) - ) => GHasDependencies (Proxy nodeTy) (Proxy depsTy) (Either node Void) (Either left (Either right rest)) where + => GHasDependencies + (Proxy nodeTy) + (Proxy depsTy) + (Either node Void) + (Either left (Either right rest)) + where genericDependsOn _ _ _ _ = error "Impossible" -- This instance matches a sum type as the dependencies. -- This is also an error. instance - ( TypeError - ( 'Text "Cannot automatically find dependencies for sum type or use a sum type as a dependency in" ':$$: - DependenciesTypeInstance nodeTy depsTy + TypeError + ( 'Text + "Cannot automatically find dependencies for sum type or use a sum type as a dependency in" + ':$$: DependenciesTypeInstance nodeTy depsTy ) - ) => GHasDependencies (Proxy nodeTy) (Proxy depsTy) (Either left1 (Either right1 rest1)) (Either left2 (Either right2 rest2)) where + => GHasDependencies + (Proxy nodeTy) + (Proxy depsTy) + (Either left1 (Either right1 rest1)) + (Either left2 (Either right2 rest2)) + where genericDependsOn _ _ _ _ = error "Impossible" -- Don't let the user specify `Void` as a dependency instance - ( TypeError - ( 'Text "Use ‘()’ instead of ‘Void’ for datatypes with no dependencies in" ':$$: - DependenciesTypeInstance nodeTy depsTy + TypeError + ( 'Text "Use ‘()’ instead of ‘Void’ for datatypes with no dependencies in" + ':$$: DependenciesTypeInstance nodeTy depsTy ) - ) => GHasDependencies (Proxy nodeTy) (Proxy depsTy) node Void where + => GHasDependencies (Proxy nodeTy) (Proxy depsTy) node Void + where genericDependsOn _ _ _ _ = error "Impossible" instance ( a ~ dep , GHasDependenciesRecursive (Proxy fields) as deps - ) => GHasDependenciesRecursive (Proxy ('Match a ': fields)) (a, as) (dep, deps) where + ) + => GHasDependenciesRecursive (Proxy ('Match a ': fields)) (a, as) (dep, deps) + where genericDependsOnRecursive _ (_, as) (dep, deps) = (dep, genericDependsOnRecursive (Proxy :: Proxy fields) as deps) instance - ( GHasDependenciesRecursive (Proxy fields) as deps - ) => GHasDependenciesRecursive (Proxy ('NoMatch a ': fields)) (a, as) deps where + GHasDependenciesRecursive (Proxy fields) as deps + => GHasDependenciesRecursive (Proxy ('NoMatch a ': fields)) (a, as) deps + where genericDependsOnRecursive _ (a, as) deps = (a, genericDependsOnRecursive (Proxy :: Proxy fields) as deps) diff --git a/src/Graphula/Idempotent.hs b/src/Graphula/Idempotent.hs index ed8c295..3243db3 100644 --- a/src/Graphula/Idempotent.hs +++ b/src/Graphula/Idempotent.hs @@ -28,7 +28,7 @@ import Control.Monad.Reader (MonadReader, ReaderT, ask, runReaderT) import Control.Monad.Trans (MonadTrans, lift) import Data.Foldable (for_) import Data.IORef (IORef, modifyIORef', newIORef, readIORef) -import Database.Persist (Entity(..)) +import Database.Persist (Entity (..)) import Graphula.Class import UnliftIO.Exception (SomeException, catch, mask, throwIO) @@ -51,12 +51,15 @@ instance MonadUnliftIO m => MonadUnliftIO (GraphulaIdempotentT m) where instance MonadTrans GraphulaIdempotentT where lift = GraphulaIdempotentT . lift -instance (MonadIO m, MonadGraphulaFrontend m) => MonadGraphulaFrontend (GraphulaIdempotentT m) where +instance + (MonadIO m, MonadGraphulaFrontend m) + => MonadGraphulaFrontend (GraphulaIdempotentT m) + where insert mKey n = do finalizersRef <- ask mEnt <- lift $ insert mKey n - for_ (entityKey <$> mEnt) - $ \key -> liftIO $ modifyIORef' finalizersRef (remove key >>) + for_ (entityKey <$> mEnt) $ + \key -> liftIO $ modifyIORef' finalizersRef (remove key >>) pure mEnt remove = lift . remove @@ -64,9 +67,9 @@ runGraphulaIdempotentT :: MonadUnliftIO m => GraphulaIdempotentT m a -> m a runGraphulaIdempotentT action = mask $ \unmasked -> do finalizersRef <- liftIO . newIORef $ pure () x <- - unmasked - $ runReaderT (runGraphulaIdempotentT' action) finalizersRef - `catch` rollbackRethrow finalizersRef + unmasked $ + runReaderT (runGraphulaIdempotentT' action) finalizersRef + `catch` rollbackRethrow finalizersRef rollback finalizersRef $ pure x where rollback :: MonadIO m => IORef (m a) -> m b -> m b diff --git a/src/Graphula/Key.hs b/src/Graphula/Key.hs index 472e7b3..60c0a6e 100644 --- a/src/Graphula/Key.hs +++ b/src/Graphula/Key.hs @@ -12,8 +12,8 @@ module Graphula.Key ) where import Database.Persist -import GHC.TypeLits (ErrorMessage(..), TypeError) -import Graphula (Only(..), only) +import GHC.TypeLits (ErrorMessage (..), TypeError) +import Graphula (Only (..), only) class EntityKeys a where -- | Type-class for turning a tuple of 'Entity' into a tuple of 'Key' @@ -38,18 +38,21 @@ class EntityKeys a where -- @ -- -- The type class instances currently scale up 4-tuple 'Dependencies'. - -- type Keys a + keys :: a -> Keys a instance - ( TypeError - ( 'Text "Cannot use naked ‘" ':<>: 'ShowType (Entity a) ':<>: - 'Text "’ as argument to ‘keys’." ':$$: - 'Text "Did you mean ‘Only (" ':<>: - 'ShowType (Entity a) ':<>: 'Text ")’?" + TypeError + ( 'Text "Cannot use naked ‘" + ':<>: 'ShowType (Entity a) + ':<>: 'Text "’ as argument to ‘keys’." + ':$$: 'Text "Did you mean ‘Only (" + ':<>: 'ShowType (Entity a) + ':<>: 'Text ")’?" ) - ) => EntityKeys (Entity a) where + => EntityKeys (Entity a) + where type Keys (Entity a) = Key a keys = entityKey @@ -76,5 +79,7 @@ instance EntityKeys (Entity a, Entity b, Entity c) where -- brittany-disable-next-binding instance EntityKeys (Entity a, Entity b, Entity c, Entity d) where - type Keys (Entity a, Entity b, Entity c, Entity d) = (Key a, Key b, Key c, Key d) + type + Keys (Entity a, Entity b, Entity c, Entity d) = + (Key a, Key b, Key c, Key d) keys (a, b, c, d) = (entityKey a, entityKey b, entityKey c, entityKey d) diff --git a/src/Graphula/Logged.hs b/src/Graphula/Logged.hs index 8d41d7a..b56ac95 100644 --- a/src/Graphula/Logged.hs +++ b/src/Graphula/Logged.hs @@ -36,10 +36,13 @@ import Data.Text (Text, pack) import qualified Data.Text.IO as T import Graphula.Class import System.Directory (createDirectoryIfMissing, getTemporaryDirectory) -import System.IO (Handle, IOMode(..), hClose, openFile) +import System.IO (Handle, IOMode (..), hClose, openFile) import System.IO.Temp (openTempFile) import Test.HUnit.Lang - (FailureReason(..), HUnitFailure(..), formatFailureReason) + ( FailureReason (..) + , HUnitFailure (..) + , formatFailureReason + ) import UnliftIO.Exception (bracket, catch, throwIO) newtype GraphulaLoggedT m a = GraphulaLoggedT @@ -101,7 +104,7 @@ logFailUsing f graphLog hunitfailure = flip rethrowHUnitLogged hunitfailure =<< logGraphToHandle graphLog f logFailFile :: MonadIO m => FilePath -> IORef (Seq Text) -> HUnitFailure -> m a -logFailFile path = logFailUsing ((path, ) <$> openFile path WriteMode) +logFailFile path = logFailUsing ((path,) <$> openFile path WriteMode) logFailTemp :: MonadIO m => IORef (Seq Text) -> HUnitFailure -> m a logFailTemp = logFailUsing $ do @@ -110,14 +113,16 @@ logFailTemp = logFailUsing $ do openTempFile tmp "fail-.graphula" logGraphToHandle - :: (MonadIO m) => IORef (Seq Text) -> IO (FilePath, Handle) -> m FilePath -logGraphToHandle graphLog openHandle = liftIO $ bracket - openHandle - (hClose . snd) - (\(path, handle) -> do - nodes <- readIORef graphLog - path <$ traverse_ (T.hPutStrLn handle) nodes - ) + :: MonadIO m => IORef (Seq Text) -> IO (FilePath, Handle) -> m FilePath +logGraphToHandle graphLog openHandle = + liftIO $ + bracket + openHandle + (hClose . snd) + ( \(path, handle) -> do + nodes <- readIORef graphLog + path <$ traverse_ (T.hPutStrLn handle) nodes + ) rethrowHUnitLogged :: MonadIO m => FilePath -> HUnitFailure -> m a rethrowHUnitLogged path = diff --git a/src/Graphula/NoConstraint.hs b/src/Graphula/NoConstraint.hs index 91e95b8..c5371fd 100644 --- a/src/Graphula/NoConstraint.hs +++ b/src/Graphula/NoConstraint.hs @@ -5,7 +5,6 @@ -- Graphula accepts constraints for various uses. Frontends do not always -- utilize these constraints. 'NoConstraint' is a universal class that all types -- inhabit. It has no behavior and no additional constraints. --- module Graphula.NoConstraint ( NoConstraint ) where diff --git a/src/Graphula/Node.hs b/src/Graphula/Node.hs index f347302..9b8cf24 100644 --- a/src/Graphula/Node.hs +++ b/src/Graphula/Node.hs @@ -20,34 +20,33 @@ {-# LANGUAGE UndecidableSuperClasses #-} module Graphula.Node - ( - -- * Generating + ( -- * Generating node , nodeKeyed - -- * 'NodeOptions' + -- * 'NodeOptions' , NodeOptions , edit , ensure - -- * Exceptions - , GenerationFailure(..) + -- * Exceptions + , GenerationFailure (..) ) where import Prelude import Control.Monad (guard, (<=<)) -import Data.Proxy (Proxy(..)) +import Data.Proxy (Proxy (..)) import Data.Semigroup.Generic (gmappend, gmempty) import Data.Traversable (for) import Data.Typeable (TypeRep, Typeable, typeRep) -import Database.Persist (Entity(..), Key, PersistEntity, PersistEntityBackend) +import Database.Persist (Entity (..), Key, PersistEntity, PersistEntityBackend) import Database.Persist.Sql (SqlBackend) import GHC.Generics (Generic) import Graphula.Arbitrary import Graphula.Class import Graphula.Dependencies -import Test.QuickCheck (Arbitrary(..)) +import Test.QuickCheck (Arbitrary (..)) import UnliftIO.Exception (Exception, throwIO) -- | Options for generating an individual node @@ -59,11 +58,10 @@ import UnliftIO.Exception (Exception, throwIO) -- > a1 <- node @A () mempty -- > a2 <- node @A () $ edit $ \a -> a { someField = True } -- > a3 <- node @A () $ ensure $ (== True) . someField --- newtype NodeOptions a = NodeOptions { nodeOptionsEdit :: Kendo Maybe a } - deriving stock Generic + deriving stock (Generic) instance Semigroup (NodeOptions a) where (<>) = gmappend @@ -74,8 +72,8 @@ instance Monoid (NodeOptions a) where {-# INLINE mempty #-} -- | Like @'Endo'@ but uses Kliesli composition -newtype Kendo m a = Kendo { appKendo :: a -> m a } - deriving stock Generic +newtype Kendo m a = Kendo {appKendo :: a -> m a} + deriving stock (Generic) instance Monad m => Semigroup (Kendo m a) where Kendo f <> Kendo g = Kendo $ f <=< g @@ -88,23 +86,20 @@ instance Monad m => Monoid (Kendo m a) where -- | Modify the node after it's been generated -- -- > a <- node @A () $ edit $ \a -> a { someField = True } --- edit :: (a -> a) -> NodeOptions a -edit f = mempty { nodeOptionsEdit = Kendo $ Just . f } +edit f = mempty {nodeOptionsEdit = Kendo $ Just . f} -- | Require a node to satisfy the specified predicate -- -- > a <- node @A () $ ensure $ (== True) . someField -- -- N.B. ensuring a condition that is infrequently met can be innefficient. --- ensure :: (a -> Bool) -> NodeOptions a -ensure f = mempty { nodeOptionsEdit = Kendo $ \a -> a <$ guard (f a) } +ensure f = mempty {nodeOptionsEdit = Kendo $ \a -> a <$ guard (f a)} -- | Generate a node with a default (Database-provided) key -- -- > a <- node @A () mempty --- node :: forall a m . ( MonadGraphula m @@ -126,7 +121,6 @@ node = nodeImpl $ generate $ generateKey @(KeySource a) @a -- -- > let someKey = UUID.fromString "..." -- > a <- nodeKeyed @A someKey () mempty --- nodeKeyed :: forall a m . ( MonadGraphula m @@ -169,10 +163,10 @@ nodeImpl genKey dependencies NodeOptions {..} = attempt 100 10 $ do pure (mKey, hydrated) data GenerationFailure - = GenerationFailureMaxAttemptsToConstrain TypeRep - -- ^ Could not satisfy constraints defined using 'ensure' - | GenerationFailureMaxAttemptsToInsert TypeRep - -- ^ Could not satisfy database constraints on 'insert' + = -- | Could not satisfy constraints defined using 'ensure' + GenerationFailureMaxAttemptsToConstrain TypeRep + | -- | Could not satisfy database constraints on 'insert' + GenerationFailureMaxAttemptsToInsert TypeRep deriving stock (Show, Eq) instance Exception GenerationFailure @@ -195,14 +189,16 @@ attempt maxEdits maxInserts source = loop 0 0 loop numEdits numInserts | numEdits >= maxEdits = die GenerationFailureMaxAttemptsToConstrain | numInserts >= maxInserts = die GenerationFailureMaxAttemptsToInsert - | otherwise = source >>= \case - Nothing -> loop (succ numEdits) numInserts - -- ^ failed to edit, only increments this - Just (mKey, value) -> insert mKey value >>= \case - Nothing -> loop (succ numEdits) (succ numInserts) - -- ^ failed to insert, but also increments this. Are we - -- sure that's what we want? - Just a -> pure a + | otherwise = + source >>= \case + Nothing -> loop (succ numEdits) numInserts + -- ^ failed to edit, only increments this + Just (mKey, value) -> + insert mKey value >>= \case + Nothing -> loop (succ numEdits) (succ numInserts) + -- ^ failed to insert, but also increments this. Are we + -- sure that's what we want? + Just a -> pure a die :: (TypeRep -> GenerationFailure) -> m (Entity a) die e = throwIO $ e $ typeRep (Proxy :: Proxy a)