hfmt 0.1.1 → 0.2.0
raw patch · 18 files changed
+620/−413 lines, 18 filesdep +conduit-combinatorsdep −pipesdep ~basedep ~haskell-src-extsdep ~hindent
Dependencies added: conduit-combinators
Dependencies removed: pipes
Dependency ranges changed: base, haskell-src-exts, hindent, hlint, stylish-haskell
Files
- .stylish-haskell.yaml +171/−0
- app/Actions.hs +21/−33
- app/ExitCode.hs +44/−0
- app/Main.hs +64/−56
- app/Options.hs +83/−0
- app/OptionsParser.hs +0/−85
- app/Types.hs +24/−22
- hfmt.cabal +16/−14
- src/Language/Haskell/Format.hs +4/−7
- src/Language/Haskell/Format/Definitions.hs +0/−46
- src/Language/Haskell/Format/HIndent.hs +12/−16
- src/Language/Haskell/Format/HLint.hs +7/−10
- src/Language/Haskell/Format/Internal.hs +1/−3
- src/Language/Haskell/Format/Stylish.hs +11/−8
- src/Language/Haskell/Format/Types.hs +46/−0
- src/Language/Haskell/Format/Utilities.hs +79/−43
- src/Language/Haskell/Source/Enumerator.hs +36/−57
- src/Path/Find.hs +1/−13
+ .stylish-haskell.yaml view
@@ -0,0 +1,171 @@+# stylish-haskell configuration file+# ==================================++# The stylish-haskell tool is mainly configured by specifying steps. These steps+# are a list, so they have an order, and one specific step may appear more than+# once (if needed). Each file is processed by these steps in the given order.+steps:+ # Convert some ASCII sequences to their Unicode equivalents. This is disabled+ # by default.+ # - unicode_syntax:+ # # In order to make this work, we also need to insert the UnicodeSyntax+ # # language pragma. If this flag is set to true, we insert it when it's+ # # not already present. You may want to disable it if you configure+ # # language extensions using some other method than pragmas. Default:+ # # true.+ # add_language_pragma: true++ # Align the right hand side of some elements. This is quite conservative+ # and only applies to statements where each element occupies a single+ # line.+ - simple_align:+ cases: true+ top_level_patterns: true+ records: true++ # Import cleanup+ - imports:+ # There are different ways we can align names and lists.+ #+ # - global: Align the import names and import list throughout the entire+ # file.+ #+ # - file: Like global, but don't add padding when there are no qualified+ # imports in the file.+ #+ # - group: Only align the imports per group (a group is formed by adjacent+ # import lines).+ #+ # - none: Do not perform any alignment.+ #+ # Default: global.+ align: file++ # Folowing options affect only import list alignment.+ #+ # List align has following options:+ #+ # - after_alias: Import list is aligned with end of import including+ # 'as' and 'hiding' keywords.+ #+ # > import qualified Data.List as List (concat, foldl, foldr, head,+ # > init, last, length)+ #+ # - with_alias: Import list is aligned with start of alias or hiding.+ #+ # > import qualified Data.List as List (concat, foldl, foldr, head,+ # > init, last, length)+ #+ # - new_line: Import list starts always on new line.+ #+ # > import qualified Data.List as List+ # > (concat, foldl, foldr, head, init, last, length)+ #+ # Default: after_alias+ list_align: after_alias++ # Long list align style takes effect when import is too long. This is+ # determined by 'columns' setting.+ #+ # - inline: This option will put as much specs on same line as possible.+ #+ # - new_line: Import list will start on new line.+ #+ # - new_line_multiline: Import list will start on new line when it's+ # short enough to fit to single line. Otherwise it'll be multiline.+ #+ # - multiline: One line per import list entry.+ # Type with contructor list acts like single import.+ #+ # > import qualified Data.Map as M+ # > ( empty+ # > , singleton+ # > , ...+ # > , delete+ # > )+ #+ # Default: inline+ long_list_align: inline++ # List padding determines indentation of import list on lines after import.+ # This option affects 'list_align' and 'long_list_align'.+ list_padding: 4++ # Separate lists option affects formating of import list for type+ # or class. The only difference is single space between type and list+ # of constructors, selectors and class functions.+ #+ # - true: There is single space between Foldable type and list of it's+ # functions.+ #+ # > import Data.Foldable (Foldable (fold, foldl, foldMap))+ #+ # - false: There is no space between Foldable type and list of it's+ # functions.+ #+ # > import Data.Foldable (Foldable(fold, foldl, foldMap))+ #+ # Default: true+ separate_lists: true++ # Language pragmas+ - language_pragmas:+ # We can generate different styles of language pragma lists.+ #+ # - vertical: Vertical-spaced language pragmas, one per line.+ #+ # - compact: A more compact style.+ #+ # - compact_line: Similar to compact, but wrap each line with+ # `{-#LANGUAGE #-}'.+ #+ # Default: vertical.+ style: vertical++ # Align affects alignment of closing pragma brackets.+ #+ # - true: Brackets are aligned in same collumn.+ #+ # - false: Brackets are not aligned together. There is only one space+ # between actual import and closing bracket.+ #+ # Default: true+ align: true++ # stylish-haskell can detect redundancy of some language pragmas. If this+ # is set to true, it will remove those redundant pragmas. Default: true.+ remove_redundant: true++ # Replace tabs by spaces. This is disabled by default.+ # - tabs:+ # # Number of spaces to use for each tab. Default: 8, as specified by the+ # # Haskell report.+ # spaces: 2++ # Remove trailing whitespace+ - trailing_whitespace: {}++# A common setting is the number of columns (parts of) code will be wrapped+# to. Different steps take this into account. Default: 80.+columns: 80++# By default, line endings are converted according to the OS. You can override+# preferred format here.+#+# - native: Native newline format. CRLF on Windows, LF on other OSes.+#+# - lf: Convert to LF ("\n").+#+# - crlf: Convert to CRLF ("\r\n").+#+# Default: native.+newline: native++# Sometimes, language extensions are specified in a cabal file or from the+# command line instead of using language pragmas in the file. stylish-haskell+# needs to be aware of these, so it can parse the file correctly.+#+# No language extensions are enabled by default.+# language_extensions:+ # - TemplateHaskell+ # - QuasiQuotes
app/Actions.hs view
@@ -3,51 +3,39 @@ ) where import Language.Haskell.Format-import OptionsParser+import Language.Haskell.Format.Utilities+import Options import Types import Control.Monad-import Data.Algorithm.Diff-import Data.Algorithm.DiffContext-import Data.Algorithm.DiffOutput-import Text.PrettyPrint -act :: Options -> ReformatResult -> IO ReformatResult-act options r@(InvalidReformat input errorString) = do- putStrLn ("Error reformatting " ++ show input ++ ": " ++ errorString)- return r-act options r@(Reformat input source result) = act' (optAction options)+act :: Options -> Formatted -> IO Formatted+act options r@(Formatted input source result) = act' (optAction options) where act' PrintDiffs =- when wasReformatted (printDiff input source result) >> return r- act' PrintSources = undefined- act' PrintFilePaths = when wasReformatted (print input) >> return r+ when wasReformatted' (printDiff input source result) >> return r+ act' PrintSources = do+ when wasReformatted' (printSource $ reformattedSource result)+ return (Formatted input (reformattedSource result) result)+ act' PrintFilePaths = when wasReformatted' (print input) >> return r act' WriteSources = do- when wasReformatted (writeSource input (reformattedSource result))- return (Reformat input (reformattedSource result) result)- wasReformatted = sourceChangedOrHasSuggestions source result--sourceChangedOrHasSuggestions :: HaskellSource -> Reformatted -> Bool-sourceChangedOrHasSuggestions source reformatted =- not (null (suggestions reformatted)) ||- source /= reformattedSource reformatted+ when wasReformatted' (writeSource input (reformattedSource result))+ return (Formatted input (reformattedSource result) result)+ wasReformatted' = wasReformatted source result -printDiff :: InputFile -> HaskellSource -> Reformatted -> IO ()-printDiff (InputFilePath path) source reformatted = do+printDiff :: SourceFile -> HaskellSource -> Reformatted -> IO ()+printDiff (SourceFilePath path) source reformatted = do putStrLn (path ++ ":") mapM_ (putStr . show) (suggestions reformatted) putStr (showDiff source (reformattedSource reformatted))-printDiff InputFromStdIn source reformatted = do+printDiff StdinSource source reformatted = do mapM_ (putStr . show) (suggestions reformatted) putStr (showDiff source (reformattedSource reformatted)) -showDiff :: HaskellSource -> HaskellSource -> String-showDiff (HaskellSource a) (HaskellSource b) = render (toDoc diff)- where- toDoc = prettyContextDiff (text "Original") (text "Reformatted") text- diff = getContextDiff linesOfContext (lines a) (lines b)- linesOfContext = 1+printSource :: HaskellSource -> IO ()+printSource (HaskellSource _ source) = putStr source -writeSource :: InputFile -> HaskellSource -> IO ()-writeSource (InputFilePath path) (HaskellSource source) = writeFile path source-writeSource InputFromStdIn (HaskellSource source) = putStr source+writeSource :: SourceFile -> HaskellSource -> IO ()+writeSource (SourceFilePath path) (HaskellSource _ source) =+ writeFile path source+writeSource StdinSource (HaskellSource _ source) = putStr source
+ app/ExitCode.hs view
@@ -0,0 +1,44 @@+module ExitCode+ ( exitCode+ , helpDoc+ , operationalFailureCode+ ) where++import Types++import System.Exit+import Text.PrettyPrint.ANSI.Leijen++-- | Decide which exit code to use+--+-- Assume if we are printing diffs that we *are* being run in a CI and so+-- should exit with failure if any format differences were found. Otherwise+-- assume we are being run in a context where non-zero exit indicates+-- failure of the tool to operate properly.+exitCode :: Action -> Bool -> ExitCode+exitCode action formattedCodeDiffers =+ if formattedCodeDiffers && failOnDifferences+ then ExitFailure formattedCodeDiffersFailureCode+ else ExitSuccess+ where+ failOnDifferences = action == PrintDiffs++helpDoc :: Doc+helpDoc =+ vsep+ [ text "Exit Codes:"+ , text " 0 = No error"+ , text " 1 = Encountered I/O or other operational error"+ , text " 2 = Failed to parse a source code file"+ , text " 3 = Source code was parsed but cannot be formatted properly"+ , text+ " 4 = Formatted code differs from existing source (--print-diffs only)"+ ]++-- | Encountered I/O or other operational error+operationalFailureCode :: Int+operationalFailureCode = 1++-- | Formatted code differs from existing source code+formattedCodeDiffersFailureCode :: Int+formattedCodeDiffersFailureCode = 4
app/Main.hs view
@@ -1,73 +1,81 @@+{-# LANGUAGE Rank2Types #-}+ module Main ( main ) where -import Actions-import Language.Haskell.Format-import Language.Haskell.Format.Utilities hiding (wasReformatted)-import Language.Haskell.Source.Enumerator-import OptionsParser as Options-import Types+import Actions+import ExitCode+import Language.Haskell.Format+import Language.Haskell.Format.Utilities+import Language.Haskell.Source.Enumerator+import Options+import Types -import Control.Applicative-import Control.Monad-import Data.List-import Data.Maybe-import Data.Monoid-import Options.Applicative.Extra as OptApp-import Pipes-import qualified Pipes.Prelude as P-import System.Exit+import Conduit+import Options.Applicative.Extra as OptApp+import System.Directory+import System.Exit main :: IO () main = do options <- execParser Options.parser- formatter <- defaultFormatter- reformatNeeded <- runFormatter formatter options- if reformatNeeded- then exitFailure- else exitSuccess+ formattedCodeDiffers <- run options+ exitWith $ exitCode (optAction options) formattedCodeDiffers -runFormatter :: Formatter -> Options -> IO Bool-runFormatter formatter options =- anyStrict- sourceChangedOrHasSuggestions- (inputFiles >-> P.map reformat >-> P.mapM writeOutput)+run :: Options -> IO Bool+run options =+ runConduit $+ sources .| mapMC readSource .| mapMC formatSource .| handleFormatErrors .|+ mapMC action .|+ summarize where- anyStrict :: Monad m => (a -> Bool) -> Producer a m () -> m Bool- anyStrict f = P.fold (\acc x -> acc || f x) False id- inputFiles = readInputFiles options- reformat = applyFormatter formatter- writeOutput = Actions.act options--readInputFiles :: Options -> Producer InputFileWithSource IO ()-readInputFiles options =- determineInputFilePaths (optPaths options) >-> P.mapM readInputFile+ paths = do+ let explicitPaths = optPaths options+ if null explicitPaths+ then do+ currentPath <- getCurrentDirectory+ return [currentPath]+ else return explicitPaths+ sources :: Source IO SourceFile+ sources = lift paths >>= mapM_ sourcesFromPath+ formatSource source = do+ formatter <- defaultFormatter+ return $ applyFormatter formatter source+ handleFormatErrors = concatMapMC handleFormatError+ action = Actions.act options+ summarize =+ anyC' (\(Formatted _ source result) -> wasReformatted source result) -determineInputFilePaths :: [FilePath] -> Producer InputFile IO ()-determineInputFilePaths [] = enumeratePath "." >-> P.map InputFilePath-determineInputFilePaths ["-"] = yield InputFromStdIn-determineInputFilePaths paths =- for (each paths) enumeratePath >-> P.map InputFilePath+sourcesFromPath :: FilePath -> Source IO SourceFile+sourcesFromPath "-" = yield StdinSource+sourcesFromPath path = enumeratePath path .| mapC SourceFilePath -readInputFile :: InputFile -> IO InputFileWithSource-readInputFile (InputFilePath path) =- InputFileWithSource (InputFilePath path) <$> readSource path-readInputFile InputFromStdIn = InputFileWithSource InputFromStdIn <$> readStdin+readSource :: SourceFile -> IO SourceFileWithContents+readSource s@(SourceFilePath path) =+ SourceFileWithContents s . HaskellSource path <$> readFile path+readSource s@StdinSource =+ SourceFileWithContents s . HaskellSource "stdin" <$> getContents -applyFormatter :: Formatter -> InputFileWithSource -> ReformatResult-applyFormatter (Formatter format) (InputFileWithSource input source) =- case format source of- Left error -> InvalidReformat input error- Right reformatted -> Reformat input source reformatted+applyFormatter :: Formatter -> SourceFileWithContents -> FormatResult+applyFormatter (Formatter doFormat) (SourceFileWithContents file contents) =+ case doFormat contents of+ Left err -> Left (FormatError file err)+ Right reformat -> Right (Formatted file contents reformat) -readSource :: HaskellSourceFilePath -> IO HaskellSource-readSource path = HaskellSource <$> readFile path+handleFormatError :: FormatResult -> IO (Maybe Formatted)+handleFormatError (Left err) = printFormatError err >> return Nothing+handleFormatError (Right formatted) = return $ Just formatted -readStdin :: IO HaskellSource-readStdin = HaskellSource <$> getContents+printFormatError :: FormatError -> IO ()+printFormatError (FormatError input errorString) =+ putStrLn ("Error reformatting " ++ show input ++ ": " ++ errorString) -sourceChangedOrHasSuggestions :: ReformatResult -> Bool-sourceChangedOrHasSuggestions (Reformat input source reformatted) =- not (null (suggestions reformatted)) ||- source /= reformattedSource reformatted+-- | Check that at least one value in the stream returns True.+--+-- Does not shortcut, entire stream is always consumed+anyC' :: Monad m => (a -> Bool) -> Consumer a m Bool+anyC' f = do+ result <- anyC f -- Check for at least one value, may shortcut+ sinkNull -- consume any remaining input skipped by a shortcut+ return result
+ app/Options.hs view
@@ -0,0 +1,83 @@+module Options+ ( Options+ , optAction+ , optPaths+ , parser+ ) where++import qualified ExitCode+import Types++import Control.Applicative+import Data.Monoid+import Options.Applicative.Builder+import Options.Applicative.Common+import Options.Applicative.Extra+import Text.PrettyPrint.ANSI.Leijen as Leijen hiding ((<$>), (<>))++data Options = Options+ { optPrintDiffs :: Bool+ , optPrintSources :: Bool+ , optPrintFilePaths :: Bool+ , optWriteSources :: Bool+ , optPaths :: [FilePath]+ }++optAction :: Options -> Action+optAction options+ | optPrintDiffs options = PrintDiffs+ | optPrintFilePaths options = PrintFilePaths+ | optPrintSources options = PrintSources+ | optWriteSources options = WriteSources+ | optPaths options == ["-"] = PrintSources+ | otherwise = PrintDiffs++optionParser :: Parser Options+optionParser =+ Options <$>+ switch+ (long "print-diffs" <> short 'd' <>+ help "If a file's formatting is different, print a diff.") <*>+ switch+ (long "print-sources" <> short 's' <> hidden <>+ help "If a file's formatting is different, print its source.") <*>+ switch+ (long "print-paths" <> short 'l' <> hidden <>+ help "If a file's formatting is different, print its path.") <*>+ switch+ (long "write-sources" <> short 'w' <> hidden <>+ help "If a file's formatting is different, overwrite it.") <*>+ many pathOption+ where+ pathOption = strArgument (metavar "FILE" <> pathOptionHelp)+ pathOptionHelp =+ helpDoc $+ Just $+ text "Explicit paths to process." <> line <>+ unorderdedList+ " - "+ [ text "A single '-' will process standard input."+ , text "Files will be processed directly."+ , text+ "Directories will be recursively searched for source files to process."+ , text+ ".cabal files will be parsed and all specified source directories and files processed."+ , text+ "If no paths are given, the current directory will be searched for .cabal files to process, if none are found the current directory will be recursively searched for source files to process."+ ]++unorderdedList :: String -> [Doc] -> Doc+unorderdedList prefix = vsep . map ((text prefix <>) . align)++optionParserInfo :: ParserInfo Options+optionParserInfo =+ info+ (helper <*> optionParser)+ (fullDesc <> header "hfmt - Format Haskell programs" <>+ progDesc+ "Reformats Haskell source files by applying HLint, hindent, and stylish-haskell." <>+ footerDoc (Just ExitCode.helpDoc) <>+ failureCode ExitCode.operationalFailureCode)++parser :: ParserInfo Options+parser = optionParserInfo
− app/OptionsParser.hs
@@ -1,85 +0,0 @@-module OptionsParser- ( Options- , optAction- , optPaths- , parser- ) where--import Types--import Control.Applicative-import Data.List-import Data.Monoid-import Options.Applicative.Builder-import Options.Applicative.Common-import Options.Applicative.Extra-import Text.PrettyPrint.ANSI.Leijen hiding (empty, (<$>), (<>))--data Options = Options- { optPrintDiffs :: Bool- , optPrintSources :: Bool- , optPrintFilePaths :: Bool- , optWriteSources :: Bool- , optPaths :: [FilePath]- }--optAction :: Options -> Action-optAction options- | optPrintDiffs options = PrintDiffs- | optPrintFilePaths options = PrintFilePaths- | optPrintSources options = PrintSources- | optWriteSources options = WriteSources- | optPaths options == ["-"] = PrintSources- | otherwise = PrintDiffs--optionParser :: Parser Options-optionParser =- Options <$>- switch- (long "print-diffs" <> short 'd' <>- help "If a file's formatting is different, print a diff.") <*>- switch- (long "print-sources" <> short 's' <>- help "If a file's formatting is different, print its source.") <*>- switch- (long "print-paths" <> short 'l' <>- help "If a file's formatting is different, print its path.") <*>- switch- (long "write-sources" <> short 'w' <>- help "If a file's formatting is different, overwrite it.") <*>- many pathOption- where- pathOption = strArgument (metavar "PATH" <> pathOptionHelp)- pathOptionHelp =- helpDoc $- Just $- paragraph "Explicit paths to process." <> hardline <>- unorderdedList- " - "- [ paragraph "A single '-' will process standard input."- , paragraph "Files will be processed directly."- , paragraph- "Directories will be recursively searched for source files to process."- , paragraph- ".cabal files will be parsed and all specified source directories and files processed."- , paragraph- "If no paths are given, the current directory will be searched for .cabal files to process, if none are found the current directory will be recursively searched for source files to process."- ]--paragraph :: String -> Doc-paragraph = mconcat . intersperse softline . map text . words--unorderdedList :: String -> [Doc] -> Doc-unorderdedList prefix =- encloseSep (text prefix) mempty (text prefix) . map (hang 0)--optionParserInfo :: ParserInfo Options-optionParserInfo =- info- (helper <*> optionParser)- (fullDesc <> header "hfmt - format Haskell programs" <>- progDesc- "Operates on Haskell source files, reformatting them by applying suggestions from HLint, hindent, and stylish-haskell. Inspired by the gofmt utility.")--parser :: ParserInfo Options-parser = optionParserInfo
app/Types.hs view
@@ -1,39 +1,41 @@ module Types ( Action(..)- , InputFile(..)- , InputFileWithSource(..)- , ErrorString- , ReformatResult(..)- , HaskellSourceFilePath+ , FormatResult+ , FormatError(..)+ , Formatted(..) , HaskellSource(..)+ , SourceFile(..)+ , SourceFileWithContents(..) ) where import Language.Haskell.Format-import Language.Haskell.Source.Enumerator (HaskellSourceFilePath) data Action = PrintDiffs | PrintSources | PrintFilePaths | WriteSources+ deriving (Eq) -data InputFile- = InputFilePath HaskellSourceFilePath- | InputFromStdIn+data SourceFile+ = SourceFilePath FilePath+ | StdinSource -instance Show InputFile where- show (InputFilePath path) = path- show InputFromStdIn = "-"+instance Show SourceFile where+ show (SourceFilePath path) = path+ show StdinSource = "-" -data InputFileWithSource =- InputFileWithSource InputFile- HaskellSource+data SourceFileWithContents =+ SourceFileWithContents SourceFile+ HaskellSource -type ErrorString = String+type FormatResult = Either FormatError Formatted -data ReformatResult- = InvalidReformat InputFile- ErrorString- | Reformat InputFile- HaskellSource- Reformatted+data FormatError =+ FormatError SourceFile+ String++data Formatted =+ Formatted SourceFile+ HaskellSource+ Reformatted
hfmt.cabal view
@@ -1,5 +1,5 @@ name: hfmt-version: 0.1.1+version: 0.2.0 synopsis: Haskell source code formatter description: Inspired by gofmt. Built using hlint, hindent, and stylish-haskell. license: MIT@@ -11,9 +11,9 @@ bug-reports: http://github.com/danstiner/hfmt/issues category: Language build-type: Simple-extra-source-files: README.md+extra-source-files: README.md, .stylish-haskell.yaml cabal-version: >=1.9.2-tested-with: GHC >= 7.10+tested-with: GHC == 7.10.3, GHC == 8.0.2, GHC == 8.2.2, GHC == 8.3 library Hs-Source-Dirs: src@@ -21,26 +21,28 @@ , Language.Haskell.Format.Utilities , Language.Haskell.Source.Enumerator Other-Modules: Language.Haskell.Format.Internal- , Language.Haskell.Format.Definitions , Language.Haskell.Format.HIndent , Language.Haskell.Format.HLint , Language.Haskell.Format.Stylish+ , Language.Haskell.Format.Types , Path.Find GHC-Options: -Wall Build-Depends: base >= 4.8 && < 5 , bytestring , Cabal+ , conduit-combinators+ , Diff , directory , exceptions , filepath- , haskell-src-exts- , hindent >= 5.2.3- , hlint >= 1.9+ , haskell-src-exts < 1.20+ , hindent == 5.*+ , hlint == 2.* , HUnit , path , path-io- , pipes- , stylish-haskell >= 0.7+ , pretty+ , stylish-haskell == 0.8.* , text , transformers , yaml@@ -49,16 +51,16 @@ Hs-Source-Dirs: app Main-Is: Main.hs Other-Modules: Actions- , OptionsParser+ , ExitCode+ , Options , Types GHC-Options: -Wall- Build-Depends: base == 4.*+ Build-Depends: base >= 4.8 && < 5+ , conduit-combinators+ , directory , hfmt , ansi-wl-pprint- , Diff , optparse-applicative- , pipes- , pretty test-suite self-formatting-test Type: exitcode-stdio-1.0
src/Language/Haskell/Format.hs view
@@ -12,13 +12,10 @@ , Reformatted(..) ) where -import Language.Haskell.Format.Definitions-import qualified Language.Haskell.Format.HIndent as HIndent-import qualified Language.Haskell.Format.HLint as HLint-import qualified Language.Haskell.Format.Stylish as Stylish--import Control.Applicative-import Data.Maybe+import qualified Language.Haskell.Format.HIndent as HIndent+import qualified Language.Haskell.Format.HLint as HLint+import qualified Language.Haskell.Format.Stylish as Stylish+import Language.Haskell.Format.Types data Settings = Settings
− src/Language/Haskell/Format/Definitions.hs
@@ -1,46 +0,0 @@-module Language.Haskell.Format.Definitions- ( HaskellSource(..)- , Reformatted(..)- , Formatter(..)- , Suggestion(..)- ) where--import Control.Applicative-import Control.Monad-import Data.Monoid--type ErrorString = String--newtype HaskellSource =- HaskellSource String- deriving (Eq)--newtype Suggestion =- Suggestion String--instance Show Suggestion where- show (Suggestion text) = text--data Reformatted = Reformatted- { reformattedSource :: HaskellSource- , suggestions :: [Suggestion]- }--instance Monoid Reformatted where- mempty = Reformatted undefined []- (Reformatted _ suggestionsA) `mappend` (Reformatted sourceB suggestionsB) =- Reformatted sourceB (suggestionsA <> suggestionsB)--newtype Formatter = Formatter- { unFormatter :: HaskellSource -> Either ErrorString Reformatted- }--instance Monoid Formatter where- mempty = Formatter (\source -> Right (Reformatted source []))- (Formatter f) `mappend` (Formatter g) = Formatter (asReformatter g <=< f)--asReformatter ::- (HaskellSource -> Either ErrorString Reformatted)- -> Reformatted- -> Either ErrorString Reformatted-asReformatter formatter r = (r <>) <$> formatter (reformattedSource r)
src/Language/Haskell/Format/HIndent.hs view
@@ -4,24 +4,20 @@ , defaultFormatter ) where -import Control.Applicative import Data.ByteString.Builder-import Data.ByteString.Lazy as L-import Data.Maybe-import qualified Data.Text as Text-import Data.Text.Encoding as Encoding-import Data.Text.Lazy as TL-import Data.Text.Lazy.Builder-import qualified Data.Yaml as Y+import Data.ByteString.Lazy as L+import qualified Data.Text as Text+import Data.Text.Encoding as Encoding+import qualified Data.Yaml as Y import HIndent import HIndent.Types-import Language.Haskell.Exts.Extension (Extension)+import Language.Haskell.Exts.Extension (Extension) import Path-import qualified Path.Find as Path-import qualified Path.IO as Path+import qualified Path.Find as Path+import qualified Path.IO as Path -import Language.Haskell.Format.Definitions import Language.Haskell.Format.Internal+import Language.Haskell.Format.Types data Settings = Settings Config@@ -57,10 +53,10 @@ formatter = mkFormatter . hindent hindent :: Settings -> HaskellSource -> Either String HaskellSource-hindent (Settings config extensions) (HaskellSource source) =- toHaskellSource <$> reformat config extensions Nothing sourceText+hindent (Settings config extensions) (HaskellSource filepath source) =+ HaskellSource filepath . unpackBuilder <$>+ reformat config extensions Nothing sourceText where sourceText = Encoding.encodeUtf8 . Text.pack $ source- toHaskellSource =- HaskellSource .+ unpackBuilder = Text.unpack . Encoding.decodeUtf8 . L.toStrict . toLazyByteString
src/Language/Haskell/Format/HLint.hs view
@@ -3,16 +3,14 @@ , suggester ) where -import Language.Haskell.Format.Definitions import Language.Haskell.Format.Internal+import Language.Haskell.Format.Types -import Control.Applicative-import Language.Haskell.HLint3 (Classify, Hint,- ParseError (..),- ParseFlags, applyHints,- parseModuleEx)-import qualified Language.Haskell.HLint3 as HLint3-import System.IO.Unsafe (unsafePerformIO)+import Language.Haskell.HLint3 (Classify, Hint,+ ParseError (..), ParseFlags,+ applyHints, parseModuleEx)+import qualified Language.Haskell.HLint3 as HLint3+import System.IO.Unsafe (unsafePerformIO) suggester :: (ParseFlags, [Classify], Hint) -> Formatter suggester = mkSuggester . hlint@@ -21,12 +19,11 @@ (ParseFlags, [Classify], Hint) -> HaskellSource -> Either String [Suggestion]-hlint (flags, classify, hint) (HaskellSource source) =+hlint (flags, classify, hint) (HaskellSource filepath source) = case unsafePerformIO (parseModuleEx flags filepath (Just source)) of Right m -> Right . map ideaToSuggestion . ideas $ m Left parseError -> Left . parseErrorMessage $ parseError where- filepath = "" ideas m = applyHints classify hint [m] autoSettings :: IO (ParseFlags, [Classify], Hint)
src/Language/Haskell/Format/Internal.hs view
@@ -3,9 +3,7 @@ , mkSuggester ) where -import Language.Haskell.Format.Definitions--import Control.Applicative+import Language.Haskell.Format.Types mkFormatter :: (HaskellSource -> Either String HaskellSource) -> Formatter mkFormatter f = Formatter (fmap (\source -> Reformatted source []) . f)
src/Language/Haskell/Format/Stylish.hs view
@@ -3,26 +3,29 @@ , formatter ) where -import Control.Applicative-import Language.Haskell.Stylish as Stylish+import Language.Haskell.Stylish as Stylish -import Language.Haskell.Format.Definitions import Language.Haskell.Format.Internal+import Language.Haskell.Format.Types newtype Settings = Settings Stylish.Config autoSettings :: IO Settings-autoSettings =- Settings <$> Stylish.loadConfig (Stylish.makeVerbose False) Nothing+autoSettings = do+ path <- Stylish.configFilePath verbose Nothing+ config <- Stylish.loadConfig verbose (Just path)+ return (Settings config)+ where+ verbose = Stylish.makeVerbose False formatter :: Settings -> Formatter formatter = mkFormatter . stylish stylish :: Settings -> HaskellSource -> Either String HaskellSource-stylish (Settings config) (HaskellSource source) =- HaskellSource . unlines <$>- Stylish.runSteps extensions Nothing steps sourceLines+stylish (Settings config) (HaskellSource filepath source) =+ HaskellSource filepath . unlines <$>+ Stylish.runSteps extensions (Just filepath) steps sourceLines where sourceLines = lines source extensions = Stylish.configLanguageExtensions config
+ src/Language/Haskell/Format/Types.hs view
@@ -0,0 +1,46 @@+module Language.Haskell.Format.Types+ ( HaskellSource(..)+ , Reformatted(..)+ , Formatter(..)+ , Suggestion(..)+ ) where++import Control.Monad+import Data.Monoid ((<>))++type ErrorString = String++data HaskellSource =+ HaskellSource FilePath+ String+ deriving (Eq)++newtype Suggestion =+ Suggestion String++instance Show Suggestion where+ show (Suggestion text) = text++data Reformatted = Reformatted+ { reformattedSource :: HaskellSource+ , suggestions :: [Suggestion]+ }++instance Monoid Reformatted where+ mempty = Reformatted undefined []+ (Reformatted _ suggestionsA) `mappend` (Reformatted sourceB suggestionsB) =+ Reformatted sourceB (suggestionsA <> suggestionsB)++newtype Formatter = Formatter+ { unFormatter :: HaskellSource -> Either ErrorString Reformatted+ }++instance Monoid Formatter where+ mempty = Formatter (\source -> Right (Reformatted source []))+ (Formatter f) `mappend` (Formatter g) = Formatter (asReformatter g <=< f)++asReformatter ::+ (HaskellSource -> Either ErrorString Reformatted)+ -> Reformatted+ -> Either ErrorString Reformatted+asReformatter formatter r = (r <>) <$> formatter (reformattedSource r)
src/Language/Haskell/Format/Utilities.hs view
@@ -1,69 +1,105 @@ module Language.Haskell.Format.Utilities- ( wasReformatted+ ( defaultFormatter , hunitTest- , defaultFormatter+ , showDiff+ , wasReformatted ) where -import Language.Haskell.Format-import Language.Haskell.Format.Definitions-import Language.Haskell.Source.Enumerator+import System.IO.Unsafe -import Control.Applicative-import Control.Monad-import Data.Monoid-import Pipes-import qualified Pipes.Prelude as P-import Test.HUnit+import Language.Haskell.Format+import Language.Haskell.Source.Enumerator +import Conduit+import Control.Monad+import Data.Algorithm.DiffContext+import Data.List+import Data.Maybe+import Test.HUnit+import Text.PrettyPrint+ type ErrorString = String data CheckResult- = InvalidCheckResult ErrorString+ = InvalidCheckResult HaskellSource+ ErrorString | CheckResult HaskellSource Reformatted +checkResultPath :: CheckResult -> FilePath+checkResultPath (InvalidCheckResult (HaskellSource filepath _) _) = filepath+checkResultPath (CheckResult (HaskellSource filepath _) _) = filepath+ hunitTest :: FilePath -> Test-hunitTest filepath = TestLabel filepath $ makeTestCase filepath+hunitTest filepath = TestLabel filepath . unsafePerformIO . testPath $ filepath -makeTestCase :: FilePath -> Test-makeTestCase filepath =- TestCase $ do- formatter <- defaultFormatter- runEffect $ check formatter filepath >-> assertResults+testPath :: FilePath -> IO Test+testPath filepath = do+ formatter <- defaultFormatter+ TestList <$>+ runConduit (check formatter filepath .| mapC makeTestCase .| sinkList) -assertResults :: Consumer CheckResult IO ()-assertResults =- forever $ do- result <- await- case result of- (InvalidCheckResult errorString) ->- lift $ assertFailure ("Error: " ++ errorString)- (CheckResult source reformatted) ->- when (wasReformatted source reformatted) $- lift $- assertFailure- ("Incorrect formatting: " ++ concatMap show (suggestions reformatted))+makeTestCase :: CheckResult -> Test+makeTestCase result =+ TestLabel (checkResultPath result) . TestCase $ assertCheckResult result -check :: Formatter -> FilePath -> Producer CheckResult IO ()-check formatter path =- enumeratePath path >-> P.mapM readSource >-> P.map (checkFormatting formatter)+assertCheckResult :: CheckResult -> IO ()+assertCheckResult result =+ case result of+ (InvalidCheckResult _ errorString) ->+ assertFailure ("Error: " ++ errorString)+ (CheckResult source reformatted) ->+ when (wasReformatted source reformatted) $+ assertFailure (showReformatted source reformatted)+ where+ showReformatted :: HaskellSource -> Reformatted -> String+ showReformatted source reformatted =+ intercalate "\n" $+ catMaybes+ [ showSourceChanges source reformatted+ , showSuggestions source reformatted+ ]+ showSourceChanges source reformatted =+ whenMaybe+ (sourceChanged source reformatted)+ (showDiff source (reformattedSource reformatted))+ showSuggestions _ reformatted =+ whenMaybe+ (hasSuggestions reformatted)+ (concatMap show (suggestions reformatted))+ whenMaybe :: Bool -> a -> Maybe a+ whenMaybe cond val = const val <$> guard cond -readSource :: HaskellSourceFilePath -> IO HaskellSource-readSource path = HaskellSource <$> readFile path+showDiff :: HaskellSource -> HaskellSource -> String+showDiff (HaskellSource _ a) (HaskellSource _ b) = render (toDoc diff)+ where+ toDoc = prettyContextDiff (text "Original") (text "Reformatted") text+ diff = getContextDiff linesOfContext (lines a) (lines b)+ linesOfContext = 1 +check :: Formatter -> FilePath -> Source IO CheckResult+check formatter filepath =+ enumeratePath filepath .| mapMC readSourceFile .|+ mapC (checkFormatting formatter)++readSourceFile :: FilePath -> IO HaskellSource+readSourceFile filepath = HaskellSource filepath <$> readFile filepath+ checkFormatting :: Formatter -> HaskellSource -> CheckResult-checkFormatting (Formatter format) source =- case format source of- Left error -> InvalidCheckResult error+checkFormatting (Formatter doFormat) source =+ case doFormat source of+ Left err -> InvalidCheckResult source err Right reformatted -> CheckResult source reformatted -fix :: Formatter -> FilePath -> IO ()-fix = undefined- defaultFormatter :: IO Formatter defaultFormatter = mconcat <$> (autoSettings >>= formatters) wasReformatted :: HaskellSource -> Reformatted -> Bool wasReformatted source reformatted =- not (null (suggestions reformatted)) ||- source /= reformattedSource reformatted+ hasSuggestions reformatted || sourceChanged source reformatted++sourceChanged :: HaskellSource -> Reformatted -> Bool+sourceChanged source reformatted = source /= reformattedSource reformatted++hasSuggestions :: Reformatted -> Bool+hasSuggestions reformatted = not (null (suggestions reformatted))
src/Language/Haskell/Source/Enumerator.hs view
@@ -1,72 +1,49 @@ module Language.Haskell.Source.Enumerator ( enumeratePath- , HaskellSourceFilePath ) where +import Conduit import Control.Applicative import Control.Monad import Data.List import Distribution.PackageDescription import Distribution.PackageDescription.Parse import qualified Distribution.Verbosity as Verbosity-import Pipes import System.Directory import System.FilePath -type HaskellSourceFilePath = FilePath--enumeratePath :: FilePath -> Producer HaskellSourceFilePath IO ()-enumeratePath = filePath--filePath :: FilePath -> Producer HaskellSourceFilePath IO ()-filePath path = do- isCabal <- lift $ isCabalFile path- if isCabal- then package' path- else directoryOrFile path--simpleFilePath :: FilePath -> Producer HaskellSourceFilePath IO ()-simpleFilePath path = do- isHaskellSource <- lift $ isHaskellSourceFile path- when isHaskellSource $ yield path--simpleDirectory :: FilePath -> Producer HaskellSourceFilePath IO ()-simpleDirectory path = do- contents <- lift $ getDirectoryContentsFullPaths path- mapM_ simpleFileOrDirectory contents+enumeratePath :: FilePath -> Source IO FilePath+enumeratePath path = enumPath path .| mapC normalise -simpleFileOrDirectory :: FilePath -> Producer HaskellSourceFilePath IO ()-simpleFileOrDirectory filepath = do- dir <- lift $ doesDirectoryExist filepath- if dir- then simpleDirectory filepath- else simpleFilePath filepath+enumPath :: FilePath -> Source IO FilePath+enumPath path = do+ isDirectory <- lift $ doesDirectoryExist path+ case isDirectory of+ True -> enumDirectory path+ False+ | hasCabalExtension path -> enumPackage path+ False+ | hasHaskellExtension path -> yield path+ False -> return () -package' :: FilePath -> Producer HaskellSourceFilePath IO ()-package' path = readPackage path >>= expandPaths+enumPackage :: FilePath -> Source IO FilePath+enumPackage cabalFile = readPackage cabalFile >>= expandPaths where readPackage = lift . readPackageDescription Verbosity.silent- expandPaths = mapM_ (simpleFileOrDirectory . mkFull) . sourcePaths- dir = dropFileName path- mkFull = (dir </>)+ expandPaths = mapM_ (enumPath . mkFull) . sourcePaths+ packageDir = dropFileName cabalFile+ mkFull = (packageDir </>) -directory :: FilePath -> Producer HaskellSourceFilePath IO ()-directory path = do- contents <- lift $ getDirectoryContentsFullPaths path+enumDirectory :: FilePath -> Source IO FilePath+enumDirectory path = do+ contents <- lift $ getDirectoryContentFullPaths path cabalFiles <- lift $ filterM isCabalFile contents if null cabalFiles- then mapM_ directoryOrFile contents- else mapM_ package' cabalFiles--directoryOrFile :: FilePath -> Producer HaskellSourceFilePath IO ()-directoryOrFile path = do- isFile <- lift $ doesFileExist path- if isFile- then simpleFilePath path- else directory path+ then mapM_ enumPath contents+ else mapM_ enumPackage cabalFiles -getDirectoryContentsFullPaths :: FilePath -> IO [FilePath]-getDirectoryContentsFullPaths path =+getDirectoryContentFullPaths :: FilePath -> IO [FilePath]+getDirectoryContentFullPaths path = mkFull . notHidden . notMeta <$> getDirectoryContents path where mkFull = map (path </>)@@ -74,17 +51,14 @@ notMeta = (\\ [".", ".."]) isCabalFile :: FilePath -> IO Bool-isCabalFile path = (hasCabalExtension &&) <$> isFile- where- isFile = doesFileExist path- hasCabalExtension = ".cabal" `isSuffixOf` path+isCabalFile path = return (hasCabalExtension path) <&&> doesFileExist path -isHaskellSourceFile :: FilePath -> IO Bool-isHaskellSourceFile path = (hasHaskellExtension &&) <$> isFile- where- isFile = doesFileExist path- hasHaskellExtension = ".hs" `isSuffixOf` path || ".lhs" `isSuffixOf` path+hasCabalExtension :: FilePath -> Bool+hasCabalExtension path = ".cabal" `isSuffixOf` path +hasHaskellExtension :: FilePath -> Bool+hasHaskellExtension path = ".hs" `isSuffixOf` path || ".lhs" `isSuffixOf` path+ sourcePaths :: GenericPackageDescription -> [FilePath] sourcePaths pkg = nub $ concatMap ($ pkg) pathExtractors where@@ -97,3 +71,8 @@ , concatMap (hsSourceDirs . benchmarkBuildInfo . condTreeData . snd) . condBenchmarks ]++(<&&>) :: Applicative f => f Bool -> f Bool -> f Bool+(<&&>) = liftA2 (&&)++infixr 3 <&&> -- same as (&&)
src/Path/Find.hs view
@@ -6,14 +6,11 @@ ( findFileUp ) where -import Control.Exception (evaluate)-import Control.Monad import Control.Monad.Catch import Control.Monad.IO.Class import Data.List import Path-import Path.IO hiding (findFiles)-import System.IO.Error (isPermissionError)+import Path.IO -- | Find the location of a file matching the given predicate. findFileUp ::@@ -23,15 +20,6 @@ -> Maybe (Path Abs Dir) -- ^ Do not ascend above this directory. -> m (Maybe (Path Abs File)) -- ^ Absolute file path. findFileUp = findPathUp snd---- | Find the location of a directory matching the given predicate.-findDirUp ::- (MonadIO m, MonadThrow m)- => Path Abs Dir -- ^ Start here.- -> (Path Abs Dir -> Bool) -- ^ Predicate to match the directory.- -> Maybe (Path Abs Dir) -- ^ Do not ascend above this directory.- -> m (Maybe (Path Abs Dir)) -- ^ Absolute directory path.-findDirUp = findPathUp fst -- | Find the location of a path matching the given predicate. findPathUp ::