Skip to content

Commit

Permalink
Merge pull request #2 from roman/v0.0.0.2
Browse files Browse the repository at this point in the history
v0.0.0.2
  • Loading branch information
roman authored Apr 9, 2017
2 parents 479f9ea + f1a5049 commit 0950fdb
Show file tree
Hide file tree
Showing 14 changed files with 248 additions and 22 deletions.
1 change: 1 addition & 0 deletions .ghci
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
:seti -XNoImplicitPrelude
:seti -XOverloadedStrings
:seti -XScopedTypeVariables

:set -Wall
:set -fno-warn-type-defaults
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
.stack-work/
.#*
etc-spec/.tasty-rerun-log
.tasty-rerun-log
1 change: 0 additions & 1 deletion .tasty-rerun-log

This file was deleted.

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ help: ## Display this message
.DEFAULT_GOAL := help

TEST_RESOLVER ?= lts-8
TEST:=stack build --resolver $(TEST_RESOLVER) --install-ghc --test --haddock --no-haddock-deps --pedantic --flag etc:yaml --flag etc:cli --flag etc:printer
TEST:=stack build --resolver $(TEST_RESOLVER) --install-ghc --test --haddock --no-haddock-deps --pedantic --flag etc:yaml --flag etc:cli --flag etc:extra
test: ## Execute test suite with all compiler flags
$(TEST) etc:etc-testsuite
.PHONY: test
Expand Down
49 changes: 47 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ these values are to be found and located in a configuration map.
* [Reading From Pure Sources](#reading-from-pure-sources)
* [Accessing configuration values](#accessing-configuration-values)
* [Printing your configuration values](#printing-your-configuration-values)
* [Reporting Misspellings on Environment Variables](#reporting-misspellings-on-environment-variables)
* [Cabal Flags](#cabal-flags)
* [Full Example](#full-example)

Expand Down Expand Up @@ -464,6 +465,48 @@ program. You an use the `System.Etc.printPrettyConfig` function to render the
configuration map and the different values/sources that were resolved when
calculating it. This function is _really_ useful for debugging purposes.

### Example

Here is the output of one of
the
[example applications](https://github.com/roman/Haskell-etc/tree/master/etc-command-example):

```bash
$ MY_APP_USERNAME=foo etc-command-example run -u bar -p 123
Executing main program
credentials.username
bar [ Cli ]
foo [ Env: MY_APP_USERNAME ]
root [ Default ]

credentials.password
123 [ Cli ]
```

The output displays all the configuration values and their sources, the first
value on the list is the value that `System.Etc.getConfigValue` returns for that
particular key.

## Report Misspellings on Environment Variables

When you define `env` keys on the `etc/entries` map of your spec file, we can
infer what are the valid Environment Variables that need to be defined for your
application, knowing this, `etc` can infer when there is a typo on one of this
environment variables and report this. You need to have the `extra` cabal flag and
call the `System.Etc.reportEnvMisspellingWarnings` with the configuration spec as
as an argument.

### Example

Here is an example of the output this function prints to `stderr` when the given
Environment Variables are almost identical to the ones found on the spec file:

```bash
$ MY_AP_USERNAME=foo etc-command-example run -u bar -p 123

WARNING: Environment variable `MY_AP_USERNAME' found, perhaps you meant `MY_APP_USERNAME'
```

## Cabal Flags

Expand All @@ -475,8 +518,8 @@ the exact bits of functionality you need for your application.

- `cli`: Provides the CLI functionality explained in this README

- `printer`: Provides helper functions for printing the resolved configuration map
with all its entries + sources
- `extra`: Provides helper functions for inspecting the resolved configuration
as well as providing warning messages for misspelled environment variables

## Full Example

Expand Down Expand Up @@ -537,6 +580,8 @@ getConfiguration :: IO (Cmd, Etc.Config)
getConfiguration = do
spec <- Etc.readConfigSpec "./path/to/spec.yaml"
Etc.reportEnvMisspellingWarnings spec
let
defaultConfig =
Etc.resolveDefault spec
Expand Down
2 changes: 2 additions & 0 deletions etc-command-example/src/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ main = do
specPath <- getDataFileName "spec.yaml"
configSpec <- Etc.readConfigSpec (Text.pack specPath)

Etc.reportEnvMisspellingWarnings configSpec

(configFiles, _fileWarnings) <- Etc.resolveFiles configSpec
(cmd, configCli) <- Etc.resolveCommandCli configSpec
configEnv <- Etc.resolveEnv configSpec
Expand Down
2 changes: 2 additions & 0 deletions etc-plain-example/src/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ main = do
specPath <- getDataFileName "spec.yaml"
configSpec <- Etc.readConfigSpec (Text.pack specPath)

Etc.reportEnvMisspellingWarnings configSpec

(configFiles, _fileWarnings) <- Etc.resolveFiles configSpec
configEnv <- Etc.resolveEnv configSpec
configOptParser <- Etc.resolvePlainCli configSpec
Expand Down
22 changes: 15 additions & 7 deletions etc/etc.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
-- see: https://github.com/sol/hpack

name: etc
version: 0.0.0.1
version: 0.0.0.2
synopsis: Declarative configuration spec for Haskell projects
description: Please see README.md
category: Configuration, System
Expand All @@ -30,8 +30,8 @@ source-repository head
type: git
location: https://github.com/roman/Haskell-etc

flag printer
description: Include support for config printer
flag extra
description: Include extra utilities
manual: False
default: False

Expand Down Expand Up @@ -78,12 +78,15 @@ library

default-language: Haskell2010

if flag(printer)
cpp-options: -DWITH_PRINTER
if flag(extra)
cpp-options: -DWITH_EXTRA
build-depends:
ansi-wl-pprint >=0.6 && <0.7
ansi-wl-pprint >=0.6 && <0.7,
edit-distance >=0.2 && <0.3

exposed-modules:
System.Etc.Internal.Printer
System.Etc.Internal.Extra.Printer
System.Etc.Internal.Extra.EnvMisspell

if flag(cli)
cpp-options: -DWITH_CLI
Expand Down Expand Up @@ -130,6 +133,10 @@ test-suite etc-testsuite
cpp-options: -DWITH_YAML
build-depends:
yaml >=0.8 && <0.9
if flag(extra)
cpp-options: -DWITH_EXTRA
build-depends:
edit-distance >=0.2 && <0.3
other-modules:
Paths_etc
System.Etc.Resolver.CliTest
Expand All @@ -138,5 +145,6 @@ test-suite etc-testsuite
System.Etc.Resolver.DefaultTest
System.Etc.Resolver.EnvTest
System.Etc.Resolver.FileTest
System.Etc.Extra.EnvMisspellTest
System.Etc.SpecTest
default-language: Haskell2010
31 changes: 23 additions & 8 deletions etc/src/System/Etc.hs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,19 @@ module System.Etc (
, CliConfigError(..)
#endif

#ifdef WITH_PRINTER
-- * Printer
-- $printer
#ifdef WITH_EXTRA
-- * Extra utilities
-- $extra
, renderConfig
, printPrettyConfig
, hPrintPrettyConfig

, EnvMisspell(..)
, getEnvMisspellings
, getEnvMisspellingsPure
, renderEnvMisspellings
, hPrintEnvMisspellings
, reportEnvMisspellingWarnings
#endif
) where

Expand All @@ -63,8 +70,17 @@ import System.Etc.Internal.Resolver.Cli.Common (CliConfigError (..), getErrorMe
import System.Etc.Internal.Resolver.Cli.Plain (resolvePlainCli, resolvePlainCliPure)
#endif

#ifdef WITH_PRINTER
import System.Etc.Internal.Printer (hPrintPrettyConfig, printPrettyConfig, renderConfig)
#ifdef WITH_EXTRA
import System.Etc.Internal.Extra.EnvMisspell
( EnvMisspell (..)
, getEnvMisspellings
, getEnvMisspellingsPure
, hPrintEnvMisspellings
, renderEnvMisspellings
, reportEnvMisspellingWarnings
)
import System.Etc.Internal.Extra.Printer
(hPrintPrettyConfig, printPrettyConfig, renderConfig)
#endif

import System.Etc.Internal.Config
Expand Down Expand Up @@ -93,8 +109,7 @@ import System.Etc.Internal.Resolver.File (resolveFiles)
together using the mappend function
-}

{- $printer
{- $extra
Use these function to render the configuration map and understand how the
resolving was performed.
Some extra utilities that are great for debugging (miss)-configurations.
-}
105 changes: 105 additions & 0 deletions etc/src/System/Etc/Internal/Extra/EnvMisspell.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
module System.Etc.Internal.Extra.EnvMisspell (
EnvMisspell (..)
, getEnvMisspellings
, getEnvMisspellingsPure
, renderEnvMisspellings
, hPrintEnvMisspellings
, reportEnvMisspellingWarnings
) where

import Protolude hiding ((<$>), (<>))

import Data.Vector (Vector)
import System.Environment (getEnvironment)

import qualified Data.HashMap.Strict as HashMap
import qualified Data.Text as Text
import qualified Data.Vector as Vector
import qualified Text.EditDistance as Distance

import System.Etc.Internal.Spec.Types
import Text.PrettyPrint.ANSI.Leijen

data EnvMisspell
= EnvMisspell {
currentText :: Text
, suggestionText :: Text
}
deriving (Show, Eq, Generic)

lookupSpecEnvKeys :: ConfigSpec a -> Vector Text
lookupSpecEnvKeys spec =
let
foldEnvSettings val acc =
case val of
ConfigValue _ sources ->
maybe acc (`Vector.cons` acc) (envVar sources)
SubConfig hsh ->
HashMap.foldr foldEnvSettings acc hsh
in
foldEnvSettings (SubConfig $ specConfigValues spec) Vector.empty

{-|
-}
getEnvMisspellingsPure :: ConfigSpec a -> Vector Text -> Vector EnvMisspell
getEnvMisspellingsPure spec env = do
specEnvName <- lookupSpecEnvKeys spec
currentEnvName <- env

let
distance =
Distance.levenshteinDistance
Distance.defaultEditCosts
(Text.unpack specEnvName)
(Text.unpack currentEnvName)

guard (distance >= 1 && distance < 4)
return $ EnvMisspell currentEnvName specEnvName

{-|
-}
getEnvMisspellings :: ConfigSpec a -> IO (Vector EnvMisspell)
getEnvMisspellings spec =
getEnvironment
& fmap (Vector.fromList . map (Text.pack . fst))
& fmap (getEnvMisspellingsPure spec)

{-|
-}
renderEnvMisspellings :: Vector EnvMisspell -> Doc
renderEnvMisspellings misspells
| Vector.null misspells =
mempty
| otherwise =
misspells
& Vector.map
(\misspell ->
text "WARNING: Environment variable `"
<> text (Text.unpack $ currentText misspell)
<> text "' found, perhaps you meant `"
<> text (Text.unpack $ suggestionText misspell)
<> text "'")
& Vector.foldl' (<$>) mempty
& (<$> mempty)
& (<$> mempty)

{-|
-}
hPrintEnvMisspellings :: Handle -> Vector EnvMisspell -> IO ()
hPrintEnvMisspellings h =
hPutDoc h . renderEnvMisspellings

{-|
-}
reportEnvMisspellingWarnings :: ConfigSpec a -> IO ()
reportEnvMisspellingWarnings spec =
getEnvMisspellings spec >>=
hPrintEnvMisspellings stderr
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{-# OPTIONS_GHC -fno-warn-deprecations #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
module System.Etc.Internal.Printer (
module System.Etc.Internal.Extra.Printer (
renderConfig
, printPrettyConfig
, hPrintPrettyConfig
Expand Down
41 changes: 41 additions & 0 deletions etc/test/System/Etc/Extra/EnvMisspellTest.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module System.Etc.Extra.EnvMisspellTest where

import Protolude

import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertBool, assertEqual, testCase)

import qualified Data.Vector as Vector

import System.Etc

tests :: TestTree
tests =
testGroup "env misspells"
[
testCase "it warns when misspell is present" $ do
let
input =
mconcat
[
"{\"etc/entries\": {"
, " \"greeting\": { \"etc/spec\": { \"env\": \"GREETING\" }}}}"
]

(spec :: ConfigSpec ()) <- parseConfigSpec input

let
result =
getEnvMisspellingsPure spec ["GREEING"]

assertBool "expecting to get a warning for typo"
(not $ Vector.null result)

assertEqual "expecting to get typo for key GREETING"
(EnvMisspell "GREEING" "GREETING")
(Vector.head result)
]
Loading

0 comments on commit 0950fdb

Please sign in to comment.