diff --git a/haskell/repohs/.ghci b/haskell/repohs/.ghci new file mode 100644 index 00000000..05c16656 --- /dev/null +++ b/haskell/repohs/.ghci @@ -0,0 +1 @@ +:load app/Main.hs \ No newline at end of file diff --git a/haskell/repohs/.gitignore b/haskell/repohs/.gitignore new file mode 100644 index 00000000..3443f060 --- /dev/null +++ b/haskell/repohs/.gitignore @@ -0,0 +1,4 @@ +.stack-work/ +*~ +.vscode +repos \ No newline at end of file diff --git a/haskell/repohs/ChangeLog.md b/haskell/repohs/ChangeLog.md new file mode 100644 index 00000000..fe4723ce --- /dev/null +++ b/haskell/repohs/ChangeLog.md @@ -0,0 +1,3 @@ +# Changelog for repohs + +## Unreleased changes diff --git a/haskell/repohs/LICENSE b/haskell/repohs/LICENSE new file mode 100644 index 00000000..342c588b --- /dev/null +++ b/haskell/repohs/LICENSE @@ -0,0 +1,30 @@ +Copyright Author name here (c) 2022 + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Author name here nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/haskell/repohs/README.md b/haskell/repohs/README.md new file mode 100644 index 00000000..b9241290 --- /dev/null +++ b/haskell/repohs/README.md @@ -0,0 +1,45 @@ +# repohs + +Provides a convenient way to download several archives with source code + +## How it works +* It defines several constants: + * `_PATH` - where all files will be stored + * `_LANGUAGE` - extension of files written in language of interest. For now, defines a single extension. E.g., `.py` for `Python` + * `_ZIPPED` - name of subdirectory in `_PATH` where the zipped archives will be stored + * `_UNZIPPED` - name of subdirectory in `_PATH` where the unzipped folders will be stored + * `_URLs` - a list of URLs of interest + +* It will concurrently download the files and unpack them into corresponding folders + * Each zip-archive will be named after URL's path: file for `https://github.com/django/django/archive/refs/heads/main.zip` will become `django.django.archive.refs.heads.main.zip` + * Contents of such archive will be unpacked into subdirectory `django.django.archive.refs.heads.main` + +* If you have any questions, have a look at [Main.hs](./app/Main.hs) + +## SLOC +* See current results in the [report](./report) + +## Requirements +1. `npm` +```sh +sudo apt install nodejs +``` +1. A utility for counting code lines ([link](https://github.com/flosse/sloc)) +```sh +sudo npm install -g sloc +``` +1. `stack` ([link](https://docs.haskellstack.org/en/stable/install_and_upgrade/)) + +## Running +* From this project's root +```sh +stack run +``` +* As a script. Assume you put the code into `script.hs` somewhere +```sh +chmod +x script.hs +./script.hs +``` + +## Articles +* [Resource Management in Haskell](https://aherrmann.github.io/programming/2016/01/04/resource-management-in-haskell/index.html) \ No newline at end of file diff --git a/haskell/repohs/Setup.hs b/haskell/repohs/Setup.hs new file mode 100644 index 00000000..9a994af6 --- /dev/null +++ b/haskell/repohs/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/haskell/repohs/app/Main.hs b/haskell/repohs/app/Main.hs new file mode 100755 index 00000000..fe2efd17 --- /dev/null +++ b/haskell/repohs/app/Main.hs @@ -0,0 +1,368 @@ +#!/usr/bin/env stack +{- stack + script + --resolver lts-18.28 + --install-ghc + --package wreq + --package lens + --package bytestring + --package directory + --package network-uri + --package filepath + --package async + --package fmt + --package MissingH + --package zip + --package conduit-extra + --package containers + --package process + --package aeson + --package scientific + --package text + --package rio + --package mtl + --package monad-extras + --package http-types +-} + +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE UndecidableInstances #-} + +module Main ( + main, +)where + +import Codec.Archive.Zip (getEntries, getEntry, getEntryName, + mkEntrySelector, sourceEntry, + unpackInto, withArchive) +import Control.Applicative (empty) +import Control.Concurrent.Async (mapConcurrently) +import Data.Aeson (FromJSON (parseJSON), Object, + Value (Number, Object), decode, + (.:), (.:?)) +import Data.Aeson.Types (Array, FromJSON (parseJSON), Parser, + parseMaybe) +import qualified Data.ByteString.Lazy as BL +import qualified Data.Conduit.Binary as CB +import Data.Either (fromLeft, fromRight, isLeft, + isRight) +import Data.Foldable (forM_) +import Data.List.Utils (replace) +import qualified Data.Map as M +import Data.Maybe (fromJust, fromMaybe, isJust) +import Data.Scientific (scientific) +import qualified Data.String as BL +import Data.Text (pack) +import Fmt (Buildable (build), Builder, + blockListF, fmt, format, (+|), (|+), + (|++|)) +import Fmt.Internal.Core (FromBuilder) +import Network.URI (URI (URI, uriPath), parseURI) +import Network.Wreq +import System.Directory (createDirectoryIfMissing, + doesDirectoryExist, doesFileExist, + doesPathExist, getDirectoryContents) +import System.FilePath.Posix (joinPath, takeBaseName, + takeDirectory) +import System.Process +import Text.Printf (printf) + +import Control.Monad.Cont (Cont, ContT (ContT, runContT), + MonadCont (callCC)) +import Control.Monad.Except (ExceptT (ExceptT), runExceptT) +import Control.Monad.Extra (doCallCC) +import Data.List (intercalate, partition) +import Data.Text.IO as TIO (writeFile) +import Network.HTTP.Types.Status (status200) +import Prelude (print, putStrLn, writeFile) +import RIO hiding (mapConcurrently) +import RIO.List.Partial (tail) + +_URLs :: [String] +_URLs = [ + "https://github.com/django/django/archive/refs/heads/main.zip", + "https://github.com/matplotlib/matplotlib/archive/refs/heads/main.zip", + "https://github.com/keras-team/keras/archive/refs/heads/master.zip", + "https://github.com/taseikyo/PyQt5-Apps/archive/refs/heads/master.zip", + "https://github.com/pallets/flask/archive/refs/heads/main.zip", + "https://github.com/ansible/ansible/archive/refs/heads/devel.zip", + "https://github.com/zulip/zulip/archive/refs/heads/main.zip", + "https://github.com/Theano/Theano/archive/refs/heads/master.zip" + ] + +_PATH :: String +_PATH = "repos" + +-- _LANGUAGE :: HaskellExt +-- _LANGUAGE = HaskellExt Nothing + +_LANGUAGE :: PythonExt +_LANGUAGE = PythonExt Nothing + +_CODE_JSON :: String +_CODE_JSON = "code" + +_ZIPPED :: String +_ZIPPED = "zipped" + +_UNZIPPED :: String +_UNZIPPED = "unzipped" + + +main :: IO () +main = runSimpleApp $ doCallCC $ \cont -> do + path <- liftIO $ createDirectoryIfMissing True _PATH + logInfo (Utf8Builder $ "The files will be stored in \"" +| _PATH |+ "\"") + path' <- liftIO $ getUnpackRepos _URLs _PATH + let path = unzipped _PATH + p <- liftIO $ runSLOC _LANGUAGE path + case p of + Right r -> do + let msg = Utf8Builder $ "Files in " +| path |+ " contain " +| maybe "no" show (getData r) |+ " SLOC in " +\ showName r + logInfo msg + liftIO $ TIO.writeFile "report" (utf8BuilderToText msg) + Left r -> + logError (Utf8Builder $ path |+ " doesn't contain " +| showName _LANGUAGE |+ " code") + logInfo "Done!" + +{- +>>>zipped "path" +"path/zipped" +-} +zipped :: String -> String +zipped x = joinPath [x, _ZIPPED] + +{- | +>>>unzipped "path" +"path/unzipped" +-} +unzipped :: String -> String +unzipped x = joinPath [x, _UNZIPPED] + +class Convertible a where + getZipName :: a -> String + getName :: a -> String + getPath :: a -> String + getURI :: a -> String + +{- +>>>getName $ fromJust $ parseURI "https://github.com/django/django/archive/refs/heads/main.zip" +"django.django.archive.refs.heads.main" +-} + +instance Convertible URI where + getZipName URI {..} = uriPath & tail & replace "/" "." + getName = takeBaseName . getZipName + getPath URI {..} = uriPath + getURI = show + +data LocationError = + NoSuchDirectory FilePath + | NoSuchFile FilePath + | ProblematicURL URI + deriving Show + +instance Display LocationError where + display (NoSuchDirectory s) = Utf8Builder ("No such directory exists: " +\ s) + display (NoSuchFile s) = Utf8Builder ("No such file exists: " +\ s) + display (ProblematicURL s) = Utf8Builder ("Unavailable URL: " +\ show s) + + +-- checkExists :: FilePath -> LocationError -> IO Bool +-- checkExists p locErr = runSimpleApp $ do +-- exists <- liftIO $ doesPathExist p +-- unless exists (logInfo $ display locErr) +-- return exists + +{- | filter out URIs for already unzipped projects + +>>>getMissing (_PATH /+\ "/unzipped") (fromJust <$> (filter (isJust) (parseURI <$> _URLs))) +-} +getMissing :: [FilePath] -> [URI] -> [URI] +getMissing paths = filter (\x -> getName x `notElem` paths) + +(+\) :: (FromBuilder b, Buildable a) => Fmt.Builder -> a -> b +b +\ x = b +| x |+ "" + +(/+\) :: (Buildable a, Buildable b, FromBuilder c) => a -> b -> c +a /+\ b = "" +| a |++| b |+ "" + +{- | Given a `directory` and a `URL` + +gets a `file` from `URL` + +writes this `file` into `directory` + +returns this `file`'s path +-} + + +downloadRepo :: FilePath -> URI -> IO (Either LocationError FilePath) +downloadRepo dir uri = runSimpleApp $ doCallCC $ \cont -> do + let url = getURI uri + liftIO $ putStrLn ("Downloading " +\ url) + resp <- liftIO $ get url + when (resp ^. responseStatus /= status200) (cont $ Left $ ProblematicURL uri) + let + contents = resp ^. responseBody + zipPath = dir |+ "/" +\ getZipName uri + liftIO $ BL.writeFile zipPath contents + logThis ("Downloaded " +| url |+ " into " +\ zipPath) + return $ Right zipPath + +{- +Given path to a `fileName.zip` and a `directory`, + +unzip this file into `directory/fileName` + +and return this new path +-} +-- unlessValid :: (Monad (t1 IO), MonadTrans t1) => +-- FilePath +-- -> (t2 -> t1 IO ()) +-- -> (LocationError -> t2) +-- -> LocationError +-- -> t1 IO () +-- unlessValid path c ret err = do +-- ex <- lift $ checkExists path err +-- unless ex $ c (ret err) + +-- logThis :: (MonadIO m, HasLogFunc env, HasCallStack) => RIO.Builder -> m () +logThis :: (MonadIO m, MonadReader env m, HasLogFunc env) => RIO.Builder -> m () +logThis x = logInfo $ Utf8Builder x + + + +unzipRepo :: FilePath -> FilePath -> IO (Either LocationError FilePath) +unzipRepo zipPath unzipDir = runSimpleApp $ doCallCC $ \cont -> do + let + name = takeBaseName zipPath + newDir = unzipDir |+ "/" +\ name + liftIO $ createDirectoryIfMissing True newDir + logThis ("Unzipping " +| zipPath |+ " into " +\ newDir) + withArchive zipPath (unpackInto newDir) + logThis ("Unzipped " +| zipPath |+ " into " +\ newDir) + return $ Right newDir + + +putContent :: FilePath -> URI -> FilePath -> IO (Either LocationError FilePath) +putContent zipDir url unzipDir = do + r <- downloadRepo zipDir url + case r of + Left _ -> return r + Right path -> unzipRepo path unzipDir + + +{- | + +> downloadAndProcessRepos urls dir +downloads files accessible by `urls` into directory `dir` + +returns dir +>>>downloadAndProcessRepos _URLs _PATH +-} +getUnpackRepos :: [String] -- ^ list of possibly correct URLs + -> FilePath -- ^ directory to store files into + -> IO (Either LocationError FilePath) +getUnpackRepos urls path = runSimpleApp $ do + let zdir = zipped path + uzdir = unzipped path + liftIO $ createDirectoryIfMissing True zdir + liftIO $ createDirectoryIfMissing True uzdir + let + urlsGood = fromJust <$> filter isJust (parseURI <$> urls) + urlsBad = filter (isNothing . parseURI) urls + unless + (null urlsBad) + (logThis $ "The following URLs are unavailable:\n" +\ intercalate "\n" urlsBad) + -- TODO handle exceptions + ms <- (`getMissing` urlsGood) <$> liftIO (getDirectoryContents uzdir) + unless + (null ms) + (logInfo $ Utf8Builder $ "Missing repos:\n" +\ intercalate "\n" (show <$> urlsGood)) + zips <- liftIO $ mapConcurrently (\x -> putContent zdir x uzdir) ms + return $ Right uzdir + + +newtype LanguageError a = NoLanguage {b :: (LangExtension a) => a} + +instance (LangExtension a) => Show (LanguageError a) where + show (NoLanguage m) = "No " +| showName m |+ " files detected" + +{- +counts SLOC in a directory + +returns Left if no info about a language is available +>>>runSLOC (PythonExt Nothing) "app" +-} +runSLOC :: (LangExtension a, FromJSON a) => a -> FilePath -> IO (Either (LanguageError a) a) +runSLOC lang path = runSimpleApp $ doCallCC $ \cont -> do + logInfo (Utf8Builder $ "Counting lines for " +| showName lang |+ " in " +| path |+ "...") + p <- liftIO $ readProcess "sloc" ["-f", "json", path] "" + let p' = decode (BL.fromString p) + when (isNothing p') (cont $ Left $ NoLanguage lang) + return (Right $ fromJust p') + + +newtype PythonExt = PythonExt (Maybe Int) +newtype JavaExt = JavaExt (Maybe Int) +newtype HaskellExt = HaskellExt (Maybe Int) + +class LangExtension a where + -- TODO allow several file extensions + showExt :: a -> String + showName :: a -> String + showFull :: a -> String + getData :: a -> Maybe Int + getNoLangError :: a -> LanguageError a + +showData' :: (LangExtension a) => a -> String +showData' a = showName a |+ ": " +| maybe "?" show (getData a) |+ " SLOC" + +instance LangExtension PythonExt where + showExt _ = "py" + showName _ = "Python" + getData (PythonExt a) = a + showFull = showData' + getNoLangError = NoLanguage + +instance LangExtension JavaExt where + showExt _ = "java" + showName _ = "Java" + getData (JavaExt a) = a + showFull = showData' + getNoLangError = NoLanguage + +instance LangExtension HaskellExt where + showExt _ = "hs" + showName _ = "Haskell" + getData (HaskellExt a) = a + showFull = showData' + getNoLangError = NoLanguage + +instance FromJSON PythonExt where + parseJSON v = parseJSONExt v PythonExt + +instance FromJSON JavaExt where + parseJSON v = parseJSONExt v JavaExt + +instance FromJSON HaskellExt where + parseJSON v = parseJSONExt v HaskellExt + +parseJSONExt :: (LangExtension a) => Value -> (Maybe Int -> a) -> Parser a +parseJSONExt (Object v) a = do + ext <- v .: "byExt" + hs <- ext .:? pack (showExt (a Nothing)) + cnt <- + case hs of + Just k -> do + k1 <- k .: "summary" + k1 .: "source" + Nothing -> return Nothing + return (a cnt) +parseJSONExt _ _ = undefined diff --git a/haskell/repohs/package.yaml b/haskell/repohs/package.yaml new file mode 100644 index 00000000..377d6a9d --- /dev/null +++ b/haskell/repohs/package.yaml @@ -0,0 +1,69 @@ +name: repohs +version: 0.1.0.0 +github: "githubuser/repohs" +license: BSD3 +author: "Author name here" +maintainer: "example@example.com" +copyright: "2022 Author name here" + +extra-source-files: +- README.md +- ChangeLog.md + +# Metadata used when publishing your package +# synopsis: Short description of your package +# category: Web + +# To avoid duplicated efforts in documentation and dealing with the +# complications of embedding Haddock markup inside cabal files, it is +# common to point users to the README.md file. +description: Please see the README on GitHub at + +dependencies: +- base >= 4.7 && < 5 +- wreq +- lens +- bytestring +- directory +- network-uri +- filepath +- async +- fmt +- MissingH +- zip +- conduit-extra +- containers +- process +- aeson +- scientific +- text +- rio +- data-default +- mtl +- monad-extras +- http-types + +library: + source-dirs: src + +executables: + repohs-exe: + main: Main.hs + source-dirs: app + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N + dependencies: + - repohs + +tests: + repohs-test: + main: Spec.hs + source-dirs: test + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N + dependencies: + - repohs diff --git a/haskell/repohs/repohs.cabal b/haskell/repohs/repohs.cabal new file mode 100644 index 00000000..365ff6c5 --- /dev/null +++ b/haskell/repohs/repohs.cabal @@ -0,0 +1,123 @@ +cabal-version: 1.12 + +-- This file has been generated from package.yaml by hpack version 0.34.4. +-- +-- see: https://github.com/sol/hpack + +name: repohs +version: 0.1.0.0 +description: Please see the README on GitHub at +homepage: https://github.com/githubuser/repohs#readme +bug-reports: https://github.com/githubuser/repohs/issues +author: Author name here +maintainer: example@example.com +copyright: 2022 Author name here +license: BSD3 +license-file: LICENSE +build-type: Simple +extra-source-files: + README.md + ChangeLog.md + +source-repository head + type: git + location: https://github.com/githubuser/repohs + +library + exposed-modules: + Lib + other-modules: + Paths_repohs + hs-source-dirs: + src + build-depends: + MissingH + , aeson + , async + , base >=4.7 && <5 + , bytestring + , conduit-extra + , containers + , data-default + , directory + , filepath + , fmt + , http-types + , lens + , monad-extras + , mtl + , network-uri + , process + , rio + , scientific + , text + , wreq + , zip + default-language: Haskell2010 + +executable repohs-exe + main-is: Main.hs + other-modules: + Paths_repohs + hs-source-dirs: + app + ghc-options: -threaded -rtsopts -with-rtsopts=-N + build-depends: + MissingH + , aeson + , async + , base >=4.7 && <5 + , bytestring + , conduit-extra + , containers + , data-default + , directory + , filepath + , fmt + , http-types + , lens + , monad-extras + , mtl + , network-uri + , process + , repohs + , rio + , scientific + , text + , wreq + , zip + default-language: Haskell2010 + +test-suite repohs-test + type: exitcode-stdio-1.0 + main-is: Spec.hs + other-modules: + Paths_repohs + hs-source-dirs: + test + ghc-options: -threaded -rtsopts -with-rtsopts=-N + build-depends: + MissingH + , aeson + , async + , base >=4.7 && <5 + , bytestring + , conduit-extra + , containers + , data-default + , directory + , filepath + , fmt + , http-types + , lens + , monad-extras + , mtl + , network-uri + , process + , repohs + , rio + , scientific + , text + , wreq + , zip + default-language: Haskell2010 diff --git a/haskell/repohs/report b/haskell/repohs/report new file mode 100644 index 00000000..96f227fb --- /dev/null +++ b/haskell/repohs/report @@ -0,0 +1 @@ +Files in repos/unzipped contain 1080815 SLOC in Python \ No newline at end of file diff --git a/haskell/repohs/src/Lib.hs b/haskell/repohs/src/Lib.hs new file mode 100644 index 00000000..d36ff271 --- /dev/null +++ b/haskell/repohs/src/Lib.hs @@ -0,0 +1,6 @@ +module Lib + ( someFunc + ) where + +someFunc :: IO () +someFunc = putStrLn "someFunc" diff --git a/haskell/repohs/stack.yaml b/haskell/repohs/stack.yaml new file mode 100644 index 00000000..f69cd79d --- /dev/null +++ b/haskell/repohs/stack.yaml @@ -0,0 +1,76 @@ +# This file was automatically generated by 'stack init' +# +# Some commonly used options have been documented as comments in this file. +# For advanced use and comprehensive documentation of the format, please see: +# https://docs.haskellstack.org/en/stable/yaml_configuration/ + +# Resolver to choose a 'specific' stackage snapshot or a compiler version. +# A snapshot resolver dictates the compiler version and the set of packages +# to be used for project dependencies. For example: +# +# resolver: lts-3.5 +# resolver: nightly-2015-09-21 +# resolver: ghc-7.10.2 +# +# The location of a snapshot can be provided as a file or url. Stack assumes +# a snapshot provided as a file might change, whereas a url resource does not. +# +# resolver: ./custom-snapshot.yaml +# resolver: https://example.com/snapshots/2018-01-01.yaml +resolver: + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/28.yaml + +# User packages to be built. +# Various formats can be used as shown in the example below. +# +# packages: +# - some-directory +# - https://example.com/foo/bar/baz-0.0.2.tar.gz +# subdirs: +# - auto-update +# - wai +packages: +- . +# Dependency packages to be pulled from upstream that are not in the resolver. +# These entries can reference officially published versions as well as +# forks / in-progress versions pinned to a git hash. For example: +# +extra-deps: +- wreq-0.5.3.3 +- lens-5.1 +- bytestring-0.10.12.0 +- directory-1.3.6.0 +- uri-0.1.6.4 +- filepath-1.4.2.1 +- async-2.2.4 +- MissingH-1.5.0.0 +- conduit-1.3.4.2 + +ghc-options: + '$everything': -haddock + +# extra-deps: [] + +# 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: ">=2.7" +# +# 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] +# +# Allow a newer minor version of GHC than the snapshot specifies +# compiler-check: newer-minor diff --git a/haskell/repohs/stack.yaml.lock b/haskell/repohs/stack.yaml.lock new file mode 100644 index 00000000..1b8c50a6 --- /dev/null +++ b/haskell/repohs/stack.yaml.lock @@ -0,0 +1,76 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/lock_files + +packages: +- completed: + hackage: wreq-0.5.3.3@sha256:841539c9d3e7b39c17aee32414d9232a11ab73ed51163fb5e7f68213ea15fe62,5317 + pantry-tree: + size: 2423 + sha256: 76e00e840e629536a7ef80e0486d40309cfa1ebdfb4a2427c54082f304568854 + original: + hackage: wreq-0.5.3.3 +- completed: + hackage: lens-5.1@sha256:2cd46b613b9fb37e6eb64c518fd41cfdf55b15bd217d8780163b5c75974920bf,15013 + pantry-tree: + size: 8214 + sha256: a01b10a8b32e7aeb1e4fbb922761a3f9a8f28bb9d2270277b849984409aca067 + original: + hackage: lens-5.1 +- completed: + hackage: bytestring-0.10.12.0@sha256:3756939e46a9adb9cd5654442e7a44f53d0ad94fca8a9ad7029cc28fa6b0a081,6070 + pantry-tree: + size: 2140 + sha256: 4e979f335e85ccb677f171f1e8e4d1139c8bc4e3ace7fb1fdf9bdffe9b0d9287 + original: + hackage: bytestring-0.10.12.0 +- completed: + hackage: directory-1.3.6.0@sha256:d3d89c752f99e78f3e1d11c74696d2ba78b199f47c89e92934003deb84cb3ff9,2810 + pantry-tree: + size: 3433 + sha256: 54ddcb94f07eab9c6070497d8cbc565fc7e9f159ac596581c2e959e7460c1e41 + original: + hackage: directory-1.3.6.0 +- completed: + hackage: uri-0.1.6.4@sha256:8277b1191666ded2b46fb7b02f95a898093e452a57c16e0d37aa8c9b852b483a,567 + pantry-tree: + size: 198 + sha256: 000a9ffeb2f8306b1abd04da61fc5b7909f34d0c4dd45bbe1b253d28cc9ffb86 + original: + hackage: uri-0.1.6.4 +- completed: + hackage: filepath-1.4.2.1@sha256:aff13ab4dd895931e77c22756e6e4e7bc8189351e0fa48270a998b826a63f54a,2291 + pantry-tree: + size: 681 + sha256: ed5ffa1e42c2626fc3b13b9f7ff3fef6b841515a57021819f8066db4e93264dc + original: + hackage: filepath-1.4.2.1 +- completed: + hackage: async-2.2.4@sha256:b83dec34a53520de84c6dd3dc7aae45d22409b46eb471c478b98108215a370f0,3095 + pantry-tree: + size: 501 + sha256: 08b8a9f8d2e27d51b04fb9c14f65e1209c7f24160d2a4557963900edb2c0f07d + original: + hackage: async-2.2.4 +- completed: + hackage: MissingH-1.5.0.0@sha256:34df0817c543894b53942852109da1ba7f3f615ca0aded9b9c58bf52d33017e8,4740 + pantry-tree: + size: 5272 + sha256: e651ec6ac04238ae27e645f5c8316459999bed57c388c459a44c21c58525fba7 + original: + hackage: MissingH-1.5.0.0 +- completed: + hackage: conduit-1.3.4.2@sha256:92165c9fc22f4e8c75b936b8ed4739360e8318bec2a05cd2c8c5b293d126b477,5129 + pantry-tree: + size: 1957 + sha256: 8f5f18d84acb44440b7de5df4a02c2954a5c25c38b86268cdccb055ed4ee5e8b + original: + hackage: conduit-1.3.4.2 +snapshots: +- completed: + size: 590100 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/28.yaml + sha256: 428ec8d5ce932190d3cbe266b9eb3c175cd81e984babf876b64019e2cbe4ea68 + original: + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/28.yaml diff --git a/haskell/repohs/test/Spec.hs b/haskell/repohs/test/Spec.hs new file mode 100644 index 00000000..cd4753fc --- /dev/null +++ b/haskell/repohs/test/Spec.hs @@ -0,0 +1,2 @@ +main :: IO () +main = putStrLn "Test suite not yet implemented"