toml-combine 0.1.9__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
toml_combine/__init__.py CHANGED
@@ -1,55 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import pathlib
4
- from typing import Any, cast, overload
3
+ from .lib import combine
5
4
 
6
- from . import combiner, toml
7
-
8
-
9
- # Provide already parsed config
10
- @overload
11
- def combine(
12
- *, config: dict[str, Any], **filters: str | list[str]
13
- ) -> dict[str, Any]: ...
14
- # Provide toml config content
15
- @overload
16
- def combine(*, config: str, **filters: str | list[str]) -> dict[str, Any]: ...
17
- # Provide toml config file path
18
- @overload
19
- def combine(
20
- *, config_file: str | pathlib.Path, **filters: str | list[str]
21
- ) -> dict[str, Any]: ...
22
-
23
-
24
- def combine(*, config=None, config_file=None, **filters):
25
- """
26
- Generate outputs of configurations based on the provided TOML
27
- configuration.
28
-
29
- Args:
30
- config: The TOML configuration as a string or an already parsed dictionary.
31
- OR:
32
- config_file: The path to the TOML configuration file.
33
- **filters: Filters to apply to the combined configuration
34
- (dimension="value" or dimension=["list", "of", "values"]).
35
-
36
- Returns:
37
- dict[str, Any]: The combined configuration ({"output_id": {...}}).
38
- """
39
- if (config is None) is (config_file is None):
40
- raise ValueError("Either 'config' or 'config_file' must be provided.")
41
-
42
- if isinstance(config, dict):
43
- dict_config = config
44
- else:
45
- if config_file:
46
- config_string = pathlib.Path(config_file).read_text()
47
- else:
48
- config = cast(str, config)
49
- config_string = config
50
-
51
- dict_config = toml.loads(config_string)
52
-
53
- config_obj = combiner.build_config(dict_config)
54
-
55
- return combiner.generate_outputs(config_obj, **filters)
5
+ __all__ = ["combine"]
toml_combine/cli.py CHANGED
@@ -6,7 +6,7 @@ import pathlib
6
6
  import sys
7
7
  from collections.abc import Mapping
8
8
 
9
- from . import combiner, exceptions, toml
9
+ from . import combiner, exceptions, lib, toml
10
10
 
11
11
 
