toml-combine 0.1.9__py3-none-any.whl → 0.2.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
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
 
@@ -137,6 +109,7 @@ def merge_configs(a: Any, b: Any, /) -> Any:
137
109
 
138
110
 
139
111
  def build_config(config: dict[str, Any]) -> Config:
112
+ config = copy.deepcopy(config)
140
113
  # Parse dimensions
141
114
  dimensions = config.pop("dimensions")
142
115
 
@@ -150,8 +123,11 @@ def build_config(config: dict[str, Any]) -> Config:
150
123
  when = override.pop("when")
151
124
  except KeyError:
152
125
  raise exceptions.MissingOverrideCondition(id=override)
153
-
154
- when = {k: wrap_in_list(v) for k, v in when.items()}
126
+ when = clean_dimensions_dict(
127
+ to_sort={k: v if isinstance(v, list) else [v] for k, v in when.items()},
128
+ clean=dimensions,
129
+ type="override",
130
+ )
155
131
 
156
132
  conditions = tuple((k, tuple(v)) for k, v in when.items())
157
133
  if conditions in seen_conditions:
@@ -173,93 +149,38 @@ def build_config(config: dict[str, Any]) -> Config:
173
149
  key=partial(override_sort_key, dimensions=dimensions),
174
150
  )
175
151
 
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
152
  return Config(
200
153
  dimensions=dimensions,
201
- outputs=outputs,
202
154
  default=default,
203
155
  overrides=overrides,
204
156
  )
205
157
 
206
158
 
207
- def output_matches_override(output: Output, override: Override) -> bool:
159
+ def mapping_matches_override(mapping: dict[str, str], override: Override) -> bool:
208
160
  """
209
- Check if the values in the override match the output dimensions.
161
+ Check if the values in the override match the given dimensions.
210
162
  """
211
163
  for dim, values in override.when.items():
212
- if dim not in output.dimensions:
164
+ if dim not in mapping:
213
165
  return False
214
166
 
215
- if output.dimensions[dim] not in values:
167
+ if mapping[dim] not in values:
216
168
  return False
217
169
 
218
170
  return True
219
171
 
220
172
 
