diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..0356765
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,18 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = space
+max_line_length = 120
+
+[**.{yml,yaml,md}]
+max_line_length = 160
+indent_size = 2
+
+[.github/actions/spelling/*]
+max_line_length = unset
+
+[**.cabal]
+max_line_length = unset
diff --git a/.github/actions/spelling/README.md b/.github/actions/spelling/README.md
new file mode 100644
index 0000000..cc187a5
--- /dev/null
+++ b/.github/actions/spelling/README.md
@@ -0,0 +1,17 @@
+# check-spelling/check-spelling configuration
+
+File | Purpose | Format | Info
+-|-|-|-
+dictionary.txt | Replacement dictionary (creating this file will override the default dictionary) | one word per line | [dictionary](https://github.com/check-spelling/check-spelling/wiki/Configuration#dictionary)
+[allow.txt](allow.txt) | Add words to the dictionary | one word per line (only letters and `'`s allowed) | [allow](https://github.com/check-spelling/check-spelling/wiki/Configuration#allow)
+[reject.txt](reject.txt) | Remove words from the dictionary (after allow) | grep pattern matching whole dictionary words | [reject](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-reject)
+[excludes.txt](excludes.txt) | Files to ignore entirely | perl regular expression | [excludes](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-excludes)
+only.txt | Only check matching files (applied after excludes) | perl regular expression | [only](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-only)
+[patterns.txt](patterns.txt) | Patterns to ignore from checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns)
+[candidate.patterns](candidate.patterns) | Patterns that might be worth adding to [patterns.txt](patterns.txt) | perl regular expression with optional comment block introductions (all matches will be suggested) | [candidates](https://github.com/check-spelling/check-spelling/wiki/Feature:-Suggest-patterns)
+[line_forbidden.patterns](line_forbidden.patterns) | Patterns to flag in checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns)
+[expect.txt](expect.txt) | Expected words that aren't in the dictionary | one word per line (sorted, alphabetically) | [expect](https://github.com/check-spelling/check-spelling/wiki/Configuration#expect)
+[advice.md](advice.md) | Supplement for GitHub comment when unrecognized words are found | GitHub Markdown | [advice](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice)
+
+Note: you can replace any of these files with a directory by the same name (minus the suffix)
+and then include multiple files inside that directory (with that suffix) to merge multiple files together.
diff --git a/.github/actions/spelling/advice.md b/.github/actions/spelling/advice.md
new file mode 100644
index 0000000..e5292bc
--- /dev/null
+++ b/.github/actions/spelling/advice.md
@@ -0,0 +1,22 @@
+
+If the flagged items are false positives
+
+If items relate to a ...
+* binary file (or some other file you wouldn't want to check at all).
+
+ Please add a file path to the `excludes.txt` file matching the containing file.
+
+ File paths are Perl 5 Regular Expressions - you can [test](https://www.regexplanet.com/advanced/perl) yours before committing to verify it will match your files.
+
+ `^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude README.md (on whichever branch you're using).
+
+* well-formed pattern.
+
+ If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it,
+ try adding it to the `patterns.txt` file.
+
+ Patterns are Perl 5 Regular Expressions - you can [test](https://www.regexplanet.com/advanced/perl) yours before committing to verify it will match your lines.
+
+ Note that patterns can't match multiline strings.
+
+
diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt
new file mode 100644
index 0000000..157728f
--- /dev/null
+++ b/.github/actions/spelling/allow.txt
@@ -0,0 +1,26 @@
+autotool
+Bifunctor
+concat
+elems
+finalise
+fmap
+fmidue
+foldl
+foldr
+fst
+ghc
+GHC
+ghci
+github
+hlint
+Hspec
+HUnit
+mempty
+mplus
+msum
+mzero
+snd
+thd
+uncurry
+unlines
+yml
diff --git a/.github/actions/spelling/candidate.patterns b/.github/actions/spelling/candidate.patterns
new file mode 100644
index 0000000..340922d
--- /dev/null
+++ b/.github/actions/spelling/candidate.patterns
@@ -0,0 +1,527 @@
+# marker to ignore all code on line
+^.*/\* #no-spell-check-line \*/.*$
+# marker to ignore all code on line
+^.*\bno-spell-check(?:-line|)(?:\s.*|)$
+
+# https://cspell.org/configuration/document-settings/
+# cspell inline
+^.*\b[Cc][Ss][Pp][Ee][Ll]{2}:\s*[Dd][Ii][Ss][Aa][Bb][Ll][Ee]-[Ll][Ii][Nn][Ee]\b
+
+# patch hunk comments
+^\@\@ -\d+(?:,\d+|) \+\d+(?:,\d+|) \@\@ .*
+# git index header
+index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40}
+
+# cid urls
+(['"])cid:.*?\g{-1}
+
+# data url in parens
+\(data:[^)]*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\)
+# data url in quotes
+([`'"])data:.*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1}
+# data url
+data:[-a-zA-Z=;:/0-9+]*,\S*
+
+# https/http/file urls
+(?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]
+
+# mailto urls
+mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,}
+
+# magnet urls
+magnet:[?=:\w]+
+
+# magnet urls
+"magnet:[^"]+"
+
+# obs:
+"obs:[^"]*"
+
+# The `\b` here means a break, it's the fancy way to handle urls, but it makes things harder to read
+# In this examples content, I'm using a number of different ways to match things to show various approaches
+# asciinema
+\basciinema\.org/a/[0-9a-zA-Z]+
+
+# apple
+\bdeveloper\.apple\.com/[-\w?=/]+
+# Apple music
+\bembed\.music\.apple\.com/fr/playlist/usr-share/[-\w.]+
+
+# appveyor api
+\bci\.appveyor\.com/api/projects/status/[0-9a-z]+
+# appveyor project
+\bci\.appveyor\.com/project/(?:[^/\s"]*/){2}builds?/\d+/job/[0-9a-z]+
+
+# Amazon
+
+# Amazon
+\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|)
+# AWS S3
+\b\w*\.s3[^.]*\.amazonaws\.com/[-\w/%_?:=]*
+# AWS execute-api
+\b[0-9a-z]{10}\.execute-api\.[-0-9a-z]+\.amazonaws\.com\b
+# AWS ELB
+\b\w+\.[-0-9a-z]+\.elb\.amazonaws\.com\b
+# AWS SNS
+\bsns\.[-0-9a-z]+.amazonaws\.com/[-\w/%_?:=]*
+# AWS VPC
+vpc-\w+
+
+# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there
+# YouTube url
+\b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]*
+# YouTube music
+\bmusic\.youtube\.com/youtubei/v1/browse(?:[?&]\w+=[-a-zA-Z0-9?&=_]*)
+# YouTube tag
+<\s*youtube\s+id=['"][-a-zA-Z0-9?_]*['"]
+# YouTube image
+\bimg\.youtube\.com/vi/[-a-zA-Z0-9?&=_]*
+# Google Accounts
+\baccounts.google.com/[-_/?=.:;+%&0-9a-zA-Z]*
+# Google Analytics
+\bgoogle-analytics\.com/collect.[-0-9a-zA-Z?%=&_.~]*
+# Google APIs
+\bgoogleapis\.(?:com|dev)/[a-z]+/(?:v\d+/|)[a-z]+/[-@:./?=\w+|&]+
+# Google Storage
+\b[-a-zA-Z0-9.]*\bstorage\d*\.googleapis\.com(?:/\S*|)
+# Google Calendar
+\bcalendar\.google\.com/calendar(?:/u/\d+|)/embed\?src=[@./?=\w&%]+
+\w+\@group\.calendar\.google\.com\b
+# Google DataStudio
+\bdatastudio\.google\.com/(?:(?:c/|)u/\d+/|)(?:embed/|)(?:open|reporting|datasources|s)/[-0-9a-zA-Z]+(?:/page/[-0-9a-zA-Z]+|)
+# The leading `/` here is as opposed to the `\b` above
+# ... a short way to match `https://` or `http://` since most urls have one of those prefixes
+# Google Docs
+/docs\.google\.com/[a-z]+/(?:ccc\?key=\w+|(?:u/\d+|d/(?:e/|)[0-9a-zA-Z_-]+/)?(?:edit\?[-\w=#.]*|/\?[\w=&]*|))
+# Google Drive
+\bdrive\.google\.com/(?:file/d/|open)[-0-9a-zA-Z_?=]*
+# Google Groups
+\bgroups\.google\.com/(?:(?:forum/#!|d/)(?:msg|topics?|searchin)|a)/[^/\s"]+/[-a-zA-Z0-9$]+(?:/[-a-zA-Z0-9]+)*
+# Google Maps
+\bmaps\.google\.com/maps\?[\w&;=]*
+# Google themes
+themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+.
+# Google CDN
+\bclients2\.google(?:usercontent|)\.com[-0-9a-zA-Z/.]*
+# Goo.gl
+/goo\.gl/[a-zA-Z0-9]+
+# Google Chrome Store
+\bchrome\.google\.com/webstore/detail/[-\w]*(?:/\w*|)
+# Google Books
+\bgoogle\.(?:\w{2,4})/books(?:/\w+)*\?[-\w\d=.]*
+# Google Fonts
+\bfonts\.(?:googleapis|gstatic)\.com/[-/?=:;+&0-9a-zA-Z]*
+# Google Forms
+\bforms\.gle/\w+
+# Google Scholar
+\bscholar\.google\.com/citations\?user=[A-Za-z0-9_]+
+# Google Colab Research Drive
+\bcolab\.research\.google\.com/drive/[-0-9a-zA-Z_?=]*
+
+# GitHub SHAs (api)
+\bapi.github\.com/repos(?:/[^/\s"]+){3}/[0-9a-f]+\b
+# GitHub SHAs (markdown)
+(?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|)
+# GitHub SHAs
+\bgithub\.com(?:/[^/\s"]+){2}[@#][0-9a-f]+\b
+# GitHub wiki
+\bgithub\.com/(?:[^/]+/){2}wiki/(?:(?:[^/]+/|)_history|[^/]+(?:/_compare|)/[0-9a-f.]{40,})\b
+# githubusercontent
+/[-a-z0-9]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]*
+# githubassets
+\bgithubassets.com/[0-9a-f]+(?:[-/\w.]+)
+# gist github
+\bgist\.github\.com/[^/\s"]+/[0-9a-f]+
+# git.io
+\bgit\.io/[0-9a-zA-Z]+
+# GitHub JSON
+"node_id": "[-a-zA-Z=;:/0-9+]*"
+# Contributor
+\[[^\]]+\]\(https://github\.com/[^/\s"]+\)
+# GHSA
+GHSA(?:-[0-9a-z]{4}){3}
+
+# GitLab commit
+\bgitlab\.[^/\s"]*/\S+/\S+/commit/[0-9a-f]{7,16}#[0-9a-f]{40}\b
+# GitLab merge requests
+\bgitlab\.[^/\s"]*/\S+/\S+/-/merge_requests/\d+/diffs#[0-9a-f]{40}\b
+# GitLab uploads
+\bgitlab\.[^/\s"]*/uploads/[-a-zA-Z=;:/0-9+]*
+# GitLab commits
+\bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b
+
+# binanace
+accounts.binance.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*
+
+# bitbucket diff
+\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}diff(?:stat|)(?:/[^/\s"]+){2}:[0-9a-f]+
+# bitbucket repositories commits
+\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}commits?/[0-9a-f]+
+# bitbucket commits
+\bbitbucket\.org/(?:[^/\s"]+/){2}commits?/[0-9a-f]+
+
+# bit.ly
+\bbit\.ly/\w+
+
+# bitrise
+\bapp\.bitrise\.io/app/[0-9a-f]*/[\w.?=&]*
+
+# bootstrapcdn.com
+\bbootstrapcdn\.com/[-./\w]+
+
+# cdn.cloudflare.com
+\bcdnjs\.cloudflare\.com/[./\w]+
+
+# circleci
+\bcircleci\.com/gh(?:/[^/\s"]+){1,5}.[a-z]+\?[-0-9a-zA-Z=&]+
+
+# gitter
+\bgitter\.im(?:/[^/\s"]+){2}\?at=[0-9a-f]+
+
+# gravatar
+\bgravatar\.com/avatar/[0-9a-f]+
+
+# ibm
+[a-z.]*ibm\.com/[-_#=:%!?~.\\/\d\w]*
+
+# imgur
+\bimgur\.com/[^.]+
+
+# Internet Archive
+\barchive\.org/web/\d+/(?:[-\w.?,'/\\+&%$#_:]*)
+
+# discord
+/discord(?:app\.com|\.gg)/(?:invite/)?[a-zA-Z0-9]{7,}
+
+# Disqus
+\bdisqus\.com/[-\w/%.()!?&=_]*
+
+# medium link
+\blink\.medium\.com/[a-zA-Z0-9]+
+# medium
+\bmedium\.com/\@?[^/\s"]+/[-\w]+
+
+# microsoft
+\b(?:https?://|)(?:(?:download\.visualstudio|docs|msdn2?|research)\.microsoft|blogs\.msdn)\.com/[-_a-zA-Z0-9()=./%]*
+# powerbi
+\bapp\.powerbi\.com/reportEmbed/[^"' ]*
+# vs devops
+\bvisualstudio.com(?::443|)/[-\w/?=%&.]*
+# microsoft store
+\bmicrosoft\.com/store/apps/\w+
+
+# mvnrepository.com
+\bmvnrepository\.com/[-0-9a-z./]+
+
+# now.sh
+/[0-9a-z-.]+\.now\.sh\b
+
+# oracle
+\bdocs\.oracle\.com/[-0-9a-zA-Z./_?#&=]*
+
+# chromatic.com
+/\S+.chromatic.com\S*[")]
+
+# codacy
+\bapi\.codacy\.com/project/badge/Grade/[0-9a-f]+
+
+# compai
+\bcompai\.pub/v1/png/[0-9a-f]+
+
+# mailgun api
+\.api\.mailgun\.net/v3/domains/[0-9a-z]+\.mailgun.org/messages/[0-9a-zA-Z=@]*
+# mailgun
+\b[0-9a-z]+.mailgun.org
+
+# /message-id/
+/message-id/[-\w@./%]+
+
+# Reddit
+\breddit\.com/r/[/\w_]*
+
+# requestb.in
+\brequestb\.in/[0-9a-z]+
+
+# sched
+\b[a-z0-9]+\.sched\.com\b
+
+# Slack url
+slack://[a-zA-Z0-9?&=]+
+# Slack
+\bslack\.com/[-0-9a-zA-Z/_~?&=.]*
+# Slack edge
+\bslack-edge\.com/[-a-zA-Z0-9?&=%./]+
+# Slack images
+\bslack-imgs\.com/[-a-zA-Z0-9?&=%.]+
+
+# shields.io
+\bshields\.io/[-\w/%?=&.:+;,]*
+
+# stackexchange -- https://stackexchange.com/feeds/sites
+\b(?:askubuntu|serverfault|stack(?:exchange|overflow)|superuser).com/(?:questions/\w+/[-\w]+|a/)
+
+# Sentry
+[0-9a-f]{32}\@o\d+\.ingest\.sentry\.io\b
+
+# Twitter markdown
+\[\@[^[/\]:]*?\]\(https://twitter.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)\)
+# Twitter hashtag
+\btwitter\.com/hashtag/[\w?_=&]*
+# Twitter status
+\btwitter\.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)
+# Twitter profile images
+\btwimg\.com/profile_images/[_\w./]*
+# Twitter media
+\btwimg\.com/media/[-_\w./?=]*
+# Twitter link shortened
+\bt\.co/\w+
+
+# facebook
+\bfburl\.com/[0-9a-z_]+
+# facebook CDN
+\bfbcdn\.net/[\w/.,]*
+# facebook watch
+\bfb\.watch/[0-9A-Za-z]+
+
+# dropbox
+\bdropbox\.com/sh?/[^/\s"]+/[-0-9A-Za-z_.%?=&;]+
+
+# ipfs protocol
+ipfs://[0-9a-z]*
+# ipfs url
+/ipfs/[0-9a-z]*
+
+# w3
+\bw3\.org/[-0-9a-zA-Z/#.]+
+
+# loom
+\bloom\.com/embed/[0-9a-f]+
+
+# regex101
+\bregex101\.com/r/[^/\s"]+/\d+
+
+# figma
+\bfigma\.com/file(?:/[0-9a-zA-Z]+/)+
+
+# freecodecamp.org
+\bfreecodecamp\.org/[-\w/.]+
+
+# image.tmdb.org
+\bimage\.tmdb\.org/[/\w.]+
+
+# mermaid
+\bmermaid\.ink/img/[-\w]+|\bmermaid-js\.github\.io/mermaid-live-editor/#/edit/[-\w]+
+
+# Wikipedia
+\ben\.wikipedia\.org/wiki/[-\w%.#]+
+
+# gitweb
+[^"\s]+/gitweb/\S+;h=[0-9a-f]+
+
+# HyperKitty lists
+/archives/list/[^@/]+\@[^/\s"]*/message/[^/\s"]*/
+
+# lists
+/thread\.html/[^"\s]+
+
+# list-management
+\blist-manage\.com/subscribe(?:[?&](?:u|id)=[0-9a-f]+)+
+
+# kubectl.kubernetes.io/last-applied-configuration
+"kubectl.kubernetes.io/last-applied-configuration": ".*"
+
+# pgp
+\bgnupg\.net/pks/lookup[?&=0-9a-zA-Z]*
+
+# Spotify
+\bopen\.spotify\.com/embed/playlist/\w+
+
+# Mastodon
+\bmastodon\.[-a-z.]*/(?:media/|\@)[?&=0-9a-zA-Z_]*
+
+# scastie
+\bscastie\.scala-lang\.org/[^/]+/\w+
+
+# images.unsplash.com
+\bimages\.unsplash\.com/(?:(?:flagged|reserve)/|)[-\w./%?=%&.;]+
+
+# pastebin
+\bpastebin\.com/[\w/]+
+
+# heroku
+\b\w+\.heroku\.com/source/archive/\w+
+
+# quip
+\b\w+\.quip\.com/\w+(?:(?:#|/issues/)\w+)?
+
+# badgen.net
+\bbadgen\.net/badge/[^")\]'\s]+
+
+# statuspage.io
+\w+\.statuspage\.io\b
+
+# media.giphy.com
+\bmedia\.giphy\.com/media/[^/]+/[\w.?&=]+
+
+# tinyurl
+\btinyurl\.com/\w+
+
+# getopts
+\bgetopts\s+(?:"[^"]+"|'[^']+')
+
+# ANSI color codes
+(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m
+
+# URL escaped characters
+\%[0-9A-F][A-F]
+# IPv6
+\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b
+# c99 hex digits (not the full format, just one I've seen)
+0x[0-9a-fA-F](?:\.[0-9a-fA-F]*|)[pP]
+# Punycode
+\bxn--[-0-9a-z]+
+# sha
+sha\d+:[0-9]*[a-f]{3,}[0-9a-f]*
+# sha-... -- uses a fancy capture
+(['"]|")[0-9a-f]{40,}\g{-1}
+# hex runs
+\b[0-9a-fA-F]{16,}\b
+# hex in url queries
+=[0-9a-fA-F]*?(?:[A-F]{3,}|[a-f]{3,})[0-9a-fA-F]*?&
+# ssh
+(?:ssh-\S+|-nistp256) [-a-zA-Z=;:/0-9+]{12,}
+
+# PGP
+\b(?:[0-9A-F]{4} ){9}[0-9A-F]{4}\b
+# GPG keys
+\b(?:[0-9A-F]{4} ){5}(?: [0-9A-F]{4}){5}\b
+# Well known gpg keys
+.well-known/openpgpkey/[\w./]+
+
+# uuid:
+\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
+# hex digits including css/html color classes:
+(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|u\d+)\b
+# integrity
+integrity="sha\d+-[-a-zA-Z=;:/0-9+]{40,}"
+
+# https://www.gnu.org/software/groff/manual/groff.html
+# man troff content
+\\f[BCIPR]
+# '
+\\\(aq
+
+# .desktop mime types
+^MimeTypes?=.*$
+# .desktop localized entries
+^[A-Z][a-z]+\[[a-z]+\]=.*$
+# Localized .desktop content
+Name\[[^\]]+\]=.*
+
+# IServiceProvider
+\bI(?=(?:[A-Z][a-z]{2,})+\b)
+
+# crypt
+"\$2[ayb]\$.{56}"
+
+# scrypt / argon
+\$(?:scrypt|argon\d+[di]*)\$\S+
+
+# Input to GitHub JSON
+content: "[-a-zA-Z=;:/0-9+]*="
+
+# Python stringprefix / binaryprefix
+# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings
+(?v#
+(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
+# Compiler flags (Scala)
+(?:^|[\t ,>"'`=(])-J-[DPWXY](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
+# Compiler flags
+(?:^|[\t ,"'`=(])-[DPWXYLlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
+# Compiler flags (linker)
+,-B
+# curl arguments
+\b(?:\\n|)curl(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
+# set arguments
+\bset(?:\s+-[abefimouxE]{1,2})*\s+-[abefimouxE]{3,}(?:\s+-[abefimouxE]+)*
+# tar arguments
+\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
+# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long...
+\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b
+# macOS temp folders
+/var/folders/\w\w/[+\w]+/(?:T|-Caches-)/
diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt
new file mode 100644
index 0000000..dc86085
--- /dev/null
+++ b/.github/actions/spelling/excludes.txt
@@ -0,0 +1,68 @@
+# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-excludes
+(?:^|/)(?i)COPYRIGHT
+(?:^|/)(?i)LICEN[CS]E
+(?:^|/)3rdparty/
+(?:^|/)go\.sum$
+(?:^|/)package(?:-lock|)\.json$
+(?:^|/)pyproject.toml
+(?:^|/)requirements(?:-dev|-doc|-test|)\.txt$
+(?:^|/)vendor/
+ignore$
+\.a$
+\.ai$
+\.avi$
+\.bmp$
+\.bz2$
+\.cabal$
+\.class$
+\.coveragerc$
+\.crt$
+\.dll$
+\.docx?$
+\.drawio$
+\.DS_Store$
+\.eot$
+\.exe$
+\.gif$
+\.git-blame-ignore-revs$
+\.gitattributes$
+\.graffle$
+\.gz$
+\.icns$
+\.ico$
+\.jar$
+\.jks$
+\.jpe?g$
+\.key$
+\.lib$
+\.lock$
+\.map$
+\.min\..
+\.mod$
+\.mp[34]$
+\.o$
+\.ocf$
+\.otf$
+\.pdf$
+\.pem$
+\.png$
+\.psd$
+\.pyc$
+\.pylintrc$
+\.s$
+\.svg$
+\.svgz?$
+\.tar$
+\.tiff?$
+\.ttf$
+\.wav$
+\.webm$
+\.webp$
+\.woff2?$
+\.xlsx?$
+\.yaml$
+\.yml$
+\.zip$
+^\.github/actions/spelling/
+^\.github/linters/
+^\Qtest/Spec.hs\E$
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
new file mode 100644
index 0000000..d4e17de
--- /dev/null
+++ b/.github/actions/spelling/expect.txt
@@ -0,0 +1,22 @@
+ekat
+exts
+deepseq
+Felgenhauer
+FSolution
+gfoldl
+gmap
+IOrep
+KPatterns
+Leijen
+lhs
+listify
+NPlus
+perkid
+Programmierung
+PVar
+quickcheck
+rhs
+ssi
+testables
+vcat
+werror
diff --git a/.github/actions/spelling/line_forbidden.patterns b/.github/actions/spelling/line_forbidden.patterns
new file mode 100644
index 0000000..bf7c45d
--- /dev/null
+++ b/.github/actions/spelling/line_forbidden.patterns
@@ -0,0 +1,59 @@
+# reject `m_data` as there's a certain OS which has evil defines that break things if it's used elsewhere
+# \bm_data\b
+
+# If you have a framework that uses `it()` for testing and `fit()` for debugging a specific test,
+# you might not want to check in code where you were debugging w/ `fit()`, in which case, you might want
+# to use this:
+#\bfit\(
+
+# s.b. GitHub
+\bGithub\b
+
+# s.b. GitLab
+\bGitlab\b
+
+# s.b. Microsoft
+\bMicroSoft\b
+
+# s.b. another
+\ban[- ]other\b
+
+# s.b. greater than
+\bgreater then\b
+
+# s.b. into
+\sin to\s
+
+# s.b. opt-in
+\sopt in\s
+
+# s.b. less than
+\bless then\b
+
+# s.b. otherwise
+\bother[- ]wise\b
+
+# s.b. nonexistent
+\bnon existing\b
+\b[Nn]o[nt][- ]existent\b
+
+# s.b. preexisting
+[Pp]re[- ]existing
+
+# s.b. preempt
+[Pp]re[- ]empt\b
+
+# s.b. preemptively
+[Pp]re[- ]emptively
+
+# s.b. reentrancy
+[Rr]e[- ]entrancy
+
+# s.b. reentrant
+[Rr]e[- ]entrant
+
+# s.b. workaround(s)
+\bwork[- ]arounds?\b
+
+# Reject duplicate words
+\s([A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})\s\g{-1}\s
diff --git a/.github/actions/spelling/patterns.txt b/.github/actions/spelling/patterns.txt
new file mode 100644
index 0000000..b7106d6
--- /dev/null
+++ b/.github/actions/spelling/patterns.txt
@@ -0,0 +1,41 @@
+# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns
+
+# Questionably acceptable forms of `in to`
+# Personally, I prefer `log into`, but people object
+# https://www.tprteaching.com/log-into-log-in-to-login/
+\b[Ll]og in to\b
+
+# acceptable duplicates
+
+# ls directory listings
+[-bcdlpsw](?:[-r][-w][-Ssx]){3}\s+\d+\s+\S+\s+\S+\s+\d+\s+
+
+# C types and repeated CSS values
+\s(center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?: \g{-1})+\s
+
+# go templates
+\s(\w+)\s+\g{-1}\s+\`(?:graphql|json|yaml):
+
+# javadoc / .net
+(?:[\\@](?:groupname|param)|(?:public|private)(?:\s+static|\s+readonly)*)\s+(\w+)\s+\g{-1}\s
+
+# Commit message -- Signed-off-by and friends
+^\s*(?:(?:Based-on-patch|Co-authored|Helped|Mentored|Reported|Reviewed|Signed-off)-by|Thanks-to): (?:[^<]*<[^>]*>|[^<]*)\s*$
+
+# Autogenerated revert commit message
+^This reverts commit [0-9a-f]{40}\.$
+
+# GitHub SHAs (markdown)
+\bcommit: [0-9a-f]*\b
+
+# ignore long runs of a single character:
+\b([A-Za-z])\g{-1}{3,}\b
+
+# GHC LANGUAGE extension, HLINT options etc.
+\{-# .* #-\}
+
+# ignore urls
+https?://[-+0-9a-zA-Z?&=_\/%.]*
+
+# marker to ignore all code on line
+^.*-- no-spell-check$
diff --git a/.github/actions/spelling/reject.txt b/.github/actions/spelling/reject.txt
new file mode 100644
index 0000000..b5a6d36
--- /dev/null
+++ b/.github/actions/spelling/reject.txt
@@ -0,0 +1,10 @@
+^attache$
+benefitting
+occurences?
+^dependan.*
+^oer$
+Sorce
+^[Ss]pae.*
+^untill$
+^untilling$
+^wether.*
diff --git a/.github/linters/.markdown-lint.yml b/.github/linters/.markdown-lint.yml
new file mode 100644
index 0000000..a03953b
--- /dev/null
+++ b/.github/linters/.markdown-lint.yml
@@ -0,0 +1,26 @@
+---
+default: true
+extends: null
+
+# Unordered list indentation
+MD007:
+ # Spaces for indent
+ indent: 4
+ # Whether to indent the first level of the list
+ start_indented: false
+ # Spaces for first level indent (when start_indented is set)
+ start_indent: 2
+
+# Multiple consecutive blank lines
+MD012:
+ # Consecutive blank lines
+ maximum: 2
+
+# Line length
+MD013: false
+
+# Lists should be surrounded by blank lines
+MD032: false
+
+# Disable ASCII table check
+MD060: false
diff --git a/.github/linters/.yaml-lint.yml b/.github/linters/.yaml-lint.yml
new file mode 100644
index 0000000..8b01185
--- /dev/null
+++ b/.github/linters/.yaml-lint.yml
@@ -0,0 +1,59 @@
+---
+###########################################
+# These are the rules used for #
+# linting all the yaml files in the stack #
+# NOTE: #
+# You can disable line with: #
+# # yamllint disable-line #
+###########################################
+rules:
+ braces:
+ level: warning
+ min-spaces-inside: 0
+ max-spaces-inside: 0
+ min-spaces-inside-empty: 1
+ max-spaces-inside-empty: 5
+ brackets:
+ level: warning
+ min-spaces-inside: 0
+ max-spaces-inside: 0
+ min-spaces-inside-empty: 1
+ max-spaces-inside-empty: 5
+ colons:
+ level: warning
+ max-spaces-before: 0
+ max-spaces-after: 1
+ commas:
+ level: warning
+ max-spaces-before: 0
+ min-spaces-after: 1
+ max-spaces-after: 1
+ comments: disable
+ comments-indentation: disable
+ document-end: disable
+ document-start:
+ level: warning
+ present: true
+ empty-lines:
+ level: warning
+ max: 2
+ max-start: 0
+ max-end: 0
+ hyphens:
+ level: warning
+ max-spaces-after: 1
+ indentation:
+ level: warning
+ spaces: consistent
+ indent-sequences: true
+ check-multi-line-strings: false
+ key-duplicates: enable
+ line-length:
+ level: warning
+ max: 140
+ allow-non-breakable-words: true
+ allow-non-breakable-inline-mappings: true
+ new-line-at-end-of-file: disable
+ new-lines:
+ type: unix
+ trailing-spaces: disable
diff --git a/.github/linters/zizmor.yaml b/.github/linters/zizmor.yaml
new file mode 100644
index 0000000..bbb0366
--- /dev/null
+++ b/.github/linters/zizmor.yaml
@@ -0,0 +1,6 @@
+---
+rules:
+ unpinned-uses:
+ config:
+ policies:
+ "*": ref-pin
diff --git a/.github/workflows/haskell.yml b/.github/workflows/haskell.yml
new file mode 100644
index 0000000..61a971f
--- /dev/null
+++ b/.github/workflows/haskell.yml
@@ -0,0 +1,59 @@
+---
+name: Haskell CI
+
+on:
+ push:
+ branches: ["master"]
+ pull_request:
+ branches: ["master"]
+
+permissions: read-all
+
+jobs:
+ build_and_test:
+ strategy:
+ matrix:
+ os:
+ - ubuntu-latest
+ plan:
+ - {build: stack}
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - name: Cache Stack
+ id: cache-stack-unix
+ uses: actions/cache@v4
+ with:
+ path: '~/.stack'
+ key: ${{ matrix.os }}-${{ matrix.plan.build }}-stack-home-${{ hashFiles('**/stack.yaml') }}-${{ hashFiles('**/package.yaml') }}
+
+ - name: Setup stack
+ uses: haskell-actions/setup@v2
+ with:
+ enable-stack: true
+ stack-no-global: true
+
+ - name: Install dependencies
+ run: |
+ set -ex
+ stack --no-terminal --install-ghc build --test --bench --only-dependencies
+ cd raw/embedded
+ stack build --no-terminal --install-ghc build --test --bench --only-dependencies
+ set +ex
+ env:
+ BUILD: ${{ matrix.plan.build }}
+
+ - name: Build and test
+ run: |
+ set -ex
+ stack --no-terminal test --coverage --bench --no-run-benchmarks --haddock --no-haddock-deps
+ cd raw/embedded
+ stack --no-terminal test --coverage --bench --no-run-benchmarks --haddock --no-haddock-deps
+ set +ex
+ env:
+ BUILD: ${{ matrix.plan.build }}
diff --git a/.github/workflows/hlint.yml b/.github/workflows/hlint.yml
new file mode 100644
index 0000000..ff9a88e
--- /dev/null
+++ b/.github/workflows/hlint.yml
@@ -0,0 +1,29 @@
+---
+name: Haskell Linter
+
+on:
+ push:
+ branches: ["master"]
+ pull_request:
+ branches: ["master"]
+
+permissions: read-all
+
+jobs:
+ run:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - name: 'Set up HLint'
+ uses: haskell-actions/hlint-setup@v2
+ with:
+ version: '3.8'
+
+ - name: 'Run HLint'
+ uses: haskell-actions/hlint-run@v2
+ with:
+ path: '["src/", "raw/src/", "raw/embedded/src/"]'
+ fail-on: suggestion
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
new file mode 100644
index 0000000..73251df
--- /dev/null
+++ b/.github/workflows/linter.yml
@@ -0,0 +1,37 @@
+---
+name: Super-Linter
+
+on:
+ push:
+ branches: ["master"]
+ pull_request:
+ branches: ["master"]
+
+permissions: read-all
+
+jobs:
+ build:
+ name: Lint Code Base
+ runs-on: ubuntu-latest
+
+ permissions:
+ contents: read
+ statuses: write
+
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout@v4
+ with:
+ # Full git history is needed to get a proper
+ # list of changed files within `super-linter`
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Run Linter
+ uses: super-linter/super-linter/slim@v8.3.2
+ env:
+ YAML_ERROR_ON_WARNING: true
+ GITHUB_ACTIONS_COMMAND_ARGS: -shellcheck=
+ VALIDATE_MARKDOWN_PRETTIER: false
+ VALIDATE_YAML_PRETTIER: false
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml
new file mode 100644
index 0000000..b275bd4
--- /dev/null
+++ b/.github/workflows/spelling.yml
@@ -0,0 +1,82 @@
+---
+name: Check Spelling
+
+on:
+ push:
+ branches: ["master"]
+ pull_request:
+ branches: ['**']
+ issue_comment:
+ types: [created]
+
+permissions: read-all
+
+jobs:
+ spelling:
+ name: Check Spelling
+ permissions:
+ contents: read
+ pull-requests: read
+ actions: read
+ outputs:
+ followup: ${{ steps.spelling.outputs.followup }}
+ runs-on: ubuntu-latest
+ if: "contains(github.event_name, 'pull_request') || github.event_name == 'push'"
+ concurrency:
+ group: spelling-${{ github.event.pull_request.number || github.ref }}
+ # note: If you use only_check_changed_files, you do not want cancel-in-progress
+ cancel-in-progress: true
+ steps:
+ - name: check-spelling
+ id: spelling
+ uses: check-spelling/check-spelling@v0.0.25
+ with:
+ suppress_push_for_open_pull_request: 1
+ checkout: true
+ spell_check_this: check-spelling/spell-check-this@v0.0.25
+ post_comment: 0
+ experimental_apply_changes_via_bot: 1
+ extra_dictionaries:
+ cspell:aws/aws.txt
+ cspell:haskell/dict/haskell.txt
+ cspell:software-terms/dict/softwareTerms.txt
+ cspell:filetypes/filetypes.txt
+
+ comment:
+ name: Report
+ runs-on: ubuntu-latest
+ needs: spelling
+ permissions:
+ contents: write
+ pull-requests: write
+ if: (success() || failure()) && needs.spelling.outputs.followup
+ steps:
+ - name: comment
+ uses: check-spelling/check-spelling@v0.0.25
+ with:
+ checkout: true
+ spell_check_this: check-spelling/spell-check-this@v0.0.25
+ task: ${{ needs.spelling.outputs.followup }}
+ experimental_apply_changes_via_bot: 1
+
+ update:
+ name: Update PR
+ permissions:
+ contents: write
+ pull-requests: write
+ runs-on: ubuntu-latest
+ if: ${{
+ github.event_name == 'issue_comment' &&
+ github.event.issue.pull_request &&
+ contains(github.event.comment.body, '@check-spelling-bot apply')
+ }}
+ concurrency:
+ group: spelling-update-${{ github.event.issue.number }}
+ cancel-in-progress: false
+ steps:
+ - name: apply spelling updates
+ uses: check-spelling/check-spelling@v0.0.25
+ with:
+ experimental_apply_changes_via_bot: 1
+ checkout: true
+ ssh_key: "${{ secrets.CHECK_SPELLING }}"
diff --git a/README.md b/README.md
index e9959b2..e6c5c04 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ exports a function named `test`.
## Issues & Pull requests
-If you required further explanations on the task type or or having issues with
+If you require further explanations on the task type or are having issues with
the task type please file an issue.
-Improvements and bug fixes should be shared by filing a pull request.
+Improvements and bugfixes should be shared by filing a pull request.
diff --git a/hie.yaml b/hie.yaml
index d85179a..2c0d52c 100644
--- a/hie.yaml
+++ b/hie.yaml
@@ -1,3 +1,4 @@
+---
cradle:
stack:
- path: "./src"
diff --git a/package.yaml b/package.yaml
index 1788434..0d72b7b 100644
--- a/package.yaml
+++ b/package.yaml
@@ -1,19 +1,20 @@
-name: haskell-template-task
-version: 0.2
-github: "fmidue/haskell-template-task"
-license: MIT
-author: "Marcellus Siegburg"
-maintainer: "marcellus.siegburg@uni-due.de"
-copyright: "2020 Formal Methods in Computer Science - University of Duisburg-Essen"
+---
+name: haskell-template-task
+version: 0.2
+github: "fmidue/haskell-template-task"
+license: MIT
+author: "Marcellus Siegburg"
+maintainer: "marcellus.siegburg@uni-due.de"
+copyright: "2020 Formal Methods in Computer Science - University of Duisburg-Essen"
extra-source-files:
- README.md
- ChangeLog.md
-synopsis: A task type for haskell tasks developed for the e-learning platform Autotool
-category: E-Learning
+synopsis: A task type for haskell tasks developed for the e-learning platform Autotool
+category: E-Learning
-description: Please see the README on GitHub at
+description: Please see the README on GitHub at
dependencies:
- base >= 4.7 && < 5
@@ -44,8 +45,8 @@ library:
tests:
haskell-template-task-test:
- main: Spec.hs
- source-dirs: test
+ main: Spec.hs
+ source-dirs: test
ghc-options:
- -threaded
- -rtsopts
diff --git a/raw/embedded/hie.yaml b/raw/embedded/hie.yaml
index 9ecf604..008aaed 100644
--- a/raw/embedded/hie.yaml
+++ b/raw/embedded/hie.yaml
@@ -1,3 +1,4 @@
+---
cradle:
stack:
- path: "./src"
diff --git a/raw/embedded/package.yaml b/raw/embedded/package.yaml
index 95009cf..78567bf 100644
--- a/raw/embedded/package.yaml
+++ b/raw/embedded/package.yaml
@@ -1,3 +1,4 @@
+---
name: haskell-template-task-embedded
ghc-options:
-Wall
diff --git a/raw/embedded/src/TestHarness.hs b/raw/embedded/src/TestHarness.hs
index 6f0f995..9959abd 100644
--- a/raw/embedded/src/TestHarness.hs
+++ b/raw/embedded/src/TestHarness.hs
@@ -104,7 +104,7 @@ allowFailures limit testCases = do
]
where
collectResults = fmap (groupIntoTwoLists . concat)
- . mapM (\(l, action) -> either (\(e :: SomeException) -> [(l,Just e)]) (const [(l,Nothing)]) <$> try action)
+ . mapM (\(l, action) -> either (\(e :: SomeException) -> [(l,Just e)]) (const [(l,Nothing)]) <$> try action)
groupIntoTwoLists :: [(a,Maybe b)] -> ([a],[(a,b)])
groupIntoTwoLists = foldr (\(a,mb) (ns,js) -> maybe (a:ns,js) (\b -> (ns,(a,b):js)) mb) ([],[])
diff --git a/raw/embedded/src/TestHelper.hs b/raw/embedded/src/TestHelper.hs
index 2505e42..b56b8ee 100644
--- a/raw/embedded/src/TestHelper.hs
+++ b/raw/embedded/src/TestHelper.hs
@@ -69,7 +69,7 @@ mustFail x msg =
monadicIO $ run $ do
resultOrError <- try (evaluate x)
case resultOrError of
- Left (_::SomeException) -> return () -- expected failure occured.
+ Left (_::SomeException) -> return () -- expected failure occurred.
Right _ -> error msg
isDeeplyDefined :: NFData a => a -> IO Bool
@@ -90,7 +90,8 @@ tcWithTimeoutAndArgs :: Int -> IOTasks.Args -> IOrep () -> Specification -> Asse
tcWithTimeoutAndArgs to args prog spec = tcCustomizedWithTimeoutAndArgs to args prog spec id
tcCustomizedWithTimeoutAndArgs :: Int -> IOTasks.Args -> IOrep () -> Specification -> (String -> String) -> Assertion
-tcCustomizedWithTimeoutAndArgs to args prog spec transfom = tcTimeoutAndArgsHandleFailure to args prog spec (transfom . defaultErrorMessage args)
+tcCustomizedWithTimeoutAndArgs to args prog spec transform =
+ tcTimeoutAndArgsHandleFailure to args prog spec (transform . defaultErrorMessage args)
tcWithInputsOnFailure :: Int -> IOTasks.Args -> IOrep () -> Specification -> ([String] -> String) -> Assertion
tcWithInputsOnFailure to args prog spec withInputs = tcTimeoutAndArgsHandleFailure to args prog spec handleFailure
@@ -101,11 +102,18 @@ tcWithInputsOnFailure to args prog spec withInputs = tcTimeoutAndArgsHandleFail
defaultErrorMessage :: IOTasks.Args -> IOTasks.Outcome -> String
defaultErrorMessage args = show . printOutcomeWith (feedbackStyle args)
-tcTimeoutAndArgsHandleFailure :: Int -> IOTasks.Args -> IOrep () -> Specification -> (IOTasks.Outcome -> String) -> Assertion
+tcTimeoutAndArgsHandleFailure
+ :: Int
+ -> IOTasks.Args
+ -> IOrep ()
+ -> Specification
+ -> (IOTasks.Outcome -> String)
+ -> Assertion
tcTimeoutAndArgsHandleFailure to args prog spec withFailure = do
outcome <- System.timeout to $ taskCheckWithOutcome args{ terminalOutput = False } prog spec
case outcome of
Just (IOTasks.Outcome IOTasks.Success{} _) -> return ()
- Just (IOTasks.Outcome IOTasks.GaveUp _) -> assertFailure "Gave up on testing. This is usually not caused by a fault within your solution. Please contact your lecturers"
+ Just (IOTasks.Outcome IOTasks.GaveUp _) -> assertFailure
+ "Gave up on testing. This is usually not caused by a fault within your solution. Please contact your lecturers"
Just o@(IOTasks.Outcome IOTasks.Failure{} _) -> assertFailure $ withFailure o
Nothing -> assertFailure "Failure: Timeout"
diff --git a/raw/embedded/stack.yaml b/raw/embedded/stack.yaml
index 4561d1c..3e9618b 100644
--- a/raw/embedded/stack.yaml
+++ b/raw/embedded/stack.yaml
@@ -1,13 +1,14 @@
+---
resolver: lts-21.25
packages:
-- .
+ - .
extra-deps:
-- git: https://github.com/fmidue/IOTasks
- commit: 3b33001441a888ae3443def19e516e4c699fb95f
+ - git: https://github.com/fmidue/IOTasks
+ commit: 3b33001441a888ae3443def19e516e4c699fb95f
-- git: https://github.com/owestphal/type-match
- commit: e1afab43d2e8bfa5e2006492ed34060036e7be51
+ - git: https://github.com/owestphal/type-match
+ commit: e1afab43d2e8bfa5e2006492ed34060036e7be51
-- git: https://github.com/IagoAbal/haskell-z3
- commit: 6368e451e45359563106da7e917f6594453c5161
+ - git: https://github.com/IagoAbal/haskell-z3
+ commit: 6368e451e45359563106da7e917f6594453c5161
diff --git a/raw/package.yaml b/raw/package.yaml
index b0ef3a9..fa14a4c 100644
--- a/raw/package.yaml
+++ b/raw/package.yaml
@@ -1,12 +1,13 @@
-name: haskell-template-task-raw
-version: 0.0.0.1
+---
+name: haskell-template-task-raw
+version: 0.0.0.1
ghc-options:
- - -Wall
+ - -Wall
dependencies:
- - base >= 4.7 && < 5
- - filepath
- - template-haskell
- - th-utilities
+ - base >= 4.7 && < 5
+ - filepath
+ - template-haskell
+ - th-utilities
library:
source-dirs: src
diff --git a/raw/stack.yaml b/raw/stack.yaml
index f5a60b6..dba5645 100644
--- a/raw/stack.yaml
+++ b/raw/stack.yaml
@@ -1,3 +1,4 @@
+---
resolver: lts-21.25
packages:
- .
diff --git a/src/Haskell/Template/Match.hs b/src/Haskell/Template/Match.hs
index bb4c20e..4cf0b07 100644
--- a/src/Haskell/Template/Match.hs
+++ b/src/Haskell/Template/Match.hs
@@ -1,5 +1,9 @@
-- Based on a version of
-- (c) Bertram Felgenhauer, 2011
+
+{- HLINT ignore "Avoid lambda" -}
+{- HLINT ignore "Use camelCase" -}
+
{-# LANGUAGE CPP #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
@@ -53,7 +57,7 @@ instance Monad M where
case a' of
Ok a'' -> runM (b a'')
Fail loc -> return $ Fail loc
- Continue -> return $ Continue
+ Continue -> return Continue
instance Functor M where
fmap = liftM
@@ -171,7 +175,7 @@ matchUndef _ = False
{-|
Stores src span locations in state.
-In conrast to 'matchSrcSpanInfo' it checks also arguments of constructors for
+In contrast to 'matchSrcSpanInfo' it checks also arguments of constructors for
existing 'S.SrcSpanInfo'.
(uses 'matchSrcSpanInfo')
-}
diff --git a/src/Haskell/Template/Task.hs b/src/Haskell/Template/Task.hs
index d25ed79..28c357f 100644
--- a/src/Haskell/Template/Task.hs
+++ b/src/Haskell/Template/Task.hs
@@ -315,9 +315,9 @@ check reject inform i = do
$ reject "wants to use System.IO.Unsafe"
when ("unsafePerformIO" `isInfixOf` i)
$ reject "wants to use unsafePerformIO"
- (mconfig, modules) <- splitConfigAndModules reject i
- inform $ string $ "Parsed the following setting options:\n" ++ show mconfig
- config <- addDefaults reject mconfig
+ (mConfig, modules) <- splitConfigAndModules reject i
+ inform $ string $ "Parsed the following setting options:\n" ++ show mConfig
+ config <- addDefaults reject mConfig
inform $ string $ "Completed configuration to:\n" ++ show config
let exts = extensionsOf config
((m,s), ms) <- nameModules (reject . string) exts modules
@@ -374,14 +374,14 @@ grade eval reject inform tmp task submission =
$ void $ reject "wants to use System.IO.Unsafe"
when ("unsafePerformIO" `isInfixOf` submission)
$ void $ reject "wants to use unsafePerformIO"
- (mconfig, rawModules) <- splitConfigAndModules reject task
- config <- addDefaults reject mconfig
+ (mConfig, rawModules) <- splitConfigAndModules reject task
+ config <- addDefaults reject mConfig
let exts = extensionsOf config
((moduleName', template), others) <-
nameModules (reject . string) exts rawModules
files <- liftIO $ ((moduleName', submission) : others)
- `forM` \(mname, contents) -> do
- let fname = dirname > mname <.> "hs"
+ `forM` \(mName, contents) -> do
+ let fname = dirname > mName <.> "hs"
strictWriteFile fname contents
return fname
let existingModules = map takeBaseName
@@ -519,9 +519,9 @@ matchTemplate
-> String
-> m ()
matchTemplate reject config context exts template submission = do
- mtemplate <- parse reject exts template
- msubmission <- parse reject exts submission
- case test mtemplate msubmission of
+ mTemplate <- parse reject exts template
+ mSubmission <- parse reject exts submission
+ case test mTemplate mSubmission of
Fail loc -> mapM_ (rejectMatch rejectWithHint config context template submission) loc
where
rejectWithHint = reject . vcat . (: singleton rejectHint)
@@ -604,21 +604,21 @@ parse
parse reject' exts' m = case E.readExtensions m of
Nothing -> reject' "cannot parse LANGUAGE pragmas at top of file"
Just (_, exts) ->
- let pamo = P.defaultParseMode
- { P.extensions = exts ++ exts' }
- in case P.parseModuleWithMode pamo m of
+ let parseMode = P.defaultParseMode
+ { P.extensions = exts ++ exts' }
+ in case P.parseModuleWithMode parseMode m of
P.ParseOk a -> return a
P.ParseFailed loc msg ->
rejectParse reject' m loc msg
rejectParse :: (Doc -> t) -> String -> E.SrcLoc -> String -> t
rejectParse reject' m loc msg =
- let (lpre, _) = splitAt (E.srcLine loc) $ lines m
- lpre' = takeEnd 3 lpre
+ let (lPre, _) = splitAt (E.srcLine loc) $ lines m
+ lPre' = takeEnd 3 lPre
tag = replicate (E.srcColumn loc - 1) '.' ++ "^"
in reject' $ vcat
["Syntax error (your solution is no Haskell program):",
- bloc $ lpre' ++ [tag],
+ bloc $ lPre' ++ [tag],
string msg]
rejectMatch
@@ -689,9 +689,9 @@ splitBy p = dropOdd . groupBy (\l r -> not (p l) && not (p r))
unsafeTemplateSegment :: String -> String
unsafeTemplateSegment task = either id id $ do
- let Just (mconfig, modules) =
+ let Just (mConfig, modules) =
splitConfigAndModules (const $ Just (defaultSolutionConfig, [])) task
- exts = maybe [] extensionsOf $ addDefaults (const Nothing) mconfig
+ exts = maybe [] extensionsOf $ addDefaults (const Nothing) mConfig
snd . fst <$> nameModules Left exts modules
nameModules
@@ -714,4 +714,4 @@ withNames exts mods =
moduleName :: E.Module l -> String
moduleName (E.Module _ (Just (E.ModuleHead _ (E.ModuleName _ n) _ _)) _ _ _) = n
moduleName (E.Module _ Nothing _ _ _) = "Main"
-moduleName _ = error "unsopported module type"
+moduleName _ = error "unsupported module type"
diff --git a/stack.yaml b/stack.yaml
index 76b46ee..ceaaecf 100644
--- a/stack.yaml
+++ b/stack.yaml
@@ -1,3 +1,4 @@
+---
resolver: lts-21.25
packages:
- .
diff --git a/test/Haskell/Template/MatchSpec.hs b/test/Haskell/Template/MatchSpec.hs
index 2fa1327..b018c9f 100644
--- a/test/Haskell/Template/MatchSpec.hs
+++ b/test/Haskell/Template/MatchSpec.hs
@@ -133,12 +133,12 @@ getComment
-> String
-> Either String [MatchKind [String]]
getComment config template submission = do
- mtemplate <- parse errorP [] template
- msubmission <- parse errorP [] submission
- case test mtemplate msubmission of
+ mTemplate <- parse errorP [] template
+ mSubmission <- parse errorP [] submission
+ case test mTemplate mSubmission of
Fail loc ->
- let state = sequence
- $ rejectMatch storeP config 0 template submission <$> loc
+ let state = mapM
+ (rejectMatch storeP config 0 template submission) loc
in Right $ kindOfMatch <$> retrieve state
Ok _ -> Right []
Continue -> Left "This should never happen"
diff --git a/test/Haskell/Template/TaskSpec.hs b/test/Haskell/Template/TaskSpec.hs
index 04a07ea..80d5c51 100644
--- a/test/Haskell/Template/TaskSpec.hs
+++ b/test/Haskell/Template/TaskSpec.hs
@@ -128,8 +128,8 @@ spec = do
test :: [Test]
test = [
"'r' does #{negateString}use 'reverse'?" ~:
- syntaxCheck $ \\modul ->
- contains (ident "reverse") (findTopLevelDeclsOf "r" modul) @?= #{withReverse}
+ syntaxCheck $ \\code ->
+ contains (ident "reverse") (findTopLevelDeclsOf "r" code) @?= #{withReverse}
]
|]
where