{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE ScopedTypeVariables #-} -- | This module contains functions for stack template creation. module Summoner.Template ( createStackTemplate ) where import Universum 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 :: Bool -> Text -> Text emptyIfNot p txt = if p then txt else "" -- | Creating template file to use in `stack new` command createStackTemplate :: ProjectData -> TreeFs createStackTemplate ProjectData{..} = Dir (toString repo) $ [ File (toString repo <> ".cabal") ( createCabalTop <> emptyIfNot isLib createCabalLib <> emptyIfNot isExe ( createCabalExe $ emptyIfNot isLib $ ", " <> repo ) <> emptyIfNot test createCabalTest <> emptyIfNot bench ( createCabalBenchmark $ emptyIfNot isLib $ ", " <> repo ) <> emptyIfNot github createCabalGit ) , File "README.md" readme , File "CHANGELOG.md" changelog , File "LICENSE" licenseText ] ++ createCabalFiles ++ 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 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 |] createCabalGit :: Text createCabalGit = [text| source-repository head type: git location: https://github.com/${owner}/${repo}.git $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) [![Build status](${travisShield})](${travisLink}) [![Windows build status](${appVeyorShield})](${appVeyorLink}) [![$license license](${licenseShield})](${licenseLink}) $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 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 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 travisMtr = T.concat (map (travisMatrixItem . showGhcVer) (delete defaultGHC testedVersions)) defGhc = showGhcVer defaultGHC in [text| sudo: true language: haskell git: depth: 5 cache: directories: - "$$HOME/.stack" - "$$HOME/build/${owner}/${repo}/.stack-work" matrix: include: $travisMtr - ghc: $defGhc env: GHCVER='${defGhc}' STACK_YAML="$$HOME/build/${owner}/${repo}/stack.yaml" addons: apt: sources: - sourceline: 'ppa:hvr/ghc' packages: - libgmp-dev before_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 install: - travis_wait 30 stack setup --no-terminal - stack ghc -- --version - travis_wait 40 stack build --only-dependencies --no-terminal - travis_wait 40 stack build --test --bench --haddock --no-run-tests --no-run-benchmarks --no-haddock-deps --no-terminal script: - travis_wait 40 stack build --test --no-terminal notifications: email: false $endLine |] travisMatrixItem :: Text -> Text travisMatrixItem ghcV = [text| - ghc: ${ghcV} env: GHCVER='${ghcV}' STACK_YAML="$$HOME/build/${owner}/${repo}/stack-$$GHCVER.yaml" $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 Ghc822 -> "" _ -> "-" <> 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-sourcepaths |] -- 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 |]