pax_global_header00006660000000000000000000000064145235117750014524gustar00rootroot0000000000000052 comment=55d5bfd186fb600010fd1004c0ba575afa17d0ed .github/000077500000000000000000000000001452351177500124305ustar00rootroot00000000000000.github/dependabot.yml000066400000000000000000000003151452351177500152570ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "pip" directory: "/" schedule: interval: "weekly" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" .github/workflows/000077500000000000000000000000001452351177500144655ustar00rootroot00000000000000.github/workflows/python-package.yaml000066400000000000000000000032071452351177500202650ustar00rootroot00000000000000name: CI/CD Pipeline on: - push - pull_request jobs: test: name: Test Python ${{ matrix.python-version }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: - ubuntu-latest - macos-latest - windows-latest python-version: - "3.8" - "3.9" - "3.10" - "3.11" - "3.12" exclude: # 3.8 on windows fails due to some pip issue - os: windows-latest python-version: "3.8" steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - run: | # ensure we're using *our* dotenv during testing and not some other one # installed on the system, e.g. gh machines apparently have sometimes # the ruby dotenv package installed pip install --upgrade -r requirements-dev.txt pip install -e .['dev'] make test lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: "3.x" - run: | make lint mypy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: "3.x" - run: | make mypy test-release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: "3.x" - run: | make test-release .gitignore000066400000000000000000000001531452351177500130570ustar00rootroot00000000000000__pycache__/ *.pyc *.egg-info/ build/ dist/ .coverage htmlcov/ .mypy_cache/ .pytest_cache/ venv/ .idea/ CHANGELOG.md000066400000000000000000000046541452351177500127120ustar00rootroot00000000000000# Changelog ## [3.2.2] -- 2023-11-10 * replaced flake8 with ruff * when building the debian package, don't run coverage when running tests ## [3.2.1] -- 2023-08-27 * updated debian/watch * updated dev-dependencies ## [3.2.0] -- 2023-07-01 * on POSIX systems we don't fork a new child process anymore but use `exec*` to replace the `dotenv` process * Dropped Python 3.7 support * replaced setup.py/.cfg with pyproject.toml * modernized github actions: * don't run linter and mypy on all platforms, only one * run test-release * updated dev-dependencies ## [3.1.1] -- 2023-04-13 * updated dependencies: * mypy * pytest * pytest-cov * wheel * Debian: * added htmlcov and .mypy_cache to extended-diff-ignore * bump debhelper from 11 -> 13 * use debhelper-compat * use standards-version 4.6.2 ## [3.1.0] -- 2022-09-07 * added type hints and mypy --strict to test suite * updated dependencies: * flake8 * pytest * twine ## [3.0.1] - 2022-06-26 * bumped version (no changes) ## [3.0.0] - 2022-05-31 * removed python 3.6 support * added dependabot * updated makefile ## [2.2.0] - 2020-10-30 * Allow for missing .env file -- in this case the command will be executed without setting any environment variables. The previous behaviour was to fail with a FileNotFoundError * Migrated from TravisCI to github actions. We test now on Linux, Mac and Windows x all supported Python versions! * Fixed tests under windows, where NamedTemporaryFile cannot be opened twice. * refactored __main__.py into cli.py and wrapped argparsing into dedicated function * bumped minimal Python version to 3.6 * Added 3.8, 3.9 to travis tests * Cleaned up Makefile * Added twine to dev-dependencies ## [2.1.0] - 2020-10-27 * make sure child process terminates when dotenv terminates * measure coverage for tests as well * skip coverage report for files w/ complete coverage * use twine for uploading to pypi ## [2.0.1] - 2019-09-07 * Version bump for Debian source-only upload ## [2.0.0] - 2019-08-03 * Differentiate single vs double quotes ## [1.3.0] - 2019-05-11 * Support for lines starting with `export` * Support for empty values ## [1.2.0] - 2019-05-10 * Fixed newlines * Added more tests ## [1.1.0] - 2019-04-28 * Added Bash completion and provide it via sdist and Debian package ## [1.0.2] - 2019-04-14 * Debian package * Fixed Travis-CI pipeline and added tests for py37 ## [1.0.0] - 2018-10-14 * Initial Release LICENSE000066400000000000000000000020601452351177500120730ustar00rootroot00000000000000MIT License Copyright (c) 2018 Bastian Venthur 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. MANIFEST.in000066400000000000000000000000371452351177500126260ustar00rootroot00000000000000recursive-include completion * Makefile000066400000000000000000000021441452351177500125310ustar00rootroot00000000000000# system python interpreter. used only to create virtual environment PY = python3 VENV = venv BIN=$(VENV)/bin ifeq ($(OS), Windows_NT) BIN=$(VENV)/Scripts PY=python endif .PHONY: all all: lint mypy test test-release $(VENV): requirements-dev.txt pyproject.toml $(PY) -m venv $(VENV) $(BIN)/pip install --upgrade -r requirements-dev.txt $(BIN)/pip install -e .['dev'] touch $(VENV) .PHONY: test test: $(VENV) $(BIN)/pytest .PHONY: mypy mypy: $(VENV) $(BIN)/mypy .PHONY: lint lint: $(VENV) $(BIN)/ruff check . .PHONY: build build: $(VENV) rm -rf dist $(BIN)/python3 -m build .PHONY: test-release test-release: $(VENV) build $(BIN)/twine check dist/* .PHONY: release release: $(VENV) build $(BIN)/twine upload dist/* VERSION = $(shell python3 -c 'from dotenv_cli import __VERSION__; print(__VERSION__)') tarball: git archive --output=../dotenv-cli_$(VERSION).orig.tar.gz HEAD .PHONY: clean clean: rm -rf build dist *.egg-info rm -rf $(VENV) find . -type f -name *.pyc -delete find . -type d -name __pycache__ -delete # coverage rm -rf htmlcov .coverage rm -rf .mypy_cache rm -rf .pytest_cache README.md000066400000000000000000000044531452351177500123550ustar00rootroot00000000000000# dotenv CLI Dotenv-CLI is a simple package that provides the `dotenv` command. It reads the `.env` file from the current directory puts the contents in the environment and executes the given command. `dotenv` supports alternative `.env` files like `.env.development` via the `-e` or `--dotenv` parametes. `dotenv` provides bash completion, so you can use `dotenv` like this: ```bash $ dotenv make all clean docs lint release test ``` ## Install ### Using PyPi dotenv-cli is [available on PyPi][pypi], you can install it via: [pypi]: https://pypi.org/project/dotenv-cli/ ```bash $ pip install dotenv-cli ``` ### On Debian and Ubuntu Alternatively, you can install dotenv-cli on Debian based distributions via: ```bash # apt-get install python3-dotenv-cli ``` ## Usage Create an `.env` file in the root of your project and populate it with some values like so: ```sh SOME_SECRET=donttrythisathome SOME_CONFIG=foo ``` Just prepend the command you want to run with the extra environment variables from the `.env` file with `dotenv`: ```bash $ dotenv some-command ``` and those variables will be available in your environment variables. ## Rules The parser understands the following: * Basic unquoted values (`BASIC=basic basic`) * Lines starting with `export` (`export EXPORT=foo`), so you can `source` the file in bash * Lines starting with `#` are ignored (`# Comment`) * Empty values (`EMPTY=`) become empty strings * Inner quotes are maintained in basic values: `INNER_QUOTES=this 'is' a test` or `INNER_QUOTES2=this "is" a test` * White spaces are trimmed from unquoted values: `TRIM_WHITESPACE= foo ` and maintained in quoted values: `KEEP_WHITESPACE=" foo "` * Interpret escapes (e.g. `\n`) in double quoted values, keep them as-is in single quoted values. Example `.env` file: ```sh BASIC=basic basic export EXPORT=foo EMPTY= INNER_QUOTES=this 'is' a test INNER_QUOTES2=this "is" a test TRIM_WHITESPACE= foo KEEP_WHITESPACE=" foo " MULTILINE_DQ="multi\nline" MULTILINE_SQ='multi\nline' MULTILINE_NQ=multi\nline # # some comment ``` becomes: ```sh $ dotenv env BASIC=basic basic EXPORT=foo EMPTY= INNER_QUOTES=this 'is' a test INNER_QUOTES2=this "is" a test TRIM_WHITESPACE=foo KEEP_WHITESPACE= foo MULTILINE_DQ=multi line MULTILINE_SQ=multi\nline MULTILINE_NQ=multi\nline ``` completion/000077500000000000000000000000001452351177500132415ustar00rootroot00000000000000completion/bash/000077500000000000000000000000001452351177500141565ustar00rootroot00000000000000completion/bash/dotenv000066400000000000000000000015451452351177500154050ustar00rootroot00000000000000_dotenv() { local cur prev words cword _init_completion || return # find completion(s) for command executed with dotenv local i for (( i=1; i <= COMP_CWORD; i++ )); do if [[ ${COMP_WORDS[i]} != -* ]]; then _command_offset $i return fi # if current option requires a parameter, ignore the next one case "${COMP_WORDS[i]}" in -e|--dotenv) ((i++)) ;; esac done # completion for dotenv files case "$prev" in -e|--dotenv) COMPREPLY=( $( compgen -f -- "$cur" ) ) return ;; esac # check dotenv's options if [[ $cur == -* ]]; then COMPREPLY=( $( compgen -W '$( _parse_help "$1" )' -- "$cur" ) ) return fi } && complete -F _dotenv dotenv # vim: filetype=sh debian/000077500000000000000000000000001452351177500123125ustar00rootroot00000000000000debian/changelog000066400000000000000000000076411452351177500141740ustar00rootroot00000000000000dotenv-cli (3.2.2-1) unstable; urgency=medium * replaced flake8 with ruff * when building the debian package, don't run coverage when executing the tests (Closes: #1040232) -- Bastian Venthur Fri, 10 Nov 2023 21:16:08 +0100 dotenv-cli (3.2.1-1) unstable; urgency=medium * fixed debian/watch * updated dev-dependencies -- Bastian Venthur Sun, 27 Aug 2023 15:16:48 +0200 dotenv-cli (3.2.0-1) unstable; urgency=medium * on POSIX systems we don't fork a new child process anymore but use `exec*` to replace the `dotenv` process * Dropped Python 3.7 support * replaced setup.py/.cfg with pyproject.toml * modernized github actions: * don't run linter and mypy on all platforms, only one * run test-release * updated dev-dependencies * re-enabled tests on building debian package -- Bastian Venthur Sat, 01 Jul 2023 13:24:59 +0200 dotenv-cli (3.1.1-1) unstable; urgency=medium * new upstream version * added htmlcov and .mypy_cache to extended-diff-ignore * bump debhelper from 11 -> 13 * use debhelper-compat * use standards-version 4.6.2 -- Bastian Venthur Thu, 13 Apr 2023 10:20:04 +0200 dotenv-cli (3.1.0-1) unstable; urgency=medium * added type hints * updated dependencies -- Bastian Venthur Wed, 07 Sep 2022 20:15:01 +0200 dotenv-cli (3.0.1-2) unstable; urgency=medium * source-only upload -- Bastian Venthur Wed, 29 Jun 2022 21:33:12 +0200 dotenv-cli (3.0.1-1) unstable; urgency=medium * removed python 3.6 support * added dependabot * updated makefile -- Bastian Venthur Tue, 31 May 2022 19:58:13 +0200 dotenv-cli (2.2.0-1) unstable; urgency=medium * Allow for missing .env file -- in this case the command will be executed without setting any environment variables. The previous behaviour was to fail with a FileNotFoundError * Migrated from TravisCI to github actions. We test now on Linux, Mac and Windows x all supported Python versions! * Fixed tests under windows, where NamedTemporaryFile cannot be opened twice. * refactored __main__.py into cli.py and wrapped argparsing into dedicated function * bumped minimal Python version to 3.6 * Added 3.8, 3.9 to travis tests * Cleaned up Makefile * Added twine to dev-dependencies -- Bastian Venthur Fri, 30 Oct 2020 16:48:44 +0100 dotenv-cli (2.1.0-1) unstable; urgency=medium * make sure child process terminates when dotenv terminates * measure coverage for tests as well * skip coverage report for files w/ complete coverage * use twine for uploading to pypi -- Bastian Venthur Tue, 27 Oct 2020 22:21:31 +0100 dotenv-cli (2.0.1-1) unstable; urgency=medium * Source only upload * Minor version bump -- Bastian Venthur Sat, 07 Sep 2019 13:30:25 +0200 dotenv-cli (2.0.0-1) unstable; urgency=medium * Interpret escapes only in double quoted values, keep them as is in single quoted -- Bastian Venthur Sat, 03 Aug 2019 14:38:36 +0200 dotenv-cli (1.3.0-1) unstable; urgency=medium * Added support for export-lines * Added support for empty values -- Bastian Venthur Sat, 11 May 2019 14:34:47 +0200 dotenv-cli (1.2.0-1) unstable; urgency=medium * Fixed newlines -- Bastian Venthur Fri, 10 May 2019 19:47:57 +0200 dotenv-cli (1.1.0-1) unstable; urgency=medium * Added bash completion -- Bastian Venthur Sun, 28 Apr 2019 12:56:49 +0200 dotenv-cli (1.0.2-1) unstable; urgency=medium * Conflict with ruby-dotenv (Closes: #926916) -- Bastian Venthur Sun, 14 Apr 2019 17:42:29 +0200 dotenv-cli (1.0.0-1) unstable; urgency=medium * Initial release (Closes: #923856) -- Bastian Venthur Wed, 06 Mar 2019 09:55:47 +0100 debian/control000066400000000000000000000016151452351177500137200ustar00rootroot00000000000000Source: dotenv-cli Section: python Priority: optional Maintainer: Bastian Venthur Build-Depends: debhelper-compat (= 13), pybuild-plugin-pyproject, dh-python, python3-all, python3-setuptools, python3-pytest, python3-pytest-cov, bash-completion Standards-Version: 4.6.2 Homepage: https://github.com/venthur/dotenv-cli Vcs-Browser: https://github.com/venthur/dotenv-cli Vcs-Git: https://github.com/venthur/dotenv-cli.git Testsuite: autopkgtest-pkg-python Package: python3-dotenv-cli Architecture: all Depends: ${python3:Depends}, ${misc:Depends} Conflicts: ruby-dotenv Description: CLI that loads .env configuration This package provides the dotenv command. It reads the .env file from the current directory puts the contents in the environment and executes the given command. . dotenv supports alternative .env files like .env.development via the -e or --dotenv parameters. debian/copyright000066400000000000000000000025241452351177500142500ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: dotenv-cli Source: https://github.com/venthur/dotenv-cli Files: * Copyright: 2018 Bastian Venthur License: MIT Files: debian/* Copyright: 2019 Bastian Venthur License: MIT License: MIT 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. debian/python3-dotenv-cli.bash-completion000066400000000000000000000000271452351177500207650ustar00rootroot00000000000000completion/bash/dotenv debian/rules000077500000000000000000000012101452351177500133640ustar00rootroot00000000000000#!/usr/bin/make -f # See debhelper(7) (uncomment to enable) # output every command that modifies files on the build system. #export DH_VERBOSE = 1 export PYBUILD_NAME=dotenv-cli export PYBUILD_SYSTEM=pyproject export PYBUILD_TEST_ARGS=--no-cov %: dh $@ --with python3,bash-completion --buildsystem=pybuild # If you need to rebuild the Sphinx documentation # Add spinxdoc to the dh --with line #override_dh_auto_build: # dh_auto_build # PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bhtml docs/ build/html # HTML generator # PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bman docs/ build/man # Manpage generator debian/source/000077500000000000000000000000001452351177500136125ustar00rootroot00000000000000debian/source/format000066400000000000000000000000141452351177500150200ustar00rootroot000000000000003.0 (quilt) debian/source/options000066400000000000000000000005351452351177500152330ustar00rootroot00000000000000extend-diff-ignore = "^[^/]*[.]egg-info/" # below are my exceptions, probably best to try to mirror the .gitignore extend-diff-ignore = "^build/" extend-diff-ignore = "^dist/" extend-diff-ignore = "^.pytest_cache/" extend-diff-ignore = "^htmlcov/" extend-diff-ignore = "^.coverage" extend-diff-ignore = "^.mypy_cache/" extend-diff-ignore = "^venv/" debian/watch000066400000000000000000000004131452351177500133410ustar00rootroot00000000000000# You can run the "uscan" command to check for upstream updates and more. # See uscan(1) for format # Compulsory line, this is a version 4 file version=4 # Direct Git opts="mode=git" https://github.com/venthur/dotenv-cli.git \ refs/tags/v([\d\.]+) debian uupdate dotenv_cli/000077500000000000000000000000001452351177500132165ustar00rootroot00000000000000dotenv_cli/__init__.py000066400000000000000000000001021452351177500153200ustar00rootroot00000000000000from dotenv_cli.version import __VERSION__ as __VERSION__ # noqa dotenv_cli/cli.py000066400000000000000000000023401452351177500143360ustar00rootroot00000000000000"""Command line interface for dotenv-cli.""" # remove when we don't support py38 anymore from __future__ import annotations import argparse import logging from typing import NoReturn from dotenv_cli import __VERSION__ from dotenv_cli.core import run_dotenv logger = logging.getLogger(__name__) def parse_args(args: list[str] | None = None) -> argparse.Namespace: """Parse arguments. Paramters --------- args This if for debugging only. Returns ------- argparse.Namespace """ parser = argparse.ArgumentParser() parser.add_argument( "-e", "--dotenv", help="Alternative .env file", default=".env", ) parser.add_argument( "command", help="Shell command to execute", nargs=argparse.REMAINDER, ) parser.add_argument( "--version", action="version", version=__VERSION__, ) return parser.parse_args(args) def main() -> NoReturn | int: """Run dotenv. This function parses sys.argv and runs dotenv. Returns ------- int the return value """ args = parse_args() if not args.command: return 0 return run_dotenv(args.dotenv, args.command) dotenv_cli/core.py000066400000000000000000000060611452351177500145230ustar00rootroot00000000000000"""Core functions.""" # remove when we don't support py38 anymore from __future__ import annotations import atexit import logging import os from subprocess import Popen from typing import NoReturn logger = logging.getLogger(__name__) def read_dotenv(filename: str) -> dict[str, str]: """Read dotenv file. Parameters ---------- filename path to the filename Returns ------- dict """ try: with open(filename) as fh: data = fh.read() except FileNotFoundError: logger.warning( f"{filename} does not exist, continuing without " "setting environment variables." ) data = "" res = {} for line in data.splitlines(): logger.debug(line) line = line.strip() # ignore comments if line.startswith("#"): continue # ignore empty lines or lines w/o '=' if "=" not in line: continue key, value = line.split("=", 1) # allow export if key.startswith("export "): key = key.split(" ", 1)[-1] key = key.strip() value = value.strip() # remove quotes (not sure if this is standard behaviour) if len(value) >= 2 and value[0] == value[-1] == '"': value = value[1:-1] # escape escape characters value = bytes(value, "utf-8").decode("unicode-escape") elif len(value) >= 2 and value[0] == value[-1] == "'": value = value[1:-1] res[key] = value logger.debug(res) return res def run_dotenv(filename: str, command: list[str]) -> NoReturn | int: """Run dotenv. This function executes the commands with the environment variables parsed from filename. Parameters ---------- filename path to the .env file command command to execute Returns ------- NoReturn | int The exit status code in Windows. In POSIX-compatible systems, the function does not return normally. """ # read dotenv dotenv = read_dotenv(filename) # update env env = os.environ.copy() env.update(dotenv) # in POSIX, we replace the current process with the command, execvpe does # not return if os.name == "posix": os.execvpe(command[0], command, env) # in Windows, we spawn a new process # execute proc = Popen( command, # stdin=PIPE, # stdout=PIPE, # stderr=STDOUT, universal_newlines=True, bufsize=0, shell=False, env=env, ) def terminate_proc() -> None: """Kill child process. All signals should be forwarded to the child processes automatically, however child processes are also free to ignore some of them. With this we make sure the child processes get killed once dotenv exits. """ proc.kill() # register atexit.register(terminate_proc) _, _ = proc.communicate() # unregister atexit.unregister(terminate_proc) return proc.returncode dotenv_cli/version.py000066400000000000000000000001161452351177500152530ustar00rootroot00000000000000"""Version information for the dotenv-cli package.""" __VERSION__ = "3.2.2" pyproject.toml000066400000000000000000000027121452351177500140060ustar00rootroot00000000000000[build-system] requires = ["setuptools>=64.0"] build-backend = "setuptools.build_meta" [project] name = "dotenv-cli" authors = [ { name="Bastian Venthur", email="mail@venthur.de" } ] description = "Simple dotenv CLI." keywords = ["dotenv", "cli", ".env"] readme = "README.md" license = { file = "LICENSE" } requires-python = ">=3.8" dynamic = ["version"] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", ] [project.scripts] dotenv = "dotenv_cli.cli:main" [project.urls] 'Source' = 'https://github.com/venthur/dotenv-cli' 'Changelog' = 'https://github.com/venthur/dotenv-cli/blob/master/CHANGELOG.md' [project.optional-dependencies] dev = [ "pytest", "pytest-cov", "ruff", "build", "twine", "mypy" ] [tool.setuptools.dynamic] version = { attr = "dotenv_cli.__VERSION__" } [tool.setuptools] packages = ["dotenv_cli"] [tool.pytest.ini_options] addopts = [ "--cov=dotenv_cli", "--cov=tests", "--cov-report=html", "--cov-report=term-missing:skip-covered" ] [tool.ruff] select = [ "F", # pyflakes "E", "W", # pycodestyle "C90", # mccabe "I", # isort "D", # pydocstyle "UP" # pyupgrade ] line-length = 79 target-version = "py38" [tool.ruff.pydocstyle] convention = "numpy" [tool.mypy] files = ["dotenv_cli", "tests"] strict = true requirements-dev.txt000066400000000000000000000001221452351177500151230ustar00rootroot00000000000000twine==4.0.2 build==1.0.3 pytest==7.4.3 pytest-cov==4.1.0 ruff==0.1.4 mypy==1.6.1 tests/000077500000000000000000000000001452351177500122325ustar00rootroot00000000000000tests/__init__.py000066400000000000000000000000341452351177500143400ustar00rootroot00000000000000"""Tests for dotenv-cli.""" tests/test_cli.py000066400000000000000000000033301452351177500144110ustar00rootroot00000000000000"""Test the CLI interface.""" import tempfile from pathlib import Path from subprocess import PIPE, run from typing import Iterator import pytest DOTENV_FILE = """ # comment=foo TEST=foo TWOLINES='foo\nbar' TEST_COMMENT=foo # bar LINE_WITH_EQUAL='foo=bar' """ @pytest.fixture def dotenvfile() -> Iterator[Path]: """Provide temporary dotenv file.""" _file = Path.cwd() / ".env" with _file.open("w") as fh: fh.write(DOTENV_FILE) yield _file _file.unlink() def test_stdout(dotenvfile: Path) -> None: """Test stdout.""" proc = run(["dotenv", "echo", "test"], stdout=PIPE) assert b"test" in proc.stdout def test_stderr(dotenvfile: Path) -> None: """Test stderr.""" proc = run(["dotenv echo test 1>&2"], stderr=PIPE, shell=True) assert b"test" in proc.stderr def test_returncode(dotenvfile: Path) -> None: """Test returncode.""" proc = run(["dotenv", "false"]) assert proc.returncode == 1 proc = run(["dotenv", "true"]) assert proc.returncode == 0 def test_alternative_dotenv() -> None: """Test alternative dotenv file.""" with tempfile.NamedTemporaryFile("w", delete=False) as f: f.write("foo=bar") proc = run(["dotenv", "-e", f.name, "env"], stdout=PIPE) assert b"foo=bar" in proc.stdout proc = run(["dotenv", "--dotenv", f.name, "env"], stdout=PIPE) assert b"foo=bar" in proc.stdout def test_nonexisting_dotenv() -> None: """Test non-existing dotenv file.""" proc = run(["dotenv", "-e", "/tmp/i.dont.exist", "true"], stderr=PIPE) assert proc.returncode == 0 assert b"does not exist" in proc.stderr def test_no_command() -> None: """Test no command.""" proc = run(["dotenv"]) assert proc.returncode == 0 tests/test_core.py000066400000000000000000000077451452351177500146100ustar00rootroot00000000000000"""Test core module.""" import tempfile import pytest from dotenv_cli import core def test_full() -> None: """Test full dotenv file.""" TEST = r""" BASIC=basic basic export EXPORT=foo EMPTY= INNER_QUOTES=this 'is' a test INNER_QUOTES2=this "is" a test TRIM_WHITESPACE= foo KEEP_WHITESPACE=" foo " MULTILINE_DQ="multi\nline" MULTILINE_SQ='multi\nline' MULTILINE_NQ=multi\nline # some comment should be ignored """ with tempfile.NamedTemporaryFile("w", delete=False) as f: f.write(TEST) env = core.read_dotenv(f.name) assert env["BASIC"] == "basic basic" assert env["EXPORT"] == "foo" assert env["EMPTY"] == "" assert env["INNER_QUOTES"] == "this 'is' a test" assert env["INNER_QUOTES2"] == 'this "is" a test' assert env["TRIM_WHITESPACE"] == "foo" assert env["KEEP_WHITESPACE"] == " foo " assert env["MULTILINE_DQ"] == "multi\nline" assert env["MULTILINE_SQ"] == "multi\\nline" assert env["MULTILINE_NQ"] == "multi\\nline" assert len(env) == 10 def test_basic() -> None: """Basic unquoted strings.""" TEST = "FOO=BAR" with tempfile.NamedTemporaryFile("w", delete=False) as f: f.write(TEST) env = core.read_dotenv(f.name) assert env["FOO"] == "BAR" def test_empty() -> None: """Empty values become empty strings.""" TEST = "FOO=" with tempfile.NamedTemporaryFile("w", delete=False) as f: f.write(TEST) env = core.read_dotenv(f.name) assert env["FOO"] == "" def test_inner_quotes() -> None: """Inner quotes are mainained.""" TEST = "\n".join(["FOO1=this 'is' a test", 'FOO2=this "is" a test']) with tempfile.NamedTemporaryFile("w", delete=False) as f: f.write(TEST) env = core.read_dotenv(f.name) assert env["FOO1"] == "this 'is' a test" assert env["FOO2"] == 'this "is" a test' def test_trim_whitespaces() -> None: """Whitespaces are stripped from unquoted values.""" TEST = "FOO= test " with tempfile.NamedTemporaryFile("w", delete=False) as f: f.write(TEST) env = core.read_dotenv(f.name) assert env["FOO"] == "test" def test_keep_whitespaces() -> None: """Whitespaces are mainteined from quoted values.""" TEST = "FOO=' test '" with tempfile.NamedTemporaryFile("w", delete=False) as f: f.write(TEST) env = core.read_dotenv(f.name) assert env["FOO"] == " test " def test_multiline() -> None: """Quoted values can contain newlines.""" TEST = r'FOO="This is\nbar"' with tempfile.NamedTemporaryFile("w", delete=False) as f: f.write(TEST) env = core.read_dotenv(f.name) assert env["FOO"] == "This is\nbar" @pytest.mark.parametrize( "input_, expected", [ ('FOO="Test"', "Test"), ("FOO='Test'", "Test"), ("FOO='\"Test\"'", '"Test"'), ("FOO=\"'Test'\"", "'Test'"), ], ) def test_quotes(input_: str, expected: str) -> None: """Test different quotes.""" with tempfile.NamedTemporaryFile("w", delete=False) as f: f.write(input_) env = core.read_dotenv(f.name) assert env["FOO"] == expected def test_comments() -> None: """Test comments.""" """Lines starting with # are ignored.""" TEST = """ FOO=BAR # comment BAR=BAZ """ with tempfile.NamedTemporaryFile("w", delete=False) as f: f.write(TEST) env = core.read_dotenv(f.name) assert len(env) == 2 assert env["FOO"] == "BAR" assert env["BAR"] == "BAZ" def test_emtpy_lines() -> None: """Empty lines are skipped.""" TEST = """ FOO=BAR BAR=BAZ """ with tempfile.NamedTemporaryFile("w", delete=False) as f: f.write(TEST) env = core.read_dotenv(f.name) assert len(env) == 2 assert env["FOO"] == "BAR" assert env["BAR"] == "BAZ" def test_export() -> None: """Exports are allowed.""" TEST = "export FOO=BAR" with tempfile.NamedTemporaryFile("w", delete=False) as f: f.write(TEST) env = core.read_dotenv(f.name) assert env["FOO"] == "BAR" tests/test_version.py000066400000000000000000000002461452351177500153320ustar00rootroot00000000000000"""Tests for version module.""" def test_version() -> None: """Test version.""" from dotenv_cli import __VERSION__ assert isinstance(__VERSION__, str)