221
- def generate_output(
222
- default: Mapping[str, Any], overrides: Sequence[Override], output: Output
173
+ def generate_for_mapping(
174
+ default: Mapping[str, Any],
175
+ overrides: Sequence[Override],
176
+ mapping: dict[str, str],
223
177
  ) -> dict[str, Any]:
224
178
  result = copy.deepcopy(default)
225
179
  # Apply each matching override
226
180
  for override in overrides:
227
181
  # Check if all dimension values in the override match
228
182
 
229
- if output_matches_override(output=output, override=override):
183
+ if mapping_matches_override(mapping=mapping, override=override):
230
184
  result = merge_configs(result, override.config)
231
185
 
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
- return result
186
+ return {"dimensions": mapping, **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.2.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,29 @@ 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. The mapping you pass is also returned in the output as a
135
+ dict under the `dimensions` key.
136
+
137
+ By default, the output is `toml` though you can switch to `json` with `--format=json`
154
138
 
155
- Generates all the outputs described by the given TOML config.
139
+ ## CLI
156
140
 
157
- Note that you can restrict generation to some dimension values by passing
158
- `--{dimension}={value}`
141
+ Example with the config from the previous section:
142
+
143
+ ```console
144
+ $ toml-combine path/to/config.toml --environment=staging
145
+ [dimensions]
146
+ environment = "staging"
147
+
148
+ [fruits]
149
+ apple.color = "red"
150
+ orange.color = "orange"
151
+ ```
159
152
 
160
153
  ## Lib
161
154
 
@@ -163,40 +156,24 @@ Note that you can restrict generation to some dimension values by passing
163
156
  import toml_combine
164
157
 
165
158
 
166
- result = toml_combine.combine(
167
- config_file=config_file,
168
- environment=["production", "staging"],
169
- type="job",
170
- job=["manage", "special-command"],
171
- )
159
+ result = toml_combine.combine(config_file=config_file, environment="staging")
172
160
 
173
161
  print(result)
174
162
  {
175
- "production-job-manage": {...},
176
- "production-job-special-command": {...},
177
- "staging-job-manage": {...},
178
- "staging-job-special-command": {...},
163
+ "dimensions": {"environment": "staging"},
164
+ "fruits": {"apple": {"color": "red"}, "orange": {"color": "orange"}}
179
165
  }
180
166
  ```
181
167
 
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.
168
+ 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
169
 
184
- ### A bigger example
170
+ ## A bigger example
185
171
 
186
172
  ```toml
187
173
  [dimensions]
188
174
  environment = ["production", "staging", "dev"]
189
175
  service = ["frontend", "backend"]
190
176
 
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
177
  [default]
201
178
  registry = "gcr.io/my-project/"
202
179
  service_account = "my-service-account"
@@ -225,81 +202,77 @@ container.env.ENABLE_EXPENSIVE_MONITORING = false
225
202
 
226
203
  This produces the following configs:
227
204
 
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
- }
205
+ ```console
206
+ $ uv run toml-combine example.toml --environment=production --service=frontend
207
+ registry = "gcr.io/my-project/"
208
+ service_account = "my-service-account"
209
+ name = "service-frontend"
210
+ [dimensions]
211
+ environment = "production"
212
+ service = "frontend"
213
+
214
+ [container]
215
+ image_name = "my-image-frontend"
216
+ ```
217
+
218
+ ```console
219
+ $ toml-combine example.toml --environment=production --service=backend
220
+ registry = "gcr.io/my-project/"
221
+ service_account = "my-service-account"
222
+ name = "service-backend"
223
+ [dimensions]
224
+ environment = "production"
225
+ service = "backend"
226
+
227
+ [container]
228
+ image_name = "my-image-backend"
229
+ port = 8080
230
+ ```
231
+
232
+ ```console
233
+ $ toml-combine example.toml --environment=staging --service=frontend
234
+ registry = "gcr.io/my-project/"
235
+ service_account = "my-service-account"
236
+ name = "service-frontend"
237
+ [dimensions]
238
+ environment = "staging"
239
+ service = "frontend"
240
+
241
+ [container]
242
+ image_name = "my-image-frontend"
243
+ ```
244
+
245
+ ```console
246
+ $ toml-combine example.toml --environment=staging --service=backend
247
+ registry = "gcr.io/my-project/"
248
+ service_account = "my-service-account"
249
+ name = "service-backend"
250
+ [dimensions]
251
+ environment = "staging"
252
+ service = "backend"
253
+
254
+ [container]
255
+ image_name = "my-image-backend"
256
+ port = 8080
257
+
258
+ [container.env]
259
+ ENABLE_EXPENSIVE_MONITORING = false
260
+ ```
261
+
262
+ ```console
263
+ $ toml-combine example.toml --environment=dev --service=backend
264
+ registry = "gcr.io/my-project/"
265
+ service_account = "my-service-account"
266
+ name = "service-backend"
267
+ [dimensions]
268
+ environment = "dev"
269
+ service = "backend"
270
+
271
+ [container]
272
+ image_name = "my-image-backend"
273
+ port = 8080
274
+ [container.env]
275
+ DEBUG = true
276
+ ENABLE_EXPENSIVE_MONITORING = false
277
+
305
278
  ```
@@ -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=LLjBFNKz4sjwiyOM5hq9JaqvyNuogdzdyWJbGS5hpXs,5500
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.2.0.dist-info/METADATA,sha256=tuHxaP5V3DhE0unQt-XOnsL_TmCrNGP3mpxrSLuk30E,7777
9
+ toml_combine-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ toml_combine-0.2.0.dist-info/entry_points.txt,sha256=dXUQNom54uZt_7ylEG81iNYMamYpaFo9-ItcZJU6Uzc,58
11
+ toml_combine-0.2.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,,