packages feed

stack-hpc-coveralls (empty) → 0.0.0.1

raw patch · 14 files changed

+716/−0 lines, 14 filesdep +aesondep +basedep +bytestringsetup-changed

Dependencies added: aeson, base, bytestring, containers, directory, directory-tree, docopt, filepath, hlint, hpc, hspec, http-client, lens, lens-aeson, process, pureMD5, safe, stack-hpc-coveralls, text, utf8-string, wreq

Files

+ LICENSE view
@@ -0,0 +1,13 @@+Copyright Michele Lacchia (c) 2015++Permission to use, copy, modify, and/or distribute this software for any+purpose with or without fee is hereby granted, provided that the above+copyright notice and this permission notice appear in all copies.++THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE QuasiQuotes #-}+module Main+    where++import Data.List (find)+import Data.Maybe (isJust)+import Data.Aeson (encode)+import qualified Data.ByteString.Lazy as BSL+import Control.Concurrent (threadDelay)+import System.Console.Docopt+import System.Environment (getArgs, getEnv, getEnvironment)+import System.Exit (exitFailure)++import SHC+++urlApiV1 :: String+urlApiV1 = "https://coveralls.io/api/v1/jobs"++getServiceAndJobId :: IO (String, String)+getServiceAndJobId = do+    env <- getEnvironment+    case snd <$> find (isJust . flip lookup env . fst) ciEnvVars of+        Just (ciName, jobIdVarName) -> do+            jId <- getEnv jobIdVarName+            return (ciName, jId)+        _ -> error "Unsupported CI service."+    where ciEnvVars = [+           ("TRAVIS",      ("travis-ci", "TRAVIS_JOB_ID")),+           ("CIRCLECI",    ("circleci",  "CIRCLE_BUILD_NUM")),+           ("SEMAPHORE",   ("semaphore", "REVISION")),+           ("JENKINS_URL", ("jenkins",   "BUILD_ID")),+           ("CI_NAME",     ("codeship",  "CI_BUILD_NUMBER"))]++patterns :: Docopt+patterns = [docoptFile|USAGE.txt|]++getArgOrExit :: Arguments -> Option -> IO String+getArgOrExit = getArgOrExitWith patterns++defaultOr :: Arguments -> Option -> IO String -> IO String+defaultOr args opt action = maybe action return $ args `getArg` opt++getConfig :: Arguments -> IO Config+getConfig args = do+    -- TODO: Read from Cabal?+    pn <- args `getArgOrExit` (argument "package-name")+    (sn, jId) <- getServiceAndJobId+    Config <$> args `getArgOrExit` (argument "suite-name")+           <*> pure sn+           <*> pure jId+           <*> pure (args `getArg` (longOption "repo-token"))+           <*> getGitInfo+           <*> defaultOr args (longOption "hpc-dir") (getHpcDir pn)+           <*> defaultOr args (longOption "hpc-dir") getMixDir+           <*> pure (if args `isPresent` (longOption "partial-coverage")+                        then PartialLines+                        else FullLines)++main :: IO ()+main = do+    args <- parseArgsOrExit patterns =<< getArgs+    conf <- getConfig args+    coverallsJson <- generateCoverallsFromTix conf+    if args `isPresent` (longOption "dont-send")+       then BSL.putStr $ encode coverallsJson+       else do+         response <- sendData conf urlApiV1 coverallsJson+         case response of+             PostSuccess url -> do+                 putStrLn $ "URL: " ++ url+                 -- wait 5 seconds until the page is available+                 threadDelay $ 5 * 1000 * 1000+                 coverageResult <- readCoverageResult url+                 case coverageResult of+                     Just totalCov -> putStrLn $ "Coverage: " ++ totalCov+                     Nothing -> putStrLn "Failed to read total coverage"+             PostFailure msg -> do+                 putStrLn $ "Error: " ++ msg+                 exitFailure
+ src/SHC.hs view
@@ -0,0 +1,18 @@+module SHC (+    Config(..)+  , ConversionType(..)+  , PostResult(..)+  , generateCoverallsFromTix+  , sendData+  , readCoverageResult+  , getHpcDir+  , getMixDir+  , getGitInfo+  )+    where+++import SHC.Types+import SHC.Api+import SHC.Utils+import SHC.Coveralls
+ src/SHC/Api.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE OverloadedStrings #-}+module SHC.Api (sendData, readCoverageResult)+    where++import Safe (atMay, headMay)+import Data.Aeson (Value, encode)+import Data.Aeson.Lens (key, _String)+import qualified Data.Text as T+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as LBS+import Codec.Binary.UTF8.String (decode)+import Control.Lens+import Network.Wreq+import Network.HTTP.Client (RequestBody(RequestBodyLBS))+import Network.HTTP.Client.MultipartFormData (partFileRequestBody)++import SHC.Types+++sendData :: Config -> String -> Value -> IO PostResult+sendData conf url json = do+    r <- post url [partFileRequestBody "json_file" fileName requestBody]+    if r ^. responseStatus . statusCode == 200+       then return $ readResponse r+       else return . PostFailure $+           "Error: " ++ decode (BS.unpack $ r ^. responseStatus . statusMessage)+    where fileName    = serviceName conf ++ "-" ++ jobId conf ++ ".json"+          requestBody = RequestBodyLBS $ encode json++readResponse :: Response LBS.ByteString -> PostResult+readResponse r =+    case r ^? responseBody . key "error" . _String of+      Just err -> PostFailure $ T.unpack err+      Nothing  -> case r ^? responseBody . key "url" . _String of+                    Just url -> PostSuccess $ T.unpack url+                    Nothing  -> PostFailure "Error: malformed response body"++-- | Extract the total coverage percentage value from coveralls coverage result+--   page content.  The current implementation is kept as low level as possible+--   in order not to increase the library build time, by not relying on+--   additional packages.+extractCoverage :: T.Text -> Maybe T.Text+extractCoverage body = T.splitOn "<"+                    <$> T.splitOn prefix body `atMay` 1 >>= headMay+    where prefix = "div class='run-statistics'>\n<strong>"++-- | Read the coveraege result page from coveralls.io+readCoverageResult :: String -> IO (Maybe String)+readCoverageResult url = do+    r <- get url+    return . fmap T.unpack . extractCoverage $+        r ^. responseBody . _String
+ src/SHC/Coveralls.hs view
@@ -0,0 +1,134 @@+{-# LANGUAGE OverloadedStrings #-}++-- |+-- Module:      SHC.Coveralls+-- Copyright:   (c) 2015 Michele Lacchia+-- License:     ISC+-- Maintainer:  Michele Lacchia <michelelacchia@gmail.com>+-- Stability:   experimental+--+-- Functions for reading, converting and sending HPC output to coveralls.io.++module SHC.Coveralls (generateCoverallsFromTix)+    where++import           Control.Applicative+import           Data.Aeson+import           Data.Aeson.Types ()+import qualified Data.ByteString.Lazy.Char8 as LBS+import           Data.Digest.Pure.MD5+import           Data.Function+import           Data.List+import qualified Data.Map.Strict as M+import           System.Exit (exitFailure)+import           SHC.Types+import           SHC.Utils+import           SHC.Lix+import           SHC.Paths+import           Trace.Hpc.Mix+import           Trace.Hpc.Tix+import           Trace.Hpc.Util++type ModuleCoverageData = (+    String,    -- file source code+    Mix,       -- module index data+    [Integer]) -- tixs recorded by hpc++type TestSuiteCoverageData = M.Map FilePath ModuleCoverageData++-- Is there a way to restrict this to only Number and Null?+type CoverageValue = Value++-- Single file coverage data in the format defined by coveralls.io+type SimpleCoverage = [CoverageValue]+type LixConverter = Lix -> SimpleCoverage++strictConverter :: LixConverter+strictConverter = map $ \lix -> case lix of+    Full       -> Number 1+    Partial    -> Number 0+    None       -> Number 0+    Irrelevant -> Null++looseConverter :: LixConverter+looseConverter = map $ \lix -> case lix of+    Full       -> Number 2+    Partial    -> Number 1+    None       -> Number 0+    Irrelevant -> Null++toSimpleCoverage :: LixConverter -> Int -> [CoverageEntry] -> SimpleCoverage+toSimpleCoverage convert lineCount = convert . toLix lineCount++getExprSource :: [String] -> MixEntry -> [String]+getExprSource source (hpcPos, _) = subSubSeq startCol endCol subLines+    where subLines = subSeq startLine endLine source+          startLine = startLine' - 1+          startCol = startCol' - 1+          (startLine', startCol', endLine, endCol) = fromHpcPos hpcPos++groupMixEntryTixs :: [(MixEntry, Integer, [String])] -> [CoverageEntry]+groupMixEntryTixs = map mergeOnLst3 . groupBy ((==) `on` fst . fst3)+    where mergeOnLst3 xxs@(x : _) = (map fst3 xxs, map snd3 xxs, trd3 x)+          mergeOnLst3 [] = error "mergeOnLst3 appliedTo empty list"++coverageToJson :: LixConverter -> FilePath -> ModuleCoverageData -> Value+coverageToJson converter filePath (source, mix, tixs) = object [+    "name" .= filePath,+    "source_digest" .= (show . md5 . LBS.pack) source,+    "coverage" .= coverage]+    where coverage = toSimpleCoverage converter lineCount mixEntriesTixs+          lineCount = length $ lines source+          mixEntriesTixs = groupMixEntryTixs mixEntryTixs+          mixEntryTixs = zip3 mixEntries tixs (map getExprSource' mixEntries)+          Mix _ _ _ _ mixEntries = mix+          getExprSource' = getExprSource $ lines source++toCoverallsJson :: Config -> LixConverter -> TestSuiteCoverageData -> Value+toCoverallsJson conf converter testSuiteCoverageData =+    object $ if serviceName conf == "travis-ci" then withRepoToken else withGitInfo+    where base = [+              "service_job_id" .= jobId conf,+              "service_name"   .= serviceName conf,+              "source_files"   .= toJsonCoverageList testSuiteCoverageData]+          toJsonCoverageList = map (uncurry $ coverageToJson converter) . M.toList+          withRepoToken = mcons (("repo_token" .=) <$> repoToken conf) base+          withGitInfo   = ("git" .= gitInfo conf) : withRepoToken++mergeCoverageData :: [TestSuiteCoverageData] -> TestSuiteCoverageData+mergeCoverageData = foldr1 (M.unionWith mergeModule)+    where mergeModule (source, mix, tixs1) (_, _, tixs2) =+            (source, mix, zipWith (+) tixs1 tixs2)++readMix' :: Config -> TixModule -> IO Mix+readMix' conf tix = readMix [SHC.Types.mixDir conf] (Right tix)++-- | Create a list of coverage data from the tix input+readCoverageData :: Config -> String -> IO TestSuiteCoverageData+readCoverageData conf suite = do+    let tixPath = getTixPath (hpcDir conf) suite+    mTix <- readTix tixPath+    case mTix of+        Nothing -> putStrLn ("Couldn't find the file " ++ tixPath) >>+                   dumpDirectoryTree (hpcDir conf) >> exitFailure+        Just (Tix tixs) -> do+            mixs <- mapM (readMix' conf) tixs+            let files = map filePath mixs+            sources <- mapM readFile files+            let coverageDataList = zip4 files sources mixs (map tixModuleTixs tixs)+            let filteredCoverageDataList = filter sourceDirFilter coverageDataList+            return $ M.fromList $ map toFirstAndRest filteredCoverageDataList+            where filePath (Mix fp _ _ _ _) = fp+                  sourceDirFilter = not . matchAny excludeDirPatterns . fst4+                  excludeDirPatterns = []  -- XXX: for now++-- | Generate Coveralls JSON formatted code coverage from HPC coverage data+generateCoverallsFromTix :: Config -> IO Value+generateCoverallsFromTix conf = do+    testSuitesCoverages <- mapM (readCoverageData conf) [testSuiteName] -- XXX: fix suites+    let coverageData = mergeCoverageData testSuitesCoverages+    return $ toCoverallsJson conf converter coverageData+    where testSuiteName = suiteName conf+          converter = case conversion conf of+              FullLines -> strictConverter+              PartialLines -> looseConverter
+ src/SHC/Lix.hs view
@@ -0,0 +1,58 @@+-- |+-- Module:      SHC.Lix+-- Copyright:   (c) 2014-2015 Guillaume Nargeot+-- License:     BSD3+-- Maintainer:  Michele Lacchia <michelelacchia@gmail.com>+-- Stability:   experimental+-- Portability: portable+--+-- Functions for converting HPC output to line-based code coverage data.++module SHC.Lix+    where++import Data.Ord+import Data.List+import Prelude hiding (getLine)+import SHC.Types+import SHC.Utils+import Trace.Hpc.Mix+import Trace.Hpc.Util++toHit :: [Bool] -> Hit+toHit []  = Irrelevant+toHit [x] = if x then Full else None+toHit xs+    | and xs    = Full+    | or xs     = Partial+    | otherwise = None++getLine :: MixEntry -> Int+getLine = fffst . fromHpcPos . fst+    where fffst (x, _, _, _) = x++toLineHit :: CoverageEntry -> (Int, Bool)+toLineHit (entries, counts, _source) = (getLine (head entries) - 1, all (> 0) counts)++isOtherwiseEntry :: CoverageEntry -> Bool+isOtherwiseEntry (mixEntries, _, source) =+    source == ["otherwise"] && boxLabels == otherwiseBoxLabels+    where boxLabels = map snd mixEntries+          otherwiseBoxLabels = [+              ExpBox False,+              BinBox GuardBinBox True,+              BinBox GuardBinBox False]++adjust :: CoverageEntry -> CoverageEntry+adjust coverageEntry@(mixEntries, tixs, source) =+    if isOtherwiseEntry coverageEntry && any (> 0) tixs+    then (mixEntries, [1, 1, 1], source)+    else coverageEntry++-- | Convert hpc coverage entries into a line based coverage format+toLix :: Int             -- ^ Source line count+      -> [CoverageEntry] -- ^ Mix entries and associated hit count+      -> Lix             -- ^ Line coverage+toLix lineCount entries = map toHit (groupByIndex lineCount sortedLineHits)+    where sortedLineHits = sortBy (comparing fst) lineHits+          lineHits = map (toLineHit . adjust) entries
+ src/SHC/Paths.hs view
@@ -0,0 +1,56 @@+-- |+-- Module:      Trace.Hpc.Coveralls.Paths+-- Copyright:   (c) 2014-2015 Guillaume Nargeot+-- License:     BSD3+-- Maintainer:  Guillaume Nargeot <guillaume+hackage@nargeot.com>+-- Stability:   experimental+--+-- Paths constants and functions for hpc coverage report output.++module SHC.Paths+    where++import Control.Monad+import Data.Maybe+import Data.List (isSuffixOf)+import Data.Traversable (traverse)+import System.Directory (+    doesDirectoryExist, getDirectoryContents+    )+import System.Directory.Tree (+    AnchoredDirTree(..), dirTree, readDirectoryWith+    )+import System.FilePath ((</>))+import Trace.Hpc.Tix+++tixDir :: FilePath -> FilePath+tixDir = (</> "tix")++mixDir :: FilePath -> FilePath+mixDir = (</> "mix")++getTixPath :: FilePath -> String -> FilePath+getTixPath hpcDir testSuiteName = tixDir hpcDir </> testSuiteName </> getTixFileName testSuiteName++firstExistingDirectory :: [FilePath] -> IO (Maybe FilePath)+firstExistingDirectory = fmap msum . mapM pathIfExist+    where pathIfExist path = do+              pathExists <- doesDirectoryExist path+              return $ if pathExists then Just path else Nothing++dumpDirectory :: FilePath -> IO ()+dumpDirectory path = do+    directoryExists <- doesDirectoryExist path+    unless directoryExists $ putStrLn ("Couldn't find the directory " ++ path)+    putStrLn $ "Dumping " ++ path ++ " directory content:"+    contents <- getDirectoryContents path+    traverse putStrLn contents+    return ()++dumpDirectoryTree :: FilePath -> IO ()+dumpDirectoryTree path = do+    putStrLn $ "Dumping " ++ path ++ " directory tree:"+    tree <- readDirectoryWith return path+    traverse putStrLn $ dirTree tree+    return ()
+ src/SHC/Types.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE OverloadedStrings #-}++module SHC.Types+    where++import Data.Aeson+import Trace.Hpc.Mix+++data ConversionType = FullLines+                    | PartialLines+                    deriving (Show, Eq)++type CoverageEntry = ( [MixEntry]  -- ^ Mix entries+                     , [Integer]   -- ^ Tix values+                     , [String]    -- ^ Entry source code+                     )++data Hit = Full+         | Partial+         | None+         | Irrelevant+         deriving (Eq, Show)++type Lix = [Hit]++data PostResult = PostSuccess String+                | PostFailure String+                deriving (Show)++data Config = Config {+    suiteName   :: String+  , serviceName :: String+  , jobId       :: String+  , repoToken   :: Maybe String+  , gitInfo     :: GitInfo+  , hpcDir      :: FilePath+  , mixDir      :: FilePath+  , conversion  :: ConversionType+  }++data GitInfo = GitInfo {+    headRef :: Commit+  , branch  :: String+  , remotes :: [Remote]+  }++data Commit = Commit {+    hash           :: String+  , authorName     :: String+  , authorEmail    :: String+  , committerName  :: String+  , committerEmail :: String+  , message        :: String+  }++data Remote = Remote {+    name :: String+  , url  :: String+  }++instance ToJSON GitInfo where+    toJSON i = object [ "head"    .= headRef i+                      , "branch"  .= branch i+                      , "remotes" .= remotes i+                      ]++instance ToJSON Commit where+    toJSON c = object [ "id"              .= hash c+                      , "author_name"     .= authorName c+                      , "author_email"    .= authorEmail c+                      , "committer_name"  .= committerName c+                      , "committer_email" .= committerEmail c+                      , "message"         .= message c+                      ]++instance ToJSON Remote where+    toJSON r = object [ "name" .= name r+                      , "url"  .= url r+                      ]
+ src/SHC/Utils.hs view
@@ -0,0 +1,95 @@+module SHC.Utils+    where++import Data.List+import Data.Function (on)+import Control.Monad (guard)+import Control.Applicative ((<$>), (<*>))+import System.Process (readProcess)+import System.FilePath ((</>))++import SHC.Types+++readP :: String -> [String] -> IO String+readP name args = init <$> readProcess name args []  -- strip trailing \n++git :: [String] -> IO String+git = readP "git"++stack :: [String] -> IO String+stack = readP "stack"++-- | Get information about the Git repo in the current directory.+getGitInfo :: IO GitInfo+getGitInfo = GitInfo <$> headRef <*> branch <*> getRemotes+    where headRef = Commit <$> git ["rev-parse", "HEAD"]+                           <*> git ["log", "-1", "--pretty=%aN"]+                           <*> git ["log", "-1", "--pretty=%aE"]+                           <*> git ["log", "-1", "--pretty=%cN"]+                           <*> git ["log", "-1", "--pretty=%cE"]+                           <*> git ["log", "-1", "--pretty=%s"]+          branch = git ["rev-parse", "--abbrev-ref", "HEAD"]++getRemotes :: IO [Remote]+getRemotes = nubBy ((==) `on` name) <$> parseRemotes <$> git ["remote", "-v"]+    where parseRemotes :: String -> [Remote]+          parseRemotes input = do+            line <- lines input+            let fields = words line+            guard $ length fields >= 2+            return $ Remote (head fields) (fields !! 1)++getHpcDir :: String -> IO FilePath+getHpcDir package = (</> package) <$> stack ["path", "--local-hpc-root"]++getMixDir :: IO FilePath+getMixDir = (</> "hpc") <$> stack ["path", "--dist-dir"]++fst3 :: (a, b, c) -> a+fst3 (x, _, _) = x++snd3 :: (a, b, c) -> b+snd3 (_, x, _) = x++trd3 :: (a, b, c) -> c+trd3 (_, _, x) = x++fst4 :: (a, b, c, d) -> a+fst4 (x, _, _, _) = x++toFirstAndRest :: (a, b, c, d) -> (a, (b, c, d))+toFirstAndRest (a, b, c, d) = (a, (b, c, d))++listToMaybe :: [a] -> Maybe [a]+listToMaybe [] = Nothing+listToMaybe xs = Just xs++mcons :: Maybe a -> [a] -> [a]+mcons Nothing xs = xs+mcons (Just x) xs = x : xs++matchAny :: [String] -> String -> Bool+matchAny patterns fileName = any (`isPrefixOf` fileName) patterns++mapFirst :: (a -> a) -> [a] -> [a]+mapFirst f (x : xs) = f x : xs+mapFirst _ []       = []++mapLast :: (a -> a) -> [a] -> [a]+mapLast f [x]      = [f x]+mapLast f (x : xs) = x : mapLast f xs+mapLast _ []       = []++subSeq :: Int -> Int -> [a] -> [a]+subSeq start end = drop start . take end++subSubSeq :: Int -> Int -> [[a]] -> [[a]]+subSubSeq start end = mapFirst (drop start) . mapLast (take end)++groupByIndex :: Int -> [(Int, a)] -> [[a]]+groupByIndex size = take size . flip (++) (repeat []) . groupByIndex' 0 []+    where groupByIndex' _ ys [] = [ys]+          groupByIndex' i ys xx@((xi, x) : xs) = if xi == i+              then groupByIndex' i (x : ys) xs+              else ys : groupByIndex' (i + 1) [] xx
+ stack-hpc-coveralls.cabal view
@@ -0,0 +1,77 @@+name:                stack-hpc-coveralls+version:             0.0.0.1+synopsis:            Initial project template from stack+description:         Please see README.md+homepage:            http://github.com/rubik/stack-hpc-coveralls+license:             ISC+license-file:        LICENSE+author:              Michele Lacchia+maintainer:          michelelacchia@gmail.com+copyright:           Copyright (c) 2015 Michele Lacchia+category:            Control+build-type:          Simple+extra-source-files:+    stack.yaml+cabal-version:       >=1.18++library+  hs-source-dirs:      src+  exposed-modules:     SHC+  other-modules:       SHC.Coveralls+                       SHC.Types+                       SHC.Paths+                       SHC.Utils+                       SHC.Lix+                       SHC.Api+  build-depends:       base           >=4.7  && <5+                     , hpc            >=0.6+                     , filepath       >=1.4+                     , process        >=1.2+                     , pureMD5        >=2.1+                     , containers     >=0.5+                     , aeson          >=0.8+                     , bytestring     >=0.10+                     , utf8-string    >=1.0+                     , text           >=1.2+                     , directory      >=1.2+                     , directory-tree >=0.12+                     , wreq           >=0.4+                     , http-client    >=0.4+                     , lens           >=4.12+                     , lens-aeson     >=1.0+                     , safe           >=0.3+  default-language:    Haskell2010++executable stack-hpc-coveralls+  hs-source-dirs:      app+  main-is:             Main.hs+  ghc-options:         -Wall+  build-depends:       base       >=4.7 && <5+                     , aeson      >=0.8+                     , bytestring >=0.10+                     , docopt     >=0.7+                     , stack-hpc-coveralls -any+  default-language:    Haskell2010++test-suite stack-hpc-coveralls-test+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             Spec.hs+  build-depends:       base                >=4.7 && <5+                     , stack-hpc-coveralls -any+                     , hspec               >=2.2+  ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N+  default-language:    Haskell2010++test-suite style+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             HLint.hs+  build-depends:       base  >=4.7 && <5+                     , hlint ==1.*+  default-language:    Haskell2010+  ghc-options:         -Wall++source-repository head+  type:     git+  location: https://github.com/rubik/stack-hpc-coveralls
+ stack.yaml view
@@ -0,0 +1,33 @@+# For more information, see: https://github.com/commercialhaskell/stack/blob/release/doc/yaml_configuration.md++# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2)+resolver: lts-3.11++# Local packages, usually specified by relative directory name+packages:+- '.'++# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3)+extra-deps:+- docopt-0.7.0.4++# Override default flag values for local packages and extra-deps+flags: {}++# Extra package databases containing global packages+extra-package-dbs: []++# Control whether we use the GHC we find on the path+# system-ghc: true++# Require a specific version of stack, using version ranges+# require-stack-version: -any # Default+# require-stack-version: >= 0.1.4.0++# Override the architecture used by stack, especially useful on Windows+# arch: i386+# arch: x86_64++# Extra directories used by stack for building+# extra-include-dirs: [/path/to/dir]+# extra-lib-dirs: [/path/to/dir]
+ test/HLint.hs view
@@ -0,0 +1,16 @@+module Main (main) where++import Language.Haskell.HLint (hlint)+import System.Exit (exitFailure, exitSuccess)++arguments :: [String]+arguments =+    [ "app"+    , "src"+    , "test"+    ]++main :: IO ()+main = do+    hints <- hlint arguments+    if null hints then exitSuccess else exitFailure
+ test/Spec.hs view
@@ -0,0 +1,2 @@+main :: IO ()+main = putStrLn "Test suite not yet implemented"