{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ViewPatterns #-} -- | This module contains functions for stack template creation. module Summoner.Template ( createStackTemplate ) where import Relude import Data.List (delete) import NeatInterpolation (text) import Summoner.Default (defaultGHC, endLine) import Summoner.ProjectData (CustomPrelude (..), GhcVer (..), ProjectData (..), baseNopreludeVer, latestLts, showGhcVer) import Summoner.Text (intercalateMap, packageToModule) import Summoner.Tree (TreeFs (..)) import qualified Data.Text as T ---------------------------------------------------------------------------- -- Stack File Creation ---------------------------------------------------------------------------- emptyIfNot :: Monoid m => Bool -> m -> m emptyIfNot p val = if p then val else mempty -- | Creating template file to use in `stack new` command createStackTemplate :: ProjectData -> TreeFs createStackTemplate ProjectData{..} = Dir (toString repo) $ [ File (toString repo <> ".cabal") ( createCabalTop <> emptyIfNot github createCabalGit <> emptyIfNot isLib createCabalLib <> emptyIfNot isExe ( createCabalExe $ emptyIfNot isLib $ ", " <> repo ) <> emptyIfNot test createCabalTest <> emptyIfNot bench ( createCabalBenchmark $ emptyIfNot isLib $ ", " <> repo ) ) , File "README.md" readme , File "CHANGELOG.md" changelog , File "LICENSE" licenseText ] ++ createCabalFiles ++ emptyIfNot stack (createStackYamls testedVersions) ++ [File ".gitignore" gitignore | github] ++ [File ".travis.yml" travisYml | travis] ++ [File "appveyor.yml" appVeyorYml | appVey] ++ [File "b" scriptSh | script] where -- Creates module name from the name of the project libModuleName :: Text libModuleName = packageToModule repo preludeMod :: Text preludeMod = case prelude of Nothing -> "" Just _ -> "Prelude" customPreludePack :: Text customPreludePack = case prelude of Nothing -> "" Just Prelude{..} -> ", " <> cpPackage -- all basic project information for `*.cabal` file createCabalTop :: Text createCabalTop = [text| name: $repo version: 0.0.0 description: $description synopsis: $description homepage: https://github.com/${owner}/${repo} bug-reports: https://github.com/${owner}/${repo}/issues license: $license license-file: LICENSE author: $nm maintainer: $email copyright: $year $nm category: $category build-type: Simple extra-doc-files: README.md , CHANGELOG.md cabal-version: 1.24 tested-with: $testedGhcs $endLine |] testedGhcs :: Text testedGhcs = intercalateMap ", " (mappend "GHC == " . showGhcVer) testedVersions defaultExtensions :: Text defaultExtensions = case extensions of [] -> "" xs -> "default-extensions: " <> T.intercalate "\n " xs createCabalGit :: Text createCabalGit = [text| source-repository head type: git location: https://github.com/${owner}/${repo}.git $endLine |] createCabalLib :: Text createCabalLib = [text| library hs-source-dirs: src exposed-modules: $libModuleName $preludeMod ghc-options: -Wall build-depends: $base $customPreludePack default-language: Haskell2010 $defaultExtensions $endLine |] createCabalExe :: Text -> Text createCabalExe r = [text| executable $repo hs-source-dirs: app main-is: Main.hs ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N build-depends: $base $r $customPreludePack default-language: Haskell2010 $defaultExtensions $endLine |] createCabalTest :: Text createCabalTest = [text| test-suite ${repo}-test type: exitcode-stdio-1.0 hs-source-dirs: test main-is: Spec.hs build-depends: $base , $repo $customPreludePack ghc-options: -Wall -Werror -threaded -rtsopts -with-rtsopts=-N default-language: Haskell2010 $defaultExtensions $endLine |] createCabalBenchmark :: Text -> Text createCabalBenchmark r = [text| benchmark ${repo}-benchmark type: exitcode-stdio-1.0 default-language: Haskell2010 ghc-options: -Wall -Werror -O2 -threaded -rtsopts -with-rtsopts=-N hs-source-dirs: benchmark main-is: Main.hs build-depends: $base , gauge $customPreludePack $r $defaultExtensions $endLine |] createCabalFiles :: [TreeFs] createCabalFiles = [ Dir "app" [exeFile] | isExe ] ++ [ Dir "test" [testFile] | test ] ++ [ Dir "benchmark" [benchmarkFile] | bench ] ++ [ Dir "src" $ libFile : preludeFile | isLib ] testFile :: TreeFs testFile = File "Spec.hs" [text| main :: IO () main = putStrLn ("Test suite not yet implemented" :: String) $endLine |] libFile :: TreeFs libFile = File (toString libModuleName <> ".hs") [text| module $libModuleName ( someFunc ) where someFunc :: IO () someFunc = putStrLn ("someFunc" :: String) $endLine |] preludeFile :: [TreeFs] preludeFile = case prelude of Nothing -> [] Just Prelude{..} -> one $ File "Prelude.hs" [text| -- | Uses [$cpPackage](https://hackage.haskell.org/package/${cpPackage}) as default Prelude. module Prelude ( module $cpModule ) where import $cpModule $endLine |] exeFile :: TreeFs exeFile = File "Main.hs" $ if isLib then createExe else createOnlyExe createOnlyExe :: Text createOnlyExe = [text| module Main where main :: IO () main = putStrLn ("Hello, world!" :: String) $endLine |] createExe :: Text createExe = [text| module Main where import $libModuleName (someFunc) main :: IO () main = someFunc $endLine |] benchmarkFile :: TreeFs benchmarkFile = File "Main.hs" [text| import Gauge.Main main :: IO () main = defaultMain [bench "const" (whnf const ())] $endLine |] -- create README template readme :: Text readme = [text| # $repo [![Hackage]($hackageShield)]($hackageLink) [![$license license](${licenseShield})](${licenseLink}) $travisBadge $appVeyorBadge $description $endLine |] where hackageShield :: Text = "https://img.shields.io/hackage/v/" <> repo <> ".svg" hackageLink :: Text = "https://hackage.haskell.org/package/" <> repo travisShield :: Text = "https://secure.travis-ci.org/" <> owner <> "/" <> repo <> ".svg" travisLink :: Text = "https://travis-ci.org/" <> owner <> "/" <> repo travisBadge :: Text = emptyIfNot travis [text|[![Build status](${travisShield})](${travisLink})|] appVeyorShield :: Text = "https://ci.appveyor.com/api/projects/status/github/" <> owner <> "/" <> repo <> "?branch=master&svg=true" appVeyorLink :: Text = "https://ci.appveyor.com/project/" <> owner <> "/" <> repo appVeyorBadge :: Text = emptyIfNot appVey [text|[![Windows build status](${appVeyorShield})](${appVeyorLink})|] licenseShield :: Text = "https://img.shields.io/badge/license-" <> T.replace "-" "--" license <> "-blue.svg" licenseLink :: Text = "https://github.com/" <> owner <> "/" <> repo <> "/blob/master/LICENSE" -- create .gitignore template gitignore :: Text gitignore = [text| ### Haskell dist dist-* cabal-dev *.o *.hi *.chi *.chs.h *.dyn_o *.dyn_hi *.prof *.aux *.hp *.eventlog .virtualenv .hsenv .hpc .cabal-sandbox/ cabal.sandbox.config cabal.config cabal.project.local .ghc.environment.* .HTF/ # Stack .stack-work/ ### IDE/support # Vim [._]*.s[a-v][a-z] [._]*.sw[a-p] [._]s[a-v][a-z] [._]sw[a-p] *~ tags # IntellijIDEA .idea/ .ideaHaskellLib/ *.iml # Atom .haskell-ghc-mod.json # VS .vscode/ # Emacs *# .dir-locals.el TAGS # other .DS_Store $endLine |] -- create CHANGELOG template changelog :: Text changelog = [text| Change log ========== $repo uses [PVP Versioning][1]. The change log is available [on GitHub][2]. 0.0.0 ===== * Initially created. [1]: https://pvp.haskell.org [2]: https://github.com/${owner}/${repo}/releases $endLine |] -- create travis.yml template travisYml :: Text travisYml = let travisStackMtr = emptyIfNot stack $ T.concat (map travisStackMatrixItem $ delete defaultGHC testedVersions) <> travisStackMatrixDefaultItem travisCabalMtr = emptyIfNot cabal $ T.concat $ map travisCabalMatrixItem testedVersions installAndScript = if cabal then if stack then installScriptBoth else installScriptCabal else installScriptStack in [text| sudo: true language: haskell git: depth: 5 cache: directories: - "$$HOME/.stack" - "$$TRAVIS_BUILD_DIR/.stack-work" matrix: include: $travisCabalMtr $travisStackMtr $installAndScript notifications: email: false $endLine |] cabalTest :: Text cabalTest = if test then "cabal new-test" else "echo 'No tests'" travisCabalMatrixItem :: GhcVer -> Text travisCabalMatrixItem (showGhcVer -> ghcV) = [text| - ghc: ${ghcV} env: GHCVER='${ghcV}' CABALVER='head' os: linux addons: apt: sources: - hvr-ghc packages: - ghc-${ghcV} - cabal-install-head $endLine |] travisStackMatrixItem :: GhcVer -> Text travisStackMatrixItem (showGhcVer -> ghcV) = [text| - ghc: ${ghcV} env: GHCVER='${ghcV}' STACK_YAML="$$TRAVIS_BUILD_DIR/stack-$$GHCVER.yaml" os: linux addons: apt: packages: - libgmp-dev $endLine |] travisStackMatrixDefaultItem :: Text travisStackMatrixDefaultItem = let defGhc = showGhcVer defaultGHC in [text| - ghc: ${defGhc} env: GHCVER='${defGhc}' STACK_YAML="$$TRAVIS_BUILD_DIR/stack.yaml" os: linux addons: apt: packages: - libgmp-dev $endLine |] installScriptBoth :: Text installScriptBoth = [text| install: - | if [ -z "$$STACK_YAML" ]; then export PATH="/opt/ghc/$$GHCVER/bin:/opt/cabal/$$CABALVER/bin:$$PATH" echo $$PATH cabal new-update cabal new-build --enable-tests --enable-benchmarks else mkdir -p ~/.local/bin export PATH="$$HOME/.local/bin:$$PATH" travis_retry curl -L 'https://www.stackage.org/stack/linux-x86_64' | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack' stack --version stack setup --no-terminal --install-cabal 2.0.1.0 stack ghc -- --version stack build --only-dependencies --no-terminal fi script: - | if [ -z "$$STACK_YAML" ]; then ${cabalTest} else stack build --test --bench --no-run-benchmarks --no-terminal fi $endLine |] installScriptCabal :: Text installScriptCabal = [text| install: - export PATH="/opt/ghc/$$GHCVER/bin:/opt/cabal/$$CABALVER/bin:$$PATH" - echo $$PATH - cabal new-update - cabal new-build --enable-tests --enable-benchmarks script: - ${cabalTest} $endLine |] installScriptStack :: Text installScriptStack = [text| install: - mkdir -p ~/.local/bin - export PATH="$$HOME/.local/bin:$$PATH" - travis_retry curl -L 'https://www.stackage.org/stack/linux-x86_64' | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack' - stack --version - stack setup --no-terminal --install-cabal 2.0.1.0 - stack ghc -- --version - stack build --only-dependencies --no-terminal script: - stack build --test --bench --no-run-benchmarks --no-terminal $endLine |] -- create @stack.yaml@ file with LTS corresponding to specified ghc version createStackYamls :: [GhcVer] -> [TreeFs] createStackYamls = map createStackYaml where createStackYaml :: GhcVer -> TreeFs createStackYaml ghcV = let ver = case ghcV of Ghc843 -> "" _ -> "-" <> showGhcVer ghcV in stackYaml ver (latestLts ghcV) (baseNopreludeVer ghcV) where stackYaml :: Text -> Text -> Text -> TreeFs stackYaml ghc lts baseVer = File (toString $ "stack" <> ghc <> ".yaml") [text| resolver: lts-${lts} $extraDeps $ghcOpts $endLine |] where extraDeps :: Text extraDeps = case prelude of Nothing -> "" Just _ -> "extra-deps: [base-noprelude-" <> baseVer <> "]" ghcOpts :: Text ghcOpts = if ghcV <= Ghc802 then "" else [text| ghc-options: "$$locals": -fhide-source-paths |] -- create appveyor.yml template appVeyorYml :: Text appVeyorYml = [text| build: off before_test: # http://help.appveyor.com/discussions/problems/6312-curl-command-not-found - set PATH=C:\Program Files\Git\mingw64\bin;%PATH% - curl -sS -ostack.zip -L --insecure http://www.stackage.org/stack/windows-i386 - 7z x stack.zip stack.exe clone_folder: "c:\\stack" environment: global: STACK_ROOT: "c:\\sr" test_script: - stack setup > nul # The ugly echo "" hack is to avoid complaints about 0 being an invalid file # descriptor - echo "" | stack --no-terminal build --bench --no-run-benchmarks --test |] scriptSh :: Text scriptSh = [text| #!/usr/bin/env bash set -e # DESCRIPTION # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # This script builds the project in a way that is convenient for developers. # It passes the right flags into right places, builds the project with --fast, # tidies up and highlights error messages in GHC output. # USAGE # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ./b build whole project with all targets # ./b -c do stack clean # ./b -t build and run tests # ./b -b build and run benchmarks # ./b --nix use nix to build package args='' test=false bench=false with_nix=false clean=false for var in "$$@" do # -t = run tests if [[ $$var == "-t" ]]; then test=true # -b = run benchmarks elif [[ $$var == "-b" ]]; then bench=true elif [[ $$var == "--nix" ]]; then with_nix=true # -c = clean elif [[ $$var == "-c" ]]; then clean=true else args="$$args $$var" fi done # Cleaning project if [[ $$clean == true ]]; then echo "Cleaning project..." stack clean exit fi if [[ $$no_nix == true ]]; then args="$$args --nix" fi xperl='$|++; s/(.*) Compiling\s([^\s]+)\s+\(\s+([^\/]+).*/\1 \2/p' xgrep="((^.*warning.*$|^.*error.*$|^ .*$|^.*can't find source.*$|^Module imports form a cycle.*$|^ which imports.*$)|^)" stack build $$args \ --ghc-options="+RTS -A256m -n2m -RTS" \ --test \ --no-run-tests \ --no-haddock-deps \ --bench \ --no-run-benchmarks \ --jobs=4 \ --dependencies-only stack build $$args \ --fast \ --ghc-options="+RTS -A256m -n2m -RTS" \ --test \ --no-run-tests \ --no-haddock-deps \ --bench \ --no-run-benchmarks \ --jobs=4 2>&1 | perl -pe "$$xperl" | { grep -E --color "$$xgrep" || true; } if [[ $$test == true ]]; then stack build $$args \ --fast \ --ghc-options="+RTS -A256m -n2m -RTS" \ --test \ --no-haddock-deps \ --bench \ --no-run-benchmarks \ --jobs=4 fi if [[ $$bench == true ]]; then stack build $$args \ --fast \ --ghc-options="+RTS -A256m -n2m -RTS" \ --test \ --no-run-tests \ --no-haddock-deps \ --bench \ --jobs=4 fi $endLine |]