bazel-lib/lib/yq.bzl

182 lines
5.8 KiB
Python

"""Wrapper rule around the `yq` tool
From the documentation at https://mikefarah.gitbook.io/yq:
> yq is a a lightweight and portable command-line YAML processor.
> yq uses jq like syntax but works with yaml files as well as json.
## Usage examples
```starlark
load("@aspect_bazel_lib//lib:yq.bzl", "yq")
```
```starlark
# Remove fields
yq(
name = "safe-config",
srcs = ["config.yaml"],
expression = "del(.credentials)",
)
```
```starlark
# Merge two yaml documents
yq(
name = "ab",
srcs = [
"a.yaml",
"b.yaml",
],
expression = ". as $item ireduce ({}; . * $item )",
)
```
```starlark
# Split a yaml file into several files
yq(
name = "split",
srcs = ["multidoc.yaml"],
outs = [
"first.yml",
"second.yml",
],
args = [
"-s '.a'", # Split expression
"--no-doc", # Exclude document separator --
],
)
```
```starlark
# Convert a yaml file to json
yq(
name = "convert-to-json",
srcs = ["foo.yaml"],
args = ["-o=json"],
outs = ["foo.json"],
)
```
```starlark
# Convert a json file to yaml
yq(
name = "convert-to-yaml",
srcs = ["bar.json"],
args = ["-P"],
outs = ["bar.yaml"],
)
```
```starlark
# Call yq in a genrule
genrule(
name = "generate",
srcs = ["farm.yaml"],
outs = ["genrule_output.yaml"],
cmd = "$(YQ_BIN) '.moo = \"cow\"' $(location farm.yaml) > $@",
toolchains = ["@yq_toolchains//:resolved_toolchain"],
)
```
```starlark
# With --stamp, causes properties to be replaced by version control info.
yq(
name = "stamped",
srcs = ["package.yaml"],
expression = "|".join([
"load(strenv(STAMP)) as $stamp",
# Provide a default using the "alternative operator" in case $stamp is empty dict.
".version = ($stamp.BUILD_EMBED_LABEL // "<unstamped>")",
]),
)
```
"""
load("//lib/private:yq.bzl", _is_split_operation = "is_split_operation", _yq_lib = "yq_lib")
_yq_rule = rule(
attrs = _yq_lib.attrs,
implementation = _yq_lib.implementation,
toolchains = ["@aspect_bazel_lib//lib:yq_toolchain_type"],
)
def yq(name, srcs, expression = ".", args = [], outs = None, **kwargs):
"""Invoke yq with an expression on a set of input files.
yq is capable of parsing and outputting to other formats. See their [docs](https://mikefarah.gitbook.io/yq) for more examples.
Args:
name: Name of the rule
srcs: List of input file labels
expression: yq expression (https://mikefarah.gitbook.io/yq/commands/evaluate).
Defaults to the identity
expression ".". Subject to stamp variable replacements, see [Stamping](./stamping.md).
When stamping is enabled, an environment variable named "STAMP" will be available in the expression.
Be careful to write the filter so that it handles unstamped builds, as in the example above.
args: Additional args to pass to yq.
Note that you do not need to pass _eval_ or _eval-all_ as this
is handled automatically based on the number `srcs`. Passing the output format or the parse format
is optional as these can be guessed based on the file extensions in `srcs` and `outs`.
outs: Name of the output files.
Defaults to a single output with the name plus a ".yaml" extension, or
the extension corresponding to a passed output argument (e.g., "-o=json"). For split operations you
must declare all outputs as the name of the output files depends on the expression.
**kwargs: Other common named parameters such as `tags` or `visibility`
"""
args = args[:]
if not _is_split_operation(args):
# For split operations we can't predeclare outs because the name of the resulting files
# depends on the expression. For non-split operations, set a default output file name
# based on the name and the output format passed, defaulting to yaml.
if not outs:
outs = [name + ".yaml"]
if "-o=json" in args or "--outputformat=json" in args:
outs = [name + ".json"]
if "-o=xml" in args or "--outputformat=xml" in args:
outs = [name + ".xml"]
elif "-o=props" in args or "--outputformat=props" in args:
outs = [name + ".properties"]
elif "-o=c" in args or "--outputformat=csv" in args:
outs = [name + ".csv"]
elif "-o=t" in args or "--outputformat=tsv" in args:
outs = [name + ".tsv"]
elif outs and len(outs) == 1:
# If an output file with an extension was provided, try to set the corresponding output
# argument if it wasn't already passed.
if outs[0].endswith(".json") and "-o=json" not in args and "--outputformat=json" not in args:
args.append("-o=json")
elif outs[0].endswith(".xml") and "-o=xml" not in args and "--outputformat=xml" not in args:
args.append("-o=xml")
elif outs[0].endswith(".properties") and "-o=props" not in args and "--outputformat=props" not in args:
args.append("-o=props")
elif outs[0].endswith(".csv") and "-o=c" not in args and "--outputformat=csv" not in args:
args.append("-o=c")
elif outs[0].endswith(".tsv") and "-o=t" not in args and "--outputformat=tsv" not in args:
args.append("-o=t")
# If the input files are json or xml, set the parse flag if it isn't already set
if len(srcs) > 0:
if srcs[0].endswith(".json") and "-P" not in args:
args.append("-P")
elif srcs[0].endswith(".xml") and "-p=xml" not in args:
args.append("-p=xml")
_yq_rule(
name = name,
srcs = srcs,
expression = expression,
args = args,
outs = outs,
**kwargs
)