Skip to content

Instantly share code, notes, and snippets.

@roman
Last active January 9, 2018 21:14
Show Gist options
  • Save roman/2f6855d747457ce4c22cd64f5dc2996b to your computer and use it in GitHub Desktop.
Save roman/2f6855d747457ce4c22cd64f5dc2996b to your computer and use it in GitHub Desktop.
Snapshot of library skeleton for Haskell Projects
{-# START_FILE README.md #-}
# {{name}}
> Description of what this library does
## Table Of Contents
* [Raison d'etre](#raison-detre)
* [Library Usage](#library-usage)
* [Installation](#installation)
* [Development Notes](#development)
## Raison d'etre
Give me some reason to care
## Library Usage
Give me some instructions on how to use your library, or point me to the right
spot.
## Installation
[![Hackage](https://img.shields.io/hackage/v/{{name}}.svg)](https://img.shields.io/hackage/v/{{name}}.svg)
[![Stackage LTS](https://stackage.org/package/{{name}}/badge/lts)](http://stackage.org/lts/package/{{name}})
[![Stackage Nightly](https://stackage.org/package/{{name}}/badge/nightly)](http://stackage.org/nightly/package/{{name}})
Make sure you include the following entry on your [cabal file's
dependecies](https://www.haskell.org/cabal/users-guide/developing-packages.html#build-information)
section.
## Development
[![Build Status](https://travis-ci.org/{{github-username}}/Haskell-{{name}}.svg?branch=master)](https://travis-ci.org/{{github-username}}/Haskell-{{name}})
[![Github](https://img.shields.io/github/commits-since/{{github-username}}/haskell-{{name}}/v0.0.0.1.svg)](https://img.shields.io/github/commits-since/{{github-username}}/haskell-{{name}}/v0.0.0.1.svg)
[![Hackage Dependencies](https://img.shields.io/hackage-deps/v/{{name}}.svg)](https://img.shields.io/hackage/v/{{name}}.svg)
### Getting started
1. Make sure you have the [stack binary](https://docs.haskellstack.org/en/stable/install_and_upgrade/) installed.
2. Execute `make setup` to install development dependencies
### Open Commit Bit
{{name}} has an open commit bit policy: Anyone with an accepted pull request
gets added as a repository collaborator. Please try to follow these simple
rules:
* Commit directly onto the master branch only for typos, improvements to the
README and documentation.
* Create a feature branch and open a pull-request early for any new features to
get feedback.
* Make sure you adhere to the general pull request rules above.
{-# START_FILE .gitignore #-}
bin/*
tmp/*
target/*
.make/*
.tasty-rerun-log
**/.stack-work/*
*.cabal
/tutorial.md
{-# START_FILE .travis.yaml #-}
sudo: false
language: haskell
cache:
directories:
- $HOME/.ghc
- $HOME/.stack
matrix:
include:
- env: BUILD=nightly
compiler: "GHC-8.2.1"
addons: {apt: {packages: [libgmp-dev]}}
- env: BUILD=lts-9
compiler: "GHC-8.0.2"
addons: {apt: {packages: [libgmp-dev]}}
- env: BUILD=lts-8
compiler: "GHC-8.0.2"
addons: {apt: {packages: [libgmp-dev]}}
- env: BUILD=lts-7
compiler: "GHC-8.0.1"
addons: {apt: {packages: [libgmp-dev]}}
- env: BUILD=lint
compiler: "GHC-8.0.2"
addons: {apt: {packages: [libgmp-dev]}}
- env: BUILD=pretty
compiler: "GHC-8.0.2"
addons: {apt: {packages: [libgmp-dev]}}
- env: BUILD=sdist
compiler: "GHC-8.0.2"
addons: {apt: {packages: [libgmp-dev]}}
before_install:
- export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$HOME/.local/bin:/opt/alex/$ALEXVER/bin:/opt/happy/$HAPPYVER/bin:$HOME/.cabal/bin:$PATH
- |
set -ex
mkdir -p ~/.local/bin
travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
set +ex
install:
- |
set -ex
stack setup --upgrade-cabal && stack install --pedantic
set +ex
script:
- |
set -ex
case "$BUILD" in
lts-*)
RESOLVER=$BUILD make test
;;
nightly)
RESOLVER=$BUILD make test
;;
lint)
make lint
;;
pretty)
make pretty
;;
sdist)
make test_sdist
;;
esac
set +ex
notifications:
email: false
{-# START_FILE .ghci #-}
:seti -XNoImplicitPrelude
:seti -XOverloadedStrings
:seti -XScopedTypeVariables
:set -Wall
:set -fno-warn-type-defaults
:set -package pretty-show
import Protolude
import Text.Show.Pretty (pPrint)
:set -interactive-print pPrint
:set +s
:set +t
:def rt const $ return $ unlines [":r", ":main --rerun-update"]
:def rtfe const $ return $ unlines [":r", ":main --rerun-update --rerun-filter failures,exceptions"]
:def rtn const $ return $ unlines [":r", ":main --rerun-update --rerun-filter new"]
{-# START_FILE .hlint.yaml #-}
# HLint configuration file
# https://github.com/ndmitchell/hlint
##########################
# This file contains a template configuration file, which is typically
# placed as .hlint.yaml in the root of your project
# Specify additional command line arguments
#
# - arguments: [--color, --cpp-simple, -XQuasiQuotes]
# Control which extensions/flags/modules/functions can be used
#
# - extensions:
# - default: false # all extension are banned by default
# - name: [PatternGuards, ViewPatterns] # only these listed extensions can be used
# - {name: CPP, within: CrossPlatform} # CPP can only be used in a given module
#
# - flags:
# - {name: -w, within: []} # -w is allowed nowhere
#
- modules:
# if you import Data.Set qualified, it must be as 'Set'
# - {name: Control.Arrow, within: []} # Certain modules are banned entirely
- {name: [Data.HashMap.Strict], as: HashMap}
- {name: [Data.ByteString], as: BS}
- {name: [Data.ByteString.Lazy], as: BL}
- {name: [Data.ByteString.Char8], as: C}
- {name: [Data.ByteString.Lazy.Char8], as: CL}
- {name: [Data.Text], as: T}
- {name: [Data.Text.IO], as: TIO}
- {name: [Data.Text.Lazy.IO], as: TLIO}
- {name: [Data.Text.Encoding], as: TE}
- {name: [Data.Text.Lazy.Encoding], as: TLE}
- {name: [Data.Aeson], as: JSON}
# - functions:
# - {name: unsafePerformIO, within: []} # unsafePerformIO can only appear in no modules
# Add custom hints for this project
#
# Will suggest replacing "wibbleMany [myvar]" with "wibbleOne myvar"
# - error: {lhs: "wibbleMany [x]", rhs: wibbleOne x}
# Turn on hints that are off by default
#
# Ban "module X(module X) where", to require a real export list
# - warn: {name: Use explicit module export list}
#
# Replace a $ b $ c with a . b $ c
# - group: {name: dollar, enabled: true}
#
# Generalise map to fmap, ++ to <>
- group: {name: generalise, enabled: true}
# Ignore some builtin hints
- ignore: {name: Use String}
# - ignore: {name: Use let}
# - ignore: {name: Use const, within: SpecialModule} # Only within certain modules
# Define some custom infix operators
# - fixity: infixr 3 ~^#^~
# To generate a suitable file for HLint do:
# $ hlint --default > .hlint.yaml
{-# START_FILE .stylish-haskell.yaml #-}
# stylish-haskell configuration file
# ==================================
# The stylish-haskell tool is mainly configured by specifying steps. These steps
# are a list, so they have an order, and one specific step may appear more than
# once (if needed). Each file is processed by these steps in the given order.
steps:
# Convert some ASCII sequences to their Unicode equivalents. This is disabled
# by default.
# - unicode_syntax:
# # In order to make this work, we also need to insert the UnicodeSyntax
# # language pragma. If this flag is set to true, we insert it when it's
# # not already present. You may want to disable it if you configure
# # language extensions using some other method than pragmas. Default:
# # true.
# add_language_pragma: true
# Align the right hand side of some elements. This is quite conservative
# and only applies to statements where each element occupies a single
# line.
- simple_align:
cases: true
top_level_patterns: true
records: true
# Import cleanup
- imports:
# There are different ways we can align names and lists.
#
# - global: Align the import names and import list throughout the entire
# file.
#
# - file: Like global, but don't add padding when there are no qualified
# imports in the file.
#
# - group: Only align the imports per group (a group is formed by adjacent
# import lines).
#
# - none: Do not perform any alignment.
#
# Default: global.
align: group
# Folowing options affect only import list alignment.
#
# List align has following options:
#
# - after_alias: Import list is aligned with end of import including
# 'as' and 'hiding' keywords.
#
# > import qualified Data.List as List (concat, foldl, foldr, head,
# > init, last, length)
#
# - with_alias: Import list is aligned with start of alias or hiding.
#
# > import qualified Data.List as List (concat, foldl, foldr, head,
# > init, last, length)
#
# - new_line: Import list starts always on new line.
#
# > import qualified Data.List as List
# > (concat, foldl, foldr, head, init, last, length)
#
# Default: after_alias
list_align: after_alias
# Long list align style takes effect when import is too long. This is
# determined by 'columns' setting.
#
# - inline: This option will put as much specs on same line as possible.
#
# - new_line: Import list will start on new line.
#
# - new_line_multiline: Import list will start on new line when it's
# short enough to fit to single line. Otherwise it'll be multiline.
#
# - multiline: One line per import list entry.
# Type with contructor list acts like single import.
#
# > import qualified Data.Map as M
# > ( empty
# > , singleton
# > , ...
# > , delete
# > )
#
# Default: inline
long_list_align: new_line_multiline
# Align empty list (importing instances)
#
# Empty list align has following options
#
# - inherit: inherit list_align setting
#
# - right_after: () is right after the module name:
#
# > import Vector.Instances ()
#
# Default: inherit
empty_list_align: right_after
# List padding determines indentation of import list on lines after import.
# This option affects 'long_list_align'.
#
# - <integer>: constant value
#
# - module_name: align under start of module name.
# Useful for 'file' and 'group' align settings.
list_padding: 4
# Separate lists option affects formating of import list for type
# or class. The only difference is single space between type and list
# of constructors, selectors and class functions.
#
# - true: There is single space between Foldable type and list of it's
# functions.
#
# > import Data.Foldable (Foldable (fold, foldl, foldMap))
#
# - false: There is no space between Foldable type and list of it's
# functions.
#
# > import Data.Foldable (Foldable(fold, foldl, foldMap))
#
# Default: true
separate_lists: true
# Language pragmas
- language_pragmas:
# We can generate different styles of language pragma lists.
#
# - vertical: Vertical-spaced language pragmas, one per line.
#
# - compact: A more compact style.
#
# - compact_line: Similar to compact, but wrap each line with
# `{-#LANGUAGE #-}'.
#
# Default: vertical.
style: vertical
# Align affects alignment of closing pragma brackets.
#
# - true: Brackets are aligned in same collumn.
#
# - false: Brackets are not aligned together. There is only one space
# between actual import and closing bracket.
#
# Default: true
align: true
# stylish-haskell can detect redundancy of some language pragmas. If this
# is set to true, it will remove those redundant pragmas. Default: true.
remove_redundant: true
# Replace tabs by spaces. This is disabled by default.
# - tabs:
# # Number of spaces to use for each tab. Default: 8, as specified by the
# # Haskell report.
# spaces: 8
# Remove trailing whitespace
- trailing_whitespace: {}
# A common setting is the number of columns (parts of) code will be wrapped
# to. Different steps take this into account. Default: 80.
columns: 92
# By default, line endings are converted according to the OS. You can override
# preferred format here.
#
# - native: Native newline format. CRLF on Windows, LF on other OSes.
#
# - lf: Convert to LF ("\n").
#
# - crlf: Convert to CRLF ("\r\n").
#
# Default: native.
newline: native
# Sometimes, language extensions are specified in a cabal file or from the
# command line instead of using language pragmas in the file. stylish-haskell
# needs to be aware of these, so it can parse the file correctly.
#
# No language extensions are enabled by default.
# language_extensions:
# - TemplateHaskell
# - QuasiQuotes
{-# START_FILE Makefile #-}
SOURCES=$$(find . -maxdepth 3 -type d | grep 'src\|test\|benchmark')
DIST_DIR:=$$(stack path --dist-dir)
SDIST_TAR:=$$(find $(DIST_DIR) -name "*.tar.gz" | tail -1)
SDIST_FOLDER:=$$(basename $(SDIST_TAR) .tar.gz)
SDIST_INIT:=$$(stack init --force)
STYLISH=stylish-haskell -i {} \;
HLINT=hlint --refactor --refactor-options -i {} \;
RESOLVER ?= lts
STACK:=stack --resolver $(RESOLVER) --install-ghc --local-bin-path ./bin
TEST_DOC:=$(STACK) --haddock --no-haddock-deps build --pedantic
TEST:=$(STACK) build --test
################################################################################
help: ## Display this message
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
.DEFAULT_GOAL := help
################################################################################
bin/hlint:
$(STACK) install hlint
bin/stylish-haskell:
$(STACK) install stylish-haskell
bin/intero:
$(STACK) install pretty-show intero
.make/setup_done:
$(STACK) install hlint stylish-haskell pretty-show
mkdir -p .make
mkdir -p .stack-work/intero
chmod go-w .
chmod go-w .ghci
chmod go-w .stack-work/intero
touch .make/setup_done
################################################################################
test: ## Execute test suites
$(TEST_DOC) {{name}}
$(TEST) {{name}}:{{name}}-test
$(TEST) {{name}}:{{name}}-doctest
.PHONY: test
sdist: ## Build a release
mkdir -p target
stack sdist . --pvp-bounds both
cp $(SDIST_TAR) target
.PHONY: sdist
untar_sdist: sdist
mkdir -p tmp
tar xzf $(SDIST_TAR)
@rm -rf tmp/$(SDIST_FOLDER) || true
mv $(SDIST_FOLDER) tmp
.PHONY: untar_sdist
test_sdist: untar_sdist
cd tmp/$(SDIST_FOLDER) && $(SDIST_INIT) && $(TEST) {{name}}:{{name}}-test
cd tmp/$(SDIST_FOLDER) && $(TEST) {{name}}:{{name}}-doctest
.PHONY: test_sdist
pretty: bin/stylish-haskell ## Normalize style of source files
find $(SOURCES) -name "*.hs" -exec $(STYLISH) && git diff --exit-code
.PHONY: pretty
lint: bin/hlint ## Execute linter
hlint $(SOURCES)
.PHONY: lint
repl: bin/intero ## Start project's repl
stack ghci
.PHONY: repl
setup: .make/setup_done ## Install development dependencies
.PHONY: setup
{-# START_FILE package.yaml #-}
name: {{ name }}
version: '0.0.0.1'
category: TODO
author: {{author-name}}{{^author-name}}TODO:<Author name here>{{/author-name}}
maintainer: {{author-email}}{{^author-email}}TODO:<[email protected]>{{/author-email}}
copyright: © {{year}}{{^year}}2017{{/year}} {{author-name}}{{^author-name}}TODO:<Author name here>{{/author-name}}
license: MIT
license-file: LICENSE
github: {{github-username}}/Haskell-{{name}}
tested-with: GHC==8.0.1 GHC==8.0.2 GHC==8.2.1
extra-source-files:
- README.md
- CHANGELOG.md
ghc-options:
- -Wall
# as recommended in:
# https://functor.tokyo/blog/2017-07-28-ghc-warnings-you-should-enable
- -Wincomplete-uni-patterns
- -Wincomplete-record-updates
dependencies:
- base
- protolude
- flow
- safe-exceptions
- text
- bytestring
- unordered-containers
- vector
library:
source-dirs: src
exposed-modules:
- Lib
tests:
{{ name }}-test:
main: Main.hs
source-dirs: test/testsuite
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
- -O2
dependencies:
- tasty
- tasty-hunit
- tasty-smallcheck
- tasty-rerun
- {{ name }}
{{ name }}-doctest:
main: Main.hs
source-dirs: test/doctest
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
- -O2
dependencies:
- doctest
- Glob
- QuickCheck
- {{ name }}
benchmarks:
{{ name }}-benchmark:
main: Main.hs
source-dirs: benchmark
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
- -O2
dependencies:
- criterion
- {{ name }}
stability: alpha (experimental)
{-# START_FILE src/Lib.hs #-}
{-# LANGUAGE NoImplicitPrelude #-}
-- | Example of a library file. It is also used for testing the test suites.
module Lib
(
-- * Exported functions
inc
) where
import Protolude
-- | Increment one 'Num' value.
--
-- >>> let answer = 42 :: Int
-- >>> let prev = answer - 1
-- >>> inc prev
-- 42
-- >>> succ . Prelude.last . Prelude.take prev . iterate inc $ 1
-- 42
--
-- Properties:
--
-- prop> succ x == inc x
-- prop> inc (negate x) == negate (pred x)
--
inc :: Num a => a -- ^ value to increment
-> a -- ^ result
inc x = x + 1
{-# START_FILE Setup.hs #-}
import Distribution.Simple
main = defaultMain
{-# START_FILE test/testsuite/Main.hs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Main where
import Protolude
import Test.Tasty (TestTree, defaultMainWithIngredients, testGroup)
import Test.Tasty.HUnit (Assertion, (@?=), testCase)
import Test.Tasty.SmallCheck (testProperty)
import Test.Tasty.Runners (listingTests, consoleTestReporter)
import Test.Tasty.Ingredients.Rerun (rerunningTests)
import Lib (inc)
main :: IO ()
main =
defaultMainWithIngredients
[ rerunningTests [listingTests, consoleTestReporter] ]
(testGroup "all-tests" tests)
tests :: [TestTree]
tests =
[ testGroup "SmallCheck" scTests
, testGroup "Unit tests" huTests
]
scTests :: [TestTree]
scTests =
[ testProperty "inc == succ" prop_succ
, testProperty "inc . negate == negate . pred" prop_pred
]
huTests :: [TestTree]
huTests =
[ testCase "Increment below TheAnswer" case_inc_below
, testCase "Decrement above TheAnswer" case_dec_above
]
prop_succ :: Int -> Bool
prop_succ n = inc n == succ n
prop_pred :: Int -> Bool
prop_pred n = inc (negate n) == negate (pred n)
case_inc_below :: Assertion
case_inc_below = inc 41 @?= (42 :: Int)
case_dec_above :: Assertion
case_dec_above = negate (inc (negate 43)) @?= (42 :: Int)
{-# START_FILE test/doctest/Main.hs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Main where
import Protolude
import System.FilePath.Glob (glob)
import Test.DocTest (doctest)
main :: IO ()
main = glob "src/**/*.hs" >>= doctest
{-# START_FILE benchmark/Main.hs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Main where
import Protolude
import Criterion
import Criterion.Main
import Lib (inc)
main :: IO ()
main = defaultMain [bench "inc 41" (whnf inc (41 :: Int))]
{-# START_FILE LICENSE #-}
Copyright (c) {{year}}{{^year}}2017{{/year}}, {{author-name}}{{^author-name}}TODO:<Author name here>{{/author-name}}
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
{-# START_FILE CHANGELOG.md #-}
# Change log
{{ name }} uses [Semantic Versioning][1].
The change log is available [on GitHub][2].
[1]: http://semver.org/spec/v2.0.0.html
[2]: https://github.com/{{github-username}}{{^github-username}}githubuser{{/github-username}}/{{ name }}/releases
## v0.0.0.1
* Initially created.
{-# START_FILE examples/{{name}}-example/README.md #-}
# {{name}}-example
This is an example that showcases how the `{{name}}` API works
For more information about `{{name}}` API and how to use it, see
the [`{{name}}` homepage](https://github.com/{{github-username}}/Haskell-{{name}}).
{-# START_FILE examples/{{name}}-example/package.yaml #-}
name: {{ name }}-example
version: '0.0.0.1'
category: TODO
author: {{author-name}}{{^author-name}}TODO:<Author name here>{{/author-name}}
maintainer: {{author-email}}{{^author-email}}TODO:<[email protected]>{{/author-email}}
copyright: © {{year}}{{^year}}2017{{/year}} {{author-name}}{{^author-name}}TODO:<Author name here>{{/author-name}}
license: MIT
github: {{github-username}}/Haskell-{{name}}
extra-source-files:
- README.md
executables:
{{name}}-example:
main: Main.hs
source-dirs: src
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
- -O2
dependencies:
- base
- protolude
- {{name}}
{-# START_FILE examples/{{name}}-example/Setup.hs #-}
import Distribution.Simple
main = defaultMain
{-# START_FILE examples/{{name}}-example/src/Main.hs #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Main where
import Protolude
main :: IO ()
main = return ()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment