Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ bazel_dep(name = "rules_pkg", version = "1.0.1", dev_dependency = True)
bazel_dep(name = "rules_testing", version = "0.6.0", dev_dependency = True)
bazel_dep(name = "rules_cc", version = "0.0.17", dev_dependency = True)
bazel_dep(name = "rules_shell", version = "0.3.0", dev_dependency = True)
bazel_dep(name = "bazel_features", version = "1.32.0", dev_dependency = True)

# Needed for bazelci and for building distribution tarballs.
# If using an unreleased version of bazel_skylib via git_override, apply
Expand Down
11 changes: 11 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ rules_shell_dependencies()

rules_shell_toolchains()

http_archive(
name = "bazel_features",
sha256 = "07bd2b18764cdee1e0d6ff42c9c0a6111ffcbd0c17f0de38e7f44f1519d1c0cd",
strip_prefix = "bazel_features-1.32.0",
url = "https://github.com/bazel-contrib/bazel_features/releases/download/v1.32.0/bazel_features-v1.32.0.tar.gz",
)

load("@bazel_features//:deps.bzl", "bazel_features_deps")

bazel_features_deps()

maybe(
http_archive,
name = "io_bazel_stardoc",
Expand Down
25 changes: 25 additions & 0 deletions docs/structs_doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,31 @@

Skylib module containing functions that operate on structs.

<a id="structs.merge"></a>

## structs.merge

<pre>
load("@bazel_skylib//lib:structs.bzl", "structs")

structs.merge(<a href="#structs.merge-first">first</a>, <a href="#structs.merge-rest">*rest</a>)
</pre>

Merges multiple `struct` instances together. Later `struct` keys overwrite early `struct` keys.

**PARAMETERS**


| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
| <a id="structs.merge-first"></a>first | The initial `struct` to merge keys/values into. | none |
| <a id="structs.merge-rest"></a>rest | Other `struct` instances to merge. | none |

**RETURNS**

A merged `struct`.


<a id="structs.to_dict"></a>

## structs.to_dict
Expand Down
32 changes: 31 additions & 1 deletion lib/structs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@

"""Skylib module containing functions that operate on structs."""

_built_in_function = type(str)

def _is_built_in_function(v):
"""Returns True if v is an instance of a built-in function.

Args:
v: The value whose type should be checked.

Returns:
True if v is an instance of a built-in function, False otherwise.
"""

return type(v) == _built_in_function

def _to_dict(s):
"""Converts a `struct` to a `dict`.

Expand All @@ -31,9 +45,25 @@ def _to_dict(s):
return {
key: getattr(s, key)
for key in dir(s)
if key != "to_json" and key != "to_proto"
if not ((key == "to_json" or key == "to_proto") and _is_built_in_function(getattr(s, key)))
}

def _merge(first, *rest):
"""Merges multiple `struct` instances together. Later `struct` keys overwrite early `struct` keys.

Args:
first: The initial `struct` to merge keys/values into.
*rest: Other `struct` instances to merge.

Returns:
A merged `struct`.
"""
map = _to_dict(first)
for r in rest:
map |= _to_dict(r)
return struct(**map)

structs = struct(
to_dict = _to_dict,
merge = _merge,
)
54 changes: 50 additions & 4 deletions tests/structs_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@

"""Unit tests for structs.bzl."""

load("@bazel_features//:features.bzl", "bazel_features")
load("//lib:structs.bzl", "structs")
load("//lib:unittest.bzl", "asserts", "unittest")

def _add_test(ctx):
"""Unit tests for dicts.add."""
def _placeholder():
pass

def _to_dict_test(ctx):
"""Unit tests for structs.to_dict."""
env = unittest.begin(ctx)

# Test zero- and one-argument behavior.
Expand All @@ -42,13 +46,55 @@ def _add_test(ctx):
structs.to_dict(struct(a = 1, b = struct(bb = 1))),
)

# Older Bazel denied creating `struct` with `to_json`/`to_proto`
if not bazel_features.rules.no_struct_field_denylist:
return unittest.end(env)

# Test `to_json`/`to_proto` values are propagated
asserts.equals(
env,
{"to_json": 1, "to_proto": 2},
structs.to_dict(struct(to_json = 1, to_proto = 2)),
)

# Test `to_json`/`to_proto` functions are propagated
asserts.equals(
env,
{"to_json": _placeholder, "to_proto": _placeholder},
structs.to_dict(struct(to_json = _placeholder, to_proto = _placeholder)),
)

return unittest.end(env)

to_dict_test = unittest.make(_to_dict_test)

def _merge_test(ctx):
"""Unit tests for structs.merge."""
env = unittest.begin(ctx)

# Fixtures
a = struct(a = 1)
b = struct(b = 2)
c = struct(a = 3)

# Test one argument
asserts.equals(env, {"a": 1}, structs.to_dict(structs.merge(a)))

# Test two arguments
asserts.equals(env, {"a": 1}, structs.to_dict(structs.merge(a, a)))
asserts.equals(env, {"a": 1, "b": 2}, structs.to_dict(structs.merge(a, b)))

# Test overwrite
asserts.equals(env, {"a": 3}, structs.to_dict(structs.merge(a, c)))

return unittest.end(env)

add_test = unittest.make(_add_test)
merge_test = unittest.make(_merge_test)

def structs_test_suite():
"""Creates the test targets and test suite for structs.bzl tests."""
unittest.suite(
"structs_tests",
add_test,
to_dict_test,
merge_test,
)