• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

thesimj / tomlev / 22327581809

23 Feb 2026 10:29PM UTC coverage: 94.118% (-1.0%) from 95.129%
22327581809

push

github

Nick Bubelich
Refactor substitution and async parity for v1.0.9

This aligns custom separator and strict override behavior across sync and async loaders, hardens include merge isolation, and ships the 1.0.9 release metadata/docs.

143 of 149 new or added lines in 7 files covered. (95.97%)

7 existing lines in 2 files now uncovered.

720 of 765 relevant lines covered (94.12%)

0.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.11
/tomlev/cli.py
1
"""
2
MIT License
3

4
Copyright (c) 2025 Nick Bubelich
5

6
Permission is hereby granted, free of charge, to any person obtaining a copy
7
of this software and associated documentation files (the "Software"), to deal
8
in the Software without restriction, including without limitation the rights
9
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
copies of the Software, and to permit persons to whom the Software is
11
furnished to do so, subject to the following conditions:
12

13
The above copyright notice and this permission notice shall be included in all
14
copies or substantial portions of the Software.
15

16
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
SOFTWARE.
23
"""
24

25
from __future__ import annotations
1✔
26

27
import argparse
1✔
28
import json
1✔
29
import sys
1✔
30
from os import environ
1✔
31

32
from .constants import (
1✔
33
    DEFAULT_ENV_FILE,
34
    DEFAULT_SEPARATOR,
35
    DEFAULT_TOML_FILE,
36
    TOMLEV_ENV_FILE,
37
    TOMLEV_TOML_FILE,
38
    VERSION,
39
)
40
from .env_loader import EnvDict, read_env_file
1✔
41
from .errors import ConfigValidationError
1✔
42
from .parser import read_toml
1✔
43

44
__all__ = ["cli_validate", "cli_render", "main"]
1✔
45

46

47
def _build_env(env_file: str | None, strict: bool, include_environment: bool) -> EnvDict:
1✔
48
    """Build environment mapping from process env and optional .env file."""
49
    env: EnvDict = dict(environ) if include_environment else {}
1✔
50
    dotenv = read_env_file(env_file, strict)
1✔
51
    env.update(dotenv)
1✔
52
    return env
1✔
53

54

55
def cli_validate(toml_file: str, env_file: str | None, strict: bool, include_environment: bool, separator: str) -> int:
1✔
56
    """Validate TOML parsing and env substitution without a model.
57

58
    Returns exit code 0 on success, 1 on failure. Prints a concise
59
    success message or a meaningful error to stderr.
60

61
    Args:
62
        toml_file: Path to the TOML file to validate.
63
        env_file: Path to the .env file, or None to skip.
64
        strict: Whether to operate in strict mode.
65
        include_environment: Whether to include system environment variables.
66
        separator: Separator for default values in environment variables.
67

68
    Returns:
69
        Exit code: 0 on success, 1 on failure.
70
    """
71
    try:
1✔
72
        env = _build_env(env_file, strict, include_environment)
1✔
73
    except (OSError, IOError, PermissionError) as e:
1✔
74
        print(f"Error reading env file: {e}", file=sys.stderr)
1✔
75
        return 1
1✔
76
    except ConfigValidationError as e:
×
NEW
77
        print(str(e), file=sys.stderr)
×
78
        return 1
×
79

80
    try:
1✔
81
        read_toml(toml_file, env, strict, separator)
1✔
82
    except FileNotFoundError:
1✔
83
        print(f"TOML file not found: {toml_file}", file=sys.stderr)
1✔
84
        return 1
1✔
85
    except ConfigValidationError as e:
1✔
86
        print(str(e), file=sys.stderr)
1✔
87
        return 1
1✔
88
    except (OSError, IOError, ValueError, TypeError) as e:
1✔
89
        print(f"Validation error: {e}", file=sys.stderr)
1✔
90
        return 1
1✔
91

92
    print("Validation successful.")
1✔
93
    return 0
1✔
94

95

96
def cli_render(toml_file: str, env_file: str | None, strict: bool, include_environment: bool, separator: str) -> int:
1✔
97
    """Render TOML configuration as JSON after environment substitution and includes.
98

99
    Returns exit code 0 on success, 1 on failure. Prints the rendered
100
    configuration as pretty-formatted JSON to stdout.
101

102
    Args:
103
        toml_file: Path to the TOML file to render.
104
        env_file: Path to the .env file, or None to skip.
105
        strict: Whether to operate in strict mode.
106
        include_environment: Whether to include system environment variables.
107
        separator: Separator for default values in environment variables.
108

109
    Returns:
110
        Exit code: 0 on success, 1 on failure.
111
    """
