packages feed

cabal-fmt 0.1.4 → 0.1.5

raw patch · 17 files changed

+431/−141 lines, 17 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- CabalFmt.Refactoring.Type: traverseFields :: Applicative f => RefactoringOfField' r f -> [Field CommentsPragmas] -> f [Field CommentsPragmas]
- CabalFmt.Refactoring.Type: type Refactoring' r m = [Field CommentsPragmas] -> m [Field CommentsPragmas]
- CabalFmt.Refactoring.Type: type RefactoringOfField' r m = Name CommentsPragmas -> [FieldLine CommentsPragmas] -> m (Name CommentsPragmas, [FieldLine CommentsPragmas])
+ CabalFmt.Fields: instance GHC.Base.Monoid (CabalFmt.Fields.FieldDescrs s a)
+ CabalFmt.Fields.SourceFiles: fileFields :: [FieldName]
+ CabalFmt.Fields.SourceFiles: sourceFilesF :: [FieldDescrs () ()]
+ CabalFmt.Glob: Glob :: FilePath -> [GlobPiece] -> Glob
+ CabalFmt.Glob: GlobChar :: Char -> GlobChar
+ CabalFmt.Glob: GlobPiece :: NonEmpty GlobChar -> GlobPiece
+ CabalFmt.Glob: GlobStar :: GlobChar
+ CabalFmt.Glob: GlobStarStar :: GlobPiece
+ CabalFmt.Glob: data Glob
+ CabalFmt.Glob: data GlobChar
+ CabalFmt.Glob: data GlobPiece
+ CabalFmt.Glob: instance GHC.Show.Show CabalFmt.Glob.Glob
+ CabalFmt.Glob: instance GHC.Show.Show CabalFmt.Glob.GlobChar
+ CabalFmt.Glob: instance GHC.Show.Show CabalFmt.Glob.GlobPiece
+ CabalFmt.Glob: match :: Glob -> FilePath -> Bool
+ CabalFmt.Glob: parseGlob :: String -> Either String Glob
+ CabalFmt.Pragma: FieldPragma :: FieldPragma -> Pragma
+ CabalFmt.Pragma: GlobalPragma :: GlobalPragma -> Pragma
+ CabalFmt.Pragma: PragmaGlobFiles :: Glob -> FieldPragma
+ CabalFmt.Pragma: data FieldPragma
+ CabalFmt.Pragma: data GlobalPragma
+ CabalFmt.Pragma: instance GHC.Show.Show CabalFmt.Pragma.FieldPragma
+ CabalFmt.Pragma: instance GHC.Show.Show CabalFmt.Pragma.GlobalPragma
+ CabalFmt.Refactoring: refactor :: forall m r. MonadCabalFmt r m => [Field CommentsPragmas] -> m [Field CommentsPragmas]
+ CabalFmt.Refactoring: type CommentsPragmas = (Comments, [FieldPragma])
+ CabalFmt.Refactoring.GlobFiles: refactoringGlobFiles :: FieldRefactoring
+ CabalFmt.Refactoring.Type: type FieldRefactoring = forall r m. MonadCabalFmt r m => (Field CommentsPragmas -> m (Maybe (Field CommentsPragmas)))
- CabalFmt.Pragma: PragmaExpandModules :: FilePath -> [ModuleName] -> Pragma
+ CabalFmt.Pragma: PragmaExpandModules :: FilePath -> [ModuleName] -> FieldPragma
- CabalFmt.Pragma: PragmaFragment :: FilePath -> Pragma
+ CabalFmt.Pragma: PragmaFragment :: FilePath -> FieldPragma
- CabalFmt.Pragma: PragmaOptIndent :: Int -> Pragma
+ CabalFmt.Pragma: PragmaOptIndent :: Int -> GlobalPragma
- CabalFmt.Pragma: PragmaOptTabular :: Bool -> Pragma
+ CabalFmt.Pragma: PragmaOptTabular :: Bool -> GlobalPragma
- CabalFmt.Refactoring.ExpandExposedModules: refactoringExpandExposedModules :: Refactoring
+ CabalFmt.Refactoring.ExpandExposedModules: refactoringExpandExposedModules :: FieldRefactoring
- CabalFmt.Refactoring.Fragments: refactoringFragments :: Refactoring
+ CabalFmt.Refactoring.Fragments: refactoringFragments :: FieldRefactoring
- CabalFmt.Refactoring.Type: rewriteFields :: Monad m => (Field CommentsPragmas -> m (Maybe (Field CommentsPragmas))) -> [Field CommentsPragmas] -> m [Field CommentsPragmas]
+ CabalFmt.Refactoring.Type: rewriteFields :: MonadCabalFmt r m => (Field CommentsPragmas -> m (Maybe (Field CommentsPragmas))) -> [Field CommentsPragmas] -> m [Field CommentsPragmas]
- CabalFmt.Refactoring.Type: type CommentsPragmas = (Comments, [Pragma])
+ CabalFmt.Refactoring.Type: type CommentsPragmas = (Comments, [FieldPragma])

