filib/codeclimate-shellcheck

View on GitHub
app/Main.hs

Summary

Maintainability
Test Coverage
{-# LANGUAGE RecordWildCards #-}

module Main where

import CC
import CC.ShellCheck.Analyze
import CC.ShellCheck.Env
import CC.ShellCheck.ShellScript
import CC.ShellCheck.Types
import CLI
import Control.Concurrent
import Control.Monad
import Data.Maybe
import Options.Applicative

--------------------------------------------------------------------------------

main :: IO ()
main = execParser cliOpts >>= runCli

--------------------------------------------------------------------------------

-- | Takes CLIOpts options and runs the main program.
runCli :: CLIOpts -> IO ()
runCli CLIOpts{..} = do
  config  <- loadConfig (fromMaybe "./config.json" configPath)
  env     <- loadEnv (fromMaybe "./data/env.yml" envPath)
  scripts <- findShellScripts $! _include_paths config
  chan0   <- newChan
  chan1   <- dupChan chan0
  chan2   <- dupChan chan0
  _       <- forkIO (reporter chan1)
  _       <- forkIO (analyzer env scripts chan0)
  waitUntilReported chan2
  where
    waitUntilReported :: Chan Analysis -> IO ()
    waitUntilReported chan = do
      analysis <- readChan chan
      case analysis of
        Reported     -> return ()
        _            -> waitUntilReported chan

--------------------------------------------------------------------------------

-- | Represents three possible states of an analysis chan.
data Analysis = Result !Issue
              | Complete
              | Reported
              deriving Show

--------------------------------------------------------------------------------

-- | Takes a list of scripts, analyzes them and writes the results to a chan.
analyzer :: Env -> [FilePath] -> Chan Analysis -> IO ()
analyzer env paths chan = do
  forM_ paths $ \path -> do
    issues <- analyze env path
    mapM_ writeChanAndForce issues
  writeChan chan Complete
  where
    -- | To get brief locking and no space leaks, we need to use a trick.
    -- Source: Parallel and Concurrent Programming in Haskell
    --         MVar as a Building Block: Unbounded Channels, p135
    writeChanAndForce :: Issue -> IO ()
    writeChanAndForce issue = do
      let result = Result issue
      writeChan chan result
      seq result (return ())

--------------------------------------------------------------------------------

-- | Continually tries to report issue written to a chan.
reporter :: Chan Analysis -> IO ()
reporter chan = doUntilComplete
  where
    doUntilComplete :: IO ()
    doUntilComplete = do
      analysis <- readChan chan
      case analysis of
        Result issue -> printIssue issue >> doUntilComplete
        Complete     -> writeChan chan Reported
        Reported     -> return ()