From 729b52919ff31d8c0f104dcc44ba92e4d61d83a6 Mon Sep 17 00:00:00 2001 From: "A. Cody Schuffelen" Date: Fri, 9 Jan 2026 15:30:12 -0800 Subject: [PATCH] Add pretty-printing for structs, comparable to Rust's std::fmt::DebugStruct. Supports abseil string formatting, libfmt, and ostreams. Example usage: ``` const PrettyStruct inner = PrettyStruct("Inner").Member("i1", 1).Member("i2", 2); const PrettyStruct outer = PrettyStruct("Outer").Member("o1", inner).Member("o2", inner); ``` formats as ``` Outer { o1: Inner { i1: 1, i2: 2 }, o2: Inner { i1: 1, i2: 2 } } ``` Bug: b/474678754 --- base/cvd/cuttlefish/pretty/BUILD.bazel | 27 ++++++ base/cvd/cuttlefish/pretty/struct.cc | 73 ++++++++++++++++ base/cvd/cuttlefish/pretty/struct.h | 102 ++++++++++++++++++++++ base/cvd/cuttlefish/pretty/struct_test.cc | 89 +++++++++++++++++++ 4 files changed, 291 insertions(+) create mode 100644 base/cvd/cuttlefish/pretty/BUILD.bazel create mode 100644 base/cvd/cuttlefish/pretty/struct.cc create mode 100644 base/cvd/cuttlefish/pretty/struct.h create mode 100644 base/cvd/cuttlefish/pretty/struct_test.cc diff --git a/base/cvd/cuttlefish/pretty/BUILD.bazel b/base/cvd/cuttlefish/pretty/BUILD.bazel new file mode 100644 index 00000000000..c66367103b8 --- /dev/null +++ b/base/cvd/cuttlefish/pretty/BUILD.bazel @@ -0,0 +1,27 @@ +load("//cuttlefish/bazel:rules.bzl", "cf_cc_library", "cf_cc_test") + +package( + default_visibility = ["//:android_cuttlefish"], +) + +cf_cc_library( + name = "struct", + srcs = ["struct.cc"], + hdrs = ["struct.h"], + deps = [ + "@abseil-cpp//absl/strings", + "@abseil-cpp//absl/strings:str_format", + ], +) + +cf_cc_test( + name = "struct_test", + srcs = ["struct_test.cc"], + deps = [ + "//cuttlefish/pretty:struct", + "@abseil-cpp//absl/strings", + "@fmt", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) diff --git a/base/cvd/cuttlefish/pretty/struct.cc b/base/cvd/cuttlefish/pretty/struct.cc new file mode 100644 index 00000000000..ed1c02fa345 --- /dev/null +++ b/base/cvd/cuttlefish/pretty/struct.cc @@ -0,0 +1,73 @@ +// +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cuttlefish/pretty/struct.h" + +#include +#include + +#include "absl/strings/str_format.h" +#include "absl/strings/str_replace.h" + +namespace cuttlefish { + +PrettyStruct::PrettyStruct(std::string_view name) : name_(name) {} + +PrettyStruct& PrettyStruct::Member(std::string_view name, + std::string_view value) & { + MemberInternal(absl::StrCat(name, ": \"", value, "\"")); + return *this; +} + +PrettyStruct PrettyStruct::Member(std::string_view name, + std::string_view value) && { + Member(name, value); + return *this; +} + +PrettyStruct& PrettyStruct::Member(std::string_view name, const char* value) & { + Member(name, std::string_view(value)); + return *this; +} + +PrettyStruct PrettyStruct::Member(std::string_view name, const char* value) && { + Member(name, std::string_view(value)); + return *this; +} + +PrettyStruct& PrettyStruct::Member(std::string_view name, + const std::string& value) & { + Member(name, std::string_view(value)); + return *this; +} + +PrettyStruct PrettyStruct::Member(std::string_view name, + const std::string& value) && { + Member(name, std::string_view(value)); + return *this; +} + +void PrettyStruct::MemberInternal(std::string_view line) { + members_.emplace_back(absl::StrReplaceAll(line, {{"\n", "\n "}})); +} + +std::ostream& operator<<(std::ostream& out, const PrettyStruct& ps) { + return out << absl::StreamFormat("%v", ps); +} + +// For libfmt +std::string format_as(const PrettyStruct& ps) { return absl::StrCat(ps); } + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/pretty/struct.h b/base/cvd/cuttlefish/pretty/struct.h new file mode 100644 index 00000000000..50e363ef707 --- /dev/null +++ b/base/cvd/cuttlefish/pretty/struct.h @@ -0,0 +1,102 @@ +// +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include +#include + +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" + +namespace cuttlefish { + +/** + * Creates a "formatted struct", comparable to Rust's std::fmt::DebugStruct. + * + * Supports abseil string formatting, libfmt, and ostreams. + * + * Example usage: + * ``` + * const PrettyStruct inner = + * PrettyStruct("Inner").Member("i1", 1).Member("i2", 2); + * const PrettyStruct outer = + * PrettyStruct("Outer").Member("o1", inner).Member("o2", inner); + * ``` + * formats as + * ``` + * Outer { + * o1: Inner { + * i1: 1, + * i2: 2 + * }, + * o2: Inner { + * i1: 1, + * i2: 2 + * } + * } + * ``` + */ +class PrettyStruct { + public: + explicit PrettyStruct(std::string_view name); + + template + PrettyStruct& Member(std::string_view name, const T& value) & { + MemberInternal(absl::StrCat(name, ": ", value)); + return *this; + } + + template + PrettyStruct Member(std::string_view name, const T& value) && { + this->Member(name, value); + return std::move(*this); + } + + // String members are quoted + PrettyStruct& Member(std::string_view name, std::string_view value) &; + PrettyStruct Member(std::string_view name, std::string_view value) &&; + PrettyStruct& Member(std::string_view name, const char* value) &; + PrettyStruct Member(std::string_view name, const char* value) &&; + PrettyStruct& Member(std::string_view name, const std::string& value) &; + PrettyStruct Member(std::string_view name, const std::string& value) &&; + + template + friend void AbslStringify(Sink& sink, const PrettyStruct& ps) { + if (ps.members_.empty()) { + absl::Format(&sink, "%v {}", ps.name_); + } else { + absl::Format(&sink, "%v {\n %v\n}", ps.name_, + absl::StrJoin(ps.members_, ",\n ")); + } + } + + private: + void MemberInternal(std::string_view); + + std::string name_; + std::vector members_; +}; + +std::ostream& operator<<(std::ostream&, const PrettyStruct&); + +// For libfmt +std::string format_as(const PrettyStruct&); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/pretty/struct_test.cc b/base/cvd/cuttlefish/pretty/struct_test.cc new file mode 100644 index 00000000000..8f476f9bc27 --- /dev/null +++ b/base/cvd/cuttlefish/pretty/struct_test.cc @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cuttlefish/pretty/struct.h" + +#include + +#include "absl/strings/ascii.h" +#include "fmt/format.h" +#include "gtest/gtest.h" + +namespace cuttlefish { +namespace { + +void ExpectFormatsTo(const PrettyStruct& ps, const std::string_view expected) { + const std::string_view trimmed = absl::StripAsciiWhitespace(expected); + + EXPECT_EQ(absl::StrCat(ps), trimmed); + EXPECT_EQ(fmt::format("{}", ps), trimmed); + + std::stringstream sstream; + sstream << ps; + EXPECT_EQ(sstream.str(), trimmed); +} + +} // namespace + +TEST(PrettyStruct, Empty) { + ExpectFormatsTo(PrettyStruct("Empty"), "Empty {}"); +} + +TEST(PrettyStruct, OneMember) { + ExpectFormatsTo(PrettyStruct("Pretty").Member("member", 5), R"( +Pretty { + member: 5 +} +)"); +} + +TEST(PrettyStruct, StringMember) { + ExpectFormatsTo(PrettyStruct("Pretty").Member("member", "value"), R"( +Pretty { + member: "value" +} +)"); +} + +TEST(PrettyStruct, TwoMembers) { + ExpectFormatsTo( + PrettyStruct("Pretty").Member("member_a", 5).Member("member_b", 6), R"( +Pretty { + member_a: 5, + member_b: 6 +} +)"); +} + +TEST(PrettyStruct, NestedMembers) { + const PrettyStruct inner = + PrettyStruct("Inner").Member("i1", 1).Member("i2", 2); + ExpectFormatsTo(PrettyStruct("Outer").Member("o1", inner).Member("o2", inner), + R"( +Outer { + o1: Inner { + i1: 1, + i2: 2 + }, + o2: Inner { + i1: 1, + i2: 2 + } +} +)"); +} + +} // namespace cuttlefish