aboutsummaryrefslogtreecommitdiff
path: root/src/libutil/fmt.hh
diff options
context:
space:
mode:
authoreldritch horrors <pennae@lix.systems>2024-03-08 08:10:05 +0100
committereldritch horrors <pennae@lix.systems>2024-03-09 07:00:13 -0700
commitb221a14f0a477db06f8ab705bd08404e431ec135 (patch)
tree33cab3bfa39ca17a070e1afca13e531052021eee /src/libutil/fmt.hh
parent3d9c7fc1e72e3471ae35c96ec418e8ac0913b099 (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.hh160
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);
}