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 +5/−0
- LICENSE +21/−0
- README.md +7/−0
- doctest/Main.hs +16/−0
- freckle-env.cabal +77/−0
- library/Freckle/App/Env.hs +172/−0
- package.yaml +68/−0
+ 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