gtest-printers.cc (googletest-release-1.10.0) | : | gtest-printers.cc (googletest-release-1.11.0) | ||
---|---|---|---|---|
skipping to change at line 44 | skipping to change at line 44 | |||
// | // | |||
// void ::testing::internal::UniversalPrinter<T>::Print(value, ostream_ptr); | // void ::testing::internal::UniversalPrinter<T>::Print(value, ostream_ptr); | |||
// | // | |||
// It uses the << operator when possible, and prints the bytes in the | // It uses the << operator when possible, and prints the bytes in the | |||
// object otherwise. A user can override its behavior for a class | // object otherwise. A user can override its behavior for a class | |||
// type Foo by defining either operator<<(::std::ostream&, const Foo&) | // type Foo by defining either operator<<(::std::ostream&, const Foo&) | |||
// or void PrintTo(const Foo&, ::std::ostream*) in the namespace that | // or void PrintTo(const Foo&, ::std::ostream*) in the namespace that | |||
// defines Foo. | // defines Foo. | |||
#include "gtest/gtest-printers.h" | #include "gtest/gtest-printers.h" | |||
#include <stdio.h> | #include <stdio.h> | |||
#include <cctype> | #include <cctype> | |||
#include <cstdint> | ||||
#include <cwchar> | #include <cwchar> | |||
#include <ostream> // NOLINT | #include <ostream> // NOLINT | |||
#include <string> | #include <string> | |||
#include <type_traits> | ||||
#include "gtest/internal/gtest-port.h" | #include "gtest/internal/gtest-port.h" | |||
#include "src/gtest-internal-inl.h" | #include "src/gtest-internal-inl.h" | |||
namespace testing { | namespace testing { | |||
namespace { | namespace { | |||
using ::std::ostream; | using ::std::ostream; | |||
// Prints a segment of bytes in the given object. | // Prints a segment of bytes in the given object. | |||
skipping to change at line 104 | skipping to change at line 109 | |||
} else { | } else { | |||
PrintByteSegmentInObjectTo(obj_bytes, 0, kChunkSize, os); | PrintByteSegmentInObjectTo(obj_bytes, 0, kChunkSize, os); | |||
*os << " ... "; | *os << " ... "; | |||
// Rounds up to 2-byte boundary. | // Rounds up to 2-byte boundary. | |||
const size_t resume_pos = (count - kChunkSize + 1)/2*2; | const size_t resume_pos = (count - kChunkSize + 1)/2*2; | |||
PrintByteSegmentInObjectTo(obj_bytes, resume_pos, count - resume_pos, os); | PrintByteSegmentInObjectTo(obj_bytes, resume_pos, count - resume_pos, os); | |||
} | } | |||
*os << ">"; | *os << ">"; | |||
} | } | |||
// Helpers for widening a character to char32_t. Since the standard does not | ||||
// specify if char / wchar_t is signed or unsigned, it is important to first | ||||
// convert it to the unsigned type of the same width before widening it to | ||||
// char32_t. | ||||
template <typename CharType> | ||||
char32_t ToChar32(CharType in) { | ||||
return static_cast<char32_t>( | ||||
static_cast<typename std::make_unsigned<CharType>::type>(in)); | ||||
} | ||||
} // namespace | } // namespace | |||
namespace internal2 { | namespace internal { | |||
// Delegates to PrintBytesInObjectToImpl() to print the bytes in the | // Delegates to PrintBytesInObjectToImpl() to print the bytes in the | |||
// given object. The delegation simplifies the implementation, which | // given object. The delegation simplifies the implementation, which | |||
// uses the << operator and thus is easier done outside of the | // uses the << operator and thus is easier done outside of the | |||
// ::testing::internal namespace, which contains a << operator that | // ::testing::internal namespace, which contains a << operator that | |||
// sometimes conflicts with the one in STL. | // sometimes conflicts with the one in STL. | |||
void PrintBytesInObjectTo(const unsigned char* obj_bytes, size_t count, | void PrintBytesInObjectTo(const unsigned char* obj_bytes, size_t count, | |||
ostream* os) { | ostream* os) { | |||
PrintBytesInObjectToImpl(obj_bytes, count, os); | PrintBytesInObjectToImpl(obj_bytes, count, os); | |||
} | } | |||
} // namespace internal2 | ||||
namespace internal { | ||||
// Depending on the value of a char (or wchar_t), we print it in one | // Depending on the value of a char (or wchar_t), we print it in one | |||
// of three formats: | // of three formats: | |||
// - as is if it's a printable ASCII (e.g. 'a', '2', ' '), | // - as is if it's a printable ASCII (e.g. 'a', '2', ' '), | |||
// - as a hexadecimal escape sequence (e.g. '\x7F'), or | // - as a hexadecimal escape sequence (e.g. '\x7F'), or | |||
// - as a special escape sequence (e.g. '\r', '\n'). | // - as a special escape sequence (e.g. '\r', '\n'). | |||
enum CharFormat { | enum CharFormat { | |||
kAsIs, | kAsIs, | |||
kHexEscape, | kHexEscape, | |||
kSpecialEscape | kSpecialEscape | |||
}; | }; | |||
// Returns true if c is a printable ASCII character. We test the | // Returns true if c is a printable ASCII character. We test the | |||
// value of c directly instead of calling isprint(), which is buggy on | // value of c directly instead of calling isprint(), which is buggy on | |||
// Windows Mobile. | // Windows Mobile. | |||
inline bool IsPrintableAscii(wchar_t c) { | inline bool IsPrintableAscii(char32_t c) { return 0x20 <= c && c <= 0x7E; } | |||
return 0x20 <= c && c <= 0x7E; | ||||
} | ||||
// Prints a wide or narrow char c as a character literal without the | // Prints c (of type char, char8_t, char16_t, char32_t, or wchar_t) as a | |||
// quotes, escaping it when necessary; returns how c was formatted. | // character literal without the quotes, escaping it when necessary; returns how | |||
// The template argument UnsignedChar is the unsigned version of Char, | // c was formatted. | |||
// which is the type of c. | template <typename Char> | |||
template <typename UnsignedChar, typename Char> | ||||
static CharFormat PrintAsCharLiteralTo(Char c, ostream* os) { | static CharFormat PrintAsCharLiteralTo(Char c, ostream* os) { | |||
wchar_t w_c = static_cast<wchar_t>(c); | const char32_t u_c = ToChar32(c); | |||
switch (w_c) { | switch (u_c) { | |||
case L'\0': | case L'\0': | |||
*os << "\\0"; | *os << "\\0"; | |||
break; | break; | |||
case L'\'': | case L'\'': | |||
*os << "\\'"; | *os << "\\'"; | |||
break; | break; | |||
case L'\\': | case L'\\': | |||
*os << "\\\\"; | *os << "\\\\"; | |||
break; | break; | |||
case L'\a': | case L'\a': | |||
skipping to change at line 179 | skipping to change at line 187 | |||
case L'\r': | case L'\r': | |||
*os << "\\r"; | *os << "\\r"; | |||
break; | break; | |||
case L'\t': | case L'\t': | |||
*os << "\\t"; | *os << "\\t"; | |||
break; | break; | |||
case L'\v': | case L'\v': | |||
*os << "\\v"; | *os << "\\v"; | |||
break; | break; | |||
default: | default: | |||
if (IsPrintableAscii(w_c)) { | if (IsPrintableAscii(u_c)) { | |||
*os << static_cast<char>(c); | *os << static_cast<char>(c); | |||
return kAsIs; | return kAsIs; | |||
} else { | } else { | |||
ostream::fmtflags flags = os->flags(); | ostream::fmtflags flags = os->flags(); | |||
*os << "\\x" << std::hex << std::uppercase | *os << "\\x" << std::hex << std::uppercase << static_cast<int>(u_c); | |||
<< static_cast<int>(static_cast<UnsignedChar>(c)); | ||||
os->flags(flags); | os->flags(flags); | |||
return kHexEscape; | return kHexEscape; | |||
} | } | |||
} | } | |||
return kSpecialEscape; | return kSpecialEscape; | |||
} | } | |||
// Prints a wchar_t c as if it's part of a string literal, escaping it when | // Prints a char32_t c as if it's part of a string literal, escaping it when | |||
// necessary; returns how c was formatted. | // necessary; returns how c was formatted. | |||
static CharFormat PrintAsStringLiteralTo(wchar_t c, ostream* os) { | static CharFormat PrintAsStringLiteralTo(char32_t c, ostream* os) { | |||
switch (c) { | switch (c) { | |||
case L'\'': | case L'\'': | |||
*os << "'"; | *os << "'"; | |||
return kAsIs; | return kAsIs; | |||
case L'"': | case L'"': | |||
*os << "\\\""; | *os << "\\\""; | |||
return kSpecialEscape; | return kSpecialEscape; | |||
default: | default: | |||
return PrintAsCharLiteralTo<wchar_t>(c, os); | return PrintAsCharLiteralTo(c, os); | |||
} | } | |||
} | } | |||
static const char* GetCharWidthPrefix(char) { | ||||
return ""; | ||||
} | ||||
static const char* GetCharWidthPrefix(signed char) { | ||||
return ""; | ||||
} | ||||
static const char* GetCharWidthPrefix(unsigned char) { | ||||
return ""; | ||||
} | ||||
#ifdef __cpp_char8_t | ||||
static const char* GetCharWidthPrefix(char8_t) { | ||||
return "u8"; | ||||
} | ||||
#endif | ||||
static const char* GetCharWidthPrefix(char16_t) { | ||||
return "u"; | ||||
} | ||||
static const char* GetCharWidthPrefix(char32_t) { | ||||
return "U"; | ||||
} | ||||
static const char* GetCharWidthPrefix(wchar_t) { | ||||
return "L"; | ||||
} | ||||
// Prints a char c as if it's part of a string literal, escaping it when | // Prints a char c as if it's part of a string literal, escaping it when | |||
// necessary; returns how c was formatted. | // necessary; returns how c was formatted. | |||
static CharFormat PrintAsStringLiteralTo(char c, ostream* os) { | static CharFormat PrintAsStringLiteralTo(char c, ostream* os) { | |||
return PrintAsStringLiteralTo( | return PrintAsStringLiteralTo(ToChar32(c), os); | |||
static_cast<wchar_t>(static_cast<unsigned char>(c)), os); | } | |||
#ifdef __cpp_char8_t | ||||
static CharFormat PrintAsStringLiteralTo(char8_t c, ostream* os) { | ||||
return PrintAsStringLiteralTo(ToChar32(c), os); | ||||
} | ||||
#endif | ||||
static CharFormat PrintAsStringLiteralTo(char16_t c, ostream* os) { | ||||
return PrintAsStringLiteralTo(ToChar32(c), os); | ||||
} | } | |||
// Prints a wide or narrow character c and its code. '\0' is printed | static CharFormat PrintAsStringLiteralTo(wchar_t c, ostream* os) { | |||
// as "'\\0'", other unprintable characters are also properly escaped | return PrintAsStringLiteralTo(ToChar32(c), os); | |||
// using the standard C++ escape sequence. The template argument | } | |||
// UnsignedChar is the unsigned version of Char, which is the type of c. | ||||
template <typename UnsignedChar, typename Char> | // Prints a character c (of type char, char8_t, char16_t, char32_t, or wchar_t) | |||
// and its code. '\0' is printed as "'\\0'", other unprintable characters are | ||||
// also properly escaped using the standard C++ escape sequence. | ||||
template <typename Char> | ||||
void PrintCharAndCodeTo(Char c, ostream* os) { | void PrintCharAndCodeTo(Char c, ostream* os) { | |||
// First, print c as a literal in the most readable form we can find. | // First, print c as a literal in the most readable form we can find. | |||
*os << ((sizeof(c) > 1) ? "L'" : "'"); | *os << GetCharWidthPrefix(c) << "'"; | |||
const CharFormat format = PrintAsCharLiteralTo<UnsignedChar>(c, os); | const CharFormat format = PrintAsCharLiteralTo(c, os); | |||
*os << "'"; | *os << "'"; | |||
// To aid user debugging, we also print c's code in decimal, unless | // To aid user debugging, we also print c's code in decimal, unless | |||
// it's 0 (in which case c was printed as '\\0', making the code | // it's 0 (in which case c was printed as '\\0', making the code | |||
// obvious). | // obvious). | |||
if (c == 0) | if (c == 0) | |||
return; | return; | |||
*os << " (" << static_cast<int>(c); | *os << " (" << static_cast<int>(c); | |||
// For more convenience, we print c's code again in hexadecimal, | // For more convenience, we print c's code again in hexadecimal, | |||
// unless c was already printed in the form '\x##' or the code is in | // unless c was already printed in the form '\x##' or the code is in | |||
// [1, 9]. | // [1, 9]. | |||
if (format == kHexEscape || (1 <= c && c <= 9)) { | if (format == kHexEscape || (1 <= c && c <= 9)) { | |||
// Do nothing. | // Do nothing. | |||
} else { | } else { | |||
*os << ", 0x" << String::FormatHexInt(static_cast<int>(c)); | *os << ", 0x" << String::FormatHexInt(static_cast<int>(c)); | |||
} | } | |||
*os << ")"; | *os << ")"; | |||
} | } | |||
void PrintTo(unsigned char c, ::std::ostream* os) { | void PrintTo(unsigned char c, ::std::ostream* os) { PrintCharAndCodeTo(c, os); } | |||
PrintCharAndCodeTo<unsigned char>(c, os); | void PrintTo(signed char c, ::std::ostream* os) { PrintCharAndCodeTo(c, os); } | |||
} | ||||
void PrintTo(signed char c, ::std::ostream* os) { | ||||
PrintCharAndCodeTo<unsigned char>(c, os); | ||||
} | ||||
// Prints a wchar_t as a symbol if it is printable or as its internal | // Prints a wchar_t as a symbol if it is printable or as its internal | |||
// code otherwise and also as its code. L'\0' is printed as "L'\\0'". | // code otherwise and also as its code. L'\0' is printed as "L'\\0'". | |||
void PrintTo(wchar_t wc, ostream* os) { | void PrintTo(wchar_t wc, ostream* os) { PrintCharAndCodeTo(wc, os); } | |||
PrintCharAndCodeTo<wchar_t>(wc, os); | ||||
// TODO(dcheng): Consider making this delegate to PrintCharAndCodeTo() as well. | ||||
void PrintTo(char32_t c, ::std::ostream* os) { | ||||
*os << std::hex << "U+" << std::uppercase << std::setfill('0') << std::setw(4) | ||||
<< static_cast<uint32_t>(c); | ||||
} | } | |||
// Prints the given array of characters to the ostream. CharType must be either | // Prints the given array of characters to the ostream. CharType must be either | |||
// char or wchar_t. | // char, char8_t, char16_t, char32_t, or wchar_t. | |||
// The array starts at begin, the length is len, it may include '\0' characters | // The array starts at begin, the length is len, it may include '\0' characters | |||
// and may not be NUL-terminated. | // and may not be NUL-terminated. | |||
template <typename CharType> | template <typename CharType> | |||
GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ | GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ | |||
GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ | GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ | |||
GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ | GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ | |||
GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ | GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ | |||
static CharFormat PrintCharsAsStringTo( | static CharFormat PrintCharsAsStringTo( | |||
const CharType* begin, size_t len, ostream* os) { | const CharType* begin, size_t len, ostream* os) { | |||
const char* const kQuoteBegin = sizeof(CharType) == 1 ? "\"" : "L\""; | const char* const quote_prefix = GetCharWidthPrefix(*begin); | |||
*os << kQuoteBegin; | *os << quote_prefix << "\""; | |||
bool is_previous_hex = false; | bool is_previous_hex = false; | |||
CharFormat print_format = kAsIs; | CharFormat print_format = kAsIs; | |||
for (size_t index = 0; index < len; ++index) { | for (size_t index = 0; index < len; ++index) { | |||
const CharType cur = begin[index]; | const CharType cur = begin[index]; | |||
if (is_previous_hex && IsXDigit(cur)) { | if (is_previous_hex && IsXDigit(cur)) { | |||
// Previous character is of '\x..' form and this character can be | // Previous character is of '\x..' form and this character can be | |||
// interpreted as another hexadecimal digit in its number. Break string to | // interpreted as another hexadecimal digit in its number. Break string to | |||
// disambiguate. | // disambiguate. | |||
*os << "\" " << kQuoteBegin; | *os << "\" " << quote_prefix << "\""; | |||
} | } | |||
is_previous_hex = PrintAsStringLiteralTo(cur, os) == kHexEscape; | is_previous_hex = PrintAsStringLiteralTo(cur, os) == kHexEscape; | |||
// Remember if any characters required hex escaping. | // Remember if any characters required hex escaping. | |||
if (is_previous_hex) { | if (is_previous_hex) { | |||
print_format = kHexEscape; | print_format = kHexEscape; | |||
} | } | |||
} | } | |||
*os << "\""; | *os << "\""; | |||
return print_format; | return print_format; | |||
} | } | |||
skipping to change at line 324 | skipping to change at line 373 | |||
// that the array is not NUL-terminated. | // that the array is not NUL-terminated. | |||
PrintCharsAsStringTo(begin, len, os); | PrintCharsAsStringTo(begin, len, os); | |||
*os << " (no terminating NUL)"; | *os << " (no terminating NUL)"; | |||
} | } | |||
// Prints a (const) char array of 'len' elements, starting at address 'begin'. | // Prints a (const) char array of 'len' elements, starting at address 'begin'. | |||
void UniversalPrintArray(const char* begin, size_t len, ostream* os) { | void UniversalPrintArray(const char* begin, size_t len, ostream* os) { | |||
UniversalPrintCharArray(begin, len, os); | UniversalPrintCharArray(begin, len, os); | |||
} | } | |||
#ifdef __cpp_char8_t | ||||
// Prints a (const) char8_t array of 'len' elements, starting at address | ||||
// 'begin'. | ||||
void UniversalPrintArray(const char8_t* begin, size_t len, ostream* os) { | ||||
UniversalPrintCharArray(begin, len, os); | ||||
} | ||||
#endif | ||||
// Prints a (const) char16_t array of 'len' elements, starting at address | ||||
// 'begin'. | ||||
void UniversalPrintArray(const char16_t* begin, size_t len, ostream* os) { | ||||
UniversalPrintCharArray(begin, len, os); | ||||
} | ||||
// Prints a (const) char32_t array of 'len' elements, starting at address | ||||
// 'begin'. | ||||
void UniversalPrintArray(const char32_t* begin, size_t len, ostream* os) { | ||||
UniversalPrintCharArray(begin, len, os); | ||||
} | ||||
// Prints a (const) wchar_t array of 'len' elements, starting at address | // Prints a (const) wchar_t array of 'len' elements, starting at address | |||
// 'begin'. | // 'begin'. | |||
void UniversalPrintArray(const wchar_t* begin, size_t len, ostream* os) { | void UniversalPrintArray(const wchar_t* begin, size_t len, ostream* os) { | |||
UniversalPrintCharArray(begin, len, os); | UniversalPrintCharArray(begin, len, os); | |||
} | } | |||
// Prints the given C string to the ostream. | namespace { | |||
void PrintTo(const char* s, ostream* os) { | ||||
// Prints a null-terminated C-style string to the ostream. | ||||
template <typename Char> | ||||
void PrintCStringTo(const Char* s, ostream* os) { | ||||
if (s == nullptr) { | if (s == nullptr) { | |||
*os << "NULL"; | *os << "NULL"; | |||
} else { | } else { | |||
*os << ImplicitCast_<const void*>(s) << " pointing to "; | *os << ImplicitCast_<const void*>(s) << " pointing to "; | |||
PrintCharsAsStringTo(s, strlen(s), os); | PrintCharsAsStringTo(s, std::char_traits<Char>::length(s), os); | |||
} | } | |||
} | } | |||
} // anonymous namespace | ||||
void PrintTo(const char* s, ostream* os) { PrintCStringTo(s, os); } | ||||
#ifdef __cpp_char8_t | ||||
void PrintTo(const char8_t* s, ostream* os) { PrintCStringTo(s, os); } | ||||
#endif | ||||
void PrintTo(const char16_t* s, ostream* os) { PrintCStringTo(s, os); } | ||||
void PrintTo(const char32_t* s, ostream* os) { PrintCStringTo(s, os); } | ||||
// MSVC compiler can be configured to define whar_t as a typedef | // MSVC compiler can be configured to define whar_t as a typedef | |||
// of unsigned short. Defining an overload for const wchar_t* in that case | // of unsigned short. Defining an overload for const wchar_t* in that case | |||
// would cause pointers to unsigned shorts be printed as wide strings, | // would cause pointers to unsigned shorts be printed as wide strings, | |||
// possibly accessing more memory than intended and causing invalid | // possibly accessing more memory than intended and causing invalid | |||
// memory accesses. MSVC defines _NATIVE_WCHAR_T_DEFINED symbol when | // memory accesses. MSVC defines _NATIVE_WCHAR_T_DEFINED symbol when | |||
// wchar_t is implemented as a native type. | // wchar_t is implemented as a native type. | |||
#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) | #if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) | |||
// Prints the given wide C string to the ostream. | // Prints the given wide C string to the ostream. | |||
void PrintTo(const wchar_t* s, ostream* os) { | void PrintTo(const wchar_t* s, ostream* os) { PrintCStringTo(s, os); } | |||
if (s == nullptr) { | ||||
*os << "NULL"; | ||||
} else { | ||||
*os << ImplicitCast_<const void*>(s) << " pointing to "; | ||||
PrintCharsAsStringTo(s, wcslen(s), os); | ||||
} | ||||
} | ||||
#endif // wchar_t is native | #endif // wchar_t is native | |||
namespace { | namespace { | |||
bool ContainsUnprintableControlCodes(const char* str, size_t length) { | bool ContainsUnprintableControlCodes(const char* str, size_t length) { | |||
const unsigned char *s = reinterpret_cast<const unsigned char *>(str); | const unsigned char *s = reinterpret_cast<const unsigned char *>(str); | |||
for (size_t i = 0; i < length; i++) { | for (size_t i = 0; i < length; i++) { | |||
unsigned char ch = *s++; | unsigned char ch = *s++; | |||
if (std::iscntrl(ch)) { | if (std::iscntrl(ch)) { | |||
skipping to change at line 433 | skipping to change at line 510 | |||
} // anonymous namespace | } // anonymous namespace | |||
void PrintStringTo(const ::std::string& s, ostream* os) { | void PrintStringTo(const ::std::string& s, ostream* os) { | |||
if (PrintCharsAsStringTo(s.data(), s.size(), os) == kHexEscape) { | if (PrintCharsAsStringTo(s.data(), s.size(), os) == kHexEscape) { | |||
if (GTEST_FLAG(print_utf8)) { | if (GTEST_FLAG(print_utf8)) { | |||
ConditionalPrintAsText(s.data(), s.size(), os); | ConditionalPrintAsText(s.data(), s.size(), os); | |||
} | } | |||
} | } | |||
} | } | |||
#ifdef __cpp_char8_t | ||||
void PrintU8StringTo(const ::std::u8string& s, ostream* os) { | ||||
PrintCharsAsStringTo(s.data(), s.size(), os); | ||||
} | ||||
#endif | ||||
void PrintU16StringTo(const ::std::u16string& s, ostream* os) { | ||||
PrintCharsAsStringTo(s.data(), s.size(), os); | ||||
} | ||||
void PrintU32StringTo(const ::std::u32string& s, ostream* os) { | ||||
PrintCharsAsStringTo(s.data(), s.size(), os); | ||||
} | ||||
#if GTEST_HAS_STD_WSTRING | #if GTEST_HAS_STD_WSTRING | |||
void PrintWideStringTo(const ::std::wstring& s, ostream* os) { | void PrintWideStringTo(const ::std::wstring& s, ostream* os) { | |||
PrintCharsAsStringTo(s.data(), s.size(), os); | PrintCharsAsStringTo(s.data(), s.size(), os); | |||
} | } | |||
#endif // GTEST_HAS_STD_WSTRING | #endif // GTEST_HAS_STD_WSTRING | |||
} // namespace internal | } // namespace internal | |||
} // namespace testing | } // namespace testing | |||
End of changes. 30 change blocks. | ||||
53 lines changed or deleted | 144 lines changed or added |