#pragma once ///@file #include #include #include #include // Darwin stdenv does not define _GNU_SOURCE but does have _Unwind_Backtrace. #ifdef __APPLE__ #define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED #endif #include #include "ansicolor.hh" namespace nix { /** * Values wrapped in this struct are printed in magenta. * * By default, arguments to `HintFmt` are printed in magenta. To avoid this, * either wrap the argument in `Uncolored` or add a specialization of * `HintFmt::operator%`. */ template struct Magenta { Magenta(const T & s) : value(s) {} const T & value; }; template std::ostream & operator<<(std::ostream & out, const Magenta & y) { return out << ANSI_MAGENTA << y.value << ANSI_NORMAL; } /** * Values wrapped in this class are printed without coloring. * * By default, arguments to `HintFmt` are printed in magenta (see `Magenta`). */ template struct Uncolored { Uncolored(const T & s) : value(s) {} const T & value; }; template std::ostream & operator<<(std::ostream & out, const Uncolored & y) { return out << ANSI_NORMAL << y.value; } namespace fmt_internal { /** * Set the correct exceptions for `fmt`. */ inline void setExceptions(boost::format & fmt) { fmt.exceptions( boost::io::all_error_bits ^ boost::io::too_many_args_bit ^ boost::io::too_few_args_bit ); } /** * Helper class for `HintFmt` that supports the evil `operator%`. * * See: https://git.lix.systems/lix-project/lix/issues/178 */ struct HintFmt { boost::format fmt; template HintFmt(boost::format && fmt, const Args &... args) : fmt(std::move(fmt)) { setExceptions(fmt); (*this % ... % args); } template HintFmt & operator%(const T & value) { fmt % Magenta(value); return *this; } template HintFmt & operator%(const Uncolored & value) { fmt % value.value; return *this; } boost::format into_format() { return std::move(fmt); } }; } /** * A helper for writing a `boost::format` expression to a string. * * These are (roughly) equivalent: * * ``` * fmt(formatString, a_0, ..., a_n) * (boost::format(formatString) % a_0 % ... % a_n).str() * ``` * * However, when called with a single argument, the string is returned * unchanged. * * If you write code like this: * * ``` * std::cout << boost::format(stringFromUserInput) << std::endl; * ``` * * And `stringFromUserInput` contains formatting placeholders like `%s`, then * the code will crash at runtime. `fmt` helps you avoid this pitfall. */ inline std::string fmt(const std::string & s) { return s; } inline std::string fmt(const char * s) { return s; } template inline std::string fmt(const std::string & fs, const Args &... args) { boost::format f(fs); fmt_internal::setExceptions(f); (f % ... % args); return f.str(); } /** * A wrapper around `boost::format` which colors interpolated arguments in * magenta by default. */ class HintFmt { private: boost::format fmt; public: /** * Format the given string literally, without interpolating format * placeholders. */ HintFmt(const std::string & literal) : HintFmt("%s", Uncolored(literal)) {} /** * Interpolate the given arguments into the format string. */ template HintFmt(const std::string & format, const Args &... args) // Note the function try block. try : fmt(fmt_internal::HintFmt(boost::format(format), args...).into_format()) { if (this->fmt.remaining_args() != 0) { // Abort. I don't want anything to catch this, I want a coredump. std::cerr << "HintFmt received incorrect number of format args. Original format string: '"; std::cerr << format << "'; number of arguments: " << sizeof...(args) << "\n"; // And regardless of the coredump give me a damn stacktrace. std::cerr << boost::stacktrace::stacktrace() << std::endl; abort(); } } catch (boost::io::format_error & ex) { // Same thing, but for anything that happens in the member initializers. std::cerr << "HintFmt received incorrect format string. Original format string: '"; std::cerr << format << "'; number of arguments: " << sizeof...(args) << "\n"; std::cerr << boost::stacktrace::stacktrace() << std::endl; abort(); } HintFmt(const HintFmt & hf) : fmt(hf.fmt) {} std::string str() const { return fmt.str(); } }; std::ostream & operator<<(std::ostream & os, const HintFmt & hf); }