hfmt (empty) → 0.0.1.0
raw patch · 17 files changed
+653/−0 lines, 17 filesdep +Cabaldep +Diffdep +HUnitsetup-changed
Dependencies added: Cabal, Diff, HUnit, ansi-wl-pprint, base, directory, filepath, haskell-src-exts, hfmt, hindent, hlint, optparse-applicative, pipes, pretty, stylish-haskell, test-framework, test-framework-hunit, text
Files
- LICENSE +22/−0
- Setup.hs +2/−0
- app/Actions.hs +46/−0
- app/Main.hs +48/−0
- app/OptionsParser.hs +77/−0
- app/Types.hs +31/−0
- hfmt.cabal +79/−0
- src/Language/Haskell/Format.hs +42/−0
- src/Language/Haskell/Format/Definitions.hs +31/−0
- src/Language/Haskell/Format/HIndent.hs +28/−0
- src/Language/Haskell/Format/HLint.hs +41/−0
- src/Language/Haskell/Format/Internal.hs +11/−0
- src/Language/Haskell/Format/Stylish.hs +23/−0
- src/Language/Haskell/Format/Utilities.hs +60/−0
- src/Language/Haskell/Source/Enumerator.hs +91/−0
- test/pure/Spec.hs +8/−0
- test/self-formatting/Spec.hs +13/−0
+ LICENSE view
@@ -0,0 +1,22 @@+The MIT License (MIT) + +Copyright (c) 2014 Daniel Stiner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple +main = defaultMain
+ app/Actions.hs view
@@ -0,0 +1,46 @@+module Actions (act) where + +import Language.Haskell.Format +import OptionsParser +import Types + +import Control.Monad +import Data.Algorithm.Diff +import Data.Algorithm.DiffContext +import Data.Algorithm.DiffOutput +import Text.PrettyPrint + +act :: Options -> ReformatResult -> IO () +act options (InvalidReformat input errorString) = + putStrLn ("Error reformatting " ++ show input ++ ": " ++ errorString) +act options (Reformat input source result) = act' (optAction options) + where + act' PrintDiffs = when wasReformatted (printDiff input source result) + act' PrintSources = undefined + act' PrintFilePaths = when wasReformatted (print input) + act' WriteSources = when wasReformatted (writeSource input (reformattedSource result)) + wasReformatted = sourceChangedOrHasSuggestions source result + +sourceChangedOrHasSuggestions :: HaskellSource -> Reformatted -> Bool +sourceChangedOrHasSuggestions source reformatted = + not (null (suggestions reformatted)) || source /= reformattedSource reformatted + +printDiff :: InputFile -> HaskellSource -> Reformatted -> IO () +printDiff (InputFilePath path) source reformatted = do + putStrLn (path ++ ":") + mapM_ (putStr . show) (suggestions reformatted) + putStr (showDiff source (reformattedSource reformatted)) +printDiff (InputFromStdIn) 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 + +writeSource :: InputFile -> HaskellSource -> IO () +writeSource (InputFilePath path) (HaskellSource source) = writeFile path source +writeSource InputFromStdIn (HaskellSource source) = putStr source
+ app/Main.hs view
@@ -0,0 +1,48 @@+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 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 + +main :: IO () +main = do + options <- execParser Options.parser + formatter <- defaultFormatter + runEffect $ inputFiles options >-> P.map (reformat formatter) >-> P.mapM_ (Actions.act options) + +inputFiles :: Options -> Producer InputFileWithSource IO () +inputFiles options = determineInputFilePaths (optPaths options) >-> P.mapM readInputFile + where + determineInputFilePaths :: [FilePath] -> Producer InputFile IO () + determineInputFilePaths [] = enumeratePath "." >-> P.map InputFilePath + determineInputFilePaths ["-"] = yield InputFromStdIn + determineInputFilePaths paths = for (each paths) enumeratePath >-> P.map InputFilePath + + readInputFile :: InputFile -> IO InputFileWithSource + readInputFile (InputFilePath path) = InputFileWithSource (InputFilePath path) <$> readSource + path + readInputFile (InputFromStdIn) = InputFileWithSource InputFromStdIn <$> readStdin + +reformat :: Formatter -> InputFileWithSource -> ReformatResult +reformat (Formatter format) (InputFileWithSource input source) = + case format source of + Left error -> InvalidReformat input error + Right reformatted -> Reformat input source reformatted + +readSource :: HaskellSourceFilePath -> IO HaskellSource +readSource path = HaskellSource <$> readFile path + +readStdin :: IO HaskellSource +readStdin = HaskellSource <$> getContents
+ app/OptionsParser.hs view
@@ -0,0 +1,77 @@+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 currend 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 - formats 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
@@ -0,0 +1,31 @@+module Types ( + Action(..), + InputFile(..), + InputFileWithSource(..), + ErrorString, + ReformatResult(..), + HaskellSourceFilePath, + HaskellSource(..), + ) where + +import Language.Haskell.Format +import Language.Haskell.Source.Enumerator (HaskellSourceFilePath) + +data Action = PrintDiffs + | PrintSources + | PrintFilePaths + | WriteSources + +data InputFile = InputFilePath HaskellSourceFilePath + | InputFromStdIn + +instance Show InputFile where + show (InputFilePath path) = path + show InputFromStdIn = "-" + +data InputFileWithSource = InputFileWithSource InputFile HaskellSource + +type ErrorString = String + +data ReformatResult = InvalidReformat InputFile ErrorString + | Reformat InputFile HaskellSource Reformatted
+ hfmt.cabal view
@@ -0,0 +1,79 @@+name: hfmt +version: 0.0.1.0 +synopsis: Haskell source code formatter +description: Inspired by gofmt. Built using hlint, hindent, and stylish-haskell. +license: MIT +license-file: LICENSE +author: Daniel Stiner +maintainer: Daniel Stiner <daniel.stiner@gmail.com> +stability: Experimental +homepage: http://github.com/danstiner/hfmt +bug-reports: http://github.com/danstiner/hfmt/issues +category: Language +build-type: Simple +cabal-version: >=1.9.2 +tested-with: GHC==7.8.4, GHC==7.10.2, GHC == 7.11.* + +library + Hs-Source-Dirs: src + Exposed-Modules: Language.Haskell.Format + , 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 + GHC-Options: -Wall + Build-Depends: base == 4.* + , Cabal + , Diff + , directory + , filepath + , haskell-src-exts + , hindent >= 4.5 + , hlint >= 1.9.13 , hlint < 2 + , HUnit + , pipes + , pretty + , stylish-haskell + , text + +executable hfmt + Hs-Source-Dirs: app + Main-Is: Main.hs + Other-Modules: Actions + , OptionsParser + , Types + GHC-Options: -Wall + Build-Depends: base == 4.* + , hfmt + , ansi-wl-pprint + , Diff + , optparse-applicative + , pipes + , pretty + +test-suite self-formatting-test + Type: exitcode-stdio-1.0 + Hs-Source-Dirs: test/self-formatting + Main-Is: Spec.hs + Build-Depends: base == 4.* + , hfmt + , HUnit + , test-framework + , test-framework-hunit + +test-suite pure-test + Type: exitcode-stdio-1.0 + Hs-Source-Dirs: test/pure + Main-Is: Spec.hs + Build-Depends: base == 4.* + , hfmt + , HUnit + , test-framework + , test-framework-hunit + +source-repository head + type: git + location: git://github.com:danstiner/hfmt.git
+ src/Language/Haskell/Format.hs view
@@ -0,0 +1,42 @@+module Language.Haskell.Format ( + autoSettings, + format, + formatters, + hlint, + hindent, + stylish, + Settings, + Formatter(..), + Suggestion(..), + HaskellSource(..), + 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 Language.Haskell.HLint3 (Idea) + +data Settings = Settings + +autoSettings :: IO Settings +autoSettings = return Settings + +hlint :: Settings -> IO Formatter +hlint _ = HLint.suggester <$> HLint.autoSettings + +hindent :: Settings -> IO Formatter +hindent _ = HIndent.formatter <$> HIndent.autoSettings + +stylish :: Settings -> IO Formatter +stylish _ = Stylish.formatter <$> Stylish.autoSettings + +formatters :: Settings -> IO [Formatter] +formatters s = sequence [hlint s, hindent s, stylish s] + +format :: Formatter -> HaskellSource -> Either String Reformatted +format = unFormatter
+ src/Language/Haskell/Format/Definitions.hs view
@@ -0,0 +1,31 @@+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) + +data 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
@@ -0,0 +1,28 @@+module Language.Haskell.Format.HIndent (autoSettings, formatter, defaultFormatter) where + +import Control.Applicative +import Data.Maybe +import Data.Text.Lazy as L +import Data.Text.Lazy.Builder +import HIndent +import Language.Haskell.Exts.Extension (Extension) + +import Language.Haskell.Format.Definitions +import Language.Haskell.Format.Internal + +data Settings = Settings Style (Maybe [Extension]) + +defaultFormatter :: IO Formatter +defaultFormatter = formatter <$> autoSettings + +autoSettings :: IO Settings +autoSettings = return (Settings gibiansky Nothing) + +formatter :: Settings -> Formatter +formatter = mkFormatter . hindent + +hindent :: Settings -> HaskellSource -> Either String HaskellSource +hindent (Settings style extensions) (HaskellSource source) = + HaskellSource . L.unpack . toLazyText <$> reformat style extensions sourceText + where + sourceText = L.pack source
+ src/Language/Haskell/Format/HLint.hs view
@@ -0,0 +1,41 @@+module Language.Haskell.Format.HLint (autoSettings, formatter, suggester) where + +import Language.Haskell.Format.Definitions +import Language.Haskell.Format.Internal + +import Control.Applicative +import Data.Maybe +import Language.Haskell.Exts.Annotated as Hse +import Language.Haskell.HLint3 hiding (autoSettings) + +type Settings = (ParseMode, [Classify], Hint) + +formatter = undefined + +suggester :: (ParseMode, [Classify], Hint) -> Formatter +suggester = mkSuggester . hlint + +hlint :: (ParseMode, [Classify], Hint) -> HaskellSource -> Either String [Suggestion] +hlint (parseMode, classifications, hint) (HaskellSource source) = + getSuggestions <$> parseResultAsEither (parse source) + where + parse = Hse.parseFileContentsWithComments parseMode + getSuggestions moduleSource = map ideaToSuggestion $ applyHints classifications hint + [moduleSource] + +autoSettings :: IO (ParseMode, [Classify], Hint) +autoSettings = do + (fixities, classify, hints) <- findSettings (readSettingsFile Nothing) Nothing + return (hseFlags (parseFlagsAddFixities fixities defaultParseFlags), classify, resolveHints hints) + +parseResultAsEither :: ParseResult a -> Either String a +parseResultAsEither (ParseOk a) = Right a +parseResultAsEither (ParseFailed loc error) = + Left ("Parse failed at " ++ location ++ ": " ++ error) + where + location = filename ++ " " ++ linecol + filename = "[" ++ srcFilename loc ++ "]" + linecol = "(" ++ show (srcLine loc) ++ ":" ++ show (srcColumn loc) ++ ")" + +ideaToSuggestion :: Idea -> Suggestion +ideaToSuggestion = Suggestion . show
+ src/Language/Haskell/Format/Internal.hs view
@@ -0,0 +1,11 @@+module Language.Haskell.Format.Internal (mkFormatter, mkSuggester) where + +import Language.Haskell.Format.Definitions + +import Control.Applicative + +mkFormatter :: (HaskellSource -> Either String HaskellSource) -> Formatter +mkFormatter f = Formatter (fmap (\source -> Reformatted source []) . f) + +mkSuggester :: (HaskellSource -> Either String [Suggestion]) -> Formatter +mkSuggester f = Formatter $ \source -> Reformatted source <$> f source
+ src/Language/Haskell/Format/Stylish.hs view
@@ -0,0 +1,23 @@+module Language.Haskell.Format.Stylish (autoSettings, formatter) where + +import Control.Applicative +import Language.Haskell.Stylish as Stylish + +import Language.Haskell.Format.Definitions +import Language.Haskell.Format.Internal + +data Settings = Settings Stylish.Config + +autoSettings :: IO Settings +autoSettings = Settings <$> Stylish.loadConfig (Stylish.makeVerbose False) Nothing + +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 + where + sourceLines = lines source + extensions = Stylish.configLanguageExtensions config + steps = Stylish.configSteps config
+ src/Language/Haskell/Format/Utilities.hs view
@@ -0,0 +1,60 @@+module Language.Haskell.Format.Utilities (wasReformatted, hunitTest, defaultFormatter) where + +import Language.Haskell.Format +import Language.Haskell.Format.Definitions +import Language.Haskell.Source.Enumerator + +import Control.Applicative +import Control.Monad +import Data.Monoid +import Pipes +import qualified Pipes.Prelude as P +import Test.HUnit + +type ErrorString = String + +data CheckResult = InvalidCheckResult ErrorString + | CheckResult HaskellSource Reformatted + +hunitTest :: FilePath -> Test +hunitTest filepath = TestLabel filepath $ makeTestCase filepath + +makeTestCase :: FilePath -> Test +makeTestCase filepath = TestCase $ do + formatter <- defaultFormatter + runEffect $ check formatter filepath >-> assertResults + +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)) + +check :: Formatter -> FilePath -> Producer CheckResult IO () +check formatter path = enumeratePath path >-> + P.mapM readSource >-> + P.map (checkFormatting formatter) + +readSource :: HaskellSourceFilePath -> IO HaskellSource +readSource path = HaskellSource <$> readFile path + +checkFormatting :: Formatter -> HaskellSource -> CheckResult +checkFormatting (Formatter format) source = + case format source of + Left error -> InvalidCheckResult error + 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
+ src/Language/Haskell/Source/Enumerator.hs view
@@ -0,0 +1,91 @@+module Language.Haskell.Source.Enumerator (enumeratePath, HaskellSourceFilePath) where + +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 + +simpleFileOrDirectory :: FilePath -> Producer HaskellSourceFilePath IO () +simpleFileOrDirectory filepath = do + dir <- lift $ doesDirectoryExist filepath + if dir + then simpleDirectory filepath + else simpleFilePath filepath + +package' :: FilePath -> Producer HaskellSourceFilePath IO () +package' path = readPackage path >>= expandPaths + where + readPackage = lift . readPackageDescription Verbosity.silent + expandPaths = mapM_ (simpleFileOrDirectory . mkFull) . sourcePaths + dir = dropFileName path + mkFull = (dir </>) + +directory :: FilePath -> Producer HaskellSourceFilePath IO () +directory path = do + contents <- lift $ getDirectoryContentsFullPaths 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 + +getDirectoryContentsFullPaths :: FilePath -> IO [FilePath] +getDirectoryContentsFullPaths path = mkFull . notHidden . notMeta <$> getDirectoryContents path + where + mkFull = map (path </>) + notHidden = filter (not . isPrefixOf ".") + notMeta = (\\ [".", ".."]) + +isCabalFile :: FilePath -> IO Bool +isCabalFile path = (hasCabalExtension &&) <$> isFile + where + isFile = doesFileExist path + hasCabalExtension = ".cabal" `isSuffixOf` path + +isHaskellSourceFile :: FilePath -> IO Bool +isHaskellSourceFile path = (hasHaskellExtension &&) <$> isFile + where + isFile = doesFileExist path + hasHaskellExtension = ".hs" `isSuffixOf` path || ".lhs" `isSuffixOf` path + +sourcePaths :: GenericPackageDescription -> [FilePath] +sourcePaths pkg = nub $ concatMap ($ pkg) pathExtractors + where + pathExtractors = [ maybe [] (hsSourceDirs . libBuildInfo . condTreeData) . condLibrary + , concatMap (hsSourceDirs . buildInfo . condTreeData . snd) . condExecutables + , concatMap (hsSourceDirs . testBuildInfo . condTreeData . snd) . condTestSuites + , concatMap (hsSourceDirs . benchmarkBuildInfo . condTreeData . snd) . condBenchmarks + ]
+ test/pure/Spec.hs view
@@ -0,0 +1,8 @@+module Main where + +import Test.Framework (defaultMain, testGroup) +import Test.Framework.Providers.HUnit +import Test.HUnit hiding (Test) + +main :: IO () +main = defaultMain []
+ test/self-formatting/Spec.hs view
@@ -0,0 +1,13 @@+module Main where + +import Language.Haskell.Format.Utilities + +import Test.Framework (defaultMain, testGroup) +import Test.Framework.Providers.HUnit +import Test.HUnit hiding (Test) + +main :: IO () +main = defaultMain + [ testGroup "Check formatting of package sources" + (hUnitTestToTests $ hunitTest "hfmt.cabal") + ]