summoner 1.2.0 → 1.3.0
raw patch · 20 files changed
+622/−190 lines, 20 filesdep +shellmetdep +tree-diffdep ~ansi-terminaldep ~directorydep ~relude
Dependencies added: shellmet, tree-diff
Dependency ranges changed: ansi-terminal, directory, relude, tomland
Files
- CHANGELOG.md +36/−0
- README.md +93/−8
- src/Summoner/CLI.hs +109/−16
- src/Summoner/Config.hs +39/−26
- src/Summoner/Default.hs +5/−0
- src/Summoner/GhcVer.hs +19/−1
- src/Summoner/Process.hs +0/−8
- src/Summoner/Project.hs +31/−19
- src/Summoner/Settings.hs +23/−1
- src/Summoner/Source.hs +19/−13
- src/Summoner/Template/Cabal.hs +9/−8
- src/Summoner/Template/GitHub.hs +82/−57
- src/Summoner/Template/Script.hs +44/−0
- src/Summoner/Template/Stack.hs +4/−3
- src/Summoner/Tree.hs +2/−0
- summoner.cabal +15/−9
- test/Spec.hs +2/−0
- test/Test/Golden.hs +37/−16
- test/Test/Script.hs +45/−0
- test/Test/TomlSpec.hs +8/−5
CHANGELOG.md view
@@ -3,6 +3,41 @@ `summoner` uses [PVP Versioning][1]. The changelog is available [on GitHub][2]. +## 1.3.0 — Apr 9, 2019++* [#285](https://github.com/kowainik/summoner/issues/285):+ Implement `summon script` command.+* [#151](https://github.com/kowainik/summoner/issues/151),+ [#295](https://github.com/kowainik/summoner/issues/295):+ Add support for GHC-8.6. Make GHC-8.6.4 default.+* [#269](https://github.com/kowainik/summoner/issues/269):+ __Important:__ Introduce `ghc-options` configuration parameter. Deprecate `warnings` field in TOML file.++ _Migration guide:_ Please, rename `warnings` field if you use one, it will be+ removed in the very next release. Use `ghc-options` instead.+* [#303](https://github.com/kowainik/summoner/issues/303):+ Add option `noUpload` to TOML config file.+* __Important:__ Summoner doesn't add old GHC versions into Travis matrix for+ Stack anymore. See this Stack issue for reasoning:+ https://github.com/commercialhaskell/stack/issues/4488+* [#280](https://github.com/kowainik/summoner/issues/280):+ Support build with `cabal-install` on the AppVeyor CI.+* [#272](https://github.com/kowainik/summoner/issues/272):+ Simplify Travis config for Cabal.+* [#275](https://github.com/kowainik/summoner/issues/275):+ Simplify Travis settings/installation process for Stack.+* [#261](https://github.com/kowainik/summoner/issues/261):+ Guess author login, name and email from `.gitconfig`.+* [#268](https://github.com/kowainik/summoner/issues/268):+ Simplify process of adding custom prelude in the interactive mode.+* [#282](https://github.com/kowainik/summoner/issues/282):+ Allow users to extend the `.gitignore` file.+* [#286](https://github.com/kowainik/summoner/issues/286),+ [#288](https://github.com/kowainik/summoner/issues/288),+ [#304](https://github.com/kowainik/summoner/issues/304):+ Bump up to `tomland-1.0.0`, `relude-0.5.0`, `shellmet-0.0.1`.+* Drop support for GHC-8.6.3 on CI. Support GHC-8.6.4 on CI.+ ## 1.2.0 — Nov 30, 2018 * Make `cabal-version: 2.0` default in generated projects.@@ -46,6 +81,7 @@ * Remove `tasty` dependencies from tests. * Drop support for GHC-8.6.1 on CI. Support GHC-8.6.2 on CI. * Drop support for GHC-8.4.3 on CI. Build with GHC-8.4.4 on CI.+* Use `defaultDescription` for project description if not specified. ## 1.1.0.1 – Sep 10, 2018
README.md view
@@ -11,7 +11,7 @@ > > Christopher Reeve -Summoner is a tool for scaffolding fully configured batteries-included production Haskell projects.+Summoner is a tool for scaffolding fully configured batteries-included production-level Haskell projects. Do you want to create a library that is to be uploaded to Hackage/Stackage, that builds with both Cabal and Stack and supports the latest three major GHC versions? Or you are building a production application which uses a custom prelude and has CI with Travis Linux and AppVeyors Windows checks?@@ -42,6 +42,7 @@ + [File configuration](#file-configuration-) + [TUI](#tui-) + [CLI](#cli-)+* [FAQ](#faq-) * [For Developers](#for-developers-) + [Build](#build-) + [Test](#test-)@@ -109,12 +110,13 @@ + Option to include an alternative prelude, if desired. The project would then use [`base-noprelude` technique](http://hackage.haskell.org/package/Prelude), and the `Prelude` module would be added to the library target. + Whole Hackage-upload checklist support. + Support for multiple GHC versions, with thoughtful reflection on project meta, base versions (e.g. `base >= 4.9 && < 4.12`), etc.++ Ability to create runnable Haskell scripts. + Different license support: MIT, BSD2, BSD3, GPL-2, GPL-3, LGPL-2.1, LGPL-3, AGPL-3, Apache-2.0, MPL-2.0, None (All Rights Reserved license without file). + Creation of the `CHANGELOG.md` file with [PVP versioning policy](https://pvp.haskell.org). + Ability to include your `.stylish-haskell.yaml` file. + Usage of the `ghc-options` field with sensible defaults. - If warnings are not explicitly stated in the configuration file, then the following list of GHC flags is added to all stanzas:+ If `ghc-options` are not explicitly stated in the configuration file, then the following list of GHC flags is added to all stanzas: ``` -Wall@@ -145,10 +147,12 @@ + Formation of the `README` file with Hackage, Stackage and CI badges. + Linking to the GitHub repository in the `.cabal` file. + Ability to include your `CONTRIBUTING.md` file.++ Guessing user credentials from `.gitconfig`. ### CI [↑](#structure) -+ Generation of the `.travis.yml` file that runs build and tests on CI under Linux.++ Generation of the `.travis.yml` file that runs build and tests on CI under Linux using+ [Dead simple Haskell Travis Settings for Cabal and Stack](https://chshersh.github.io/posts/2019-02-25-haskell-travis). + Configuration matrix on CI to build with multiple GHC versions and various build tools. + `-Werror` is enabled on CI not to miss any warnings. + Run HLint checks on CI.@@ -298,9 +302,10 @@ There are several options you can use to set particular configurations for new projects (in increasing order of priority): 1. Default configuration file (`~/.summoner.toml`).-2. Explicitly specified configuration file by `--file FILENAME` option (used instead of the default one if specified).-3. Options that are stated by CLI arguments.-4. User input. It could be+2. Fields `user.login`, `user.name` and `user.email` from `~/.gitconfig`.+3. Explicitly specified configuration file by `--file FILENAME` option (used instead of the default one if specified).+4. Options that are stated by CLI arguments.+5. User input. It could be * **TUI** – User data specified in the window of `summon-tui`. * **CLI** – Interactively inputted answers during execution of the `summon` command (for the options that were not specified on previous steps). @@ -318,8 +323,10 @@ | `fullName` | Text | Full name. | | `email` | Text | E-mail address. | | `license` | License | One of: `MIT`, `BSD2`, `BSD3`, `GPL-2`, `GPL-3`, `LGPL-2.1`, `LGPL-3`, `AGPL-3`, `Apache-2.0`, `MPL-2.0`, `None`. |-| `ghcVersions` | [GHC] | `summoner` uses default `GHC-8.4.4`. However, additionally you can specify other versions. For each version `x.y.z` the `stack-x.y.z.yaml` will be created. |+| `ghcVersions` | [GHC] | `summoner` uses default `GHC-8.6.4`. However, additionally you can specify other versions. For each version `x.y.z` the `stack-x.y.z.yaml` will be created. | | `github` | Bool | Turn on `GitHub` integration by default? |+| `gitignore` | [Text] | List of files you want added to the default `.gitignore`. (Ignored if `github = false`) |+| `noUpload` | Bool | Do not upload to GitHub, but create all GitHub related files if specified (Ignored if `github = false`) | | `private` | Bool | Create private repository by default? (Ignored if `github = false`) | | `travis` | Bool | Turn on `Travis` integration by default? (Ignored if `github = false`) | | `appveyor` | Bool | Turn on `AppVeyor` integration by default? (Ignored if `github = false`) |@@ -328,7 +335,7 @@ | `test` | Bool | Create `test` folder with simple `Spec.hs` file and test target? | | `bench` | Bool | Create `benchmark` folder with `Main.hs` file with [`gauge`](https://hackage.haskell.org/package/gauge) library usage example? | | `extensions` | [Text] | List of the default extensions to add into `default-extensions` section in the `.cabal`. |-| `warnings` | [Text] | List of the default checks and warnings to add into `ghc-options` section in the `.cabal`. |+| `ghc-options` | [Text] | List of the default GHC options to add into `ghc-options` section in the `.cabal`. | | `stylish.*` | Text | `stylish.file` to provide the absolute file path OR `stylish.url` to download the `.stylish-haskell.yaml` file to use in the project. | | `contributing.*` | Text | `contributing.file` to provide the absolute file path OR `contributing.url` download OR `contribuint.link` to link the `CONTRIBUTING.md` file to use in the project. | |`[prelude]` | | |@@ -348,6 +355,7 @@ Available commands: new Create a new Haskell project+ script Create a new Haskell script show Show available licenses or ghc versions Available global options:@@ -401,6 +409,19 @@ -b, --benchmark Benchmarks ``` +#### **summon script** command: [↑](#structure)++```+Usage:+ summon script BUILD_TOOL [-g|--ghc GHC_VERSION] [-n|--name FILE_NAME]+ Create a new Haskell script++Available options:+ -h,--help Show this help text+ -g,--ghc GHC_VERSION Version of the compiler to be used for script+ -n,--name FILE_NAME Name of the script file+```+ #### **summon show** command: [↑](#structure) ```@@ -459,6 +480,10 @@ Note that the form should be valid in order to be able to go to the Confirm window. If there are some errors in the input, you could see details about these errors in the `Status` section of the form. +#### TUI script command [↑](#structure)++See [CLI description](#cli-script-command-) of the `summon script` command.+ #### TUI show command [↑](#structure) These commands display the list of supported GHC versions or Licenses. Also, when the license name is specified the window with the scrollable content of the License text is shown.@@ -470,10 +495,66 @@ In CLI mode of operation Summoner asks about every project setting. Most of the questions contain a default value, so you can press <kbd>Enter</kbd> to choose the default value. If some option is specified via a configuration file or CLI arguments, then the question is skipped and the predefined value is used. +#### CLI script command [↑](#structure)++This command creates minimal `cabal` or `stack` script file which allows you to save some keystrokes and eliminates the need to remember magic words for scripts.++**Cabal example:** `summon script cabal -n Main.hs` generates executable file `Main.hs` with the following content:++```haskell+#!/usr/bin/env cabal+{- cabal:+build-depends:+ , base ^>= 4.12.0.0+-}++main :: IO ()+main = putStrLn "Hello, World!"+```++**Stack example:** `summon script stack -n Main.hs` generates executable file `Main.hs` with the following content:++```haskell+#!/usr/bin/env stack+{- stack+ --resolver lts-13.16+ script+ --package base+-}++main :: IO ()+main = putStrLn "Hello, World!"+```+ #### CLI show command [↑](#structure) These commands display the list of supported GHC versions, or Licenses. Also, when the license name is specified, the content of the License is outputted to the terminal. +## FAQ [↑](#structure)++> I want to use HTTPS remote for the created GitHub project, but it creates SSH one. How should I fix this?++We are using `hub` tool to create the projects at GitHub. It uses SSH so that you would get the remote links in the following format:++```+git@github.com:user/repo.git+```++We can not change or configure this behaviour, but there are several workarounds in case you _need_ to use HTTPS link for the remote.++1. Change the remote of the repository after its creation:+ ```+ git remote set-url origin https://github.com/user/repo.git+ ```+2. Change `hub` configurations globally. Simply run the following command:+ ```shell+ git config --global hub.protocol https+ ```+3. Alternatively, change `hub` configurations for a single session:+ ```+ export HUB_PROTOCOL="https"+ ```+ ## For Developers [↑](#structure) If you'd like to take part in the development processes, here are a few things to keep in mind:@@ -483,6 +564,10 @@ * We are using the [Kowainik style guide](https://github.com/kowainik/org/blob/master/style-guide.md#haskell-style-guide). * To sum up, [here is the Contributing guide](https://github.com/kowainik/org/blob/master/CONTRIBUTING.md#contributing-to-the-kowainik-repositories) we use across the repositories. * This project is contributor-friendly, so be kind to other people working on the project.++### Dependencies [↑](#structure)++On Linux, to build the `summoner-tui` you'll need to have `libtinfo` installed. The easiest way to get this is from your system's package manager and is usually available as the package `libtinfo-dev`. ### Build [↑](#structure)
src/Summoner/CLI.hs view
@@ -15,6 +15,9 @@ , summon , summonCli + -- * Runners+ , runScript+ -- * Common helper functions , getFinalConfig ) where@@ -22,23 +25,27 @@ import Data.Version (Version, showVersion) import Development.GitRev (gitCommitDate, gitDirty, gitHash) import NeatInterpolation (text)-import Options.Applicative (Parser, ParserInfo, command, execParser, flag, fullDesc, help, helper,- info, infoFooter, infoHeader, infoOption, long, metavar, optional,- progDesc, short, strArgument, strOption, subparser, switch)+import Options.Applicative (Parser, ParserInfo, argument, command, execParser, flag, fullDesc, help,+ helper, info, infoFooter, infoHeader, infoOption, long, maybeReader,+ metavar, option, optional, progDesc, short, strArgument, strOption,+ subparser, switch, value) import Options.Applicative.Help.Chunk (stringChunk)+import Shellmet (($?), ($|)) import System.Directory (doesFileExist)+import System.Info (os) import Summoner.Ansi (blueCode, boldCode, errorMessage, infoMessage, redCode, resetCode,- warningMessage)+ successMessage, warningMessage) import Summoner.Config (Config, ConfigP (..), PartialConfig, defaultConfig, finalise, loadFileConfig) import Summoner.Decision (Decision (..))-import Summoner.Default (defaultConfigFile)-import Summoner.GhcVer (GhcVer, showGhcVer)+import Summoner.Default (defaultConfigFile, defaultGHC)+import Summoner.GhcVer (GhcVer, parseGhcVer, showGhcVer) import Summoner.License (License (..), LicenseName (..), fetchLicense, parseLicenseName, showLicenseWithDesc) import Summoner.Project (generateProject)-import Summoner.Settings (CustomPrelude (..))+import Summoner.Settings (CustomPrelude (..), Tool, parseTool)+import Summoner.Template.Script (scriptFile) import qualified Data.Text as T import qualified Paths_summoner as Meta (version)@@ -57,6 +64,7 @@ runCliCommand :: Command -> IO () runCliCommand = \case New opts -> runNew opts+ Script opts -> runScript opts ShowInfo opts -> runShow opts {- | Runs @show@ command.@@ -94,6 +102,31 @@ showBulletList :: (a -> Text) -> [a] -> IO () showBulletList showT = mapM_ (infoMessage . T.append "➤ " . showT) +{- | Runs @script@ command.++@+Usage: summon script BUILD_TOOL (-g|--ghc GHC_VERSION) (-n|--name FILE_NAME)+ Create a new Haskell script++Available options:+ -h,--help Show this help text+ -g,--ghc GHC_VERSION Version of the compiler to be used for script+ -n,--name FILE_NAME Name of the script file+@+-}+runScript :: ScriptOpts -> IO ()+runScript ScriptOpts{..} = do+ let pathTxt = toText scriptOptsName+ whenM (doesFileExist scriptOptsName) $ do+ errorMessage $ "File already exists: " <> pathTxt+ exitFailure++ let script = scriptFile scriptOptsGhc scriptOptsTool+ writeFileText scriptOptsName script+ unless (os == "mingw32") $+ "chmod" ["+x", pathTxt]+ successMessage $ "Successfully created script: " <> pathTxt+ {- | Runs @new@ command. @@@ -114,7 +147,7 @@ -- get the final config finalConfig <- getFinalConfig newOpts -- Generate the project.- generateProject newOptsNoUpload newOptsOffline newOptsProjectName finalConfig+ generateProject newOptsOffline newOptsProjectName finalConfig -- | By the given 'NewOpts' return the final configurations. getFinalConfig :: NewOpts -> IO Config@@ -122,14 +155,29 @@ -- read config from file fileConfig <- readFileConfig newOptsIgnoreFile newOptsConfigFile + -- guess config from the global ~/.gitconfig file+ gitConfig <- guessFromGit+ -- union all possible configs- let unionConfig = defaultConfig <> fileConfig <> newOptsCliConfig+ let unionConfig = defaultConfig <> gitConfig <> fileConfig <> newOptsCliConfig -- get the final config case finalise unionConfig of Success c -> pure c Failure msgs -> for_ msgs errorMessage >> exitFailure+ where+ guessFromGit :: IO PartialConfig+ guessFromGit = do+ gitOwner <- (Just <$> "git" $| ["config", "user.login"]) $? pure Nothing+ gitName <- (Just <$> "git" $| ["config", "user.name"]) $? pure Nothing+ gitEmail <- (Just <$> "git" $| ["config", "user.email"]) $? pure Nothing+ pure $ defaultConfig+ { cOwner = Last gitOwner+ , cFullName = Last gitName+ , cEmail = Last gitEmail+ } + -- | Reads and parses the given config file. If no file is provided the default -- configuration returned. readFileConfig :: Bool -> Maybe FilePath -> IO PartialConfig@@ -159,6 +207,8 @@ data Command -- | @new@ command creates a new project = New NewOpts+ -- | @script@ command creates Haskell script+ | Script ScriptOpts -- | @show@ command shows supported licenses or GHC versions | ShowInfo ShowOpts @@ -166,14 +216,22 @@ data NewOpts = NewOpts { newOptsProjectName :: Text -- ^ project name , newOptsIgnoreFile :: Bool -- ^ ignore all config files if 'True'- , newOptsNoUpload :: Bool -- ^ don't upload to github , newOptsOffline :: Bool -- ^ Offline mode , newOptsConfigFile :: Maybe FilePath -- ^ file with custom configuration , newOptsCliConfig :: PartialConfig -- ^ config gathered during CLI } +-- | Options parsed with @script@ command+data ScriptOpts = ScriptOpts+ { scriptOptsTool :: !Tool -- ^ Build tool: `cabal` or `stack`+ , scriptOptsName :: !FilePath -- ^ File path to the script+ , scriptOptsGhc :: !GhcVer -- ^ GHC version for this script+ }+ -- | Commands parsed with @show@ command-data ShowOpts = GhcList | LicenseList (Maybe String)+data ShowOpts+ = GhcList+ | LicenseList (Maybe String) ---------------------------------------------------------------------------- -- Parsers@@ -207,10 +265,11 @@ summonerP :: Parser Command summonerP = subparser $ command "new" (info (helper <*> newP) $ progDesc "Create a new Haskell project")+ <> command "script" (info (helper <*> scriptP) $ progDesc "Create a new Haskell script") <> command "show" (info (helper <*> showP) $ progDesc "Show supported licenses or ghc versions") ------------------------------------------------------------------------------- Show command parsers+-- @show@ command parsers ---------------------------------------------------------------------------- -- | Parses options of the @show@ command.@@ -225,15 +284,49 @@ (strArgument (metavar "LICENSE_NAME" <> help "Show specific license text")) ------------------------------------------------------------------------------- New command parsers+-- @script@ command parsers ---------------------------------------------------------------------------- +-- | Parses options of the @script@ command.+scriptP :: Parser Command+scriptP = do+ scriptOptsTool <- toolArgP+ scriptOptsGhc <- ghcVerP+ scriptOptsName <- strOption+ $ long "name"+ <> short 'n'+ <> value "my_script"+ <> metavar "FILE_NAME"+ <> help "Name of the script file"++ pure $ Script ScriptOpts{..}++-- | Argument parser for 'Tool'.+toolArgP :: Parser Tool+toolArgP = argument+ (maybeReader $ parseTool . toText)+ (metavar "BUILD_TOOL")++ghcVerP :: Parser GhcVer+ghcVerP = option+ (maybeReader $ parseGhcVer . toText)+ ( long "ghc"+ <> short 'g'+ <> value defaultGHC+ <> metavar "GHC_VERSION"+ <> help "Version of the compiler to be used for script"+ )++----------------------------------------------------------------------------+-- @new@ command parsers+----------------------------------------------------------------------------+ -- | Parses options of the @new@ command. newP :: Parser Command newP = do newOptsProjectName <- strArgument (metavar "PROJECT_NAME") newOptsIgnoreFile <- ignoreFileP- newOptsNoUpload <- noUploadP+ noUpload <- noUploadP newOptsOffline <- offlineP newOptsConfigFile <- optional fileP cabal <- cabalP@@ -244,11 +337,11 @@ without <- optional withoutP pure $ New $ NewOpts- { newOptsNoUpload = newOptsNoUpload || newOptsOffline- , newOptsCliConfig = (maybeToMonoid $ with <> without)+ { newOptsCliConfig = (maybeToMonoid $ with <> without) { cPrelude = Last $ CustomPrelude <$> preludePack <*> preludeMod , cCabal = cabal , cStack = stack+ , cNoUpload = Any $ noUpload || newOptsOffline } , .. }
src/Summoner/Config.hs view
@@ -2,11 +2,9 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE KindSignatures #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-}-{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE UndecidableInstances #-} -- | Summoner configurations.@@ -26,7 +24,7 @@ import Data.List (lookup) import Generics.Deriving.Monoid (GMonoid, gmemptydefault) import Generics.Deriving.Semigroup (GSemigroup, gsappenddefault)-import Toml (BiMap (..), Key, TomlCodec, (.=))+import Toml (Key, TomlBiMap, TomlCodec, (.=)) import Summoner.Decision (Decision (..)) import Summoner.GhcVer (GhcVer (..), parseGhcVer, showGhcVer)@@ -42,26 +40,29 @@ -- | Potentially incomplete configuration. data ConfigP (p :: Phase) = Config- { cOwner :: p :- Text- , cFullName :: p :- Text- , cEmail :: p :- Text- , cLicense :: p :- LicenseName- , cGhcVer :: p :- [GhcVer]- , cCabal :: Decision- , cStack :: Decision- , cGitHub :: Decision- , cTravis :: Decision- , cAppVey :: Decision- , cPrivate :: Decision- , cLib :: Decision- , cExe :: Decision- , cTest :: Decision- , cBench :: Decision- , cPrelude :: Last CustomPrelude- , cExtensions :: [Text]- , cWarnings :: [Text]- , cStylish :: Last Source- , cContributing :: Last Source+ { cOwner :: !(p :- Text)+ , cFullName :: !(p :- Text)+ , cEmail :: !(p :- Text)+ , cLicense :: !(p :- LicenseName)+ , cGhcVer :: !(p :- [GhcVer])+ , cCabal :: !Decision+ , cStack :: !Decision+ , cGitHub :: !Decision+ , cTravis :: !Decision+ , cAppVey :: !Decision+ , cPrivate :: !Decision+ , cLib :: !Decision+ , cExe :: !Decision+ , cTest :: !Decision+ , cBench :: !Decision+ , cPrelude :: !(Last CustomPrelude)+ , cExtensions :: ![Text]+ , cWarnings :: ![Text]+ , cGhcOptions :: ![Text] -- ^ GHC options to add every stanza+ , cGitignore :: ![Text]+ , cStylish :: !(Last Source)+ , cContributing :: !(Last Source)+ , cNoUpload :: !Any -- ^ Do not upload to the GitHub (even if enabled) } deriving (Generic) deriving instance@@ -124,8 +125,11 @@ , cPrelude = Last Nothing , cExtensions = [] , cWarnings = []+ , cGhcOptions = []+ , cGitignore = [] , cStylish = Last Nothing , cContributing = Last Nothing+ , cNoUpload = Any False } -- | Identifies how to read 'Config' data from the @.toml@ file.@@ -149,20 +153,26 @@ <*> lastT preludeT "prelude" .= cPrelude <*> textArr "extensions" .= cExtensions <*> textArr "warnings" .= cWarnings+ <*> textArr "ghc-options" .= cGhcOptions+ <*> textArr "gitignore" .= cGitignore <*> lastT sourceT "stylish" .= cStylish <*> lastT sourceT "contributing" .= cContributing+ <*> anyT "noUpload" .= cNoUpload where lastT :: (Key -> TomlCodec a) -> Key -> TomlCodec (Last a) lastT codec = Toml.dimap getLast Last . Toml.dioptional . codec - _GhcVer :: BiMap GhcVer Toml.AnyValue- _GhcVer = Toml._TextBy showGhcVer parseGhcVer+ anyT :: Key -> TomlCodec Any+ anyT = Toml.dimap (Just . getAny) (Any . fromMaybe False) . Toml.dioptional . Toml.bool + _GhcVer :: TomlBiMap GhcVer Toml.AnyValue+ _GhcVer = Toml._TextBy showGhcVer (maybeToRight "Wrong GHC version" . parseGhcVer)+ ghcVerArr :: Key -> TomlCodec [GhcVer] ghcVerArr = Toml.arrayOf _GhcVer license :: Key -> TomlCodec LicenseName- license = Toml.mdimap show parseLicenseName . Toml.text+ license = Toml.textBy show (maybeToRight "Wrong license" . parseLicenseName) textArr :: Key -> TomlCodec [Text] textArr = Toml.dimap Just maybeToMonoid . Toml.dioptional . Toml.arrayOf Toml._Text@@ -207,8 +217,11 @@ <*> pure cPrelude <*> pure cExtensions <*> pure cWarnings+ <*> pure cGhcOptions+ <*> pure cGitignore <*> pure cStylish <*> pure cContributing+ <*> pure cNoUpload where fin name = maybe (Failure ["Missing field: " <> name]) Success . getLast
src/Summoner/Default.hs view
@@ -2,6 +2,7 @@ module Summoner.Default ( defaultGHC+ , defaultCabal , defaultTomlFile , defaultConfigFile , defaultDescription@@ -21,6 +22,10 @@ -- | Default GHC version is the latest available. defaultGHC :: GhcVer defaultGHC = maxBound++-- | Default version of the Cabal.+defaultCabal :: Text+defaultCabal = "2.0" defaultTomlFile :: FilePath defaultTomlFile = ".summoner.toml"
src/Summoner/GhcVer.hs view
@@ -1,3 +1,7 @@+{- | Contains data type for GHC versions supported by Summoner+and some useful functions for manipulation with them.+-}+ module Summoner.GhcVer ( GhcVer (..) , Pvp (..)@@ -6,6 +10,8 @@ , latestLts , baseVer , cabalBaseVersions++ , oldGhcs ) where import Data.List (maximum, minimum)@@ -21,6 +27,8 @@ | Ghc822 | Ghc843 | Ghc844+ | Ghc863+ | Ghc864 deriving (Eq, Ord, Show, Enum, Bounded) -- | Converts 'GhcVer' into dot-separated string.@@ -32,7 +40,13 @@ Ghc822 -> "8.2.2" Ghc843 -> "8.4.3" Ghc844 -> "8.4.4"+ Ghc863 -> "8.6.3"+ Ghc864 -> "8.6.4" +-- | These are old GHC versions that are not working with default GHC versions when using Stack.+oldGhcs :: [GhcVer]+oldGhcs = [minBound .. Ghc802]+ parseGhcVer :: Text -> Maybe GhcVer parseGhcVer = inverseMap showGhcVer @@ -44,7 +58,9 @@ Ghc802 -> "9.21" Ghc822 -> "11.22" Ghc843 -> "12.14"- Ghc844 -> "12.20"+ Ghc844 -> "12.26"+ Ghc863 -> "13.11"+ Ghc864 -> "13.16" -- | Represents PVP versioning (4 numbers). data Pvp = Pvp@@ -67,6 +83,8 @@ Ghc822 -> Pvp 4 10 1 0 Ghc843 -> Pvp 4 11 1 0 Ghc844 -> Pvp 4 11 1 0+ Ghc863 -> Pvp 4 12 0 0+ Ghc864 -> Pvp 4 12 0 0 -- | Returns corresponding @base@ version of the given GHC version. baseVer :: GhcVer -> Text
src/Summoner/Process.hs view
@@ -10,17 +10,9 @@ import Control.Exception (catch) import System.Directory (removeFile)-import System.Process (callCommand, showCommandForUser) import Summoner.Ansi (errorMessage) ---- | This is needed to be able to call commands by writing strings.-instance (a ~ Text, b ~ ()) => IsString ([a] -> IO b) where- fromString cmd args = do- let cmdStr = showCommandForUser cmd (map toString args)- putStrLn $ "⚙ " ++ cmdStr- callCommand cmdStr -- | Delete file, but just print a message if delete fails and continue instead of raising an error. deleteFile :: FilePath -> IO ()
src/Summoner/Project.hs view
@@ -7,21 +7,23 @@ , initializeProject ) where +import Data.List (intersect) import NeatInterpolation (text)+import Shellmet () import System.Directory (setCurrentDirectory) import Summoner.Ansi (Color (Green), beautyPrint, bold, errorMessage, infoMessage, setColor,- successMessage)+ skipMessage, successMessage, warningMessage) import Summoner.Config (Config, ConfigP (..)) import Summoner.Decision (Decision (..), decisionToBool) import Summoner.Default (currentYear, defaultDescription, defaultGHC)-import Summoner.GhcVer (parseGhcVer, showGhcVer)+import Summoner.GhcVer (oldGhcs, parseGhcVer, showGhcVer) import Summoner.License (LicenseName (..), customizeLicense, fetchLicense, licenseShortDesc, parseLicenseName) import Summoner.Process ()-import Summoner.Question (YesNoPrompt (..), checkUniqueName, choose, chooseYesNo, falseMessage,+import Summoner.Question (YesNoPrompt (..), checkUniqueName, choose, falseMessage, mkDefaultYesNoPrompt, query, queryDef, queryManyRepeatOnFail,- queryNotNull, targetMessageWithText, trueMessage)+ targetMessageWithText, trueMessage) import Summoner.Settings (CustomPrelude (..), Settings (..)) import Summoner.Source (fetchSource) import Summoner.Template (createProjectTemplate)@@ -31,12 +33,14 @@ -- | Generate the project. generateProject- :: Bool -- ^ @noUpload@ option (to not upload to @Github@).- -> Bool -- ^ @offline@ mode option+ :: Bool -- ^ @offline@ mode option -> Text -- ^ Given project name. -> Config -- ^ Given configurations. -> IO ()-generateProject settingsNoUpload isOffline projectName Config{..} = do+generateProject isOffline projectName Config{..} = do+ unless (null cWarnings) $+ warningMessage "Please, rename 'warnings' field if you use one, it will be removed in the very next release. Use 'ghc-options' instead."+ settingsRepo <- checkUniqueName projectName -- decide cabal stack or both (settingsCabal, settingsStack) <- getCabalStack (cCabal, cStack)@@ -65,12 +69,17 @@ settingsGitHub <- decisionToBool cGitHub (YesNoPrompt "GitHub integration" "Do you want to create a GitHub repository?")++ let settingsNoUpload = getAny cNoUpload+ when settingsNoUpload $ do+ infoMessage "'No upload' option is selected. The project won't be uploaded to GitHub."+ infoMessage "Use 'hub' and 'git' commands manually in order to upload the project to GitHub" settingsPrivate <- decisionIf (settingsGitHub && not settingsNoUpload) (YesNoPrompt "private repository" "Create as a private repository (Requires a GitHub private repo plan)?") cPrivate settingsTravis <- decisionIf settingsGitHub (mkDefaultYesNoPrompt "Travis CI integration") cTravis- settingsAppVeyor <- decisionIf (settingsStack && settingsGitHub) (mkDefaultYesNoPrompt "AppVeyor CI integration") cAppVey+ settingsAppVeyor <- decisionIf settingsGitHub (mkDefaultYesNoPrompt "AppVeyor CI integration") cAppVey settingsIsLib <- decisionToBool cLib (mkDefaultYesNoPrompt "library target") settingsIsExe <- let target = "executable target" in if settingsIsLib@@ -84,9 +93,11 @@ Just _ -> "base-noprelude" let settingsExtensions = cExtensions- let settingsWarnings = cWarnings+ let settingsGhcOptions = cWarnings ++ cGhcOptions+ let settingsGitignore = cGitignore - putTextLn $ "The project will be created with the latest resolver for default GHC-" <> showGhcVer defaultGHC++ putTextLn $ "The project will be created with GHC-" <> showGhcVer defaultGHC settingsTestedVersions <- sortNub . (defaultGHC :) <$> case cGhcVer of [] -> do putTextLn "Additionally you can specify versions of GHC to test with (space-separated): "@@ -95,6 +106,10 @@ vers -> do putTextLn $ "Also these GHC versions will be added: " <> intercalateMap " " showGhcVer vers pure vers+ -- Inform if there are old GHCs that won't be included to Travis Stack matrix+ let oldGhcIncluded = not $ null $ settingsTestedVersions `intersect` oldGhcs+ when (oldGhcIncluded && settingsStack && settingsTravis) $+ warningMessage "Old GHC versions won't be included into Stack matrix at Travis CI because of the Stack issue with newer Cabal versions." let fetchLast = maybe (pure Nothing) (fetchSource isOffline) . getLast settingsStylish <- fetchLast cStylish@@ -141,14 +156,12 @@ getPrelude :: IO (Maybe CustomPrelude) getPrelude = case cPrelude of Last Nothing -> do- let yesDo, noDo :: IO (Maybe CustomPrelude)- yesDo = do- p <- queryNotNull "Custom prelude package: "- m <- queryDef "Custom prelude module: " (packageToModule p)- successMessage $ "Custom prelude " <> p <> " will be used in the project"- pure $ Just $ CustomPrelude p m- noDo = pure Nothing- chooseYesNo (mkDefaultYesNoPrompt "custom prelude") yesDo noDo+ p <- query "Custom prelude package (leave empty if no custom prelude is needed): "+ if p == "" then Nothing <$ skipMessage "No custom prelude will be used in the project"+ else do+ m <- queryDef "Custom prelude module: " (packageToModule p)+ successMessage $ "Custom prelude " <> p <> " will be used in the project"+ pure $ Just $ CustomPrelude p m Last prelude@(Just (CustomPrelude p _)) -> prelude <$ successMessage ("Custom prelude " <> p <> " will be used in the project") @@ -178,7 +191,6 @@ createProjectDirectory settings when settingsGitHub $ doGithubCommands settings beautyPrint [bold, setColor Green] "\nJob's done\n"- -- | From the given 'Settings' creates the project. createProjectDirectory :: Settings -> IO ()
src/Summoner/Settings.hs view
@@ -1,7 +1,12 @@ module Summoner.Settings ( Settings (..)+ , CustomPrelude (..) , customPreludeT++ , Tool (..)+ , showTool+ , parseTool ) where import Toml (TomlCodec, (.=))@@ -45,10 +50,27 @@ , settingsBaseType :: !Text -- ^ Base library to use , settingsPrelude :: !(Maybe CustomPrelude) -- ^ custom prelude to be used , settingsExtensions :: ![Text] -- ^ default extensions- , settingsWarnings :: ![Text] -- ^ default warnings+ , settingsGhcOptions :: ![Text] -- ^ default GHC options+ , settingsGitignore :: ![Text] -- ^ .gitignore file , settingsCabal :: !Bool , settingsStack :: !Bool , settingsStylish :: !(Maybe Text) -- ^ @.stylish-haskell.yaml@ file , settingsContributing :: !(Maybe Text) -- ^ @CONTRIBUTING.md@ file , settingsNoUpload :: !Bool -- ^ do not upload to GitHub } deriving (Show)++-- | Enum for supported build tools.+data Tool+ = Cabal+ | Stack+ deriving (Show, Eq, Enum, Bounded)++-- | Show 'Tool' in lowercase.+showTool :: Tool -> Text+showTool = \case+ Cabal -> "cabal"+ Stack -> "stack"++-- | Parse 'Tool' from string. Inverse of 'showTool'.+parseTool :: Text -> Maybe Tool+parseTool = inverseMap showTool
src/Summoner/Source.hs view
@@ -10,7 +10,7 @@ import Control.Exception (catch) import NeatInterpolation (text) import System.Process (readProcess)-import Toml (BiMap, Key, TomlCodec)+import Toml (Key, TomlBiMap, TomlBiMapError (..), TomlCodec) import Summoner.Ansi (errorMessage, infoMessage) @@ -25,30 +25,36 @@ | Link Text deriving (Show, Eq) -matchUrl :: Source -> Maybe Text-matchUrl (Url url) = Just url-matchUrl _ = Nothing+showSource :: Source -> Text+showSource = \case+ Url _ -> "Url"+ File _ -> "File"+ Link _ -> "Link" -matchFile :: Source -> Maybe FilePath-matchFile (File file) = Just file-matchFile _ = Nothing+matchUrl :: Source -> Either TomlBiMapError Text+matchUrl (Url url) = Right url+matchUrl e = Left $ WrongConstructor "Url" $ showSource e -matchLink :: Source -> Maybe Text-matchLink (Link link) = Just link-matchLink _ = Nothing+matchFile :: Source -> Either TomlBiMapError FilePath+matchFile (File file) = Right file+matchFile e = Left $ WrongConstructor "File" $ showSource e +matchLink :: Source -> Either TomlBiMapError Text+matchLink (Link link) = Right link+matchLink e = Left $ WrongConstructor "Link" $ showSource e+ sourceT :: Key -> TomlCodec Source sourceT nm = Toml.match (_Url >>> Toml._Text) (nm <> "url") <|> Toml.match (_File >>> Toml._String) (nm <> "file") <|> Toml.match (_Link >>> Toml._Text) (nm <> "link") where- _Url :: BiMap Source Text+ _Url :: TomlBiMap Source Text _Url = Toml.prism Url matchUrl - _File :: BiMap Source FilePath+ _File :: TomlBiMap Source FilePath _File = Toml.prism File matchFile - _Link :: BiMap Source Text+ _Link :: TomlBiMap Source Text _Link = Toml.prism Link matchLink fetchSource :: Bool -> Source -> IO (Maybe Text)
src/Summoner/Template/Cabal.hs view
@@ -6,6 +6,7 @@ import NeatInterpolation (text) +import Summoner.Default (defaultCabal) import Summoner.GhcVer (GhcVer (..), cabalBaseVersions, showGhcVer) import Summoner.License (LicenseName (..), cabalLicense) import Summoner.Settings (CustomPrelude (..), Settings (..))@@ -31,7 +32,7 @@ -- TODO: do something to not have empty lines cabalHeader :: Text cabalHeader = unlines $- [ "cabal-version: 2.0"+ [ "cabal-version: " <> defaultCabal , "name: " <> settingsRepo , "version: 0.0.0" , "synopsis: " <> settingsDescription@@ -171,22 +172,22 @@ <> "\n" ghcOptions :: Text- ghcOptions = case settingsWarnings of- [] -> defaultWarnings+ ghcOptions = case settingsGhcOptions of+ [] -> defaultGhcOptions xs -> T.intercalate "\n" xs - defaultWarnings :: Text- defaultWarnings =+ defaultGhcOptions :: Text+ defaultGhcOptions = [text| -Wincomplete-uni-patterns -Wincomplete-record-updates -Wcompat -Widentities- $versionWarnings+ $versionGhcOptions |] - versionWarnings :: Text- versionWarnings+ versionGhcOptions :: Text+ versionGhcOptions = memptyIfFalse (settingsTestedVersions `hasLeast` Ghc801) "-Wredundant-constraints\n" <> memptyIfFalse (settingsTestedVersions `hasLeast` Ghc822)
src/Summoner/Template/GitHub.hs view
@@ -1,15 +1,29 @@ {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE ViewPatterns #-} +{-| This module contains template for GitHub related docs:++ * @.gitignore@ — static file with all Haskell related ignored files.+ * @appveyor.yml@ — Appveyor CI for Stack project only.+ * @.travis.yml@ — depending on the build tool and supported GHC versions+ builds the Travis matrix with all necessary checks, including HLint check.+ __NOTE:__ Old GHC versions is not included into Travis matrix for Stack due to+ Stack limitations with the Cabal version usage on each GHC. See this issue to+ track the problem:++ + https://github.com/commercialhaskell/stack/issues/4488++-}+ module Summoner.Template.GitHub ( gitHubFiles ) where -import Data.List (delete)+import Data.List ((\\)) import NeatInterpolation (text) import Summoner.Default (defaultGHC)-import Summoner.GhcVer (GhcVer (..), showGhcVer)+import Summoner.GhcVer (GhcVer (..), oldGhcs, showGhcVer) import Summoner.Settings (Settings (..)) import Summoner.Text (tconcatMap) import Summoner.Tree (TreeFs (..))@@ -17,13 +31,13 @@ gitHubFiles :: Settings -> [TreeFs] gitHubFiles Settings{..} =- [File ".gitignore" gitignore | settingsGitHub]+ [File ".gitignore" (gitignoreDefault <> gitignoreCustom) | settingsGitHub] ++ [File ".travis.yml" travisYml | settingsTravis] ++ [File "appveyor.yml" appVeyorYml | settingsAppVeyor] where- -- create .gitignore template- gitignore :: Text- gitignore =+ -- default .gitignore template+ gitignoreDefault :: Text+ gitignoreDefault = [text| ### Haskell dist@@ -80,6 +94,11 @@ .DS_Store |] + -- additional .gitignore+ gitignoreCustom :: Text+ gitignoreCustom = if null settingsGitignore+ then ""+ else unlines ("\n# User specific" : settingsGitignore) -- create travis.yml template travisYml :: Text@@ -91,6 +110,8 @@ git: depth: 5 + cabal: "2.4"+ cache: directories: $travisCabalCache@@ -108,7 +129,7 @@ |] travisCabalCache, travisStackCache :: Text- travisCabalCache = memptyIfFalse settingsCabal "- \"$HOME/.cabal\""+ travisCabalCache = memptyIfFalse settingsCabal "- \"$HOME/.cabal/store\"" travisStackCache = memptyIfFalse settingsStack [text| - "$$HOME/.stack"@@ -117,44 +138,29 @@ cabalTest :: Text- cabalTest = if settingsTest then "cabal new-test" else "echo 'No tests'"+ cabalTest = if settingsTest+ then "cabal new-test --enable-tests"+ else "echo 'No tests'" travisCabalMtr :: Text travisCabalMtr = memptyIfFalse settingsCabal $ tconcatMap travisCabalMatrixItem settingsTestedVersions travisCabalMatrixItem :: GhcVer -> Text- travisCabalMatrixItem (showGhcVer -> ghcV) =- [text|- $endLine- - ghc: ${ghcV}- env: GHCVER='${ghcV}' CABALVER='head'- os: linux- addons:- apt:- sources:- - hvr-ghc- packages:- - ghc-${ghcV}- - cabal-install-head- |]+ travisCabalMatrixItem (showGhcVer -> ghcV) = [text|- ghc: $ghcV|] + -- Due to Stach issues with newer Cabal versions we are not supporting Travis CI for GHC <= 8.0.2 for stack travisStackMtr :: Text- travisStackMtr = memptyIfFalse settingsStack $- tconcatMap travisStackMatrixItem (delete defaultGHC settingsTestedVersions)- <> travisStackMatrixDefaultItem+ travisStackMtr = memptyIfFalse settingsStack $ tconcatMap+ travisStackMatrixItem (settingsTestedVersions \\ (defaultGHC:oldGhcs))+ <> travisStackMatrixDefaultItem travisStackMatrixItem :: GhcVer -> Text travisStackMatrixItem (showGhcVer -> ghcV) = [text| $endLine - ghc: ${ghcV}- env: GHCVER='${ghcV}' STACK_YAML="$$TRAVIS_BUILD_DIR/stack-$$GHCVER.yaml"- os: linux- addons:- apt:- packages:- - libgmp-dev+ env: STACK_YAML="$$TRAVIS_BUILD_DIR/stack-$ghcV.yaml" |] travisStackMatrixDefaultItem :: Text@@ -162,12 +168,7 @@ [text| $endLine - ghc: ${defGhc}- env: GHCVER='${defGhc}' STACK_YAML="$$TRAVIS_BUILD_DIR/stack.yaml"- os: linux- addons:- apt:- packages:- - libgmp-dev+ env: STACK_YAML="$$TRAVIS_BUILD_DIR/stack.yaml" |] installAndScript :: Text@@ -184,18 +185,14 @@ install: - | if [ -z "$$STACK_YAML" ]; then- export PATH="/opt/ghc/$$GHCVER/bin:/opt/cabal/$$CABALVER/bin:$$PATH"- echo $$PATH+ ghc --version+ cabal --version 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'+ curl -sSL https://get.haskellstack.org/ | sh stack --version- stack setup --no-terminal --install-cabal 2.2.0.1- stack ghc -- --version- stack build --only-dependencies --no-terminal+ stack build --system-ghc --test --bench --no-run-tests --no-run-benchmarks --ghc-options=-Werror fi script:@@ -203,9 +200,10 @@ if [ -z "$$STACK_YAML" ]; then ${cabalTest} else- stack build --test --bench --no-run-benchmarks --no-terminal --ghc-options=-Werror+ stack test --system-ghc fi + # HLint check - curl -sSL https://raw.github.com/ndmitchell/neil/master/misc/travis.sh | sh -s -- hlint . |] @@ -213,8 +211,6 @@ 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 @@ -226,23 +222,52 @@ 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'+ - curl -sSL https://get.haskellstack.org/ | sh - stack --version- - stack setup --no-terminal --install-cabal 2.2.0.1- - stack ghc -- --version- - stack build --only-dependencies --no-terminal+ - stack build --system-ghc --test --bench --no-run-tests --no-run-benchmarks --ghc-options=-Werror script:- - stack build --test --bench --no-run-benchmarks --no-terminal --ghc-options=-Werror+ - stack test --system-ghc |] + appVeyorYml :: Text+ appVeyorYml =+ if settingsCabal+ then appVeyorYmlCabal+ else appVeyorYmlStack + appVeyorYmlCabal :: Text+ appVeyorYmlCabal =+ [text|+ install:+ # Using '-y' and 'refreshenv' as a workaround to:+ # https://github.com/haskell/cabal/issues/3687+ - choco source add -n mistuke -s https://www.myget.org/F/mistuke/api/v2+ - choco install -y ghc --ignore-dependencies+ - choco install -y cabal-head -pre+ - refreshenv+ # See http://help.appveyor.com/discussions/problems/6312-curl-command-not-found#comment_42195491+ # NB: Do this after refreshenv, otherwise it will be clobbered!+ - set PATH=%APPDATA%\cabal\bin;C:\Program Files\Git\cmd;C:\Program Files\Git\mingw64\bin;C:\msys64\usr\bin;%PATH%+ - cabal --version+ - cabal %CABOPTS% new-update + environment:+ global:+ CABOPTS: "--store-dir=C:\\SR"++ cache:+ - dist-newstyle+ - "C:\\SR"++ build_script:+ - cabal %CABOPTS% new-build --enable-tests --enable-benchmarks+ - cabal %CABOPTS% new-test --enable-tests+ |]+ -- create appveyor.yml template- appVeyorYml :: Text- appVeyorYml =+ appVeyorYmlStack :: Text+ appVeyorYmlStack = [text| build: off
+ src/Summoner/Template/Script.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE QuasiQuotes #-}++-- | File templates for @cabal@ and @stack@ scripts.++module Summoner.Template.Script+ ( scriptFile+ ) where++import NeatInterpolation (text)++import Summoner.GhcVer (GhcVer, baseVer, latestLts)+import Summoner.Settings (Tool (..))+++-- | 'Text' content for a single script file.+scriptFile :: GhcVer -> Tool -> Text+scriptFile ghcVer = \case+ Cabal ->+ [text|+ #!/usr/bin/env cabal+ {- cabal:+ build-depends:+ , base ^>= $baseVersion+ -}++ main :: IO ()+ main = putStrLn "Hello, World!"+ |]+ Stack ->+ [text|+ #!/usr/bin/env stack+ {- stack+ --resolver lts-${ltsVersion}+ script+ --package base+ -}++ main :: IO ()+ main = putStrLn "Hello, World!"+ |]+ where+ baseVersion, ltsVersion :: Text+ baseVersion = baseVer ghcVer+ ltsVersion = latestLts ghcVer
src/Summoner/Template/Stack.hs view
@@ -6,6 +6,7 @@ import NeatInterpolation (text) +import Summoner.Default (defaultGHC) import Summoner.GhcVer (GhcVer (..), baseVer, latestLts, showGhcVer) import Summoner.Settings (Settings (..)) import Summoner.Tree (TreeFs (..))@@ -20,9 +21,9 @@ $ "resolver: lts-" <> latestLts ghcV <> extraDeps <> ghcOpts where ver :: Text- ver = case ghcV of- Ghc844 -> ""- _ -> "-" <> showGhcVer ghcV+ ver = if ghcV == defaultGHC+ then ""+ else "-" <> showGhcVer ghcV extraDeps :: Text extraDeps = case settingsPrelude of
src/Summoner/Tree.hs view
@@ -11,12 +11,14 @@ import Summoner.Ansi (boldCode, resetCode) + -- | Describes simple structure of filesystem tree. data TreeFs -- | Name of directory (relative) and its containing entries = Dir FilePath [TreeFs] -- | File name (relative) and file content | File FilePath Text+ deriving (Generic, Show, Eq, Ord) -- | Walks through directory tree and write file contents, creating all -- intermediate directories.
summoner.cabal view
@@ -1,8 +1,8 @@ cabal-version: 2.0 name: summoner-version: 1.2.0-synopsis: Tool for scaffolding completely configured production Haskell projects.-description: Tool for scaffolding completely configured production Haskell projects.+version: 1.3.0+synopsis: Tool for scaffolding fully configured batteries-included production-level Haskell projects.+description: Tool for scaffolding fully configured batteries-included production-level Haskell projects. See [README.md](https://github.com/kowainik/summoner#-summoner) for details. homepage: https://github.com/kowainik/summoner bug-reports: https://github.com/kowainik/summoner/issues@@ -10,7 +10,7 @@ license-file: LICENSE author: Kowainik maintainer: xrom.xkov@gmail.com-copyright: 2018 Kowainik+copyright: 2018-2019 Kowainik category: CLI, CLI Tool, Development build-type: Simple stability: stable@@ -18,7 +18,7 @@ , CHANGELOG.md tested-with: GHC == 8.2.2 , GHC == 8.4.4- , GHC == 8.6.2+ , GHC == 8.6.4 source-repository head type: git@@ -44,6 +44,7 @@ Summoner.Template.Doc Summoner.Template.GitHub Summoner.Template.Haskell+ Summoner.Template.Script Summoner.Template.Stack Summoner.Text Summoner.Tree@@ -54,7 +55,7 @@ build-depends: base-noprelude >= 4.10 && < 4.13 , aeson >= 1.2.4.0 && < 1.5- , ansi-terminal ^>= 0.8.0.4+ , ansi-terminal >= 0.8 && < 0.10 , bytestring ^>= 0.10.8.2 , directory ^>= 1.3.0.2 , filepath ^>= 1.4.1.2@@ -63,10 +64,11 @@ , neat-interpolation ^>= 0.3.2.2 , optparse-applicative ^>= 0.14.2.0 , process ^>= 1.6.1.0- , relude ^>= 0.4.0+ , relude ^>= 0.5.0+ , shellmet ^>= 0.0.1 , text ^>= 1.2.3.0 , time ^>= 1.8- , tomland ^>= 0.5.0+ , tomland ^>= 1.0 ghc-options: -Wall -Wincomplete-uni-patterns@@ -113,17 +115,20 @@ main-is: Spec.hs other-modules: Test.DecisionSpec Test.Golden+ Test.Script Test.TomlSpec Test.QuestionSpec Prelude build-depends: base-noprelude >= 4.10 && < 4.13+ , directory , filepath , hedgehog >= 0.5.3 && < 0.7 , hspec >= 2.4.8 , neat-interpolation , relude , tomland+ , tree-diff ^>= 0.0.2 , summoner @@ -138,7 +143,8 @@ -Wredundant-constraints -fhide-source-paths - default-extensions: OverloadedStrings+ default-extensions: LambdaCase+ OverloadedStrings RecordWildCards default-language: Haskell2010
test/Spec.hs view
@@ -6,6 +6,7 @@ import Test.DecisionSpec (decisionMonoidMempty, decisionSemigroupAssoc) import Test.Golden (goldenSpec) import Test.QuestionSpec (yesNoPromptSpec)+import Test.Script (scriptSpec) import Test.TomlSpec (tomlProp) @@ -13,6 +14,7 @@ main = do hspec $ do yesNoPromptSpec+ scriptSpec goldenSpec ifM (checkParallel hedgehogTests) exitSuccess exitFailure
test/Test/Golden.hs view
@@ -1,3 +1,5 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+ {-# LANGUAGE QuasiQuotes #-} {- | Golden tests for @summoner@.@@ -17,9 +19,11 @@ ( goldenSpec ) where +import Data.TreeDiff (ToExpr, ansiWlEditExprCompact, ediff) import NeatInterpolation (text)-import System.FilePath ((</>))-import Test.Hspec (Spec, describe, it, shouldReturn)+import System.Directory (doesDirectoryExist, listDirectory)+import System.FilePath (takeFileName, (</>))+import Test.Hspec (Spec, describe, expectationFailure, it) import Summoner (CustomPrelude (..), GhcVer (..), License (..), LicenseName (..), Settings (..), defaultGHC)@@ -30,22 +34,33 @@ goldenSpec :: Spec goldenSpec = describe "golden tests" $ do it "correctly creates full project" $- checkProject fullProject+ checkProject "test/golden/fullProject" fullProject it "correctly creates small project" $- checkProject smallProject+ checkProject "test/golden/smallProject" smallProject where- checkProject pr = compareTree "test/golden" (createProjectTemplate pr) `shouldReturn` []+ checkProject :: FilePath -> Settings -> IO ()+ checkProject path settings = do+ goldenFs <- sortTree <$> readTreeFs path+ let testFs = sortTree $ createProjectTemplate settings+ when (goldenFs /= testFs) $ do+ putTextLn $ show $ ansiWlEditExprCompact $ ediff goldenFs testFs+ expectationFailure "Golden and scaffolded project don't match" +readTreeFs :: FilePath -> IO TreeFs+readTreeFs filePath = doesDirectoryExist filePath >>= \case+ True -> do+ dirs <- listDirectory filePath+ Dir (takeFileName filePath) <$> traverse (\dir -> readTreeFs $ filePath </> dir) dirs+ False -> do+ content <- readFileText filePath+ pure $ File (takeFileName filePath) content --- | Returns the list of the files that don't match the golden ones.-compareTree :: FilePath -> TreeFs -> IO [FilePath]-compareTree filePath (Dir name children) =- foldlM (\l ch -> (l ++) <$> compareTree (filePath </> name) ch) [] children-compareTree filePath (File name content) = do- let curFile = filePath </> name- golden <- readFileText curFile- pure $ if golden == content then [] else [curFile]+sortTree :: TreeFs -> TreeFs+sortTree = \case+ file@(File _ _) -> file+ Dir path fs -> Dir path $ sort $ map sortTree fs + fullProject :: Settings fullProject = Settings { settingsRepo = "fullProject"@@ -65,11 +80,12 @@ , settingsIsExe = True , settingsTest = True , settingsBench = True- , settingsTestedVersions = [Ghc802, Ghc822, Ghc844]+ , settingsTestedVersions = [Ghc802, Ghc822, Ghc844, defaultGHC] , settingsBaseType = "base-noprelude" , settingsPrelude = Just $ CustomPrelude "relude" "Relude" , settingsExtensions = ["ConstraintKinds", "LambdaCase", "OverloadedStrings"]- , settingsWarnings = ["-Wcompat", "-Widentities"]+ , settingsGitignore = [".secret"]+ , settingsGhcOptions = ["-Wcompat", "-Widentities"] , settingsCabal = True , settingsStack = True , settingsStylish = Just "This is stylish-haskell.yaml\n"@@ -125,10 +141,15 @@ , settingsBaseType = "base" , settingsPrelude = Nothing , settingsExtensions = []- , settingsWarnings = []+ , settingsGhcOptions = []+ , settingsGitignore = [] , settingsCabal = True , settingsStack = False , settingsStylish = Nothing , settingsContributing = Nothing , settingsNoUpload = True }++-- Orphan instances++instance ToExpr TreeFs
+ test/Test/Script.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE QuasiQuotes #-}++module Test.Script+ ( scriptSpec+ ) where++import NeatInterpolation (text)+import Test.Hspec (Spec, describe, it, shouldBe)++import Summoner.GhcVer (GhcVer (..))+import Summoner.Settings (Tool (..))+import Summoner.Template.Script (scriptFile)+++scriptSpec :: Spec+scriptSpec = describe "script golden tests" $ do+ it "correctly creates cabal script" $+ scriptFile Ghc844 Cabal `shouldBe` cabalScript+ it "correctly creates stack script" $+ scriptFile Ghc864 Stack `shouldBe` stackScript++cabalScript :: Text+cabalScript = [text|+#!/usr/bin/env cabal+{- cabal:+build-depends:+ , base ^>= 4.11.1.0+-}++main :: IO ()+main = putStrLn "Hello, World!"+|]++stackScript :: Text+stackScript = [text|+#!/usr/bin/env stack+{- stack+ --resolver lts-13.16+ script+ --package base+-}++main :: IO ()+main = putStrLn "Hello, World!"+|]
test/Test/TomlSpec.hs view
@@ -50,11 +50,11 @@ genPartialConfig :: MonadGen m => m PartialConfig genPartialConfig = do- cOwner <- Last . Just <$> genText- cFullName <- Last . Just <$> genText- cEmail <- Last . Just <$> genText- cLicense <- Last . Just <$> genLicense- cGhcVer <- Last . Just <$> genGhcVerArr+ cOwner <- Last <$> Gen.maybe genText+ cFullName <- Last <$> Gen.maybe genText+ cEmail <- Last <$> Gen.maybe genText+ cLicense <- Last <$> Gen.maybe genLicense+ cGhcVer <- Last <$> Gen.maybe genGhcVerArr cCabal <- genDecision cStack <- genDecision cGitHub <- genDecision@@ -68,6 +68,9 @@ cPrelude <- Last <$> Gen.maybe genCustomPrelude cExtensions <- genTextArr cWarnings <- genTextArr+ cGhcOptions <- genTextArr+ cGitignore <- genTextArr cStylish <- Last <$> Gen.maybe genSource cContributing <- Last <$> Gen.maybe genSource+ cNoUpload <- Any <$> Gen.bool pure Config{..}