examples/re-sort-imports.lhs

Example: Sort-Import Processor

This example looks for Haskell files and sorts their import statements into a standard (alphabetical) order

{-# LANGUAGE NoImplicitPrelude          #-}
{-# LANGUAGE RecordWildCards            #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TemplateHaskell            #-}
{-# LANGUAGE QuasiQuotes                #-}
{-# LANGUAGE OverloadedStrings          #-}

module Main
  ( main
  ) where

import           Control.Applicative
import qualified Control.Monad                            as M
import qualified Data.ByteString.Lazy.Char8               as LBS
import           Prelude.Compat
import           System.Directory
import           System.Environment
import           System.Exit
import           System.FilePath
import           TestKit
import           Text.Printf
import           Text.RE.TDFA.String
import           Text.RE.Tools.Find

Mode

This program can run in one of two modes.

data Mode
    = Check           -- only check for unsorted files, generating an
                      -- error if any not sorted
    | Update          -- update any unsorted files
  deriving (Eq,Show)
main :: IO ()
main = do
  as  <- getArgs
  case as of
    []                         -> test
    ["test"]                   -> test
    ["update",fp] | is_file fp -> sort_r Update fp
    ["check" ,fp] | is_file fp -> sort_r Check  fp
    _                          -> usage
  where
    is_file = not . (== "--") . take 2

    test = do
      sort_r Check "Text"
      sort_r Check "examples"

    usage = do
      prg <- getProgName
      putStr $ unlines
        [ "usage:"
        , "  "++prg++" [test]"
        , "  "++prg++" check  <directory>"
        , "  "++prg++" update <directory>"
        ]

The Find Script

sort_r :: Mode -> FilePath -> IO ()
sort_r md root = findMatches_ fm [re|\.l?hs|] root >>= sort_these md root
  where
    fm = FindMethods
      { doesDirectoryExistDM = doesDirectoryExist
      , listDirectoryDM      = getDirectoryContents
      , combineDM            = (</>)
      }

Processing the List of Files

sort_these :: Mode -> FilePath -> [FilePath] -> IO ()
sort_these md root fps = do
  ok <- and <$> mapM (sort_this md) fps
  case ok of
    True  -> msg "all imports sorted"
    False -> case md of
      Check  -> do
        msg "Some imports need sorting"
        exitWith $ ExitFailure 1
      Update ->
        msg "Some imports were sorted"
  where
    msg :: String -> IO ()
    msg s = printf "%-10s : %s\n" root s

Processing a single File

sort_this :: Mode -> FilePath -> IO Bool
sort_this md fp = LBS.readFile fp >>= sort_this'
  where
    sort_this' lbs = do
        M.when (not same)   $ putStrLn fp
        M.when (md==Update) $ LBS.writeFile fp lbs'
        return same
      where
        same = lbs==lbs'
        lbs' = sortImports lbs

Sorting the Imports of the Text of a Haskell Script

The function for sorting a Haskell script, sortImports has been placed in TestKit so that it can be shared with re-gen-modules`.

sortImports :: LBS.ByteString -> LBS.ByteString
sortImports lbs =
    LBS.unlines $ map (matchesSource . getLineMatches) $
      hdr ++ L.sortBy cMp bdy
  where
    cMp ln1 ln2 = case (extr ln1,extr ln2) of
        (Nothing,Nothing) -> EQ
        (Nothing,Just _ ) -> GT
        (Just _ ,Nothing) -> LT
        (Just x ,Just  y) -> compare x y

    extr Line{..} = case allMatches getLineMatches of
      mtch:_  -> mtch !$$? [cp|mod|]
      _       -> Nothing

    (hdr,bdy) = span (not . anyMatches . getLineMatches) lns
    lns       = grepFilter rex lbs
    rex       = [re|^import +(qualified|         ) ${mod}([^ ].*)$|]