blob: 28a2348d4dcd53825fd65282bedfe5e4ed78eb89 [file]
// SPDX-License-Identifier: Apache-2.0
// Copyright Apache Software Foundation 2019
/** @file
Formatted output for BufferWriter.
*/
#include <array>
#include <cctype>
#include <chrono>
#include <cmath>
#include <ctime>
#include <cmath>
#include <sys/param.h>
#include <unistd.h>
#include "swoc/BufferWriter.h"
#include "swoc/bwf_base.h"
#include "swoc/bwf_ex.h"
#include "swoc/swoc_meta.h"
#include "swoc/DiscreteRange.h"
using namespace std::literals;
using namespace swoc::literals;
using swoc::svto_radix;
namespace swoc { inline namespace SWOC_VERSION_NS {
namespace bwf {
ExternalNames& Global_Names() {
static swoc::bwf::ExternalNames Global_Names;
return Global_Names;
}
const Spec Spec::DEFAULT;
#pragma GCC diagnostic ignored "-Wchar-subscripts"
Spec::Property::Property() {
memset(_data, 0, sizeof(_data));
_data['b'] = TYPE_CHAR | NUMERIC_TYPE_CHAR;
_data['B'] = TYPE_CHAR | NUMERIC_TYPE_CHAR | UPPER_TYPE_CHAR;
_data['d'] = TYPE_CHAR | NUMERIC_TYPE_CHAR;
_data['g'] = TYPE_CHAR;
_data['o'] = TYPE_CHAR | NUMERIC_TYPE_CHAR;
_data['p'] = TYPE_CHAR;
_data['P'] = TYPE_CHAR | UPPER_TYPE_CHAR;
_data['s'] = TYPE_CHAR;
_data['S'] = TYPE_CHAR | UPPER_TYPE_CHAR;
_data['x'] = TYPE_CHAR | NUMERIC_TYPE_CHAR;
_data['X'] = TYPE_CHAR | NUMERIC_TYPE_CHAR | UPPER_TYPE_CHAR;
_data[SIGN_NEVER] = SIGN_CHAR;
_data[SIGN_NEG] = SIGN_CHAR;
_data[SIGN_ALWAYS] = SIGN_CHAR;
_data['<'] = static_cast<uint8_t>(Spec::Align::LEFT);
_data['>'] = static_cast<uint8_t>(Spec::Align::RIGHT);
_data['^'] = static_cast<uint8_t>(Spec::Align::CENTER);
_data['='] = static_cast<uint8_t>(Spec::Align::SIGN);
}
Spec::Spec(const TextView &fmt) {
this->parse(fmt);
}
/// Parse a format specification.
bool
Spec::parse(TextView fmt) {
TextView num; // temporary for number parsing.
_name = fmt.take_prefix_at(':');
// if it's parsable as a number, treat it as an index.
num = _name;
auto n = svto_radix<10>(num);
if (num.empty()) {
_idx = static_cast<decltype(_idx)>(n);
}
if (fmt.size()) {
TextView sz = fmt.take_prefix_at(':'); // the format specifier.
_ext = fmt; // anything past the second ':' is the extension.
if (sz.size()) {
// fill and alignment
if ('%' == *sz) { // enable URI encoding of the fill character so
// metasyntactic chars can be used if needed.
if (sz.size() < 4) {
throw std::invalid_argument("Fill URI encoding without 2 hex characters and align mark");
}
if (Align::NONE == (_align = align_of(sz[3]))) {
throw std::invalid_argument("Fill URI without alignment mark");
}
char d1 = sz[1], d0 = sz[2];
if (!isxdigit(d0) || !isxdigit(d1)) {
throw std::invalid_argument("URI encoding with non-hex characters");
}
_fill = isdigit(d0) ? d0 - '0' : tolower(d0) - 'a' + 10;
_fill += (isdigit(d1) ? d1 - '0' : tolower(d1) - 'a' + 10) << 4;
sz += 4;
} else if (sz.size() > 1 && Align::NONE != (_align = align_of(sz[1]))) {
_fill = *sz;
sz += 2;
} else if (Align::NONE != (_align = align_of(*sz))) {
++sz;
}
if (!sz.size()) {
return true;
}
// sign
if (is_sign(*sz)) {
_sign = *sz;
if (!(++sz).size()) {
return true;
}
}
// radix prefix
if ('#' == *sz) {
_radix_lead_p = true;
if (!(++sz).size()) {
return true;
}
}
// 0 fill for integers
if ('0' == *sz) {
if (Align::NONE == _align) {
_align = Align::SIGN;
}
_fill = '0';
++sz;
}
num = sz;
n = svto_radix<10>(num);
if (num.size() < sz.size()) {
_min = static_cast<decltype(_min)>(n);
sz = num;
if (!sz.size()) {
return true;
}
}
// precision
if ('.' == *sz) {
num = ++sz;
n = svto_radix<10>(num);
if (num.size() < sz.size()) {
_prec = static_cast<decltype(_prec)>(n);
sz = num;
if (!sz.size()) {
return true;
}
} else {
throw std::invalid_argument("Precision mark without precision");
}
}
// style (type). Hex, octal, etc.
if (is_type(*sz)) {
_type = *sz;
if (!(++sz).size()) {
return true;
}
}
// maximum width
if (',' == *sz) {
num = ++sz;
n = svto_radix<10>(num);
if (num.size() < sz.size()) {
_max = static_cast<decltype(_max)>(n);
sz = num;
if (!sz.size()) {
return true;
}
} else {
throw std::invalid_argument("Maximum width mark without width");
}
// Can only have a type indicator here if there was a max width.
if (is_type(*sz)) {
_type = *sz;
if (!(++sz).size()) {
return true;
}
}
}
}
}
return true;
}
/// Parse out the next literal and/or format specifier from the format string.
/// Pass the results back in @a literal and @a specifier as appropriate.
/// Update @a fmt to strip the parsed text.
/// @return @c true if a specifier was parsed, @c false if not.
bool
Format::TextViewExtractor::parse(TextView &fmt, std::string_view &literal, std::string_view &specifier) {
TextView::size_type off = 0;
// Check for brace delimiters.
off = fmt.find_if([](char c) { return '{' == c || '}' == c; });
if (off == TextView::npos) {
// not found, it's a literal, ship it.
literal = fmt;
fmt.remove_prefix(literal.size());
return false;
}
// Processing for braces that don't enclose specifiers.
if (fmt.size() > off + 1) {
char c1 = fmt[off];
char c2 = fmt[off + 1];
if (c1 == c2) {
// double braces count as literals, but must tweak to output only 1 brace.
literal = fmt.take_prefix(off + 1);
return false;
} else if ('}' == c1) {
throw std::invalid_argument("Unopened } in format string.");
} else {
literal = std::string_view{fmt.data(), off};
fmt.remove_prefix(off + 1);
}
} else {
throw std::invalid_argument("Invalid trailing character in format string.");
}
if (fmt.size()) {
// Need to be careful, because an empty format is OK and it's hard to tell
// if take_prefix_at failed to find the delimiter or found it as the first
// byte.
off = fmt.find('}');
if (off == TextView::npos) {
throw std::invalid_argument("BWFormat: Unclosed { in format string");
}
specifier = fmt.take_prefix(off);
return true;
}
return false;
}
bool
Format::TextViewExtractor::operator()(std::string_view &literal_v, Spec &spec) {
if (!_fmt.empty()) {
std::string_view spec_v;
if (parse(_fmt, literal_v, spec_v)) {
return spec.parse(spec_v);
}
}
return false;
}
bool
Format::FormatExtractor::operator()(std::string_view &literal_v, swoc::bwf::Spec &spec) {
literal_v = {};
if (_idx < int(_fmt.size()) && _fmt[_idx]._type == Spec::LITERAL_TYPE) {
literal_v = _fmt[_idx++]._ext;
}
if (_idx < int(_fmt.size()) && _fmt[_idx]._type != Spec::LITERAL_TYPE) {
spec = _fmt[_idx++];
return true;
}
return false;
}
void
Err_Bad_Arg_Index(BufferWriter &w, int i, size_t n) {
static const Format fmt{"{{BAD_ARG_INDEX:{} of {}}}"sv};
w.print(fmt, i, n);
}
/** This performs generic alignment operations.
If a formatter specialization performs this operation instead, that should
result in output that is at least @a spec._min characters wide, which will
cause this function to make no further adjustments.
*/
void
Adjust_Alignment(BufferWriter &aux, Spec const &spec) {
size_t extent = aux.extent();
size_t min = spec._min;
if (extent < min) {
size_t delta = min - extent;
size_t left_delta = 0, right_delta = delta; // left justify values
if (Spec::Align::RIGHT == spec._align) {
left_delta = delta;
right_delta = 0;
} else if (Spec::Align::CENTER == spec._align) {
left_delta = delta / 2;
right_delta = (delta + 1) / 2;
}
if (left_delta > 0) {
size_t work_area = extent + left_delta;
aux.commit(left_delta); // cover work area.
aux.copy(left_delta, 0, extent); // move to create space for left fill.
aux.discard(work_area); // roll back to write the left fill.
for (int i = left_delta; i > 0; --i) {
aux.write(spec._fill);
}
aux.commit(extent);
}
for (int i = right_delta; i > 0; --i) {
aux.write(spec._fill);
}
} else {
size_t max = spec._max;
if (max < extent) {
aux.discard(extent - max);
}
}
}
// Conversions from remainder to character, in upper and lower case versions.
// Really only useful for hexadecimal currently.
namespace {
char UPPER_DIGITS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char LOWER_DIGITS[] = "0123456789abcdefghijklmnopqrstuvwxyz";
static const std::array<uint64_t, 11> POWERS_OF_TEN = {
{1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000}
};
} // namespace
/// Templated radix based conversions. Only a small number of radix are
/// supported and providing a template minimizes cut and paste code while also
/// enabling compiler optimizations (e.g. for power of 2 radix the modulo /
/// divide become bit operations).
template <size_t RADIX>
size_t
To_Radix(uintmax_t n, char *buff, size_t width, char *digits) {
static_assert(1 < RADIX && RADIX <= 36, "RADIX must be in the range 2..36");
char *out = buff + width;
if (n) {
while (n) {
*--out = digits[n % RADIX];
n /= RADIX;
}
} else {
*--out = '0';
}
return (buff + width) - out;
}
/** Output a string with a specified alignment.
*
* @tparam F Output functor.
* @param w Output buffer.
* @param f Functor that generates output.
* @param align Alignment.
* @param width Field width.
* @param fill Fill character.
* @param neg Character to use for negative (null char means do not output).
*/
template <typename F>
void
Write_Aligned(BufferWriter &w, F const &f, Spec::Align align, int width, char fill, char neg) {
switch (align) {
case Spec::Align::LEFT:
if (neg) {
w.write(neg);
}
f();
while (width-- > 0) {
w.write(fill);
}
break;
case Spec::Align::RIGHT:
while (width-- > 0) {
w.write(fill);
}
if (neg) {
w.write(neg);
}
f();
break;
case Spec::Align::CENTER:
for (int i = width / 2; i > 0; --i) {
w.write(fill);
}
if (neg) {
w.write(neg);
}
f();
for (int i = (width + 1) / 2; i > 0; --i) {
w.write(fill);
}
break;
case Spec::Align::SIGN:
if (neg) {
w.write(neg);
}
while (width-- > 0) {
w.write(fill);
}
f();
break;
default:
if (neg) {
w.write(neg);
}
f();
break;
}
}
BufferWriter &
Format_Integer(BufferWriter &w, Spec const &spec, uintmax_t i, bool neg_p) {
size_t n = 0;
int width = static_cast<int>(spec._min); // amount left to fill.
char neg = 0;
char prefix1 = spec._radix_lead_p ? '0' : 0;
char prefix2 = 0;
char buff[std::numeric_limits<uintmax_t>::digits + 1];
if (spec._sign != Spec::SIGN_NEVER) {
if (neg_p) {
neg = '-';
} else if (spec._sign == Spec::SIGN_ALWAYS) {
neg = spec._sign;
}
}
switch (spec._type) {
case 'x':
prefix2 = 'x';
n = bwf::To_Radix<16>(i, buff, sizeof(buff), bwf::LOWER_DIGITS);
break;
case 'X':
prefix2 = 'X';
n = bwf::To_Radix<16>(i, buff, sizeof(buff), bwf::UPPER_DIGITS);
break;
case 'b':
prefix2 = 'b';
n = bwf::To_Radix<2>(i, buff, sizeof(buff), bwf::LOWER_DIGITS);
break;
case 'B':
prefix2 = 'B';
n = bwf::To_Radix<2>(i, buff, sizeof(buff), bwf::UPPER_DIGITS);
break;
case 'o':
n = bwf::To_Radix<8>(i, buff, sizeof(buff), bwf::LOWER_DIGITS);
break;
default:
prefix1 = 0;
n = bwf::To_Radix<10>(i, buff, sizeof(buff), bwf::LOWER_DIGITS);
break;
}
// Clip fill width by stuff that's already committed to be written.
if (neg) {
--width;
}
if (prefix1) {
--width;
if (prefix2) {
--width;
}
}
width -= static_cast<int>(n);
std::string_view digits{buff + sizeof(buff) - n, n};
if (spec._align == Spec::Align::SIGN) { // custom for signed case because
// prefix and digits are seperated.
if (neg) {
w.write(neg);
}
if (prefix1) {
w.write(prefix1);
if (prefix2) {
w.write(prefix2);
}
}
while (width-- > 0) {
w.write(spec._fill);
}
w.write(digits);
} else { // use generic Write_Aligned
Write_Aligned(
w,
[&]() {
if (prefix1) {
w.write(prefix1);
if (prefix2) {
w.write(prefix2);
}
}
w.write(digits);
},
spec._align, width, spec._fill, neg);
}
return w;
}
/// Format for floating point values. Seperates floating point into a whole
/// number and a fraction. The fraction is converted into an unsigned integer
/// based on the specified precision, spec._prec. ie. 3.1415 with precision two
/// is seperated into two unsigned integers 3 and 14. The different pieces are
/// assembled and placed into the BufferWriter. The default is two decimal
/// places. ie. X.XX. The value is always written in base 10.
///
/// format: whole.fraction
/// or: left.right
BufferWriter &
Format_Float(BufferWriter &w, Spec const &spec, double f, bool negative_p) {
static const std::string_view infinity_bwf{"Inf"};
static const std::string_view nan_bwf{"NaN"};
static const std::string_view zero_bwf{"0"};
static const std::string_view subnormal_bwf{"subnormal"};
static const std::string_view unknown_bwf{"unknown float"};
// Handle floating values that are not normal
if (!std::isnormal(f)) {
std::string_view unnormal;
switch (std::fpclassify(f)) {
case FP_INFINITE:
unnormal = infinity_bwf;
break;
case FP_NAN:
unnormal = nan_bwf;
break;
case FP_ZERO:
unnormal = zero_bwf;
break;
case FP_SUBNORMAL:
unnormal = subnormal_bwf;
break;
default:
unnormal = unknown_bwf;
}
w.write(unnormal);
return w;
}
auto whole_part = static_cast<uint64_t>(f);
if (whole_part == f || spec._prec == 0) { // integral
return Format_Integer(w, spec, whole_part, negative_p);
}
static constexpr char dec = '.';
double frac = NAN;
size_t l = 0;
size_t r = 0;
char whole[std::numeric_limits<double>::digits10 + 1];
char fraction[std::numeric_limits<double>::digits10 + 1];
char neg = 0;
int width = static_cast<int>(spec._min); // amount left to fill.
unsigned int precision = (spec._prec == Spec::DEFAULT._prec) ? 2 : spec._prec; // default precision 2
frac = f - whole_part; // split the number
if (negative_p) {
neg = '-';
} else if (spec._sign != '-') {
neg = spec._sign;
}
// Shift the floating point based on the precision. Used to convert
// trailing fraction into an integer value.
uint64_t shift = 0;
if (precision < POWERS_OF_TEN.size()) {
shift = POWERS_OF_TEN[precision];
} else { // not precomputed.
shift = POWERS_OF_TEN.back();
for (precision -= (POWERS_OF_TEN.size() - 1); precision > 0; --precision) {
shift *= 10;
}
}
auto frac_part = static_cast<uint64_t>(frac * shift + 0.5 /* rounding */);
l = bwf::To_Radix<10>(whole_part, whole, sizeof(whole), bwf::LOWER_DIGITS);
r = bwf::To_Radix<10>(frac_part, fraction, sizeof(fraction), bwf::LOWER_DIGITS);
// Clip fill width
if (neg) {
--width;
}
width -= static_cast<int>(l);
--width; // '.'
width -= static_cast<int>(r);
std::string_view whole_digits{whole + sizeof(whole) - l, l};
std::string_view frac_digits{fraction + sizeof(fraction) - r, r};
Write_Aligned(
w,
[&]() {
w.write(whole_digits);
w.write(dec);
w.write(frac_digits);
},
spec._align, width, spec._fill, neg);
return w;
}
void
Format_As_Hex(BufferWriter &w, std::string_view view, const char *digits) {
const char *ptr = view.data();
for (auto n = view.size(); n > 0; --n) {
char c = *ptr++;
w.write(digits[(c >> 4) & 0xF]);
w.write(digits[c & 0xf]);
}
}
/// Preparse format string for later use.
Format::Format(TextView fmt) {
Spec lit_spec;
int arg_idx = 0;
auto ex{bind(fmt)};
std::string_view literal_v;
lit_spec._type = Spec::LITERAL_TYPE;
while (ex) {
Spec spec;
bool spec_p = ex(literal_v, spec);
if (literal_v.size()) {
lit_spec._ext = literal_v;
_items.emplace_back(lit_spec);
}
if (spec_p) {
if (spec._name.size() == 0) { // no name provided, use implicit index.
spec._idx = arg_idx++;
}
if (spec._idx >= 0) {
++arg_idx;
}
_items.emplace_back(spec);
}
}
}
bool
Format::is_literal() const {
for (auto const &spec : _items) {
if (Spec::LITERAL_TYPE != spec._type) {
return false;
}
}
return true;
}
NameBinding::~NameBinding() = default;
} // namespace bwf
BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, std::string_view sv) {
auto width = int(spec._min); // amount to fill.
if (spec._prec > 0) {
sv = sv.substr(0, spec._prec);
}
if ('x' == spec._type || 'X' == spec._type) {
return bwformat(w, spec, bwf::HexDump(sv.data(), sv.size()));
} else if ('s' == spec._type) {
bwformat(w, spec, transform_view_of(&tolower, sv));
} else if ('S' == spec._type) {
bwformat(w, spec, transform_view_of(&toupper, sv));
} else {
width -= sv.size();
bwf::Write_Aligned(
w, [&w, &sv]() { w.write(sv); }, spec._align, width, spec._fill, 0);
}
return w;
}
// Generic pointer formatting
BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, const void *ptr) {
using namespace swoc::literals;
bwf::Spec ptr_spec{spec};
ptr_spec._radix_lead_p = true;
if (ptr == nullptr) {
if (spec._type == 's' || spec._type == 'S') {
ptr_spec._type = bwf::Spec::DEFAULT_TYPE;
ptr_spec._ext = ""_sv; // clear any extension.
return bwformat(w, spec, spec._type == 's' ? "null"_sv : "NULL"_sv);
} else if (spec._type == bwf::Spec::DEFAULT_TYPE) {
return w; // print nothing if there is no format character override.
}
}
if (ptr_spec._type == bwf::Spec::DEFAULT_TYPE || ptr_spec._type == 'p') {
ptr_spec._type = 'x'; // if default or 'p;, switch to lower hex.
} else if (ptr_spec._type == 'P') {
ptr_spec._type = 'X'; // P means upper hex, overriding other specializations.
}
return bwf::Format_Integer(w, ptr_spec, reinterpret_cast<intptr_t>(ptr), false);
}
// doc end
BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::HexDump const &hex) {
char fmt_type = spec._type;
const char *digits = bwf::UPPER_DIGITS;
if ('X' != fmt_type) {
fmt_type = 'x';
digits = bwf::LOWER_DIGITS;
}
int width = int(spec._min) - hex._view.size() * 2; // amount left to fill.
if (spec._radix_lead_p) {
w.write('0');
w.write(fmt_type);
width -= 2;
}
bwf::Write_Aligned(
w, [&w, &hex, digits]() { bwf::Format_As_Hex(w, hex._view, digits); }, spec._align, width, spec._fill, 0);
return w;
}
BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, MemSpan<void const> const &span) {
if ('x' == spec._type || 'X' == spec._type) {
const char *digits = ('X' == spec._type) ? bwf::UPPER_DIGITS : bwf::LOWER_DIGITS;
size_t block = spec._prec > 0 ? spec._prec : span.size();
TextView view{span.rebind<char const>()};
bool space_p = false;
while (view) {
if (space_p) {
w.write(' ');
}
space_p = true;
if (spec._radix_lead_p) {
w.write('0').write(digits[33]);
}
bwf::Format_As_Hex(w, view.prefix(block), digits);
view.remove_prefix(block);
}
} else {
static const bwf::Format default_fmt{"{:#x}@{:p}"};
w.print(default_fmt, span.size(), span.data());
}
return w;
}
std::ostream &
FixedBufferWriter::operator>>(std::ostream &s) const {
return s << this->view();
}
namespace {
// errno value -> symbolic name, indexed directly by the errno number (O(1)).
// The numbering differs between Linux and the BSD-derived platforms, and even
// macOS and FreeBSD disagree on a few of their own high-numbered codes, so each
// platform carries a table generated from its real <errno.h>. Gaps in the
// numbering are empty and fall through to "Unknown"; a platform with no table
// is a compile error. The long form (the 'l' spec, below) still uses strerror().
// clang-format off
#if defined(__linux__)
static constexpr std::array<std::string_view, 134> ERRNO_NAMES = {{
/* 0 */ "SUCCESS", "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO", "E2BIG",
/* 8 */ "ENOEXEC", "EBADF", "ECHILD", "EAGAIN", "ENOMEM", "EACCES", "EFAULT", "ENOTBLK",
/* 16 */ "EBUSY", "EEXIST", "EXDEV", "ENODEV", "ENOTDIR", "EISDIR", "EINVAL", "ENFILE",
/* 24 */ "EMFILE", "ENOTTY", "ETXTBSY", "EFBIG", "ENOSPC", "ESPIPE", "EROFS", "EMLINK",
/* 32 */ "EPIPE", "EDOM", "ERANGE", "EDEADLK", "ENAMETOOLONG", "ENOLCK", "ENOSYS", "ENOTEMPTY",
/* 40 */ "ELOOP", "", "ENOMSG", "EIDRM", "ECHRNG", "EL2NSYNC", "EL3HLT", "EL3RST",
/* 48 */ "ELNRNG", "EUNATCH", "ENOCSI", "EL2HLT", "EBADE", "EBADR", "EXFULL", "ENOANO",
/* 56 */ "EBADRQC", "EBADSLT", "", "EBFONT", "ENOSTR", "ENODATA", "ETIME", "ENOSR",
/* 64 */ "ENONET", "ENOPKG", "EREMOTE", "ENOLINK", "EADV", "ESRMNT", "ECOMM", "EPROTO",
/* 72 */ "EMULTIHOP", "EDOTDOT", "EBADMSG", "EOVERFLOW", "ENOTUNIQ", "EBADFD", "EREMCHG", "ELIBACC",
/* 80 */ "ELIBBAD", "ELIBSCN", "ELIBMAX", "ELIBEXEC", "EILSEQ", "ERESTART", "ESTRPIPE", "EUSERS",
/* 88 */ "ENOTSOCK", "EDESTADDRREQ", "EMSGSIZE", "EPROTOTYPE", "ENOPROTOOPT", "EPROTONOSUPPORT", "ESOCKTNOSUPPORT", "EOPNOTSUPP",
/* 96 */ "EPFNOSUPPORT", "EAFNOSUPPORT", "EADDRINUSE", "EADDRNOTAVAIL", "ENETDOWN", "ENETUNREACH", "ENETRESET", "ECONNABORTED",
/* 104 */ "ECONNRESET", "ENOBUFS", "EISCONN", "ENOTCONN", "ESHUTDOWN", "ETOOMANYREFS", "ETIMEDOUT", "ECONNREFUSED",
/* 112 */ "EHOSTDOWN", "EHOSTUNREACH", "EALREADY", "EINPROGRESS", "ESTALE", "EUCLEAN", "ENOTNAM", "ENAVAIL",
/* 120 */ "EISNAM", "EREMOTEIO", "EDQUOT", "ENOMEDIUM", "EMEDIUMTYPE", "ECANCELED", "ENOKEY", "EKEYEXPIRED",
/* 128 */ "EKEYREVOKED", "EKEYREJECTED", "EOWNERDEAD", "ENOTRECOVERABLE", "ERFKILL", "EHWPOISON",
}};
#elif defined(__APPLE__)
static constexpr std::array<std::string_view, 108> ERRNO_NAMES = {{
/* 0 */ "SUCCESS", "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO", "E2BIG",
/* 8 */ "ENOEXEC", "EBADF", "ECHILD", "EDEADLK", "ENOMEM", "EACCES", "EFAULT", "ENOTBLK",
/* 16 */ "EBUSY", "EEXIST", "EXDEV", "ENODEV", "ENOTDIR", "EISDIR", "EINVAL", "ENFILE",
/* 24 */ "EMFILE", "ENOTTY", "ETXTBSY", "EFBIG", "ENOSPC", "ESPIPE", "EROFS", "EMLINK",
/* 32 */ "EPIPE", "EDOM", "ERANGE", "EAGAIN", "EINPROGRESS", "EALREADY", "ENOTSOCK", "EDESTADDRREQ",
/* 40 */ "EMSGSIZE", "EPROTOTYPE", "ENOPROTOOPT", "EPROTONOSUPPORT", "ESOCKTNOSUPPORT", "ENOTSUP", "EPFNOSUPPORT", "EAFNOSUPPORT",
/* 48 */ "EADDRINUSE", "EADDRNOTAVAIL", "ENETDOWN", "ENETUNREACH", "ENETRESET", "ECONNABORTED", "ECONNRESET", "ENOBUFS",
/* 56 */ "EISCONN", "ENOTCONN", "ESHUTDOWN", "ETOOMANYREFS", "ETIMEDOUT", "ECONNREFUSED", "ELOOP", "ENAMETOOLONG",
/* 64 */ "EHOSTDOWN", "EHOSTUNREACH", "ENOTEMPTY", "EPROCLIM", "EUSERS", "EDQUOT", "ESTALE", "EREMOTE",
/* 72 */ "EBADRPC", "ERPCMISMATCH", "EPROGUNAVAIL", "EPROGMISMATCH", "EPROCUNAVAIL", "ENOLCK", "ENOSYS", "EFTYPE",
/* 80 */ "EAUTH", "ENEEDAUTH", "EPWROFF", "EDEVERR", "EOVERFLOW", "EBADEXEC", "EBADARCH", "ESHLIBVERS",
/* 88 */ "EBADMACHO", "ECANCELED", "EIDRM", "ENOMSG", "EILSEQ", "ENOATTR", "EBADMSG", "EMULTIHOP",
/* 96 */ "ENODATA", "ENOLINK", "ENOSR", "ENOSTR", "EPROTO", "ETIME", "EOPNOTSUPP", "ENOPOLICY",
/* 104 */ "ENOTRECOVERABLE", "EOWNERDEAD", "EQFULL", "ENOTCAPABLE",
}};
#elif defined(__FreeBSD__)
static constexpr std::array<std::string_view, 98> ERRNO_NAMES = {{
/* 0 */ "SUCCESS", "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO", "E2BIG",
/* 8 */ "ENOEXEC", "EBADF", "ECHILD", "EDEADLK", "ENOMEM", "EACCES", "EFAULT", "ENOTBLK",
/* 16 */ "EBUSY", "EEXIST", "EXDEV", "ENODEV", "ENOTDIR", "EISDIR", "EINVAL", "ENFILE",
/* 24 */ "EMFILE", "ENOTTY", "ETXTBSY", "EFBIG", "ENOSPC", "ESPIPE", "EROFS", "EMLINK",
/* 32 */ "EPIPE", "EDOM", "ERANGE", "EAGAIN", "EINPROGRESS", "EALREADY", "ENOTSOCK", "EDESTADDRREQ",
/* 40 */ "EMSGSIZE", "EPROTOTYPE", "ENOPROTOOPT", "EPROTONOSUPPORT", "ESOCKTNOSUPPORT", "EOPNOTSUPP", "EPFNOSUPPORT", "EAFNOSUPPORT",
/* 48 */ "EADDRINUSE", "EADDRNOTAVAIL", "ENETDOWN", "ENETUNREACH", "ENETRESET", "ECONNABORTED", "ECONNRESET", "ENOBUFS",
/* 56 */ "EISCONN", "ENOTCONN", "ESHUTDOWN", "ETOOMANYREFS", "ETIMEDOUT", "ECONNREFUSED", "ELOOP", "ENAMETOOLONG",
/* 64 */ "EHOSTDOWN", "EHOSTUNREACH", "ENOTEMPTY", "EPROCLIM", "EUSERS", "EDQUOT", "ESTALE", "EREMOTE",
/* 72 */ "EBADRPC", "ERPCMISMATCH", "EPROGUNAVAIL", "EPROGMISMATCH", "EPROCUNAVAIL", "ENOLCK", "ENOSYS", "EFTYPE",
/* 80 */ "EAUTH", "ENEEDAUTH", "EIDRM", "ENOMSG", "EOVERFLOW", "ECANCELED", "EILSEQ", "ENOATTR",
/* 88 */ "EDOOFUS", "EBADMSG", "EMULTIHOP", "ENOLINK", "EPROTO", "ENOTCAPABLE", "ECAPMODE", "ENOTRECOVERABLE",
/* 96 */ "EOWNERDEAD", "EINTEGRITY",
}};
#else
#error "errno name table not defined for this platform"
#endif
// clang-format on
std::string_view
errno_short_name(int e)
{
if (e >= 0 && e < static_cast<int>(ERRNO_NAMES.size()) && !ERRNO_NAMES[e].empty()) {
return ERRNO_NAMES[e];
}
return "Unknown"sv;
}
} // namespace
BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Errno const &e) {
static const bwf::Format number_fmt{"[{}]"sv}; // numeric value format.
if (spec.has_numeric_type()) { // if numeric type, print just the numeric part
w.print(number_fmt, e._e);
} else {
TextView ext{spec._ext};
bool short_p = false;
if (ext.empty() || ext.npos != ext.find('s')) {
w.write(errno_short_name(e._e));
short_p = true;
}
if (ext.empty() || ext.npos != ext.find('l')) {
if (short_p) {
w.write(": ");
}
w.write(TextView(strerror(e._e), TextView::npos));
}
if (spec._type != 's' && spec._type != 'S') {
w.write(' ');
w.print(number_fmt, e._e);
}
}
return w;
}
bwf::Date::Date(std::string_view fmt) : _epoch(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())), _fmt(fmt) {}
BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Date const &date) {
if (spec.has_numeric_type()) {
bwformat(w, spec, date._epoch);
} else {
struct tm t {};
auto r = w.remaining();
size_t n{0};
// Verify @a fmt is null terminated, even outside the bounds of the view.
if (date._fmt.data()[date._fmt.size() - 1] != 0 && date._fmt.data()[date._fmt.size()] != 0) {
throw(std::invalid_argument{"BWF Date String is not null terminated."});
}
// Get the time, GMT or local if specified.
if (spec._ext == "local"sv) {
localtime_r(&date._epoch, &t);
} else {
gmtime_r(&date._epoch, &t);
}
// Try a direct write, faster if it works.
if (r > 0) {
n = strftime(w.aux_data(), r, date._fmt.data(), &t);
}
if (n > 0) {
w.commit(n);
} else {
// Direct write didn't work. Unfortunately need to write to a temporary
// buffer or the sizing isn't correct if @a w is clipped because @c
// strftime returns 0 if the buffer isn't large enough.
char buff[256]; // hope for the best - no real way to resize appropriately on failure.
n = strftime(buff, sizeof(buff), date._fmt.data(), &t);
w.write(buff, n);
}
}
return w;
}
BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::Pattern const &pattern) {
if (!pattern._text.empty()) { // If there's no text, no point in looping.
auto limit = std::min<size_t>(spec._max, pattern._text.size() * pattern._n);
decltype(limit) n = 0;
while (n < limit) {
w.write(pattern._text);
n += pattern._text.size();
}
}
return w;
}
BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, std::error_code const &ec) {
static const auto G_CAT = &std::generic_category();
static const auto S_CAT = &std::system_category();
// This provides convenient safe access to the errno short name array.
static const swoc::bwf::Format number_fmt{"[{}]"_sv}; // numeric value format.
if (spec.has_numeric_type()) { // if numeric type, print just the numeric part.
bwformat(w, spec, ec.value());
} else {
auto short_name = (&ec.category() == G_CAT || &ec.category() == S_CAT) ? errno_short_name(ec.value()) : "Unknown"sv;
if (short_name != "Unknown"sv) {
bwformat(w, spec, short_name);
} else {
w.write(ec.message());
}
if (spec._type != 's' && spec._type != 'S') {
w.write(' ').write('[').format(spec, ec.value()).write(']');
}
}
return w;
}
BufferWriter &
bwformat(BufferWriter &w, bwf::Spec const &spec, bwf::UnHex const &obj) {
auto span{obj._span};
size_t limit = spec._max;
while (span.size() >= 2 && limit--) {
auto b = svto_radix<16>(span.clip_prefix(2).rebind<char const>());
w.write(b);
}
return w;
}
}} // namespace swoc::SWOC_VERSION_NS
namespace std {
ostream &
operator<<(ostream &s, swoc::FixedBufferWriter &w) {
return s << w.view();
}
} // namespace std