packages feed

stylish-haskell 0.9.3.0 → 0.9.4.0

raw patch · 8 files changed

+301/−24 lines, 8 filesdep +Cabaldep +randomdep ~basePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: Cabal, random

Dependency ranges changed: base

API changes (from Hackage documentation)

+ Language.Haskell.Stylish: [configCabal] :: Config -> Bool
- Language.Haskell.Stylish: Config :: [Step] -> Int -> [String] -> Newline -> Config
+ Language.Haskell.Stylish: Config :: [Step] -> Int -> [String] -> Newline -> Bool -> Config

Files

CHANGELOG view
@@ -1,5 +1,11 @@ # CHANGELOG +- 0.9.4.0 (2019-10-19)+    * Read language extensions from `.cabal` file (by Georgy Lukyanov)++- 0.9.3.1 (2019-10-08)+    * Fix CircleCI configuration+ - 0.9.3.0 (2019-10-08)     * Bump `optparse-applicative` to 0.15     * Don't remove page breaks in the trailing whitespace step (by Chris
data/stylish-haskell.yaml view
@@ -241,3 +241,9 @@ # language_extensions:   # - TemplateHaskell   # - QuasiQuotes++# Attempt to find the cabal file in ancestors of the current directory, and+# parse options (currently only language extensions) from that.+#+# Default: true+cabal: true
lib/Language/Haskell/Stylish/Config.hs view
@@ -17,22 +17,22 @@ import qualified Data.Aeson.Types                                 as A import qualified Data.ByteString                                  as B import qualified Data.FileEmbed                                   as FileEmbed-import           Data.List                                        (inits,-                                                                   intercalate)+import           Data.List                                        (intercalate,+                                                                   nub) import           Data.Map                                         (Map) import qualified Data.Map                                         as M import           Data.Maybe                                       (fromMaybe) import           Data.Yaml                                        (decodeEither',                                                                    prettyPrintParseException) import           System.Directory-import           System.FilePath                                  (joinPath,-                                                                   splitPath,-                                                                   (</>))+import           System.FilePath                                  ((</>)) import qualified System.IO                                        as IO (Newline (..),                                                                          nativeNewline)   --------------------------------------------------------------------------------+import qualified Language.Haskell.Stylish.Config.Cabal            as Cabal+import           Language.Haskell.Stylish.Config.Internal import           Language.Haskell.Stylish.Step import qualified Language.Haskell.Stylish.Step.Imports            as Imports import qualified Language.Haskell.Stylish.Step.LanguagePragmas    as LanguagePragmas@@ -54,6 +54,7 @@     , configColumns            :: Int     , configLanguageExtensions :: [String]     , configNewline            :: IO.Newline+    , configCabal              :: Bool     }  @@ -79,24 +80,19 @@     current    <- getCurrentDirectory     configPath <- getXdgDirectory XdgConfig "stylish-haskell"     home       <- getHomeDirectory-    mbConfig   <- search $+    mbConfig   <- search verbose $         [d </> configFileName | d <- ancestors current] ++         [configPath </> "config.yaml", home </> configFileName]      return mbConfig-  where-    -- All ancestors of a dir (including that dir)-    ancestors :: FilePath -> [FilePath]-    ancestors = init . map joinPath . reverse . inits . splitPath -    search :: [FilePath] -> IO (Maybe FilePath)-    search []       = return Nothing-    search (f : fs) = do-        -- TODO Maybe catch an error here, dir might be unreadable-        exists <- doesFileExist f-        verbose $ f ++ if exists then " exists" else " does not exist"-        if exists then return (Just f) else search fs-+search :: Verbose -> [FilePath] -> IO (Maybe FilePath)+search _ []             = return Nothing+search verbose (f : fs) = do+    -- TODO Maybe catch an error here, dir might be unreadable+    exists <- doesFileExist f+    verbose $ f ++ if exists then " exists" else " does not exist"+    if exists then return (Just f) else search verbose fs  -------------------------------------------------------------------------------- loadConfig :: Verbose -> Maybe FilePath -> IO Config@@ -107,9 +103,17 @@     case decodeEither' bytes of         Left err     -> error $             "Language.Haskell.Stylish.Config.loadConfig: " ++ prettyPrintParseException err-        Right config -> return config+        Right config -> do+          cabalLanguageExtensions <- if configCabal config+            then map show <$> Cabal.findLanguageExtensions verbose+            else pure [] +          return $ config+            { configLanguageExtensions = nub $+                configLanguageExtensions config ++ cabalLanguageExtensions+            } + -------------------------------------------------------------------------------- parseConfig :: A.Value -> A.Parser Config parseConfig (A.Object o) = do@@ -119,6 +123,7 @@         <*> (o A..:? "columns"             A..!= 80)         <*> (o A..:? "language_extensions" A..!= [])         <*> (o A..:? "newline"             >>= parseEnum newlines IO.nativeNewline)+        <*> (o A..:? "cabal"               A..!= True)      -- Then fill in the steps based on the partial config we already have     stepValues <- o A..: "steps" :: A.Parser [A.Value]
+ lib/Language/Haskell/Stylish/Config/Cabal.hs view
@@ -0,0 +1,92 @@+--------------------------------------------------------------------------------+module Language.Haskell.Stylish.Config.Cabal+    ( findLanguageExtensions+    ) where+++--------------------------------------------------------------------------------+import           Data.Either                              (isRight)+import           Data.List                                (nub)+import           Data.Maybe                               (maybeToList)+import qualified Distribution.PackageDescription          as Cabal+import qualified Distribution.PackageDescription.Parsec   as Cabal+import qualified Distribution.Simple.Utils                as Cabal+import qualified Distribution.Types.CondTree              as Cabal+import qualified Distribution.Verbosity                   as Cabal+import qualified Language.Haskell.Extension               as Language+import           Language.Haskell.Stylish.Verbose+import           System.Directory                         (getCurrentDirectory)+++--------------------------------------------------------------------------------+import           Language.Haskell.Stylish.Config.Internal+++--------------------------------------------------------------------------------+findLanguageExtensions :: Verbose -> IO [Language.KnownExtension]+findLanguageExtensions verbose =+    findCabalFile verbose >>=+    maybe (pure []) (readDefaultLanguageExtensions verbose)+++--------------------------------------------------------------------------------+-- | Find the closest .cabal file, possibly going up the directory structure.+findCabalFile :: Verbose -> IO (Maybe FilePath)+findCabalFile verbose = do+  potentialProjectRoots <- ancestors <$> getCurrentDirectory+  potentialCabalFile <- filter isRight <$>+    traverse Cabal.findPackageDesc potentialProjectRoots+  case potentialCabalFile of+    [Right cabalFile] -> return (Just cabalFile)+    _ -> do+      verbose $ ".cabal file not found, directories searched: " <>+        show potentialProjectRoots+      verbose $ "Stylish Haskell will work basing on LANGUAGE pragmas in source files."+      return Nothing+++--------------------------------------------------------------------------------+-- | Extract @default-extensions@ fields from a @.cabal@ file+readDefaultLanguageExtensions :: Verbose -> FilePath -> IO [Language.KnownExtension]+readDefaultLanguageExtensions verbose cabalFile = do+  verbose $ "Parsing " <> cabalFile <> "..."+  packageDescription <- Cabal.readGenericPackageDescription Cabal.silent cabalFile+  let library :: [Cabal.Library]+      library = maybeToList $ fst . Cabal.ignoreConditions <$>+        Cabal.condLibrary packageDescription++      subLibraries :: [Cabal.Library]+      subLibraries = fst . Cabal.ignoreConditions . snd <$>+        Cabal.condSubLibraries packageDescription++      executables :: [Cabal.Executable]+      executables = fst . Cabal.ignoreConditions . snd <$>+        Cabal.condExecutables packageDescription++      testSuites :: [Cabal.TestSuite]+      testSuites = fst . Cabal.ignoreConditions . snd <$>+        Cabal.condTestSuites packageDescription++      benchmarks :: [Cabal.Benchmark]+      benchmarks = fst . Cabal.ignoreConditions . snd <$>+        Cabal.condBenchmarks packageDescription++      gatherBuildInfos :: [Cabal.BuildInfo]+      gatherBuildInfos = map Cabal.libBuildInfo library <>+                         map Cabal.libBuildInfo subLibraries <>+                         map Cabal.buildInfo executables <>+                         map Cabal.testBuildInfo testSuites <>+                         map Cabal.benchmarkBuildInfo benchmarks++      defaultExtensions :: [Language.KnownExtension]+      defaultExtensions = map fromEnabled . filter isEnabled $+        concatMap Cabal.defaultExtensions gatherBuildInfos+        where isEnabled (Language.EnableExtension _) = True+              isEnabled _                            = False++              fromEnabled (Language.EnableExtension x) = x+              fromEnabled x                             =+                error $ "Language.Haskell.Stylish.Config.readLanguageExtensions: " <>+                        "invalid LANGUAGE pragma:  " <> show x+  verbose $ "Gathered default-extensions: " <> show defaultExtensions+  pure $ nub defaultExtensions
+ lib/Language/Haskell/Stylish/Config/Internal.hs view
@@ -0,0 +1,15 @@+--------------------------------------------------------------------------------+module Language.Haskell.Stylish.Config.Internal+    ( ancestors+    ) where+++--------------------------------------------------------------------------------+import           Data.List       (inits)+import           System.FilePath (joinPath, splitPath)+++--------------------------------------------------------------------------------+-- All ancestors of a dir (including that dir)+ancestors :: FilePath -> [FilePath]+ancestors = map joinPath . reverse . dropWhile null . inits . splitPath
stylish-haskell.cabal view
@@ -1,5 +1,5 @@ Name:          stylish-haskell-Version:       0.9.3.0+Version:       0.9.4.0 Synopsis:      Haskell code prettifier Homepage:      https://github.com/jaspervdj/stylish-haskell License:       BSD3@@ -41,6 +41,8 @@     Language.Haskell.Stylish.Align     Language.Haskell.Stylish.Block     Language.Haskell.Stylish.Config+    Language.Haskell.Stylish.Config.Cabal+    Language.Haskell.Stylish.Config.Internal     Language.Haskell.Stylish.Editor     Language.Haskell.Stylish.Parse     Language.Haskell.Stylish.Step@@ -52,6 +54,7 @@     aeson            >= 0.6    && < 1.5,     base             >= 4.8    && < 5,     bytestring       >= 0.9    && < 0.11,+    Cabal            >= 2.4    && < 2.5,     containers       >= 0.3    && < 0.7,     directory        >= 1.2.3  && < 1.4,     filepath         >= 1.1    && < 1.5,@@ -75,6 +78,7 @@     aeson            >= 0.6    && < 1.5,     base             >= 4.8    && < 5,     bytestring       >= 0.9    && < 0.11,+    Cabal            >= 2.4    && < 2.5,     containers       >= 0.3    && < 0.7,     directory        >= 1.2.3  && < 1.4,     filepath         >= 1.1    && < 1.5,@@ -94,18 +98,21 @@     Language.Haskell.Stylish.Align     Language.Haskell.Stylish.Block     Language.Haskell.Stylish.Config+    Language.Haskell.Stylish.Config.Cabal+    Language.Haskell.Stylish.Config.Internal+    Language.Haskell.Stylish.Config.Tests     Language.Haskell.Stylish.Editor     Language.Haskell.Stylish.Parse     Language.Haskell.Stylish.Parse.Tests     Language.Haskell.Stylish.Step-    Language.Haskell.Stylish.Step.SimpleAlign-    Language.Haskell.Stylish.Step.SimpleAlign.Tests-    Language.Haskell.Stylish.Step.Squash-    Language.Haskell.Stylish.Step.Squash.Tests     Language.Haskell.Stylish.Step.Imports     Language.Haskell.Stylish.Step.Imports.Tests     Language.Haskell.Stylish.Step.LanguagePragmas     Language.Haskell.Stylish.Step.LanguagePragmas.Tests+    Language.Haskell.Stylish.Step.SimpleAlign+    Language.Haskell.Stylish.Step.SimpleAlign.Tests+    Language.Haskell.Stylish.Step.Squash+    Language.Haskell.Stylish.Step.Squash.Tests     Language.Haskell.Stylish.Step.Tabs     Language.Haskell.Stylish.Step.Tabs.Tests     Language.Haskell.Stylish.Step.TrailingWhitespace@@ -120,10 +127,12 @@     HUnit                >= 1.2 && < 1.7,     test-framework       >= 0.4 && < 0.9,     test-framework-hunit >= 0.2 && < 0.4,+    random               >= 1.1,     -- Copied from regular dependencies...     aeson            >= 0.6    && < 1.5,     base             >= 4.8    && < 5,     bytestring       >= 0.9    && < 0.11,+    Cabal            >= 2.4    && < 2.5,     containers       >= 0.3    && < 0.7,     directory        >= 1.2.3  && < 1.4,     filepath         >= 1.1    && < 1.5,
+ tests/Language/Haskell/Stylish/Config/Tests.hs view
@@ -0,0 +1,142 @@+module Language.Haskell.Stylish.Config.Tests+    ( tests+    ) where+++--------------------------------------------------------------------------------+import           Control.Exception               hiding (assert)+import qualified Data.Set                        as Set+import           System.Directory+import           System.FilePath                 ((</>))+import           System.IO.Error+import           System.Random+import           Test.Framework                  (Test, testGroup)+import           Test.Framework.Providers.HUnit  (testCase)+import           Test.HUnit                      (Assertion, assert)+--------------------------------------------------------------------------------+import           Language.Haskell.Stylish.Config++--------------------------------------------------------------------------------+tests :: Test+tests = testGroup "Language.Haskell.Stylish.Config"+    [ testCase "Extensions extracted correctly from .cabal file"+               testExtensionsFromDotCabal+    , testCase "Extensions extracted correctly from .stylish-haskell.yaml file"+               testExtensionsFromDotStylish+    , testCase "Extensions extracted correctly from .stylish-haskell.yaml and .cabal files"+               testExtensionsFromBoth+    ]+--------------------------------------------------------------------------------++-- | Create a temporary directory with a randomised name built from the template provided+createTempDirectory :: String -> IO FilePath+createTempDirectory template  = do+  tmpRootDir <- getTemporaryDirectory+  dirId <- randomIO :: IO Word+  findTempName tmpRootDir dirId+  where+    findTempName :: FilePath -> Word -> IO FilePath+    findTempName tmpRootDir x = do+      let dirpath = tmpRootDir </> template ++ show x+      r <- try $ createDirectory dirpath+      case r of+        Right _ -> return dirpath+        Left  e | isAlreadyExistsError e -> findTempName tmpRootDir (x+1)+                | otherwise              -> ioError e++-- | Perform an action inside a temporary directory tree and purge the tree afterwords+withTestDirTree :: IO a -> IO a+withTestDirTree action = bracket+    ((,) <$> getCurrentDirectory <*> createTempDirectory "stylish_haskell")+    (\(current, temp) ->+        setCurrentDirectory current *>+        removeDirectoryRecursive temp)+    (\(_, temp) -> setCurrentDirectory temp *> action)++-- | Put an example config files (.cabal/.stylish-haskell.yaml/both)+-- into the current directory and extract extensions from it.+createFilesAndGetExtensions :: [(FilePath, String)] -> IO Extensions+createFilesAndGetExtensions files = withTestDirTree $ do+  mapM_ (\(k, v) -> writeFile k v) files+  -- create an empty directory and change into it+  createDirectory "src"+  setCurrentDirectory "src"+  -- from that directory read the config file and extract extensions+  -- to make sure the search for .cabal file works+  config <- loadConfig (const (pure ())) Nothing+  pure $ configLanguageExtensions config++--------------------------------------------------------------------------------+testExtensionsFromDotCabal :: Assertion+testExtensionsFromDotCabal =+  assert $ (expected ==) . Set.fromList <$>+    createFilesAndGetExtensions [("test.cabal", dotCabal True)]+    where+      expected = Set.fromList ["ScopedTypeVariables", "DataKinds"]++--------------------------------------------------------------------------------+testExtensionsFromDotStylish :: Assertion+testExtensionsFromDotStylish =+  assert $ (expected ==) . Set.fromList <$>+    createFilesAndGetExtensions [(".stylish-haskell.yaml", dotStylish)]+    where+      expected = Set.fromList ["TemplateHaskell", "QuasiQuotes"]++--------------------------------------------------------------------------------+testExtensionsFromBoth :: Assertion+testExtensionsFromBoth =+  assert $ (expected ==) . Set.fromList <$>+    createFilesAndGetExtensions [ ("test.cabal", dotCabal True)+                                , (".stylish-haskell.yaml", dotStylish)]+    where+      expected = Set.fromList+        ["ScopedTypeVariables", "DataKinds", "TemplateHaskell", "QuasiQuotes"]++-- | Example cabal file borrowed from+--   https://www.haskell.org/cabal/users-guide/developing-packages.html+--   with some default-extensions added+dotCabal :: Bool -> String+dotCabal includeExtensions = unlines $+  [ "name:            TestPackage"+  , "version:         0.0"+  , "synopsis:        Package with library and two programs"+  , "license:         BSD3"+  , "author:          Angela Author"+  , "build-type:      Simple"+  , "cabal-version:   >= 1.2"+  , ""+  , "library"+  , "   build-depends:   HUnit"+  , "   exposed-modules: A, B, C"+  ] +++  [if includeExtensions then "   default-extensions: ScopedTypeVariables"+                        else ""]+  +++  [ ""+  , "executable program1"+  , "   main-is:         Main.hs"+  , "   hs-source-dirs:  prog1"+  , "   other-modules:   A, B"+  ] +++  [if includeExtensions then "   default-extensions: DataKinds"+                        else ""]++-- | Example .stylish-haskell.yaml+dotStylish :: String+dotStylish = unlines $+  [ "steps:"+  , "  - imports:"+  , "      align: none"+  , "      list_align: after_alias"+  , "      long_list_align: inline"+  , "      separate_lists: true"+  , "  - language_pragmas:"+  , "      style: vertical"+  , "      align: false"+  , "      remove_redundant: true"+  , "  - trailing_whitespace: {}"+  , "columns: 110"+  , "language_extensions:"+  , "  - TemplateHaskell"+  , "  - QuasiQuotes"+  ]
tests/TestSuite.hs view
@@ -9,6 +9,7 @@   --------------------------------------------------------------------------------+import qualified Language.Haskell.Stylish.Config.Tests import qualified Language.Haskell.Stylish.Parse.Tests import qualified Language.Haskell.Stylish.Step.Imports.Tests import qualified Language.Haskell.Stylish.Step.LanguagePragmas.Tests@@ -23,6 +24,7 @@ main :: IO () main = defaultMain     [ Language.Haskell.Stylish.Parse.Tests.tests+    , Language.Haskell.Stylish.Config.Tests.tests     , Language.Haskell.Stylish.Step.Imports.Tests.tests     , Language.Haskell.Stylish.Step.LanguagePragmas.Tests.tests     , Language.Haskell.Stylish.Step.SimpleAlign.Tests.tests