diff options
author | eldritch horrors <pennae@lix.systems> | 2024-03-08 08:10:05 +0100 |
---|---|---|
committer | eldritch horrors <pennae@lix.systems> | 2024-03-09 07:00:13 -0700 |
commit | b221a14f0a477db06f8ab705bd08404e431ec135 (patch) | |
tree | 33cab3bfa39ca17a070e1afca13e531052021eee /src/libutil/fmt.hh | |
parent | 3d9c7fc1e72e3471ae35c96ec418e8ac0913b099 (diff) |
Merge pull request #9925 from 9999years/fmt-cleanup
Cleanup `fmt.hh`
(cherry picked from commit 47a1dbb4b8e7913cbb9b4d604728b912e76e4ca0)
Change-Id: Id076a45cb39652f437fe3f8bda10c310a9894777
Diffstat (limited to 'src/libutil/fmt.hh')
-rw-r--r-- | src/libutil/fmt.hh | 160 |
1 files changed, 100 insertions, 60 deletions
diff --git a/src/libutil/fmt.hh b/src/libutil/fmt.hh index f552a6b77..75354a07c 100644 --- a/src/libutil/fmt.hh +++ b/src/libutil/fmt.hh @@ -8,37 +8,64 @@ namespace nix { - -/** - * Inherit some names from other namespaces for convenience. - */ -using boost::format; - - -/** - * A variadic template that does nothing. Useful to call a function - * for all variadic arguments but ignoring the result. - */ -struct nop { template<typename... T> nop(T...) {} }; - - +namespace { /** - * A helper for formatting strings. ‘fmt(format, a_0, ..., a_n)’ is - * equivalent to ‘boost::format(format) % a_0 % ... % - * ... a_n’. However, ‘fmt(s)’ is equivalent to ‘s’ (so no %-expansion - * takes place). + * A helper for writing `boost::format` expressions. + * + * These are equivalent: + * + * ``` + * formatHelper(formatter, a_0, ..., a_n) + * formatter % a_0 % ... % a_n + * ``` + * + * With a single argument, `formatHelper(s)` is a no-op. */ template<class F> inline void formatHelper(F & f) -{ -} +{ } template<class F, typename T, typename... Args> inline void formatHelper(F & f, const T & x, const Args & ... args) { + // Interpolate one argument and then recurse. formatHelper(f % x, args...); } +/** + * Set the correct exceptions for `fmt`. + */ +void setExceptions(boost::format & fmt) +{ + fmt.exceptions( + boost::io::all_error_bits ^ + boost::io::too_many_args_bit ^ + boost::io::too_few_args_bit); +} +} + +/** + * 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; @@ -53,66 +80,96 @@ template<typename... Args> inline std::string fmt(const std::string & fs, const Args & ... args) { boost::format f(fs); - f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit); + setExceptions(f); formatHelper(f, args...); return f.str(); } -// format function for hints in errors. same as fmt, except templated values -// are always in magenta. +/** + * 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 <class T> -struct magentatxt +struct Magenta { - magentatxt(const T &s) : value(s) {} + Magenta(const T &s) : value(s) {} const T & value; }; template <class T> -std::ostream & operator<<(std::ostream & out, const magentatxt<T> & y) +std::ostream & operator<<(std::ostream & out, const Magenta<T> & y) { return out << ANSI_WARNING << 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 <class T> -struct normaltxt +struct Uncolored { - normaltxt(const T & s) : value(s) {} + Uncolored(const T & s) : value(s) {} const T & value; }; template <class T> -std::ostream & operator<<(std::ostream & out, const normaltxt<T> & y) +std::ostream & operator<<(std::ostream & out, const Uncolored<T> & y) { return out << ANSI_NORMAL << y.value; } -class hintformat +/** + * A wrapper around `boost::format` which colors interpolated arguments in + * magenta by default. + */ +class HintFmt { +private: + boost::format fmt; + public: - hintformat(const std::string & format) : fmt(format) - { - fmt.exceptions(boost::io::all_error_bits ^ - boost::io::too_many_args_bit ^ - boost::io::too_few_args_bit); - } + /** + * 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<typename... Args> + HintFmt(const std::string & format, const Args & ... args) + : HintFmt(boost::format(format), args...) + { } - hintformat(const hintformat & hf) + HintFmt(const HintFmt & hf) : fmt(hf.fmt) { } - hintformat(format && fmt) + template<typename... Args> + HintFmt(boost::format && fmt, const Args & ... args) : fmt(std::move(fmt)) - { } + { + setExceptions(fmt); + formatHelper(*this, args...); + } template<class T> - hintformat & operator%(const T & value) + HintFmt & operator%(const T & value) { - fmt % magentatxt(value); + fmt % Magenta(value); return *this; } template<class T> - hintformat & operator%(const normaltxt<T> & value) + HintFmt & operator%(const Uncolored<T> & value) { fmt % value.value; return *this; @@ -122,25 +179,8 @@ public: { return fmt.str(); } - -private: - format fmt; }; -std::ostream & operator<<(std::ostream & os, const hintformat & hf); - -template<typename... Args> -inline hintformat hintfmt(const std::string & fs, const Args & ... args) -{ - hintformat f(fs); - formatHelper(f, args...); - return f; -} - -inline hintformat hintfmt(const std::string & plain_string) -{ - // we won't be receiving any args in this case, so just print the original string - return hintfmt("%s", normaltxt(plain_string)); -} +std::ostream & operator<<(std::ostream & os, const HintFmt & hf); } |