{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE ViewPatterns #-} -- | This module contains functions for stack template creation. module Summoner.Template ( createProjectTemplate ) where import Data.List (delete) import NeatInterpolation (text) import Summoner.Default (defaultGHC, endLine) import Summoner.GhcVer (GhcVer (..), baseVer, latestLts, showGhcVer) import Summoner.License (License (..)) import Summoner.ProjectData (CustomPrelude (..), ProjectData (..)) import Summoner.Text (intercalateMap, packageToModule) import Summoner.Tree (TreeFs (..)) import qualified Data.Text as T ---------------------------------------------------------------------------- -- Project Directory Creation ---------------------------------------------------------------------------- memptyIfFalse :: Monoid m => Bool -> m -> m memptyIfFalse p val = if p then val else mempty -- | Creating tree structure of the project. createProjectTemplate :: ProjectData -> TreeFs createProjectTemplate ProjectData{..} = Dir (toString repo) $ [ File (toString repo <> ".cabal") ( createCabalTop <> memptyIfFalse github createCabalGit <> memptyIfFalse isLib createCabalLib <> memptyIfFalse isExe ( createCabalExe $ memptyIfFalse isLib $ ", " <> repo ) <> memptyIfFalse test ( createCabalTest $ memptyIfFalse isLib $ ", " <> repo ) <> memptyIfFalse bench ( createCabalBenchmark $ memptyIfFalse isLib $ ", " <> repo ) ) , File "README.md" readme , File "CHANGELOG.md" changelog , File "LICENSE" $ unLicense licenseText ] ++ createCabalFiles ++ memptyIfFalse stack (createStackYamls testedVersions) ++ [File ".gitignore" gitignore | github] ++ [File ".travis.yml" travisYml | travis] ++ [File "appveyor.yml" appVeyorYml | appVey] ++ maybe [] (\x -> [File ".stylish-haskell.yaml" x]) stylish ++ maybe [] (\x -> [File "CONTRIBUTING.md" x]) contributing where license = show licenseName -- 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| cabal-version: 1.24 name: $repo version: 0.0.0 description: $description synopsis: $description $githubHomepage $githubBugReports license: $license license-file: LICENSE author: $nm maintainer: $email copyright: $year $nm category: $category build-type: Simple extra-doc-files: README.md , CHANGELOG.md tested-with: $testedGhcs $endLine |] where githubHomepage :: Text = memptyIfFalse github $ "homepage: https://github.com/" <> owner <> "/" <> repo githubBugReports :: Text = memptyIfFalse github $ "bug-reports: https://github.com/" <> owner <> "/" <> repo <> "/issues" 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 |] ghcOptions :: Text ghcOptions = case warnings of [] -> defaultWarnings xs -> T.intercalate "\n" xs defaultWarnings :: Text defaultWarnings = [text| -Wincomplete-uni-patterns -Wincomplete-record-updates -Wcompat -Widentities $versionWarnings |] versionWarnings :: Text versionWarnings = memptyIfFalse (testedVersions `hasLeast` Ghc801) "-Wredundant-constraints\n" <> memptyIfFalse (testedVersions `hasLeast` Ghc822) "-fhide-source-paths\n" <> memptyIfFalse (testedVersions `hasLeast` Ghc843) [text| -Wmissing-export-lists -Wpartial-fields |] where hasLeast list el = all (>= el) list createCabalLib :: Text createCabalLib = [text| library hs-source-dirs: src exposed-modules: $libModuleName $preludeMod ghc-options: -Wall $ghcOptions 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 $ghcOptions build-depends: $base $r $customPreludePack default-language: Haskell2010 $defaultExtensions $endLine |] createCabalTest :: Text -> Text createCabalTest r = [text| test-suite ${repo}-test type: exitcode-stdio-1.0 hs-source-dirs: test main-is: Spec.hs ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N $ghcOptions build-depends: $base $r $customPreludePack default-language: Haskell2010 $defaultExtensions $endLine |] createCabalBenchmark :: Text -> Text createCabalBenchmark r = [text| benchmark ${repo}-benchmark type: exitcode-stdio-1.0 hs-source-dirs: benchmark main-is: Main.hs ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N $ghcOptions build-depends: $base , gauge $r $customPreludePack default-language: Haskell2010 $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| main :: IO () main = putStrLn ("Hello, world!" :: String) $endLine |] createExe :: Text createExe = [text| 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 = T.intercalate "\n" $ [ "# " <> repo , "" , hackage , licenseBadge ] ++ [stackLtsBadge | stack] ++ [stackNightlyBadge | stack] ++ [travisBadge | travis] ++ [appVeyorBadge | appVey] ++ ["" , description ] where hackageShield :: Text = "https://img.shields.io/hackage/v/" <> repo <> ".svg" hackageLink :: Text = "https://hackage.haskell.org/package/" <> repo hackage :: Text = makeBadge "Hackage" hackageShield hackageLink licenseShield :: Text = "https://img.shields.io/badge/license-" <> T.replace "-" "--" license <> "-blue.svg" licenseBadge :: Text = makeBadge (license <> " license") licenseShield "LICENSE" stackShieldLts :: Text = "http://stackage.org/package/" <> repo <> "/badge/lts" stackLinkLts :: Text = "http://stackage.org/lts/package/" <> repo stackShieldNightly :: Text = "http://stackage.org/package/" <> repo <> "/badge/nightly" stackLinkNightly :: Text = "http://stackage.org/nightly/package/" <> repo stackLtsBadge :: Text = makeBadge "Stackage Lts" stackShieldLts stackLinkLts stackNightlyBadge :: Text = makeBadge "Stackage Nightly" stackShieldNightly stackLinkNightly travisShield :: Text = "https://secure.travis-ci.org/" <> owner <> "/" <> repo <> ".svg" travisLink :: Text = "https://travis-ci.org/" <> owner <> "/" <> repo travisBadge :: Text = makeBadge "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 = makeBadge "Windows build status" appVeyorShield appVeyorLink makeBadge :: Text -> Text -> Text -> Text makeBadge title shield link = "[![" <> title <> "](" <> shield <> ")](" <> link <> ")" -- 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]. $githubLine 0.0.0 ===== * Initially created. [1]: https://pvp.haskell.org $githubFootNote |] where githubLine :: Text = memptyIfFalse github "The change log is available [on GitHub][2]." githubFootNote :: Text = memptyIfFalse github $ "[2]: https://github.com/" <> owner <> "/" <> repo <> "/releases" -- create travis.yml template travisYml :: Text travisYml = let travisStackMtr = memptyIfFalse stack $ T.concat (map travisStackMatrixItem $ delete defaultGHC testedVersions) <> travisStackMatrixDefaultItem travisCabalMtr = memptyIfFalse cabal $ T.concat $ map travisCabalMatrixItem testedVersions installAndScript = if cabal then if stack then installScriptBoth else installScriptCabal else installScriptStack travisCabalCache = memptyIfFalse cabal "- \"$HOME/.cabal\"" travisStackCache = memptyIfFalse stack [text| - "$$HOME/.stack" - "$$TRAVIS_BUILD_DIR/.stack-work" |] in [text| sudo: true language: haskell git: depth: 5 cache: directories: $travisCabalCache $travisStackCache 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) (baseVer ghcV) where stackYaml :: Text -> Text -> Text -> TreeFs stackYaml ghc lts baseV = 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-" <> baseV <> "]" 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 |]