112
    try:
1✔
113
        env = _build_env(env_file, strict, include_environment)
1✔
114
    except (OSError, IOError, PermissionError) as e:
1✔
115
        print(f"Error reading env file: {e}", file=sys.stderr)
1✔
116
        return 1
1✔
117
    except ConfigValidationError as e:
×
NEW
118
        print(str(e), file=sys.stderr)
×
119
        return 1
×
120

121
    try:
1✔
122
        config = read_toml(toml_file, env, strict, separator)
1✔
123
    except FileNotFoundError:
1✔
124
        print(f"TOML file not found: {toml_file}", file=sys.stderr)
1✔
125
        return 1
1✔
126
    except ConfigValidationError as e:
1✔
NEW
127
        print(str(e), file=sys.stderr)
×
128
        return 1
×
129
    except (OSError, IOError, ValueError, TypeError) as e:
1✔
130
        print(f"Render error: {e}", file=sys.stderr)
1✔
131
        return 1
1✔
132

133
    # Output as JSON
134
    print(json.dumps(config, indent=2))
1✔
135
    return 0
1✔
136

137

138
def main(argv: list[str] | None = None) -> int:
1✔
139
    """TomlEv CLI entry point.
140

141
    Commands:
142
    - validate: Validate TOML file with env substitution (schema-less).
143
    - render: Render TOML configuration as JSON with substitution and includes.
144

145
    Args:
146
        argv: Command line arguments. If None, uses sys.argv.
147

148
    Returns:
149
        Exit code: 0 on success, 1 on failure.
150
    """
151
    # Get defaults from environment variables or fall back to constants
152
    default_toml = environ.get(TOMLEV_TOML_FILE, DEFAULT_TOML_FILE)
1✔
153
    default_env = environ.get(TOMLEV_ENV_FILE, DEFAULT_ENV_FILE)
1✔
154

155
    parser = argparse.ArgumentParser(prog="tomlev", description="TomlEv CLI")
1✔
156
    parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {VERSION}")
1✔
157
    sub = parser.add_subparsers(dest="command", required=True)
1✔
158

159
    p_validate = sub.add_parser("validate", help="Validate TOML with env substitution")
1✔
160
    p_validate.add_argument("--toml", default=default_toml, help="Path to TOML file")
1✔
161
    p_validate.add_argument("--env-file", default=default_env, help="Path to .env file (use --no-env-file to disable)")
1✔
162
    p_validate.add_argument("--no-env-file", action="store_true", help="Do not read .env file")
1✔
163
    p_validate.add_argument(
1✔
164
        "--strict", dest="strict", action="store_true", default=True, help="Enable strict mode (default)"
165
    )
166
    p_validate.add_argument("--no-strict", dest="strict", action="store_false", help="Disable strict mode")
1✔
167
    p_validate.add_argument("--no-environ", action="store_true", help="Do not include system environment variables")
1✔
168
    p_validate.add_argument("--separator", default=DEFAULT_SEPARATOR, help="Default separator for ${VAR|-default}")
1✔
169

170
    p_render = sub.add_parser("render", help="Render TOML configuration as JSON")
1✔
171
    p_render.add_argument("--toml", default=default_toml, help="Path to TOML file")
1✔
172
    p_render.add_argument("--env-file", default=default_env, help="Path to .env file (use --no-env-file to disable)")
1✔
173
    p_render.add_argument("--no-env-file", action="store_true", help="Do not read .env file")
1✔
174
    p_render.add_argument(
1✔
175
        "--strict", dest="strict", action="store_true", default=True, help="Enable strict mode (default)"
176
    )
177
    p_render.add_argument("--no-strict", dest="strict", action="store_false", help="Disable strict mode")
1✔
178
    p_render.add_argument("--no-environ", action="store_true", help="Do not include system environment variables")
1✔
179
    p_render.add_argument("--separator", default=DEFAULT_SEPARATOR, help="Default separator for ${VAR|-default}")
1✔
180

181
    args = parser.parse_args(argv)
1✔
182

183
    if args.command == "validate":
1✔
184
        env_file = None if args.no_env_file else args.env_file
1✔
185
        include_environment = not args.no_environ
1✔
186
        return cli_validate(args.toml, env_file, args.strict, include_environment, args.separator)
1✔
187
    elif args.command == "render":
1✔
188
        env_file = None if args.no_env_file else args.env_file
1✔
189
        include_environment = not args.no_environ
1✔
190
        return cli_render(args.toml, env_file, args.strict, include_environment, args.separator)
1✔
191

192
    parser.print_help()  # pragma: no cover - subparsers require a command
193
    return 1  # pragma: no cover - unreachable with required=True
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc