{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ViewPatterns #-}
module Summoner.Template
( createProjectTemplate
) where
import Relude
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
memptyIfFalse :: Monoid m => Bool -> m -> m
memptyIfFalse p val = if p then val else mempty
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
libModuleName :: Text
libModuleName = packageToModule repo
preludeMod :: Text
preludeMod = case prelude of
Nothing -> ""
Just _ -> "Prelude"
customPreludePack :: Text
customPreludePack = case prelude of
Nothing -> ""
Just Prelude{..} -> ", " <> cpPackage
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
|]
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 <> ")"
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
|]
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"
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
|]
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
|]
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
|]