packages feed

scan-0.1.0.0: src/scan.hs

{- |
Module      :  scan.hs
Description :  the standalone Haskell style scanner
Copyright   :  (c) Christian Maeder 2010
License     :  BSD

Maintainer  :  chr.maeder@web.de
Stability   :  experimental
Portability :  portable

the Haskell style scanner
-}

module Main where

import Control.Monad

import Data.Char
import Data.List

import System.Environment
import Text.ParserCombinators.Parsec

import Language.Haskell.Scanner

main :: IO ()
main = do
  args <- getArgs
  let (opts, files) = span (== "-") args
      b = null opts
  case files of
    [] -> putStrLn "missing file argument"
    _ -> mapM_ (process $ null opts) $ if b then files else
         take 3 files -- do not spoil more than 3 files

process :: Bool -> String -> IO ()
process b f = do
  str <- readFile f
  let nls = zip [1 ..] $ lines str
      bls = checkBlankLines f 1 nls
  when b $ mapM_ (checkLine f) nls
  when (b && not (isSuffixOf "\n" str))
    $ diag f (length nls) "missing final newline"
  when b $ mapM_ putStrLn bls
  case parse scan f str of
    Right ts -> let x = splitLines ts in
      if b then let o = showScan x in unless (null o) $ putStrLn o else
      let rstr = processScan x in
      if rstr == str then putStrLn $ "no changes in \"" ++ f ++ "\"" else do
      writeFile (f ++ ".bak") str
      writeFile f rstr
      putStrLn $ "updated \"" ++ f ++ "\" (and created .bak)"
    Left err -> fail $ show err

checkBlankLines :: FilePath -> Int -> [(Int, String)] -> [String]
checkBlankLines f c l = case l of
  [] -> if c > 0 then ["trailing blank lines"] else []
  (n, s) : r ->
    if null $ filter (not . isSpace) s then
      if c >= 2 then
        diagStr f n "too many consecutive blank lines"
        : checkBlankLines f (- 100) r
      else checkBlankLines f (c + 1) r
    else checkBlankLines f 0 r

diagStr :: FilePath -> Int -> String -> String
diagStr f n str = "\"" ++ f ++ "\" (line " ++ show n ++ ") " ++ str

diag :: FilePath -> Int -> String -> IO ()
diag f n = putStrLn . diagStr f n

checkLine :: FilePath -> (Int, String) -> IO ()
checkLine f (n, s) = do
  let r = reverse s
      rt = dropWhile isSpace r
      l = length rt
      trailBSlash = takeWhile (== '\\') rt
      badChars = filter (\ c -> not $ isAscii c && isPrint c) s
  when (l > 80) $
    diag f n $ "too long line (" ++ show l ++ " chars)"
  unless (null badChars) $
    diag f n $ "contains undesirable characters: " ++ show badChars
  unless (null trailBSlash) $
    diag f n "back slash at line end (may disturb cpp)"