diff --git a/example.d b/example.d index 192fbbd..0c790b0 100755 --- a/example.d +++ b/example.d @@ -4,6 +4,7 @@ name "example" dependency "log" path="." +/ +import std.compiler : version_minor; import util.log; string details() @@ -30,8 +31,14 @@ void main() log.warn("mostly harmless"d); log.info("the answer is %s", 42); log.info!"the answer is %s"(42); + static if (version_minor >= 108) + { + // mixin so that it passes the lexer + mixin(`log.info(i"the answer is $(42)");`); + } log.trace(details); + version (Posix) { Log syslog = Log(syslogLogger); diff --git a/src/util/log.d b/src/util/log.d index 9bd0808..ba5d56f 100644 --- a/src/util/log.d +++ b/src/util/log.d @@ -7,15 +7,23 @@ module util.log; import std.algorithm; import std.array; +import std.compiler : version_minor; import std.conv; import std.datetime; -import std.format; +import std.format : formattedWrite; import std.range; import std.stdio; import std.string; import std.traits; import std.typecons; +private enum supportsStringInterpolation = version_minor >= 108; + +static if (supportsStringInterpolation) +{ + import core.interpolation; +} + /// Defines the importance of a log message. enum LogLevel { @@ -165,6 +173,25 @@ struct Log } } } + + static if (supportsStringInterpolation) + { + void append(Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__, A...) + (InterpolationHeader _1, lazy A args, InterpolationFooter _2) + { + + static if (!level.disabled) + { + if (level & levels) + { + A evaluatedArgs = args; + + _append(level, file, line, + (scope Sink sink) { sink.formattedWrite(_1, evaluatedArgs, _2); }); + } + } + } + } } private void _append(LogLevel level, string file, size_t line, @@ -622,3 +649,37 @@ unittest (-90).minutes._toISOString(writer); assert(writer.data == "-01:30"); } + +static if (supportsStringInterpolation) +{ + // Placeholder pending https://issues.dlang.org/show_bug.cgi?id=24550 + private void formattedWrite(Sink, Args...)(ref Sink sink, InterpolationHeader _1, Args args, InterpolationFooter _2) + { + import std.format : formattedWrite; + import std.meta : aliasSeqOf, Filter, staticMap; + + // Translate interpolation string to classic format string + enum string formatString = [staticMap!(toFormatStringFragment, Args)].join; + enum bool isFormatValueArgument(size_t i) = toFormatStringFragment!(Args[i]) == ""; + enum size_t[] valueArgIndexes = [Filter!(isFormatValueArgument, aliasSeqOf!(Args.length.iota))]; + enum string valueArgs = valueArgIndexes.map!(a => format!"args[%s]"(a)).join; + + mixin("sink.formattedWrite!formatString(" ~ valueArgs ~ ");"); + } + + private template toFormatStringFragment(alias A) + { + static if (is(A : InterpolatedLiteral!str, string str)) + { + enum toFormatStringFragment = str; + } + else static if (is(A : InterpolatedExpression!str, string str)) + { + enum toFormatStringFragment = "%s"; + } + else + { + enum toFormatStringFragment = ""; + } + } +}