├── .gitignore ├── .travis.yml ├── LICENSE ├── MORE.md ├── README.md ├── Setup.hs ├── app └── Main.hs ├── examples └── ReadmePrinter.hs ├── hackmanager.cabal ├── src └── Hack │ └── Manager │ ├── Collector.hs │ ├── Gitignore.hs │ ├── Readme.hs │ ├── Travis.hs │ └── Types.hs ├── stack.yaml ├── templates ├── README.mustache ├── gitignore.mustache ├── travis-cabal.mustache └── travis-stack.mustache └── update-readme.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.hi 3 | *.chi 4 | *.chs.h 5 | *.dyn_o 6 | *.dyn_hi 7 | .hpc 8 | .hsenv 9 | *.prof 10 | *.aux 11 | *.hp 12 | .stack-work/ 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: haskell 2 | env: 3 | - GHCVER=7.10.2 4 | - GHCVER=head 5 | matrix: 6 | allow_failures: 7 | - env: GHCVER=head 8 | before_install: 9 | - | 10 | if [ $GHCVER = `ghc --numeric-version` ]; then 11 | travis/cabal-apt-install --enable-tests $MODE 12 | export CABAL=cabal 13 | else 14 | travis_retry sudo add-apt-repository -y ppa:hvr/ghc 15 | travis_retry sudo apt-get update 16 | travis_retry sudo apt-get install cabal-install-1.22 ghc-$GHCVER happy 17 | export CABAL=cabal-1.22 18 | export PATH=/opt/ghc/$GHCVER/bin:$PATH 19 | fi 20 | - $CABAL update 21 | - | 22 | $CABAL install happy alex 23 | export PATH=$HOME/.cabal/bin:$PATH 24 | install: 25 | - $CABAL install --dependencies-only --enable-tests 26 | - $CABAL configure -flib-Werror --enable-tests $MODE 27 | script: 28 | - ghc --numeric-version 29 | - $CABAL check 30 | - $CABAL build 31 | - $CABAL test --show-details=always 32 | deploy: 33 | provider: hackage 34 | username: AlexanderThiemann 35 | skip_cleanup: true 36 | on: 37 | condition: $GHCVER = 7.10.2 38 | tags: true 39 | password: 40 | secure: QpP4N7a5PUAu8dKhtqItjnr5v2THAu96cIUXn300IkBYhWBOn+LAfCnzzda0oEKg7YPAhfg70BFAQA06MBj18Bbb7rPaSNHi2WxRdxmYMBq31Mg9YGJ4MgpgmgCxOTE9nNai1GPJldxUPVV/+6xnszBoUO1Brcd2avvpf2arVXLPUmEK5kfL0UdwAAUs2qxI2jV4bfWjAnck7vdRd94g4CX0cARkNZGJUl9DlvyiHIvufgsu6Av7ve0bMZ13ZbbiMLTTGhlK4yALqx6/dqayFkQ1l/fvrDA4G9If83gbWkPC5XiLMxPQR5W/ra1Mx31MzReqqELOso4ZOVH+hokb4FS9+ZHbw0F63T54jiM4plSqOWiv/ZmMwsHNhTUbd6uZ3AmfXoFtKrBAIjE/w1IFIvssbNKL6ctnPcK8N5oyIl23llLBpgd9/LSxXF17ChZM+3a1PQ0UM3TSlybgeffRGt7GJA3ZByUy0MBI71TGDFAZC6Sz4fUqg+qN7emKRLnNBwImC5psYZHDdaNJfHNI+Fk98c4AShe1H3lcReRm8xGw6+A4vF0qQSWt5Thn8/t26MyUvbBFW+Al75AqxbsjAUxwrfv3qCZEYhpSHWe6swvvKpcWSI34oFwpFrv5iaT9DtSmnaY8MVn3nM+7ZMx+t5xfiWMo/uWFcz3rqNr+HSw= 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Alexander Thiemann 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Alexander Thiemann nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /MORE.md: -------------------------------------------------------------------------------- 1 | ## Features 2 | 3 | * Automagically collect package information such as 4 | * package name 5 | * GHC compatibility 6 | * stack Project 7 | * Hackage / Stackage status 8 | * License 9 | * Examples 10 | * Cli Usage 11 | * Typecheck examples 12 | * Generate informative README.md (Can be extended using a MORE.md) 13 | * Generate .travis.yml (cabal or stack based) 14 | * Generate .gitignore 15 | 16 | The generated `.travis.yml` and `.gitignore` are intended as starting templates, while the generated `README.md` should not be modified by hand. Rerun `hackmanager readme` before every commit (commit hook?) to keep it up to date. If you would like to add custom sections, create a `MORE.md`. 17 | 18 | ## Roadmap 19 | 20 | There's no real roadmap - I will add features as needed. I am open to any contributions! -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | hackmanager 2 | ===== 3 | 4 | [![Build Status](https://travis-ci.org/agrafix/hackmanager.svg)](https://travis-ci.org/agrafix/hackmanager) 5 | [![Hackage](https://img.shields.io/hackage/v/hackmanager.svg)](http://hackage.haskell.org/package/hackmanager) 6 | 7 | ## Intro 8 | 9 | Hackage: [hackmanager](http://hackage.haskell.org/package/hackmanager) 10 | Stackage: [hackmanager](https://www.stackage.org/package/hackmanager) 11 | 12 | Generate useful files for Haskell projects 13 | 14 | ## Cli Usage: hackmanager 15 | 16 | ```sh 17 | $ hackmanager --help 18 | hackmanager - Generate useful files for Haskell projects 19 | 20 | Usage: hackmanager COMMAND 21 | Simplify managing Haskell projects by generating files like README.md, 22 | .travis.yml, etc. 23 | 24 | Available options: 25 | -h,--help Show this help text 26 | 27 | Available commands: 28 | readme 29 | travis 30 | gitignore 31 | 32 | (c) 2015 Alexander Thiemann - BSD3 License 33 | 34 | ``` 35 | 36 | ## Library Usage Example 37 | 38 | ```haskell 39 | module Main where 40 | 41 | import Hack.Manager.Collector 42 | import Hack.Manager.Readme 43 | 44 | import qualified Data.Text as T 45 | 46 | main :: IO () 47 | main = 48 | do pi <- getProjectInfo 49 | case pi of 50 | Left err -> putStrLn err 51 | Right info -> 52 | do rm <- renderReadme info 53 | putStrLn (T.unpack rm) 54 | 55 | ``` 56 | 57 | ## Install 58 | 59 | * Using cabal: `cabal install hackmanager` 60 | * Using Stack: `stack install hackmanager` 61 | * From Source (cabal): `git clone https://github.com/agrafix/hackmanager.git && cd hackmanager && cabal install` 62 | * From Source (stack): `git clone https://github.com/agrafix/hackmanager.git && cd hackmanager && stack build` 63 | 64 | ## Features 65 | 66 | * Automagically collect package information such as 67 | * package name 68 | * GHC compatibility 69 | * stack Project 70 | * Hackage / Stackage status 71 | * License 72 | * Examples 73 | * Cli Usage 74 | * Typecheck examples 75 | * Generate informative README.md (Can be extended using a MORE.md) 76 | * Generate .travis.yml (cabal or stack based) 77 | * Generate .gitignore 78 | 79 | The generated `.travis.yml` and `.gitignore` are intended as starting templates, while the generated `README.md` should not be modified by hand. Rerun `hackmanager readme` before every commit (commit hook?) to keep it up to date. If you would like to add custom sections, create a `MORE.md`. 80 | 81 | ## Roadmap 82 | 83 | There's no real roadmap - I will add features as needed. I am open to any contributions! 84 | 85 | ## Misc 86 | 87 | ### Supported GHC Versions 88 | 89 | * 7.10.2 90 | 91 | ### License 92 | 93 | Released under the BSD3 license. 94 | (c) 2015 Alexander Thiemann 95 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /app/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Main where 3 | 4 | import Hack.Manager.Collector 5 | import Hack.Manager.Gitignore 6 | import Hack.Manager.Readme 7 | import Hack.Manager.Travis 8 | import Hack.Manager.Types 9 | 10 | import Control.Monad 11 | import Options.Applicative 12 | import qualified Data.Text as T 13 | import qualified Data.Text.IO as T 14 | 15 | main :: IO () 16 | main = join $ execParser optParser 17 | 18 | withProjectInfo :: (ProjectInfo -> IO ()) -> IO () 19 | withProjectInfo cont = 20 | do raw <- getProjectInfo 21 | case raw of 22 | Left err -> 23 | putStrLn err 24 | Right pinfo -> 25 | cont pinfo 26 | 27 | makeReadme :: IO () 28 | makeReadme = 29 | withProjectInfo $ \pinfo -> 30 | do rm <- renderReadme pinfo 31 | T.writeFile "README.md" rm 32 | 33 | makeTravis :: TravisOpts -> IO () 34 | makeTravis to = 35 | withProjectInfo $ \pinfo -> 36 | do travis <- renderTravis to pinfo 37 | T.writeFile ".travis.yml" travis 38 | 39 | travisOptsParser :: Parser TravisOpts 40 | travisOptsParser = 41 | TravisOpts 42 | <$> (T.pack <$> argument str (metavar "GHC-RELEASE_VERSION")) 43 | <*> switch (long "use-stack" <> help "Use stack to run travis build") 44 | 45 | makeGitignore :: IO () 46 | makeGitignore = 47 | withProjectInfo $ \pinfo -> 48 | do gitignore <- renderGitignore pinfo 49 | T.writeFile ".gitignore" gitignore 50 | 51 | commands :: Mod CommandFields (IO ()) 52 | commands = 53 | command "readme" (info (pure makeReadme) idm) 54 | <> command "travis" (info (makeTravis <$> travisOptsParser) idm) 55 | <> command "gitignore" (info (pure makeGitignore) idm) 56 | 57 | optParser :: ParserInfo (IO ()) 58 | optParser = 59 | info (helper <*> subparser commands) 60 | ( fullDesc 61 | <> progDesc "Simplify managing Haskell projects by generating files like README.md, .travis.yml, etc." 62 | <> header "hackmanager - Generate useful files for Haskell projects" 63 | <> footer "(c) 2015 Alexander Thiemann - BSD3 License" 64 | ) 65 | -------------------------------------------------------------------------------- /examples/ReadmePrinter.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Hack.Manager.Collector 4 | import Hack.Manager.Readme 5 | 6 | import qualified Data.Text as T 7 | 8 | main :: IO () 9 | main = 10 | do pi <- getProjectInfo 11 | case pi of 12 | Left err -> putStrLn err 13 | Right info -> 14 | do rm <- renderReadme info 15 | putStrLn (T.unpack rm) 16 | -------------------------------------------------------------------------------- /hackmanager.cabal: -------------------------------------------------------------------------------- 1 | name: hackmanager 2 | version: 0.1.0.0 3 | synopsis: Generate useful files for Haskell projects 4 | description: Simplify managing Haskell projects by generating files like README.md, .travis.yml, etc. 5 | homepage: http://github.com/agrafix/hackmanager 6 | license: BSD3 7 | license-file: LICENSE 8 | author: Alexander Thiemann 9 | maintainer: Alexander Thiemann 10 | copyright: (c) 2015 Alexander Thiemann 11 | category: Development 12 | build-type: Simple 13 | cabal-version: >=1.10 14 | tested-with: GHC==7.10.* 15 | extra-source-files: 16 | README.md 17 | templates/README.mustache 18 | templates/travis-stack.mustache 19 | templates/travis-cabal.mustache 20 | templates/gitignore.mustache 21 | examples/ReadmePrinter.hs 22 | 23 | library 24 | hs-source-dirs: src 25 | exposed-modules: 26 | Hack.Manager.Readme 27 | Hack.Manager.Types 28 | Hack.Manager.Collector 29 | Hack.Manager.Travis 30 | Hack.Manager.Gitignore 31 | build-depends: 32 | base >= 4.7 && < 5, 33 | hastache >=0.6, 34 | file-embed >=0.0.8, 35 | bytestring >=0.10, 36 | Glob >=0.7, 37 | Cabal >=1.20, 38 | text >=1.2, 39 | mtl >=2.2, 40 | process >=1.2, 41 | http-client >=0.4, 42 | http-client-tls >=0.2, 43 | http-types >=0.8.6, 44 | directory >=1.2 45 | default-language: Haskell2010 46 | 47 | executable hackmanager 48 | hs-source-dirs: app 49 | main-is: Main.hs 50 | ghc-options: -threaded -rtsopts -with-rtsopts=-N 51 | build-depends: 52 | base, 53 | hackmanager, 54 | text >=1.2, 55 | optparse-applicative >=0.11 56 | default-language: Haskell2010 57 | 58 | source-repository head 59 | type: git 60 | location: https://github.com/agrafix/hackmanager 61 | -------------------------------------------------------------------------------- /src/Hack/Manager/Collector.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ScopedTypeVariables #-} 2 | {-# LANGUAGE FlexibleContexts #-} 3 | module Hack.Manager.Collector where 4 | 5 | import Hack.Manager.Types 6 | 7 | import Control.Exception 8 | import Control.Monad.Except 9 | import System.Directory 10 | import System.Exit 11 | import System.Process 12 | import qualified Data.List as L 13 | import qualified Data.Text as T 14 | import qualified Distribution.Compiler as Comp 15 | import qualified Distribution.Package as Pkg 16 | import qualified Distribution.PackageDescription as PD 17 | import qualified Distribution.PackageDescription.Parse as PD 18 | import qualified Distribution.Text as DT 19 | import qualified Distribution.Version as Vers 20 | import qualified Network.HTTP.Client as C 21 | import qualified Network.HTTP.Client.TLS as C 22 | import qualified Network.HTTP.Types.Status as Http 23 | import qualified System.FilePath.Glob as G 24 | 25 | getProjectInfo :: IO (Either String ProjectInfo) 26 | getProjectInfo = 27 | runExceptT $ 28 | do cabalFiles <- liftIO $ G.glob "*.cabal" 29 | case cabalFiles of 30 | [] -> throwError "No cabal file in working directory!" 31 | (f1:_) -> 32 | do cabalData <- liftIO $ readFile f1 33 | case PD.parsePackageDescription cabalData of 34 | PD.ParseFailed err -> throwError (show err) 35 | PD.ParseOk _ val -> compileProjectInfo val 36 | 37 | onStackageCheck :: T.Text -> IO Bool 38 | onStackageCheck projectName = 39 | do mgr <- C.newManager C.tlsManagerSettings 40 | initReq <- C.parseUrl ("https://www.stackage.org/package/" ++ T.unpack projectName) 41 | let tryGet = 42 | do resp <- C.httpNoBody initReq mgr 43 | return $ C.responseStatus resp == Http.ok200 44 | tryGet `catch` \(_ :: SomeException) -> return False 45 | 46 | onHackageCheck :: T.Text -> IO Bool 47 | onHackageCheck projectName = 48 | do mgr <- C.newManager C.tlsManagerSettings 49 | initReq <- C.parseUrl ("https://hackage.haskell.org/package/" ++ T.unpack projectName) 50 | let tryGet = 51 | do resp <- C.httpNoBody initReq mgr 52 | return $ C.responseStatus resp == Http.ok200 53 | tryGet `catch` \(_ :: SomeException) -> return False 54 | 55 | findGhcVersions :: [(Comp.CompilerFlavor, Vers.VersionRange)] -> ExceptT String IO [T.Text] 56 | findGhcVersions origVersions = 57 | forM versions $ \vers -> 58 | let loop [] = throwError ("Unknown ghc version: " ++ show vers) 59 | loop (x:xs) = 60 | if Vers.withinRange x vers then return x else loop xs 61 | in liftM (T.pack . DT.display) $ loop ghcLatest 62 | where 63 | versions = map snd $ filter (\(flavor, _) -> flavor == Comp.GHC) origVersions 64 | ghcLatest = 65 | [ Vers.Version [7, 4, 2] [] 66 | , Vers.Version [7, 6, 3] [] 67 | , Vers.Version [7, 8, 4] [] 68 | , Vers.Version [7, 10, 2] [] 69 | ] 70 | 71 | getCliUsage :: Bool -> [String] -> ExceptT String IO [CliExecutable] 72 | getCliUsage hasStack exec = 73 | forM exec $ \program -> 74 | do (ec, stdOut, stdErr) <- 75 | liftIO $ 76 | if hasStack 77 | then readProcessWithExitCode "/bin/bash" ["-c", "stack exec -- " ++ program ++ " --help"] "" 78 | else readProcessWithExitCode "/bin/bash" ["-c", "cabal run -- " ++ program ++ " --help"] "" 79 | when (ec /= ExitSuccess) $ 80 | throwError $ "Failed to run " ++ program ++ " --help to retrieve cli usage. StdOut was: " ++ stdOut ++ " \n StdErr was: " ++ stdErr 81 | return 82 | CliExecutable 83 | { ce_name = T.pack program 84 | , ce_help = T.pack stdOut 85 | } 86 | 87 | moreFile :: ExceptT String IO (Maybe T.Text) 88 | moreFile = 89 | liftIO $ 90 | do more <- doesFileExist "MORE.md" 91 | if more 92 | then do ct <- readFile "MORE.md" 93 | return (Just $ T.pack ct) 94 | else return Nothing 95 | 96 | compileProjectInfo :: PD.GenericPackageDescription -> ExceptT String IO ProjectInfo 97 | compileProjectInfo gpd = 98 | do let pkgName = T.pack $ Pkg.unPackageName $ Pkg.pkgName $ PD.package pd 99 | ghInfo <- 100 | case filter repoFilter $ PD.sourceRepos pd of 101 | [] -> throwError "No head github source-repository given in cabal file!" 102 | (repo:_) -> 103 | case PD.repoLocation repo of 104 | Nothing -> 105 | throwError "Missing source-repository location" 106 | Just loc -> extractGithub loc 107 | hasStack <- liftM (not . L.null) $ liftIO $ G.glob "stack*.yaml" 108 | (example, hasMoreEx) <- 109 | do files <- liftIO $ G.glob "examples/*.hs" 110 | forM_ files $ \file -> 111 | do res <- 112 | liftIO $ 113 | if hasStack 114 | then system $ "stack exec -- ghc -fno-code " ++ file 115 | else system $ "cabal exec -- ghc -fno-code " ++ file 116 | when (res /= ExitSuccess) $ throwError $ "Failed to compile " ++ file 117 | case files of 118 | [] -> return (Nothing, False) 119 | (file:xs) -> 120 | do ex <- liftIO $ readFile file 121 | return (Just $ T.pack ex, not $ L.null xs) 122 | onStackage <- liftIO $ onStackageCheck pkgName 123 | onHackage <- liftIO $ onHackageCheck pkgName 124 | ghcVers <- findGhcVersions (PD.testedWith pd) 125 | cliUsage <- getCliUsage hasStack (map fst $ PD.condExecutables gpd) 126 | moreFile <- moreFile 127 | return 128 | ProjectInfo 129 | { pi_name = pkgName 130 | , pi_pkgName = pkgName 131 | , pi_pkgDesc = T.pack $ PD.synopsis pd 132 | , pi_stackFile = hasStack 133 | , pi_onStackage = onStackage 134 | , pi_onHackage = onHackage 135 | , pi_example = example 136 | , pi_moreExamples = hasMoreEx 137 | , pi_github = ghInfo 138 | , pi_license = 139 | LicenseInfo 140 | { li_copyright = T.pack $ PD.copyright pd 141 | , li_type = T.pack $ DT.display $ PD.license pd 142 | } 143 | , pi_ghcVersions = ghcVers 144 | , pi_cliUsage = cliUsage 145 | , pi_moreInfo = moreFile 146 | } 147 | where 148 | pd = PD.packageDescription gpd 149 | repoFilter rep = 150 | PD.repoKind rep == PD.RepoHead 151 | && PD.repoType rep == Just PD.Git 152 | && checkGithub (PD.repoLocation rep) 153 | checkGithub r = 154 | case r of 155 | Nothing -> False 156 | Just str -> "github.com" `L.isInfixOf` str 157 | extractGithub loc = 158 | case L.stripPrefix "https://github.com/" loc of 159 | Nothing -> 160 | throwError "source-repository location must start with https://github.com/" 161 | Just rest -> 162 | let (usr, slashedRepo) = L.break (=='/') rest 163 | in return 164 | GithubInfo 165 | { gi_user = T.pack usr 166 | , gi_project = T.pack $ L.drop 1 slashedRepo 167 | } 168 | -------------------------------------------------------------------------------- /src/Hack/Manager/Gitignore.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | module Hack.Manager.Gitignore 4 | ( renderGitignore ) 5 | where 6 | 7 | import Hack.Manager.Types 8 | 9 | import Control.Monad 10 | import Data.FileEmbed 11 | import qualified Data.ByteString as BS 12 | import qualified Data.Text as T 13 | import qualified Data.Text.Encoding as T 14 | import qualified Data.Text.Lazy as TL 15 | import qualified Text.Hastache as H 16 | import qualified Text.Hastache.Context as H 17 | 18 | giTemplate :: BS.ByteString 19 | giTemplate = $(embedFile "templates/gitignore.mustache") 20 | 21 | renderGitignore :: ProjectInfo -> IO T.Text 22 | renderGitignore pinfo = 23 | liftM TL.toStrict $ H.hastacheStr cfg (T.decodeUtf8 giTemplate) ctx 24 | where 25 | ctx = H.mkGenericContext pinfo 26 | cfg = 27 | H.defaultConfig 28 | { H.muTemplateFileDir = Nothing 29 | , H.muEscapeFunc = H.emptyEscape 30 | } 31 | -------------------------------------------------------------------------------- /src/Hack/Manager/Readme.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | module Hack.Manager.Readme 4 | ( renderReadme ) 5 | where 6 | 7 | import Hack.Manager.Types 8 | 9 | import Control.Monad 10 | import Data.FileEmbed 11 | import qualified Data.ByteString as BS 12 | import qualified Data.Text as T 13 | import qualified Data.Text.Encoding as T 14 | import qualified Data.Text.Lazy as TL 15 | import qualified Text.Hastache as H 16 | import qualified Text.Hastache.Context as H 17 | 18 | readmeTemplate :: BS.ByteString 19 | readmeTemplate = $(embedFile "templates/README.mustache") 20 | 21 | renderReadme :: ProjectInfo -> IO T.Text 22 | renderReadme pinfo = 23 | liftM TL.toStrict $ H.hastacheStr cfg (T.decodeUtf8 readmeTemplate) ctx 24 | where 25 | ctx = H.mkGenericContext pinfo 26 | cfg = 27 | H.defaultConfig 28 | { H.muTemplateFileDir = Nothing 29 | , H.muEscapeFunc = H.emptyEscape 30 | } 31 | -------------------------------------------------------------------------------- /src/Hack/Manager/Travis.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE DeriveDataTypeable #-} 4 | module Hack.Manager.Travis 5 | ( renderTravis 6 | , TravisOpts (..) 7 | ) 8 | where 9 | 10 | import Hack.Manager.Types 11 | 12 | import Control.Monad 13 | import Data.Data 14 | import Data.FileEmbed 15 | import qualified Data.ByteString as BS 16 | import qualified Data.Text as T 17 | import qualified Data.Text.Encoding as T 18 | import qualified Data.Text.Lazy as TL 19 | import qualified Text.Hastache as H 20 | import qualified Text.Hastache.Context as H 21 | 22 | data TravisOpts 23 | = TravisOpts 24 | { to_ghcRelease :: T.Text 25 | , to_useStack :: Bool 26 | } deriving (Show, Eq, Data, Typeable) 27 | 28 | data TravisCtx 29 | = TravisCtx 30 | { tc_project :: ProjectInfo 31 | , tc_opts :: TravisOpts 32 | } deriving (Show, Eq, Data, Typeable) 33 | 34 | stackTemplate :: BS.ByteString 35 | stackTemplate = $(embedFile "templates/travis-stack.mustache") 36 | 37 | cabalTemplate :: BS.ByteString 38 | cabalTemplate = $(embedFile "templates/travis-cabal.mustache") 39 | 40 | renderTravis :: TravisOpts -> ProjectInfo -> IO T.Text 41 | renderTravis to pinfo = 42 | liftM TL.toStrict $ H.hastacheStr cfg (T.decodeUtf8 tpl) ctx 43 | where 44 | tpl = 45 | if to_useStack to then stackTemplate else cabalTemplate 46 | ctx = 47 | H.mkGenericContext $ 48 | TravisCtx 49 | { tc_project = pinfo 50 | , tc_opts = to 51 | } 52 | cfg = 53 | H.defaultConfig 54 | { H.muTemplateFileDir = Nothing 55 | , H.muEscapeFunc = H.emptyEscape 56 | } 57 | -------------------------------------------------------------------------------- /src/Hack/Manager/Types.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveDataTypeable #-} 2 | module Hack.Manager.Types where 3 | 4 | import Data.Data 5 | import Data.Typeable 6 | import qualified Data.Text as T 7 | 8 | data GithubInfo 9 | = GithubInfo 10 | { gi_user :: T.Text 11 | , gi_project :: T.Text 12 | } deriving (Show, Eq, Data, Typeable) 13 | 14 | data LicenseInfo 15 | = LicenseInfo 16 | { li_type :: T.Text 17 | , li_copyright :: T.Text 18 | } deriving (Show, Eq, Data, Typeable) 19 | 20 | data CliExecutable 21 | = CliExecutable 22 | { ce_name :: T.Text 23 | , ce_help :: T.Text 24 | } deriving (Show, Eq, Data, Typeable) 25 | 26 | data ProjectInfo 27 | = ProjectInfo 28 | { pi_name :: T.Text 29 | , pi_pkgName :: T.Text 30 | , pi_pkgDesc :: T.Text 31 | , pi_stackFile :: Bool 32 | , pi_onStackage :: Bool 33 | , pi_onHackage :: Bool 34 | , pi_example :: Maybe T.Text 35 | , pi_moreExamples :: Bool 36 | , pi_cliUsage :: [CliExecutable] 37 | , pi_github :: GithubInfo 38 | , pi_license :: LicenseInfo 39 | , pi_ghcVersions :: [T.Text] 40 | , pi_moreInfo :: Maybe T.Text 41 | } deriving (Show, Eq, Data, Typeable) 42 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | flags: {} 2 | packages: 3 | - '.' 4 | extra-deps: [] 5 | resolver: lts-3.0 6 | -------------------------------------------------------------------------------- /templates/README.mustache: -------------------------------------------------------------------------------- 1 | {{pi_name}} 2 | ===== 3 | 4 | [![Build Status](https://travis-ci.org/{{pi_github.gi_user}}/{{pi_github.gi_project}}.svg)](https://travis-ci.org/{{pi_github.gi_user}}/{{pi_github.gi_project}}) 5 | {{#pi_onHackage}}[![Hackage](https://img.shields.io/hackage/v/{{pi_pkgName}}.svg)](http://hackage.haskell.org/package/{{pi_pkgName}}){{/pi_onHackage}} 6 | 7 | ## Intro 8 | 9 | {{#pi_onHackage}} 10 | Hackage: [{{pi_pkgName}}](http://hackage.haskell.org/package/{{pi_pkgName}}) 11 | {{/pi_onHackage}} 12 | {{#pi_onStackage}} 13 | Stackage: [{{pi_pkgName}}](https://www.stackage.org/package/{{pi_pkgName}}) 14 | {{/pi_onStackage}} 15 | 16 | {{pi_pkgDesc}} 17 | 18 | {{#pi_cliUsage}} 19 | ## Cli Usage: {{ce_name}} 20 | 21 | ```sh 22 | $ {{ce_name}} --help 23 | {{ce_help}} 24 | ``` 25 | {{/pi_cliUsage}} 26 | 27 | {{#pi_example}} 28 | ## Library Usage Example 29 | 30 | ```haskell 31 | {{.}} 32 | ``` 33 | {{#pi_moreExamples}} 34 | 35 | For more examples check the examples/ directory. 36 | {{/pi_moreExamples}} 37 | 38 | {{/pi_example}} 39 | ## Install 40 | 41 | {{#pi_onHackage}} 42 | * Using cabal: `cabal install {{pi_pkgName}}` 43 | {{/pi_onHackage}} 44 | {{#pi_onStackage}} 45 | * Using Stack: `stack install {{pi_pkgName}}` 46 | {{/pi_onStackage}} 47 | * From Source (cabal): `git clone https://github.com/{{pi_github.gi_user}}/{{pi_github.gi_project}}.git && cd {{pi_github.gi_project}} && cabal install` 48 | {{#pi_stackFile}} 49 | * From Source (stack): `git clone https://github.com/{{pi_github.gi_user}}/{{pi_github.gi_project}}.git && cd {{pi_github.gi_project}} && stack build` 50 | {{/pi_stackFile}} 51 | 52 | {{#pi_moreInfo}} 53 | {{.}} 54 | {{/pi_moreInfo}} 55 | 56 | ## Misc 57 | 58 | ### Supported GHC Versions 59 | 60 | {{#pi_ghcVersions}} 61 | * {{.}} 62 | {{/pi_ghcVersions}} 63 | 64 | ### License 65 | 66 | Released under the {{pi_license.li_type}} license. 67 | {{pi_license.li_copyright}} 68 | -------------------------------------------------------------------------------- /templates/gitignore.mustache: -------------------------------------------------------------------------------- 1 | {{^pi_stackFile}} 2 | dist 3 | cabal-dev 4 | {{/pi_stackFile}} 5 | *.o 6 | *.hi 7 | *.chi 8 | *.chs.h 9 | *.dyn_o 10 | *.dyn_hi 11 | .hpc 12 | .hsenv 13 | {{^pi_stackFile}} 14 | .cabal-sandbox/ 15 | cabal.sandbox.config 16 | {{/pi_stackFile}} 17 | *.prof 18 | *.aux 19 | *.hp 20 | {{#pi_stackFile}} 21 | .stack-work/ 22 | {{/pi_stackFile}} 23 | -------------------------------------------------------------------------------- /templates/travis-cabal.mustache: -------------------------------------------------------------------------------- 1 | language: haskell 2 | env: 3 | {{#tc_project.pi_ghcVersions}} 4 | - GHCVER={{.}} 5 | {{/tc_project.pi_ghcVersions}} 6 | - GHCVER=head 7 | matrix: 8 | allow_failures: 9 | - env: GHCVER=head 10 | before_install: 11 | - | 12 | if [ $GHCVER = `ghc --numeric-version` ]; then 13 | travis/cabal-apt-install --enable-tests $MODE 14 | export CABAL=cabal 15 | else 16 | travis_retry sudo add-apt-repository -y ppa:hvr/ghc 17 | travis_retry sudo apt-get update 18 | travis_retry sudo apt-get install cabal-install-1.22 ghc-$GHCVER happy 19 | export CABAL=cabal-1.22 20 | export PATH=/opt/ghc/$GHCVER/bin:$PATH 21 | fi 22 | - $CABAL update 23 | - | 24 | $CABAL install happy alex 25 | export PATH=$HOME/.cabal/bin:$PATH 26 | install: 27 | - $CABAL install --dependencies-only --enable-tests 28 | - $CABAL configure -flib-Werror --enable-tests $MODE 29 | script: 30 | - ghc --numeric-version 31 | - $CABAL check 32 | - $CABAL build 33 | - $CABAL test --show-details=always 34 | deploy: 35 | provider: hackage 36 | username: AlexanderThiemann 37 | skip_cleanup: true 38 | on: 39 | condition: "$GHCVER = {{tc_opts.to_ghcRelease}}" 40 | tags: true 41 | -------------------------------------------------------------------------------- /templates/travis-stack.mustache: -------------------------------------------------------------------------------- 1 | # NB: don't set `language: haskell` here 2 | 3 | sudo: false 4 | 5 | matrix: 6 | include: 7 | {{#tc_project.pi_ghcVersions}} 8 | - env: GHCVER={{.}} STACK_YAML=stack.yaml 9 | addons: 10 | apt: 11 | sources: 12 | - hvr-ghc 13 | packages: 14 | - ghc-{{.}} 15 | {{/tc_project.pi_ghcVersions}} 16 | 17 | before_install: 18 | # ghc 19 | - export PATH=/opt/ghc/$GHCVER/bin:$PATH 20 | # stack 21 | - mkdir -p ~/.local/bin 22 | - export PATH=~/.local/bin:$PATH 23 | - travis_retry curl -L https://github.com/commercialhaskell/stack/releases/download/v0.1.2.0/stack-0.1.2.0-x86_64-linux.gz | gunzip > ~/.local/bin/stack 24 | - chmod a+x ~/.local/bin/stack 25 | # versions 26 | - stack +RTS -N1 -RTS --version 27 | - echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]" 28 | 29 | install: 30 | - ./travis_long stack +RTS -N1 -RTS --no-terminal --skip-ghc-check setup 31 | - ./travis_long stack +RTS -N1 -RTS --no-terminal --skip-ghc-check test --only-snapshot 32 | 33 | script: 34 | - stack +RTS -N2 -RTS --no-terminal --skip-ghc-check test 35 | 36 | cache: 37 | directories: 38 | - $HOME/.stack 39 | 40 | deploy: 41 | provider: hackage 42 | username: AlexanderThiemann 43 | skip_cleanup: true 44 | on: 45 | condition: "$GHCVER = {{tc_opts.to_ghcRelease}}" 46 | tags: true 47 | -------------------------------------------------------------------------------- /update-readme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | stack build 4 | stack exec -- hackmanager readme 5 | --------------------------------------------------------------------------------