Skip to content
Merged
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
53 changes: 53 additions & 0 deletions base/cvd/cuttlefish/pretty/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ cf_cc_library(
srcs = ["container.cc"],
hdrs = ["container.h"],
deps = [
"//cuttlefish/pretty",
"//cuttlefish/pretty:string",
"@abseil-cpp//absl/strings",
"@abseil-cpp//absl/strings:str_format",
],
Expand All @@ -26,11 +28,44 @@ cf_cc_test(
],
)

cf_cc_library(
name = "pretty",
hdrs = ["pretty.h"],
)

cf_cc_test(
name = "pretty_test",
srcs = ["pretty_test.cc"],
deps = [
"//cuttlefish/pretty",
"//cuttlefish/pretty:string",
"//cuttlefish/pretty:struct",
"//cuttlefish/pretty:unique_ptr",
"//cuttlefish/pretty:vector",
"@abseil-cpp//absl/strings",
"@fmt",
"@googletest//:gtest",
"@googletest//:gtest_main",
],
)

cf_cc_library(
name = "string",
srcs = ["string.cc"],
hdrs = ["string.h"],
deps = [
"//cuttlefish/pretty",
"@abseil-cpp//absl/strings",
],
)

cf_cc_library(
name = "struct",
srcs = ["struct.cc"],
hdrs = ["struct.h"],
deps = [
"//cuttlefish/pretty",
"//cuttlefish/pretty:string",
"@abseil-cpp//absl/strings",
"@abseil-cpp//absl/strings:str_format",
],
Expand All @@ -40,10 +75,28 @@ cf_cc_test(
name = "struct_test",
srcs = ["struct_test.cc"],
deps = [
"//cuttlefish/pretty:string",
"//cuttlefish/pretty:struct",
"@abseil-cpp//absl/strings",
"@fmt",
"@googletest//:gtest",
"@googletest//:gtest_main",
],
)

cf_cc_library(
name = "unique_ptr",
hdrs = ["unique_ptr.h"],
deps = [
"@abseil-cpp//absl/strings",
],
)

cf_cc_library(
name = "vector",
hdrs = ["vector.h"],
deps = [
"//cuttlefish/pretty:container",
"@abseil-cpp//absl/strings",
],
)
27 changes: 7 additions & 20 deletions base/cvd/cuttlefish/pretty/container.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,16 @@
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>

#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"

namespace cuttlefish {
namespace {
#include "cuttlefish/pretty/pretty.h"
#include "cuttlefish/pretty/string.h" // IWYU pragma: export

template <typename T>
constexpr bool ShouldQuote() {
using Simplified = std::decay_t<T>;
bool is_string = std::is_same_v<Simplified, std::string>;
bool is_string_view = std::is_same_v<Simplified, std::string_view>;
bool is_char_ptr = std::is_same_v<Simplified, char*>;
return is_string || is_string_view || is_char_ptr;
};

} // namespace
namespace cuttlefish {

/**
* Pretty-prints a container. Invoke this using the `PrettyContainer` overloads.
Expand Down Expand Up @@ -87,19 +77,16 @@ PrettyContainerType PrettyContainer(const T& container,
FmtMemberFn format_member_fn) {
PrettyContainerType pretty;
for (const auto& member : container) {
std::string formatted =
ShouldQuote<decltype(member)>()
? absl::StrCat("\"", format_member_fn(member), "\"")
: absl::StrCat(format_member_fn(member));
pretty.MemberInternal(formatted);
pretty.MemberInternal(absl::StrCat(format_member_fn(member)));
}
return pretty;
}

template <typename T>
PrettyContainerType PrettyContainer(const T& container) {
return PrettyContainer(container,
[](auto&& v) { return std::forward<decltype(v)>(v); });
return PrettyContainer(container, [](const auto& member) {
return Pretty(member, PrettyAdlPlaceholder());
});
}

std::ostream& operator<<(std::ostream& out, const PrettyContainerType&);
Expand Down
12 changes: 6 additions & 6 deletions base/cvd/cuttlefish/pretty/container_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,27 @@ void ExpectFormatsTo(const PrettyContainerType& ps,

} // namespace

TEST(PrettyStruct, Empty) {
TEST(PrettyContainer, Empty) {
ExpectFormatsTo(PrettyContainer(std::vector<int>{}), "{}");
}

TEST(PrettyStruct, OneMember) {
TEST(PrettyContainer, OneMember) {
ExpectFormatsTo(PrettyContainer(std::vector<int>{1}), R"(
{
1
}
)");
}

TEST(PrettyStruct, StringMember) {
TEST(PrettyContainer, StringMember) {
ExpectFormatsTo(PrettyContainer(std::vector<std::string_view>{"abc"}), R"(
{
"abc"
}
)");
}

TEST(PrettyStruct, TwoMembers) {
TEST(PrettyContainer, TwoMembers) {
ExpectFormatsTo(PrettyContainer(std::vector<int>{1, 2}), R"(
{
1,
Expand All @@ -69,7 +69,7 @@ TEST(PrettyStruct, TwoMembers) {
)");
}

TEST(PrettyStruct, MembersWithNewlines) {
TEST(PrettyContainer, MembersWithNewlines) {
ExpectFormatsTo(PrettyContainer(std::vector<std::string_view>{"abc\ndef"}),
R"(
{
Expand All @@ -79,7 +79,7 @@ TEST(PrettyStruct, MembersWithNewlines) {
)");
}

TEST(PrettyStruct, NestedMember) {
TEST(PrettyContainer, NestedMember) {
std::vector<std::vector<int>> container = {{1, 2}, {3, 4}};
ExpectFormatsTo(PrettyContainer(container, PrettyContainer<std::vector<int>>),
R"(
Expand Down
56 changes: 56 additions & 0 deletions base/cvd/cuttlefish/pretty/pretty.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// 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

namespace cuttlefish {

struct PrettyAdlPlaceholder {};

/**
* This is the fallback pretty-printing case: if abseil can print the value,
* have abseil do it.
*
* This function is intended to be overloaded. Overloads for common types are in
* this directory, but other types can also define their own overloads.
*
* Not all overloads will return `std::string`. The returned type should be
* formattable by abseil, libfmt, and ostreams. This allows deferring formatting
* to avoid allocating fewer intermediate strings.
*
* If a type defines both a cuttlefish::Pretty overload and an AbslStringify
* hook, the cuttlefish::Pretty implementation is preferred by overload
* resolution as it is a more specific type. However, if the cuttlefish::Pretty
* overload is defined in a different header, the presence of the header affects
* which overloads are in scope, so leaving the header include out could still
* compile but unintentionally refer to this default fallback. Therefore,
* cuttlefish::Pretty overloads should be declared together with the header.
*
* Worse than that, the `PrettyAdlPlaceHolder()` hack is there to trigger
* argument-dependent lookup within `namespace cuttlefish`: otherwise the
* `Pretty` overloads for non-`namespace cuttlefish` types will only be detected
* based on declaration order through standard name lookup rules. Having an
* argument type declared within `namespace cuttlefish` forces it to search the
* entire namespace rather than only declarations defined earlier in the
* translation unit. This is relevant for `Pretty` invocations within
* `PrettyContainer` and `PrettyStruct`.
*/
template <typename T>
const T& Pretty(const T& value,
PrettyAdlPlaceholder unused = PrettyAdlPlaceholder()) {
return value;
}

} // namespace cuttlefish
118 changes: 118 additions & 0 deletions base/cvd/cuttlefish/pretty/pretty_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* 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/pretty.h"

#include <memory>
#include <vector>

#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
#include "gtest/gtest.h"

#include "cuttlefish/pretty/string.h"
#include "cuttlefish/pretty/struct.h"
#include "cuttlefish/pretty/unique_ptr.h"
#include "cuttlefish/pretty/vector.h"

namespace cuttlefish {
namespace {

struct InnerStruct {
std::string inner_string;
int inner_number;
};

PrettyStruct Pretty(const InnerStruct& inner,
PrettyAdlPlaceholder unused = PrettyAdlPlaceholder()) {
return PrettyStruct("InnerStruct")
.Member("inner_string", inner.inner_string)
.Member("inner_number", inner.inner_number);
}

struct OuterStruct {
std::vector<int> number_vector;
InnerStruct nested_member;
std::vector<InnerStruct> nested_vector;
std::unique_ptr<int> int_ptr_set;
std::unique_ptr<int> int_ptr_unset;
};

PrettyStruct Pretty(const OuterStruct& outer,
PrettyAdlPlaceholder unused = PrettyAdlPlaceholder()) {
return PrettyStruct("OuterStruct")
.Member("number_vector", outer.number_vector)
.Member("nested_member", outer.nested_member)
.Member("nested_vector", outer.nested_vector)
.Member("int_ptr_set", outer.int_ptr_set)
.Member("int_ptr_unset", outer.int_ptr_unset);
}

} // namespace

TEST(Pretty, OuterInnerStruct) {
OuterStruct outer{
.number_vector = {1, 2, 3},
.nested_member =
{
.inner_string = "a",
.inner_number = 1,
},
.nested_vector =
{
{
.inner_string = "b",
.inner_number = 2,
},
{
.inner_string = "c",
.inner_number = 3,
},
},
.int_ptr_set = std::make_unique<int>(5),
.int_ptr_unset = std::unique_ptr<int>(),
};

std::string expected(absl::StripAsciiWhitespace(R"(
OuterStruct {
number_vector: {
1,
2,
3
},
nested_member: InnerStruct {
inner_string: "a",
inner_number: 1
},
nested_vector: {
InnerStruct {
inner_string: "b",
inner_number: 2
},
InnerStruct {
inner_string: "c",
inner_number: 3
}
},
int_ptr_set: 5,
int_ptr_unset: (nullptr)
}
)"));

EXPECT_EQ(absl::StrCat(Pretty(outer)), expected);
}

} // namespace cuttlefish
Loading
Loading