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 +6/−0
- data/stylish-haskell.yaml +6/−0
- lib/Language/Haskell/Stylish/Config.hs +24/−19
- lib/Language/Haskell/Stylish/Config/Cabal.hs +92/−0
- lib/Language/Haskell/Stylish/Config/Internal.hs +15/−0
- stylish-haskell.cabal +14/−5
- tests/Language/Haskell/Stylish/Config/Tests.hs +142/−0
- tests/TestSuite.hs +2/−0
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