Skip to content

Commit 11933c0

Browse files
authored
Add a C++ native StringFormat (#1289)
* Add StringFormat * Use StringFormat
1 parent 936659e commit 11933c0

File tree

8 files changed

+399
-39
lines changed

8 files changed

+399
-39
lines changed

Firestore/Example/Firestore.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
/* Begin PBXBuildFile section */
2727
132E3E53179DE287D875F3F2 /* FSTLevelDBTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */; };
2828
3B843E4C1F3A182900548890 /* remote_store_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 3B843E4A1F3930A400548890 /* remote_store_spec_test.json */; };
29+
54131E9720ADE679001DF3FF /* string_format_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54131E9620ADE678001DF3FF /* string_format_test.cc */; };
2930
5436F32420008FAD006E51E3 /* string_printf_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5436F32320008FAD006E51E3 /* string_printf_test.cc */; };
3031
54511E8E209805F8005BD28F /* hashing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54511E8D209805F8005BD28F /* hashing_test.cc */; };
3132
5467FB01203E5717009C9584 /* FIRFirestoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */; };
@@ -257,6 +258,7 @@
257258
3B843E4A1F3930A400548890 /* remote_store_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = remote_store_spec_test.json; sourceTree = "<group>"; };
258259
3C81DE3772628FE297055662 /* Pods-Firestore_Example_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS.debug.xcconfig"; sourceTree = "<group>"; };
259260
3F0992A4B83C60841C52E960 /* Pods-Firestore_Example_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS.release.xcconfig"; sourceTree = "<group>"; };
261+
54131E9620ADE678001DF3FF /* string_format_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = string_format_test.cc; sourceTree = "<group>"; };
260262
5436F32320008FAD006E51E3 /* string_printf_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = string_printf_test.cc; sourceTree = "<group>"; };
261263
54511E8D209805F8005BD28F /* hashing_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = hashing_test.cc; sourceTree = "<group>"; };
262264
5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRFirestoreTests.mm; sourceTree = "<group>"; };
@@ -560,6 +562,7 @@
560562
54A0352B20A3B3D7003E0143 /* status_test_util.h */,
561563
54A0352C20A3B3D7003E0143 /* status_test.cc */,
562564
54A0352D20A3B3D7003E0143 /* statusor_test.cc */,
565+
54131E9620ADE678001DF3FF /* string_format_test.cc */,
563566
5436F32320008FAD006E51E3 /* string_printf_test.cc */,
564567
AB380CFC201A2EE200D97691 /* string_util_test.cc */,
565568
);
@@ -1474,6 +1477,7 @@
14741477
B686F2B22025000D0028D6BE /* resource_path_test.cc in Sources */,
14751478
5492E0CA2021557E00B64F25 /* FSTWatchChangeTests.mm in Sources */,
14761479
5492E063202154B900B64F25 /* FSTViewSnapshotTest.mm in Sources */,
1480+
54131E9720ADE679001DF3FF /* string_format_test.cc in Sources */,
14771481
5492E058202154AB00B64F25 /* FSTAPIHelpers.mm in Sources */,
14781482
AB380CFB2019388600D97691 /* target_id_generator_test.cc in Sources */,
14791483
5492E0A82021552D00B64F25 /* FSTLevelDBLocalStoreTests.mm in Sources */,

Firestore/core/src/firebase/firestore/util/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@ include(CheckIncludeFiles)
2222
cc_library(
2323
firebase_firestore_util_base
2424
SOURCES
25+
string_format.cc
26+
string_format.h
2527
string_printf.cc
2628
string_printf.h
2729
DEPENDS
2830
absl_base
31+
absl_strings
2932
)
3033

3134
## assert and log

Firestore/core/src/firebase/firestore/util/status.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
#include "Firestore/core/src/firebase/firestore/util/status.h"
1818

19-
#include "Firestore/core/src/firebase/firestore/util/string_printf.h"
19+
#include "Firestore/core/src/firebase/firestore/util/string_format.h"
2020

