Skip to content

Commit

Permalink
[#70] Add 'summon config' command (#420)
Browse files Browse the repository at this point in the history
* [#70] Add 'summon config' command

Resolves #70

* Apply suggestions from code review

Co-Authored-By: Veronika Romashkina <vrom911@gmail.com>

* Add golden testing

* Update a few more comments on configuration files

* Rename tomlSpec to tomlConfigSpec

Co-authored-by: Veronika Romashkina <vrom911@gmail.com>
  • Loading branch information
chshersh and vrom911 authored Feb 23, 2020
1 parent 126f8dd commit dfe6c05
Show file tree
Hide file tree
Showing 12 changed files with 367 additions and 54 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ The changelog is available [on GitHub][2].
* [#363](https://github.com/kowainik/summoner/issues/363):
Move from `generic-deriving` to `generic-data`.
(by [@chshersh](https://github.com/chshersh))
* [#70](https://github.com/kowainik/summoner/issues/70):
Implement `summon config` command. This command will generate the
default TOML configuration file with helpful comments.
(by [@chshersh](https://github.com/chshersh))
* [#361](https://github.com/kowainik/summoner/issues/361):
Always put all default warnings in `ghc-options` inside common
stanza under cabal conditionals on the `GHC` version. Now they look
Expand Down
37 changes: 34 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,11 @@ Features related to the structure and content of the generated projects.

### Others [](#structure)

+ Carefully collected Haskell project best practices gathered in your projects' scaffold.
+ Generate beginner-friendly default configuration using the `summon config` command.
+ Ability to create a project in the offline mode.
+ Ability to check GHC-specific versions of the corresponding `base` library
and Stackage snapshot resolver via `summon show ghc` command.
+ Carefully collected Haskell project best practices gathered in your projects' scaffold.

### Project structure example [](#structure)

Expand Down Expand Up @@ -379,7 +380,10 @@ Here is the list of the options that can be configured to suit your needs. If op
| `package` | Text | The package name of the custom prelude you'd like to use in the project (doesn't work without `module` field). |
| `module` | Text | The module name of the custom prelude you'd like to use in the project (doesn't work without `package` field). |

See the example of [the configuration for projects of the `Kowainik` organization](https://github.com/kowainik/org/blob/master/.summoner.toml).
You can create default configuration using the `summon config`
command. See [the default content here](summoner-cli/test/golden/summoner-default.toml).

For a real-life example of the configuration, see [the configuration for projects of the `Kowainik` organization](https://github.com/kowainik/org/blob/master/.summoner.toml).

### Command line arguments [](#structure)

Expand All @@ -394,6 +398,7 @@ Available commands:
new Create a new Haskell project
script Create a new Haskell script
show Show available licenses or ghc versions
config Create default TOML configuration for summoner
Available global options:
-h, --help Show this help text
Expand Down Expand Up @@ -422,7 +427,7 @@ Available options:
--cabal Cabal support for the project
--stack Stack support for the project
-f, --file FILENAME Path to the toml file with configurations. If not
specified '~/.summoner.toml' will be used if present
specified '~/.summoner.toml' will be used by default
--prelude-package PACKAGE_NAME
Name for the package of the custom prelude to use in
the project
Expand Down Expand Up @@ -485,6 +490,32 @@ Travis CI and AppVeyor CI integrated.
summon new my-project with -letgcpwa without -b --prelude-package relude --prelude-module Relude
```

#### **summon config** command: [](#structure)

```
Usage: summon config [-f|--file=FILENAME]
Create a default TOML configuration file for summoner
Available options:
-h,--help Show this help text
-f,--file=FILENAME Path to the toml file with configurations. If not
specified '~/.summoner.toml' will be used by default
```

This command will generate a TOML configuration file with the default settings
that can be used to scaffold future Haskell packages. It contains all
options supported by Summoner with comments and examples. Though, all
options would be turned off by default and to use them one will need
to uncomment the correspoding lines.
See [the default content here](summoner-cli/test/golden/summoner-default.toml).

Possible command usages:

```shell
summon config
summon config --file ~/.summoner-demo.toml
```

### TUI [](#structure)

#### TUI new command [](#structure)
Expand Down
69 changes: 59 additions & 10 deletions summoner-cli/src/Summoner/CLI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module Summoner.CLI
, summonCli

-- * Runners
, runConfig
, runScript

-- * Common helper functions
Expand All @@ -35,8 +36,8 @@ import NeatInterpolation (text)
import Options.Applicative (Parser, ParserInfo, ParserPrefs, argument, command, customExecParser,
flag, fullDesc, help, helpLongEquals, helper, info, infoFooter,
infoHeader, infoOption, long, maybeReader, metavar, option, prefs,
progDesc, short, showHelpOnEmpty, strArgument, strOption, subparser,
subparserInline, switch, value)
progDesc, short, showDefault, showHelpOnEmpty, strArgument, strOption,
subparser, subparserInline, switch, value)
import Options.Applicative.Help.Chunk (stringChunk)
import Relude.Extra.Enum (universe)
import Relude.Extra.Validation (Validation (..))
Expand All @@ -49,7 +50,7 @@ import Summoner.Config (Config, ConfigP (..), PartialConfig, defaultConfig, fina
loadFileConfig)
import Summoner.CustomPrelude (CustomPrelude (..))
import Summoner.Decision (Decision (..))
import Summoner.Default (defaultConfigFile, defaultGHC)
import Summoner.Default (defaultConfigFile, defaultConfigFileContent, defaultGHC)
import Summoner.GhcVer (GhcVer, ghcTable, parseGhcVer)
import Summoner.License (License (..), LicenseName (..), fetchLicense, parseLicenseName,
showLicenseWithDesc)
Expand Down Expand Up @@ -85,6 +86,35 @@ runCliCommand = \case
New opts -> runNew opts
Script opts -> runScript opts
ShowInfo opts -> runShow opts
Config opts -> runConfig opts


{- | Runs @config@ command
@
Usage: summon config [-f|--file=FILENAME]
Create a default TOML configuration file for summoner
Available options:
-h,--help Show this help text
-f,--file=FILENAME Path to the toml file with configurations. If not
specified '~/.summoner.toml' will be used by default
@
-}
runConfig :: ConfigOpts -> IO ()
runConfig ConfigOpts{..} = do
configFile <- whenNothing configOptsName defaultConfigFile
let configFileTxt = toText configFile
alreadyExist <- doesFileExist configFile
if alreadyExist
then do
warningMessage $ "File '" <> configFileTxt <> "' already exits."
infoMessage "Use 'summon config --file <path>' to specify another path."
exitFailure
else do
writeFileText configFile defaultConfigFileContent
infoMessage $ "Created default configuration file: " <> configFileTxt
infoMessage "Open the file using the editor of your choice."

{- | Runs @show@ command.
Expand Down Expand Up @@ -230,8 +260,10 @@ data Command
| Script ScriptOpts
-- | @show@ command shows supported licenses or GHC versions
| ShowInfo ShowOpts
-- | @config@ command creates the TOML configuration file
| Config ConfigOpts

-- | Options parsed with @new@ command
-- | Options parsed with the @new@ command
data NewOpts = NewOpts
{ newOptsProjectName :: Text -- ^ project name
, newOptsIgnoreFile :: Bool -- ^ ignore all config files if 'True'
Expand All @@ -240,7 +272,7 @@ data NewOpts = NewOpts
, newOptsCliConfig :: PartialConfig -- ^ config gathered during CLI
}

-- | Options parsed with @script@ command
-- | Options parsed with the @script@ command
data ScriptOpts = ScriptOpts
{ scriptOptsTool :: !Tool -- ^ Build tool: `cabal` or `stack`
, scriptOptsName :: !FilePath -- ^ File path to the script
Expand All @@ -252,6 +284,11 @@ data ShowOpts
= GhcList
| LicenseList (Maybe String)

-- | Options parsed with the @config@ command
newtype ConfigOpts = ConfigOpts
{ configOptsName :: Maybe FilePath
}

----------------------------------------------------------------------------
-- Parsers
----------------------------------------------------------------------------
Expand All @@ -278,12 +315,23 @@ summonerVersion version = intercalate "\n" [sVersion, sHash, sDate]
sHash = "" <> formatWith [blue, bold] "Git revision: " <> $(gitHash)
sDate = "" <> formatWith [blue, bold] "Commit date: " <> $(gitCommitDate)

-- All possible commands.
-- | All possible commands.
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")
<> command "config" (info (helper <*> configP) $ progDesc "Create a default TOML configuration file for summoner")

----------------------------------------------------------------------------
-- @config@ command parsers
----------------------------------------------------------------------------

-- | Parses options of the @config@ command.
configP :: Parser Command
configP = do
configOptsName <- optional configFileP
pure $ Config ConfigOpts{..}

----------------------------------------------------------------------------
-- @show@ command parsers
Expand Down Expand Up @@ -330,6 +378,7 @@ ghcVerP = option
( long "ghc"
<> short 'g'
<> value defaultGHC
<> showDefault
<> metavar "GHC_VERSION"
<> help "Version of the compiler to be used for script"
)
Expand All @@ -345,7 +394,7 @@ newP = do
newOptsIgnoreFile <- ignoreFileP
noUpload <- noUploadP
newOptsOffline <- offlineP
newOptsConfigFile <- optional fileP
newOptsConfigFile <- optional configFileP
cabal <- cabalP
stack <- stackP
preludePack <- optional preludePackP
Expand Down Expand Up @@ -463,12 +512,12 @@ offlineP = switch
$ long "offline"
<> help "Offline mode: create project with 'All Rights Reserved' license and without uploading to GitHub."

fileP :: Parser FilePath
fileP = strOption
configFileP :: Parser FilePath
configFileP = strOption
$ long "file"
<> short 'f'
<> metavar "FILENAME"
<> help "Path to the toml file with configurations. If not specified '~/.summoner.toml' will be used if present"
<> help "Path to the toml file with configurations. If not specified '~/.summoner.toml' will be used by default"

preludePackP :: Parser Text
preludePackP = strOption
Expand Down
61 changes: 31 additions & 30 deletions summoner-cli/src/Summoner/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import Toml (Key, TomlBiMap, TomlCodec, (.=))

import Summoner.CustomPrelude (CustomPrelude (..), customPreludeT)
import Summoner.Decision (Decision (..))
import Summoner.Default (defaultEmail, defaultFullName, defaultLicenseName, defaultOwner)
import Summoner.GhcVer (GhcVer (..), parseGhcVer, showGhcVer)
import Summoner.License (LicenseName (..), parseLicenseName)
import Summoner.Source (Source, sourceCodec)
Expand All @@ -54,29 +55,29 @@ type family phase :- field where
'Final :- field = field

-- | 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
, cGhActions :: !Decision
, cTravis :: !Decision
, cAppVey :: !Decision
, cPrivate :: !Decision
, cLib :: !Decision
, cExe :: !Decision
, cTest :: !Decision
, cBench :: !Decision
, cPrelude :: !(Last CustomPrelude)
, cExtensions :: ![Text]
, cGhcOptions :: ![Text] -- ^ GHC options to add to each stanza
, cGitignore :: ![Text]
, cNoUpload :: !Any -- ^ Do not upload to the GitHub (even if enabled)
, cFiles :: !(Map FilePath Source) -- ^ Custom files
data ConfigP (p :: Phase) = ConfigP
{ cOwner :: !(p :- Text)
, cFullName :: !(p :- Text)
, cEmail :: !(p :- Text)
, cLicense :: !(p :- LicenseName)
, cGhcVer :: !(p :- [GhcVer])
, cCabal :: !Decision
, cStack :: !Decision
, cGitHub :: !Decision
, cGhActions :: !Decision
, cTravis :: !Decision
, cAppVey :: !Decision
, cPrivate :: !Decision
, cLib :: !Decision
, cExe :: !Decision
, cTest :: !Decision
, cBench :: !Decision
, cPrelude :: !(Last CustomPrelude)
, cExtensions :: ![Text]
, cGhcOptions :: ![Text] -- ^ GHC options to add to each stanza
, cGitignore :: ![Text]
, cNoUpload :: !Any -- ^ Do not upload to the GitHub (even if enabled)
, cFiles :: !(Map FilePath Source) -- ^ Custom files
} deriving stock (Generic)

deriving stock instance
Expand Down Expand Up @@ -106,11 +107,11 @@ type Config = ConfigP 'Final

-- | Default 'Config' configurations.
defaultConfig :: PartialConfig
defaultConfig = Config
{ cOwner = Last (Just "kowainik")
, cFullName = Last (Just "Kowainik")
, cEmail = Last (Just "xrom.xkov@gmail.com")
, cLicense = Last (Just MIT)
defaultConfig = ConfigP
{ cOwner = Last (Just defaultOwner)
, cFullName = Last (Just defaultFullName)
, cEmail = Last (Just defaultEmail)
, cLicense = Last (Just defaultLicenseName)
, cGhcVer = Last (Just [])
, cCabal = Idk
, cStack = Idk
Expand All @@ -133,7 +134,7 @@ defaultConfig = Config

-- | Identifies how to read 'Config' data from the @.toml@ file.
configCodec :: TomlCodec PartialConfig
configCodec = Config
configCodec = ConfigP
<$> Toml.last Toml.text "owner" .= cOwner
<*> Toml.last Toml.text "fullName" .= cFullName
<*> Toml.last Toml.text "email" .= cEmail
Expand Down Expand Up @@ -192,7 +193,7 @@ configCodec = Config

-- | Make sure that all the required configurations options were specified.
finalise :: PartialConfig -> Validation [Text] Config
finalise Config{..} = Config
finalise ConfigP{..} = ConfigP
<$> fin "owner" cOwner
<*> fin "fullName" cFullName
<*> fin "email" cEmail
Expand Down
Loading

0 comments on commit dfe6c05

Please sign in to comment.