packages feed

hpack 0.21.2 → 0.22.0

raw patch · 26 files changed

+1309/−419 lines, 26 filesdep +http-clientdep +http-client-tlsdep +http-typesdep −HUnitPVP ok

version bump matches the API change (PVP)

Dependencies added: http-client, http-client-tls, http-types

Dependencies removed: HUnit

API changes (from Hackage documentation)

- Hpack.Config: instance (Data.Aeson.Types.FromJSON.FromJSON a, Hpack.UnknownFields.HasFieldNames a) => Data.Aeson.Types.FromJSON.FromJSON (Hpack.Config.ParseCommonOptions a)
- Hpack.Config: instance (Data.Aeson.Types.FromJSON.FromJSON a, Hpack.UnknownFields.HasFieldNames a) => Data.Aeson.Types.FromJSON.FromJSON (Hpack.Config.ParseConditionalSection a)
- Hpack.Config: instance (Data.Aeson.Types.FromJSON.FromJSON a, Hpack.UnknownFields.HasFieldNames a) => Data.Aeson.Types.FromJSON.FromJSON (Hpack.Config.ParseThenElse a)
- Hpack.Config: instance (Hpack.UnknownFields.HasFieldNames a, Hpack.UnknownFields.HasFieldNames b) => Hpack.UnknownFields.HasFieldNames (Hpack.Config.Product a b)
- Hpack.Config: instance GHC.Generics.Generic (Hpack.Config.CommonOptions a capture cSources jsSources)
- Hpack.Config: instance GHC.Generics.Generic (Hpack.Config.PackageConfig capture cSources jsSources)
- Hpack.Config: instance GHC.Generics.Generic (Hpack.Config.ThenElse a capture cSources jsSources)
- Hpack.Config: instance Hpack.UnknownFields.HasFieldNames (Hpack.Config.ParseCommonOptions a)
- Hpack.Config: instance Hpack.UnknownFields.HasFieldNames (Hpack.Config.ParseThenElse a)
- Hpack.Config: instance Hpack.UnknownFields.HasFieldNames Hpack.Config.Condition
- Hpack.Config: instance Hpack.UnknownFields.HasFieldNames Hpack.Config.CustomSetupSection
- Hpack.Config: instance Hpack.UnknownFields.HasFieldNames Hpack.Config.Empty
- Hpack.Config: instance Hpack.UnknownFields.HasFieldNames Hpack.Config.ExecutableSection
- Hpack.Config: instance Hpack.UnknownFields.HasFieldNames Hpack.Config.FlagSection
- Hpack.Config: instance Hpack.UnknownFields.HasFieldNames Hpack.Config.LibrarySection
- Hpack.Config: instance Hpack.UnknownFields.HasFieldNames Hpack.Config.ParsePackageConfig
+ Hpack.Config: [librarySignatures] :: Library -> [String]
+ Hpack.Config: instance (Data.Aeson.Types.FromJSON.FromJSON a, Hpack.Syntax.UnknownFields.HasFieldNames a) => Data.Aeson.Types.FromJSON.FromJSON (Hpack.Config.ParseCommonOptions a)
+ Hpack.Config: instance (Data.Aeson.Types.FromJSON.FromJSON a, Hpack.Syntax.UnknownFields.HasFieldNames a) => Data.Aeson.Types.FromJSON.FromJSON (Hpack.Config.ParseConditionalSection a)
+ Hpack.Config: instance (Data.Aeson.Types.FromJSON.FromJSON a, Hpack.Syntax.UnknownFields.HasFieldNames a) => Data.Aeson.Types.FromJSON.FromJSON (Hpack.Config.ParseThenElse a)
+ Hpack.Config: instance (GHC.Base.Monoid a, GHC.Base.Monoid b) => GHC.Base.Monoid (Hpack.Config.Product a b)
+ Hpack.Config: instance (GHC.Base.Monoid cSources, GHC.Base.Monoid jsSources) => GHC.Base.Monoid (Hpack.Config.CommonOptions capture cSources jsSources a)
+ Hpack.Config: instance (Hpack.Syntax.UnknownFields.HasFieldNames a, Hpack.Syntax.UnknownFields.HasFieldNames b) => Hpack.Syntax.UnknownFields.HasFieldNames (Hpack.Config.Product a b)
+ Hpack.Config: instance Data.Aeson.Types.FromJSON.FromJSON (Hpack.Config.DefaultsConfig Hpack.Syntax.UnknownFields.CaptureUnknownFields)
+ Hpack.Config: instance Data.Aeson.Types.FromJSON.FromJSON Hpack.Config.Cond
+ Hpack.Config: instance Data.Foldable.Foldable (Hpack.Config.Product a)
+ Hpack.Config: instance Data.Traversable.Traversable (Hpack.Config.Product a)
+ Hpack.Config: instance GHC.Base.Functor (Hpack.Config.Product a)
+ Hpack.Config: instance GHC.Base.Functor capture => GHC.Base.Functor (Hpack.Config.CommonOptions capture cSources jsSources)
+ Hpack.Config: instance GHC.Base.Functor capture => GHC.Base.Functor (Hpack.Config.ConditionalSection capture cSources jsSources)
+ Hpack.Config: instance GHC.Base.Functor capture => GHC.Base.Functor (Hpack.Config.ThenElse capture cSources jsSources)
+ Hpack.Config: instance GHC.Base.Monoid Hpack.Config.Empty
+ Hpack.Config: instance GHC.Base.Monoid Hpack.Config.ExecutableSection
+ Hpack.Config: instance GHC.Base.Monoid Hpack.Config.LibrarySection
+ Hpack.Config: instance GHC.Classes.Eq Hpack.Config.Cond
+ Hpack.Config: instance GHC.Generics.Generic (Hpack.Config.CommonOptions capture cSources jsSources a)
+ Hpack.Config: instance GHC.Generics.Generic (Hpack.Config.DefaultsConfig capture)
+ Hpack.Config: instance GHC.Generics.Generic (Hpack.Config.PackageConfig_ library executable capture cSources jsSources)
+ Hpack.Config: instance GHC.Generics.Generic (Hpack.Config.ThenElse capture cSources jsSources a)
+ Hpack.Config: instance GHC.Show.Show Hpack.Config.Cond
+ Hpack.Config: instance Hpack.Syntax.UnknownFields.HasFieldNames (Hpack.Config.DefaultsConfig a)
+ Hpack.Config: instance Hpack.Syntax.UnknownFields.HasFieldNames (Hpack.Config.ParseCommonOptions a)
+ Hpack.Config: instance Hpack.Syntax.UnknownFields.HasFieldNames (Hpack.Config.ParseThenElse a)
+ Hpack.Config: instance Hpack.Syntax.UnknownFields.HasFieldNames Hpack.Config.Condition
+ Hpack.Config: instance Hpack.Syntax.UnknownFields.HasFieldNames Hpack.Config.CustomSetupSection
+ Hpack.Config: instance Hpack.Syntax.UnknownFields.HasFieldNames Hpack.Config.Empty
+ Hpack.Config: instance Hpack.Syntax.UnknownFields.HasFieldNames Hpack.Config.ExecutableSection
+ Hpack.Config: instance Hpack.Syntax.UnknownFields.HasFieldNames Hpack.Config.FlagSection
+ Hpack.Config: instance Hpack.Syntax.UnknownFields.HasFieldNames Hpack.Config.LibrarySection
+ Hpack.Config: instance Hpack.Syntax.UnknownFields.HasFieldNames Hpack.Config.ParsePackageConfig
- Hpack.Config: Library :: Maybe Bool -> [String] -> [String] -> [String] -> Library
+ Hpack.Config: Library :: Maybe Bool -> [String] -> [String] -> [String] -> [String] -> Library
- Hpack.Config: readPackageConfig :: FilePath -> IO (Either String ([String], Package))
+ Hpack.Config: readPackageConfig :: FilePath -> FilePath -> IO (Either String (Package, [String]))

Files

