packages feed

freckle-env (empty) → 0.0.0.0

raw patch · 7 files changed

+366/−0 lines, 7 filesdep +basedep +doctestdep +envparse

Dependencies added: base, doctest, envparse, errors, relude, text, time

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+## [_Unreleased_](https://github.com/freckle/freckle-app/compare/freckle-env-v0.0.0.0...main)++## [v0.0.0.0](https://github.com/freckle/freckle-app/tree/freckle-env-v0.0.0.0/freckle-env)++First release, sprouted from `freckle-app-1.18.2.0`.
+ LICENSE view
@@ -0,0 +1,21 @@+The MIT License (MIT)++Copyright (c) 2021-2024 Renaissance Learning Inc++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,7 @@+# freckle-env++Some extensions to the `envparse` library.++---++[CHANGELOG](./CHANGELOG.md) | [LICENSE](./LICENSE)
+ doctest/Main.hs view
@@ -0,0 +1,16 @@+module Main (main) where++import Prelude++import Test.DocTest++main :: IO ()+main =+  doctest+    [ "library/"+    , "-XGHC2021"+    , "-XDerivingStrategies"+    , "-XLambdaCase"+    , "-XNoImplicitPrelude"+    , "-XNoMonomorphismRestriction"+    ]
+ freckle-env.cabal view
@@ -0,0 +1,77 @@+cabal-version:      1.18+name:               freckle-env+version:            0.0.0.0+license:            MIT+license-file:       LICENSE+maintainer:         Freckle Education+homepage:           https://github.com/freckle/freckle-app#readme+bug-reports:        https://github.com/freckle/freckle-app/issues+synopsis:           Some extension to the envparse library+description:        Please see README.md+category:           Environment, System+build-type:         Simple+extra-source-files: package.yaml+extra-doc-files:+    README.md+    CHANGELOG.md++source-repository head+    type:     git+    location: https://github.com/freckle/freckle-app++library+    exposed-modules:    Freckle.App.Env+    hs-source-dirs:     library+    other-modules:      Paths_freckle_env+    default-language:   GHC2021+    default-extensions:+        DataKinds DeriveAnyClass DerivingVia DerivingStrategies GADTs+        LambdaCase NoImplicitPrelude NoMonomorphismRestriction+        OverloadedStrings TypeFamilies++    ghc-options:+        -fignore-optim-changes -fwrite-ide-info -Weverything+        -Wno-all-missed-specialisations -Wno-missing-exported-signatures+        -Wno-missing-import-lists -Wno-missing-kind-signatures+        -Wno-missing-local-signatures -Wno-missing-safe-haskell-mode+        -Wno-monomorphism-restriction -Wno-prepositive-qualified-module+        -Wno-safe -Wno-unsafe++    build-depends:+        base >=4.16.4.0 && <5,+        envparse >=0.5.0,+        errors >=2.3.0,+        relude >=1.1.0.0,+        text >=1.2.5.0,+        time >=1.11.1.1++    if impl(ghc >=9.8)+        ghc-options:+            -Wno-missing-role-annotations -Wno-missing-poly-kind-signatures++test-suite doctest+    type:               exitcode-stdio-1.0+    main-is:            Main.hs+    hs-source-dirs:     doctest+    other-modules:      Paths_freckle_env+    default-language:   GHC2021+    default-extensions:+        DataKinds DeriveAnyClass DerivingVia DerivingStrategies GADTs+        LambdaCase NoImplicitPrelude NoMonomorphismRestriction+        OverloadedStrings TypeFamilies++    ghc-options:+        -fignore-optim-changes -fwrite-ide-info -Weverything+        -Wno-all-missed-specialisations -Wno-missing-exported-signatures+        -Wno-missing-import-lists -Wno-missing-kind-signatures+        -Wno-missing-local-signatures -Wno-missing-safe-haskell-mode+        -Wno-monomorphism-restriction -Wno-prepositive-qualified-module+        -Wno-safe -Wno-unsafe++    build-depends:+        base >=4.16.4.0 && <5,+        doctest >=0.20.1++    if impl(ghc >=9.8)+        ghc-options:+            -Wno-missing-role-annotations -Wno-missing-poly-kind-signatures
+ library/Freckle/App/Env.hs view
@@ -0,0 +1,172 @@+-- | Parse the shell environment for configuration+--+-- A minor extension of [envparse](https://hackage.haskell.org/package/envparse).+--+-- Usage:+--+-- > import Freckle.App.Env+-- >+-- > data Config = Config -- Example+-- >   { cBatchSize :: Natural+-- >   , cDryRun :: Bool+-- >   , cLogLevel :: LogLevel+-- >   }+-- >+-- > loadConfig :: IO Config+-- > loadConfig = parse $ Config+-- >   <$> var auto "BATCH_SIZE" (def 1)+-- >   <*> switch "DRY_RUN" mempty+-- >   <*> flag (Off LevelInfo) (On LevelDebug) "DEBUG" mempty+module Freckle.App.Env+  ( module Env++    -- * Replacements+  , Off (..)+  , On (..)+  , flag++    -- * Extensions+  , Timeout (..)+  , eitherReader+  , time+  , keyValues+  , keyValue+  , splitOnParse+  , timeout+  ) where++import Relude++import Control.Error.Util (note)+import Data.Char (isDigit)+import Data.Text qualified as T+import Data.Time (UTCTime, defaultTimeLocale, parseTimeM)+import Env hiding (flag)+import Env qualified+import Prelude qualified as Unsafe (read)++-- | Designates the value of a parameter when a flag is not provided.+newtype Off a = Off a++-- | Designates the value of a parameter when a flag is provided.+newtype On a = On a++-- | Parse a simple flag+--+-- If the variable is present and non-empty in the environment, the active value+-- is returned, otherwise the default is used.+--+-- >>> data LogLevel = LevelDebug | LevelInfo | LevelWarn | LevelError | LevelOther Text deriving (Eq, Prelude.Show, Prelude.Read, Ord)+--+-- >>> flag (Off LevelInfo) (On LevelDebug) "DEBUG" mempty `parsePure` [("DEBUG", "1")]+-- Right LevelDebug+--+-- >>> flag (Off LevelInfo) (On LevelDebug) "DEBUG" mempty `parsePure` [("DEBUG", "")]+-- Right LevelInfo+--+-- >>> flag (Off LevelInfo) (On LevelDebug) "DEBUG" mempty `parsePure` []+-- Right LevelInfo+--+-- N.B. only the empty string is falsey:+--+-- >>> flag (Off LevelInfo) (On LevelDebug) "DEBUG" mempty `parsePure` [("DEBUG", "false")]+-- Right LevelDebug+--+-- >>> flag (Off LevelInfo) (On LevelDebug) "DEBUG" mempty `parsePure` [("DEBUG", "no")]+-- Right LevelDebug+flag :: Off a -> On a -> String -> Mod Flag a -> Parser Error a+flag (Off f) (On t) n m = Env.flag f t n m++-- | Create a 'Reader' from a simple parser function+--+-- This is a building-block for other 'Reader's+eitherReader :: (String -> Either String a) -> Env.Reader Error a+eitherReader f s = first (unread . suffix) $ f s+ where+  suffix x = x <> ": " <> show s++-- | Read a time value using the given format+--+-- >>> var (time "%Y-%m-%d") "TIME" mempty `parsePure` [("TIME", "1985-02-12")]+-- Right 1985-02-12 00:00:00 UTC+--+-- >>> var (time "%Y-%m-%d") "TIME" mempty `parsePure` [("TIME", "10:00PM")]+-- Left [("TIME",UnreadError "unable to parse time as %Y-%m-%d: \"10:00PM\"")]+time :: String -> Env.Reader Error UTCTime+time fmt =+  eitherReader+    $ note ("unable to parse time as " <> fmt)+    . parseTimeM True defaultTimeLocale fmt++-- | Read key-value pairs+--+-- >>> var keyValues "TAGS" mempty `parsePure` [("TAGS", "foo:bar,baz:bat")]+-- Right [("foo","bar"),("baz","bat")]+--+-- Value-less keys are not supported:+--+-- >>> var keyValues "TAGS" mempty `parsePure` [("TAGS", "foo,baz:bat")]+-- Left [("TAGS",UnreadError "Key foo has no value: \"foo\"")]+--+-- Nor are key-less values:+--+-- >>> var keyValues "TAGS" mempty `parsePure` [("TAGS", "foo:bar,:bat")]+-- Left [("TAGS",UnreadError "Value bat has no key: \":bat\"")]+keyValues :: Env.Reader Error [(Text, Text)]+keyValues = splitOnParse ',' $ keyValue ':'++keyValue :: Char -> Env.Reader Error (Text, Text)+keyValue c =+  eitherReader $ go . second (T.drop 1) . T.breakOn (T.singleton c) . toText+ where+  go = \case+    (k, v) | T.null v -> Left $ "Key " <> toString k <> " has no value"+    (k, v) | T.null k -> Left $ "Value " <> toString v <> " has no key"+    (k, v) -> Right (k, v)++-- | Use 'splitOn' then call the given 'Reader' on each element+--+-- @+-- 'splitOnParse' c pure == 'splitOn' c+-- @+--+-- >>> var (splitOnParse @Error ',' nonempty) "X" mempty `parsePure` [("X", "a,b")] :: Either [(String, Error)] [Text]+-- Right ["a","b"]+--+-- >>> var (splitOnParse @Error ',' nonempty) "X" mempty `parsePure` [("X", ",,")] :: Either [(String, Error)] [Text]+-- Left [("X",EmptyError)]+splitOnParse :: Char -> Env.Reader e a -> Env.Reader e [a]+splitOnParse c p = traverse p <=< splitOn c++-- | Represents a timeout in seconds or milliseconds+data Timeout+  = TimeoutSeconds Int+  | TimeoutMilliseconds Int+  deriving stock (Show, Eq)++-- | Read a timeout value as seconds or milliseconds+--+-- >>> var timeout "TIMEOUT" mempty `parsePure` [("TIMEOUT", "10")]+-- Right (TimeoutSeconds 10)+--+-- >>> var timeout "TIMEOUT" mempty `parsePure` [("TIMEOUT", "10s")]+-- Right (TimeoutSeconds 10)+--+-- >>> var timeout "TIMEOUT" mempty `parsePure` [("TIMEOUT", "10ms")]+-- Right (TimeoutMilliseconds 10)+--+-- >>> var timeout "TIMEOUT" mempty `parsePure` [("TIMEOUT", "20m")]+-- Left [("TIMEOUT",UnreadError "must be {digits}(s|ms): \"20m\"")]+--+-- >>> var timeout "TIMEOUT" mempty `parsePure` [("TIMEOUT", "2m0")]+-- Left [("TIMEOUT",UnreadError "must be {digits}(s|ms): \"2m0\"")]+timeout :: Env.Reader Error Timeout+timeout = eitherReader $ parseTimeout . span isDigit+ where+  parseTimeout = \case+    ("", _) -> Left "must be {digits}(s|ms)"+    (digits, "") -> Right $ TimeoutSeconds $ Unsafe.read digits+    (digits, "s") -> Right $ TimeoutSeconds $ Unsafe.read digits+    (digits, "ms") ->+      Right $ TimeoutMilliseconds $ Unsafe.read digits+    _ -> Left "must be {digits}(s|ms)"
+ package.yaml view
@@ -0,0 +1,68 @@+name: freckle-env+version: 0.0.0.0+maintainer: Freckle Education+category: Environment, System+github: freckle/freckle-app+synopsis: Some extension to the envparse library+description: Please see README.md++extra-doc-files:+  - README.md+  - CHANGELOG.md++extra-source-files:+  - package.yaml++language: GHC2021++ghc-options:+  - -fignore-optim-changes+  - -fwrite-ide-info+  - -Weverything+  - -Wno-all-missed-specialisations+  - -Wno-missing-exported-signatures # re-enables missing-signatures+  - -Wno-missing-import-lists+  - -Wno-missing-kind-signatures+  - -Wno-missing-local-signatures+  - -Wno-missing-safe-haskell-mode+  - -Wno-monomorphism-restriction+  - -Wno-prepositive-qualified-module+  - -Wno-safe+  - -Wno-unsafe++when:+  - condition: "impl(ghc >= 9.8)"+    ghc-options:+      - -Wno-missing-role-annotations+      - -Wno-missing-poly-kind-signatures++dependencies:+  - base < 5++default-extensions:+  - DataKinds+  - DeriveAnyClass+  - DerivingVia+  - DerivingStrategies+  - GADTs+  - LambdaCase+  - NoImplicitPrelude+  - NoMonomorphismRestriction+  - OverloadedStrings+  - TypeFamilies++library:+  source-dirs: library+  dependencies:+    - envparse+    - errors+    - relude+    - text+    - time++tests:+  doctest:+    main: Main.hs+    source-dirs: doctest+    dependencies:+      - doctest