Path: blob/main/contrib/kyua/utils/format/formatter.cpp
48180 views
// Copyright 2010 The Kyua Authors.1// All rights reserved.2//3// Redistribution and use in source and binary forms, with or without4// modification, are permitted provided that the following conditions are5// met:6//7// * Redistributions of source code must retain the above copyright8// notice, this list of conditions and the following disclaimer.9// * Redistributions in binary form must reproduce the above copyright10// notice, this list of conditions and the following disclaimer in the11// documentation and/or other materials provided with the distribution.12// * Neither the name of Google Inc. nor the names of its contributors13// may be used to endorse or promote products derived from this software14// without specific prior written permission.15//16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.2728#include "utils/format/formatter.hpp"2930#include <memory>31#include <string>32#include <utility>3334#include "utils/format/exceptions.hpp"35#include "utils/sanity.hpp"36#include "utils/text/exceptions.hpp"37#include "utils/text/operations.ipp"3839namespace format = utils::format;40namespace text = utils::text;414243namespace {444546/// Finds the next placeholder in a string.47///48/// \param format The original format string provided by the user; needed for49/// error reporting purposes only.50/// \param expansion The string containing the placeholder to look for. Any51/// '%%' in the string will be skipped, and they must be stripped later by52/// strip_double_percent().53/// \param begin The position from which to start looking for the next54/// placeholder.55///56/// \return The position in the string in which the placeholder is located and57/// the placeholder itself. If there are no placeholders left, this returns58/// the length of the string and an empty string.59///60/// \throw bad_format_error If the input string contains a trailing formatting61/// character. We cannot detect any other kind of invalid formatter because62/// we do not implement a full parser for them.63static std::pair< std::string::size_type, std::string >64find_next_placeholder(const std::string& format,65const std::string& expansion,66std::string::size_type begin)67{68begin = expansion.find('%', begin);69while (begin != std::string::npos && expansion[begin + 1] == '%')70begin = expansion.find('%', begin + 2);71if (begin == std::string::npos)72return std::make_pair(expansion.length(), "");73if (begin == expansion.length() - 1)74throw format::bad_format_error(format, "Trailing %");7576std::string::size_type end = begin + 1;77while (end < expansion.length() && expansion[end] != 's')78end++;79const std::string placeholder = expansion.substr(begin, end - begin + 1);80if (end == expansion.length() ||81placeholder.find('%', 1) != std::string::npos)82throw format::bad_format_error(format, "Unterminated placeholder '" +83placeholder + "'");84return std::make_pair(begin, placeholder);85}868788/// Converts a string to an integer.89///90/// \param format The format string; for error reporting purposes only.91/// \param str The string to conver.92/// \param what The name of the field this integer belongs to; for error93/// reporting purposes only.94///95/// \return An integer representing the input string.96inline int97to_int(const std::string& format, const std::string& str, const char* what)98{99try {100return text::to_type< int >(str);101} catch (const text::value_error& e) {102throw format::bad_format_error(format, "Invalid " + std::string(what) +103"specifier");104}105}106107108/// Constructs an std::ostringstream based on a formatting placeholder.109///110/// \param format The format placeholder; may be empty.111///112/// \return A new std::ostringstream that is prepared to format a single113/// object in the manner specified by the format placeholder.114///115/// \throw bad_format_error If the format string is bad. We do minimal116/// validation on this string though.117static std::ostringstream*118new_ostringstream(const std::string& format)119{120std::unique_ptr< std::ostringstream > output(new std::ostringstream());121122if (format.length() <= 2) {123// If the format is empty, we create a new stream so that we don't have124// to check for NULLs later on. We rarely should hit this condition125// (and when we do it's a bug in the caller), so this is not a big deal.126//127// Otherwise, if the format is a regular '%s', then we don't have to do128// any processing for additional formatters. So this is just a "fast129// path".130} else {131std::string partial = format.substr(1, format.length() - 2);132if (partial[0] == '0') {133output->fill('0');134partial.erase(0, 1);135}136if (!partial.empty()) {137const std::string::size_type dot = partial.find('.');138if (dot != 0)139output->width(to_int(format, partial.substr(0, dot), "width"));140if (dot != std::string::npos) {141output->setf(std::ios::fixed, std::ios::floatfield);142output->precision(to_int(format, partial.substr(dot + 1),143"precision"));144}145}146}147148return output.release();149}150151152/// Replaces '%%' by '%' in a given string range.153///154/// \param in The input string to be rewritten.155/// \param begin The position at which to start the replacement.156/// \param end The position at which to end the replacement.157///158/// \return The modified string and the amount of characters removed.159static std::pair< std::string, int >160strip_double_percent(const std::string& in, const std::string::size_type begin,161std::string::size_type end)162{163std::string part = in.substr(begin, end - begin);164165int removed = 0;166std::string::size_type pos = part.find("%%");167while (pos != std::string::npos) {168part.erase(pos, 1);169++removed;170pos = part.find("%%", pos + 1);171}172173return std::make_pair(in.substr(0, begin) + part + in.substr(end), removed);174}175176177} // anonymous namespace178179180/// Performs internal initialization of the formatter.181///182/// This is separate from the constructor just because it is shared by different183/// overloaded constructors.184void185format::formatter::init(void)186{187const std::pair< std::string::size_type, std::string > placeholder =188find_next_placeholder(_format, _expansion, _last_pos);189const std::pair< std::string, int > no_percents =190strip_double_percent(_expansion, _last_pos, placeholder.first);191192_oss = new_ostringstream(placeholder.second);193194_expansion = no_percents.first;195_placeholder_pos = placeholder.first - no_percents.second;196_placeholder = placeholder.second;197}198199200/// Constructs a new formatter object (internal).201///202/// \param format The format string.203/// \param expansion The format string with any replacements performed so far.204/// \param last_pos The position from which to start looking for formatting205/// placeholders. This must be maintained in case one of the replacements206/// introduced a new placeholder, which must be ignored. Think, for207/// example, replacing a "%s" string with "foo %s".208format::formatter::formatter(const std::string& format,209const std::string& expansion,210const std::string::size_type last_pos) :211_format(format),212_expansion(expansion),213_last_pos(last_pos),214_oss(NULL)215{216init();217}218219220/// Constructs a new formatter object.221///222/// \param format The format string. The formatters in the string are not223/// validated during construction, but will cause errors when used later if224/// they are invalid.225format::formatter::formatter(const std::string& format) :226_format(format),227_expansion(format),228_last_pos(0),229_oss(NULL)230{231init();232}233234235format::formatter::~formatter(void)236{237delete _oss;238}239240241/// Returns the formatted string.242///243/// \return A string representation of the formatted string.244const std::string&245format::formatter::str(void) const246{247return _expansion;248}249250251/// Automatic conversion of formatter objects to strings.252///253/// This is provided to allow painless injection of formatter objects into254/// streams, without having to manually call the str() method.255format::formatter::operator const std::string&(void) const256{257return _expansion;258}259260261/// Specialization of operator% for booleans.262///263/// \param value The boolean to inject into the format string.264///265/// \return A new formatter that has one less format placeholder.266format::formatter267format::formatter::operator%(const bool& value) const268{269(*_oss) << (value ? "true" : "false");270return replace(_oss->str());271}272273274/// Replaces the first formatting placeholder with a value.275///276/// \param arg The replacement string.277///278/// \return A new formatter in which the first formatting placeholder has been279/// replaced by arg and is ready to replace the next item.280///281/// \throw utils::format::extra_args_error If there are no more formatting282/// placeholders in the input string, or if the placeholder is invalid.283format::formatter284format::formatter::replace(const std::string& arg) const285{286if (_placeholder_pos == _expansion.length())287throw format::extra_args_error(_format, arg);288289const std::string expansion = _expansion.substr(0, _placeholder_pos)290+ arg + _expansion.substr(_placeholder_pos + _placeholder.length());291return formatter(_format, expansion, _placeholder_pos + arg.length());292}293294295