12
12
  def get_argument_parser(
@@ -17,20 +17,30 @@ def get_argument_parser(
17
17
  description="Create combined configurations from a TOML file",
18
18
  add_help=(dimensions is not None),
19
19
  )
20
+ arg_parser.add_argument(
21
+ "--format",
22
+ choices=["json", "toml"],
23
+ default="toml",
24
+ help="Output format",
25
+ )
20
26
  arg_parser.add_argument(
21
27
  "config",
22
28
  type=pathlib.Path,
23
29
  help="Path to the TOML configuration file",
24
30
  )
25
- if dimensions:
31
+ if dimensions is None:
32
+ arg_parser.epilog = (
33
+ "Additional arguments will be provided with a valid config file."
34
+ )
35
+ else:
26
36
  group = arg_parser.add_argument_group(
27
37
  "dimensions",
28
- "Filter the generated outputs by dimensions",
38
+ "Output the file with these dimensions",
29
39
  )
30
40
 
31
41
  for name, values in dimensions.items():
32
42
  group.add_argument(
33
- f"--{name}", choices=values, help=f"Limit to given {name}"
43
+ f"--{name}", choices=values, help=f"Provide value for {name}"
34
44
  )
35
45
 
36
46
  return arg_parser
@@ -41,7 +51,12 @@ def cli(argv) -> int:
41
51
 
42
52
  # Parse the config file argument to get the dimensions
43
53
  arg_parser = get_argument_parser(dimensions=None)
44
- args, _ = arg_parser.parse_known_args(argv)
54
+ try:
55
+ args, _ = arg_parser.parse_known_args(argv)
56
+ except SystemExit:
57
+ # If the user didn't provide a config file, print the help message
58
+ arg_parser.print_help()
59
+ return 1
45
60
 
46
61
  try:
47
62
  dict_config = toml.loads(args.config.read_text())
@@ -62,22 +77,25 @@ def cli(argv) -> int:
62
77
  arg_parser = get_argument_parser(dimensions=config.dimensions)
63
78
  args = arg_parser.parse_args(argv)
64
79
 
65
- dimensions_filter = {
80
+ mapping = {
66
81
  key: value
67
82
  for key, value in vars(args).items()
68
83
  if key in config.dimensions and value
69
84
  }
70
- # Generate final configurations for each output
85
+ # Generate final configurations for requested mapping
71
86
  try:
72
- result = combiner.generate_outputs(config=config, **dimensions_filter)
87
+ result = lib.combine(config=dict_config, **mapping)
73
88
  except exceptions.TomlCombineError as exc:
74
89
  print(exc, file=sys.stderr)
75
90
  return 1
76
91
 
77
- if not result:
78
- print("No outputs found", file=sys.stderr)
92
+ if args.format == "toml":
93
+ # Convert the result to TOML format
79
94
 
80
- print(json.dumps(result, indent=2))
95
+ print(toml.dumps(result))
96
+ else:
97
+ # Convert the result to JSON format
98
+ print(json.dumps(result, indent=2))
81
99
 
82
100
  return 0
83
101
 
toml_combine/combiner.py CHANGED
@@ -2,26 +2,13 @@ from __future__ import annotations
2
2
 
3
3
  import copy
4
4
  import dataclasses
5
- import itertools
6
5
  from collections.abc import Mapping, Sequence
7
6
  from functools import partial
8
- from typing import Any, TypeVar, Union
7
+ from typing import Any, TypeVar
9
8
 
10
9
  from . import exceptions
11
10
 
12
11
 
13
- @dataclasses.dataclass()
14
- class Output:
15
- dimensions: Mapping[str, str]
16
-
17
- @property
18
- def id(self) -> str:
19
- return f"{'-'.join(self.dimensions.values())}"
20
-
21
- def __str__(self) -> str:
22
- return f"Output(id={self.id})"
23
-
24
-
25
12
  @dataclasses.dataclass()
26
13
  class Override:
27
14
  when: Mapping[str, list[str]]
@@ -34,55 +21,40 @@ class Override:
34
21
  @dataclasses.dataclass()
35
22
  class Config:
36
23
  dimensions: Mapping[str, list[str]]
37
- outputs: list[Output]
38
24
  default: Mapping[str, Any]
39
25
  overrides: Sequence[Override]
40
26
 
41
27
 
42
- def wrap_in_list(value: str | list[str]) -> list[str]:
43
- """
44
- Wrap a string in a list if it's not already a list.
45
- """
46
- if isinstance(value, str):
47
- return [value]
48
- return value
49
-
50
-
51
- T = TypeVar("T", bound=Union[str, list[str]])
52
-
53
-
54
28
  def clean_dimensions_dict(
55
- to_sort: Mapping[str, T], clean: dict[str, list[str]], type: str
56
- ) -> dict[str, T]:
29
+ to_sort: Mapping[str, list[str]], clean: dict[str, list[str]], type: str
30
+ ) -> dict[str, list[str]]:
57
31
  """
58
32
  Recreate a dictionary of dimension values with the same order as the
59
33
  dimensions list.
60
34
  """
61
35
  result = {}
62
- remaining = dict(to_sort)
36
+ if invalid_dimensions := set(to_sort) - set(clean):
37
+ raise exceptions.DimensionNotFound(
38
+ type=type,
39
+ id=to_sort,
40
+ dimension=", ".join(invalid_dimensions),
41
+ )
63
42
 
43
+ # Fix the order of the dimensions
64
44
  for dimension, valid_values in clean.items():
65
- valid_values = set(valid_values)
66
45
  if dimension not in to_sort:
67
46
  continue
68
47
 
69
- original_value = remaining.pop(dimension)
70
- values = set(wrap_in_list(original_value))
71
- if invalid_values := values - valid_values:
48
+ original_values = to_sort[dimension]
49
+ if invalid_values := set(original_values) - set(valid_values):
72
50
  raise exceptions.DimensionValueNotFound(
73
51
  type=type,
74
52
  id=to_sort,
75
53
  dimension=dimension,
76
54
  value=", ".join(invalid_values),
77
55
  )
78
- result[dimension] = original_value
79
-
80
- if remaining:
81
- raise exceptions.DimensionNotFound(
82
- type=type,
83
- id=to_sort,
84
- dimension=", ".join(to_sort),
85
- )
56
+ # Fix the order of the values
57
+ result[dimension] = [e for e in valid_values if e in original_values]
86
58
 
87
59
  return result
88
60
 
@@ -117,7 +89,10 @@ def override_sort_key(
117
89
  return tuple(result)
118
90
 
119
91
 
120
- def merge_configs(a: Any, b: Any, /) -> Any:
92
+ T = TypeVar("T", dict, list, str, int, float, bool)
93
+
94
+
95
+ def merge_configs(a: T, b: T, /) -> T:
121
96
  """
122
97
  Recursively merge two configuration dictionaries, with b taking precedence.
123
98
  """
@@ -128,7 +103,7 @@ def merge_configs(a: Any, b: Any, /) -> Any:
128
103
  return b
129
104
 
130
105
  result = a.copy()
131
- for key, b_value in b.items():
106
+ for key, b_value in b.items(): # type: ignore
132
107
  if a_value := a.get(key):
133
108
  result[key] = merge_configs(a_value, b_value)
134
109
  else:
@@ -137,6 +112,7 @@ def merge_configs(a: Any, b: Any, /) -> Any:
137
112
 
138
113
 
139
114
  def build_config(config: dict[str, Any]) -> Config:
115
+ config = copy.deepcopy(config)
140
116
  # Parse dimensions
141
117
  dimensions = config.pop("dimensions")
142
118
 
@@ -150,8 +126,11 @@ def build_config(config: dict[str, Any]) -> Config:
150
126
  when = override.pop("when")
151
127
  except KeyError:
152
128
  raise exceptions.MissingOverrideCondition(id=override)
153
-
154
- when = {k: wrap_in_list(v) for k, v in when.items()}
129
+ when = clean_dimensions_dict(
130
+ to_sort={k: v if isinstance(v, list) else [v] for k, v in when.items()},
131
+ clean=dimensions,
132
+ type="override",
133
+ )
155
134
 
156
135
  conditions = tuple((k, tuple(v)) for k, v in when.items())
157
136
  if conditions in seen_conditions:
@@ -173,93 +152,38 @@ def build_config(config: dict[str, Any]) -> Config:
173
152
  key=partial(override_sort_key, dimensions=dimensions),
174
153
  )
175
154
 
176
- outputs = []
177
- seen_conditions = set()
178
-
179
- for output in config.pop("output", []):
180
- for key in output:
181
- output[key] = wrap_in_list(output[key])
182
-
183
- for cartesian_product in itertools.product(*output.values()):
184
- single_output = dict(zip(output.keys(), cartesian_product))
185
-
186
- conditions = tuple(single_output.items())
187
- if conditions in seen_conditions:
188
- raise exceptions.DuplicateError(type="output", id=output.id)
189
- seen_conditions.add(conditions)
190
-
191
- outputs.append(
192
- Output(
193
- dimensions=clean_dimensions_dict(
194
- single_output, dimensions, type="output"
195
- ),
196
- )
197
- )
198
-
199
155
  return Config(
200
156
  dimensions=dimensions,
201
- outputs=outputs,
202
157
  default=default,
203
158
  overrides=overrides,
204
159
  )
205
160
 
206
161
 
207
- def output_matches_override(output: Output, override: Override) -> bool:
162
+ def mapping_matches_override(mapping: dict[str, str], override: Override) -> bool:
208
163
  """
209
- Check if the values in the override match the output dimensions.
164
+ Check if the values in the override match the given dimensions.
210
165
  """
211
166
  for dim, values in override.when.items():
212
- if dim not in output.dimensions:
167
+ if dim not in mapping:
213
168
  return False
214
169
 
215
- if output.dimensions[dim] not in values:
170
+ if mapping[dim] not in values:
216
171
  return False
217
172
 
218
173
  return True
219
174
 
220
175
 
221
- def generate_output(
222
- default: Mapping[str, Any], overrides: Sequence[Override], output: Output
176
+ def generate_for_mapping(
177
+ default: Mapping[str, Any],
178
+ overrides: Sequence[Override],
179
+ mapping: dict[str, str],
223
180
  ) -> dict[str, Any]:
224
181
  result = copy.deepcopy(default)
225
182
  # Apply each matching override
226
183
  for override in overrides:
227
184
  # Check if all dimension values in the override match
228
185
 
229
- if output_matches_override(output=output, override=override):
186
+ if mapping_matches_override(mapping=mapping, override=override):
230
187
  result = merge_configs(result, override.config)
231
188
 
232
- return {"dimensions": output.dimensions, **result}
233
-
234
-
235
- def generate_outputs(config: Config, **filter: str | list[str]) -> dict[str, Any]:
236
- result = {}
237
- filter_with_lists: dict[str, list[str]] = {}
238
-
239
- for key, value in list(filter.items()):
240
- if key not in config.dimensions:
241
- raise exceptions.DimensionNotFound(type="arguments", id="", dimension=key)
242
-
243
- value = wrap_in_list(value)
244
-
245
- if set(value) - set(config.dimensions[key]):
246
- raise exceptions.DimensionValueNotFound(
247
- type="arguments",
248
- id="",
249
- dimension=key,
250
- value=", ".join(set(value) - set(config.dimensions[key])),
251
- )
252
- filter_with_lists[key] = value
253
-
254
- for output in config.outputs:
255
- if all(
256
- output.dimensions.get(key) in value
257
- for key, value in filter_with_lists.items()
258
- ):
259
- result[output.id] = generate_output(
260
- default=config.default,
261
- overrides=config.overrides,
262
- output=output,
263
- )
264
-
265
189
  return result
@@ -15,6 +15,10 @@ class TomlDecodeError(TomlCombineError):
15
15
  """Error while decoding configuration file."""
16
16
 
17
17
 
18
+ class TomlEncodeError(TomlCombineError):
19
+ """Error while encoding configuration file."""
20
+
21
+
18
22
  class DuplicateError(TomlCombineError):
19
23
  """In {type} {id}: Cannot have multiple {type}s with the same dimensions."""
20
24
 
toml_combine/lib.py ADDED
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ import pathlib
4
+ from typing import Any, cast, overload
5
+
6
+ from . import combiner, toml
7
+
8
+
9
+ # Provide already parsed config
10
+ @overload
11
+ def combine(
12
+ *, config: dict[str, Any], **mapping: str | list[str]
13
+ ) -> dict[str, Any]: ...
14
+ # Provide toml config content
15
+ @overload
16
+ def combine(*, config: str, **mapping: str | list[str]) -> dict[str, Any]: ...
17
+ # Provide toml config file path
18
+ @overload
19
+ def combine(
20
+ *, config_file: str | pathlib.Path, **mapping: str | list[str]
21
+ ) -> dict[str, Any]: ...
22
+
23
+
24
+ def combine(*, config=None, config_file=None, **mapping):
25
+ """
26
+ Generate outputs of configurations based on the provided TOML
27
+ configuration and a mapping of dimensions values.
28
+
29
+ Args:
30
+ config: The TOML configuration as a string or an already parsed dictionary.
31
+ OR:
32
+ config_file: The path to the TOML configuration file.
33
+ **mapping: Define the values you want for dimensions {"<dimension>": "<value>", ...}.
34
+
35
+ Returns:
36
+ dict[str, Any]: The combined configuration.
37
+ """
38
+ if (config is None) is (config_file is None):
39
+ raise ValueError("Either 'config' or 'config_file' must be provided.")
40
+
41
+ if isinstance(config, dict):
42
+ dict_config = config
43
+ else:
44
+ if config_file:
45
+ config_string = pathlib.Path(config_file).read_text()
46
+ else:
47
+ config = cast(str, config)
48
+ config_string = config
49
+
50
+ dict_config = toml.loads(config_string)
51
+
52
+ config_obj = combiner.build_config(dict_config)
53
+
54
+ return combiner.generate_for_mapping(
55
+ default=config_obj.default,
56
+ overrides=config_obj.overrides,
57
+ mapping=mapping,
58
+ )
toml_combine/toml.py CHANGED
@@ -1,17 +1,27 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import json
3
4
  from typing import Any
4
5
 
5
- import tomli
6
+ import tomlkit
7
+ import tomlkit.exceptions
6
8
 
7
9
  from . import exceptions
8
10
 
9
11
 
10
12
  def loads(raw_config: str) -> dict[str, Any]:
11
13
  try:
12
- return tomli.loads(raw_config)
13
- except tomli.TOMLDecodeError as e:
14
- raise exceptions.TomlDecodeError(
15
- message="Syntax error in TOML configuration file",
16
- context=str(e),
17
- ) from e
14
+ return tomlkit.loads(raw_config)
15
+ except tomlkit.exceptions.ParseError as e:
16
+ raise exceptions.TomlDecodeError from e
17
+
18
+
19
+ def dumps(config: dict) -> str:
20
+ # https://github.com/python-poetry/tomlkit/issues/411
21
+ # While we're waiting for a fix, the workaround is to give up "style-preserving"
22
+ # features. The easiest way to turn tomlkit objects into plain dicts and strings
23
+ # is through a json round-trip.
24
+ try:
25
+ return tomlkit.dumps(json.loads(json.dumps(config)))
26
+ except tomlkit.exceptions.TOMLKitError as e:
27
+ raise exceptions.TomlEncodeError from e
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: toml-combine
3
- Version: 0.1.9
3
+ Version: 0.3.0
4
4
  Summary: A tool for combining complex configurations in TOML format.
5
5
  Author-email: Joachim Jablon <ewjoachim@gmail.com>
6
6
  License-Expression: MIT
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
16
  Requires-Python: >=3.9
17
- Requires-Dist: tomli>=2.2.1
17
+ Requires-Dist: tomlkit
18
18
  Description-Content-Type: text/markdown
19
19
 
20
20
  # Toml-combine
@@ -35,12 +35,6 @@ The configuration file is usually a TOML file. Here's a small example:
35
35
  [dimensions]
36
36
  environment = ["production", "staging"]
37
37
 
38
- [[output]]
39
- environment = "production"
40
-
41
- [[output]]
42
- environment = "staging"
43
-
44
38
  [default]
45
39
  name = "my-service"
46
40
  registry = "gcr.io/my-project/"
@@ -61,16 +55,6 @@ Dimensions lets you describe the main "thing" that makes the outputs differents,
61
55
  service might be `frontend` or `backend`. Some combinations of dimensions might not
62
56
  exists, for example, maybe there's no `staging` in `eu`.
63
57
 
64
- ### Outputs
65
-
66
- Create a `output` for each configuration you want to generate, and specify the
67
- dimensions relevant for this output. It's ok to omit some dimensions when they're not
68
- used for a given output.
69
-
70
- > [!Note]
71
- > Defining a list as the value of one or more dimensions in a output
72
- > is a shorthand for defining all combinations of dimensions
73
-
74
58
  ### Default
75
59
 
76
60
  The common configuration to start from, before we start overlaying overrides on top.
@@ -93,7 +77,7 @@ specific to more specific, each one overriding the values of the previous ones:
93
77
 
94
78
  ### The configuration itself
95
79
 
96
- Under the layer of `dimensions/output/default/override` system, what you actually define
80
+ Under the layer of `dimensions/default/override/mapping` system, what you actually define
97
81
  in the configuration is completely up to you. That said, only nested
98
82
  "dictionnaries"/"objects"/"tables"/"mapping" (those are all the same things in
99
83
  Python/JS/Toml lingo) will be merged between the default and the overrides, while
@@ -110,9 +94,6 @@ Let's look at an example:
110
94
  [dimensions]
111
95
  environment = ["production", "staging"]
112
96
 
113
- [[output]]
114
- environment = ["production", "staging"]
115
-
116
97
  [default]
117
98
  fruits = [{name="apple", color="red"}]
118
99
 
@@ -121,17 +102,16 @@ when.environment = "staging"
121
102
  fruits = [{name="orange", color="orange"}]
122
103
  ```
123
104
 
124
- In this example, on staging, `fruits` is `[{name="orange", color="orange"}]` and not `[{name="apple", color="red"}, {name="orange", color="orange"}]`.
125
- The only way to get multiple values to be merged is if they are tables: you'll need
105
+ In this example, with `{"environment": "staging"}`, `fruits` is
106
+ `[{name="orange", color="orange"}]` and not
107
+ `[{name="apple", color="red"}, {name="orange", color="orange"}]`.
108
+ The only way to get multiple values to be merged is if they are dicts: you'll need
126
109
  to chose an element to become the key:
127
110
 
128
111
  ```toml
129
112
  [dimensions]
130
113
  environment = ["production", "staging"]
131
114
 
132
- [[output]]
133
- environment = ["production", "staging"]
134
-
135
115
  [default]
136
116
  fruits.apple.color = "red"
137
117
 
@@ -146,16 +126,28 @@ This example is simple because `name` is a natural choice for the key. In some c
146
126
  the choice is less natural, but you can always decide to name the elements of your
147
127
  list and use that name as a key. Also, yes, you'll loose ordering.
148
128
 
149
- ### CLI
129
+ ### Mapping
150
130
 
151
- ```console
152
- $ toml-combine {path/to/config.toml}
153
- ```
131
+ When you call the tool either with the CLI or the lib (see both below), you will have to
132
+ provide a mapping of the desired dimentions. These values will be compared to overrides
133
+ to apply overrides when relevant. It's ok to omit some dimensions, corresponding
134
+ overrides won't be selected.
135
+
136
+ By default, the output is `toml` though you can switch to `json` with `--format=json`
154
137
 
155
- Generates all the outputs described by the given TOML config.
138
+ ## CLI
156
139
 
157
- Note that you can restrict generation to some dimension values by passing
158
- `--{dimension}={value}`
140
+ Example with the config from the previous section:
141
+
142
+ ```console
143
+ $ toml-combine path/to/config.toml --environment=staging
144
+ [dimensions]
145
+ environment = "staging"
146
+
147
+ [fruits]
148
+ apple.color = "red"
149
+ orange.color = "orange"
150
+ ```
159
151
 
160
152
  ## Lib
161
153
 
@@ -163,40 +155,23 @@ Note that you can restrict generation to some dimension values by passing
163
155
  import toml_combine
164
156
 
165
157
 
166
- result = toml_combine.combine(
167
- config_file=config_file,
168
- environment=["production", "staging"],
169
- type="job",
170
- job=["manage", "special-command"],
171
- )
158
+ result = toml_combine.combine(config_file=config_file, environment="staging")
172
159
 
173
160
  print(result)
174
161
  {
175
- "production-job-manage": {...},
176
- "production-job-special-command": {...},
177
- "staging-job-manage": {...},
178
- "staging-job-special-command": {...},
162
+ "fruits": {"apple": {"color": "red"}, "orange": {"color": "orange"}}
179
163
  }
180
164
  ```
181
165
 
182
- You can pass either `config` (TOML string or dict) or `config_file` (`pathlib.Path` or string path) to `combine()`. Additional `kwargs` restrict the output.
166
+ You can pass either `config` (TOML string or dict) or `config_file` (`pathlib.Path` or string path) to `combine()`. All other `kwargs` specify the mapping you want.
183
167
 
184
- ### A bigger example
168
+ ## A bigger example
185
169
 
186
170
  ```toml
187
171
  [dimensions]
188
172
  environment = ["production", "staging", "dev"]
189
173
  service = ["frontend", "backend"]
190
174
 
191
- # All 4 combinations of those values will exist
192
- [[output]]
193
- environment = ["production", "staging"]
194
- service = ["frontend", "backend"]
195
-
196
- # On dev, the "service" is not defined. That's ok.
197
- [[output]]
198
- environment = "dev"
199
-
200
175
  [default]
201
176
  registry = "gcr.io/my-project/"
202
177
  service_account = "my-service-account"
@@ -225,81 +200,62 @@ container.env.ENABLE_EXPENSIVE_MONITORING = false
225
200
 
226
201
  This produces the following configs:
227
202
 
228
- ```json
229
- {
230
- "production-frontend-eu": {
231
- "dimensions": {
232
- "environment": "production",
233
- "service": "frontend",
234
- "region": "eu"
235
- },
236
- "registry": "gcr.io/my-project/",
237
- "service_account": "my-service-account",
238
- "name": "service-frontend",
239
- "container": {
240
- "image_name": "my-image-frontend"
241
- }
242
- },
243
- "production-backend-eu": {
244
- "dimensions": {
245
- "environment": "production",
246
- "service": "backend",
247
- "region": "eu"
248
- },
249
- "registry": "gcr.io/my-project/",
250
- "service_account": "my-service-account",
251
- "name": "service-backend",
252
- "container": {
253
- "image_name": "my-image-backend",
254
- "port": 8080
255
- }
256
- },
257
- "staging-frontend-eu": {
258
- "dimensions": {
259
- "environment": "staging",
260
- "service": "frontend",
261
- "region": "eu"
262
- },
263
- "registry": "gcr.io/my-project/",
264
- "service_account": "my-service-account",
265
- "name": "service-frontend",
266
- "container": {
267
- "image_name": "my-image-frontend"
268
- }
269
- },
270
- "staging-backend-eu": {
271
- "dimensions": {
272
- "environment": "staging",
273
- "service": "backend",
274
- "region": "eu"
275
- },
276
- "registry": "gcr.io/my-project/",
277
- "service_account": "my-service-account",
278
- "name": "service-backend",
279
- "container": {
280
- "image_name": "my-image-backend",
281
- "port": 8080,
282
- "env": {
283
- "ENABLE_EXPENSIVE_MONITORING": false
284
- }
285
- }
286
- },
287
- "dev-backend": {
288
- "dimensions": {
289
- "environment": "dev",
290
- "service": "backend"
291
- },
292
- "registry": "gcr.io/my-project/",
293
- "service_account": "my-service-account",
294
- "name": "service-backend",
295
- "container": {
296
- "env": {
297
- "DEBUG": true,
298
- "ENABLE_EXPENSIVE_MONITORING": false
299
- },
300
- "image_name": "my-image-backend",
301
- "port": 8080
302
- }
303
- }
304
- }
203
+ ```console
204
+ $ uv run toml-combine example.toml --environment=production --service=frontend
205
+ registry = "gcr.io/my-project/"
206
+ service_account = "my-service-account"
207
+ name = "service-frontend"
208
+
209
+ [container]
210
+ image_name = "my-image-frontend"
211
+ ```
212
+
213
+ ```console
214
+ $ toml-combine example.toml --environment=production --service=backend
215
+ registry = "gcr.io/my-project/"
216
+ service_account = "my-service-account"
217
+ name = "service-backend"
218
+
219
+ [container]
220
+ image_name = "my-image-backend"
221
+ port = 8080
222
+ ```
223
+
224
+ ```console
225
+ $ toml-combine example.toml --environment=staging --service=frontend
226
+ registry = "gcr.io/my-project/"
227
+ service_account = "my-service-account"
228
+ name = "service-frontend"
229
+
230
+ [container]
231
+ image_name = "my-image-frontend"
232
+ ```
233
+
234
+ ```console
235
+ $ toml-combine example.toml --environment=staging --service=backend
236
+ registry = "gcr.io/my-project/"
237
+ service_account = "my-service-account"
238
+ name = "service-backend"
239
+
240
+ [container]
241
+ image_name = "my-image-backend"
242
+ port = 8080
243
+
244
+ [container.env]
245
+ ENABLE_EXPENSIVE_MONITORING = false
246
+ ```
247
+
248
+ ```console
249
+ $ toml-combine example.toml --environment=dev --service=backend
250
+ registry = "gcr.io/my-project/"
251
+ service_account = "my-service-account"
252
+ name = "service-backend"
253
+
254
+ [container]
255
+ image_name = "my-image-backend"
256
+ port = 8080
257
+ [container.env]
258
+ DEBUG = true
259
+ ENABLE_EXPENSIVE_MONITORING = false
260
+
305
261
  ```
@@ -0,0 +1,11 @@
1
+ toml_combine/__init__.py,sha256=TDkOwwEM-nS6hOh79u9Qae6g2Q6VfANpPpnKGfSgu80,84
2
+ toml_combine/__main__.py,sha256=hmF8N8xX6UEApzbKTVZ-4E1HU5-rjgUkdXNLO-mF6vo,100
3
+ toml_combine/cli.py,sha256=hG03eDKz7xU-ydJIa1kDuu6WlFzNS3GTMJ6zals9M9c,2843
4
+ toml_combine/combiner.py,sha256=RhhCevncnVvxFYNywvtVWkVMpiqtF0mq_APjg76Tg4Q,5546
5
+ toml_combine/exceptions.py,sha256=tAFTDRSg6d10bBruBhsasZXrNNgLTmr_nKfvIsRR_yU,991
6
+ toml_combine/lib.py,sha256=Iw7F8SCyQMlhaqSD2vtnmM6jbnrgzCZeX0d-LTM3VVg,1683
7
+ toml_combine/toml.py,sha256=hqWEdBQiM960uga_9A6gXRhWhT2gVZT8IiLRc3jkyT8,797
8
+ toml_combine-0.3.0.dist-info/METADATA,sha256=xWGk3SSYPwWdcnqiRthODPYSLo3SSl84d_nSwUk3wJU,7354
9
+ toml_combine-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ toml_combine-0.3.0.dist-info/entry_points.txt,sha256=dXUQNom54uZt_7ylEG81iNYMamYpaFo9-ItcZJU6Uzc,58
11
+ toml_combine-0.3.0.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- toml_combine/__init__.py,sha256=l7i0GkM9k7cc__wj1yqK5XjpB3IJ0jqFU63tqKMuYlY,1625
2
- toml_combine/__main__.py,sha256=hmF8N8xX6UEApzbKTVZ-4E1HU5-rjgUkdXNLO-mF6vo,100
3
- toml_combine/cli.py,sha256=MZrAEP4wt6f9Qn0TEXIjeLoQMlvQulFpkMciwU8GRO4,2328
4
- toml_combine/combiner.py,sha256=_sSOCVBlv8ljAdMd6BEthS6rxiCnt-VV9rMEPSK_Cvk,7643
5
- toml_combine/exceptions.py,sha256=SepRFDxeWQEbD88jhF5g7laZSSULthho83BpW8u9RWs,897
6
- toml_combine/toml.py,sha256=_vCINvfJeS3gWid35Pmm3Yz4xyJ8LpKJRHL0axSU8nk,384
7
- toml_combine-0.1.9.dist-info/METADATA,sha256=ti8e_ng-_KdRPCAJybModogRhP4guqH8UilQTeXQHAk,8375
8
- toml_combine-0.1.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
- toml_combine-0.1.9.dist-info/entry_points.txt,sha256=dXUQNom54uZt_7ylEG81iNYMamYpaFo9-ItcZJU6Uzc,58
10
- toml_combine-0.1.9.dist-info/RECORD,,