www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.announce - Scriptlike v0.9.4 - Perl-like interpolated strings, full examples

reply Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
Big update to Scriptlike, v0.9.4:
https://github.com/Abscissa/scriptlike

Scriptlike is a library to help you write script-like programs in D.

The two highlights in this release are string interpolation and a full 
set of examples in the documentation. Also of note are the new functions 
removePath and tryRemovePath which can be used to delete files (like 
remove) *or* directories (like rmdirRecurse).

Full changelog:
http://semitwist.com/scriptlike/changelog.html

=====================
String Interpolation:
=====================
https://github.com/Abscissa/scriptlike#string-interpolation

AFAICT, a string mixin is necessary to accomplish this in D, but 
otherwise it works much like other languages:

--------------------------------------------
// Output: The number 21 doubled is 42!
int num = 21;
writeln(
     mixin(interp!"The number ${num} doubled is ${num * 2}!")
);
--------------------------------------------

The interpolated sections are handled via std.conv.text(), so they 
accept any type.

Bikeshedding requested! I'm not 100% sold on the name "interp" for this 
long-term. Suggestions welcome.

=========
Examples:
=========
https://github.com/Abscissa/scriptlike
https://github.com/Abscissa/scriptlike/blob/master/USAGE.md
https://github.com/Abscissa/scriptlike/tree/master/examples

The homepage/readme now provides sample code demonstrating all of 
Scriptlike's various features.

The second link above demonstrates suggested practices for how to use 
Scriptlike in a D-based script.

And finally, all examples are included as actual runnable programs (all 
automatically tested by "dub test", to ensure continued freshness).

======================
All changes in v0.9.4:
======================
- Fixed: Previous release broke the unittest script when dub test 
support was added.

- Fixed: In echo mode, several functions would echo the wrong "try*" or 
non-"try*" version. Ex: run echoed tryRun, and tryRename echoed rename.

- Fixed: Path and buildNormalizedPathFixed now convert back/forward 
slashes to native on BOTH Windows and Posix, not just on Windows.

- Fixed: Some links within changelog and API reference were pointing to 
the reference docs for Scriptlike's latest version, instead of staying 
within the same documentation version. This made archived docs for 
previous versions difficult to navigate.



- Enhancement: Add interp for interpolated strings:
     string s = mixin( interp!"Value is ${variableOrExpression}" )

- Enhancement: Add removePath/tryRemovePath for deleting a path 
regardless of whether it's a file or directory. (Calls remove for files 
and rmdirRecurse for directories.)

- Enhancement: Add a Path-accepting overload of escapeShellArg for the 
sake of generic code.

- Enhancement: When runCollect throws, the ErrorLevelException now 
includes and displays the command's output (otherwise there'd be no way 
to inspect the command's output for diagnostic purposes).

- Enhancement: Greatly extended and improved set of tests.
Sep 22 2015
next sibling parent reply Sebastiaan Koppe <mail skoppe.eu> writes:
On Tuesday, 22 September 2015 at 20:18:48 UTC, Nick Sabalausky 
wrote:
 --------------------------------------------
 // Output: The number 21 doubled is 42!
 int num = 21;
 writeln(
     mixin(interp!"The number ${num} doubled is ${num * 2}!")
 );
 --------------------------------------------
