content: {-# LANGUAGE OverloadedStrings #-}  --
  
  import Data.Char
  -- import Data.Monoid
  import System.Directory
  import System.FilePath.Posix
  -- import System.IO
  import Test.CommonUtils
  import Turtle hiding (FilePath,(<.>))
  import qualified Data.Set as Set
  import qualified Data.Text as T
  import qualified Data.Text.IO as T
  
  -- import qualified GHC.IO.Handle.Text as GHC
  
  import Test.HUnit
  
  main :: IO ()
  main = do
    packages <- allCabalPackages
    -- packages <- allCabalPackagesTest
    myecho (T.pack $ "number of packages:" ++ (show $ length packages))
    packageDirsFull <- drop 2 <$> getDirectoryContents hackageWorkDir
    let cond c = c == '.' || c == '-' || isDigit c
    let packageDirs = map (T.dropWhileEnd cond . T.pack) packageDirsFull
    isBadPackages <- doesFileExist badpackagesFile
    badPackages <- if isBadPackages
                     then T.lines <$> T.readFile badpackagesFile
                     else return []
    let alreadyUnpacked = Set.fromList $ packageDirs ++ badPackages
    _ <- shell ("mkdir -p " <> (T.pack hackageWorkDir)) empty
    mapM_ (preparePackage alreadyUnpacked) packages
  
  -- ---------------------------------------------------------------------
  
  preparePackage :: Set.Set Text -> Text -> IO ()
  preparePackage alreadyUnpacked package = do
    myecho $ "preparePackage:" <> package
    if Set.member package alreadyUnpacked
       then myecho $ "already unpacked:" <> package
       else preparePackage' package
  
  preparePackage' :: Text -> IO ()
  preparePackage' package = do
    (ec,dir) <- shellStrict ("cabal get --destdir=" <> T.pack hackageWorkDir <> " " <> package) empty
    myecho (T.pack $ "cabal get:" ++ show dir)
    -- cabal get:"Unpacking to\n./hackage-roundtrip-work/amazonka-kinesis-video-archived-media-2.0/\n"
    myecho (T.pack $ show ec)
    when (ec == ExitSuccess) $ do
      let bits = T.splitOn " " (last $ T.lines dir)
      myecho (T.pack $ "cabal get:dir=" ++ show (last bits))
      cleanPackage (last bits)
    return ()
  
  -- ---------------------------------------------------------------------
  
  -- |Clean up whitespace in a package
  
  cleanPackage :: Text -> IO ()
  cleanPackage dir = do
    myecho ("cleaning:" <> dir)
    fs <- findSrcFiles (T.unpack dir)
    let
      doOne :: FilePath -> IO ()
      doOne fn = do
        myecho ("doOne:" <> T.pack fn)
        let tmpFn = fn <.> "clean"
        clean <- cleanupWhiteSpace fn
        writeFile tmpFn clean
        removeFile fn
        renameFile tmpFn fn
        return ()
    mapM_ doOne fs
    myecho ("cleaned up:" <> dir)
  
  -- ---------------------------------------------------------------------
  -- | The computation 'writeFile' @file str@ function writes the string @str@,
  -- to the file @file@.
  -- writeFileUtf8 :: FilePath -> String -> IO ()
  -- writeFileUtf8 ff txt = withFile ff WriteMode (\ hdl -> hSetEncoding hdl utf8 >> GHC.hPutStr hdl txt)
  
  -- ---------------------------------------------------------------------
  
  -- allCabalPackagesTest :: IO [Text]
  -- allCabalPackagesTest
  --   = return ["3d-graphics-examples","3dmodels","4Blocks","AAI","ABList"]
  --   -- = return ["airship"]
  
  
  allCabalPackages :: IO [Text]
  allCabalPackages = do
    -- let cmd = "cabal list --simple-output | awk '{ print $1 }' | uniq"
    let cmd = "cabal list --simple-output | awk '{ print $1 }' | sort | uniq"
    (_ec,r) <- shellStrict cmd empty
    let packages = T.lines r
    myecho (T.pack $ show $ take 5 packages)
    return packages
  
  -- ---------------------------------------------------------------------
  
  -- |strip trailing whitespace, and turn tabs into spaces
  cleanupWhiteSpace :: FilePath -> IO String
  cleanupWhiteSpace file = do
    contents <- readFileGhc file
    let cleaned = map cleanupOneLine (lines $ contents)
    return (unlines cleaned)
  
  tabWidth :: Int
  tabWidth = 8
  
  -- nonBreakingSpace :: Char
  -- nonBreakingSpace = '\xa0'
  
  cleanupOneLine :: String -> String
  cleanupOneLine str = str'
    where
      numSpacesForTab n = tabWidth - (n `mod` tabWidth)
      -- loop over the line, keeping current pos. Where a tab is found, insert
      -- spaces until the next tab stop. Discard any trailing whitespace.
      go col res cur =
        case cur of
          [] -> res
          ('\t':cur') -> go (col + toAdd) ((replicate toAdd ' ') ++ res) cur'
                  where
                    toAdd = numSpacesForTab col
          ('\xa0':cur') -> go (col + 1) (' ':res) cur'
  
          -- convert ISO 8859-16 euro symbol to the UTF8 equivalent
          -- ('\xa4':cur') -> go (col + 1) ('\x20ac':res) cur'
          (c:cur') ->go (col + 1) (c:res) cur'
      str1 = go 0 [] str
      str' = reverse $ dropWhile isSpace str1
  
  -- ---------------------------------------------------------------------
  
  tt :: IO Counts
  tt = runTestTT $ TestList
                    [ testCleanupOneLine
                    , testTabs
                    ]
  
  testCleanupOneLine :: Test
  testCleanupOneLine = do
    let
      makeCase n = (show n
                   ,(replicate n ' ') <> "\t|" <> replicate n ' ' <> "\t"
                   ,(replicate 8 ' ' <> "|"))
      mkTest n = TestCase $ assertEqual name outp (cleanupOneLine inp)
        where (name,inp,outp) = makeCase n
    testList "cleanupOneLine" $ map mkTest [1..7]
  
  testTabs :: Test
  testTabs = TestCase $ assertEqual "testTabs" t2tabsExpected (cleanupOneLine t2tabs)
    where
      t2tabs = "import Data.Foldable\t\t    ( foldMap )"
      t2tabsExpected ="import Data.Foldable                ( foldMap )"
  
  testList :: String -> [Test] -> Test
  testList str ts = TestLabel str (TestList ts)
  
  -- ---------------------------------------------------------------------
  
  myecho :: T.Text -> IO ()
  myecho t = mapM_ echo (textToLines t)
  
  -- ---------------------------------------------------------------------
  
  pwd :: IO FilePath
  pwd = getCurrentDirectory
  
  mcd :: FilePath -> IO ()
  mcd = setCurrentDirectory
  