Files

Changelog.md view
@@ -1,3 +1,10 @@+# 0.1.5++- Don't print redundant `-any` in `impl`+- Add `glob-files` pragma for source file fields (`c-sources`, ...)+  Glob syntax supports only stars (i.e. no `{}` etc. extras).+- Source file fields are now sorted+ # 0.1.4  - Add `-n` / `--no-cabal-file` to format cabal like, but not package files.
cabal-fmt.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.2 name:               cabal-fmt-version:            0.1.4+version:            0.1.5 synopsis:           Format .cabal files category:           Development description:@@ -47,7 +47,9 @@     CabalFmt.Fields.BuildDepends     CabalFmt.Fields.Extensions     CabalFmt.Fields.Modules+    CabalFmt.Fields.SourceFiles     CabalFmt.Fields.TestedWith+    CabalFmt.Glob     CabalFmt.Monad     CabalFmt.Options     CabalFmt.Parser@@ -56,6 +58,7 @@     CabalFmt.Refactoring     CabalFmt.Refactoring.ExpandExposedModules     CabalFmt.Refactoring.Fragments+    CabalFmt.Refactoring.GlobFiles     CabalFmt.Refactoring.Type    other-extensions:
fixtures/Cabal.format view
@@ -24,14 +24,13 @@ -- we can bootstrap.  extra-source-files:-  README.md-  tests/README.md   ChangeLog.md   doc/bugs-and-stability.rst   doc/concepts-and-development.rst   doc/conf.py   doc/config-and-install.rst   doc/developing-packages.rst+  doc/file-format-changelog.rst   doc/images/Cabal-dark.png   doc/index.rst   doc/installing-packages.rst@@ -39,9 +38,13 @@   doc/misc.rst   doc/nix-local-build-overview.rst   doc/nix-local-build.rst-  doc/file-format-changelog.rst   doc/README.md   doc/references.inc+  README.md+  tests/hackage/check.sh+  tests/hackage/download.sh+  tests/hackage/unpack.sh+  tests/misc/ghc-supported-languages.hs   tests/ParserTests/errors/common1.cabal   tests/ParserTests/errors/common1.errors   tests/ParserTests/errors/common2.cabal@@ -94,11 +97,6 @@   tests/ParserTests/ipi/transformers.cabal   tests/ParserTests/ipi/transformers.expr   tests/ParserTests/ipi/transformers.format-  tests/ParserTests/regressions/MiniAgda.cabal-  tests/ParserTests/regressions/MiniAgda.check-  tests/ParserTests/regressions/Octree-0.5.cabal-  tests/ParserTests/regressions/Octree-0.5.expr-  tests/ParserTests/regressions/Octree-0.5.format   tests/ParserTests/regressions/bad-glob-syntax.cabal   tests/ParserTests/regressions/bad-glob-syntax.check   tests/ParserTests/regressions/cc-options-with-optimization.cabal@@ -151,17 +149,22 @@   tests/ParserTests/regressions/leading-comma.cabal   tests/ParserTests/regressions/leading-comma.expr   tests/ParserTests/regressions/leading-comma.format+  tests/ParserTests/regressions/MiniAgda.cabal+  tests/ParserTests/regressions/MiniAgda.check   tests/ParserTests/regressions/multiple-libs-2.cabal   tests/ParserTests/regressions/multiple-libs-2.check   tests/ParserTests/regressions/multiple-libs-2.expr   tests/ParserTests/regressions/multiple-libs-2.format-  tests/ParserTests/regressions/noVersion.cabal-  tests/ParserTests/regressions/noVersion.expr-  tests/ParserTests/regressions/noVersion.format   tests/ParserTests/regressions/nothing-unicode.cabal   tests/ParserTests/regressions/nothing-unicode.check   tests/ParserTests/regressions/nothing-unicode.expr   tests/ParserTests/regressions/nothing-unicode.format+  tests/ParserTests/regressions/noVersion.cabal+  tests/ParserTests/regressions/noVersion.expr+  tests/ParserTests/regressions/noVersion.format+  tests/ParserTests/regressions/Octree-0.5.cabal+  tests/ParserTests/regressions/Octree-0.5.expr+  tests/ParserTests/regressions/Octree-0.5.format   tests/ParserTests/regressions/pre-1.6-glob.cabal   tests/ParserTests/regressions/pre-1.6-glob.check   tests/ParserTests/regressions/pre-2.4-globstar.cabal@@ -201,10 +204,7 @@   tests/ParserTests/warnings/unknownsection.cabal   tests/ParserTests/warnings/utf8.cabal   tests/ParserTests/warnings/versiontag.cabal-  tests/hackage/check.sh-  tests/hackage/download.sh-  tests/hackage/unpack.sh-  tests/misc/ghc-supported-languages.hs+  tests/README.md  -- Generated with 'make gen-extra-source-files' -- Do NOT edit this section manually; instead, run the script.
fixtures/simple-example.cabal view
@@ -36,6 +36,13 @@   hs-source-dirs:      src   default-language:    Haskell2010 +  if impl(ghcjs)+    cpp-options: -DFOOBAR++  if impl(ghc)+    -- cabal-fmt: glob-files cbits/**/*.c+    c-sources:+ test-suite doctests   type:                exitcode-stdio-1.0   main-is:             doctests.hs
fixtures/simple-example.format view
@@ -42,6 +42,16 @@   hs-source-dirs:     src   default-language:   Haskell2010 +  if impl(ghcjs)+    cpp-options: -DFOOBAR++  if impl(ghc)+    -- cabal-fmt: glob-files cbits/**/*.c+    c-sources:+      cbits/source1.c+      cbits/source2.c+      cbits/sub/source3.c+ test-suite doctests   type:             exitcode-stdio-1.0   main-is:          doctests.hs
src/CabalFmt.hs view
@@ -9,8 +9,9 @@ -- module CabalFmt (cabalFmt) where -import Control.Monad        (foldM, join)+import Control.Monad        (join) import Control.Monad.Reader (asks, local)+import Data.Either          (partitionEithers)  import qualified Data.ByteString                              as BS import qualified Distribution.CabalSpecVersion                as C@@ -27,6 +28,7 @@ import qualified Distribution.Types.GenericPackageDescription as C import qualified Distribution.Types.PackageDescription        as C import qualified Distribution.Types.Version                   as C+import qualified Distribution.Types.VersionRange              as C import qualified Text.PrettyPrint                             as PP  import CabalFmt.Comments@@ -34,6 +36,7 @@ import CabalFmt.Fields.BuildDepends import CabalFmt.Fields.Extensions import CabalFmt.Fields.Modules+import CabalFmt.Fields.SourceFiles import CabalFmt.Fields.TestedWith import CabalFmt.Monad import CabalFmt.Options@@ -53,16 +56,20 @@      -- parse pragmas     let parse c = case parsePragmas c of (ws, ps) -> traverse_ displayWarning ws *> return (c, ps)-    inputFieldsP <- traverse (traverse parse) inputFieldsC+    inputFieldsP' <- traverse (traverse parse) inputFieldsC     endCommentsPragmas <- case parsePragmas endComments of         (ws, ps) -> traverse_ displayWarning ws *> return ps      -- apply refactorings-    inputFieldsR  <- foldM (&) inputFieldsP refactorings+    let inputFieldsP :: [C.Field CommentsPragmas]+        inputFieldsP = map (fmap (fmap (snd . partitionPragmas))) inputFieldsP' +    inputFieldsR  <- refactor inputFieldsP+     -- options morphisms-    let pragmas :: [Pragma]-        pragmas = foldMap (foldMap snd) inputFieldsR <> endCommentsPragmas+    let pragmas :: [GlobalPragma]+        pragmas = fst $ partitionPragmas $+            foldMap (foldMap snd) inputFieldsP' <> endCommentsPragmas          optsEndo :: OptionsMorphism         optsEndo = foldMap pragmaToOM pragmas@@ -94,16 +101,6 @@ fromComments (Comments bss) = map C.fromUTF8BS bss  ---------------------------------------------------------------------------------- Refactorings----------------------------------------------------------------------------------refactorings :: MonadCabalFmt r m => [Refactoring' r m]-refactorings =-    [ refactoringFragments-    , refactoringExpandExposedModules-    ]--------------------------------------------------------------------------------- -- Field prettyfying ------------------------------------------------------------------------------- @@ -129,6 +126,7 @@     <> exposedModulesF     <> otherModulesF     <> testedWithF opts+    <> mconcat sourceFilesF     <> coerceFieldDescrs C.packageDescriptionFieldGrammar     <> coerceFieldDescrs C.buildInfoFieldGrammar @@ -163,14 +161,19 @@ ppConfVar (C.OS os)     = PP.text "os"   PP.<> PP.parens (C.pretty os) ppConfVar (C.Arch arch) = PP.text "arch" PP.<> PP.parens (C.pretty arch) ppConfVar (C.Flag name) = PP.text "flag" PP.<> PP.parens (C.pretty name)-ppConfVar (C.Impl c v)  = PP.text "impl" PP.<> PP.parens (C.pretty c PP.<+> C.pretty v)+ppConfVar (C.Impl c v)+    | v == C.anyVersion = PP.text "impl" PP.<> PP.parens (C.pretty c)+    | otherwise         = PP.text "impl" PP.<> PP.parens (C.pretty c PP.<+> C.pretty v)  ------------------------------------------------------------------------------- -- Pragma to OM ------------------------------------------------------------------------------- -pragmaToOM :: Pragma -> OptionsMorphism+partitionPragmas :: [Pragma] -> ([GlobalPragma], [FieldPragma])+partitionPragmas = partitionEithers . map p where+    p (GlobalPragma x) = Left x+    p (FieldPragma x)  = Right x++pragmaToOM :: GlobalPragma -> OptionsMorphism pragmaToOM (PragmaOptIndent n)    = mkOptionsMorphism $ \opts -> opts { optIndent = n } pragmaToOM (PragmaOptTabular b)   = mkOptionsMorphism $ \opts -> opts { optTabular = b }-pragmaToOM PragmaExpandModules {} = mempty-pragmaToOM PragmaFragment {}      = mempty
src/CabalFmt/Fields.hs view
@@ -54,6 +54,10 @@ instance Semigroup (FieldDescrs s a) where     F a <> F b = F (a <> b) +instance Monoid (FieldDescrs s a) where+    mempty  = F Map.empty+    mappend = (<>)+ instance Applicative (FieldDescrs s) where     pure _  = F mempty     f <*> x = F (mappend (runF f) (runF x))
+ src/CabalFmt/Fields/SourceFiles.hs view
@@ -0,0 +1,66 @@+-- |+-- License: GPL-3.0-or-later+-- Copyright: Oleg Grenrus+{-# LANGUAGE OverloadedStrings #-}+module CabalFmt.Fields.SourceFiles (+    sourceFilesF,+    fileFields,+    ) where++import System.FilePath.Posix (splitDirectories)++import qualified Distribution.Fields          as C+import qualified Distribution.Parsec          as C+import qualified Distribution.Parsec.Newtypes as C+import qualified Distribution.Pretty          as C+import qualified Text.PrettyPrint             as PP++import CabalFmt.Fields+import CabalFmt.Prelude++sourceFilesF :: [FieldDescrs () ()]+sourceFilesF =+    [ singletonF f pretty parse+    | f <- fileFields+    ]++fileFields :: [C.FieldName]+fileFields =+    [ "extra-source-files"+    , "extra-doc-files"+    , "data-files"+    , "license-files"+    , "asm-sources"+    , "cmm-sources"+    , "c-sources"+    , "cxx-sources"+    , "js-sources"+    , "includes"+    , "install-includes"+    ]++parse :: C.CabalParsing m => m [FilePath]+parse = unpack' (C.alaList' C.VCat C.FilePathNT) <$> C.parsec++pretty :: [FilePath] -> PP.Doc+pretty+    = PP.vcat . map C.showFilePath+    . nub+    . sortBy (cmp `on` map strToLower . splitDirectories)+  where+    cmp a b = case dropCommonPrefix a b of+        ([], [])  -> EQ+        ([], _:_) -> LT+        (_:_, []) -> GT+        (a', b')  -> compare a' b'++strToLower :: String -> String+strToLower = map toLower++dropCommonPrefix :: Eq a => [a] -> [a] -> ([a], [a])+dropCommonPrefix [] [] = ([], [])+dropCommonPrefix [] ys = ([], ys)+dropCommonPrefix xs [] = (xs, [])+dropCommonPrefix xs@(x:xs') ys@(y:ys')+    | x == y    = dropCommonPrefix xs' ys'+    | otherwise = (xs, ys)
+ src/CabalFmt/Glob.hs view
@@ -0,0 +1,70 @@+module CabalFmt.Glob where++import Data.List             (isInfixOf)+import Data.List.NonEmpty    (NonEmpty (..))+import System.FilePath.Posix (splitDirectories)++import CabalFmt.Prelude++data Glob = Glob FilePath [GlobPiece]+  deriving Show++data GlobPiece+    = GlobStarStar+    | GlobPiece (NonEmpty GlobChar)+  deriving Show++data GlobChar+    = GlobStar+    | GlobChar Char+  deriving Show++-- | Match glob+--+-- >>> let Right g = parseGlob "cbits/**/*.c"+--+-- >>> map (match g) ["foo", "cbits/header.h", "cbits/source.c", "cbits/dir/source.c"]+-- [False,False,True,True]+--+match :: Glob -> FilePath -> Bool+match (Glob g1 gs0) fp = go0 (splitDirectories fp) where+    go0 []     = False+    go0 (p:ps) = if p == g1 then go ps gs0 else False++    go :: [FilePath] -> [GlobPiece] -> Bool+    go []     []                  = True+    go []     (_:_)               = False+    go (_:_)  []                  = False+    go (s:ss) (GlobStarStar : gs) = go (s:ss) gs || go ss (GlobStarStar : gs)+    go (s:ss) (GlobPiece cs : gs) = matches s (toList cs) && go ss gs+++    matches :: FilePath -> [GlobChar] -> Bool+    matches []     []                = True+    matches (_:_)  []                = False+    matches []     (_:_)             = False+    matches (x:xs) (GlobStar : cs)   = matches (x:xs) cs || matches xs (GlobStar : cs)+    matches (x:xs) (GlobChar c : cs) = if x == c then matches xs cs else False++parseGlob :: String -> Either String Glob+parseGlob input = case splitDirectories input of+    []     -> Left "empty path"+    (x:xs) -> do+        p <- parseFirstPiece x+        ps <- traverse parsePiece xs+        return (Glob p ps)+  where+    parseFirstPiece :: String -> Either String FilePath+    parseFirstPiece ""                    = Left "empty path segment"+    parseFirstPiece s | "*" `isInfixOf` s = Left "wild card in first path segment"+    parseFirstPiece s                     = Right s++    parsePiece :: String -> Either String GlobPiece+    parsePiece ""                     = Left "empty path segment"+    parsePiece "**"                   = Right GlobStarStar+    parsePiece s | "**" `isInfixOf` s = Left $ "** inside path segment: " ++ s+    parsePiece (c:cs)                 = Right (GlobPiece (parseChar c :| map parseChar cs))++    parseChar :: Char -> GlobChar+    parseChar '*' = GlobStar+    parseChar c   = GlobChar c
src/CabalFmt/Monad.hs view
@@ -19,13 +19,16 @@     runCabalFmtIO,     ) where -import Control.Exception      (IOException, catch, throwIO, try, displayException)+import Control.Exception+       (IOException, catch, displayException, throwIO, try) import Control.Monad          (when) import Control.Monad.Except   (MonadError (..)) import Control.Monad.IO.Class (MonadIO (..)) import Control.Monad.Reader   (MonadReader (..), ReaderT (..), asks, runReaderT) import Control.Monad.Writer   (WriterT, runWriterT, tell) import Data.Bifunctor         (first)+import Data.List              (isPrefixOf, stripPrefix)+import Data.Maybe             (mapMaybe) import System.Exit            (exitFailure) import System.FilePath        ((</>)) import System.IO              (hPutStrLn, stderr)@@ -77,8 +80,17 @@     local f (CabalFmt m) = CabalFmt $ local (first f) m  instance MonadCabalFmt Options CabalFmt where-    listDirectory _      = return []-    doesDirectoryExist _ = return False+    listDirectory dir = CabalFmt $ do+        files <- asks snd+        return $ mapMaybe f (Map.keys files)+      where+        f :: FilePath -> Maybe FilePath+        f fp = do+            rest <- stripPrefix (dir ++ "/") fp+            return $ takeWhile (/= '/') rest+    doesDirectoryExist dir = CabalFmt $ do+        files <- asks snd+        return (any (isPrefixOf (dir ++ "/")) (Map.keys files))     readFileBS p         = CabalFmt $ do         files <- asks snd         return (maybe (IOError "doesn't exist") Contents $ Map.lookup p files)
src/CabalFmt/Pragma.hs view
@@ -9,14 +9,34 @@  import CabalFmt.Prelude import CabalFmt.Comments+import CabalFmt.Glob +-------------------------------------------------------------------------------+-- Types+-------------------------------------------------------------------------------+ data Pragma+    = FieldPragma FieldPragma+    | GlobalPragma GlobalPragma+  deriving (Show)++-- | Pragmas applied per field+data FieldPragma+    = PragmaExpandModules FilePath [C.ModuleName]+    | PragmaGlobFiles Glob+    | PragmaFragment FilePath+  deriving (Show)++-- | Pragmas affecting global output+data GlobalPragma     = PragmaOptIndent Int     | PragmaOptTabular Bool-    | PragmaExpandModules FilePath [C.ModuleName]-    | PragmaFragment FilePath   deriving (Show) +-------------------------------------------------------------------------------+-- Parser+-------------------------------------------------------------------------------+ -- | Parse pragma from 'ByteString'. -- -- An error ('Left') is reported only if input 'ByteString' starts with @-- cabal-fmt:@.@@ -37,8 +57,9 @@         case t of             "expand"     -> expandModules             "indent"     -> indent-            "tabular"    -> return $ PragmaOptTabular True-            "no-tabular" -> return $ PragmaOptTabular False+            "glob-files" -> globFiles+            "tabular"    -> return $ GlobalPragma $ PragmaOptTabular True+            "no-tabular" -> return $ GlobalPragma $ PragmaOptTabular False             "fragment"   -> fragment             _            -> fail $ "Unknown pragma " ++ t @@ -47,19 +68,27 @@         C.spaces         dir <- C.parsecToken         mns <- C.many (C.space *> C.spaces *> C.char '-' *> C.parsec)-        return (PragmaExpandModules dir mns)+        return $ FieldPragma $ PragmaExpandModules dir mns      indent :: C.ParsecParser Pragma     indent = do         C.spaces         n <- C.integral-        return $ PragmaOptIndent n+        return $ GlobalPragma $ PragmaOptIndent n      fragment :: C.ParsecParser Pragma     fragment = do         C.spaces         fn <- C.parsecToken-        return (PragmaFragment fn)+        return $ FieldPragma $ PragmaFragment fn++    globFiles :: C.ParsecParser Pragma+    globFiles = do+        C.spaces+        t <- C.parsecToken+        case parseGlob t of+            Right g -> return $ FieldPragma $ PragmaGlobFiles g+            Left e  -> C.unexpected e  stripWhitespace :: ByteString -> ByteString stripWhitespace bs = case BS.uncons bs of
src/CabalFmt/Refactoring.hs view
@@ -1,10 +1,57 @@+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE ScopedTypeVariables #-} -- | -- License: GPL-3.0-or-later -- Copyright: Oleg Grenrus module CabalFmt.Refactoring (-    module X,+    CommentsPragmas,+    refactor,     ) where -import CabalFmt.Refactoring.ExpandExposedModules as X-import CabalFmt.Refactoring.Fragments            as X-import CabalFmt.Refactoring.Type                 as X+import qualified Distribution.Fields as C++import CabalFmt.Monad+import CabalFmt.Refactoring.ExpandExposedModules+import CabalFmt.Refactoring.Fragments+import CabalFmt.Refactoring.GlobFiles+import CabalFmt.Refactoring.Type+import CabalFmt.Fields.SourceFiles ++-------------------------------------------------------------------------------+-- Refactorings+-------------------------------------------------------------------------------++refactor :: forall m r. MonadCabalFmt r m => [C.Field CommentsPragmas] -> m [C.Field CommentsPragmas]+refactor = rewriteFields rewrite+  where+    rewrite :: C.Field CommentsPragmas -> m (Maybe (C.Field CommentsPragmas))+    rewrite f@(C.Field (C.Name _ n) _)+        | n == "exposed-modules" || n == "other-modules" = combine+            [ refactoringFragments+            , refactoringExpandExposedModules+            ] f+        | n `elem` fileFields = combine+            [ refactoringFragments+            , refactoringGlobFiles+            ] f+        | otherwise = combine+            [ refactoringFragments+            ] f++    rewrite f@(C.Section _ _ _)+        | otherwise = combine+            [ refactoringFragments+            ] f++-- | Try refactorings in turn,+-- considering it done if one applies.+combine+    :: Monad m+    => [C.Field CommentsPragmas -> m (Maybe (C.Field CommentsPragmas))]+    -> C.Field CommentsPragmas -> m (Maybe (C.Field CommentsPragmas))+combine []     _ = return Nothing+combine (r:rs) f = do+    m <- r f+    case m of+        Nothing -> combine rs f+        Just f' -> return (Just f')
src/CabalFmt/Refactoring/ExpandExposedModules.hs view
@@ -15,33 +15,31 @@ import CabalFmt.Pragma import CabalFmt.Refactoring.Type -refactoringExpandExposedModules :: Refactoring-refactoringExpandExposedModules = traverseFields refact where-    refact :: RefactoringOfField-    refact name@(C.Name (_, pragmas) n) fls-        | n == "exposed-modules" || n == "other-modules" = do-            dirs <- parse pragmas-            files <- traverseOf (traverse . _1) getFiles dirs+refactoringExpandExposedModules :: FieldRefactoring+refactoringExpandExposedModules C.Section {} = pure Nothing+refactoringExpandExposedModules (C.Field name@(C.Name (_, pragmas) _n) fls) = do+    dirs <- parse pragmas+    files <- traverseOf (traverse . _1) getFiles dirs -            let newModules :: [C.FieldLine CommentsPragmas]-                newModules = catMaybes-                    [ return $ C.FieldLine mempty $ toUTF8BS $ intercalate "." parts-                    | (files', mns) <- files-                    , file <- files'-                    , let parts = splitDirectories $ dropExtension file-                    , all C.validModuleComponent parts-                    , let mn = C.fromComponents parts-                    , mn `notElem` mns-                    ]+    let newModules :: [C.FieldLine CommentsPragmas]+        newModules = catMaybes+            [ return $ C.FieldLine mempty $ toUTF8BS $ intercalate "." parts+            | (files', mns) <- files+            , file <- files'+            , let parts = splitDirectories $ dropExtension file+            , all C.validModuleComponent parts+            , let mn = C.fromComponents parts+            , mn `notElem` mns+            ] -            pure (name, newModules ++ fls)-        | otherwise = pure (name, fls)+    pure $ case newModules of+        [] -> Nothing+        _  -> Just (C.Field name (newModules ++ fls)) -    parse :: MonadCabalFmt r m => [Pragma] -> m [(FilePath, [C.ModuleName])]+  where+    parse :: MonadCabalFmt r m => [FieldPragma] -> m [(FilePath, [C.ModuleName])]     parse = fmap mconcat . traverse go where         go (PragmaExpandModules fp mns) = return [ (fp, mns) ]         go p = do             displayWarning $ "Skipped pragma " ++ show p             return []--
src/CabalFmt/Refactoring/Fragments.hs view
@@ -20,64 +20,66 @@ import CabalFmt.Prelude import CabalFmt.Refactoring.Type -refactoringFragments :: Refactoring-refactoringFragments = rewriteFields refact where-    refact :: MonadCabalFmt r m => C.Field CommentsPragmas -> m (Maybe (C.Field CommentsPragmas))-    refact field = do-        parse (getPragmas field) >>= \mp -> case mp of-            Nothing -> pure Nothing-            Just p  -> readFileBS p >>= \mcontents -> case mcontents of-                NoIO -> pure Nothing-                IOError err -> do-                    displayWarning $ "Fragment " ++ p ++ " failed to read: " ++ show err-                    pure Nothing-                Contents c  -> do-                    fields <- parseFields c-                    case (field, fields) of-                        (_, []) -> do-                            displayWarning $ "Fragment " ++ p ++ " is empty."-                            pure Nothing--                        (C.Field (C.Name _ n) _, C.Section name@(C.Name _ _) arg _ : _) -> do-                            displayWarning $ "Fragment " ++ p ++ " contains a section " ++ showSection name arg ++ ", expecting field " ++ show n ++ "."-                            pure Nothing-                        (C.Section name@(C.Name _ _) arg _, C.Field (C.Name _ n') _ : _) -> do-                            displayWarning $ "Fragment " ++ p ++ " contains a field " ++ show n' ++ ", expection section " ++ showSection name arg ++ "."-                            pure Nothing+-- | Expand fragments.+--+-- Applies to all fields and sections+refactoringFragments :: FieldRefactoring+refactoringFragments field = do+    mp <- parse (getPragmas field)+    case mp of+        Nothing -> pure Nothing+        Just p  -> readFileBS p >>= \mcontents -> case mcontents of+            NoIO -> pure Nothing+            IOError err -> do+                displayWarning $ "Fragment " ++ p ++ " failed to read: " ++ show err+                pure Nothing+            Contents c  -> do+                fields <- parseFields c+                case (field, fields) of+                    (_, []) -> do+                        displayWarning $ "Fragment " ++ p ++ " is empty."+                        pure Nothing -                        (C.Field name@(C.Name _ n) _, C.Field (C.Name _ n') fls' : rest) -> do-                            unless (null rest) $-                                displayWarning $ "Fragment " ++ p ++ " contains multiple fields or sections, using only the first."-                            if n == n'-                            then do-                                -- everything is fine, replace-                                pure (Just (C.Field name (noCommentsPragmas fls')))-                            else do-                                displayWarning $ "Fragment " ++ p ++ " contains field " ++ show n' ++ ", expecting field " ++ show n ++ "."-                                pure Nothing+                    (C.Field (C.Name _ n) _, C.Section name@(C.Name _ _) arg _ : _) -> do+                        displayWarning $ "Fragment " ++ p ++ " contains a section " ++ showSection name arg ++ ", expecting field " ++ show n ++ "."+                        pure Nothing+                    (C.Section name@(C.Name _ _) arg _, C.Field (C.Name _ n') _ : _) -> do+                        displayWarning $ "Fragment " ++ p ++ " contains a field " ++ show n' ++ ", expection section " ++ showSection name arg ++ "."+                        pure Nothing -                        (C.Section name@(C.Name _ _) arg _, C.Section name'@(C.Name _ _) arg' fs' : rest) -> do-                            unless (null rest) $-                                displayWarning $ "Fragment " ++ p ++ " contains multiple fields or sections, using only the first."+                    (C.Field name@(C.Name _ n) _, C.Field (C.Name _ n') fls' : rest) -> do+                        unless (null rest) $+                            displayWarning $ "Fragment " ++ p ++ " contains multiple fields or sections, using only the first."+                        if n == n'+                        then do+                            -- everything is fine, replace+                            pure (Just (C.Field name (noCommentsPragmas fls')))+                        else do+                            displayWarning $ "Fragment " ++ p ++ " contains field " ++ show n' ++ ", expecting field " ++ show n ++ "."+                            pure Nothing -                            if (void name == void name' && map void arg == map void arg')-                            then do-                                pure (Just (C.Section name arg (noCommentsPragmas fs')))-                            else do-                                displayWarning $ "Fragment " ++ p ++ " contains a section " ++ showSection name arg ++ ", expection section " ++ showSection name' arg' ++ "."-                                pure Nothing+                    (C.Section name@(C.Name _ _) arg _, C.Section name'@(C.Name _ _) arg' fs' : rest) -> do+                        unless (null rest) $+                            displayWarning $ "Fragment " ++ p ++ " contains multiple fields or sections, using only the first." +                        if (void name == void name' && map void arg == map void arg')+                        then do+                            pure (Just (C.Section name arg (noCommentsPragmas fs')))+                        else do+                            displayWarning $ "Fragment " ++ p ++ " contains a section " ++ showSection name arg ++ ", expection section " ++ showSection name' arg' ++ "."+                            pure Nothing+  where     noCommentsPragmas :: Functor f => [f ann] -> [f CommentsPragmas]     noCommentsPragmas = map ((Comments [], []) <$) -    getPragmas :: C.Field CommentsPragmas -> [Pragma]+    getPragmas :: C.Field CommentsPragmas -> [FieldPragma]     getPragmas = snd . C.fieldAnn      showSection :: C.Name ann -> [C.SectionArg ann] -> String     showSection (C.Name _ n) []   = show n     showSection (C.Name _ n) args = show (fromUTF8BS n ++ " " ++ render (hsep (C.prettySectionArgs n args))) -    parse :: MonadCabalFmt r m => [Pragma] -> m (Maybe FilePath)+    parse :: MonadCabalFmt r m => [FieldPragma] -> m (Maybe FilePath)     parse = fmap asum . traverse go where         go (PragmaFragment f) = return (Just f)         go _                  = return Nothing
+ src/CabalFmt/Refactoring/GlobFiles.hs view
@@ -0,0 +1,45 @@+-- |+-- License: GPL-3.0-or-later+-- Copyright: Oleg Grenrus+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes        #-}+module CabalFmt.Refactoring.GlobFiles (+    refactoringGlobFiles,+) where++import qualified Distribution.Fields     as C++import CabalFmt.Prelude+import CabalFmt.Monad+import CabalFmt.Glob+import CabalFmt.Pragma+import CabalFmt.Refactoring.Type++refactoringGlobFiles :: FieldRefactoring+refactoringGlobFiles C.Section {} = pure Nothing+refactoringGlobFiles (C.Field name@(C.Name (_, pragmas) _n) fls) = do+    globs <- parse pragmas+    files <- fmap concat (traverse match' globs)++    let newFiles :: [C.FieldLine CommentsPragmas]+        newFiles = catMaybes+            [ return $ C.FieldLine mempty $ toUTF8BS file+            | file <- files+            ]++    pure $ case files of+        [] -> Nothing+        _  -> Just (C.Field name (newFiles ++ fls))++  where+    parse :: MonadCabalFmt r m => [FieldPragma] -> m [Glob]+    parse = fmap mconcat . traverse go where+        go (PragmaGlobFiles g) = return [ g ]+        go p = do+            displayWarning $ "Skipped pragma " ++ show p+            return []++    match' :: MonadCabalFmt r m => Glob -> m [FilePath]+    match' g@(Glob dir _) = do+        files <- map (\fp -> dir ++ "/" ++ fp) <$> getFiles dir +        return $ filter (match g) files
src/CabalFmt/Refactoring/Type.hs view
@@ -4,12 +4,8 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes        #-} module CabalFmt.Refactoring.Type (-    Refactoring,-    Refactoring',-    RefactoringOfField,-    RefactoringOfField',+    FieldRefactoring,     CommentsPragmas,-    traverseFields,     rewriteFields,     ) where @@ -23,33 +19,19 @@ -- Refactoring type ------------------------------------------------------------------------------- -type CommentsPragmas = (Comments, [Pragma])-type Refactoring             = forall r m. MonadCabalFmt r m => Refactoring' r m-type Refactoring' r m        = [C.Field CommentsPragmas] -> m [C.Field CommentsPragmas]-type RefactoringOfField      = forall r m. MonadCabalFmt r m => RefactoringOfField' r m-type RefactoringOfField' r m = C.Name CommentsPragmas -> [C.FieldLine CommentsPragmas] -> m (C.Name CommentsPragmas, [C.FieldLine CommentsPragmas])+type CommentsPragmas = (Comments, [FieldPragma]) +type FieldRefactoring+    = forall r m. MonadCabalFmt r m+    => (C.Field CommentsPragmas -> m (Maybe (C.Field CommentsPragmas)))+ ------------------------------------------------------------------------------- -- Traversing refactoring ------------------------------------------------------------------------------- --- | Allows modification of single field ------ E.g. sorting extensions *could* be done as refactoring,--- though it's currently implemented in special pretty-printer.-traverseFields-    :: Applicative f-    => RefactoringOfField' r f-    -> [C.Field CommentsPragmas] -> f [C.Field CommentsPragmas]-traverseFields f = goMany where-    goMany = traverse go--    go (C.Field name fls)       = uncurry C.Field <$> f name fls-    go (C.Section name args fs) = C.Section name args <$> goMany fs- -- | A top-to-bottom rewrite of sections and fields rewriteFields-    :: Monad m+    :: MonadCabalFmt r m     => (C.Field CommentsPragmas -> m (Maybe (C.Field CommentsPragmas)))     -> [C.Field CommentsPragmas] -> m [C.Field CommentsPragmas] rewriteFields f = goMany where
tests/Golden.hs view
@@ -71,6 +71,11 @@      , p "multiple.fragment"         "build-depends: base\nghc-options: -Wall"++    , p "cbits/header.h" "..."+    , p "cbits/source1.c" "..."+    , p "cbits/source2.c" "..."+    , p "cbits/sub/source3.c" "..."     ]   where     p x y = (x, BS8.pack y)