What about: void echo(T)() { writeln(mixin(interp!T)); } At least it saves some typing.
Sep 22 2015
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Wednesday, 23 September 2015 at 01:24:54 UTC, Sebastiaan Koppe 
wrote:
 What about:

 void echo(T)()
 {
   writeln(mixin(interp!T));
Won't work because it won't be able to see the local variables you want to interpolate.
Sep 22 2015
parent Sebastiaan Koppe <mail skoppe.eu> writes:
On Wednesday, 23 September 2015 at 01:45:03 UTC, Adam D. Ruppe 
wrote:
 On Wednesday, 23 September 2015 at 01:24:54 UTC, Sebastiaan 
 Koppe wrote:
 What about:

 void echo(T)()
 {
   writeln(mixin(interp!T));
Won't work because it won't be able to see the local variables you want to interpolate.
facepalm... ofcourse... Well, you could still get rid of the writeln.
Sep 22 2015
prev sibling next sibling parent reply =?UTF-8?Q?S=c3=b6nke_Ludwig?= <sludwig rejectedsoftware.com> writes:
Am 22.09.2015 um 22:18 schrieb Nick Sabalausky:
 =====================
 String Interpolation:
 =====================
 https://github.com/Abscissa/scriptlike#string-interpolation

 AFAICT, a string mixin is necessary to accomplish this in D, but
 otherwise it works much like other languages:

 --------------------------------------------
 // Output: The number 21 doubled is 42!
 int num = 21;
 writeln(
      mixin(interp!"The number ${num} doubled is ${num * 2}!")
 );
 --------------------------------------------

 The interpolated sections are handled via std.conv.text(), so they
 accept any type.

 Bikeshedding requested! I'm not 100% sold on the name "interp" for this
 long-term. Suggestions welcome.
An alternative idea would be to mix in a local "writeln" function, which can then be used multiple times without syntax overhead: mixin template interp() { void iwriteln(string str)() { // pretend that we actually parse the string ;) write("This is "); write(somevar); writeln("."); } } void main() { int somevar = 42; mixin interp; iwriteln!("This is ${somevar}."); }
Sep 22 2015
next sibling parent reply Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On 09/23/2015 01:44 AM, Sönke Ludwig wrote:
 An alternative idea would be to mix in a local "writeln" function, which
 can then be used multiple times without syntax overhead:

 mixin template interp()
 {
      void iwriteln(string str)()
      {
          // pretend that we actually parse the string ;)
          write("This is ");
          write(somevar);
          writeln(".");
      }
 }

 void main()
 {
      int somevar = 42;
      mixin interp;
      iwriteln!("This is ${somevar}.");
 }
Hmm, interesting idea. I'd leave it as a string-returning function, rather than automatically printing, but a writeln convenience wrapper is a nice idea too. The one problem I'm seeing with it though, is it wouldn't be able to see symbols declared between the "mixin interp;" and any later uses of it. Ie: void main() { int somevar = 42; mixin interp; iwriteln!("This is ${somevar}."); int another = 17; iwriteln!("This won't work, using ${another}."); } Seems like it would be too awkward and confusing to be worthwhile. :(
Sep 23 2015
next sibling parent reply Chad Joan <chadjoan gmail.com> writes:
On Wednesday, 23 September 2015 at 14:33:23 UTC, Nick Sabalausky 
wrote:
 On 09/23/2015 01:44 AM, Sönke Ludwig wrote:
 An alternative idea would be to mix in a local "writeln" 
 function, which
 can then be used multiple times without syntax overhead:

 mixin template interp()
 {
      void iwriteln(string str)()
      {
          // pretend that we actually parse the string ;)
          write("This is ");
          write(somevar);
          writeln(".");
      }
 }

 void main()
 {
      int somevar = 42;
      mixin interp;
      iwriteln!("This is ${somevar}.");
 }
Hmm, interesting idea. I'd leave it as a string-returning function, rather than automatically printing, but a writeln convenience wrapper is a nice idea too. The one problem I'm seeing with it though, is it wouldn't be able to see symbols declared between the "mixin interp;" and any later uses of it. Ie: void main() { int somevar = 42; mixin interp; iwriteln!("This is ${somevar}."); int another = 17; iwriteln!("This won't work, using ${another}."); } Seems like it would be too awkward and confusing to be worthwhile. :(
This is why I argued for alternative mixin syntax in D some ... years? ... ago. It'd be really cool to have a writefln overload that did this: int somevar = 42; Which would just be shorthand for int somevar = 42; mixin writefln!("This is ${somevar}"); mixin writefln!("Plus two and you get ${somevar+2}"); I feel like a bit of syntax sugar could go a long way ;)
Sep 23 2015
parent reply Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On 09/23/2015 03:18 PM, Chad Joan wrote:
 This is why I argued for alternative mixin syntax in D some ... years?
 ... ago.

 It'd be really cool to have a writefln overload that did this:

 int somevar = 42;




 Which would just be shorthand for

 int somevar = 42;
 mixin writefln!("This is ${somevar}");

 mixin writefln!("Plus two and you get ${somevar+2}");


 I feel like a bit of syntax sugar could go a long way ;)
Yea, the trouble with string mixins is that they're ugly enough people don't like to use them. I'd argued in the past for a way to tag a CTFE-able string-returning function as being intended for mixing-in, so you could omit the "mixin(...)" part. But we only ever got it for template mixins. Allowing it for string mixins was too controversial. :( I dunno, maybe even a string mixin sugar as simple as this would be a big help: mixin!func(args to func here) ie: mixin!interp("Some string here") But I'm guessing the ship's ling since sailed for anything like that.
Sep 23 2015
next sibling parent Chad Joan <chadjoan gmail.com> writes:
On Wednesday, 23 September 2015 at 19:28:03 UTC, Nick Sabalausky 
wrote:
 On 09/23/2015 03:18 PM, Chad Joan wrote:
 This is why I argued for alternative mixin syntax in D some 
 ... years?
 ... ago.

 It'd be really cool to have a writefln overload that did this:

 int somevar = 42;




 Which would just be shorthand for

 int somevar = 42;
 mixin writefln!("This is ${somevar}");

 mixin writefln!("Plus two and you get ${somevar+2}");


 I feel like a bit of syntax sugar could go a long way ;)
Yea, the trouble with string mixins is that they're ugly enough people don't like to use them. I'd argued in the past for a way to tag a CTFE-able string-returning function as being intended for mixing-in, so you could omit the "mixin(...)" part. But we only ever got it for template mixins. Allowing it for string mixins was too controversial. :( I dunno, maybe even a string mixin sugar as simple as this would be a big help: mixin!func(args to func here) ie: mixin!interp("Some string here") But I'm guessing the ship's ling since sailed for anything like that.
I hope not :( I remember when Walter originally designed mixins, he stated something to the effect that he wanted them to be easily greppable at all times. I would argue that they are so centrally important that they should be a symbol rather than a keyword. Still greppable. But also much more useful. Since D already has the mixin keyword, I suspect it would be more practical to just ask people to grep for 'mixin|<mixin symbol>' instead of just 'mixin'. There are similar (but orthogonal) concerns with delegate (anonymous function) nesting: // D notation. foo ( (x,y) { auto z = doSomething(x+y); return z*z; }); vs // Speculative notation. foo() : (x,y) { auto z = doSomething(x+y); return z*z; } Current D notation for nesting functions reminds me of C's notation for structs... // C notation. typedef struct WhatDoIPutHereFoo { int x,y; } Foo; // D notation. (Yay, consistency!) struct Foo { int x,y; } Extra semicolon and syntax noise and such. I'm still incredibly glad D even has delegates and mixins at this point ;)
Sep 23 2015
prev sibling parent reply Meta <jared771 gmail.com> writes:
On Wednesday, 23 September 2015 at 19:28:03 UTC, Nick Sabalausky 
wrote:
 On 09/23/2015 03:18 PM, Chad Joan wrote:
 This is why I argued for alternative mixin syntax in D some 
 ... years?
 ... ago.

 It'd be really cool to have a writefln overload that did this:

 int somevar = 42;




 Which would just be shorthand for

 int somevar = 42;
 mixin writefln!("This is ${somevar}");

 mixin writefln!("Plus two and you get ${somevar+2}");


 I feel like a bit of syntax sugar could go a long way ;)
Yea, the trouble with string mixins is that they're ugly enough people don't like to use them. I'd argued in the past for a way to tag a CTFE-able string-returning function as being intended for mixing-in, so you could omit the "mixin(...)" part. But we only ever got it for template mixins. Allowing it for string mixins was too controversial. :( I dunno, maybe even a string mixin sugar as simple as this would be a big help: mixin!func(args to func here) ie: mixin!interp("Some string here") But I'm guessing the ship's ling since sailed for anything like that.
What about even just removing the syntax distinction between string mixins and template mixins? mixin "int i = 0"; mixin declareI!(); While we're at it, how about optional parens for templates as well as functions?
Sep 23 2015
parent reply Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On 09/23/2015 08:42 PM, Meta wrote:
 What about even just removing the syntax distinction between string
 mixins and template mixins?

 mixin "int i = 0";
 mixin declareI!();
I like that idea. It it feasible? I'd always assumed the syntaxes were different because they needed to be for some sort of technical reason. But now that I look at it...maybe that could work after all?
Sep 25 2015
parent Meta <jared771 gmail.com> writes:
On Friday, 25 September 2015 at 14:38:33 UTC, Nick Sabalausky 
wrote:
 I like that idea. It it feasible? I'd always assumed the 
 syntaxes were different because they needed to be for some sort 
 of technical reason. But now that I look at it...maybe that 
 could work after all?
At first glance I can't see any problem with it, other than the fact that it makes it somewhat ambiguous whether you're mixing in a string returned from a CTFE function or a template mixin in some cases. mixin template mixable() { int i = 0; } string mixable()() { return "int i = 0;"; } However, this currently causes a compile time error anyway, so I don't believe it's a problem. //Error, two templates with the same name mixin mixable!();
Sep 25 2015
prev sibling parent =?UTF-8?Q?S=c3=b6nke_Ludwig?= <sludwig rejectedsoftware.com> writes:
Am 23.09.2015 um 16:33 schrieb Nick Sabalausky:
 (...)

 void main()
 {
      int somevar = 42;
      mixin interp;
      iwriteln!("This is ${somevar}.");

      int another = 17;
      iwriteln!("This won't work, using ${another}.");
 }

 Seems like it would be too awkward and confusing to be worthwhile. :(
True, it's still far from being good. There is another possible syntax variation: mixin template iwriteln(string str, int line = __LINE__) { auto __dummy_/*~line*/ = () { import std.stdio; // pretend to parse the input string write("Result: "); write(result); writeln(); return true; } (); } void main() { int result = 42; } Works only for top-level calls, but has less visual noise, so maybe it makes sense to include that as a convenience function.
Sep 25 2015
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 09/23/2015 01:44 AM, Sönke Ludwig wrote:
 Am 22.09.2015 um 22:18 schrieb Nick Sabalausky:
 =====================
 String Interpolation:
 =====================
 https://github.com/Abscissa/scriptlike#string-interpolation

 AFAICT, a string mixin is necessary to accomplish this in D, but
 otherwise it works much like other languages:

 --------------------------------------------
 // Output: The number 21 doubled is 42!
 int num = 21;
 writeln(
      mixin(interp!"The number ${num} doubled is ${num * 2}!")
 );
 --------------------------------------------

 The interpolated sections are handled via std.conv.text(), so they
 accept any type.

 Bikeshedding requested! I'm not 100% sold on the name "interp" for this
 long-term. Suggestions welcome.
An alternative idea would be to mix in a local "writeln" function, which can then be used multiple times without syntax overhead: mixin template interp() { void iwriteln(string str)() { // pretend that we actually parse the string ;) write("This is "); write(somevar); writeln("."); } } void main() { int somevar = 42; mixin interp; iwriteln!("This is ${somevar}."); }
Yah, I think we need something like that in the stdlib. Also, we need writefln with compile-time format string (someone was working on it but I haven't heard about it in a while). -- Andrei
Sep 24 2015
parent reply "H. S. Teoh via Digitalmars-d-announce" writes:
On Thu, Sep 24, 2015 at 08:00:53PM -0400, Andrei Alexandrescu via
Digitalmars-d-announce wrote:
[...]
 Yah, I think we need something like that in the stdlib. Also, we need
 writefln with compile-time format string (someone was working on it
 but I haven't heard about it in a while). -- Andrei
https://issues.dlang.org/show_bug.cgi?id=13568 I wanted to work on it, but haven't actually gotten to it yet. Basically, the idea is relatively simple: // compile-time variant void writefln(string format="", A...)(A args) if (format.length > 0) { ... // implementation here } // runtime variant void writefln(string format="", A...)(A args) if (format.length == 0 && args.length > 0 && is(typeof(args[0]) == string)) { ... // current implementation } This will allow backward compatibility with the current writefln API, while allowing existing code to simply transition from: writefln("...", ...); to: writefln!"..."(...); The tricky part is how to extricate the various parts of the current implementation in order to take full advantage of compile-time format strings, e.g., (1) don't import anything except what's necessary to format the current format string (the current format() has to import, e.g., std.bigint even if you never use BigInt, because it can't assume that a runtime format string might not ask to format BigInts; this causes the infamous format() template bloat) -- this includes allowing format() to be pure, nogc, etc. if your format strings never ask for anything that requires that; (2) compile-time argument mismatch checking (e.g., writefln!"%d"("abc") should give a compile-time error rather than a runtime exception). A lot of the current implementation will probably have to be overhauled / rewritten in order to make this work. That part unfortunately requires a lot of time, which I don't have right now. T -- Creativity is not an excuse for sloppiness.
Sep 24 2015
parent reply Jacob Carlborg <doob me.com> writes:
On 2015-09-25 02:15, H. S. Teoh via Digitalmars-d-announce wrote:

 I wanted to work on it, but haven't actually gotten to it yet.
 Basically, the idea is relatively simple:

 	// compile-time variant
 	void writefln(string format="", A...)(A args)
 		if (format.length > 0)
 	{
 		... // implementation here
 	}

 	// runtime variant
 	void writefln(string format="", A...)(A args)
 		if (format.length == 0 && args.length > 0 &&
 			is(typeof(args[0]) == string))
 	{
 		... // current implementation
 	}
Not sure why you need to complicate it with template constraints. Just overload the function? -- /Jacob Carlborg
Sep 25 2015
parent reply "H. S. Teoh via Digitalmars-d-announce" writes:
On Fri, Sep 25, 2015 at 02:14:30PM +0200, Jacob Carlborg via
Digitalmars-d-announce wrote:
 On 2015-09-25 02:15, H. S. Teoh via Digitalmars-d-announce wrote:
 
I wanted to work on it, but haven't actually gotten to it yet.
Basically, the idea is relatively simple:

	// compile-time variant
	void writefln(string format="", A...)(A args)
		if (format.length > 0)
	{
		... // implementation here
	}

	// runtime variant
	void writefln(string format="", A...)(A args)
		if (format.length == 0 && args.length > 0 &&
			is(typeof(args[0]) == string))
	{
		... // current implementation
	}
Not sure why you need to complicate it with template constraints. Just overload the function?
[...] It's to work around a dmd bug that doesn't allow overloads between templates and non-templates. But I just checked, looks like that bug may have been fixed since, so now the following overloads would work: void writefln(string format, A...)(A args) { ... } // current void writefln(A...)(string format, A args) { ... } // new T -- Don't drink and derive. Alcohol and algebra don't mix.
Sep 25 2015
parent Jacob Carlborg <doob me.com> writes:
On 2015-09-25 23:28, H. S. Teoh via Digitalmars-d-announce wrote:

 It's to work around a dmd bug that doesn't allow overloads between
 templates and non-templates.  But I just checked, looks like that bug
 may have been fixed since, so now the following overloads would work:

 	void writefln(string format, A...)(A args) { ... } // current
 	void writefln(A...)(string format, A args) { ... } // new
1. "writefln" is already a template [1]: void writefln(Char, A...)(in Char[] fmt, A args) 2. Both of your above examples are templates 3. The easiest way to workaround that bug is to make the non-template function a template function without arguments: void foo()(int a); void foo(T)(T a, T b); But if the bug is fixed, then that's great :) [1] https://github.com/D-Programming-Language/phobos/blob/master/std/stdio.d#L1362 -- /Jacob Carlborg
Sep 26 2015
prev sibling next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2015-09-22 22:18, Nick Sabalausky wrote:

 =====================
 String Interpolation:
 =====================
 https://github.com/Abscissa/scriptlike#string-interpolation

 AFAICT, a string mixin is necessary to accomplish this in D, but
 otherwise it works much like other languages:

 --------------------------------------------
 // Output: The number 21 doubled is 42!
 int num = 21;
 writeln(
      mixin(interp!"The number ${num} doubled is ${num * 2}!")
 );
 --------------------------------------------

 The interpolated sections are handled via std.conv.text(), so they
 accept any type.

 Bikeshedding requested! I'm not 100% sold on the name "interp" for this
 long-term. Suggestions welcome.
Different bikeshedding: I would prefer to make the curly braces optional if it only contains a symbol. -- /Jacob Carlborg
Sep 22 2015
parent Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On 09/23/2015 02:21 AM, Jacob Carlborg wrote:
 Different bikeshedding: I would prefer to make the curly braces optional
 if it only contains a symbol.
I agree. I've left that as a future enhancement for the right now. Although it shouldn't be too difficult a change. Filing it here: https://github.com/Abscissa/scriptlike/issues/23
Sep 23 2015
prev sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2015-09-22 22:18, Nick Sabalausky wrote:
 Big update to Scriptlike, v0.9.4:
 https://github.com/Abscissa/scriptlike

 Scriptlike is a library to help you write script-like programs in D.
One thing that really bugs me in Phobos, Scriptlike seems to have the same problem, is that there are three (!!!) different functions to remove something from the file system. Give me just one function that removes everything, regardless if it's a file, directory and if it's empty or not. -- /Jacob Carlborg
Sep 22 2015
next sibling parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 23-Sep-2015 09:30, Jacob Carlborg wrote:
 On 2015-09-22 22:18, Nick Sabalausky wrote:
 Big update to Scriptlike, v0.9.4:
 https://github.com/Abscissa/scriptlike

 Scriptlike is a library to help you write script-like programs in D.
One thing that really bugs me in Phobos, Scriptlike seems to have the same problem, is that there are three (!!!) different functions to remove something from the file system. Give me just one function that removes everything, regardless if it's a file, directory and if it's empty or not.
Bugzilla issue with this enhancement would help a lot with this ;) And a PR would make it happen... -- Dmitry Olshansky
Sep 22 2015
parent Jacob Carlborg <doob me.com> writes:
On 2015-09-23 08:32, Dmitry Olshansky wrote:

 Bugzilla issue with this enhancement would help a lot with this ;)

 And a PR would make it happen...
https://issues.dlang.org/show_bug.cgi?id=15102 I'm too lazy for a PR. -- /Jacob Carlborg
Sep 23 2015
prev sibling next sibling parent reply Ben Boeckel via Digitalmars-d-announce writes:
On Wed, Sep 23, 2015 at 08:30:18 +0200, Jacob Carlborg via
Digitalmars-d-announce wrote:
 One thing that really bugs me in Phobos, Scriptlike seems to have the 
 same problem, is that there are three (!!!) different functions to 
 remove something from the file system. Give me just one function that 
 removes everything, regardless if it's a file, directory and if it's 
 empty or not.
Be aware that you will have to pay an extra lstat call for such a function so that *it* can call the right function. It certainly shouldn't replace the existing functions. --Ben
Sep 23 2015
parent Jacob Carlborg <doob me.com> writes:
On 2015-09-23 15:59, Ben Boeckel via Digitalmars-d-announce wrote:

 Be aware that you will have to pay an extra lstat call for such a
 function so that *it* can call the right function. It certainly
 shouldn't replace the existing functions.
Perhaps not in Phobos, but I don't see why someone using Scriptlike would care. As I understand it, the whole point of Scriptlike is convenience. -- /Jacob Carlborg
Sep 23 2015
prev sibling parent Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On 09/23/2015 02:30 AM, Jacob Carlborg wrote:
 On 2015-09-22 22:18, Nick Sabalausky wrote:
 Big update to Scriptlike, v0.9.4:
 https://github.com/Abscissa/scriptlike

 Scriptlike is a library to help you write script-like programs in D.
One thing that really bugs me in Phobos, Scriptlike seems to have the same problem, is that there are three (!!!) different functions to remove something from the file system. Give me just one function that removes everything, regardless if it's a file, directory and if it's empty or not.
Me too. That's why this latest version adds removePath and tryRemovePath, which do exactly that ;) http://semitwist.com/scriptlike-docs/v0.9.4/scriptlike/file/extras/removePath.html http://semitwist.com/scriptlike-docs/v0.9.4/scriptlike/file/extras/tryRemovePath.html (Pardon the excess paragraph breaks on those pages. *cough* https://github.com/D-Programming-Language/dmd/pull/4745 *cough*) Of course, that does mean two *more* functions, but at least they're the only ones you need. :)
Sep 23 2015