2121
namespace firebase {
2222
namespace firestore {
@@ -103,7 +103,7 @@ std::string Status::ToString() const {
103103
result = "Data loss";
104104
break;
105105
default:
106-
result = StringPrintf("Unknown code(%d)", static_cast<int>(code()));
106+
result = StringFormat("Unknown code(%s)", code());
107107
break;
108108
}
109109
result += ": ";
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2018 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "Firestore/core/src/firebase/firestore/util/string_format.h"
18+
19+
namespace firebase {
20+
namespace firestore {
21+
namespace util {
22+
namespace internal {
23+
24+
static const char* kMissing = "<missing>";
25+
static const char* kInvalid = "<invalid>";
26+
27+
std::string StringFormatPieces(
28+
const char* format, std::initializer_list<absl::string_view> pieces) {
29+
std::string result;
30+
31+
const char* format_iter = format;
32+
const char* format_end = format + strlen(format);
33+
34+
auto pieces_iter = pieces.begin();
35+
auto pieces_end = pieces.end();
36+
auto append_next_piece = [&](std::string* dest) {
37+
if (pieces_iter == pieces_end) {
38+
dest->append(kMissing);
39+
} else {
40+
// Pass a piece through
41+
dest->append(pieces_iter->data(), pieces_iter->size());
42+
++pieces_iter;
43+
}
44+
};
45+
46+
auto append_specifier = [&](char spec) {
47+
switch (spec) {
48+
case '%':
49+
// Pass through literal %.
50+
result.push_back('%');
51+
break;
52+
53+
case 's': {
54+
append_next_piece(&result);
55+
break;
56+
}
57+
58+
default:
59+
result.append(kInvalid);
60+
break;
61+
}
62+
};
63+
64+
// Iterate through the format string, advancing `format_iter` as we go.
65+
while (true) {
66+
const char* percent_ptr = std::find(format_iter, format_end, '%');
67+
68+
// percent either points to the next format specifier or the end of the
69+
// format string. Either is safe to append here:
70+
result.append(format_iter, percent_ptr);
71+
if (percent_ptr == format_end) {
72+
// No further pieces to format
73+
break;
74+
}
75+
76+
// Examine the specifier:
77+
const char* spec_ptr = percent_ptr + 1;
78+
if (spec_ptr == format_end) {
79+
// Incomplete specifier, treat as a literal "%" and be done.
80+
append_specifier('%');
81+
break;
82+
}
83+
append_specifier(*spec_ptr);
84+
85+
format_iter = spec_ptr + 1;
86+
}
87+
88+
return result;
89+
}
90+
91+
} // namespace internal
92+
} // namespace util
93+
} // namespace firestore
94+
} // namespace firebase
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* Copyright 2018 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STRING_FORMAT_H_
18+
#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STRING_FORMAT_H_
19+
20+
#include <initializer_list>
21+
#include <string>
22+
#include <utility>
23+
24+
#include "absl/base/attributes.h"
25+
#include "absl/strings/str_cat.h"
26+
#include "absl/strings/string_view.h"
27+
28+
namespace firebase {
29+
namespace firestore {
30+
namespace util {
31+
32+
namespace internal {
33+
34+
std::string StringFormatPieces(const char* format,
35+
std::initializer_list<absl::string_view> pieces);
36+
37+
/**
38+
* Explicit ranking for formatting choices. Only useful as an implementation
39+
* detail of `FormatArg`.
40+
*/
41+
template <int I>
42+
struct FormatChoice : FormatChoice<I + 1> {};
43+
44+
template <>
45+
struct FormatChoice<4> {};
46+
47+
} // namespace internal
48+
49+
/**
50+
* Acts as the main value parameter to StringFormat and related functions.
51+
*
52+
* Chooses a conversion to a text form in this order:
53+
* * If the value is exactly of `bool` type (without implicit conversions)
54+
* the text will the "true" or "false".
55+
* * If the value is of type `const char*`, the text will be the value
56+
* interpreted as a C string. To show the address of a single char or to
57+
* show the `const char*` as an address, cast to `void*`.
58+
* * If the value is any other pointer type, the text will be the hexidecimal
59+
* formatting of the value as an unsigned integer.
60+
* * Otherwise the value is interpreted as anything absl::AlphaNum accepts.
61+
*/
62+
class FormatArg : public absl::AlphaNum {
63+
public:
64+
template <typename T>
65+
FormatArg(T&& value) // NOLINT(runtime/explicit)
66+
: FormatArg{std::forward<T>(value), internal::FormatChoice<0>{}} {
67+
}
68+
69+
private:
70+
/**
71+
* Creates a FormatArg from a boolean value, representing the string
72+
* "true" or "false".
73+
*
74+
* This overload only applies if the type of the argument is exactly `bool`.
75+
* No implicit conversions to bool are accepted.
76+
*/
77+
template <typename T,
78+
typename = typename std::enable_if<std::is_same<bool, T>{}>::type>
79+
FormatArg(T bool_value, internal::FormatChoice<0>)
80+
: AlphaNum{bool_value ? "true" : "false"} {
81+
}
82+
83+
/**
84+
* Creates a FormatArg from a character string literal. This is
85+
* handled specially to avoid ambiguity with generic pointers, which are
86+
* handled differently.
87+
*/
88+
FormatArg(std::nullptr_t, internal::FormatChoice<1>) : AlphaNum{"null"} {
89+
}
90+
91+
/**
92+
* Creates a FormatArg from a character string literal. This is
93+
* handled specially to avoid ambiguity with generic pointers, which are
94+
* handled differently.
95+
*/
96+
FormatArg(const char* string_value, internal::FormatChoice<2>)
97+
: AlphaNum{string_value == nullptr ? "null" : string_value} {
98+
}
99+
100+
/**
101+
* Creates a FormatArg from an arbitrary pointer, represented as a
102+
* hexidecimal integer literal.
103+
*/
104+
template <typename T>
105+
FormatArg(T* pointer_value, internal::FormatChoice<3>)
106+
: AlphaNum{absl::Hex{reinterpret_cast<uintptr_t>(pointer_value)}} {
107+
}
108+
109+
/**
110+
* As a final fallback, creates a FormatArg from any type of value that
111+
* absl::AlphaNum accepts.
112+
*/
113+
template <typename T>
114+
FormatArg(T&& value, internal::FormatChoice<4>)
115+
: AlphaNum{std::forward<T>(value)} {
116+
}
117+
};
118+
119+
/**
120+
* Formats a string using a simplified printf-like formatting mechanism that
121+
* does not rely on C varargs.
122+
*
123+
* The following format specifiers are recognized:
124+
* * "%%" - A literal "%"
125+
* * "%s" - The next parameter is copied through
126+
*
127+
* Note:
128+
* * If you pass fewer arguments than the format requires, StringFormat will
129+
* insert "<missing>".
130+
* * If you pass more arguments than the format requires, any excess arguments
131+
* are ignored.
132+
* * If you use an invalid format specifier, StringFormat will insert
133+
* <invalid>.
134+
*/
135+
template <typename... FA>
136+
std::string StringFormat(const char* format, const FA&... args) {
137+
return internal::StringFormatPieces(
138+
format, {static_cast<const FormatArg&>(args).Piece()...});
139+
}
140+
141+
inline std::string StringFormat() {
142+
return {};
143+
}
144+
145+
} // namespace util
146+
} // namespace firestore
147+
} // namespace firebase
148+
149+
#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STRING_FORMAT_H_

Firestore/core/test/firebase/firestore/util/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ cc_test(
129129
status_test.cc
130130
status_test_util.h
131131
statusor_test.cc
132+
string_format_test.cc
132133
string_printf_test.cc
133134
string_util_test.cc
134135
DEPENDS

0 commit comments

Comments
 (0)