CHANGELOG.md view
@@ -1,3 +1,11 @@+## next+  - Add support for `defaults`+  - Add `--numeric-version`+  - Add support for `signatures`+  - `extra-doc-files` requires setting `cabal-version` to at least+    1.18; this is now done properly.+  - Accept bool for `condition` (see #230)+ ## Changes in 0.21.2   - Fix a bug in module inference for conditionals (see #236)   - Add support for `extra-doc-files`.
hpack.cabal view
@@ -1,11 +1,11 @@--- This file has been generated from package.yaml by hpack version 0.21.0.+-- This file has been generated from package.yaml by hpack version 0.21.2. -- -- see: https://github.com/sol/hpack ----- hash: 717efcb68d31a77fdd5eec9b427eaba95d5ca99c5d2aedf570e483461daa01ca+-- hash: ed684f1c6e2ea1bdc1674463da6eeef99a1dd9a3fac6bf2ce309d3d675879539  name:           hpack-version:        0.21.2+version:        0.22.0 synopsis:       An alternative format for Haskell packages description:    See README at <https://github.com/sol/hpack#readme> category:       Development@@ -40,6 +40,9 @@     , deepseq     , directory     , filepath+    , http-client+    , http-client-tls+    , http-types     , pretty     , scientific     , text@@ -53,13 +56,17 @@       Hpack.Yaml   other-modules:       Hpack.CabalFile+      Hpack.Defaults       Hpack.Dependency       Hpack.FormattingHints-      Hpack.GenericsUtil       Hpack.Haskell       Hpack.Options       Hpack.Render-      Hpack.UnknownFields+      Hpack.Syntax+      Hpack.Syntax.GenericsUtil+      Hpack.Syntax.Git+      Hpack.Syntax.UnknownFields+      Hpack.Syntax.Util       Hpack.Utf8       Hpack.Util       Paths_hpack@@ -83,6 +90,9 @@     , directory     , filepath     , hpack+    , http-client+    , http-client-tls+    , http-types     , pretty     , scientific     , text@@ -104,7 +114,6 @@   build-depends:       Cabal     , Glob-    , HUnit >=1.6.0.0     , QuickCheck     , aeson >=0.11     , base >=4.8 && <5@@ -116,6 +125,9 @@     , directory     , filepath     , hspec ==2.*+    , http-client+    , http-client-tls+    , http-types     , interpolate     , mockery >=0.3     , pretty@@ -130,27 +142,35 @@       Helper       Hpack.CabalFileSpec       Hpack.ConfigSpec+      Hpack.DefaultsSpec       Hpack.DependencySpec       Hpack.FormattingHintsSpec-      Hpack.GenericsUtilSpec       Hpack.HaskellSpec       Hpack.OptionsSpec       Hpack.RenderSpec       Hpack.RunSpec+      Hpack.Syntax.GenericsUtilSpec+      Hpack.Syntax.GitSpec+      Hpack.Syntax.UtilSpec+      Hpack.SyntaxSpec       Hpack.Utf8Spec       Hpack.UtilSpec       HpackSpec       Hpack       Hpack.CabalFile       Hpack.Config+      Hpack.Defaults       Hpack.Dependency       Hpack.FormattingHints-      Hpack.GenericsUtil       Hpack.Haskell       Hpack.Options       Hpack.Render       Hpack.Run-      Hpack.UnknownFields+      Hpack.Syntax+      Hpack.Syntax.GenericsUtil+      Hpack.Syntax.Git+      Hpack.Syntax.UnknownFields+      Hpack.Syntax.Util       Hpack.Utf8       Hpack.Util       Hpack.Yaml
src/Hpack.hs view
@@ -50,6 +50,7 @@   args <- getArgs   case parseOptions args of     PrintVersion -> putStrLn (programVersion version)+    PrintNumericVersion -> putStrLn (Version.showVersion version)     Help -> printHelp     Run options -> case options of       Options _verbose _force True dir -> hpackStdOut dir
src/Hpack/Config.hs view
@@ -1,19 +1,16 @@-{-# LANGUAGE DefaultSignatures #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveTraversable #-}-{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE FlexibleContexts #-} module Hpack.Config (   packageConfig , readPackageConfig@@ -42,6 +39,7 @@ , pathsModuleFromPackageName , determineModules , BuildType(..)+, Cond(..)  , LibrarySection(..) , fromLibrarySectionInConditional@@ -49,8 +47,8 @@ ) where  import           Control.Applicative+import           Control.Arrow ((>>>)) import           Control.Monad-import           Data.Aeson.Types import           Data.Data import           Data.Bifunctor import           Data.Bifoldable@@ -58,24 +56,28 @@ import           Data.Map.Lazy (Map) import qualified Data.Map.Lazy as Map import qualified Data.HashMap.Lazy as HashMap-import           Data.List (nub, (\\), sortBy)+import           Data.List (nub, (\\), sortBy, intercalate) import           Data.Maybe import           Data.Monoid hiding (Product) import           Data.Ord import           Data.String import           Data.Text (Text) import qualified Data.Text as T-import           GHC.Generics (Generic, Rep) import           System.Directory import           System.FilePath import           Data.Functor.Identity+import           Control.Monad.Trans.Class import           Control.Monad.Trans.Writer+import           Control.Monad.Trans.Except import           Control.Monad.IO.Class -import           Hpack.GenericsUtil-import           Hpack.UnknownFields-import           Hpack.Util-import           Hpack.Yaml+import           Hpack.Syntax.Util+import           Hpack.Syntax.UnknownFields+import           Hpack.Syntax+import           Hpack.Util hiding (expandGlobs)+import qualified Hpack.Util as Util+import           Hpack.Defaults+import qualified Hpack.Yaml as Yaml import           Hpack.Dependency  package :: String -> String -> Package@@ -141,16 +143,6 @@ packageConfig :: FilePath packageConfig = "package.yaml" -#if MIN_VERSION_aeson(1,0,0)-genericParseJSON_ :: forall a d m. (GFromJSON Zero (Rep a), HasTypeName a d m) => Value -> Parser a-#else-genericParseJSON_ :: forall a d m. (GFromJSON (Rep a), HasTypeName a d m) => Value -> Parser a-#endif-genericParseJSON_ = genericParseJSON defaultOptions {fieldLabelModifier = hyphenize name}-  where-    name :: String-    name = typeName (Proxy :: Proxy a)- data CustomSetupSection = CustomSetupSection {   customSetupSectionDependencies :: Maybe Dependencies } deriving (Eq, Show, Generic)@@ -158,37 +150,49 @@ instance HasFieldNames CustomSetupSection  instance FromJSON CustomSetupSection where-  parseJSON = genericParseJSON_+  parseJSON = genericParseJSON  data LibrarySection = LibrarySection {   librarySectionExposed :: Maybe Bool , librarySectionExposedModules :: Maybe (List String) , librarySectionOtherModules :: Maybe (List String) , librarySectionReexportedModules :: Maybe (List String)+, librarySectionSignatures :: Maybe (List String) } deriving (Eq, Show, Generic) -emptyLibrarySection :: LibrarySection-emptyLibrarySection = LibrarySection Nothing Nothing Nothing Nothing+instance Monoid LibrarySection where+  mempty = LibrarySection Nothing Nothing Nothing Nothing Nothing+  mappend a b = LibrarySection {+      librarySectionExposed = librarySectionExposed b <|> librarySectionExposed a+    , librarySectionExposedModules = librarySectionExposedModules a <> librarySectionExposedModules b+    , librarySectionOtherModules = librarySectionOtherModules a <> librarySectionOtherModules b+    , librarySectionReexportedModules = librarySectionReexportedModules a <> librarySectionReexportedModules b+    , librarySectionSignatures = librarySectionSignatures a <> librarySectionSignatures b+    }  instance HasFieldNames LibrarySection  instance FromJSON LibrarySection where-  parseJSON = genericParseJSON_+  parseJSON = genericParseJSON  data ExecutableSection = ExecutableSection {   executableSectionMain :: Maybe FilePath , executableSectionOtherModules :: Maybe (List String) } deriving (Eq, Show, Generic) -emptyExecutableSection :: ExecutableSection-emptyExecutableSection = ExecutableSection Nothing Nothing+instance Monoid ExecutableSection where+  mempty = ExecutableSection Nothing Nothing+  mappend a b = ExecutableSection {+      executableSectionMain = executableSectionMain b <|> executableSectionMain a+    , executableSectionOtherModules = executableSectionOtherModules a <> executableSectionOtherModules b+    }  instance HasFieldNames ExecutableSection  instance FromJSON ExecutableSection where-  parseJSON = genericParseJSON_+  parseJSON = genericParseJSON -data CommonOptions a capture cSources jsSources = CommonOptions {+data CommonOptions capture cSources jsSources a = CommonOptions {   commonOptionsSourceDirs :: Maybe (List FilePath) , commonOptionsDependencies :: Maybe Dependencies , commonOptionsPkgConfigDependencies :: Maybe (List String)@@ -209,16 +213,66 @@ , commonOptionsInstallIncludes :: Maybe (List FilePath) , commonOptionsLdOptions :: Maybe (List LdOption) , commonOptionsBuildable :: Maybe Bool-, commonOptionsWhen :: Maybe (List (ConditionalSection a capture cSources jsSources))+, commonOptionsWhen :: Maybe (List (ConditionalSection capture cSources jsSources a)) , commonOptionsBuildTools :: Maybe Dependencies-} deriving Generic+} deriving (Functor, Generic) -type ParseCommonOptions a = CommonOptions a CaptureUnknownFields ParseCSources ParseJsSources+instance (Monoid cSources, Monoid jsSources) => Monoid (CommonOptions capture cSources jsSources a) where+  mempty = CommonOptions {+    commonOptionsSourceDirs = Nothing+  , commonOptionsDependencies = Nothing+  , commonOptionsPkgConfigDependencies = Nothing+  , commonOptionsDefaultExtensions = Nothing+  , commonOptionsOtherExtensions = Nothing+  , commonOptionsGhcOptions = Nothing+  , commonOptionsGhcProfOptions = Nothing+  , commonOptionsGhcjsOptions = Nothing+  , commonOptionsCppOptions = Nothing+  , commonOptionsCcOptions = Nothing+  , commonOptionsCSources = mempty+  , commonOptionsJsSources = mempty+  , commonOptionsExtraLibDirs = Nothing+  , commonOptionsExtraLibraries = Nothing+  , commonOptionsExtraFrameworksDirs = Nothing+  , commonOptionsFrameworks = Nothing+  , commonOptionsIncludeDirs = Nothing+  , commonOptionsInstallIncludes = Nothing+  , commonOptionsLdOptions = Nothing+  , commonOptionsBuildable = Nothing+  , commonOptionsWhen = Nothing+  , commonOptionsBuildTools = Nothing+  }+  mappend a b = CommonOptions {+    commonOptionsSourceDirs = commonOptionsSourceDirs a <> commonOptionsSourceDirs b+  , commonOptionsDependencies = commonOptionsDependencies b <> commonOptionsDependencies a+  , commonOptionsPkgConfigDependencies = commonOptionsPkgConfigDependencies a <> commonOptionsPkgConfigDependencies b+  , commonOptionsDefaultExtensions = commonOptionsDefaultExtensions a <> commonOptionsDefaultExtensions b+  , commonOptionsOtherExtensions = commonOptionsOtherExtensions a <> commonOptionsOtherExtensions b+  , commonOptionsGhcOptions = commonOptionsGhcOptions a <> commonOptionsGhcOptions b+  , commonOptionsGhcProfOptions = commonOptionsGhcProfOptions a <> commonOptionsGhcProfOptions b+  , commonOptionsGhcjsOptions = commonOptionsGhcjsOptions a <> commonOptionsGhcjsOptions b+  , commonOptionsCppOptions = commonOptionsCppOptions a <> commonOptionsCppOptions b+  , commonOptionsCcOptions = commonOptionsCcOptions a <> commonOptionsCcOptions b+  , commonOptionsCSources = commonOptionsCSources a <> commonOptionsCSources b+  , commonOptionsJsSources = commonOptionsJsSources a <> commonOptionsJsSources b+  , commonOptionsExtraLibDirs = commonOptionsExtraLibDirs a <> commonOptionsExtraLibDirs b+  , commonOptionsExtraLibraries = commonOptionsExtraLibraries a <> commonOptionsExtraLibraries b+  , commonOptionsExtraFrameworksDirs = commonOptionsExtraFrameworksDirs a <> commonOptionsExtraFrameworksDirs b+  , commonOptionsFrameworks = commonOptionsFrameworks a <> commonOptionsFrameworks b+  , commonOptionsIncludeDirs = commonOptionsIncludeDirs a <> commonOptionsIncludeDirs b+  , commonOptionsInstallIncludes = commonOptionsInstallIncludes a <> commonOptionsInstallIncludes b+  , commonOptionsLdOptions = commonOptionsLdOptions a <> commonOptionsLdOptions b+  , commonOptionsBuildable = commonOptionsBuildable b <|> commonOptionsBuildable a+  , commonOptionsWhen = commonOptionsWhen a <> commonOptionsWhen b+  , commonOptionsBuildTools = commonOptionsBuildTools b <> commonOptionsBuildTools a+  } +type ParseCommonOptions = CommonOptions CaptureUnknownFields ParseCSources ParseJsSources+ instance HasFieldNames (ParseCommonOptions a)  instance (FromJSON a, HasFieldNames a) => FromJSON (ParseCommonOptions a) where-  parseJSON = genericParseJSON_+  parseJSON = genericParseJSON  type ParseCSources = Maybe (List FilePath) type ParseJsSources = Maybe (List FilePath)@@ -226,7 +280,7 @@ type CSources = [FilePath] type JsSources = [FilePath] -type WithCommonOptions a capture cSources jsSources = Product (CommonOptions a capture cSources jsSources) a+type WithCommonOptions capture cSources jsSources a = Product (CommonOptions capture cSources jsSources a) a  data Traverse m capture capture_ cSources cSources_ jsSources jsSources_ = Traverse {   traverseCapture :: forall a. capture a -> m (capture_ a)@@ -234,12 +288,24 @@ , traverseJsSources :: jsSources -> m jsSources_ } +defaultTraverse :: Applicative m => Traverse m capture capture cSources cSources jsSources jsSources+defaultTraverse = Traverse {+  traverseCapture = pure+, traverseCSources = pure+, traverseJsSources = pure+}+ type Traversal t = forall m capture capture_ cSources cSources_ jsSources jsSources_. (Monad m, Traversable capture_)   => Traverse m capture capture_ cSources cSources_ jsSources jsSources_   -> t capture cSources jsSources   -> m (t capture_ cSources_ jsSources_) -traverseCommonOptions :: Traversal (CommonOptions a)+type Traversal_ t = forall m capture capture_ cSources cSources_ jsSources jsSources_ a. (Monad m, Traversable capture_)+  => Traverse m capture capture_ cSources cSources_ jsSources jsSources_+  -> t capture cSources jsSources a+  -> m (t capture_ cSources_ jsSources_ a)++traverseCommonOptions :: Traversal_ CommonOptions traverseCommonOptions t@Traverse{..} c@CommonOptions{..} = do   cSources <- traverseCSources commonOptionsCSources   jsSources <- traverseJsSources commonOptionsJsSources@@ -250,23 +316,27 @@     , commonOptionsWhen = xs     } -traverseConditionalSection :: Traversal (ConditionalSection a)+traverseConditionalSection :: Traversal_ ConditionalSection traverseConditionalSection t@Traverse{..} = \ case-  ThenElseConditional c -> ThenElseConditional <$> (traverseCapture c >>= traverse (traverseThenElse t))+  ThenElseConditional c -> ThenElseConditional <$> (traverseCapture c >>= traverse (bitraverse (traverseThenElse t) return))   FlatConditional c -> FlatConditional <$> (traverseCapture c >>= traverse (bitraverse (traverseWithCommonOptions t) return)) -traverseThenElse :: Traversal (ThenElse a)+traverseThenElse :: Traversal_ ThenElse traverseThenElse t@Traverse{..} c@ThenElse{..} = do   then_ <- traverseCapture thenElseThen >>= traverse (traverseWithCommonOptions t)   else_ <- traverseCapture thenElseElse >>= traverse (traverseWithCommonOptions t)   return c{thenElseThen = then_, thenElseElse = else_} -traverseWithCommonOptions :: Traversal (WithCommonOptions a)+traverseWithCommonOptions :: Traversal_ WithCommonOptions traverseWithCommonOptions t = bitraverse (traverseCommonOptions t) return  data Product a b = Product a b-  deriving (Eq, Show)+  deriving (Eq, Show, Functor, Foldable, Traversable) +instance (Monoid a, Monoid b) => Monoid (Product a b) where+  mempty = Product mempty mempty+  Product a1 b1 `mappend` Product a2 b2 = Product (a1 <> a2) (b1 <> b2)+ instance Bifunctor Product where   bimap fa fb (Product a b) = Product (fa a) (fb b) @@ -287,12 +357,17 @@        ignoreUnderscoredUnknownFields (Proxy :: Proxy a)     || ignoreUnderscoredUnknownFields (Proxy :: Proxy b) -data ConditionalSection a capture cSources jsSources =-    ThenElseConditional (capture (ThenElse a capture cSources jsSources))-  | FlatConditional (capture (Product (WithCommonOptions a capture cSources jsSources) Condition))+data ConditionalSection capture cSources jsSources a =+    ThenElseConditional (capture (Product (ThenElse capture cSources jsSources a) Condition))+  | FlatConditional (capture (Product (WithCommonOptions capture cSources jsSources a) Condition)) -type ParseConditionalSection a = ConditionalSection a CaptureUnknownFields ParseCSources ParseJsSources+instance Functor capture => Functor (ConditionalSection capture cSources jsSources) where+  fmap f = \ case+    ThenElseConditional c -> ThenElseConditional (first (fmap f) <$> c)+    FlatConditional c -> FlatConditional (first (bimap (fmap f) f) <$> c) +type ParseConditionalSection = ConditionalSection CaptureUnknownFields ParseCSources ParseJsSources+ instance (FromJSON a, HasFieldNames a) => FromJSON (ParseConditionalSection a) where   parseJSON v     | hasKey "then" v || hasKey "else" v = ThenElseConditional <$> parseJSON v@@ -303,30 +378,48 @@ hasKey _ _ = False  newtype Condition = Condition {-  conditionCondition :: String+  _conditionCondition :: Cond } deriving (Eq, Show, Generic)  instance FromJSON Condition where-  parseJSON = genericParseJSON_+  parseJSON = genericParseJSON  instance HasFieldNames Condition -data ThenElse a capture cSources jsSources = ThenElse {-  _thenElseCondition :: String-, thenElseThen :: capture (WithCommonOptions a capture cSources jsSources)-, thenElseElse :: capture (WithCommonOptions a capture cSources jsSources)+newtype Cond = Cond String+  deriving (Eq, Show)++instance FromJSON Cond where+  parseJSON v = case v of+    String _ -> Cond <$> parseJSON v+    Bool True -> return (Cond "true")+    Bool False -> return (Cond "false")+    _ -> typeMismatch "Boolean or String" v++data ThenElse capture cSources jsSources a = ThenElse {+  thenElseThen :: capture (WithCommonOptions capture cSources jsSources a)+, thenElseElse :: capture (WithCommonOptions capture cSources jsSources a) } deriving Generic -type ParseThenElse a = ThenElse a CaptureUnknownFields ParseCSources ParseJsSources+instance Functor capture => Functor (ThenElse capture cSources jsSources) where+  fmap f c@ThenElse{..} = c{thenElseThen = map_ thenElseThen, thenElseElse = map_ thenElseElse}+    where+      map_ = fmap (bimap (fmap f) f) +type ParseThenElse = ThenElse CaptureUnknownFields ParseCSources ParseJsSources+ instance HasFieldNames (ParseThenElse a)  instance (FromJSON a, HasFieldNames a) => FromJSON (ParseThenElse a) where-  parseJSON = genericParseJSON_+  parseJSON = genericParseJSON  data Empty = Empty   deriving (Eq, Show) +instance Monoid Empty where+  mempty = Empty+  mappend Empty Empty = Empty+ instance FromJSON Empty where   parseJSON _ = return Empty @@ -349,9 +442,21 @@     "Custom"    -> return Custom     _           -> fail "build-type must be one of: Simple, Configure, Make, Custom" -type SectionConfig a capture cSources jsSources = capture (WithCommonOptions a capture cSources jsSources)+type SectionConfigWithDefaluts capture cSources jsSources a = capture (Product (DefaultsConfig capture) (WithCommonOptions capture cSources jsSources a)) -data PackageConfig capture cSources jsSources = PackageConfig {+type PackageConfigWithDefaults capture cSources jsSources = PackageConfig_+  (SectionConfigWithDefaluts capture cSources jsSources LibrarySection)+  (SectionConfigWithDefaluts capture cSources jsSources ExecutableSection)+  capture cSources jsSources++type SectionConfig capture cSources jsSources a = capture (WithCommonOptions capture cSources jsSources a)++type PackageConfig capture cSources jsSources = PackageConfig_+  (SectionConfig capture cSources jsSources LibrarySection)+  (SectionConfig capture cSources jsSources ExecutableSection)+  capture cSources jsSources++data PackageConfig_ library executable capture cSources jsSources = PackageConfig {   packageConfigName :: Maybe String , packageConfigVersion :: Maybe String , packageConfigSynopsis :: Maybe String@@ -374,14 +479,23 @@ , packageConfigGithub :: Maybe Text , packageConfigGit :: Maybe String , packageConfigCustomSetup :: Maybe (capture CustomSetupSection)-, packageConfigLibrary :: Maybe (SectionConfig LibrarySection capture cSources jsSources)-, packageConfigInternalLibraries :: Maybe (Map String (SectionConfig LibrarySection capture cSources jsSources))-, packageConfigExecutable :: Maybe (SectionConfig ExecutableSection capture cSources jsSources)-, packageConfigExecutables :: Maybe (Map String (SectionConfig ExecutableSection capture cSources jsSources))-, packageConfigTests :: Maybe (Map String (SectionConfig ExecutableSection capture cSources jsSources))-, packageConfigBenchmarks :: Maybe (Map String (SectionConfig ExecutableSection capture cSources jsSources))+, packageConfigLibrary :: Maybe library+, packageConfigInternalLibraries :: Maybe (Map String library)+, packageConfigExecutable :: Maybe executable+, packageConfigExecutables :: Maybe (Map String executable)+, packageConfigTests :: Maybe (Map String executable)+, packageConfigBenchmarks :: Maybe (Map String executable) } deriving Generic +data DefaultsConfig capture = DefaultsConfig {+  defaultsConfigDefaults :: Maybe (List (capture Defaults))+} deriving Generic++instance HasFieldNames (DefaultsConfig a)++instance FromJSON (DefaultsConfig CaptureUnknownFields) where+  parseJSON = genericParseJSON+ traversePackageConfig :: Traversal PackageConfig traversePackageConfig t@Traverse{..} p@PackageConfig{..} = do   flags <- traverse (traverse traverseCapture) packageConfigFlags@@ -405,16 +519,20 @@   where     traverseNamedConfigs = traverse . traverse . traverseSectionConfig -traverseSectionConfig :: Traversal (SectionConfig p)+traverseSectionConfig :: Traversal_ SectionConfig traverseSectionConfig t = traverseCapture t >=> traverse (traverseWithCommonOptions t) -type ParsePackageConfig = PackageConfig CaptureUnknownFields ParseCSources ParseJsSources+traverseSectionConfigWithDefaluts :: Traversal_ SectionConfigWithDefaluts+traverseSectionConfigWithDefaluts t =+  traverseCapture t >=> traverse (bitraverse (traverseDefaultsConfig t) (traverseWithCommonOptions t)) +type ParsePackageConfig = PackageConfigWithDefaults CaptureUnknownFields ParseCSources ParseJsSources+ instance HasFieldNames ParsePackageConfig where   ignoreUnderscoredUnknownFields _ = True  instance FromJSON ParsePackageConfig where-  parseJSON value = handleNullValues <$> genericParseJSON_ value+  parseJSON value = handleNullValues <$> genericParseJSON value     where       handleNullValues :: ParsePackageConfig -> ParsePackageConfig       handleNullValues =@@ -433,15 +551,18 @@   where     p = parseJSON >=> (.: fromString name) -readPackageConfig :: FilePath -> IO (Either String ([String], Package))-readPackageConfig file = do-  r <- decodeYaml file-  case r of-    Left err -> return (Left err)-    Right config -> do-      dir <- takeDirectory <$> canonicalizePath file-      Right <$> toPackage dir config+type Warnings m = WriterT [String] m+type Errors = ExceptT String +decodeYaml :: FromJSON a => FilePath -> Warnings (Errors IO) a+decodeYaml = lift . ExceptT . Yaml.decodeYaml++readPackageConfig :: FilePath -> FilePath -> IO (Either String (Package, [String]))+readPackageConfig userDataDir file = runExceptT $ runWriterT $ do+  config <- decodeYaml file+  dir <- liftIO $ takeDirectory <$> canonicalizePath file+  toPackage userDataDir dir config+ data Package = Package {   packageName :: String , packageVersion :: String@@ -480,6 +601,7 @@ , libraryExposedModules :: [String] , libraryOtherModules :: [String] , libraryReexportedModules :: [String]+, librarySignatures :: [String] } deriving (Eq, Show)  data Executable = Executable {@@ -528,7 +650,7 @@ instance HasFieldNames FlagSection  instance FromJSON FlagSection where-  parseJSON = genericParseJSON_+  parseJSON = genericParseJSON  data Flag = Flag {   flagName :: String@@ -546,46 +668,123 @@ } deriving (Eq, Show)  type Config capture cSources jsSources =-  Product (CommonOptions Empty capture cSources jsSources) (PackageConfig capture cSources jsSources)+  Product (CommonOptions capture cSources jsSources Empty) (PackageConfig capture cSources jsSources)  traverseConfig :: Traversal Config traverseConfig t = bitraverse (traverseCommonOptions t) (traversePackageConfig t) -type ParseConfig = CaptureUnknownFields (Config CaptureUnknownFields ParseCSources ParseJsSources)+type ConfigWithDefaults capture = Product+  (CommonOptionsWithDefaults capture Empty)+  (PackageConfigWithDefaults capture ParseCSources ParseJsSources) -toPackage :: FilePath -> ParseConfig -> IO ([String], Package)-toPackage dir = runWriterT . (extractUnknownFieldWarnings >=> expandForeignSources dir) >=> toPackage_ dir+type CommonOptionsWithDefaults capture a = Product+  (DefaultsConfig capture)+  (CommonOptions capture ParseCSources ParseJsSources a) -toPackage_ :: FilePath -> (Config Identity CSources JsSources, [String]) -> IO ([String], Package)-toPackage_ dir (Product (toSection . (`Product` Empty) -> globalOptions) PackageConfig{..}, packageWarnings) = do-  mLibrary <- mapM (toLibrary dir packageName_ globalOptions) mLibrarySection+type WithCommonOptionsWithDefaults capture a = Product+  (DefaultsConfig capture)+  (WithCommonOptions capture ParseCSources ParseJsSources a) -  let-    executableSections :: Map String (Section ExecutableSection)-    (executableWarning, executableSections) = (warning, sections)-      where-        sections :: Map String (Section ExecutableSection)-        sections = case mExecutable of-          Just executable -> Map.fromList [(packageName_, executable)]-          Nothing -> executables+type ParseConfig = CaptureUnknownFields (ConfigWithDefaults CaptureUnknownFields) -        warning = case mExecutable of-          Just _ | not (null executables) -> ["Ignoring field \"executables\" in favor of \"executable\""]-          _ -> []+toPackage :: FilePath -> FilePath -> ParseConfig -> Warnings (Errors IO) Package+toPackage userDataDir dir =+      warnUnknownFieldsInConfig+  >=> expandDefaultsInConfig userDataDir+  >=> traverseConfig (expandForeignSources dir)+  >=> toPackage_ dir -        executables = toSections packageConfigExecutables+expandDefaultsInConfig+  :: FilePath+  -> ConfigWithDefaults Identity+  -> Warnings (Errors IO) (Config Identity ParseCSources ParseJsSources)+expandDefaultsInConfig userDataDir = bitraverse (expandGlobalDefaults userDataDir) (expandSectionDefaults userDataDir) -        mExecutable :: Maybe (Section ExecutableSection)-        mExecutable = toSectionI <$> packageConfigExecutable+expandGlobalDefaults+  :: FilePath+  -> CommonOptionsWithDefaults Identity Empty+  -> Warnings (Errors IO) (CommonOptions Identity ParseCSources ParseJsSources Empty)+expandGlobalDefaults userDataDir = do+  fmap (`Product` Empty) >>> expandDefaults userDataDir >=> \ (Product c Empty) -> return c -  internalLibraries <- toInternalLibraries dir packageName_ globalOptions internalLibrariesSections-  executables <- toExecutables dir packageName_ globalOptions executableSections-  tests <- toExecutables dir packageName_ globalOptions testSections-  benchmarks <- toExecutables dir packageName_ globalOptions benchmarkSections+expandSectionDefaults+  :: FilePath+  -> PackageConfigWithDefaults Identity ParseCSources ParseJsSources+  -> Warnings (Errors IO) (PackageConfig Identity ParseCSources ParseJsSources)+expandSectionDefaults userDataDir p@PackageConfig{..} = do+  library <- traverse (traverse (expandDefaults userDataDir)) packageConfigLibrary+  internalLibraries <- traverse (traverse (traverse (expandDefaults userDataDir))) packageConfigInternalLibraries+  executable <- traverse (traverse (expandDefaults userDataDir)) packageConfigExecutable+  executables <- traverse (traverse (traverse (expandDefaults userDataDir))) packageConfigExecutables+  tests <- traverse (traverse (traverse (expandDefaults userDataDir))) packageConfigTests+  benchmarks <- traverse (traverse (traverse (expandDefaults userDataDir))) packageConfigBenchmarks+  return p{+      packageConfigLibrary = library+    , packageConfigInternalLibraries = internalLibraries+    , packageConfigExecutable = executable+    , packageConfigExecutables = executables+    , packageConfigTests = tests+    , packageConfigBenchmarks = benchmarks+    } -  licenseFileExists <- doesFileExist (dir </> "LICENSE")+expandDefaults+  :: (HasFieldNames a, FromJSON a, Monoid a)+  => FilePath+  -> WithCommonOptionsWithDefaults Identity a+  -> Warnings (Errors IO) (WithCommonOptions Identity ParseCSources ParseJsSources a)+expandDefaults userDataDir = expand []+  where+    expand :: (HasFieldNames a, FromJSON a, Monoid a) =>+         [FilePath]+      -> WithCommonOptionsWithDefaults Identity a+      -> Warnings (Errors IO) (WithCommonOptions Identity ParseCSources ParseJsSources a)+    expand seen (Product DefaultsConfig{..} c) = do+      d <- mconcat <$> mapM (get seen . runIdentity) (fromMaybeList defaultsConfigDefaults)+      return (d <> c) -  missingSourceDirs <- nub . sort <$> filterM (fmap not <$> doesDirectoryExist . (dir </>)) (+    get :: (HasFieldNames a, FromJSON a, Monoid a) =>+         [FilePath]+      -> Defaults+      -> Warnings (Errors IO) (WithCommonOptions Identity ParseCSources ParseJsSources a)+    get seen defaults = do+      file <- lift $ ExceptT (ensure userDataDir defaults)+      seen_ <- lift (checkCycle seen file)+      decodeYaml file >>= warnUnknownFieldsInDefaults file >>= expand seen_++    checkCycle :: [FilePath] -> FilePath -> Errors IO [FilePath]+    checkCycle seen file = do+      canonic <- liftIO $ canonicalizePath file+      let seen_ = canonic : seen+      when (canonic `elem` seen) $ do+        throwE ("cycle in defaults (" ++ intercalate " -> " (reverse seen_) ++ ")")+      return seen_++toExecutableMap :: Monad m => String -> Maybe (Map String a) -> Maybe a -> Warnings m (Maybe (Map String a))+toExecutableMap name executables mExecutable = do+  case mExecutable of+    Just executable -> do+      when (isJust executables) $ do+        tell ["Ignoring field \"executables\" in favor of \"executable\""]+      return $ Just (Map.fromList [(name, executable)])+    Nothing -> return executables++type GlobalOptions = CommonOptions Identity CSources JsSources Empty++toPackage_ :: MonadIO m => FilePath -> Product GlobalOptions (PackageConfig Identity CSources JsSources) -> Warnings m Package+toPackage_ dir (Product globalOptions PackageConfig{..}) = do+  mLibrary <- liftIO $ traverse (toLibrary dir packageName_ globalOptions) packageConfigLibrary++  internalLibraries <- liftIO $ toInternalLibraries dir packageName_ globalOptions packageConfigInternalLibraries++  executables <- toExecutableMap packageName_ packageConfigExecutables packageConfigExecutable+    >>= liftIO . toExecutables dir packageName_ globalOptions++  tests <- liftIO $ toExecutables dir packageName_ globalOptions packageConfigTests+  benchmarks <- liftIO $ toExecutables dir packageName_ globalOptions packageConfigBenchmarks++  licenseFileExists <- liftIO $ doesFileExist (dir </> "LICENSE")++  missingSourceDirs <- liftIO $ nub . sort <$> filterM (fmap not <$> doesDirectoryExist . (dir </>)) (        maybe [] sectionSourceDirs mLibrary     ++ concatMap sectionSourceDirs internalLibraries     ++ concatMap sectionSourceDirs executables@@ -593,14 +792,9 @@     ++ concatMap sectionSourceDirs benchmarks     ) -  (extraSourceFilesWarnings, extraSourceFiles) <--    expandGlobs "extra-source-files" dir (fromMaybeList packageConfigExtraSourceFiles)--  (extraDocFilesWarnings, extraDocFiles) <--    expandGlobs "extra-doc-files" dir (fromMaybeList packageConfigExtraDocFiles)--  (dataFilesWarnings, dataFiles) <--    expandGlobs "data-files" dir (fromMaybeList packageConfigDataFiles)+  extraSourceFiles <- expandGlobs "extra-source-files" dir (fromMaybeList packageConfigExtraSourceFiles)+  extraDocFiles <- expandGlobs "extra-doc-files" dir (fromMaybeList packageConfigExtraDocFiles)+  dataFiles <- expandGlobs "data-files" dir (fromMaybeList packageConfigDataFiles)    let defaultBuildType :: BuildType       defaultBuildType = maybe Simple (const Custom) mCustomSetup@@ -639,16 +833,9 @@       , packageBenchmarks = benchmarks       } -      warnings =-           packageWarnings-        ++ nameWarnings-        ++ formatMissingSourceDirs missingSourceDirs-        ++ executableWarning-        ++ extraSourceFilesWarnings-        ++ extraDocFilesWarnings-        ++ dataFilesWarnings--  return (warnings, pkg)+  tell nameWarnings+  tell (formatMissingSourceDirs missingSourceDirs)+  return pkg   where     nameWarnings :: [String]     packageName_ :: String@@ -660,21 +847,6 @@     mCustomSetup :: Maybe CustomSetup     mCustomSetup = toCustomSetup . runIdentity <$> packageConfigCustomSetup -    mLibrarySection :: Maybe (Section LibrarySection)-    mLibrarySection = toSectionI <$> packageConfigLibrary--    toSections :: Maybe (Map String (Identity (WithCommonOptions a Identity CSources JsSources))) -> Map String (Section a)-    toSections = fmap toSectionI . fromMaybe mempty--    internalLibrariesSections :: Map String (Section LibrarySection)-    internalLibrariesSections = toSections packageConfigInternalLibraries--    testSections :: Map String (Section ExecutableSection)-    testSections = toSections packageConfigTests--    benchmarkSections :: Map String (Section ExecutableSection)-    benchmarkSections = toSections packageConfigBenchmarks-     flags = map (toFlag . fmap runIdentity) $ toList packageConfigFlags      toList :: Maybe (Map String a) -> [(String, a)]@@ -710,23 +882,47 @@       where         fromGithub = (++ "/issues") . sourceRepositoryUrl <$> github -type Warnings m = WriterT [String] m+sequenceUnknownFields :: Applicative capture => Traverse capture capture Identity cSources cSources jsSources jsSources+sequenceUnknownFields = defaultTraverse{traverseCapture = fmap Identity} -extractUnknownFieldWarnings :: forall m. Monad m => ParseConfig -> Warnings m (Config Identity ParseCSources ParseJsSources)-extractUnknownFieldWarnings = warnGlobal >=> bitraverse return warnSections+warnUnknownFieldsInDefaults+  :: Monad m+  => String+  -> CaptureUnknownFields (WithCommonOptionsWithDefaults CaptureUnknownFields a)+  -> Warnings m (WithCommonOptionsWithDefaults Identity a)+warnUnknownFieldsInDefaults name =+  (warnUnknownFields In name . (>>= bitraverse sequenceDefaults (traverseWithCommonOptions sequenceUnknownFields)))++traverseDefaultsConfig+  :: Applicative m+  => Traverse m capture capture_ cSources cSources_ jsSources jsSources_+  -> DefaultsConfig capture -> m (DefaultsConfig capture_)+traverseDefaultsConfig Traverse{..} (DefaultsConfig defaults) = do+  DefaultsConfig <$> traverse (traverse $ traverseCapture) defaults++sequenceDefaults :: Applicative capture => DefaultsConfig capture -> capture (DefaultsConfig Identity)+sequenceDefaults = traverseDefaultsConfig sequenceUnknownFields++warnUnknownFieldsInConfig :: forall m. Monad m => ParseConfig -> Warnings m (ConfigWithDefaults Identity)+warnUnknownFieldsInConfig =+      warnGlobal+  >=> bitraverse (bitraverse warnDefaults return) return+  >=> traverse warnSections   where-    t :: Monad capture => Traverse capture capture Identity cSources cSources jsSources jsSources-    t = Traverse (fmap Identity) return return+    t = sequenceUnknownFields -    warnGlobal c = warnUnknownFields In "package description" (c >>= bitraverse (traverseCommonOptions t) return)+    warnGlobal c = warnUnknownFields In "package description" (c >>= bitraverse (traverse (traverseCommonOptions t)) return) -    warnSections :: ParsePackageConfig -> Warnings m (PackageConfig Identity ParseCSources ParseJsSources)+    warnDefaults :: DefaultsConfig CaptureUnknownFields -> Warnings m (DefaultsConfig Identity)+    warnDefaults = warnUnknownFields In "defaults section" . sequenceDefaults++    warnSections :: ParsePackageConfig -> Warnings m (PackageConfigWithDefaults Identity ParseCSources ParseJsSources)     warnSections p@PackageConfig{..} = do       flags <- traverse (warnNamed For "flag" . fmap (traverseCapture t)) packageConfigFlags       customSetup <- warnUnknownFields In "custom-setup section" (traverse (traverseCapture t) packageConfigCustomSetup)-      library <- warnUnknownFields In "library section" (traverse (traverseSectionConfig t) packageConfigLibrary)+      library <- warnUnknownFields In "library section" (traverse (traverseSectionConfigWithDefaluts t) packageConfigLibrary)       internalLibraries <- warnNamedSection "internal-libraries" packageConfigInternalLibraries-      executable <- warnUnknownFields In "executable section" (traverse (traverseSectionConfig t) packageConfigExecutable)+      executable <- warnUnknownFields In "executable section" (traverse (traverseSectionConfigWithDefaluts t) packageConfigExecutable)       executables <- warnNamedSection "executable" packageConfigExecutables       tests <- warnNamedSection "test" packageConfigTests       benchmarks <- warnNamedSection "benchmark" packageConfigBenchmarks@@ -743,35 +939,36 @@      warnNamedSection       :: String-      -> Maybe (Map String (SectionConfig a CaptureUnknownFields cSources jsSources))-      -> Warnings m (Maybe (Map String (SectionConfig a Identity cSources jsSources)))-    warnNamedSection sectionType = traverse (warnNamed In (sectionType ++ " section") . fmap (traverseSectionConfig t))+      -> Maybe (Map String (SectionConfigWithDefaluts CaptureUnknownFields cSources jsSources a))+      -> Warnings m (Maybe (Map String (SectionConfigWithDefaluts Identity cSources jsSources a)))+    warnNamedSection sectionType = traverse (warnNamed In (sectionType ++ " section") . fmap (traverseSectionConfigWithDefaluts t))      warnNamed :: Preposition -> String -> Map String (CaptureUnknownFields a) -> Warnings m (Map String a)     warnNamed preposition sect = fmap Map.fromList . mapM f . Map.toList       where         f (name, fields) = (,) name <$> (warnUnknownFields preposition (sect ++ " " ++ show name) fields) -    warnUnknownFields :: Preposition -> String -> CaptureUnknownFields a -> Warnings m a-    warnUnknownFields preposition name = fmap snd . bitraverse tell return . formatUnknownFields preposition name+warnUnknownFields :: forall m a. Monad m => Preposition -> String -> CaptureUnknownFields a -> Warnings m a+warnUnknownFields preposition name = fmap snd . bitraverse tell return . formatUnknownFields preposition name  expandForeignSources-  :: FilePath-  -> Config Identity ParseCSources ParseJsSources-  -> Warnings IO (Config Identity CSources JsSources)-expandForeignSources dir = traverseConfig t+  :: MonadIO m+  => FilePath+  -> Traverse (Warnings m) capture capture ParseCSources CSources ParseJsSources JsSources+expandForeignSources dir = defaultTraverse {+    traverseCSources = expand "c-sources"+  , traverseJsSources = expand "js-sources"+  }   where-    t = Traverse {-      traverseCapture = return-    , traverseCSources = expand "c-sources"-    , traverseJsSources = expand "js-sources"-    }-     expand fieldName xs = do-      (warnings, files) <- liftIO $ expandGlobs fieldName dir (fromMaybeList xs)-      tell warnings-      return files+      expandGlobs fieldName dir (fromMaybeList xs) +expandGlobs :: MonadIO m => String -> FilePath -> [String] -> Warnings m [FilePath]+expandGlobs name dir patterns = do+  (warnings, files) <- liftIO $ Util.expandGlobs name dir patterns+  tell warnings+  return files+ toCustomSetup :: CustomSetupSection -> CustomSetup toCustomSetup CustomSetupSection{..} = CustomSetup   { customSetupDependencies = fromMaybe mempty customSetupSectionDependencies }@@ -819,20 +1016,21 @@         r = fromConfig pathsModule inferableModules conf       return (outerModules ++ getInferredModules r, r) -toLibrary :: FilePath -> String -> Section global -> Section LibrarySection -> IO (Section Library)+toLibrary :: FilePath -> String -> GlobalOptions -> SectionConfig Identity CSources JsSources LibrarySection -> IO (Section Library) toLibrary dir name globalOptions =     inferModules dir name getMentionedLibraryModules getLibraryModules fromLibrarySectionTopLevel fromLibrarySectionInConditional-  . mergeSections emptyLibrarySection globalOptions+  . toSectionI (mempty <$ globalOptions)   where     getLibraryModules :: Library -> [String]     getLibraryModules Library{..} = libraryExposedModules ++ libraryOtherModules      fromLibrarySectionTopLevel pathsModule inferableModules LibrarySection{..} =-      Library librarySectionExposed exposedModules otherModules reexportedModules+      Library librarySectionExposed exposedModules otherModules reexportedModules signatures       where         (exposedModules, otherModules) =           determineModules pathsModule inferableModules librarySectionExposedModules librarySectionOtherModules         reexportedModules = fromMaybeList librarySectionReexportedModules+        signatures = fromMaybeList librarySectionSignatures  determineModules :: [String] -> [String] -> Maybe (List String) -> Maybe (List String) -> ([String], [String]) determineModules pathsModule inferableModules mExposedModules mOtherModules = case (mExposedModules, mOtherModules) of@@ -843,7 +1041,7 @@       otherModules   = maybe ((inferableModules ++ pathsModule) \\ exposedModules) fromList mOtherModules  fromLibrarySectionInConditional :: [String] -> LibrarySection -> Library-fromLibrarySectionInConditional inferableModules lib@(LibrarySection _ exposedModules otherModules _) = do+fromLibrarySectionInConditional inferableModules lib@(LibrarySection _ exposedModules otherModules _ _) = do   case (exposedModules, otherModules) of     (Nothing, Nothing) -> (fromLibrarySectionPlain lib) {libraryOtherModules = inferableModules}     _ -> fromLibrarySectionPlain lib@@ -854,23 +1052,24 @@   , libraryExposedModules = fromMaybeList librarySectionExposedModules   , libraryOtherModules = fromMaybeList librarySectionOtherModules   , libraryReexportedModules = fromMaybeList librarySectionReexportedModules+  , librarySignatures = fromMaybeList librarySectionSignatures   } -toInternalLibraries :: FilePath -> String -> Section global -> Map String (Section LibrarySection) -> IO (Map String (Section Library))-toInternalLibraries dir packageName_ globalOptions = traverse (toLibrary dir packageName_ globalOptions)+toInternalLibraries :: FilePath -> String -> GlobalOptions -> Maybe (Map String (SectionConfig Identity CSources JsSources LibrarySection)) -> IO (Map String (Section Library))+toInternalLibraries dir packageName_ globalOptions = traverse (toLibrary dir packageName_ globalOptions) . fromMaybe mempty -toExecutables :: FilePath -> String -> Section global -> Map String (Section ExecutableSection) -> IO (Map String (Section Executable))-toExecutables dir packageName_ globalOptions = traverse (toExecutable dir packageName_ globalOptions)+toExecutables :: FilePath -> String -> GlobalOptions -> Maybe (Map String (SectionConfig Identity CSources JsSources ExecutableSection)) -> IO (Map String (Section Executable))+toExecutables dir packageName_ globalOptions = traverse (toExecutable dir packageName_ globalOptions) . fromMaybe mempty  getMentionedExecutableModules :: ExecutableSection -> [String] getMentionedExecutableModules ExecutableSection{..} =   fromMaybeList executableSectionOtherModules ++ maybe [] return (executableSectionMain >>= toModule . splitDirectories) -toExecutable :: FilePath -> String -> Section global -> Section ExecutableSection -> IO (Section Executable)+toExecutable :: FilePath -> String -> GlobalOptions -> SectionConfig Identity CSources JsSources ExecutableSection -> IO (Section Executable) toExecutable dir packageName_ globalOptions =     inferModules dir packageName_ getMentionedExecutableModules executableOtherModules fromExecutableSection (fromExecutableSection [])   . expandMain-  . mergeSections emptyExecutableSection globalOptions+  . toSectionI (mempty <$ globalOptions)   where     fromExecutableSection :: [String] -> [String] -> ExecutableSection -> Executable     fromExecutableSection pathsModule inferableModules ExecutableSection{..} =@@ -897,39 +1096,14 @@       , sectionConditionals = map (fmap flatten) sectionConditionals       } -mergeSections :: a -> Section global -> Section a -> Section a-mergeSections a globalOptions options-  = Section {-    sectionData = sectionData options-  , sectionSourceDirs = sectionSourceDirs globalOptions ++ sectionSourceDirs options-  , sectionDefaultExtensions = sectionDefaultExtensions globalOptions ++ sectionDefaultExtensions options-  , sectionOtherExtensions = sectionOtherExtensions globalOptions ++ sectionOtherExtensions options-  , sectionGhcOptions = sectionGhcOptions globalOptions ++ sectionGhcOptions options-  , sectionGhcProfOptions = sectionGhcProfOptions globalOptions ++ sectionGhcProfOptions options-  , sectionGhcjsOptions = sectionGhcjsOptions globalOptions ++ sectionGhcjsOptions options-  , sectionCppOptions = sectionCppOptions globalOptions ++ sectionCppOptions options-  , sectionCcOptions = sectionCcOptions globalOptions ++ sectionCcOptions options-  , sectionCSources = sectionCSources globalOptions ++ sectionCSources options-  , sectionJsSources = sectionJsSources globalOptions ++ sectionJsSources options-  , sectionExtraLibDirs = sectionExtraLibDirs globalOptions ++ sectionExtraLibDirs options-  , sectionExtraLibraries = sectionExtraLibraries globalOptions ++ sectionExtraLibraries options-  , sectionExtraFrameworksDirs = sectionExtraFrameworksDirs globalOptions ++ sectionExtraFrameworksDirs options-  , sectionFrameworks = sectionFrameworks globalOptions ++ sectionFrameworks options-  , sectionIncludeDirs = sectionIncludeDirs globalOptions ++ sectionIncludeDirs options-  , sectionInstallIncludes = sectionInstallIncludes globalOptions ++ sectionInstallIncludes options-  , sectionLdOptions = sectionLdOptions globalOptions ++ sectionLdOptions options-  , sectionBuildable = sectionBuildable options <|> sectionBuildable globalOptions-  , sectionDependencies = sectionDependencies options <> sectionDependencies globalOptions-  , sectionPkgConfigDependencies = sectionPkgConfigDependencies globalOptions ++ sectionPkgConfigDependencies options-  , sectionConditionals = map (fmap (a <$)) (sectionConditionals globalOptions) ++ sectionConditionals options-  , sectionBuildTools = sectionBuildTools options <> sectionBuildTools globalOptions-  }+toSectionI :: CommonOptions Identity CSources JsSources a -> Identity (WithCommonOptions Identity CSources JsSources a) -> Section a+toSectionI globalOptions = toSection globalOptions . runIdentity -toSectionI :: Identity (WithCommonOptions a Identity CSources JsSources) -> Section a-toSectionI = toSection . runIdentity+toSection :: CommonOptions Identity CSources JsSources a -> WithCommonOptions Identity CSources JsSources a -> Section a+toSection globalOptions (Product options a) = toSection_ (Product (globalOptions <> options) a) -toSection :: WithCommonOptions a Identity CSources JsSources -> Section a-toSection (Product CommonOptions{..} a) = Section {+toSection_ :: WithCommonOptions Identity CSources JsSources a -> Section a+toSection_ (Product CommonOptions{..} a) = Section {         sectionData = a       , sectionSourceDirs = fromMaybeList commonOptionsSourceDirs       , sectionDefaultExtensions = fromMaybeList commonOptionsDefaultExtensions@@ -955,12 +1129,17 @@       , sectionBuildTools = fromMaybe mempty commonOptionsBuildTools       }   where+    toSectionI_ :: Identity (WithCommonOptions Identity CSources JsSources a) -> Section a+    toSectionI_ = toSection_ . runIdentity+     conditionals = map toConditional (fromMaybeList commonOptionsWhen) -    toConditional :: ConditionalSection a Identity CSources JsSources -> Conditional (Section a)+    toConditional :: ConditionalSection Identity CSources JsSources a -> Conditional (Section a)     toConditional x = case x of-      FlatConditional (Identity (Product sect c)) -> Conditional (conditionCondition c) (toSection sect) Nothing-      ThenElseConditional (Identity (ThenElse condition then_ else_)) -> Conditional condition (toSectionI then_) (Just $ toSectionI else_)+      FlatConditional (Identity (Product sect c)) -> conditional c (toSection_ sect) Nothing+      ThenElseConditional (Identity (Product (ThenElse then_ else_) c)) -> conditional c (toSectionI_ then_) (Just $ toSectionI_ else_)+      where+        conditional (Condition (Cond c)) = Conditional c  pathsModuleFromPackageName :: String -> String pathsModuleFromPackageName name = "Paths_" ++ map f name
+ src/Hpack/Defaults.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE LambdaCase #-}+module Hpack.Defaults (+  ensure+, Defaults(..)+#ifdef TEST+, Result(..)+, ensureFile+#endif+) where++import           Network.HTTP.Types+import           Network.HTTP.Client+import           Network.HTTP.Client.TLS+import           Data.List+import qualified Data.ByteString.Lazy as LB+import qualified Data.ByteString.Char8 as B+import           System.FilePath+import           System.Directory++import           Hpack.Syntax++type URL = String++defaultsUrl :: Defaults -> URL+defaultsUrl Defaults{..} = "https://raw.githubusercontent.com/" ++ defaultsGithubUser ++ "/" ++ defaultsGithubRepo ++ "/" ++ defaultsRef ++ "/" ++ intercalate "/" defaultsPath++defaultsCachePath :: FilePath -> Defaults -> FilePath+defaultsCachePath dir Defaults{..} = joinPath $+  dir : "defaults" : defaultsGithubUser : defaultsGithubRepo : defaultsRef : defaultsPath++data Result = Found | NotFound | Failed String+  deriving (Eq, Show)++get :: URL -> FilePath -> IO Result+get url file = do+  manager <- newManager tlsManagerSettings+  request <- parseRequest url+  response <- httpLbs request manager+  case responseStatus response of+    Status 200 _ -> do+      createDirectoryIfMissing True (takeDirectory file)+      LB.writeFile file (responseBody response)+      return Found+    Status 404 _ -> return NotFound+    status -> return (Failed $ "Error while downloading " ++ url ++ " (" ++ formatStatus status ++ ")")++formatStatus :: Status -> String+formatStatus (Status code message) = show code ++ " " ++ B.unpack message++ensure :: FilePath -> Defaults -> IO (Either String FilePath)+ensure dir defaults =+  ensureFile file url >>= \ case+    Found -> return (Right file)+    NotFound -> return (Left notFound)+    Failed err -> return (Left err)+  where+    url = defaultsUrl defaults+    file = defaultsCachePath dir defaults+    notFound = "Invalid value for \"defaults\"! File " ++ url ++ " does not exist!"++ensureFile :: FilePath -> URL -> IO Result+ensureFile file url = do+  doesFileExist file >>= \ case+    True -> return Found+    False -> get url file
− src/Hpack/GenericsUtil.hs
@@ -1,45 +0,0 @@-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE ConstraintKinds #-}-module Hpack.GenericsUtil (-  HasTypeName-, typeName-, Selectors-, selectors-) where--import           Data.Proxy-import           GHC.Generics--type HasTypeName a d m = (Datatype d, Generic a, Rep a ~ M1 D d m)--typeName :: forall a d m. (Datatype d, Generic a, Rep a ~ M1 D d m) => Proxy a -> String-typeName _ = datatypeName (undefined :: M1 D d x y)--selectors :: (Selectors (Rep a)) => Proxy a -> [String]-selectors = f-  where-    f :: forall a. (Selectors (Rep a)) => Proxy a -> [String]-    f _ = selNames (Proxy :: Proxy (Rep a))--class Selectors a where-  selNames :: Proxy a -> [String]--instance Selectors f => Selectors (M1 D x f) where-  selNames _ = selNames (Proxy :: Proxy f)--instance Selectors f => Selectors (M1 C x f) where-  selNames _ = selNames (Proxy :: Proxy f)--instance Selector s => Selectors (M1 S s (K1 R t)) where-  selNames _ = [selName (undefined :: M1 S s (K1 R t) ())]--instance (Selectors a, Selectors b) => Selectors (a :*: b) where-  selNames _ = selNames (Proxy :: Proxy a) ++ selNames (Proxy :: Proxy b)--instance Selectors U1 where-  selNames _ = []
src/Hpack/Options.hs view
@@ -1,6 +1,6 @@ module Hpack.Options where -data ParseResult = Help | PrintVersion | Run Options | ParseError+data ParseResult = Help | PrintVersion | PrintNumericVersion | Run Options | ParseError   deriving (Eq, Show)  data Verbose = Verbose | NoVerbose@@ -19,6 +19,7 @@ parseOptions :: [String] -> ParseResult parseOptions xs = case xs of   ["--version"] -> PrintVersion+  ["--numeric-version"] -> PrintNumericVersion   ["--help"] -> Help   _ -> case targets of     Just (target, toStdout) -> Run (Options verbose force toStdout target)
src/Hpack/Run.hs view
@@ -27,6 +27,8 @@ import           Data.List import           System.Exit import           System.FilePath+import           System.Directory+import           Data.Version import           Data.Map.Lazy (Map) import qualified Data.Map.Lazy as Map @@ -38,9 +40,10 @@ run :: Maybe FilePath -> FilePath -> IO ([String], FilePath, String) run mDir c = do   let dir = fromMaybe "" mDir-  mPackage <- readPackageConfig (dir </> c)+  userDataDir <- getAppUserDataDirectory "hpack"+  mPackage <- readPackageConfig userDataDir (dir </> c)   case mPackage of-    Right (warnings, pkg) -> do+    Right (pkg, warnings) -> do       let cabalFile = dir </> (packageName pkg ++ ".cabal")        old <- tryReadFile cabalFile@@ -126,26 +129,31 @@         separator = let Alignment n = alignment in ",\n" ++ replicate n ' '      cabalVersion :: Maybe String-    cabalVersion = maximum [-        Just ">= 1.10"+    cabalVersion = (">= " ++) . showVersion <$> maximum [+        Just (makeVersion [1,10])       , packageCabalVersion-      , packageLibrary >>= libraryCabalVersion+      , packageLibrary >>= libraryCabalVersion . sectionData       , internalLibsCabalVersion packageInternalLibraries       ]      where-      packageCabalVersion :: Maybe String-      packageCabalVersion-        | isJust packageCustomSetup = Just ">= 1.24"-        | otherwise = Nothing--      libraryCabalVersion :: Section Library -> Maybe String-      libraryCabalVersion sect = ">= 1.22" <$ guard (hasReexportedModules sect)+      packageCabalVersion :: Maybe Version+      packageCabalVersion = maximum [+          Nothing+        , makeVersion [1,24] <$ packageCustomSetup+        , makeVersion [1,18] <$ guard (not (null packageExtraDocFiles))+        ] -      hasReexportedModules :: Section Library -> Bool-      hasReexportedModules = not . null . libraryReexportedModules . sectionData+      libraryCabalVersion :: Library -> Maybe Version+      libraryCabalVersion Library{..} = maximum [+          makeVersion [1,22] <$ guard hasReexportedModules+        , makeVersion [2,0]  <$ guard hasSignatures+        ]+        where+          hasReexportedModules = (not . null) libraryReexportedModules+          hasSignatures = (not . null) librarySignatures -      internalLibsCabalVersion :: Map String (Section Library) -> Maybe String-      internalLibsCabalVersion internalLibraries = ">= 2.0" <$ guard (not (Map.null internalLibraries))+      internalLibsCabalVersion :: Map String (Section Library) -> Maybe Version+      internalLibsCabalVersion internalLibraries = makeVersion [2,0] <$ guard (not (Map.null internalLibraries))  sortSectionFields :: [(String, [String])] -> [Element] -> [Element] sortSectionFields sectionsFieldOrder = go@@ -239,6 +247,7 @@     renderExposedModules libraryExposedModules   , renderOtherModules libraryOtherModules   , renderReexportedModules libraryReexportedModules+  , renderSignatures librarySignatures   ]  renderExposed :: Bool -> Element@@ -297,6 +306,9 @@  renderReexportedModules :: [String] -> Element renderReexportedModules = Field "reexported-modules" . LineSeparatedList++renderSignatures :: [String] -> Element+renderSignatures = Field "signatures" . CommaSeparatedList  renderDependencies :: String -> Dependencies -> Element renderDependencies name = Field name . CommaSeparatedList . map renderDependency . Map.toList . unDependencies
+ src/Hpack/Syntax.hs view
@@ -0,0 +1,122 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE RecordWildCards #-}+module Hpack.Syntax (+  Defaults(..)+#ifdef TEST+, isValidUser+, isValidRepo+#endif+) where++import           Data.List+import           Data.Data+import           System.FilePath.Posix (splitDirectories)++import           Hpack.Syntax.Util+import           Hpack.Syntax.UnknownFields+import           Hpack.Syntax.Git++data ParseDefaults = ParseDefaults {+  parseDefaultsGithub :: Github+, parseDefaultsRef :: Ref+, parseDefaultsPath :: Maybe Path+} deriving Generic++instance HasFieldNames ParseDefaults++instance FromJSON ParseDefaults where+  parseJSON = genericParseJSON++data Github = Github {+  githubUser :: String+, githubRepo :: String+}++instance FromJSON Github where+  parseJSON v = parseJSON v >>= parseGithub++parseGithub :: String -> Parser Github+parseGithub github+  | not (isValidUser user) = fail ("invalid user name " ++ show user)+  | not (isValidRepo repo) = fail ("invalid repository name " ++ show repo)+  | otherwise = return (Github user repo)+  where+    (user, repo) = drop 1 <$> break (== '/') github++isValidUser :: String -> Bool+isValidUser user =+     not (null user)+  && all isAlphaNumOrHyphen user+  && doesNotHaveConsecutiveHyphens user+  && doesNotBeginWithHyphen user+  && doesNotEndWithHyphen user+  where+    isAlphaNumOrHyphen = (`elem` '-' : alphaNum)+    doesNotHaveConsecutiveHyphens = not . isInfixOf "--"+    doesNotBeginWithHyphen = not . isPrefixOf "-"+    doesNotEndWithHyphen = not . isSuffixOf "-"++isValidRepo :: String -> Bool+isValidRepo repo =+     not (null repo)+  && repo `notElem` [".", ".."]+  && all isValid repo+  where+    isValid = (`elem` '_' : '.' : '-' : alphaNum)++alphaNum :: [Char]+alphaNum = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9']++data Ref = Ref {unRef :: String}++instance FromJSON Ref where+  parseJSON v = parseJSON v >>= parseRef++parseRef :: String -> Parser Ref+parseRef ref+  | isValidRef ref = return (Ref ref)+  | otherwise = fail ("invalid Git reference " ++ show ref)++data Path = Path {unPath :: [FilePath]}++instance FromJSON Path where+  parseJSON v = parseJSON v >>= parsePath+    where+      parsePath path+        | '\\' `elem` path = fail ("rejecting '\\' in " ++ show path ++ ", please use '/' to separate path components")+        | ':' `elem` path = fail ("rejecting ':' in " ++ show path)+        | "/" `elem` p = fail ("rejecting absolute path " ++ show path)+        | ".." `elem` p = fail ("rejecting \"..\" in " ++ show path)+        | otherwise = return (Path p)+        where+          p = splitDirectories path++data Defaults = Defaults {+  defaultsGithubUser :: String+, defaultsGithubRepo :: String+, defaultsRef :: String+, defaultsPath :: [FilePath]+} deriving (Eq, Show)++instance FromJSON Defaults where+  parseJSON v = toDefaults <$> case v of+    String _ -> parseJSON v >>= parseDefaultsFromString+    Object _ -> parseJSON v+    _ -> typeMismatch "Object or String" v+    where+      toDefaults :: ParseDefaults -> Defaults+      toDefaults ParseDefaults{..} = Defaults {+          defaultsGithubUser = githubUser parseDefaultsGithub+        , defaultsGithubRepo = githubRepo parseDefaultsGithub+        , defaultsRef = unRef parseDefaultsRef+        , defaultsPath = maybe [".hpack", "defaults.yaml"] unPath parseDefaultsPath+        }++parseDefaultsFromString :: String -> Parser ParseDefaults+parseDefaultsFromString xs = case break (== '@') xs of+  (github, '@' : ref) -> ParseDefaults <$> parseGithub github <*> parseRef ref <*> pure Nothing+  _ -> fail ("missing Git reference for " ++ show xs ++ ", the expected format is user/repo@ref")++instance HasFieldNames Defaults where+  fieldNames Proxy = fieldNames (Proxy :: Proxy ParseDefaults)
+ src/Hpack/Syntax/GenericsUtil.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ConstraintKinds #-}+module Hpack.Syntax.GenericsUtil (+  HasTypeName+, typeName+, Selectors+, selectors+) where++import           Data.Proxy+import           GHC.Generics++type HasTypeName a d m = (Datatype d, Generic a, Rep a ~ M1 D d m)++typeName :: forall a d m. (Datatype d, Generic a, Rep a ~ M1 D d m) => Proxy a -> String+typeName _ = datatypeName (undefined :: M1 D d x y)++selectors :: (Selectors (Rep a)) => Proxy a -> [String]+selectors = f+  where+    f :: forall a. (Selectors (Rep a)) => Proxy a -> [String]+    f _ = selNames (Proxy :: Proxy (Rep a))++class Selectors a where+  selNames :: Proxy a -> [String]++instance Selectors f => Selectors (M1 D x f) where+  selNames _ = selNames (Proxy :: Proxy f)++instance Selectors f => Selectors (M1 C x f) where+  selNames _ = selNames (Proxy :: Proxy f)++instance Selector s => Selectors (M1 S s (K1 R t)) where+  selNames _ = [selName (undefined :: M1 S s (K1 R t) ())]++instance (Selectors a, Selectors b) => Selectors (a :*: b) where+  selNames _ = selNames (Proxy :: Proxy a) ++ selNames (Proxy :: Proxy b)++instance Selectors U1 where+  selNames _ = []
+ src/Hpack/Syntax/Git.hs view
@@ -0,0 +1,28 @@+module Hpack.Syntax.Git (+  isValidRef+) where++import           Data.Char (chr)+import           Data.List+import           System.FilePath.Posix++-- https://git-scm.com/docs/git-check-ref-format+isValidRef :: String -> Bool+isValidRef ref =+     not (null ref)+  && not (any (isSuffixOf ".lock") components)+  && not (any (isPrefixOf ".") components)+  && not (".." `isInfixOf` ref)+  && not (any isControl ref)+  && all (`notElem` " ~^:?*[\\") ref+  && not ("//" `isInfixOf` ref)+  && not ("/" `isPrefixOf` ref)+  && not ("/" `isSuffixOf` ref)+  && not ("." `isSuffixOf` ref)+  && not ("@{" `isInfixOf` ref)+  && not (ref == "@")+  where+    components = splitDirectories ref++isControl :: Char -> Bool+isControl c = c < chr 0o040 || c == chr 0o177
+ src/Hpack/Syntax/UnknownFields.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE FlexibleContexts #-}+module Hpack.Syntax.UnknownFields (+  FieldName+, HasFieldNames(..)+, hyphenize++, CaptureUnknownFields+, Preposition(..)+, formatUnknownFields+) where++import           Control.Monad+import           Data.Data+import qualified Data.HashMap.Lazy as HashMap+import           Data.List+import qualified Data.Text as T+import           GHC.Generics++import           Hpack.Syntax.GenericsUtil+import           Hpack.Syntax.Util++newtype FieldName = FieldName {unFieldName :: String}++class HasFieldNames a where+  fieldNames :: Proxy a -> [FieldName]++  default fieldNames :: (HasTypeName a d m, Selectors (Rep a)) => Proxy a -> [FieldName]+  fieldNames proxy = map (FieldName . hyphenize (typeName proxy)) (selectors proxy)++  ignoreUnderscoredUnknownFields :: Proxy a -> Bool+  ignoreUnderscoredUnknownFields _ = False++data CaptureUnknownFields a = CaptureUnknownFields [FieldName] a+  deriving Functor++instance Applicative CaptureUnknownFields where+  pure = return+  (<*>) = ap++instance Monad CaptureUnknownFields where+  return = CaptureUnknownFields mempty+  (CaptureUnknownFields xs x) >>= f = CaptureUnknownFields (xs `mappend` ys) y+    where+      CaptureUnknownFields ys y = f x++captureUnknownFields :: forall a. (HasFieldNames a, FromJSON a) => Value -> Parser (CaptureUnknownFields a)+captureUnknownFields v = CaptureUnknownFields unknown <$> parseJSON v+  where+    unknown = getUnknownFields v (Proxy :: Proxy a)++instance (HasFieldNames a, FromJSON a) => FromJSON (CaptureUnknownFields a) where+  parseJSON = captureUnknownFields++getUnknownFields :: forall a. HasFieldNames a => Value -> Proxy a -> [FieldName]+getUnknownFields v _ = case v of+  Object o -> map FieldName (ignoreUnderscored unknown)+    where+      unknown = keys \\ fields+      keys = map T.unpack (HashMap.keys o)+      fields = map unFieldName $ fieldNames (Proxy :: Proxy a)+      ignoreUnderscored+        | ignoreUnderscoredUnknownFields (Proxy :: Proxy a) = filter (not . isPrefixOf "_")+        | otherwise = id+  _ -> []++data Preposition = In | For++formatUnknownFields :: Preposition -> String -> CaptureUnknownFields a -> ([String], a)+formatUnknownFields p name (CaptureUnknownFields unknownFields a) = (formatUnknownFields_ preposition name unknownFields, a)+  where+    preposition = case p of+      In -> "in"+      For -> "for"++formatUnknownFields_ :: String -> String -> [FieldName] -> [String]+formatUnknownFields_ preposition name = map f+  where+    f (FieldName field) = "Ignoring unknown field " ++ show field ++ " " ++ preposition ++ " " ++ name
+ src/Hpack/Syntax/Util.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeFamilies #-}+module Hpack.Syntax.Util (+  Generic+, genericParseJSON+, hyphenize+, module Data.Aeson.Types+) where++import qualified Data.Aeson.Types as Aeson+import           Data.Aeson.Types hiding (genericParseJSON)+import           Data.Data+import           GHC.Generics++import           Hpack.Syntax.GenericsUtil++#if MIN_VERSION_aeson(1,0,0)+genericParseJSON :: forall a d m. (GFromJSON Zero (Rep a), HasTypeName a d m) => Value -> Parser a+#else+genericParseJSON :: forall a d m. (GFromJSON (Rep a), HasTypeName a d m) => Value -> Parser a+#endif+genericParseJSON = Aeson.genericParseJSON defaultOptions {fieldLabelModifier = hyphenize name}+  where+    name :: String+    name = typeName (Proxy :: Proxy a)++hyphenize :: String -> String -> String+hyphenize name =+#if MIN_VERSION_aeson(0,10,0)+  camelTo2+#else+  camelTo+#endif+  '-' . drop (length (dropWhile (== '_') $ reverse name)) . dropWhile (== '_')
− src/Hpack/UnknownFields.hs
@@ -1,92 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE DefaultSignatures #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE FlexibleContexts #-}-module Hpack.UnknownFields (-  FieldName-, HasFieldNames(..)-, hyphenize--, CaptureUnknownFields-, Preposition(..)-, formatUnknownFields-) where--import           Control.Monad-import           Data.Aeson.Types-import           Data.Data-import qualified Data.HashMap.Lazy as HashMap-import           Data.List-import qualified Data.Text as T-import           GHC.Generics--import           Hpack.GenericsUtil--newtype FieldName = FieldName {unFieldName :: String}--class HasFieldNames a where-  fieldNames :: Proxy a -> [FieldName]--  default fieldNames :: (HasTypeName a d m, Selectors (Rep a)) => Proxy a -> [FieldName]-  fieldNames proxy = map (FieldName . hyphenize (typeName proxy)) (selectors proxy)--  ignoreUnderscoredUnknownFields :: Proxy a -> Bool-  ignoreUnderscoredUnknownFields _ = False--hyphenize :: String -> String -> String-hyphenize name =-#if MIN_VERSION_aeson(0,10,0)-  camelTo2-#else-  camelTo-#endif-  '-' . drop (length name) . dropWhile (== '_')--data CaptureUnknownFields a = CaptureUnknownFields [FieldName] a-  deriving Functor--instance Applicative CaptureUnknownFields where-  pure = return-  (<*>) = ap--instance Monad CaptureUnknownFields where-  return = CaptureUnknownFields mempty-  (CaptureUnknownFields xs x) >>= f = CaptureUnknownFields (xs `mappend` ys) y-    where-      CaptureUnknownFields ys y = f x--captureUnknownFields :: forall a. (HasFieldNames a, FromJSON a) => Value -> Parser (CaptureUnknownFields a)-captureUnknownFields v = CaptureUnknownFields unknown <$> parseJSON v-  where-    unknown = getUnknownFields v (Proxy :: Proxy a)--instance (HasFieldNames a, FromJSON a) => FromJSON (CaptureUnknownFields a) where-  parseJSON = captureUnknownFields--getUnknownFields :: forall a. HasFieldNames a => Value -> Proxy a -> [FieldName]-getUnknownFields v _ = case v of-  Object o -> map FieldName (ignoreUnderscored unknown)-    where-      unknown = keys \\ fields-      keys = map T.unpack (HashMap.keys o)-      fields = map unFieldName $ fieldNames (Proxy :: Proxy a)-      ignoreUnderscored-        | ignoreUnderscoredUnknownFields (Proxy :: Proxy a) = filter (not . isPrefixOf "_")-        | otherwise = id-  _ -> []--data Preposition = In | For--formatUnknownFields :: Preposition -> String -> CaptureUnknownFields a -> ([String], a)-formatUnknownFields p name (CaptureUnknownFields unknownFields a) = (formatUnknownFields_ preposition name unknownFields, a)-  where-    preposition = case p of-      In -> "in"-      For -> "for"--formatUnknownFields_ :: String -> String -> [FieldName] -> [String]-formatUnknownFields_ preposition name = map f-  where-    f (FieldName field) = "Ignoring unknown field " ++ show field ++ " " ++ preposition ++ " " ++ name
src/Hpack/Util.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-} module Hpack.Util (   List(..) , GhcOption@@ -44,7 +45,7 @@ lexicographically x = (map toLower x, x)  newtype List a = List {fromList :: [a]}-  deriving (Eq, Show, Functor, Foldable, Traversable)+  deriving (Eq, Show, Functor, Foldable, Traversable, Monoid)  instance FromJSON a => FromJSON (List a) where   parseJSON v = List <$> case v of
test/EndToEndSpec.hs view
@@ -5,9 +5,13 @@ {-# LANGUAGE ConstraintKinds #-} module EndToEndSpec (spec) where +import           Prelude hiding (writeFile)+import qualified Prelude+ import           Helper -import           System.Exit+import           System.Directory (canonicalizePath)+import           Control.Exception import           Data.Maybe import           Data.List import           Data.String.Interpolate@@ -17,9 +21,125 @@ import           Hpack.Config (packageConfig, readPackageConfig) import           Hpack.FormattingHints (FormattingHints(..), sniffFormattingHints) +writeFile :: FilePath -> String -> IO ()+writeFile file c = touch file >> Prelude.writeFile file c+ spec :: Spec spec = around_ (inTempDirectoryNamed "foo") $ do   describe "hpack" $ do+    context "with defaults" $ do+      it "accepts global defaults" $ do+        writeFile "defaults/sol/hpack-template/2017/defaults.yaml" [i|+        default-extensions:+          - RecordWildCards+          - DeriveFunctor+        |]++        [i|+        defaults:+          github: sol/hpack-template+          path: defaults.yaml+          ref: "2017"+        library: {}+        |] `shouldRenderTo` library [i|+        other-modules:+            Paths_foo+        default-extensions: RecordWildCards DeriveFunctor+        |]++      it "accepts library defaults" $ do+        writeFile "defaults/sol/hpack-template/2017/defaults.yaml" [i|+        exposed-modules: Foo+        |]++        [i|+        library:+          defaults:+            github: sol/hpack-template+            path: defaults.yaml+            ref: "2017"+        |] `shouldRenderTo` library [i|+        exposed-modules:+            Foo+        other-modules:+            Paths_foo+        |]++      it "accepts a list of defaults" $ do+        writeFile "defaults/foo/bar/v1/.hpack/defaults.yaml" "default-extensions: RecordWildCards"+        writeFile "defaults/foo/bar/v2/.hpack/defaults.yaml" "default-extensions: DeriveFunctor"+        [i|+        defaults:+          - foo/bar@v1+          - foo/bar@v2+        library: {}+        |] `shouldRenderTo` library [i|+        other-modules:+            Paths_foo+        default-extensions: RecordWildCards DeriveFunctor+        |]++      it "accepts defaults recursively" $ do+        writeFile "defaults/foo/bar/v1/.hpack/defaults.yaml" "defaults: foo/bar@v2"+        writeFile "defaults/foo/bar/v2/.hpack/defaults.yaml" "default-extensions: DeriveFunctor"+        [i|+        defaults: foo/bar@v1+        library: {}+        |] `shouldRenderTo` library [i|+        other-modules:+            Paths_foo+        default-extensions: DeriveFunctor+        |]++      it "fails on cyclic defaults" $ do+        let+          file1 = "defaults/foo/bar/v1/.hpack/defaults.yaml"+          file2 = "defaults/foo/bar/v2/.hpack/defaults.yaml"+        writeFile file1 "defaults: foo/bar@v2"+        writeFile file2 "defaults: foo/bar@v1"+        canonic1 <- canonicalizePath file1+        canonic2 <- canonicalizePath file2+        [i|+        defaults: foo/bar@v1+        library: {}+        |] `shouldFailWith` [i|cycle in defaults (#{canonic1} -> #{canonic2} -> #{canonic1})|]++      it "fails if defaults don't exist" $ do+        pending+        [i|+        defaults:+          github: sol/foo+          ref: bar+        library: {}+        |] `shouldFailWith` "Invalid value for \"defaults\"! File https://raw.githubusercontent.com/sol/foo/bar/.hpack/defaults.yaml does not exist!"++      it "fails on parse error" $ do+        let file = joinPath ["defaults", "sol", "hpack-template", "2017", "defaults.yaml"]+        writeFile file "[]"+        [i|+        defaults:+          github: sol/hpack-template+          path: defaults.yaml+          ref: "2017"+        library: {}+        |] `shouldFailWith` (file ++ ": Error in $: expected record (:*:), encountered Array")++      it "warns on unknown fields" $ do+        let file = joinPath ["defaults", "sol", "hpack-template", "2017", "defaults.yaml"]+        writeFile file "foo: bar"+        [i|+        defaults:+          github: sol/hpack-template+          path: defaults.yaml+          ref: "2017"+          bar: baz+        name: foo+        library: {}+        |] `shouldWarn` [+            "Ignoring unknown field \"bar\" in defaults section"+          , "Ignoring unknown field \"foo\" in " ++ file+          ]+     describe "extra-doc-files" $ do       it "accepts a list of files" $ do         touch "CHANGES.markdown"@@ -28,11 +148,11 @@         extra-doc-files:           - CHANGES.markdown           - README.markdown-        |] `shouldRenderTo` package [i|+        |] `shouldRenderTo` (package [i|         extra-doc-files:             CHANGES.markdown             README.markdown-        |]+        |]) {packageCabalVersion = ">= 1.18"}        it "accepts glob patterns" $ do         touch "CHANGES.markdown"@@ -40,11 +160,11 @@         [i|         extra-doc-files:           - "*.markdown"-        |] `shouldRenderTo` package [i|+        |] `shouldRenderTo` (package [i|         extra-doc-files:             CHANGES.markdown             README.markdown-        |]+        |]) {packageCabalVersion = ">= 1.18"}        it "warns if a glob pattern does not match anything" $ do         [i|@@ -225,6 +345,17 @@           |] `shouldWarn` pure "Specified pattern \"foo/*.c\" for c-sources does not match any files"        context "with library" $ do+        it "accepts signatures" $ do+          [i|+          library:+            signatures: Foo+          |] `shouldRenderTo` (library [i|+          other-modules:+              Paths_foo+          signatures:+              Foo+          |]) {packageCabalVersion = ">= 2.0"}+         it "accepts global c-sources" $ do           [i|           c-sources: cbits/*.c@@ -709,13 +840,13 @@             ]  run :: FilePath -> String -> IO ([String], String)-run c old = run_ c old >>= either die return+run c old = run_ c old >>= either (throwIO . ErrorCall) return  run_ :: FilePath -> String -> IO (Either String ([String], String)) run_ c old = do-  mPackage <- readPackageConfig c+  mPackage <- readPackageConfig "" c   return $ case mPackage of-    Right (warnings, pkg) ->+    Right (pkg, warnings) ->       let         FormattingHints{..} = sniffFormattingHints old         alignment = fromMaybe 0 formattingHintsAlignment
test/Helper.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-} module Helper (   module Test.Hspec , module Test.Mockery.Directory@@ -5,7 +7,10 @@ , withTempDirectory , module System.FilePath , withCurrentDirectory+, shouldParseAs ) where+import           Data.Yaml+import           Data.ByteString (ByteString)  import           Test.Hspec import           Test.Mockery.Directory@@ -24,3 +29,7 @@ withTempDirectory :: (FilePath -> IO a) -> IO a withTempDirectory action = Temp.withSystemTempDirectory "hspec" $ \dir -> do   canonicalizePath dir >>= action++shouldParseAs :: (HasCallStack, Show a, Eq a, FromJSON a) => ByteString -> Either String a -> Expectation+shouldParseAs input expected = do+  decodeEither input `shouldBe` expected
test/Hpack/ConfigSpec.hs view
@@ -41,25 +41,25 @@ executable main_ = Executable (Just main_) ["Paths_foo"]  library :: Library-library = Library Nothing [] ["Paths_foo"] []+library = Library Nothing [] ["Paths_foo"] [] [] -withPackage :: String -> IO () -> (([String], Package) -> Expectation) -> Expectation+withPackage :: String -> IO () -> ((Package, [String]) -> Expectation) -> Expectation withPackage content beforeAction expectation = withTempDirectory $ \dir_ -> do   let dir = dir_ </> "foo"   createDirectory dir   writeFile (dir </> "package.yaml") content   withCurrentDirectory dir beforeAction-  r <- readPackageConfig (dir </> "package.yaml")+  r <- readPackageConfig undefined (dir </> "package.yaml")   either expectationFailure expectation r  withPackageConfig :: String -> IO () -> (Package -> Expectation) -> Expectation-withPackageConfig content beforeAction expectation = withPackage content beforeAction (expectation . snd)+withPackageConfig content beforeAction expectation = withPackage content beforeAction (expectation . fst)  withPackageConfig_ :: String -> (Package -> Expectation) -> Expectation withPackageConfig_ content = withPackageConfig content (return ())  withPackageWarnings :: String -> IO () -> ([String] -> Expectation) -> Expectation-withPackageWarnings content beforeAction expectation = withPackage content beforeAction (expectation . fst)+withPackageWarnings content beforeAction expectation = withPackage content beforeAction (expectation . snd)  withPackageWarnings_ :: String -> ([String] -> Expectation) -> Expectation withPackageWarnings_ content = withPackageWarnings content (return ())@@ -88,12 +88,14 @@       , librarySectionExposedModules = Nothing       , librarySectionOtherModules = Nothing       , librarySectionReexportedModules = Nothing+      , librarySectionSignatures = Nothing       }       lib = Library {         libraryExposed = Nothing       , libraryExposedModules = []       , libraryOtherModules = []       , libraryReexportedModules = []+      , librarySignatures = []       }       inferableModules = ["Foo", "Bar"]       from = fromLibrarySectionInConditional inferableModules@@ -881,7 +883,7 @@             foo: bar             foo baz             |]-          readPackageConfig file `shouldReturn` Left (file ++ ":3:12: could not find expected ':' while scanning a simple key")+          readPackageConfig undefined file `shouldReturn` Left (file ++ ":3:12: could not find expected ':' while scanning a simple key")        context "when package.yaml is invalid" $ do         it "returns an error" $ \dir -> do@@ -890,9 +892,31 @@             - one             - two             |]-          readPackageConfig file >>= (`shouldSatisfy` isLeft)+          readPackageConfig undefined file >>= (`shouldSatisfy` isLeft)        context "when package.yaml does not exist" $ do         it "returns an error" $ \dir -> do           let file = dir </> "package.yaml"-          readPackageConfig file `shouldReturn` Left [i|#{file}: Yaml file not found: #{file}|]+          readPackageConfig undefined file `shouldReturn` Left [i|#{file}: Yaml file not found: #{file}|]++  describe "parseJSON" $ do+    context "when parsing Cond" $ do+      it "accepts Strings" $ do+        [i|+        os(windows)+        |] `shouldParseAs` Right (Cond "os(windows)")++      it "accepts True" $ do+        [i|+        yes+        |] `shouldParseAs` Right (Cond "true")++      it "accepts False" $ do+        [i|+        no+        |] `shouldParseAs` Right (Cond "false")++      it "rejects other values" $ do+        [i|+        23+        |] `shouldParseAs` (Left "Error in $: expected Boolean or String, encountered Number" :: Either String Cond)
+ test/Hpack/DefaultsSpec.hs view
@@ -0,0 +1,39 @@+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}+module Hpack.DefaultsSpec (spec) where++import           Helper+import           System.Directory++import           Hpack.Defaults++spec :: Spec+spec = do+  describe "ensureFile" $ do+    let+      file = "foo"+      url = "https://raw.githubusercontent.com/sol/hpack/master/Setup.lhs"++    it "downloads file if missing" $ do+      pending+      expected <- readFile "Setup.lhs"+      inTempDirectory $ do+        Found <- ensureFile file url+        readFile file `shouldReturn` expected++    context "with existing file" $ do+      it "does nothing" $ do+        let expected = "contents of existing file"+        inTempDirectory $ do+          writeFile file expected+          Found <- ensureFile file url+          readFile file `shouldReturn` expected++    context "with 404" $ do+      let+        url = "https://raw.githubusercontent.com/sol/hpack/master/Setup.foo"++      it "does not create any files" $ do+        pending+        inTempDirectory $ do+          NotFound <- ensureFile file url+          doesFileExist file `shouldReturn` False
test/Hpack/DependencySpec.hs view
@@ -5,21 +5,14 @@ module Hpack.DependencySpec (spec) where  import           Helper-import           Test.HUnit -import           Data.Aeson.Types-import qualified Data.Map.Lazy as Map import           Data.String.Interpolate.IsString-import           Data.Yaml import           Data.ByteString (ByteString)  import           Hpack.Dependency -parsesAs :: HasCallStack => ByteString -> Either String [(String, DependencyVersion)] -> Expectation-parsesAs input expected = do-  value <- either assertFailure return (decodeEither input)-  parseEither parseJSON value `shouldBe` (Dependencies . Map.fromList <$> expected)-+parsesAs :: HasCallStack => ByteString -> Either String Dependencies -> Expectation+parsesAs = shouldParseAs  spec :: Spec spec = do
− test/Hpack/GenericsUtilSpec.hs
@@ -1,24 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-module Hpack.GenericsUtilSpec (spec) where--import           Test.Hspec--import           Data.Proxy-import           GHC.Generics--import           Hpack.GenericsUtil--data Person = Person {-  _personName :: String-, _personAge :: Int-} deriving Generic--spec :: Spec-spec = do-  describe "selectors" $ do-    it "returns a list of record selectors" $ do-      selectors (Proxy :: Proxy Person) `shouldBe` ["_personName", "_personAge"]--  describe "typeName" $ do-    it "gets datatype name" $ do-      typeName (Proxy :: Proxy Person) `shouldBe` "Person"
test/Hpack/RunSpec.hs view
@@ -11,7 +11,7 @@ import           Hpack.Run  library :: Library-library = Library Nothing [] [] []+library = Library Nothing [] [] [] []  renderEmptySection :: Empty -> [Element] renderEmptySection Empty = []
+ test/Hpack/Syntax/GenericsUtilSpec.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE DeriveGeneric #-}+module Hpack.Syntax.GenericsUtilSpec (spec) where++import           Test.Hspec++import           Data.Proxy+import           GHC.Generics++import           Hpack.Syntax.GenericsUtil++data Person = Person {+  _personName :: String+, _personAge :: Int+} deriving Generic++spec :: Spec+spec = do+  describe "selectors" $ do+    it "returns a list of record selectors" $ do+      selectors (Proxy :: Proxy Person) `shouldBe` ["_personName", "_personAge"]++  describe "typeName" $ do+    it "gets datatype name" $ do+      typeName (Proxy :: Proxy Person) `shouldBe` "Person"
+ test/Hpack/Syntax/GitSpec.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE QuasiQuotes #-}+module Hpack.Syntax.GitSpec (spec) where++import           Helper+import           Data.String.Interpolate+import           Control.Monad++import           Hpack.Syntax.Git++spec :: Spec+spec = do+  describe "isValidRef" $ do+    it "accepts slashes" $ do+      isValidRef "foo/bar" `shouldBe` True++    it "rejects the empty string" $ do+      isValidRef "" `shouldBe` False++    it "accepts .lock as a substring" $ do+      isValidRef "foo.locking" `shouldBe` True++    it "rejects .lock at the end of a component" $ do+      isValidRef "foo/bar.lock/baz" `shouldBe` False++    it "rejects . at the biginning of a component" $ do+      isValidRef "foo/.bar/baz" `shouldBe` False++    it "rejects two consecutive dots .." $ do+      isValidRef "foo..bar" `shouldBe` False++    it "rejects ASCII control characters" $ do+      isValidRef "foo\10bar" `shouldBe` False++    it "rejects space" $ do+      isValidRef "foo bar" `shouldBe` False++    forM_ ["~", "^", ":", "?", "*", "[", "\\"] $ \ xs -> do+      it [i|rejects #{xs}|] $ do+        isValidRef [i|foo#{xs}bar|] `shouldBe` False++    it "rejects multiple consecutive slashes" $ do+      isValidRef "foo//bar" `shouldBe` False++    it "rejects slash at beginning" $ do+      isValidRef "/foo" `shouldBe` False++    it "rejects slash at end" $ do+      isValidRef "foo/" `shouldBe` False++    it "rejects . at end" $ do+      isValidRef "foo." `shouldBe` False++    it "rejects @{" $ do+      isValidRef "foo@{bar" `shouldBe` False++    it "rejects the single character @" $ do+      isValidRef "@" `shouldBe` False
+ test/Hpack/Syntax/UtilSpec.hs view
@@ -0,0 +1,21 @@+module Hpack.Syntax.UtilSpec (spec) where++import           Test.Hspec++import           Hpack.Syntax.Util++spec :: Spec+spec = do+  describe "hyphenize" $ do+    it "hyphenizes" $ do+      hyphenize "" "personFirstName" `shouldBe` "person-first-name"++    it "ignores leading underscores" $ do+      hyphenize "" "__personFirstName" `shouldBe` "person-first-name"++    context "when given a type name" $ do+      it "strips type name" $ do+        hyphenize "Person" "personFirstName" `shouldBe` "first-name"++      it "ignores trailing underscores in type name" $ do+        hyphenize "Person__" "personFirstName" `shouldBe` "first-name"
+ test/Hpack/SyntaxSpec.hs view
@@ -0,0 +1,148 @@+{-# LANGUAGE QuasiQuotes #-}+module Hpack.SyntaxSpec (spec) where++import           Helper+import           Data.String.Interpolate.IsString++import           Hpack.Syntax++spec :: Spec+spec = do+  describe "isValidUser" $ do+    it "rejects the empty string" $ do+      isValidUser "" `shouldBe` False++    it "accepts valid user names" $ do+      isValidUser "Foo-Bar-23" `shouldBe` True++    it "rejects dots" $ do+      isValidUser "foo.bar" `shouldBe` False++    it "rejects multiple consecutive hyphens" $ do+      isValidUser "foo--bar" `shouldBe` False++    it "rejects hyphens at the beginning" $ do+      isValidUser "-foo" `shouldBe` False++    it "rejects hyphens at the end" $ do+      isValidUser "foo-" `shouldBe` False++  describe "isValidRepo" $ do+    it "rejects the empty string" $ do+      isValidRepo "" `shouldBe` False++    it "rejects ." $ do+      isValidRepo "." `shouldBe` False++    it "rejects .." $ do+      isValidRepo ".." `shouldBe` False++    it "accepts underscores" $ do+      isValidRepo "foo_bar" `shouldBe` True++    it "accepts dots" $ do+      isValidRepo "foo.bar" `shouldBe` True++    it "accepts hyphens" $ do+      isValidRepo "foo-bar" `shouldBe` True++  describe "parseJSON" $ do+    context "when parsing Defaults" $ do+      context "with Object" $ do+        it "accepts Defaults from GitHub" $ do+          [i|+          github: sol/hpack+          ref: 0.1.0+          path: defaults.yaml+          |] `shouldParseAs` Right Defaults {+              defaultsGithubUser = "sol"+            , defaultsGithubRepo = "hpack"+            , defaultsRef = "0.1.0"+            , defaultsPath = ["defaults.yaml"]+            }++        it "rejects invalid user names" $ do+          [i|+          github: ../hpack+          ref: 0.1.0+          path: defaults.yaml+          |] `shouldParseAs` (Left "Error in $.github: invalid user name \"..\"" :: Either String Defaults)++        it "rejects invalid repository names" $ do+          [i|+          github: sol/..+          ref: 0.1.0+          path: defaults.yaml+          |] `shouldParseAs` (Left "Error in $.github: invalid repository name \"..\"" :: Either String Defaults)++        it "rejects invalid Git references" $ do+          [i|+          github: sol/hpack+          ref: ../foo/bar+          path: defaults.yaml+          |] `shouldParseAs` (Left "Error in $.ref: invalid Git reference \"../foo/bar\"" :: Either String Defaults)++        it "rejects \\ in path" $ do+          [i|+          github: sol/hpack+          ref: 0.1.0+          path: hpack\\defaults.yaml+          |] `shouldParseAs` (Left "Error in $.path: rejecting '\\' in \"hpack\\\\defaults.yaml\", please use '/' to separate path components" :: Either String Defaults)++        it "rejects : in path" $ do+          [i|+          github: sol/hpack+          ref: 0.1.0+          path: foo:bar.yaml+          |] `shouldParseAs` (Left "Error in $.path: rejecting ':' in \"foo:bar.yaml\"" :: Either String Defaults)++        it "rejects absolute paths" $ do+          [i|+          github: sol/hpack+          ref: 0.1.0+          path: /defaults.yaml+          |] `shouldParseAs` (Left "Error in $.path: rejecting absolute path \"/defaults.yaml\"" :: Either String Defaults)++        it "rejects .. in path" $ do+          [i|+          github: sol/hpack+          ref: 0.1.0+          path: ../../defaults.yaml+          |] `shouldParseAs` (Left "Error in $.path: rejecting \"..\" in \"../../defaults.yaml\"" :: Either String Defaults)++      context "with String" $ do+        it "accepts Defaults from GitHub" $ do+          [i|+          sol/hpack@0.1.0+          |] `shouldParseAs` Right Defaults {+              defaultsGithubUser = "sol"+            , defaultsGithubRepo = "hpack"+            , defaultsRef = "0.1.0"+            , defaultsPath = [".hpack", "defaults.yaml"]+            }++        it "rejects invalid user names" $ do+          [i|+          ../hpack@0.1.0+          |] `shouldParseAs` (Left "Error in $: invalid user name \"..\"" :: Either String Defaults)++        it "rejects invalid repository names" $ do+          [i|+          sol/..@0.1.0+          |] `shouldParseAs` (Left "Error in $: invalid repository name \"..\"" :: Either String Defaults)++        it "rejects invalid Git references" $ do+          [i|+          sol/pack@../foo/bar+          |] `shouldParseAs` (Left "Error in $: invalid Git reference \"../foo/bar\"" :: Either String Defaults)++        it "rejects missing Git reference" $ do+          [i|+          sol/hpack+          |] `shouldParseAs` (Left "Error in $: missing Git reference for \"sol/hpack\", the expected format is user/repo@ref" :: Either String Defaults)++      context "with neither Object nor String" $ do+        it "fails" $ do+          [i|+          10+          |] `shouldParseAs` (Left "Error in $: expected Object or String, encountered Number" :: Either String Defaults)