packages feed

dotenv 0.1.0.9 → 0.12.0.0

raw patch · 19 files changed

Files

+ CHANGELOG.md view
@@ -0,0 +1,210 @@+## MASTER+## Dotenv 0.12.0.0+### Modified+* Ensure support from GHC 8.10 up to GHC 9.6++### Possible breaking change+* New attribute for `Config` data type to print env vars without actually+running the command (kudos to @flandrade).++## Dotenv 0.11.0.2+### Modified+* Allow optparse-applicative 0.18++## Dotenv 0.11.0.1+### Modified+* Export internal module `Configuration.Dotenv.Internal` which exports all+the internal modules.+* Export `configParser` from `Configuration.Dotenv`.++## Dotenv 0.11.0.0+### Modified (Breaking change - new behavior)+* Take last rather than first env var in dotenv file (reported by @rudymatela and+solved by @anddriex). This will be the default behavior for the CLI, too.++## Dotenv 0.10.1.0+### Added+* Short `-x` for `--example` flag++### Modified+* Fix `union`/`unionBy` bug, refactor .env.example check, and improve error+message (Kudos to @pbrisbin)++## Dotenv 0.10.0.1+### Modified+* Modify docs.++## Dotenv 0.10.0.0+### Modified+* `loadFile` change return type (back to `m ()`)++## Dotenv 0.9.0.3+### Added+* Parse multi-word command interpolations (Kudos to @pbrisbin)++## Dotenv 0.9.0.2+### Added+* Support for GHC = 9.0++### Removed+* Support for GHC < 8.6++## Dotenv 0.9.0.1+### Added+* Allow optparse-applicative-0.17.0.0++## Dotenv 0.9.0.0+* Remove `loadSafeFile`. Users must create their own parsers to convert the+read values from `System.Environment` to another data type. Therefore,+`loadSafeFile` won't be needed. We'll remove this functionality to reduce+dependencies.++## Dotenv 0.8.1.0+* Correct bounds for base. GHC support for versions older than 8.0 was dropped.++## Dotenv 0.8.0.7+* Allow megaparsec-0.9.0.0++## Dotenv 0.8.0.6+* Allow optparse-applicative-0.16.0.0++## Dotenv 0.8.0.5+* Extend ghc support to 8.8 and 8.10++## Dotenv 0.8.0.4+*  Fix test fixtures++## Dotenv 0.8.0.3+* Add suppport for `megaparsec-8.0.0`++## Dotenv 0.8.0.2+* Add support for GHC 8.6++## Dotenv 0.8.0.1+* Support for `optparse-applicative-0.15`++## Dotenv 0.8.0.0+* Add `Configuration.Dotenv.Environment` module exporting functions from `System.Environment`,+  `System.Environment.Compat`, or `System.Environment.Blank`, depending on `base` version.+* Add support for blank environment variables for `base` >= 4.11.0.0.++## Dotenv 0.7.0.0+* Hide helper modules in other-modules++## Dotenv 0.6.0.3+* Reexport `defaultConfig` in `Configuration.Dotenv` (thanks to: matsubara0507)++## Dotenv 0.6.0.2+* Support for `process-1.6.3.0`++## Dotenv 0.6.0.1+* Add support for `megaparsec-7.0.1`+* Drop support for `GHC 7.8.4`++## Dotenv 0.6.0.0+* Move `loadSafeFile` to `Configuration.Dotenv`+* Export `Configuration.Dotenv.Types` from `Configuration.Dotenv`+* Change `loadSafeFile` signature to accept different types of validators.+* Add `type ValidatorMap = Map Text (Text -> Bool)` for custom validations.++## Dotenv 0.5.2.5+* Update `exceptions` bounds `>= 0.8 && < 0.11`++## Dotenv 0.5.2.4+* Add error message when there is more than one definition in the Scheme for the+same env++## Dotenv 0.5.2.3+* Update bounds `exceptions` == 0.9.*+* Support `megaparsec` >= 6.4.0++## Dotenv 0.5.2.1++* Update documentation for Configuration.Dotenv.Types++## Dotenv 0.5.2.0++* Add `loadSafeFile` to typecheck the envs.+* Add `(--schema|-s) FILE` flag to the `dotenv` CLI tool to enable safe mode.+* Add `(--no-schema)` flag to the `dotenv` CLI tool to disable safe mode.+* Turn safe mode on automatically when the `.schema.yml` file is present.+* Make `required` optional in the `.schema.yml`.++## Dotenv 0.5.1.1++* Allow `.env` empty files++## Dotenv 0.5.1.0++* Add support for command substitution on env vars.++## Dotenv 0.5.0.2++* Set `.env` file as default file for environment variables.+* Add `--version` flag to check the version of dotenv that is in use.++## Dotenv 0.5.0.0++* Add [dotenv-safe functionality](https://www.npmjs.com/package/dotenv-safe)+* Add the `Config` type with options to override env variables, and setting the+path for .env and .env.example files.+* Changed `loadFile` function to get `Config` with the paths for the .env file+and the .env.example file.++## Dotenv 0.4.0.0++* Use Megaparsec 6.0+* Dropped support for GHC 7.6++## Dotenv 0.3.4.0++* Allow optparse-applicative 0.14++## Dotenv 0.3.3.0++* Add support for variable expansion. Thanks to حبيب الامين (GitHub: habibalamin) for making this contribution.++## Dotenv 0.3.2.0++* Add the option to pass arguments to the program passed to Dotenv. Thanks to+  Oleg Grenrus (GitHub: phadej) for making this contribution.++## Dotenv 0.3.1.0++* Made interface more polymorphic so the functions works in any instance of+  `MonadIO`, not only `IO`. This should reduce amount of lifting in some+  cases.++* Added `onMissingFile` helper to deal with possibly missing files.++* Parser was rewritten to take full advantage of Megaparsec.+  `hspec-megaparsec` is now used for testing of the parser.++* Dropped support for GHC 7.4.++## Dotenv 0.3.0.3++* Allow optparse-applicative 0.13++## Dotenv 0.3.0.1++* Remove unnecessary package dependencies.++## Dotenv 0.3.0.0++* Reverted change to Data.Text in favor of String, for maintaining compatibility+  with common Haskell system libraries. Added separate interface for parsing a+  file into tuples containing Data.Text values. Thanks to Daisuke Fujimura+  (GitHub: fujimura).+* Fixed parsing of CRLF characters for Windows users.++## Dotenv 0.2.0.0 (deprecated)++* Changed public interfaces to use Data.Text.+* Added function `parseFile` to read dotenv file without modifying the+  environment. Thanks to Daisuke Fujimura (GitHub: fujimura) for making this+  contribution.++## Dotenv 0.1.0.0++* First public release.
LICENSE view
@@ -1,20 +1,21 @@-Copyright (c) 2015 Justin Leitgeb+MIT License -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:+Copyright (c) 2022 Stack Builders Inc. -The above copyright notice and this permission notice shall be included-in all copies or substantial portions of the Software.+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 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.+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,268 @@+[![Build Status](https://github.com/stackbuilders/dotenv-hs/actions/workflows/build.yml/badge.svg)](https://github.com/stackbuilders/dotenv-hs/actions/workflows/build.yml)[![Hackage](https://img.shields.io/hackage/v/dotenv.svg)](http://hackage.haskell.org/package/dotenv)+<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->+[![All Contributors](https://img.shields.io/badge/all_contributors-23-orange.svg?style=flat-square)](#contributors-)+<!-- ALL-CONTRIBUTORS-BADGE:END -->++# Dotenv files for Haskell++In most applications,+[configuration should be separated from code](http://12factor.net/config). While+it usually works well to keep configuration in the environment, there+are cases where you may want to store configuration in a file outside+of version control.++"Dotenv" files have become popular for storing configuration,+especially in development and test environments. In+[Ruby](https://github.com/bkeepers/dotenv),+[Python](https://github.com/theskumar/python-dotenv) and+[Javascript](https://www.npmjs.com/package/dotenv) there are libraries+to facilitate loading of configuration options from configuration+files. This library loads configuration to environment variables for+programs written in Haskell.++## Install++In most cases you will just add `dotenv` to your cabal file. You can+also install the library and executable by invoking `stack install dotenv` or+you can download the dotenv binaries from our+[releases](https://github.com/stackbuilders/dotenv-hs/releases) page.++## Usage++Set configuration variables in a file following the format below:++```+S3_BUCKET=YOURS3BUCKET+SECRET_KEY=YOURSECRETKEYGOESHERE+```++Then, calling `Dotenv.load` from your Haskell program reads the above+settings into the environment:++```haskell+import Configuration.Dotenv (loadFile, defaultConfig)+loadFile defaultConfig+```++After calling `Dotenv.load`, you are able to read the values set in your+environment using standard functions from `System.Environment` or+`System.Environment.Blank` (`base` >= 4.11.0.0), such as `getEnv`.++If your version of `base` is < 4.11.0.0, then setting an environment variable value to+a blank string will remove the variable from the environment entirely.++### Variable substitution++In order to use compound env vars use the following syntax within your env vars+${your_env_var}. For instance:++```+DATABASE=postgres://${USER}@localhost/database+```++Running it on the CLI:++```+$ dotenv "echo $DATABASE"+postgres://myusername@localhost/database+```++### Command substitution++In order to use the standard output of a command in your env vars use the following syntax $(your_command). For instance:++```+DATABASE=postgres://$(whoami)@localhost/database+```++Running it on the CLI:++```+$ dotenv "echo $DATABASE"+postgres://myusername@localhost/database+```++### Surrond with quotes++If your value starts with a character that produces a parse error (e.g. `{`) . Surround your value+with quotes. You can also escape the quotes if they're inside your value. For example:++```+JSON_SQ='{"a":[1,2,3], "b": "\'asdf\'"}'+JSON_DQ="{\"a\":[1,2,3], \"b\": \"'asdf'\"}"+```++Run it:++```+$ dotenv "echo $JSON_SQ" | jq .a+[+  1,+  2,+  3+]+```++### Configuration++The first argument to `loadFile` specifies the configuration. You can use+`defaultConfig` which parses the `.env` file in your current directory and+doesn't override your envs. You can also define your own configuration with+the `Config` type.++`False` in `configOverride` means Dotenv will respect+already-defined variables, and `True` means Dotenv will overwrite+already-defined variables.++In the `configPath` you can write a list of all the dotenv files where are+envs defined (e.g `[".env", ".tokens", ".public_keys"]`).++In the `configExamplePath` you can write a list of all the dotenv example files+where you can specify which envs **must be defined** until running a program+(e.g `[".env.example", ".tokens.example", ".public_keys.example"]`). If you don't+need this functionality you can set `configExamplePath` to an empty list.++A `False` in the `configVerbose` means that Dotenv will not print any message+when loading the envs. A `True` means that Dotenv will print a message when a variable is loaded.++When `configDryRyn` is `True`, Dotenv will print out the loaded environment variables without executing the program.++A `False` on `allowDuplicates` means that Dotenv will not allow duplicate keys, and instead it will throw+an error. A `True` means that Dotenv will allow duplicate keys, and it will use the last one defined in the file (default behavior).++### Advanced Dotenv File Syntax++You can add comments to your Dotenv file, on separate lines or after+values. Values can be wrapped in single or double quotes. Multi-line+values can be specified by wrapping the value in double-quotes, and+using the "\n" character to represent newlines.++The [spec file](spec/Configuration/Dotenv/ParseSpec.hs) is the best+place to understand the nuances of Dotenv file parsing.++### Command-Line Usage++You can call dotenv from the command line in order to load settings+from one or more dotenv file before invoking an executable:++```shell+$ dotenv -f mydotenvfile myprogram+```++The `-f` flag is optional, by default it looks for the `.env` file in the current working directory.++```shell+$ dotenv myprogram+```++Additionally, you can pass arguments and flags to the program passed to+Dotenv:++```shell+$ dotenv -f mydotenvfile myprogram -- --myflag myargument+```++or:++```shell+$ dotenv -f mydotenvfile "myprogram --myflag myargument"+```++Also, you can use a `--example` flag to use [dotenv-safe functionality](https://www.npmjs.com/package/dotenv-safe)+so that you can have a list of strict envs that should be defined in the environment+or in your dotenv files before the execution of your program. For instance:++```shell+$ cat .env.example+DOTENV=+FOO=+BAR=++$ cat .env+DOTENV=123++$ echo $FOO+123+```++This will fail:++```shell+$ dotenv -f .env --example .env.example "myprogram --myflag myargument"+> dotenv: The following variables are present in .env.example, but not set in the current environment, or .env: BAR+```++This will succeed:++```shell+$ export BAR=123 # Or you can do something like: "echo 'BAR=123' >> .env"+$ dotenv -f .env --example .env.example "myprogram --myflag myargument"+```++Hint: The `env` program in most Unix-like environments prints out the+current environment settings. By invoking the program `env` in place+of `myprogram` above you can see what the environment will look like+after evaluating multiple Dotenv files.++## Contributors ✨++Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):++<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->+<!-- prettier-ignore-start -->+<!-- markdownlint-disable -->+<table>+  <tbody>+    <tr>+      <td align="center" valign="top" width="14.28%"><a href="https://cristhianmotoche.github.io/"><img src="https://avatars.githubusercontent.com/u/8370088?v=4?s=100" width="100px;" alt="Cristhian Motoche"/><br /><sub><b>Cristhian Motoche</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=CristhianMotoche" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="https://www.stackbuilders.com/news/author/justin-leitgeb"><img src="https://avatars.githubusercontent.com/u/9977?v=4?s=100" width="100px;" alt="Justin S. Leitgeb"/><br /><sub><b>Justin S. Leitgeb</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=jsl" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="https://markkarpov.com/"><img src="https://avatars.githubusercontent.com/u/8165792?v=4?s=100" width="100px;" alt="Mark Karpov"/><br /><sub><b>Mark Karpov</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=mrkkrp" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="https://juancarlos.io/"><img src="https://avatars.githubusercontent.com/u/2164411?v=4?s=100" width="100px;" alt="Juan Paucar"/><br /><sub><b>Juan Paucar</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=juanpaucar" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="https://github.com/jpvillaisaza"><img src="https://avatars.githubusercontent.com/u/584947?v=4?s=100" width="100px;" alt="Juan Pedro Villa Isaza"/><br /><sub><b>Juan Pedro Villa Isaza</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=jpvillaisaza" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="http://fujimuradaisuke.com/"><img src="https://avatars.githubusercontent.com/u/134072?v=4?s=100" width="100px;" alt="Daisuke Fujimura"/><br /><sub><b>Daisuke Fujimura</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=fujimura" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="https://github.com/cptrodolfox"><img src="https://avatars.githubusercontent.com/u/20303685?v=4?s=100" width="100px;" alt="William R. Arellano"/><br /><sub><b>William R. Arellano</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=cptrodolfox" title="Code">💻</a></td>+    </tr>+    <tr>+      <td align="center" valign="top" width="14.28%"><a href="https://dbalseiro.github.io/"><img src="https://avatars.githubusercontent.com/u/2053849?v=4?s=100" width="100px;" alt="Diego Balseiro"/><br /><sub><b>Diego Balseiro</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=dbalseiro" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="https://github.com/fefi95"><img src="https://avatars.githubusercontent.com/u/12057338?v=4?s=100" width="100px;" alt="Stefani Castellanos"/><br /><sub><b>Stefani Castellanos</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=fefi95" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="https://wikipedia.org/"><img src="https://avatars.githubusercontent.com/u/2220440?v=4?s=100" width="100px;" alt="Götz"/><br /><sub><b>Götz</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=goetzc" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="https://oleg.fi/"><img src="https://avatars.githubusercontent.com/u/51087?v=4?s=100" width="100px;" alt="Oleg Grenrus"/><br /><sub><b>Oleg Grenrus</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=phadej" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="https://github.com/sestrella"><img src="https://avatars.githubusercontent.com/u/2049686?v=4?s=100" width="100px;" alt="Sebastián Estrella"/><br /><sub><b>Sebastián Estrella</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=sestrella" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="https://git.sr.ht/~habibalamin"><img src="https://avatars.githubusercontent.com/u/1415293?v=4?s=100" width="100px;" alt="Habib Alamin"/><br /><sub><b>Habib Alamin</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=habibalamin" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="https://github.com/FranzGB"><img src="https://avatars.githubusercontent.com/u/46214532?v=4?s=100" width="100px;" alt="Franz Guzmán"/><br /><sub><b>Franz Guzmán</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=FranzGB" title="Code">💻</a></td>+    </tr>+    <tr>+      <td align="center" valign="top" width="14.28%"><a href="https://brisb.in/"><img src="https://avatars.githubusercontent.com/u/50812?v=4?s=100" width="100px;" alt="Pat Brisbin"/><br /><sub><b>Pat Brisbin</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=pbrisbin" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="https://github.com/aloussase"><img src="https://avatars.githubusercontent.com/u/84427521?v=4?s=100" width="100px;" alt="Alexander Goussas"/><br /><sub><b>Alexander Goussas</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=aloussase" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="https://github.com/alexisbcc"><img src="https://avatars.githubusercontent.com/u/38666191?v=4?s=100" width="100px;" alt="Alexis Crespo"/><br /><sub><b>Alexis Crespo</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=alexisbcc" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="https://github.com/anddriex"><img src="https://avatars.githubusercontent.com/u/42983405?v=4?s=100" width="100px;" alt="Andres Perez"/><br /><sub><b>Andres Perez</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=anddriex" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="https://github.com/ibarrae"><img src="https://avatars.githubusercontent.com/u/22796877?v=4?s=100" width="100px;" alt="Esteban Ibarra"/><br /><sub><b>Esteban Ibarra</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=ibarrae" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="http://ferandrade.com/"><img src="https://avatars.githubusercontent.com/u/6790161?v=4?s=100" width="100px;" alt="Fernanda Andrade"/><br /><sub><b>Fernanda Andrade</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=flandrade" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="https://github.com/Jagl257"><img src="https://avatars.githubusercontent.com/u/27145248?v=4?s=100" width="100px;" alt="Jorge Guerra Landázuri"/><br /><sub><b>Jorge Guerra Landázuri</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=Jagl257" title="Code">💻</a></td>+    </tr>+    <tr>+      <td align="center" valign="top" width="14.28%"><a href="https://matsubara0507.github.io/whoami"><img src="https://avatars.githubusercontent.com/u/10684493?v=4?s=100" width="100px;" alt="MATSUBARA Nobutada"/><br /><sub><b>MATSUBARA Nobutada</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=matsubara0507" title="Code">💻</a></td>+      <td align="center" valign="top" width="14.28%"><a href="https://github.com/wojtekmach"><img src="https://avatars.githubusercontent.com/u/76071?v=4?s=100" width="100px;" alt="Wojtek Mach"/><br /><sub><b>Wojtek Mach</b></sub></a><br /><a href="https://github.com/stackbuilders/dotenv-hs/commits?author=wojtekmach" title="Code">💻</a></td>+    </tr>+  </tbody>+</table>++<!-- markdownlint-restore -->+<!-- prettier-ignore-end -->++<!-- ALL-CONTRIBUTORS-LIST:END -->++This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!++## License++MIT, see [the LICENSE file](LICENSE).++## Contributing++Do you want to contribute to this project? Please take a look at our [contributing guideline](/docs/CONTRIBUTING.md) to know how you can help us build it.++---++<img src="https://cdn.stackbuilders.com/media/images/Sb-supports.original.png" alt="Stack Builders" width="50%"></img>+[Check out our libraries](https://github.com/stackbuilders/) | [Join our team](https://www.stackbuilders.com/join-us/)
+ app/Main.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE CPP             #-}+{-# LANGUAGE RecordWildCards #-}++module Main where++import           Data.Version         (showVersion)+#if !MIN_VERSION_base(4,13,0)+import           Data.Monoid          ((<>))+#endif++import           Options.Applicative+import           Paths_dotenv         (version)++import           Configuration.Dotenv (Config (..), defaultConfig, loadFile)+import           System.Exit          (exitWith)+import           System.Process       (system)++data Options = Options+  { dotenvFiles        :: [String]+  , dotenvExampleFiles :: [String]+  , override           :: Bool+  , verbose            :: Bool+  , dryRun             :: Bool+  , duplicates         :: Bool+  , program            :: String+  , args               :: [String]+  } deriving (Show)++main :: IO ()+main = do+  Options{..} <- execParser opts+  let configDotenv =+        Config+          { configExamplePath = dotenvExampleFiles+          , configOverride = override+          , configVerbose = verbose+          , configDryRun = dryRun+          , allowDuplicates = duplicates+          , configPath =+              if null dotenvFiles+                then configPath defaultConfig+                else dotenvFiles+          }+   in do+    loadFile configDotenv+    if configDryRun configDotenv+      then putStrLn "[INFO]: Dry run mode enabled. Not executing the program."+      else system (program ++ concatMap (" " ++) args) >>= exitWith+       where+         opts = info (helper <*> versionOption <*> config)+           ( fullDesc+          <> progDesc "Runs PROGRAM after loading options from FILE"+          <> header "dotenv - loads options from dotenv files" )+         versionOption =+           infoOption+             (showVersion version)+             (long "version" <> short 'v' <> help "Show version of the program")++config :: Parser Options+config = Options+     <$> many (strOption (+                  long "dotenv"+                  <> short 'f'+                  <> metavar "DOTENV"+                  <> help "File to read for environmental variables" ))++     <*> many (strOption (+                  long "example"+                  <> short 'x'+                  <> metavar "DOTENV_EXAMPLE"+                  <> help "File to read for needed environmental variables" ))++     <*> switch ( long "overload"+                  <> short 'o'+                  <> help "Specify this flag to override existing variables" )++     <*> switch (  long "verbose"+                  <> help "Specify this flag to print out the variables loaded and other useful insights" )++     <*> switch (  long "dry-run"+                  <> help "Specify this flag to print out the variables loaded without executing the program" )++     <*> flag True False (  long "no-dups"+                  <> short 'D'+                  <> help "Specify this flag to forbid duplicate variables"+                  )++     <*> argument str (metavar "PROGRAM")++     <*> many (argument str (metavar "ARG"))
dotenv.cabal view
@@ -1,20 +1,20 @@ name:                dotenv-version:             0.1.0.9+version:             0.12.0.0 synopsis:            Loads environment variables from dotenv files homepage:            https://github.com/stackbuilders/dotenv-hs description:   .   In most applications,-  <http://12factor.net/config configuration should be separated from code>.+  <https://12factor.net/config configuration should be separated from code>.   While it usually works well to keep configuration in the   environment, there are cases where you may want to store   configuration in a file outside of version control.   .-  "Dotenv" files have become popular for storing configuration,+  @Dotenv@ files have become popular for storing configuration,   especially in development and test environments. In   <https://github.com/bkeepers/dotenv Ruby>,   <https://github.com/theskumar/python-dotenv Python> and-  <https://www.npmjs.com/package/dotenv Javascript> there are libraries+  <https://www.npmjs.com/package/dotenv JavaScript> there are libraries   to facilitate loading of configuration options from configuration   files. This library loads configuration to environment variables for   programs written in Haskell.@@ -22,67 +22,117 @@   To use, call `loadFile` from your application:   .   > import Configuration.Dotenv-  > loadFile False "/my/dotenvfile"+  > loadFile defaultConfig   .   This package also includes an executable that can be used   to inspect the results of applying one or more Dotenv files   to the environment, or for invoking your executables with   an environment after one or more Dotenv files is applied.   .-  See the <https://github.com/stackbuilders/dotenv-hs Github>+  See the <https://github.com/stackbuilders/dotenv-hs#readme Github>   page for more information on this package. license:             MIT license-file:        LICENSE author:              Justin Leitgeb-maintainer:          justin@stackbuilders.com-copyright:           2015 Stack Builders Inc.+tested-with:         ghc ==8.10 ghc ==9.0 ghc ==9.2 ghc ==9.4 ghc ==9.6+maintainer:          hackage@stackbuilders.com+copyright:           2015-Present Stack Builders Inc. category:            Configuration build-type:          Simple-extra-source-files:  spec/fixtures/.dotenv+extra-source-files:    spec/fixtures/.dotenv+                     , CHANGELOG.md+                     , README.md cabal-version:       >=1.10 bug-reports:         https://github.com/stackbuilders/dotenv-hs/issues +data-dir: spec/fixtures/+data-files:+  .dotenv+  .dotenv.example+  .dotenv.dup++flag static+  description:        Creates static binary.+  manual:             True+  default:            False++flag dev+  description:        Turn on development settings.+  manual:             True+  default:            False+ executable dotenv   main-is:             Main.hs-  -- other-modules:-  -- other-extensions:-  build-depends:         base >=4.5 && <4.9+  build-depends:         base >= 4.14 && < 5.0                        , base-compat >= 0.4-                       , optparse-applicative >=0.12 && < 0.13-                       , parsec >= 3.1.0 && <= 3.2+                       , dotenv+                       , optparse-applicative >= 0.11 && < 0.19+                       , megaparsec                        , process+                       , text+  other-modules: Paths_dotenv -  hs-source-dirs:      src+  hs-source-dirs:      app+  if flag(static)+    ghc-options:      -Wall -Werror+    ld-options:       -static -pthread+  if flag(dev)+    ghc-options:      -Wall -Werror+  else+    ghc-options:      -O2 -Wall   default-language:    Haskell2010  library-  exposed-modules:    Configuration.Dotenv.Parse-                    , Configuration.Dotenv+  exposed-modules:       Configuration.Dotenv+                       , Configuration.Dotenv.Internal+                       , Configuration.Dotenv.Environment+  other-modules:         Configuration.Dotenv.Parse+                       , Configuration.Dotenv.ParsedVariable+                       , Configuration.Dotenv.Text+                       , Configuration.Dotenv.Types -  build-depends:         base >=4.5 && <4.9-                       , base-compat >= 0.4-                       , parsec >= 3.1.0 && <= 3.2+  build-depends:         base >= 4.9 && < 5.0+                       , directory+                       , megaparsec >= 7.0.1 && < 10.0+                       , containers+                       , process >= 1.6.3.0 && < 1.7+                       , shellwords >= 0.1.3.0+                       , text+                       , exceptions >= 0.8 && < 0.11+                       , mtl >= 2.2.2 && < 2.4+                       , data-default-class >= 0.1.2 && < 0.2    hs-source-dirs:      src-  default-language:    Haskell2010   ghc-options:         -Wall-+  if flag(dev)+    ghc-options:      -Wall -Werror+  else+    ghc-options:      -O2 -Wall+  default-language:    Haskell2010  test-suite dotenv-test   type: exitcode-stdio-1.0-  hs-source-dirs: spec, src+  hs-source-dirs: spec   main-is: Spec.hs   other-modules:         Configuration.DotenvSpec+                       , Configuration.Dotenv.TextSpec                        , Configuration.Dotenv.ParseSpec -  build-depends:       base >=4.5 && <4.9-                       , base-compat >= 0.4-                       , parsec >= 3.1.0 && <= 3.2-                       , parseerror-eq-                       , hspec+  build-depends:       base >= 4.9 && < 5.0+                     , dotenv+                     , megaparsec+                     , hspec+                     , process+                     , text+                     , hspec-megaparsec >= 2.0 && < 3.0 +  build-tools:        hspec-discover   >= 2.0 && < 3.0++  if flag(dev)+    ghc-options:      -Wall -Werror+  else+    ghc-options:      -O2 -Wall   default-language:    Haskell2010-  ghc-options:         -Wall  source-repository head   type:     git
spec/Configuration/Dotenv/ParseSpec.hs view
@@ -1,89 +1,171 @@ {-# OPTIONS_GHC -fno-warn-orphans #-} -module Configuration.Dotenv.ParseSpec (spec) where--import Configuration.Dotenv.Parse (configParser)--import Test.Hspec (it, describe, shouldBe, Spec)+module Configuration.Dotenv.ParseSpec (main, spec) where -import Text.Parsec (ParseError, parse)+import           Configuration.Dotenv.Internal (ParsedVariable (..),+                                                VarFragment (..), VarValue (..),+                                                configParser)+import           Data.Void                     (Void)+import           Test.Hspec                    (Spec, context, describe, hspec,+                                                it)+import           Test.Hspec.Megaparsec         (shouldFailOn, shouldParse,+                                                shouldSucceedOn)+import           Text.Megaparsec               (ParseErrorBundle, parse) -import Text.ParseErrorEq ()+main :: IO ()+main = hspec spec  spec :: Spec spec = describe "parse" $ do   it "parses unquoted values" $-    parseConfig "FOO=bar" `shouldBe` Right [("FOO", "bar")]+    parseConfig "FOO=bar"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarLiteral "bar"])]    it "parses values with spaces around equal signs" $ do-    parseConfig "FOO =bar" `shouldBe` Right [("FOO", "bar")]-    parseConfig "FOO= bar" `shouldBe` Right [("FOO", "bar")]-    parseConfig "FOO =\t bar" `shouldBe` Right [("FOO", "bar")]+    parseConfig "FOO =bar"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarLiteral "bar"])]+    parseConfig "FOO= bar"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarLiteral "bar"])]+    parseConfig "FOO =\t bar"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarLiteral "bar"])]    it "parses double-quoted values" $-    parseConfig "FOO=\"bar\"" `shouldBe` Right [("FOO", "bar")]+    parseConfig "FOO=\"bar\""+      `shouldParse` [ParsedVariable "FOO" (DoubleQuoted [VarLiteral "bar"])]    it "parses single-quoted values" $-    parseConfig "FOO='bar'" `shouldBe` Right [("FOO", "bar")]+    parseConfig "FOO='bar'"+      `shouldParse` [ParsedVariable "FOO" (SingleQuoted [VarLiteral "bar"])]    it "parses escaped double quotes" $-    parseConfig "FOO=\"escaped\\\"bar\"" `shouldBe`-    Right [("FOO", "escaped\"bar")]+    parseConfig "FOO=\"escaped\\\"bar\""+      `shouldParse` [ParsedVariable "FOO" (DoubleQuoted [VarLiteral "escaped\"bar"])] +  it "supports CRLF line breaks" $+    parseConfig "FOO=bar\r\nbaz=fbb"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarLiteral "bar"]),+                     ParsedVariable "baz" (Unquoted [VarLiteral "fbb"])]+   it "parses empty values" $-    parseConfig "FOO=" `shouldBe` Right [("FOO", "")]+    parseConfig "FOO="+      `shouldParse` [ParsedVariable "FOO" (Unquoted [])] +  it "parses unquoted interpolated values" $ do+    parseConfig "FOO=$HOME"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarInterpolation "HOME"])]+    parseConfig "FOO=abc_$HOME"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarLiteral "abc_",+                                                     VarInterpolation "HOME"])+                    ]+    parseConfig "FOO=${HOME}"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarInterpolation "HOME"])]+    parseConfig "FOO=abc_${HOME}"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarLiteral "abc_",+                                                     VarInterpolation "HOME"])+                    ]++  it "parses double-quoted interpolated values" $ do+    parseConfig "FOO=\"$HOME\""+      `shouldParse` [ParsedVariable "FOO" (DoubleQuoted [VarInterpolation "HOME"])]+    parseConfig "FOO=\"abc_$HOME\""+      `shouldParse` [ParsedVariable "FOO" (DoubleQuoted [VarLiteral "abc_",+                                                         VarInterpolation "HOME"])+                    ]+    parseConfig "FOO=\"${HOME}\""+      `shouldParse` [ParsedVariable "FOO" (DoubleQuoted [VarInterpolation "HOME"])]+    parseConfig "FOO=\"abc_${HOME}\""+      `shouldParse` [ParsedVariable "FOO" (DoubleQuoted [VarLiteral "abc_",+                                                         VarInterpolation "HOME"])+                    ]++  it "parses single-quoted interpolated values as literals" $ do+    parseConfig "FOO='$HOME'"+      `shouldParse` [ParsedVariable "FOO" (SingleQuoted [VarLiteral "$HOME"])]+    parseConfig "FOO='abc_$HOME'"+      `shouldParse` [ParsedVariable "FOO" (SingleQuoted [VarLiteral "abc_$HOME"])]+    parseConfig "FOO='${HOME}'"+      `shouldParse` [ParsedVariable "FOO" (SingleQuoted [VarLiteral "${HOME}"])]+    parseConfig "FOO='abc_${HOME}'"+      `shouldParse` [ParsedVariable "FOO" (SingleQuoted [VarLiteral "abc_${HOME}"])]+   it "does not parse if line format is incorrect" $ do-    isLeft (parseConfig "lol$wut") `shouldBe` True-    isLeft (parseConfig "KEY=\nVALUE") `shouldBe` True-    isLeft (parseConfig "KEY\n=VALUE") `shouldBe` True+    parseConfig `shouldFailOn` "lol$wut"+    parseConfig `shouldFailOn` "KEY=\nVALUE"+    parseConfig `shouldFailOn` "KEY\n=VALUE"    it "expands newlines in quoted strings" $-    parseConfig "FOO=\"bar\nbaz\"" `shouldBe` Right [("FOO", "bar\nbaz")]+    parseConfig "FOO=\"bar\nbaz\""+      `shouldParse` [ParsedVariable "FOO" (DoubleQuoted [VarLiteral "bar\nbaz"])]    it "does not parse variables with hyphens in the name" $-    isLeft (parseConfig "FOO-BAR=foobar") `shouldBe` True+    parseConfig `shouldFailOn` "FOO-BAR=foobar" -  it "parses variables with '_' in the name" $-    parseConfig "FOO_BAR=foobar" `shouldBe` Right [("FOO_BAR", "foobar")]+  it "parses variables with \"_\" in the name" $+    parseConfig "FOO_BAR=foobar"+      `shouldParse` [ParsedVariable "FOO_BAR" (Unquoted [VarLiteral "foobar"])]    it "parses variables with digits after the first character" $-    parseConfig "FOO_BAR_12=foobar" `shouldBe` Right [("FOO_BAR_12", "foobar")]+    parseConfig "FOO_BAR_12=foobar"+      `shouldParse` [ParsedVariable "FOO_BAR_12" (Unquoted [VarLiteral "foobar"])]    it "allows vertical spaces after a quoted variable" $-    parseConfig "foo='bar' " `shouldBe` Right [("foo", "bar")]+    parseConfig "foo='bar' "+      `shouldParse` [ParsedVariable "foo" (SingleQuoted [VarLiteral "bar"])]    it "does not parse variable names beginning with a digit" $-    isLeft (parse configParser "null" "45FOO_BAR=foobar") `shouldBe` True+    parseConfig `shouldFailOn` "45FOO_BAR=foobar"    it "strips unquoted values" $-    parseConfig "foo=bar " `shouldBe` Right [("foo", "bar")]+    parseConfig "foo=bar "+      `shouldParse` [ParsedVariable "foo" (Unquoted [VarLiteral "bar"])]    it "ignores empty lines" $-    parseConfig "\n \t  \nfoo=bar\n \nfizz=buzz" `shouldBe`-    Right [("foo", "bar"), ("fizz", "buzz")]+    parseConfig "\n \t  \nfoo=bar\n \nfizz=buzz"+      `shouldParse` [ParsedVariable "foo" (Unquoted [VarLiteral "bar"]),+                     ParsedVariable "fizz" (Unquoted [VarLiteral "buzz"])]    it "ignores inline comments after unquoted arguments" $-    parseConfig "FOO=bar # this is foo" `shouldBe` Right [("FOO", "bar")]+    parseConfig "FOO=bar # this is foo"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarLiteral "bar"])]    it "ignores inline comments after quoted arguments" $-    parseConfig "FOO=\"bar\" # this is foo" `shouldBe` Right [("FOO", "bar")]+    parseConfig "FOO=\"bar\" # this is foo"+      `shouldParse` [ParsedVariable "FOO" (DoubleQuoted [VarLiteral "bar"])] -  it "allows # in quoted values" $-    parseConfig "foo=\"bar#baz\" # comment" `shouldBe`-    Right [("foo", "bar#baz")]+  it "allows \"#\" in quoted values" $+    parseConfig "foo=\"bar#baz\" # comment"+      `shouldParse` [ParsedVariable "foo" (DoubleQuoted [VarLiteral "bar#baz"])]    it "ignores comment lines" $-    parseConfig "\n\t \n\n # HERE GOES FOO \nfoo=bar" `shouldBe`-    Right [("foo", "bar")]+    parseConfig "\n\t \n\n # HERE GOES FOO \nfoo=bar"+      `shouldParse` [ParsedVariable "foo" (Unquoted [VarLiteral "bar"])]    it "doesn't allow more configuration options after a quoted value" $-    isLeft (parse configParser "null" "foo='bar'baz='buz'") `shouldBe` True+    parseConfig `shouldFailOn` "foo='bar'baz='buz'" +  context "$(command) interpolation" $ do+    it "parses a simple command" $ do+      parseConfig "FOO=$(command)"+        `shouldParse` [ParsedVariable "FOO" (Unquoted [CommandInterpolation "command" []])] -isLeft :: Either a b -> Bool-isLeft ( Left _ ) = True-isLeft _          = False+    it "parses a command anywhere in the value" $ do+      parseConfig "FOO=asdf_$(command)"+        `shouldParse` [ParsedVariable "FOO" (Unquoted [VarLiteral "asdf_", CommandInterpolation "command" []])] -parseConfig :: String -> Either ParseError [(String, String)]-parseConfig = parse configParser "(unknown)"+    it "parses a command with arguments" $ do+      parseConfig "FOO=$(foo-bar arg1 arg2)"+        `shouldParse` [ParsedVariable "FOO" (Unquoted [CommandInterpolation "foo-bar" ["arg1", "arg2"]])]++    it "parses a command with quoted arguments" $ do+      parseConfig "FOO=$(bin/foo \"arg 1\" arg2)"+        `shouldParse` [ParsedVariable "FOO" (Unquoted [CommandInterpolation "bin/foo" ["arg 1", "arg2"]])]++    it "parses a command with arguments separated by newlines" $ do+      parseConfig "FOO=$(foo.sh \"arg 1\"\narg2\n)"+        `shouldParse` [ParsedVariable "FOO" (Unquoted [CommandInterpolation "foo.sh" ["arg 1", "arg2"]])]++  it "parses empty content (when the file is empty)" $+    parseConfig `shouldSucceedOn` ""++parseConfig :: String -> Either (ParseErrorBundle String Void) [ParsedVariable]+parseConfig = parse configParser ""
+ spec/Configuration/Dotenv/TextSpec.hs view
@@ -0,0 +1,29 @@+module Configuration.Dotenv.TextSpec (main, spec) where++import           Configuration.Dotenv.Environment (lookupEnv, unsetEnv)+import           Configuration.Dotenv.Internal    (parseFile)++import           Test.Hspec++import           Control.Monad                    (liftM)+import qualified Data.Text                        as T++{-# ANN module ("HLint: ignore Reduce duplication" :: String) #-}++main :: IO ()+main = hspec spec++spec :: Spec+spec =+  describe "parseFile" $ after_ (unsetEnv "DOTENV") $ do+    it "returns variables from a file without changing the environment" $ do+      lookupEnv "DOTENV" `shouldReturn` Nothing++      liftM head (parseFile "spec/fixtures/.dotenv") `shouldReturn`+        (T.pack "DOTENV", T.pack "true")++      lookupEnv "DOTENV" `shouldReturn` Nothing++    it "recognizes unicode characters" $+      liftM (!! 1) (parseFile "spec/fixtures/.dotenv") `shouldReturn`+        (T.pack "UNICODE_TEST", T.pack "Manabí")
spec/Configuration/DotenvSpec.hs view
@@ -1,16 +1,26 @@-module Configuration.DotenvSpec (spec) where+{-# LANGUAGE CPP               #-}+{-# LANGUAGE OverloadedStrings #-} -import Configuration.Dotenv (load, loadFile)+module Configuration.DotenvSpec (main, spec) where -import Test.Hspec+import           Configuration.Dotenv             (Config (..), load, loadFile,+                                                   onMissingFile, parseFile)+import           Configuration.Dotenv.Environment (getEnvironment, lookupEnv,+                                                   setEnv, unsetEnv) -import System.Environment.Compat (lookupEnv, setEnv, unsetEnv) -{-# ANN module "HLint: ignore Reduce duplication" #-}+import           Test.Hspec +import           Control.Monad                    (liftM)+import           Data.Maybe                       (fromMaybe)+import           System.Process                   (readCreateProcess, shell)++main :: IO ()+main = hspec spec+ spec :: Spec spec = do-  describe "load" $ after_ (unsetEnv "foo") $ do+  describe "load" $ after_ clearEnvs $ do     it "loads the given list of configuration options to the environment" $ do       lookupEnv "foo" `shouldReturn` Nothing @@ -32,24 +42,143 @@        lookupEnv "foo" `shouldReturn` Just "new setting" -  describe "loadFile" $ after_ (unsetEnv "DOTENV") $ do+    it "loads the last set value in config list with duplicates" $ do+      lookupEnv "foo" `shouldReturn` Nothing+      lookupEnv "bar" `shouldReturn` Nothing++      load False [("foo", "first"), ("bar", "second"), ("foo", "third")]++      lookupEnv "foo" `shouldReturn` Just "third"+      lookupEnv "bar" `shouldReturn` Just "second"++  describe "loadFile" $ before_ setupEnv $ after_ clearEnvs $  do     it "loads the configuration options to the environment from a file" $ do       lookupEnv "DOTENV" `shouldReturn` Nothing -      loadFile False "spec/fixtures/.dotenv"+      loadFile sampleConfig        lookupEnv "DOTENV" `shouldReturn` Just "true"      it "respects predefined settings when overload is false" $ do       setEnv "DOTENV" "preset" -      loadFile False "spec/fixtures/.dotenv"+      loadFile sampleConfig        lookupEnv "DOTENV" `shouldReturn` Just "preset"      it "overrides predefined settings when overload is true" $ do       setEnv "DOTENV" "preset" -      loadFile True "spec/fixtures/.dotenv"+      loadFile sampleConfig { configOverride = True }        lookupEnv "DOTENV" `shouldReturn` Just "true"++    it "loads the last set value in dotenv file with duplicates" $ do+      lookupEnv "FOO" `shouldReturn` Nothing+      lookupEnv "BAR" `shouldReturn` Nothing++      loadFile (Config ["spec/fixtures/.dotenv.dup"] [] False False False True)++      lookupEnv "FOO" `shouldReturn` Just "last"+      lookupEnv "BAR" `shouldReturn` Just "tender"++    context "when the .env.example is present" $ do+      let config = sampleConfig { configExamplePath = ["spec/fixtures/.dotenv.example"]}+      context "when the needed env vars are missing" $+        it "should fail with an error call" $ do+          unsetEnv "ANOTHER_ENV"+          loadFile config `shouldThrow` anyErrorCall++      context "when the needed env vars are not missing" $ do+        it "should succeed when loading all of the needed env vars" $ do+          -- Load extra information+          me <- init <$> readCreateProcess (shell "whoami") ""+          home <- fromMaybe "" <$> lookupEnv "HOME"++          -- Load envs+          loadFile config++          -- Check existing envs+          lookupEnv "ENVIRONMENT" `shouldReturn` Just home+          lookupEnv "ME" `shouldReturn` Just me+          lookupEnv "DOTENV" `shouldReturn` Just "true"+          lookupEnv "UNICODE_TEST" `shouldReturn` Just "Manabí"+          lookupEnv "ANOTHER_ENV" `shouldReturn` Just "hello"++        it "succeeds when variable is preset" $ do+          setEnv "DOTENV" "preset"++          loadFile config++          lookupEnv "DOTENV" `shouldReturn` Just "preset"++  describe "parseFile" $ after_ clearEnvs $ do+    it "returns variables from a file without changing the environment" $ do+      lookupEnv "DOTENV" `shouldReturn` Nothing++      liftM head (parseFile "spec/fixtures/.dotenv") `shouldReturn`+        ("DOTENV", "true")++      lookupEnv "DOTENV" `shouldReturn` Nothing++    it "recognizes unicode characters" $+      liftM (!! 1) (parseFile "spec/fixtures/.dotenv") `shouldReturn`+        ("UNICODE_TEST", "Manabí")++    it "recognises environment variables" $ do+      home <- fromMaybe "" <$> lookupEnv "HOME"+      liftM (!! 2) (parseFile "spec/fixtures/.dotenv") `shouldReturn`+        ("ENVIRONMENT", home)++    it "recognises previous variables" $+      liftM (!! 3) (parseFile "spec/fixtures/.dotenv") `shouldReturn`+        ("PREVIOUS", "true")++    it "recognises commands" $ do+      me <- init <$> readCreateProcess (shell "whoami") ""+      liftM (!! 4) (parseFile "spec/fixtures/.dotenv") `shouldReturn`+        ("ME", me)++#if MIN_VERSION_base(4,11,0)+    it "recognizes blank variable" $+      liftM (!! 5) (parseFile "spec/fixtures/.dotenv") `shouldReturn`+        ("BLANK", "")+#endif++  describe "onMissingFile" $ after_ clearEnvs $ do+    context "when target file is present" $+      it "loading works as usual" $ do+        onMissingFile (loadFile sampleConfig {configOverride = True}) (return ())+        lookupEnv "DOTENV" `shouldReturn` Just "true"++    context "when target file is missing" $+      it "executes supplied handler instead" $+        onMissingFile (True <$ loadFile sampleConfig {configPath= ["spec/fixtures/.foo"], configOverride = True}) (return False)+          `shouldReturn` False++  describe "onDuplicatedKeys" $ after_ clearEnvs $ do+    context "when duplicates are forbidden" $ do+      it "throws an error on duplicated keys" $+        loadFile sampleConfig { configPath = ["spec/fixtures/.dotenv", "spec/fixtures/.dotenv"], allowDuplicates = False }+          `shouldThrow` anyIOException+      it "works as usual when there is no duplicated keys" $ do+        loadFile sampleConfig { allowDuplicates = False }+        lookupEnv "DOTENV" `shouldReturn` Just "true"++  describe "onConfigDryRun" $ after_ clearEnvs $+    context "when dry-run is enabled " $+      it "loads the variables" $ do+        loadFile sampleConfig { configPath = ["spec/fixtures/.dotenv"], configDryRun = True }+        lookupEnv "DOTENV" `shouldReturn` Just "true"++sampleConfig :: Config+sampleConfig = Config ["spec/fixtures/.dotenv"] [] False False False True++clearEnvs :: IO ()+clearEnvs =+  getEnvironment >>= mapM_ unsetEnv . fmap fst++setupEnv :: IO ()+setupEnv = do+  setEnv "ANOTHER_ENV" "hello"+  setEnv "HOME" "/home/me"
spec/fixtures/.dotenv view
@@ -1,1 +1,6 @@ DOTENV=true+UNICODE_TEST=Manabí+ENVIRONMENT="$HOME"+PREVIOUS="$DOTENV"+ME="$(whoami)"+BLANK=
+ spec/fixtures/.dotenv.dup view
@@ -0,0 +1,3 @@+FOO=first+BAR=tender+FOO=last
+ spec/fixtures/.dotenv.example view
@@ -0,0 +1,3 @@+DOTENV=+UNICODE_TEST=+ANOTHER_ENV=
src/Configuration/Dotenv.hs view
@@ -1,41 +1,168 @@-module Configuration.Dotenv (load, loadFile) where+-- |+-- Module      :  Configuration.Dotenv.Types+-- Copyright   :  © 2015–2020 Stack Builders Inc.+-- License     :  MIT+--+-- Maintainer  :  Stack Builders <hackage@stackbuilders.com>+-- Stability   :  experimental+-- Portability :  portable+--+-- This module contains common functions to load and read dotenv files.+{-# LANGUAGE RecordWildCards #-} -import System.Environment.Compat (lookupEnv, setEnv)+module Configuration.Dotenv+  ( -- * Dotenv Load Functions+    load+  , loadFile+  , parseFile+  , onMissingFile+  , configParser+    -- * Dotenv Types+  , module Configuration.Dotenv.Types+  ) where -import Configuration.Dotenv.Parse (configParser)+import           Configuration.Dotenv.Environment    (getEnvironment, lookupEnv,+                                                      setEnv)+import           Configuration.Dotenv.Parse          (configParser)+import           Configuration.Dotenv.ParsedVariable (interpolateParsedVariables)+import           Configuration.Dotenv.Types          (Config (..),+                                                      defaultConfig)+import           Control.Exception                   (throw)+import           Control.Monad                       (unless, when)+import           Control.Monad.Catch+import           Control.Monad.IO.Class              (MonadIO (..))+import           Control.Monad.Reader                (ReaderT, ask, runReaderT)+import           Control.Monad.Trans                 (lift)+import           Data.Function                       (on)+import           Data.List                           (intercalate, union, (\\))+import           Data.List.NonEmpty                  (NonEmpty (..))+import qualified Data.List.NonEmpty                  as NE+import           Data.Map                            (fromList, toList)+import           System.IO.Error                     (isDoesNotExistError)+import           Text.Megaparsec                     (errorBundlePretty, parse) -import Text.Parsec (parse)+-- | Monad Stack for the application+type DotEnv m a = ReaderT Config m a  -- | Loads the given list of options into the environment. Optionally -- override existing variables with values from Dotenv files. load ::-  Bool -- ^ Override existing settings?+     MonadIO m+  => Bool -- ^ Override existing settings?   -> [(String, String)] -- ^ List of values to be set in environment-  -> IO ()-load override = mapM_ (applySetting override)+  -> m ()+load override kv =+  runReaderT (mapM_ applySetting (nubByLastVar kv)) defaultConfig {configOverride = override} --- | Loads the options in the given file to the environment. Optionally--- override existing variables with values from Dotenv files.+-- | @loadFile@ parses the environment variables defined in the dotenv example+-- file and checks if they are defined in the dotenv file or in the environment.+-- It also allows to override the environment variables defined in the environment+-- with the values defined in the dotenv file. loadFile ::-  Bool        -- ^ Override existing settings?-  -> FilePath -- ^ A file containing options to load into the environment-  -> IO ()-loadFile override f = do-  contents <- readFile f+     MonadIO m+  => Config -- ^ Dotenv configuration+  -> m ()+loadFile config@Config {..} = do+  environment <- liftIO getEnvironment +  vars <- case (NE.nonEmpty configPath, NE.nonEmpty configExamplePath) of+    (Nothing, _) -> pure []+    (Just envs, Nothing) -> concat <$> mapM parseFile envs+    (Just envs, Just envExamples) -> do+      readVars <- concat <$> mapM parseFile envs+      neededKeys <- map fst . concat <$> mapM parseFile envExamples++      let+        presentKeys = (union `on` map fst) environment readVars+        missingKeys = neededKeys \\ presentKeys++      pure $+        if null missingKeys+          then readVars+          else error $ concat+            [ "The following variables are present in "+            , showPaths "one of " envExamples+            , ", but not set in the current environment, or "+            , showPaths "any of " envs+            , ": "+            , intercalate ", " missingKeys+            ]++  unless allowDuplicates $ (lookUpDuplicates . map fst) vars+  runReaderT (mapM_ applySetting (nubByLastVar vars)) config+ where+  showPaths :: String -> NonEmpty FilePath -> String+  showPaths _ (p:|[]) = p+  showPaths prefix ps = prefix <> intercalate ", " (NE.toList ps)++-- | Parses the given dotenv file and returns values /without/ adding them to+-- the environment.+parseFile ::+     MonadIO m+  => FilePath -- ^ A file containing options to read+  -> m [(String, String)] -- ^ Variables contained in the file+parseFile f = do+  contents <- liftIO $ readFile f   case parse configParser f contents of-    Left e        -> error $ "Failed to read file" ++ show e-    Right options -> load override options+    Left e        -> error $ errorBundlePretty e+    Right options -> liftIO $ interpolateParsedVariables options +applySetting ::+     MonadIO m+  => (String, String) -- ^ A key-value pair to set in the environment+  -> DotEnv m (String, String)+applySetting kv@(k, v) = do+  Config {..} <- ask+  if configOverride+    then info kv >> setEnv'+    else do+      res <- lift . liftIO $ lookupEnv k+      case res of+        Nothing -> info kv >> setEnv'+        Just _  -> return kv+  where+    setEnv' = lift . liftIO $ setEnv k v >> return kv -applySetting :: Bool -> (String, String) -> IO ()-applySetting override (key, value) =-  if override then-    setEnv key value+-- | The function logs in console when a variable is loaded into the+-- environment.+info :: MonadIO m => (String, String) -> DotEnv m ()+info (key, value) = do+  Config {..} <- ask+  when (configVerbose || configDryRun) $+    lift . liftIO $+    putStrLn $ infoStr (key, value) -  else do-    res <- lookupEnv key+-- | The function prints out the variables+infoStr :: (String, String) -> String+infoStr (key, value) =  "[INFO]: Load env '" ++ key ++ "' with value '" ++ value ++ "'" -    case res of-      Nothing -> setEnv key value-      Just _  -> return ()+-- | The helper allows to avoid exceptions in the case of missing files and+-- perform some action instead.+--+-- @since 0.3.1.0+onMissingFile ::+     MonadCatch m+  => m a -- ^ Action to perform that may fail because of missing file+  -> m a -- ^ Action to perform if file is indeed missing+  -> m a+onMissingFile f h = catchIf isDoesNotExistError f (const h)++-- | The helper throws an exception if the allow duplicate is set to False.+forbidDuplicates :: MonadIO m => String -> m ()+forbidDuplicates key =+  throw $+  userError $+  "[ERROR]: Env '" +++  key +++  "' is duplicated in a dotenv file. Please, fix that (or remove --no-dups)."++lookUpDuplicates :: MonadIO m => [String] -> m ()+lookUpDuplicates [] = return ()+lookUpDuplicates [_] = return ()+lookUpDuplicates (x:xs) =+  if x `elem` xs+    then forbidDuplicates x+    else lookUpDuplicates xs++nubByLastVar :: [(String, String)] -> [(String, String)]+nubByLastVar = toList . fromList
+ src/Configuration/Dotenv/Environment.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE CPP #-}++module Configuration.Dotenv.Environment+  ( getEnvironment+  , lookupEnv+  , setEnv+  , unsetEnv+  ) where++#if MIN_VERSION_base(4,11,0)+import System.Environment.Blank (getEnvironment, getEnv, unsetEnv)+import qualified System.Environment.Blank as Blank+#else+import System.Environment (getEnvironment, lookupEnv, setEnv, unsetEnv)+#endif++#if MIN_VERSION_base(4,11,0)+-- | Re-export "System.Environment" or "System.Environment.Blank" helpers.+lookupEnv :: String -> IO (Maybe String)+lookupEnv = getEnv++-- | Re-export "System.Environment" or "System.Environment.Blank" helpers.+setEnv :: String -> String -> IO ()+setEnv name value = Blank.setEnv name value True+#endif
+ src/Configuration/Dotenv/Internal.hs view
@@ -0,0 +1,12 @@+module Configuration.Dotenv.Internal+  ( module Configuration.Dotenv.Types+  , module Configuration.Dotenv.Parse+  , module Configuration.Dotenv.ParsedVariable+  , module Configuration.Dotenv.Text+  )+where++import           Configuration.Dotenv.Parse+import           Configuration.Dotenv.ParsedVariable+import           Configuration.Dotenv.Text+import           Configuration.Dotenv.Types
src/Configuration/Dotenv/Parse.hs view
@@ -1,79 +1,121 @@-module Configuration.Dotenv.Parse (configParser) where+-- |+-- Module      :  Configuration.Dotenv.Types+-- Copyright   :  © 2015–2020 Stack Builders Inc.+-- License     :  MIT+--+-- Maintainer  :  Stack Builders <hackage@stackbuilders.com>+-- Stability   :  experimental+-- Portability :  portable+--+-- Parser for files in dotenv format. These files generally consist of lines+-- with the form key=value. Comments and blank lines are also supported. More+-- information on the dotenv format can be found in the project README and the+-- test suite. -import Text.Parsec ((<|>), (<?>), anyChar, char, many, manyTill, try)-import Text.Parsec.Combinator (eof)-import Text.Parsec.String (Parser)-import Text.ParserCombinators.Parsec.Char-  (digit, letter, newline, noneOf, oneOf)+{-# LANGUAGE OverloadedStrings #-} -import Control.Applicative ((<*), (*>), (<$>))-import Data.Maybe (catMaybes)-import Control.Monad (liftM2)+module Configuration.Dotenv.Parse (configParser) where --- | Returns a parser for a Dotenv configuration file.  Accepts key--- and value arguments separated by "=".  Comments are allowed on--- lines by themselves and on blank lines.-configParser :: Parser [(String, String)]-configParser = catMaybes <$> many envLine <* eof+import           Configuration.Dotenv.ParsedVariable+import           Control.Applicative                 (empty, many, some, (<|>))+import           Control.Monad                       (void)+import           Data.Void                           (Void)+import qualified ShellWords+import           Text.Megaparsec                     (Parsec, anySingle,+                                                      between, eof, noneOf,+                                                      oneOf, sepEndBy, (<?>))+import           Text.Megaparsec.Char                (char, digitChar, eol,+                                                      letterChar, spaceChar)+import qualified Text.Megaparsec.Char.Lexer          as L +type Parser = Parsec Void String -envLine :: Parser (Maybe (String, String))-envLine = (comment <|> blankLine) *> return Nothing <|> Just <$> optionLine+data QuoteType = SingleQuote | DoubleQuote -blankLine :: Parser String-blankLine = many verticalSpace <* newline <?> "blank line"+-- | Returns a parser for a Dotenv configuration file. Accepts key and value+-- arguments separated by @=@. Comments in all positions are handled+-- appropriately.+configParser :: Parser [ParsedVariable]+configParser = between scn eof (sepEndBy envLine (eol <* scn)) -optionLine :: Parser (String, String)-optionLine = liftM2 (,)-  (many verticalSpace *> variableName <* variableValueSeparator)-  value+-- | Parse a single environment variable assignment.+envLine :: Parser ParsedVariable+envLine = ParsedVariable <$> (lexeme variableName <* lexeme (char '=')) <*> lexeme value  -- | Variables must start with a letter or underscore, and may contain -- letters, digits or '_' character after the first character.-variableName :: Parser String-variableName = liftM2 (:) (letter <|> char '_')-  (many (letter <|> char '_' <|> digit <?>-         unwords [ "valid non-leading shell variable character (alphanumeric,"-                 , "digit or underscore)" ]))+variableName :: Parser VarName+variableName = ((:) <$> firstChar <*> many otherChar) <?> "variable name"+  where+    firstChar = char '_'  <|> letterChar+    otherChar = firstChar <|> digitChar -  <?> unwords [ "shell variable name (letter or underscore followed"-              , "by alphanumeric characters or underscores)" ]+-- | Value: quoted or unquoted.+value :: Parser VarValue+value = (quotedValue <|> unquotedValue) <?> "variable value"+  where+    quotedValue   = quotedWith SingleQuote <|> quotedWith DoubleQuote+    unquotedValue = Unquoted <$> many (fragment "\'\" \t\n\r") -value :: Parser String-value = quotedValue <|> unquotedValue <?> "variable value"+-- | Parse a value quoted with given character.+quotedWith :: QuoteType -> Parser VarValue+quotedWith SingleQuote = SingleQuoted <$> between (char '\'') (char '\'') (many (literalValueFragment "\'\\"))+quotedWith DoubleQuote = DoubleQuoted <$> between (char '\"') (char '\"') (many (fragment "\"")) -quotedValue :: Parser String-quotedValue = (quotedWith '\'' <|> quotedWith '\"')-  <* (comment *> return () <|> many verticalSpace *> endOfLineOrInput)-  <?> "variable value surrounded with single or double quotes"+fragment :: String -> Parser VarFragment+fragment charsToEscape =+  interpolatedValueCommandInterpolation+    <|> interpolatedValueVarInterpolation+    <|> literalValueFragment ('$' : '\\' : charsToEscape) -unquotedValue :: Parser String-unquotedValue =-  manyTill anyChar (comment <|> many verticalSpace <* endOfLineOrInput)+interpolatedValueVarInterpolation :: Parser VarFragment+interpolatedValueVarInterpolation = VarInterpolation <$>+                            (between (symbol "${") (symbol "}") variableName <|>+                            (char '$' >> variableName))+  where+    symbol                = L.symbol sc --- | Based on a commented-string parser in:--- http://hub.darcs.net/navilan/XMonadTasks/raw/Data/Config/Lexer.hs-quotedWith :: Char -> Parser String-quotedWith c = char c *> many chr <* (char c <?> "closing quote character")+interpolatedValueCommandInterpolation :: Parser VarFragment+interpolatedValueCommandInterpolation = do+  ws <- between (symbol "$(") (symbol ")") ShellWords.parser+  pure $ case ws of+      (commandName:arguments) -> CommandInterpolation commandName arguments+      _ -> VarLiteral "" -- Interpret "$()" as an empty value+    where+      symbol = L.symbol sc -  where chr = esc <|> noneOf [c]-        esc = escape *> char c <?> "escape character"+literalValueFragment :: String -> Parser VarFragment+literalValueFragment charsToEscape = VarLiteral <$> some (escapedChar <|> normalChar)+  where+    escapedChar = (char '\\' *> anySingle) <?> "escaped character"+    normalChar  = noneOf charsToEscape <?> "unescaped character" -comment :: Parser String-comment = try (many verticalSpace *> char '#')-          *> manyTill anyChar endOfLineOrInput-          <?> "comment"+----------------------------------------------------------------------------+-- Boilerplate and whitespace setup -endOfLineOrInput :: Parser ()-endOfLineOrInput = newline *> return () <|> eof+-- | Lexeme wrapper that takes care of consuming of white space.+lexeme :: Parser a -> Parser a+lexeme = L.lexeme sc+{-# INLINE lexeme #-} -variableValueSeparator :: Parser ()-variableValueSeparator =-  many verticalSpace *> (char '=' <?> "variable-value separator character (=)")-  *> many verticalSpace *> return ()+-- | Space consumer. Consumes all white space including comments, but never+-- consumes newlines.+sc :: Parser ()+sc = L.space (void spaceChar') skipLineComment empty+{-# INLINE sc #-} -escape :: Parser Char-escape = char '\\'+-- | Just like 'sc' but also eats newlines.+scn :: Parser ()+scn = L.space (void spaceChar) skipLineComment empty+{-# INLINE scn #-} -verticalSpace :: Parser Char-verticalSpace = oneOf " \t"+-- | Just like 'spaceChar', but does not consume newlines.+spaceChar' :: Parser Char+spaceChar' = oneOf (" \t" :: String)+{-# INLINE spaceChar' #-}++-- | Skip line comment and stop before newline character without consuming+-- it.+skipLineComment :: Parser ()+skipLineComment = L.skipLineComment "#"+{-# INLINE skipLineComment #-}
+ src/Configuration/Dotenv/ParsedVariable.hs view
@@ -0,0 +1,73 @@+-- |+-- Module      :  Configuration.Dotenv.Types+-- Copyright   :  © 2015–2020 Stack Builders Inc.+-- License     :  MIT+--+-- Maintainer  :  Stack Builders <hackage@stackbuilders.com>+-- Stability   :  experimental+-- Portability :  portable+--+-- Helpers to interpolate environment variables++module Configuration.Dotenv.ParsedVariable (ParsedVariable(..),+                                            VarName,+                                            VarValue(..),+                                            VarContents,+                                            VarFragment(..),+                                            interpolateParsedVariables) where++import           Configuration.Dotenv.Environment (lookupEnv)+import           Control.Applicative              ((<|>))+import           Control.Monad                    (foldM)+import           System.Process                   (proc, readCreateProcess)++-- | Name and value pair+data ParsedVariable+  = ParsedVariable VarName VarValue deriving (Show, Eq)++-- | Variable name+type VarName = String++-- | Possible state of values+data VarValue+  = Unquoted VarContents+  | SingleQuoted VarContents+  | DoubleQuoted VarContents deriving (Show, Eq)++-- | List of VarFragment+type VarContents = [VarFragment]++-- | Placeholder for possible values+data VarFragment+  = VarInterpolation String+  | VarLiteral String+  | CommandInterpolation String [String] deriving (Show, Eq)++-- | Interpotales parsed variables+interpolateParsedVariables :: [ParsedVariable] -> IO [(String, String)]+interpolateParsedVariables = fmap reverse . foldM addInterpolated []++addInterpolated :: [(String, String)] -> ParsedVariable -> IO [(String, String)]+addInterpolated previous (ParsedVariable name value) = (: previous) <$> ((,) name <$> interpolate previous value)++interpolate :: [(String, String)] -> VarValue -> IO String+interpolate _        (SingleQuoted contents) = return $ joinContents contents+interpolate previous (DoubleQuoted contents) = interpolateContents previous contents+interpolate previous (Unquoted     contents) = interpolateContents previous contents++interpolateContents :: [(String, String)] -> VarContents -> IO String+interpolateContents previous contents = concat <$> mapM (interpolateFragment previous) contents++interpolateFragment :: [(String, String)] -> VarFragment -> IO String+interpolateFragment _        (VarLiteral       value  ) = return value+interpolateFragment previous (VarInterpolation varname) = fromPreviousOrEnv >>= maybe (return "") return+  where+    fromPreviousOrEnv = (lookup varname previous <|>) <$> lookupEnv varname+interpolateFragment _ (CommandInterpolation commandName args) = init <$> readCreateProcess (proc commandName args) ""++joinContents :: VarContents -> String+joinContents = concatMap fragmentToString+  where+    fragmentToString (CommandInterpolation commandName args) = unwords $ commandName : args+    fragmentToString (VarInterpolation value)                = value+    fragmentToString (VarLiteral value)                      = value
+ src/Configuration/Dotenv/Text.hs view
@@ -0,0 +1,29 @@+-- |+-- Module      :  Configuration.Dotenv.Types+-- Copyright   :  © 2015–2020 Stack Builders Inc.+-- License     :  MIT+--+-- Maintainer  :  Stack Builders <hackage@stackbuilders.com>+-- Stability   :  experimental+-- Portability :  portable+--+-- Provides a Data.Text interface for retrieving variables in a dotenv file.++module Configuration.Dotenv.Text (parseFile) where++import qualified Configuration.Dotenv++import           Control.Arrow          ((***))+import qualified Data.Text              as T++import           Control.Monad          (liftM)++import           Control.Monad.IO.Class (MonadIO (..))++-- | Parses the given dotenv file and returns values /without/ adding them to+-- the environment.+parseFile ::+  MonadIO m =>+  FilePath -- ^ A file containing options to read+  -> m [(T.Text, T.Text)] -- ^ Variables contained in the file+parseFile f = map (T.pack *** T.pack) `liftM` Configuration.Dotenv.parseFile f
+ src/Configuration/Dotenv/Types.hs view
@@ -0,0 +1,43 @@+-- |+-- Module      :  Configuration.Dotenv.Types+-- Copyright   :  © 2015–2020 Stack Builders Inc.+-- License     :  MIT+--+-- Maintainer  :  Stack Builders <hackage@stackbuilders.com>+-- Stability   :  experimental+-- Portability :  portable+--+-- Provides the types with extra options for loading a dotenv file.++module Configuration.Dotenv.Types+  ( Config(..)+  , defaultConfig+  )+  where++import           Data.Default.Class++-- | Configuration Data Types with extra options for executing dotenv.+data Config = Config+  { configPath        :: [FilePath] -- ^ The paths for the .env files+  , configExamplePath :: [FilePath] -- ^ The paths for the .env.example files+  , configOverride    :: Bool     -- ^ Flag to allow override env variables+  , configVerbose     :: Bool     -- ^ Flag to log the loaded variables and other useful information+  , configDryRun      :: Bool     -- ^ Flag to print out the variables loaded without executing the program+  , allowDuplicates   :: Bool     -- ^ Flag to allow duplicate variables+  } deriving (Eq, Show)++-- | Default configuration. Use .env file without .env.example strict envs and+-- without overriding.+defaultConfig :: Config+defaultConfig =+  Config+    { configExamplePath = []+    , configOverride = False+    , configPath = [ ".env" ]+    , configVerbose = False+    , configDryRun = False+    , allowDuplicates = True+    }++instance Default Config where def = defaultConfig
− src/Main.hs
@@ -1,42 +0,0 @@-module Main where--import Options.Applicative--import Configuration.Dotenv (loadFile)--import System.Process (system)-import System.Exit (exitWith)--data Options = Options-  { files    :: [String]-  , program  :: String-  , overload :: Bool-  } deriving (Show)--main :: IO ()-main = execParser opts >>= dotEnv-  where-    opts = info (helper <*> config)-      ( fullDesc-     <> progDesc "Runs PROGRAM after loading options from FILE"-     <> header "dotenv - loads options from dotenv files" )--config :: Parser Options-config = Options-     <$> some (strOption (-                  long "file"-                  <> short 'f'-                  <> metavar "FILE"-                  <> help "File to read for options" ))--     <*> argument str (metavar "PROGRAM")--     <*> switch ( long "overload"-                  <> short 'o'-                  <> help "Specify this flag to override existing variables" )--dotEnv :: Options -> IO ()-dotEnv opts = do-  mapM_ (loadFile (overload opts)) (files opts)-  code <- system $ program opts-  exitWith code