1 /// Utilities for writing and reading ANSI styled text.
2 module jansi;
3 
4 import std.range : isOutputRange;
5 import std.typecons : Flag;
6 
7 /+++ CONSTANTS +++/
8 
9 version(JANSI_BetterC)
10 {
11     private enum BetterC = true;
12 }
13 else
14 {
15     version(D_BetterC)
16     {
17         private enum BetterC = true;
18     }
19     else
20     {
21         private enum BetterC = false;
22     }
23 }
24 
25 /// Used to determine if an `AnsiColour` is a background or foreground colour.
26 alias IsBgColour = Flag!"isBg";
27 
28 /// Used by certain functions to determine if they should only output an ANSI sequence, or output their entire sequence + data.
29 alias AnsiOnly = Flag!"ansiOnly";
30 
31 /// An 8-bit ANSI colour - an index into the terminal's colour palette.
32 alias Ansi8BitColour = ubyte;
33 
34 /// The string that starts an ANSI command sequence.
35 immutable ANSI_CSI                = "\033[";
36 
37 /// The character that delimits ANSI parameters.
38 immutable ANSI_SEPARATOR          = ';';
39 
40 /// The character used to denote that the sequence is an SGR sequence.
41 immutable ANSI_COLOUR_END         = 'm';
42 
43 /// The sequence used to reset all styling.
44 immutable ANSI_COLOUR_RESET       = ANSI_CSI~"0"~ANSI_COLOUR_END;
45 
46 /// The amount to increment an `Ansi4BitColour` by in order to access the background version of the colour.
47 immutable ANSI_FG_TO_BG_INCREMENT = 10;
48 
49 /+++ COLOUR TYPES +++/
50 
51 /++
52  + Defines what type of colour an `AnsiColour` stores.
53  + ++/
54 enum AnsiColourType
55 {
56     /// Default, failsafe.
57     none,
58 
59     /// 4-bit colours.
60     fourBit,
61 
62     /// 8-bit colours.
63     eightBit,
64 
65     /// 24-bit colours.
66     rgb
67 }
68 
69 /++
70  + An enumeration of standard 4-bit colours.
71  +
72  + These colours will have the widest support between platforms.
73  + ++/
74 enum Ansi4BitColour
75 {
76     black           = 30,
77     red             = 31,
78     green           = 32,
79     /// On Powershell, this is displayed as a very white colour.
80     yellow          = 33,
81     blue            = 34,
82     magenta         = 35,
83     cyan            = 36,
84     /// More gray than true white, use `BrightWhite` for true white.
85     white           = 37,
86     /// Grayer than `White`.
87     brightBlack     = 90,
88     brightRed       = 91,
89     brightGreen     = 92,
90     brightYellow    = 93,
91     brightBlue      = 94,
92     brightMagenta   = 95,
93     brightCyan      = 96,
94     brightWhite     = 97
95 }
96 
97 /++
98  + Contains a 3-byte, RGB colour.
99  + ++/
100 @safe
101 struct AnsiRgbColour
102 {
103     union
104     {
105         /// The RGB components as an array.
106         ubyte[3] components;
107 
108         struct {
109             /// The red component.
110             ubyte r;
111 
112             /// The green component.
113             ubyte g;
114 
115             /// The blue component.
116             ubyte b;
117         }
118     }
119 
120     @safe @nogc nothrow pure:
121 
122     /++
123      + Construct this colour from 3 ubyte components, in RGB order.
124      +
125      + Params:
126      +  components = The components to use.
127      + ++/
128     this(ubyte[3] components)
129     {
130         this.components = components;
131     }
132 
133     /++
134      + Construct this colour from the 3 provided ubyte components.
135      +
136      + Params:
137      +  r = The red component.
138      +  g = The green component.
139      +  b = The blue component.
140      + ++/
141     this(ubyte r, ubyte g, ubyte b)
142     {
143         this.r = r;
144         this.g = g;
145         this.b = b;
146     }
147 }
148 
149 private union AnsiColourUnion
150 {
151     Ansi4BitColour fourBit;
152     Ansi8BitColour eightBit;
153     AnsiRgbColour  rgb;
154 }
155 
156 /++
157  + Contains any type of ANSI colour and provides the ability to create a valid SGR command to set the foreground/background.
158  +
159  + This struct overloads `opAssign` allowing easy assignment from `Ansi4BitColour`, `Ansi8BitColour`, `AnsiRgbColour`, and any user-defined type
160  + that satisfies `isUserDefinedRgbType`.
161  + ++/
162 @safe
163 struct AnsiColour
164 {
165     private static immutable FG_MARKER        = "38";
166     private static immutable BG_MARKER        = "48";
167     private static immutable EIGHT_BIT_MARKER = '5';
168     private static immutable RGB_MARKER       = '2';
169 
170     /++
171      + The maximum amount of characters any singular `AnsiColour` sequence may use.
172      +
173      + This is often used to create a static array to temporarily, and without allocation, store the sequence for an `AnsiColour`.
174      + ++/
175     enum MAX_CHARS_NEEDED = "38;2;255;255;255".length;
176 
177     private
178     {
179         AnsiColourUnion _value;
180         AnsiColourType  _type;
181         IsBgColour      _isBg;
182 
183         @safe @nogc nothrow
184         this(IsBgColour isBg) pure
185         {
186             this._isBg = isBg;
187         }
188     }
189 
190     /// A variant of `.init` that is used for background colours.
191     static immutable bgInit = AnsiColour(IsBgColour.yes);
192 
193     /+++ CTORS AND PROPERTIES +++/
194     @safe @nogc nothrow pure
195     {
196         // Seperate, non-templated constructors as that's a lot more documentation-generator-friendly.
197 
198         /++
199          + Construct a 4-bit colour.
200          +
201          + Params:
202          +  colour = The 4-bit colour to use.
203          +  isBg   = Determines whether this colour sets the foreground or the background.
204          + ++/
205         this(Ansi4BitColour colour, IsBgColour isBg = IsBgColour.no)
206         {
207             this = colour;
208             this._isBg = isBg;
209         }
210         
211         /++
212          + Construct an 8-bit colour.
213          +
214          + Params:
215          +  colour = The 8-bit colour to use.
216          +  isBg   = Determines whether this colour sets the foreground or the background.
217          + ++/
218         this(Ansi8BitColour colour, IsBgColour isBg = IsBgColour.no)
219         {
220             this = colour;
221             this._isBg = isBg;
222         }
223 
224         /++
225          + Construct an RGB colour.
226          +
227          + Params:
228          +  colour = The RGB colour to use.
229          +  isBg   = Determines whether this colour sets the foreground or the background.
230          + ++/
231         this(AnsiRgbColour colour, IsBgColour isBg = IsBgColour.no)
232         {
233             this = colour;
234             this._isBg = isBg;
235         }
236 
237         /++
238          + Construct an RGB colour.
239          +
240          + Params:
241          +  r      = The red component.
242          +  g      = The green component.
243          +  b      = The blue component.
244          +  isBg   = Determines whether this colour sets the foreground or the background.
245          + ++/
246         this(ubyte r, ubyte g, ubyte b, IsBgColour isBg = IsBgColour.no)
247         {
248             this(AnsiRgbColour(r, g, b), isBg);
249         }
250 
251         /++
252          + Construct an RGB colour.
253          +
254          + Params:
255          +  colour = The user-defined colour type that satisfies `isUserDefinedRgbType`.
256          +  isBg   = Determines whether this colour sets the foreground or the background.
257          + ++/
258         this(T)(T colour, IsBgColour isBg = IsBgColour.no)
259         if(isUserDefinedRgbType!T)
260         {
261             this = colour;
262             this._isBg = isBg;
263         }
264 
265         /++
266          + Allows direct assignment from any type that can also be used in any of this struct's ctors.
267          + ++/
268         auto opAssign(T)(T colour) return
269         if(!is(T == typeof(this)))
270         {
271             static if(is(T == Ansi4BitColour))
272             {
273                 this._value.fourBit = colour;
274                 this._type = AnsiColourType.fourBit;
275             }
276             else static if(is(T == Ansi8BitColour))
277             {
278                 this._value.eightBit = colour;
279                 this._type = AnsiColourType.eightBit;
280             }
281             else static if(is(T == AnsiRgbColour))
282             {
283                 this._value.rgb = colour;
284                 this._type = AnsiColourType.rgb;
285             }
286             else static if(isUserDefinedRgbType!T)
287             {
288                 this = colour.to!AnsiColour();
289             }
290             else static assert(false, "Cannot implicitly convert "~T.stringof~" into an AnsiColour.");
291             
292             return this;
293         }
294 
295         /// Returns: The `AnsiColourType` of this `AnsiColour`.
296         @property
297         AnsiColourType type() const
298         {
299             return this._type;
300         }
301 
302         /// Returns: Whether this `AnsiColour` is for a background or not (it affects the output!).
303         @property
304         IsBgColour isBg() const
305         {
306             return this._isBg;
307         }
308 
309         /// ditto
310         @property
311         void isBg(IsBgColour bg)
312         {
313             this._isBg = bg;
314         }
315 
316         /// ditto
317         @property
318         void isBg(bool bg)
319         {
320             this._isBg = cast(IsBgColour)bg;
321         }
322 
323         /++
324         + Assertions:
325         +  This colour's type must be `AnsiColourType.fourBit`
326         +
327         + Returns:
328         +  This `AnsiColour` as an `Ansi4BitColour`.
329         + ++/
330         @property
331         Ansi4BitColour asFourBit() const
332         {
333             assert(this.type == AnsiColourType.fourBit);
334             return this._value.fourBit;
335         }
336 
337         /++
338         + Assertions:
339         +  This colour's type must be `AnsiColourType.eightBit`
340         +
341         + Returns:
342         +  This `AnsiColour` as a `ubyte`.
343         + ++/
344         @property
345         ubyte asEightBit() const
346         {
347             assert(this.type == AnsiColourType.eightBit);
348             return this._value.eightBit;
349         }
350 
351         /++
352         + Assertions:
353         +  This colour's type must be `AnsiColourType.rgb`
354         +
355         + Returns:
356         +  This `AnsiColour` as an `AnsiRgbColour`.
357         + ++/
358         @property
359         AnsiRgbColour asRgb() const
360         {
361             assert(this.type == AnsiColourType.rgb);
362             return this._value.rgb;
363         }
364     }
365     
366     /+++ OUTPUT +++/
367 
368     static if(!BetterC)
369     {
370         /++
371          + [Not enabled in -betterC] Converts this `AnsiColour` into a GC-allocated sequence string.
372          +
373          + See_Also:
374          +  `toSequence`
375          + ++/
376         @trusted nothrow
377         string toString() const
378         {
379             import std.exception : assumeUnique;
380 
381             auto chars = new char[MAX_CHARS_NEEDED];
382             return this.toSequence(chars[0..MAX_CHARS_NEEDED]).assumeUnique;
383         }
384         ///
385         @("AnsiColour.toString")
386         unittest
387         {
388             assert(AnsiColour(255, 128, 64).toString() == "38;2;255;128;64");
389         }
390     }
391 
392     /++
393      + Creates an ANSI SGR command that either sets the foreground, or the background (`isBg`) to the colour
394      + stored inside of this `AnsiColour`.
395      +
396      + Please note that the CSI (`ANSI_CSI`/`\033[`) and the SGR marker (`ANSI_COLOUR_END`/`m`) are not included
397      + in this output.
398      +
399      + Notes:
400      +  Any characters inside of `buffer` that are not covered by the returned slice, are left unmodified.
401      +
402      +  If this colour hasn't been initialised or assigned a value, then the returned value is simply `null`.
403      +
404      + Params:
405      +  buffer = The statically allocated buffer used to store the result of this function.
406      +
407      + Returns:
408      +  A slice into `buffer` that contains the output of this function.
409      + ++/
410     @safe @nogc
411     char[] toSequence(ref return char[MAX_CHARS_NEEDED] buffer) nothrow const
412     {
413         if(this.type == AnsiColourType.none)
414             return null;
415 
416         size_t cursor;
417 
418         void numIntoBuffer(ubyte num)
419         {
420             char[3] text;
421             const slice = numToStrBase10(text[0..3], num);
422             buffer[cursor..cursor + slice.length] = slice[];
423             cursor += slice.length;
424         }
425 
426         if(this.type != AnsiColourType.fourBit)
427         {
428             // 38; or 48;
429             auto marker = (this.isBg) ? BG_MARKER : FG_MARKER;
430             buffer[cursor..cursor+2] = marker[0..$];
431             cursor += 2;
432             buffer[cursor++] = ANSI_SEPARATOR;
433         }
434 
435         // 4bit, 5;8bit, or 2;r;g;b
436         final switch(this.type) with(AnsiColourType)
437         {
438             case none: assert(false);
439             case fourBit: 
440                 numIntoBuffer(cast(ubyte)((this.isBg) ? this._value.fourBit + 10 : this._value.fourBit)); 
441                 break;
442 
443             case eightBit:
444                 buffer[cursor++] = EIGHT_BIT_MARKER;
445                 buffer[cursor++] = ANSI_SEPARATOR;
446                 numIntoBuffer(this._value.eightBit);
447                 break;
448                 
449             case rgb:
450                 buffer[cursor++] = RGB_MARKER;
451                 buffer[cursor++] = ANSI_SEPARATOR;
452 
453                 numIntoBuffer(this._value.rgb.r); 
454                 buffer[cursor++] = ANSI_SEPARATOR;
455                 numIntoBuffer(this._value.rgb.g); 
456                 buffer[cursor++] = ANSI_SEPARATOR;
457                 numIntoBuffer(this._value.rgb.b); 
458                 break;
459         }
460 
461         return buffer[0..cursor];
462     }
463     ///
464     @("AnsiColour.toSequence(char[])")
465     unittest
466     {
467         char[AnsiColour.MAX_CHARS_NEEDED] buffer;
468 
469         void test(string expected, AnsiColour colour)
470         {
471             const slice = colour.toSequence(buffer);
472             assert(slice == expected);
473         }
474 
475         test("32",               AnsiColour(Ansi4BitColour.green));
476         test("42",               AnsiColour(Ansi4BitColour.green, IsBgColour.yes));
477         test("38;5;1",           AnsiColour(Ansi8BitColour(1)));
478         test("48;5;1",           AnsiColour(Ansi8BitColour(1), IsBgColour.yes));
479         test("38;2;255;255;255", AnsiColour(255, 255, 255));
480         test("48;2;255;128;64",  AnsiColour(255, 128, 64, IsBgColour.yes));
481     }
482 }
483 
484 /+++ MISC TYPES +++/
485 
486 /++
487  + A list of styling options provided by ANSI SGR.
488  +
489  + As a general rule of thumb, assume most of these won't work inside of a Windows command prompt (unless it's the new Windows Terminal).
490  + ++/
491 enum AnsiSgrStyle
492 {
493     none      = 0,
494     bold      = 1,
495     dim       = 2,
496     italic    = 3,
497     underline = 4,
498     slowBlink = 5,
499     fastBlink = 6,
500     invert    = 7,
501     strike    = 9
502 }
503 
504 private template getMaxSgrStyleCharCount()
505 {
506     import std.traits : EnumMembers;
507 
508     // Can't even use non-betterC features in CTFE, so no std.conv.to!string :(
509     size_t numberOfChars(int num)
510     {
511         size_t amount;
512 
513         do
514         {
515             amount++;
516             num /= 10;
517         } while(num > 0);
518 
519         return amount;
520     }
521 
522     size_t calculate()
523     {
524         size_t amount;
525         static foreach(member; EnumMembers!AnsiSgrStyle)
526             amount += numberOfChars(cast(int)member) + 1; // + 1 for the semi-colon after.
527 
528         return amount;
529     }
530 
531     enum getMaxSgrStyleCharCount = calculate();
532 }
533 
534 /++
535  + Contains any number of styling options from `AnsiStyleSgr`, and provides the ability to generate
536  + an ANSI SGR command to apply all of the selected styling options.
537  + ++/
538 @safe
539 struct AnsiStyle
540 {
541     /++
542      + The maximum amount of characters any singular `AnsiStyle` sequence may use.
543      +
544      + This is often used to create a static array to temporarily, and without allocation, store the sequence for an `AnsiStyle`.
545      + ++/
546     enum MAX_CHARS_NEEDED = getMaxSgrStyleCharCount!();
547 
548     private
549     {
550         ushort _sgrBitmask; // Each set bit index corresponds to the value from `AnsiSgrStyle`.
551 
552         @safe @nogc nothrow
553         int sgrToBit(AnsiSgrStyle style) pure const
554         {
555             return 1 << (cast(int)style);
556         }
557 
558         @safe @nogc nothrow
559         void setSgrBit(bool setOrUnset)(AnsiSgrStyle style) pure
560         {
561             static if(setOrUnset)
562                 this._sgrBitmask |= this.sgrToBit(style);
563             else
564                 this._sgrBitmask &= ~this.sgrToBit(style);
565         }
566 
567         @safe @nogc nothrow
568         bool getSgrBit(AnsiSgrStyle style) pure const
569         {
570             return (this._sgrBitmask & this.sgrToBit(style)) > 0;
571         }
572     }
573 
574     // Seperate functions for better documentation generation.
575     //
576     // Tedious, as this otherwise could've all been auto-generated.
577     /+++ SETTERS +++/
578     @safe @nogc nothrow pure
579     {
580         /// Removes all styling from this `AnsiStyle`.
581         AnsiStyle reset() return
582         {
583             this._sgrBitmask = 0;
584             return this;
585         }
586 
587         /++
588          + Enables/Disables a certain styling option.
589          +
590          + Params:
591          +  style  = The styling option to enable/disable.
592          +  enable = If true, enable the option. If false, disable it.
593          +
594          + Returns:
595          +  `this` for chaining.
596          + ++/
597         AnsiStyle set(AnsiSgrStyle style, bool enable) return
598         {
599             if(enable)
600                 this.setSgrBit!true(style);
601             else
602                 this.setSgrBit!false(style);
603             return this;
604         }
605 
606         ///
607         AnsiStyle bold(bool enable = true) return { this.setSgrBit!true(AnsiSgrStyle.bold); return this; }
608         ///
609         AnsiStyle dim(bool enable = true) return { this.setSgrBit!true(AnsiSgrStyle.dim); return this; }
610         ///
611         AnsiStyle italic(bool enable = true) return { this.setSgrBit!true(AnsiSgrStyle.italic); return this; }
612         ///
613         AnsiStyle underline(bool enable = true) return { this.setSgrBit!true(AnsiSgrStyle.underline); return this; }
614         ///
615         AnsiStyle slowBlink(bool enable = true) return { this.setSgrBit!true(AnsiSgrStyle.slowBlink); return this; }
616         ///
617         AnsiStyle fastBlink(bool enable = true) return { this.setSgrBit!true(AnsiSgrStyle.fastBlink); return this; }
618         ///
619         AnsiStyle invert(bool enable = true) return { this.setSgrBit!true(AnsiSgrStyle.invert); return this; }
620         ///
621         AnsiStyle strike(bool enable = true) return { this.setSgrBit!true(AnsiSgrStyle.strike); return this; }
622     }
623 
624     /+++ GETTERS +++/
625     @safe @nogc nothrow pure const
626     {
627         /++
628          + Get the status of a certain styling option.
629          +
630          + Params:
631          +  style = The styling option to get.
632          +
633          + Returns:
634          +  `true` if the styling option is enabled, `false` otherwise.
635          + ++/
636         bool get(AnsiSgrStyle style)
637         {
638             return this.getSgrBit(style);
639         }
640         
641         ///
642         bool bold() { return this.getSgrBit(AnsiSgrStyle.bold); }
643         ///
644         bool dim() { return this.getSgrBit(AnsiSgrStyle.dim); }
645         ///
646         bool italic() { return this.getSgrBit(AnsiSgrStyle.italic); }
647         ///
648         bool underline() { return this.getSgrBit(AnsiSgrStyle.underline); }
649         ///
650         bool slowBlink() { return this.getSgrBit(AnsiSgrStyle.slowBlink); }
651         ///
652         bool fastBlink() { return this.getSgrBit(AnsiSgrStyle.fastBlink); }
653         ///
654         bool invert() { return this.getSgrBit(AnsiSgrStyle.invert); }
655         ///
656         bool strike() { return this.getSgrBit(AnsiSgrStyle.strike); }
657     }
658 
659     /+++ OUTPUT +++/
660 
661     static if(!BetterC)
662     {
663         /++
664          + [Not enabled in -betterC] Converts this `AnsiStyle` into a GC-allocated sequence string.
665          +
666          + See_Also:
667          +  `toSequence`
668          + ++/
669         @trusted nothrow
670         string toString() const
671         {
672             import std.exception : assumeUnique;
673 
674             auto chars = new char[MAX_CHARS_NEEDED];
675             return this.toSequence(chars[0..MAX_CHARS_NEEDED]).assumeUnique;
676         }
677     }
678 
679     /++
680      + Creates an ANSI SGR command that enables all of the desired styling options, while leaving all of the other options unchanged.
681      +
682      + Please note that the CSI (`ANSI_CSI`/`\033[`) and the SGR marker (`ANSI_COLOUR_END`/`m`) are not included
683      + in this output.
684      +
685      + Notes:
686      +  Any characters inside of `buffer` that are not covered by the returned slice, are left unmodified.
687      +
688      +  If this colour hasn't been initialised or assigned a value, then the returned value is simply `null`.
689      +
690      + Params:
691      +  buffer = The statically allocated buffer used to store the result of this function.
692      +
693      + Returns:
694      +  A slice into `buffer` that contains the output of this function.
695      + ++/
696     @safe @nogc
697     char[] toSequence(ref return char[MAX_CHARS_NEEDED] buffer) nothrow const
698     {
699         import std.traits : EnumMembers;
700 
701         if(this._sgrBitmask == 0)
702             return null;
703 
704         size_t cursor;
705         void numIntoBuffer(uint num)
706         {
707             char[10] text;
708             const slice = numToStrBase10(text[0..$], num);
709             buffer[cursor..cursor + slice.length] = slice[];
710             cursor += slice.length;
711         }
712 
713         bool isFirstValue = true;
714         static foreach(flag; EnumMembers!AnsiSgrStyle)
715         {{
716             if(this.getSgrBit(flag))
717             {
718                 if(!isFirstValue)
719                     buffer[cursor++] = ANSI_SEPARATOR;
720                 isFirstValue = false;
721 
722                 numIntoBuffer(cast(uint)flag);
723             }
724         }}
725 
726         return buffer[0..cursor];
727     }
728     ///
729     @("AnsiStyle.toSequence(char[])")
730     unittest
731     {
732         static if(!BetterC)
733         {
734             char[AnsiStyle.MAX_CHARS_NEEDED] buffer;
735             
736             void test(string expected, AnsiStyle style)
737             {
738                 const slice = style.toSequence(buffer);
739                 assert(slice == expected, "Got '"~slice~"' wanted '"~expected~"'");
740             }
741 
742             test("", AnsiStyle.init);
743             test("1;2;3", AnsiStyle.init.bold.dim.italic);
744         }
745     }
746 }
747 
748 /+++ DATA WITH COLOUR TYPES +++/
749 
750 /++
751  + Contains an `AnsiColour` for the foreground, an `AnsiColour` for the background, and an `AnsiStyle` for additional styling,
752  + and provides the ability to create an ANSI SGR command to set the foreground, background, and overall styling of the terminal.
753  +
754  + A.k.a This is just a container over two `AnsiColour`s and an `AnsiStyle`.
755  + ++/
756 @safe
757 struct AnsiStyleSet
758 {
759     /++
760      + The maximum amount of characters any singular `AnsiStyle` sequence may use.
761      +
762      + This is often used to create a static array to temporarily, and without allocation, store the sequence for an `AnsiStyle`.
763      + ++/
764     enum MAX_CHARS_NEEDED = (AnsiColour.MAX_CHARS_NEEDED * 2) + AnsiStyle.MAX_CHARS_NEEDED;
765 
766     private AnsiColour _fg;
767     private AnsiColour _bg;
768     private AnsiStyle _style;
769 
770     // As usual, functions are manually made for better documentation.
771 
772     /+++ SETTERS +++/
773     @safe @nogc nothrow
774     {
775         ///
776         AnsiStyleSet fg(AnsiColour colour) return { this._fg = colour; this._fg.isBg = IsBgColour.no; return this; }
777         ///
778         AnsiStyleSet fg(Ansi4BitColour colour) return { return this.fg(AnsiColour(colour)); }
779         ///
780         AnsiStyleSet fg(Ansi8BitColour colour) return { return this.fg(AnsiColour(colour)); }
781         ///
782         AnsiStyleSet fg(AnsiRgbColour colour) return { return this.fg(AnsiColour(colour)); }
783 
784         ///
785         AnsiStyleSet bg(AnsiColour colour) return { this._bg = colour; this._bg.isBg = IsBgColour.yes; return this; }
786         ///
787         AnsiStyleSet bg(Ansi4BitColour colour) return { return this.bg(AnsiColour(colour)); }
788         ///
789         AnsiStyleSet bg(Ansi8BitColour colour) return { return this.bg(AnsiColour(colour)); }
790         ///
791         AnsiStyleSet bg(AnsiRgbColour colour) return { return this.bg(AnsiColour(colour)); }
792         ///
793 
794         ///
795         AnsiStyleSet style(AnsiStyle style) return { this._style = style; return this; }
796     }
797 
798     /+++ GETTERS +++/
799     @safe @nogc nothrow const
800     {
801         ///
802         AnsiColour fg() { return this._fg; }
803         ///
804         AnsiColour bg() { return this._bg; }
805         ///
806         AnsiStyle style() { return this._style; }
807     }
808 
809     /+++ OUTPUT ++/
810     /++
811      + Creates an ANSI SGR command that sets the foreground colour, sets the background colour,
812      + and enables all of the desired styling options, while leaving all of the other options unchanged.
813      +
814      + Please note that the CSI (`ANSI_CSI`/`\033[`) and the SGR marker (`ANSI_COLOUR_END`/`m`) are not included
815      + in this output.
816      +
817      + Notes:
818      +  Any characters inside of `buffer` that are not covered by the returned slice, are left unmodified.
819      +
820      +  If this colour hasn't been initialised or assigned a value, then the returned value is simply `null`.
821      +
822      + Params:
823      +  buffer = The statically allocated buffer used to store the result of this function.
824      +
825      + Returns:
826      +  A slice into `buffer` that contains the output of this function.
827      + ++/
828     @safe @nogc
829     char[] toSequence(ref return char[MAX_CHARS_NEEDED] buffer) nothrow const
830     {
831         size_t cursor;
832 
833         char[AnsiColour.MAX_CHARS_NEEDED] colour;
834         char[AnsiStyle.MAX_CHARS_NEEDED] style;
835 
836         auto slice = this._fg.toSequence(colour);
837         buffer[cursor..cursor + slice.length] = slice[];
838         cursor += slice.length;
839 
840         slice = this._bg.toSequence(colour);
841         if(slice.length > 0 && cursor > 0)
842             buffer[cursor++] = ANSI_SEPARATOR;
843         buffer[cursor..cursor + slice.length] = slice[];
844         cursor += slice.length;
845 
846         slice = this.style.toSequence(style);
847         if(slice.length > 0 && cursor > 0)
848             buffer[cursor++] = ANSI_SEPARATOR;
849         buffer[cursor..cursor + slice.length] = slice[];
850         cursor += slice.length;
851 
852         return buffer[0..cursor];
853     }
854     ///
855     @("AnsiStyleSet.toSequence")
856     unittest
857     {
858         char[AnsiStyleSet.MAX_CHARS_NEEDED] buffer;
859 
860         void test(string expected, AnsiStyleSet ch)
861         {
862             auto slice = ch.toSequence(buffer);
863             assert(slice == expected, "Got '"~slice~"' expected '"~expected~"'");
864         }
865 
866         test("", AnsiStyleSet.init);
867         test(
868             "32;48;2;255;128;64;1;4", 
869             AnsiStyleSet.init
870                     .fg(Ansi4BitColour.green)
871                     .bg(AnsiRgbColour(255, 128, 64))
872                     .style(AnsiStyle.init.bold.underline)
873         );
874     }
875 }
876 
877 /++
878  + An enumeration used by an `AnsiText` implementation to describe any special features that `AnsiText` needs to mold
879  + itself around.
880  + ++/
881 enum AnsiTextImplementationFeatures
882 {
883     /// Supports at least `.put`, `.toSink`, `char[] .newSlice`, and allows `AnsiText` to handle the encoding.
884     basic = 0, 
885 }
886 
887 /++
888  + A lightweight alternative to `AnsiText` which only supports a singular coloured string, at the cost
889  + of removing most of the other complexity & dynamic allocation needs of `AnsiText`.
890  +
891  + If you only need to style your string in one certain way, or want to avoid `AnsiText` altogether, then this struct
892  + is the way to go.
893  +
894  + Usage_(Manually):
895  +  First, retrieve and the ANSI styling sequence via `AnsiTextLite.toFullStartSequence` and output it.
896  +
897  +  Second, output `AnsiTextLite.text`.
898  +
899  +  Finally, and optionally, retrieve the ANSI reset sequence via `AnsiTextLite.toFullEndSequence` and output it.
900  +
901  + Usage_(Range):
902  +  Call `AnsiTextLite.toRange` to get the range, please read its documentation as it is important (it'll return slices to stack-allocated memory).
903  +
904  + Usage_(GC):
905  +  If you're not compiling under `-betterc`, then `AnsiTextLite.toString()` will provide you with a GC-allocated string containing:
906  +  the start ANSI sequence; the text to display; and the end ANSI sequence. i.e. A string that is just ready to be printed.
907  +
908  +  This struct also implements the sink-based version of `toString`, which means that when used directly with things like `writeln`, this struct
909  +  is able to avoid allocations (unless the sink itself allocates). See the unittest for an example.
910  +
911  + See_Also:
912  +  `ansi` for fluent creation of an `AnsiTextLite`.
913  +
914  +  This struct's unittest for an example of usage.
915  + ++/
916 struct AnsiTextLite
917 {
918     /++
919      + The maximum amount of chars required by the start sequence of an `AnsiTextLite` (`toFullStartSequence`).
920      + ++/
921     enum MAX_CHARS_NEEDED = AnsiStyleSet.MAX_CHARS_NEEDED + ANSI_CSI.length + 1; // + 1 for the ANSI_COLOUR_END
922 
923     /// The text to output.
924     const(char)[] text;
925     
926     /// The styling to apply to the text.
927     AnsiStyleSet styleSet;
928 
929     /+++ SETTERS +++/
930     // TODO: Should probably make a mixin template for this, but I need to see how the documentation generators handle that.
931     //       I also can't just do an `alias this`, as otherwise the style functions wouldn't return `AnsiTextLite`, but instead `AnsiStyleSet`.
932     //       Or just suck it up and make some of the setters templatised, much to the dismay of documentation.
933     @safe @nogc nothrow
934     {
935         ///
936         AnsiTextLite fg(AnsiColour colour) return { this.styleSet.fg = colour; this.styleSet.fg.isBg = IsBgColour.no; return this; }
937         ///
938         AnsiTextLite fg(Ansi4BitColour colour) return { return this.fg(AnsiColour(colour)); }
939         ///
940         AnsiTextLite fg(Ansi8BitColour colour) return { return this.fg(AnsiColour(colour)); }
941         ///
942         AnsiTextLite fg(AnsiRgbColour colour) return { return this.fg(AnsiColour(colour)); }
943 
944         ///
945         AnsiTextLite bg(AnsiColour colour) return { this.styleSet.bg = colour; this.styleSet.bg.isBg = IsBgColour.yes; return this; }
946         ///
947         AnsiTextLite bg(Ansi4BitColour colour) return { return this.bg(AnsiColour(colour)); }
948         ///
949         AnsiTextLite bg(Ansi8BitColour colour) return { return this.bg(AnsiColour(colour)); }
950         ///
951         AnsiTextLite bg(AnsiRgbColour colour) return { return this.bg(AnsiColour(colour)); }
952         ///
953 
954         ///
955         AnsiTextLite style(AnsiStyle style) return { this.styleSet.style = style; return this; }
956     }
957 
958     /+++ GETTERS +++/
959     @safe @nogc nothrow const
960     {
961         ///
962         AnsiColour fg() { return this.styleSet.fg; }
963         ///
964         AnsiColour bg() { return this.styleSet.bg; }
965         ///
966         AnsiStyle style() { return this.styleSet.style; }
967     }
968 
969     @safe @nogc nothrow const
970     {
971         /++
972          + Populates the given buffer with the full ANSI sequence needed to enable the styling
973          + defined within this `AnsiTextLite`
974          +
975          + Unlike the usual `toSequence` functions, this function includes the `ANSI_CSI` and `ANSI_COLOUR_END` markers,
976          + meaning the output from this function is ready to be printed as-is.
977          +
978          + Do note that this function doesn't insert a null-terminator, so if you're using anything based on C strings, you need
979          + to insert that yourself.
980          +
981          + Notes:
982          +  Any parts of the `buffer` that are not populated by this function are left untouched.
983          +
984          + Params:
985          +  buffer = The buffer to populate.
986          +
987          + Returns:
988          +  The slice of `buffer` that has been populated.
989          + ++/
990         char[] toFullStartSequence(ref return char[MAX_CHARS_NEEDED] buffer)
991         {
992             size_t cursor;
993 
994             buffer[0..ANSI_CSI.length] = ANSI_CSI[];
995             cursor += ANSI_CSI.length;
996 
997             char[AnsiStyleSet.MAX_CHARS_NEEDED] styleBuffer;
998             const styleSlice = this.styleSet.toSequence(styleBuffer);
999             buffer[cursor..cursor+styleSlice.length] = styleSlice[];
1000             cursor += styleSlice.length;
1001 
1002             buffer[cursor++] = ANSI_COLOUR_END;
1003 
1004             return buffer[0..cursor];
1005         }
1006 
1007         /++
1008          + Returns:
1009          +  The end ANSI sequence for `AnsiTextLite`, which is simply a statically allocated version of the `ANSI_COLOUR_RESET` constant.
1010          + ++/
1011         char[ANSI_COLOUR_RESET.length] toFullEndSequence()
1012         {
1013             typeof(return) buffer;
1014             buffer[0..$] = ANSI_COLOUR_RESET[];
1015             return buffer;
1016         }
1017 
1018         /++
1019          + Provides a range that returns, in this order: The start sequence (`.toFullStartSequence`); the output text (`.text`),
1020          + and finally the end sequence (`.toFullEndSequence`).
1021          +
1022          + This range is $(B weakly-safe) as it $(B returns slices to stack memory) so please ensure that $(B any returned slices don't outlive the origin range object).
1023          +
1024          + Please also note that non of the returned slices contain null terminators.
1025          +
1026          + Returns:
1027          +  An Input Range that returns all the slices required to correctly display this `AnsiTextLite` onto a console.
1028          + ++/
1029         auto toRange()
1030         {
1031             static struct Range
1032             {
1033                 char[MAX_CHARS_NEEDED] start;
1034                 const(char)[] middle;
1035                 char[ANSI_COLOUR_RESET.length] end;
1036                 char[] startSlice;
1037 
1038                 size_t sliceCount;
1039 
1040                 @safe @nogc nothrow:
1041 
1042                 bool empty()
1043                 {
1044                     return this.sliceCount >= 3;
1045                 }
1046 
1047                 void popFront()
1048                 {
1049                     this.sliceCount++;
1050                 }
1051 
1052                 @trusted
1053                 const(char)[] front() return
1054                 {
1055                     switch(sliceCount)
1056                     {
1057                         case 0: return this.startSlice;
1058                         case 1: return this.middle;
1059                         case 2: return this.end[0..$];
1060                         default: assert(false, "Cannot use empty range.");
1061                     }   
1062                 }
1063             }
1064 
1065             Range r;
1066             r.startSlice = this.toFullStartSequence(r.start);
1067             r.middle = this.text;
1068             r.end = this.toFullEndSequence();
1069 
1070             return r;
1071         }
1072     }
1073 
1074     static if(!BetterC)
1075     /++
1076      + Notes:
1077      +  This struct implements the sink-based `toString` which performs no allocations, so the likes of `std.stdio.writeln` will
1078      +  automatically use the sink-based version if you pass this struct to it directly.
1079      +
1080      + Returns: 
1081      +  A GC-allocated string containing this `AnsiTextLite` as an ANSI-encoded string, ready for printing.
1082      + ++/
1083     @trusted nothrow // @trusted due to .assumeUnique
1084     string toString() const
1085     {
1086         import std.exception : assumeUnique;
1087 
1088         char[MAX_CHARS_NEEDED] styleBuffer;
1089         const styleSlice = this.toFullStartSequence(styleBuffer);
1090 
1091         auto buffer = new char[styleSlice.length + this.text.length + ANSI_COLOUR_RESET.length];
1092         buffer[0..styleSlice.length]                                  = styleSlice[];
1093         buffer[styleSlice.length..styleSlice.length+this.text.length] = this.text[];
1094         buffer[$-ANSI_COLOUR_RESET.length..$]                         = ANSI_COLOUR_RESET[];
1095 
1096         return buffer.assumeUnique;
1097     }
1098 
1099     /++
1100      + The sink-based version of `toString`, which doesn't allocate by itself unless the `sink` decides to allocate.
1101      +
1102      + Params:
1103      +  sink = The sink to output into.
1104      +
1105      + See_Also:
1106      +  `toSink` for a templatised version of this function which can infer attributes, and supports any form of Output Range instead of just a delegate.
1107      + ++/
1108     void toString(scope void delegate(const(char)[]) sink) const
1109     {
1110         foreach(slice; this.toRange())
1111             sink(slice);
1112     }
1113 
1114     /++
1115      + Outputs in order: The start sequence (`.toFullStartSequence`), the output text (`.text`), and the end sequence (`.toFullEndSequence`)
1116      + into the given `sink`.
1117      +
1118      + This function by itself does not allocate memory.
1119      +
1120      + This function will infer attributes, so as to be used in whatever attribute-souped environment your sink supports.
1121      +
1122      + $(B Please read the warnings described in `.toRange`) TLDR; don't persist the slices given to the sink under any circumstance. You must
1123      + copy the data as soon as you get it.
1124      +
1125      + Params:
1126      +  sink = The sink to output into.
1127      + ++/
1128     void toSink(Sink)(scope ref Sink sink) const
1129     {
1130         foreach(slice; this.toRange())
1131             sink.put(slice);
1132     }
1133 }
1134 ///
1135 @("AnsiTextLite")
1136 unittest
1137 {
1138     static if(!BetterC)
1139     {
1140         auto text = "Hello!".ansi
1141                             .fg(Ansi4BitColour.green)
1142                             .bg(AnsiRgbColour(128, 128, 128))
1143                             .style(AnsiStyle.init.bold.underline);
1144 
1145         // Usage 1: Manually
1146         import core.stdc.stdio : printf;
1147         import std.stdio : writeln, write;
1148         version(JANSI_TestOutput) // Just so test output isn't clogged. This still shows you how to use things though.
1149         {
1150             char[AnsiTextLite.MAX_CHARS_NEEDED + 1] startSequence; // + 1 for null terminator.
1151             const sliceFromStartSequence = text.toFullStartSequence(startSequence[0..AnsiTextLite.MAX_CHARS_NEEDED]);
1152             startSequence[sliceFromStartSequence.length] = '\0';
1153 
1154             char[200] textBuffer;
1155             textBuffer[0..text.text.length] = text.text[];
1156             textBuffer[text.text.length] = '\0';
1157 
1158             char[ANSI_COLOUR_RESET.length + 1] endSequence;
1159             endSequence[0..ANSI_COLOUR_RESET.length] = text.toFullEndSequence()[];
1160             endSequence[$-1] = '\0';
1161 
1162             printf("%s%s%s\n", startSequence.ptr, textBuffer.ptr, endSequence.ptr);
1163         }
1164 
1165         // Usage 2: Range (RETURNS STACK MEMORY, DO NOT ALLOW SLICES TO OUTLIVE RANGE OBJECT WITHOUT EXPLICIT COPY)
1166         version(JANSI_TestOutput)
1167         {
1168             // -betterC
1169             foreach(slice; text.toRange)
1170             {
1171                 char[200] buffer;
1172                 buffer[0..slice.length] = slice[];
1173                 buffer[slice.length] = '\0';
1174                 printf("%s", buffer.ptr);
1175             }
1176             printf("\n");
1177 
1178             // GC
1179             foreach(slice; text.toRange)
1180                 write(slice);
1181             writeln();
1182         }
1183         
1184         // Usage 3: toString (Sink-based, so AnsiTextLite doesn't allocate, but writeln/the sink might)
1185         version(JANSI_TestOutput)
1186         {
1187             writeln(text); // Calls the sink-based .toString();
1188         }
1189 
1190         // Usage 4: toString (non-sink, non-betterc only)
1191         version(JANSI_TestOutput)
1192         {
1193             writeln(text.toString());
1194         }
1195 
1196         // Usage 5: toSink
1197         version(JANSI_TestOutput)
1198         {
1199             struct CustomOutputRange
1200             {
1201                 char[] output;
1202                 @safe
1203                 void put(const(char)[] slice) nothrow
1204                 {
1205                     const start = output.length;
1206                     output.length += slice.length;
1207                     output[start..$] = slice[];
1208                 }
1209             }
1210 
1211             CustomOutputRange sink;
1212             ()@safe nothrow{ text.toSink(sink); }();
1213             
1214             writeln(sink.output);
1215         }
1216     }
1217 }
1218 
1219 /++
1220  + Contains a string that supports the ability for different parts of the string to be styled seperately.
1221  +
1222  + This struct is highly flexible and dynamic, as it requires the use of external code to provide some
1223  + of the implementation.
1224  +
1225  + Because this is provided via a `mixin template`, implementations can also $(B extend) this struct to 
1226  + provide their own functionality, make things non-copyable if needed, allows data to be stored via ref-counting, etc.
1227  +
1228  + This struct itself is mostly just a consistant user-facing interface that all implementations share, while the implementations
1229  + themselves can transform this struct to any level it requires.
1230  +
1231  + Implementations_:
1232  +  While implementations can add whatever functions, operator overloads, constructors, etc. that they want, there is a small
1233  +  set of functions and value that each implmentation must define in order to be useable.
1234  +
1235  +  Every implementation must define an enum called `Features` who's value is one of the values of `AnsiTextImplementationFeatures`.
1236  +  For example: `enum Features = AnsiTextImplementationFeatures.xxx`
1237  +
1238  +  Builtin implementations consist of `AnsiTextGC` (not enabled with -betterC), `AnsiTextStack`, and `AnsiTextMalloc`, which are self-descriptive.
1239  +
1240  + Basic_Implemetations:
1241  +  An implementation that doesn't require anything noteworthy from `AnsiText` itself should define their features as `AnsiTextImplementationFeatures.basic`.
1242  +
1243  +  This type of implementation must implement the following functions (expressed here as an interface for simplicity):
1244  +
1245  +  ```
1246  interface BasicImplementation
1247  {
1248      /// Provides `AnsiText` with a slice that is of at least `minLength` in size.
1249      ///
1250      /// This function is called `AnsiText` needs to insert more styled characters into the string.
1251      ///
1252      /// How this slice is stored and allocated and whatever else, is completely down to the implementation.
1253      /// Remember that because you're a mixin template, you can use referencing counting, disable the copy ctor, etc!
1254      ///
1255      /// The slice will never be escaped by `AnsiText` itself, and will not be stored beyond a single function call.
1256      char[] newSlice(size_t minLength);
1257 
1258      /// Outputs the styled string into the provided sink.
1259      ///
1260      /// Typically this is an OutputRange that can handle `char[]`s, but it can really be whatever the implementation wants to support.
1261      void toSink(Sink)(Sink sink);
1262 
1263      static if(NotCompilingUnderBetterC && ImplementationDoesntDefineToString)
1264      final string toString()
1265      {
1266          // Autogenerated GC-based implementation provided by `AnsiText`.
1267          //
1268          // For implementations where this can be generated, it just makes them a little easier for the user
1269          // to use with things like `writeln`.
1270          //
1271          // The `static if` shows the conditions for this to happen.
1272      }
1273  }
1274  +  ```
1275  + ++/
1276 struct AnsiText(alias ImplementationMixin)
1277 {
1278     mixin ImplementationMixin;
1279     alias ___TEST = TestAnsiTextImpl!(typeof(this));
1280 
1281     void put()(const(char)[] text, AnsiColour fg = AnsiColour.init, AnsiColour bg = AnsiColour.bgInit, AnsiStyle style = AnsiStyle.init)
1282     {
1283         fg.isBg = IsBgColour.no;
1284         bg.isBg = IsBgColour.yes;
1285 
1286         char[AnsiStyleSet.MAX_CHARS_NEEDED] sequence;
1287         auto sequenceSlice = AnsiStyleSet.init.fg(fg).bg(bg).style(style).toSequence(sequence);
1288 
1289         auto minLength = ANSI_CSI.length + sequenceSlice.length + /*ANSI_COLOUR_END*/1 + text.length + ((sequenceSlice.length > 0) ? 2 : 1); // Last one is for the '0' or '0;'
1290         char[] slice = this.newSlice(minLength);
1291         size_t cursor;
1292 
1293         void appendToSlice(const(char)[] source)
1294         {
1295             slice[cursor..cursor+source.length] = source[];
1296             cursor += source.length;
1297         }
1298 
1299         appendToSlice(ANSI_CSI);
1300         appendToSlice("0"); // Reset all previous styling
1301         if(sequenceSlice.length > 0)
1302             slice[cursor++] = ANSI_SEPARATOR;
1303         appendToSlice(sequenceSlice);
1304         slice[cursor++] = ANSI_COLOUR_END;
1305         appendToSlice(text);
1306     }
1307 
1308     /// ditto.
1309     void put()(const(char)[] text, AnsiStyleSet styling)
1310     {
1311         this.put(text, styling.fg, styling.bg, styling.style);
1312     }
1313 
1314     /// ditto.
1315     void put()(AnsiTextLite text)
1316     {
1317         this.put(text.text, text.fg, text.bg, text.style);
1318     }
1319 
1320     // Generate a GC-based toString if circumstances allow.
1321     static if(
1322         Features == AnsiTextImplementationFeatures.basic
1323      && !__traits(hasMember, typeof(this), "toString")
1324      && !BetterC
1325      && __traits(compiles, { struct S{void put(const(char)[]){}} S s; typeof(this).init.toSink(s); }) // Check if this toSink can take a char[] output range.
1326     )
1327     {
1328         /++
1329          + [Not enabled with -betterC] Provides this `AnsiText` as a printable string.
1330          +
1331          + If the implementation is a basic implementation (see the documentation for `AnsiText`); if the
1332          + implementation doesn't define its own `toString`, and if we're not compliling under -betterC, then
1333          + `AnsiText` will generate this function on behalf of the implementation.
1334          +
1335          + Description:
1336          +  For basic implementations this function will call `toSink` with an `Appender!(char[])` as the sink.
1337          +
1338          +  For $(B this default generated) implementation of `toString`, it is a seperate GC-allocated string so is
1339          +  fine for any usage. If an implementation defines its own `toString` then it should also document what the lifetime
1340          +  of its returned string is.
1341          +
1342          + Returns:
1343          +  This `AnsiText` as a useable string.
1344          + ++/
1345         string toString()()
1346         {
1347             import std.array : Appender;
1348             import std.exception : assumeUnique;
1349 
1350             Appender!(char[]) output;
1351             this.toSink(output);
1352 
1353             return ()@trusted{return output.data.assumeUnique;}();
1354         }
1355 
1356         /++
1357          + [Not enabled with -betterC] Provides the sink-based version of the autogenerated `toString`.
1358          +
1359          + This functions and is generated under the same conditions as the parameterless `toString`, except it
1360          + supports the sink-based interface certain parts of Phobos recognises, helping to prevent needless allocations.
1361          +
1362          + This function simply wraps the given `sink` and forwards it to the implementation's `toSink` function, so there's no
1363          + implicit GC overhead as with the other `toString`. (At least, not by `AnsiText` itself.)
1364          + ++/
1365         void toString(scope void delegate(const(char)[]) sink)
1366         {
1367             struct Sink
1368             {
1369                 void put(const(char)[] slice)
1370                 {
1371                     sink(slice);
1372                 }
1373             }
1374             
1375             Sink s;
1376             this.toSink(s);
1377         }
1378     }
1379 }
1380 
1381 private template TestAnsiTextImpl(alias TextT)
1382 {
1383     // Ensures that the implementation has the required functions, and that they can be used in every required way.
1384     static assert(__traits(hasMember, TextT, "Features"),
1385         "Implementation must define: `enum Features = AnsiTextImplementationFeatures.xxx;`"
1386     );
1387 
1388     static if(TextT.Features == AnsiTextImplementationFeatures.basic)
1389     {
1390         static assert(__traits(hasMember, TextT, "newSlice"),
1391             "Implementation must define: `char[] newSlice(size_t minLength)`"
1392         );
1393         static assert(__traits(hasMember, TextT, "toSink"),
1394             "Implementation must define: `void toSink(Sink)(Sink sink)`"
1395         );
1396     }
1397 }
1398 
1399 @("AnsiText.toString - Autogenerated GC-based")
1400 unittest
1401 {
1402     static if(!BetterC)
1403     {
1404         import std.format : format;
1405 
1406         void genericTest(AnsiTextT)(auto ref AnsiTextT text)
1407         {
1408             text.put("Hello, ");
1409             text.put("Wor", AnsiColour(1, 2, 3), AnsiColour(3, 2, 1), AnsiStyle.init.bold.underline);
1410             text.put("ld!", AnsiColour(Ansi4BitColour.green));
1411 
1412             auto str      = text.toString();
1413             auto expected = "\033[0mHello, \033[0;38;2;1;2;3;48;2;3;2;1;1;4mWor\033[0;32mld!\033[0m";
1414 
1415             assert(
1416                 str == expected, 
1417                 "Got is %s chars long. Expected is %s chars long\nGot: %s\nExp: %s".format(str.length, expected.length, [str], [expected])
1418             );
1419 
1420             version(JANSI_TestOutput)
1421             {
1422                 import std.stdio, std.traits;
1423                 static if(isCopyable!AnsiTextT)
1424                     writeln(text);
1425             }
1426         }
1427 
1428         genericTest(AnsiTextGC.init);
1429         genericTest(AnsiTextMalloc.init);
1430         genericTest(AnsiTextStack!100.init);
1431     }
1432 }
1433 
1434 static if(!BetterC)
1435 {
1436     // Very naive implementation just so I have something to start off with.
1437     ///
1438     mixin template AnsiTextGCImplementation()
1439     {
1440         private char[][] _slices;
1441 
1442         enum Features = AnsiTextImplementationFeatures.basic;
1443 
1444         @safe
1445         char[] newSlice(size_t minLength) nothrow
1446         {
1447             this._slices ~= new char[minLength];
1448             return this._slices[$-1];
1449         }
1450 
1451         void toSink(Sink)(ref scope Sink sink)
1452         if(isOutputRange!(Sink, char[]))
1453         {
1454             foreach(slice; this._slices)
1455                 sink.put(slice);
1456             sink.put(ANSI_COLOUR_RESET);
1457         }
1458     }
1459 
1460     /++
1461      + A basic implementation that uses the GC for memory storage.
1462      +
1463      + Since the memory is GC allocated there's no real fears to note.
1464      +
1465      + Allows `AnsiText` to be copied, but changes between copies are not reflected between eachother. Remember to use `ref`!
1466      + ++/
1467     alias AnsiTextGC = AnsiText!AnsiTextGCImplementation;
1468 }
1469 
1470 ///
1471 mixin template AnsiTextMallocImplementation()
1472 {
1473     import std.experimental.allocator.mallocator, std.experimental.allocator;
1474 
1475     enum Features = AnsiTextImplementationFeatures.basic;
1476 
1477     // Again, very naive implementation just to get stuff to show off.
1478     private char[][] _slices;
1479 
1480     // Stuff like this is why I went for this very strange design decision of using user-defined mixin templates.
1481     @disable this(this){}
1482 
1483     @nogc
1484     ~this() nothrow
1485     {
1486         if(this._slices !is null)
1487         {
1488             foreach(slice; this._slices)
1489                 Mallocator.instance.dispose(slice);
1490             Mallocator.instance.dispose(this._slices);
1491         }
1492     }
1493 
1494     @nogc
1495     char[] newSlice(size_t minLength) nothrow
1496     {
1497         auto slice = Mallocator.instance.makeArray!char(minLength);
1498         if(this._slices is null)
1499             this._slices = Mallocator.instance.makeArray!(char[])(1);
1500         else
1501             Mallocator.instance.expandArray(this._slices, 1);
1502         this._slices[$-1] = slice;
1503         return slice;
1504     }
1505 
1506     void toSink(Sink)(ref scope Sink sink)
1507     if(isOutputRange!(Sink, char[]))
1508     {
1509         foreach(slice; this._slices)
1510             sink.put(slice);
1511         sink.put(ANSI_COLOUR_RESET);
1512     }
1513 }
1514 
1515 /++
1516  + A basic implementation using `malloc` backed memory.
1517  +
1518  + This implementation disables copying for `AnsiText`, as it makes use of RAII to cleanup its resources.
1519  +
1520  + Sinks should keep in mind that they are being passed manually managed memory, so it should be considered an error
1521  + if the sink stores any provided slices outside of its `.put` function. i.e. Copy the data, don't keep it around unless you know what you're doing.
1522  + ++/
1523 alias AnsiTextMalloc = AnsiText!AnsiTextMallocImplementation;
1524 
1525 ///
1526 template AnsiTextStackImplementation(size_t Capacity)
1527 {
1528     mixin template AnsiTextStackImplementation()
1529     {
1530         enum Features = AnsiTextImplementationFeatures.basic;
1531 
1532         private char[Capacity] _output;
1533         private size_t _cursor;
1534 
1535         // This code by itself is *technically* safe, but the way the user uses it might not be.
1536 
1537         @safe @nogc
1538         char[] newSlice(size_t minLength) nothrow
1539         {
1540             const end = this._cursor + minLength;
1541             assert(end <= this._output.length, "Ran out of space.");
1542 
1543             auto slice = this._output[this._cursor..end];
1544             this._cursor = end;
1545 
1546             return slice;
1547         }
1548 
1549         void toSink(Sink)(ref Sink sink)
1550         if(isOutputRange!(Sink, char[]))
1551         {
1552             sink.put(this.asStackSlice);
1553             sink.put(ANSI_COLOUR_RESET);
1554         }
1555 
1556         @safe @nogc
1557         char[] asStackSlice() nothrow
1558         {
1559             return this._output[0..this._cursor];    
1560         }
1561 
1562         @safe @nogc
1563         char[Capacity] asStackSliceCopy(ref size_t lengthInUse) nothrow
1564         {
1565             lengthInUse = this._cursor;
1566             return this._output;
1567         }
1568     }
1569 }
1570 
1571 /++
1572  + A basic implementation using a static amount of stack memory.
1573  +
1574  + Sinks should keep in mind that they're being passed a slice to stack memory, so should not persist slices outside of their `.put` function,
1575  + they must instead make a copy of the data.
1576  +
1577  + This implementation will fail an assert if the user attempts to push more data into it than it can handle.
1578  +
1579  + Params:
1580  +  Capacity = The amount of characters to use on the stack.
1581  + ++/
1582 alias AnsiTextStack(size_t Capacity) = AnsiText!(AnsiTextStackImplementation!Capacity);
1583 
1584 /+++ READING/PARSING +++/
1585 
1586 /++
1587  + Executes the SGR sequence found in `input`, and populates the passed in `style` based on the command sequence.
1588  +
1589  + Anything directly provided by this library is supported.
1590  +
1591  + The previous state of `style` is preserved unless specifically untoggled/reset via the command sequence (e.g. `ESC[0m` to reset everything).
1592  +
1593  + If an error occurs during execution of the sequence, the given `style` is left completely unmodified.
1594  +
1595  + Params:
1596  +  input     = The slice containing the command sequence. The first character should be the start (`ANSI_CSI`) character of the sequence (`\033`), and
1597  +              characters will continue to be read until the command sequence has been finished. Any characters after the command sequence are left unread.
1598  +  style     = A reference to an `AnsiStyleSet` to populate. As mentioned, this function will only untoggle styling, or reset the style if the command sequence specifies.
1599  +              This value is left unmodified if an error is encountered.
1600  +  charsRead = This value will be set to the amount of chars read from the given `input`, so the caller knows where to continue reading from (if applicable).
1601  +              This value is populated on both error and ok.
1602  +
1603  + Returns:
1604  +  Either `null` on ok, or a string describing the error that was encountered.
1605  + ++/
1606 @safe @nogc
1607 string ansiExecuteSgrSequence(const(char)[] input, ref AnsiStyleSet style, out size_t charsRead) nothrow
1608 {
1609     import std.traits : EnumMembers;
1610 
1611     enum ReadResult { foundEndMarker, foundSemiColon, foundEnd, foundBadCharacter }
1612 
1613     if(input.length < 3)
1614         return "A valid SGR is at least 3 characters long: ESC[m";
1615 
1616     if(input[0..ANSI_CSI.length] != ANSI_CSI)
1617         return "Input does not start with the CSI: ESC[";
1618 
1619     auto styleCopy = style;
1620 
1621     charsRead = 2;
1622     ReadResult readToSemiColonOrEndMarker(ref const(char)[] slice)
1623     {
1624         const start = charsRead;
1625         while(true)
1626         {
1627             if(charsRead >= input.length)
1628                 return ReadResult.foundEnd;
1629 
1630             const ch = input[charsRead];
1631             if(ch == 'm')
1632             {
1633                 slice = input[start..charsRead];
1634                 return ReadResult.foundEndMarker;
1635             }
1636             else if(ch == ';')
1637             {
1638                 slice = input[start..charsRead];
1639                 return ReadResult.foundSemiColon;
1640             }
1641             else if(ch >= '0' && ch <= '9')
1642             {
1643                 charsRead++;
1644                 continue;
1645             }
1646             else
1647                 return ReadResult.foundBadCharacter;
1648         }
1649     }
1650 
1651     int toValue(const(char)[] slice)
1652     {
1653         return (slice.length == 0) ? 0 : slice.strToNum!int;
1654     }
1655 
1656     string resultToString(ReadResult result)
1657     {
1658         final switch(result) with(ReadResult)
1659         {
1660             case foundEnd: return "Unexpected end of input.";
1661             case foundBadCharacter: return "Unexpected character in input.";
1662 
1663             case foundSemiColon: return "Unexpected semi-colon.";
1664             case foundEndMarker: return "Unexpected end marker ('m').";
1665         }
1666     }
1667 
1668     const(char)[] generalSlice;
1669     while(charsRead < input.length)
1670     {
1671         const ch = input[charsRead];
1672 
1673         switch(ch)
1674         {
1675             case '0':..case '9':
1676                 auto result = readToSemiColonOrEndMarker(generalSlice);
1677                 if(result != ReadResult.foundSemiColon && result != ReadResult.foundEndMarker)
1678                     return resultToString(result);
1679 
1680                 const commandAsNum = toValue(generalSlice);
1681                 Switch: switch(commandAsNum)
1682                 {
1683                     // Full reset
1684                     case 0: styleCopy = AnsiStyleSet.init; break;
1685 
1686                     // Basic style flag setters.
1687                     static foreach(member; EnumMembers!AnsiSgrStyle)
1688                     {
1689                         static if(member != AnsiSgrStyle.none)
1690                         {
1691                             case cast(int)member:
1692                                 styleCopy.style = styleCopy.style.set(member, true);
1693                                 break Switch;
1694                         }
1695                     }
1696 
1697                     // Set foreground to a 4-bit colour.
1698                     case 30:..case 37:
1699                     case 90:..case 97:
1700                         styleCopy.fg = cast(Ansi4BitColour)commandAsNum;
1701                         break;
1702 
1703                     // Set background to a 4-bit colour.
1704                     case 40:..case 47:
1705                     case 100:..case 107:
1706                         styleCopy.bg = cast(Ansi4BitColour)(commandAsNum - ANSI_FG_TO_BG_INCREMENT); // Since we work in the foreground colour until we're outputting to sequences.
1707                         break;
1708                     
1709                     // Set foreground (38) or background (48) to an 8-bit (5) or 24-bit (2) colour.
1710                     case 38:
1711                     case 48:
1712                         if(result == ReadResult.foundEndMarker)
1713                             return "Incomplete 'set foreground/background' command, expected another parameter, got none.";
1714                         charsRead++; // Skip semi-colon.
1715 
1716                         result = readToSemiColonOrEndMarker(generalSlice);
1717                         if(result != ReadResult.foundEndMarker && result != ReadResult.foundSemiColon)
1718                             return resultToString(result);
1719                         if(result == ReadResult.foundSemiColon)
1720                             charsRead++;
1721 
1722                         const subcommand = toValue(generalSlice);
1723                         if(subcommand == 5)
1724                         {
1725                             result = readToSemiColonOrEndMarker(generalSlice);
1726                             if(result != ReadResult.foundEndMarker && result != ReadResult.foundSemiColon)
1727                                 return resultToString(result);
1728                             if(result == ReadResult.foundSemiColon)
1729                                 charsRead++;
1730 
1731                             if(commandAsNum == 38) styleCopy.fg = cast(Ansi8BitColour)toValue(generalSlice);
1732                             else                   styleCopy.bg = cast(Ansi8BitColour)toValue(generalSlice);
1733                         }
1734                         else if(subcommand == 2)
1735                         {
1736                             ubyte[3] components;
1737                             foreach(i; 0..3)
1738                             {
1739                                 result = readToSemiColonOrEndMarker(generalSlice);
1740                                 if(result != ReadResult.foundEndMarker && result != ReadResult.foundSemiColon)
1741                                     return resultToString(result);
1742                                 if(result == ReadResult.foundSemiColon)
1743                                     charsRead++;
1744 
1745                                 components[i] = cast(ubyte)toValue(generalSlice);
1746                             }
1747 
1748                             if(commandAsNum == 38) styleCopy.fg = AnsiRgbColour(components);
1749                             else                   styleCopy.bg = AnsiRgbColour(components);
1750                         }
1751                         else
1752                             break; // Assume it's a valid command, just that we don't support this specific sub command.
1753                         break;
1754 
1755                     default: continue; // Assume it's just a command we don't support.
1756                 }
1757                 break;
1758 
1759             case 'm':
1760                 charsRead++;
1761                 style = styleCopy;
1762                 return null;
1763 
1764             case ';': charsRead++; continue;
1765             default: return null; // Assume we've hit an end-marker we don't support.
1766         }
1767     }
1768 
1769     return "Input did not contain an end marker.";
1770 }
1771 ///
1772 @("ansiExecuteSgrSequence")
1773 unittest
1774 {
1775     static if(!BetterC)
1776     {
1777         import std.conv : to;
1778         import std.traits : EnumMembers;
1779 
1780         void test(AnsiStyleSet sourceAndExpected)
1781         {
1782             char[AnsiStyleSet.MAX_CHARS_NEEDED] buffer;
1783             const sequence = ANSI_CSI~sourceAndExpected.toSequence(buffer)~ANSI_COLOUR_END;
1784 
1785             AnsiStyleSet got;
1786             size_t charsRead;
1787             const error = ansiExecuteSgrSequence(sequence, got, charsRead);
1788             if(error !is null)
1789                 assert(false, error);
1790 
1791             assert(charsRead == sequence.length, "Read "~charsRead.to!string~" not "~sequence.length.to!string);
1792             assert(sourceAndExpected == got, "Expected "~sourceAndExpected.to!string~" got "~got.to!string);
1793         }
1794 
1795         test(AnsiStyleSet.init.fg(Ansi4BitColour.green));
1796         test(AnsiStyleSet.init.fg(Ansi4BitColour.brightGreen));
1797         test(AnsiStyleSet.init.bg(Ansi4BitColour.green));
1798         test(AnsiStyleSet.init.bg(Ansi4BitColour.brightGreen));
1799         test(AnsiStyleSet.init.fg(Ansi4BitColour.green).bg(Ansi4BitColour.brightRed));
1800 
1801         test(AnsiStyleSet.init.fg(20));
1802         test(AnsiStyleSet.init.bg(40));
1803         test(AnsiStyleSet.init.fg(20).bg(40));
1804 
1805         test(AnsiStyleSet.init.fg(AnsiRgbColour(255, 128, 64)));
1806         test(AnsiStyleSet.init.bg(AnsiRgbColour(255, 128, 64)));
1807         test(AnsiStyleSet.init.fg(AnsiRgbColour(255, 128, 64)).bg(AnsiRgbColour(64, 128, 255)));
1808         
1809         static foreach(member; EnumMembers!AnsiSgrStyle)
1810         static if(member != AnsiSgrStyle.none)
1811             test(AnsiStyleSet.init.style(AnsiStyle.init.set(member, true)));
1812 
1813         test(AnsiStyleSet.init.style(AnsiStyle.init.bold.underline.slowBlink.italic));
1814     }
1815 }
1816 
1817 /++
1818  + The resulting object from `AnsiSectionRange`, describes whether a slice of text is an ANSI sequence or not.
1819  + ++/
1820 struct AnsiSection
1821 {
1822     /// `true` if the slice is an ANSI sequence, `false` if it's just text.
1823     bool isAnsiSequence;
1824 
1825     /// The slice of text that this section consists of.
1826     const(char)[] slice;
1827 }
1828 
1829 /++
1830  + An input range of `AnsiSection`s that splits a piece of text up into ANSI sequence and plain text sections.
1831  +
1832  + For example, the text "\033[37mABC\033[0m" has three sections: [ANSI "\033[37m", TEXT "ABC", ANSI "\033[0m"].
1833  + ++/
1834 struct AnsiSectionRange
1835 {
1836     private
1837     {
1838         const(char)[] _input;
1839         size_t        _cursor;
1840         AnsiSection   _front;
1841         bool          _empty = true; // So .init.empty is true
1842     }
1843     
1844     @safe @nogc nothrow pure:
1845 
1846     ///
1847     this(const(char)[] input)
1848     {
1849         this._input = input;
1850         this._empty = false;
1851         this.popFront();
1852     }
1853 
1854     ///
1855     bool empty() const
1856     {
1857         return this._empty;
1858     }
1859     
1860     ///
1861     AnsiSection front() const
1862     {
1863         return this._front;
1864     }
1865 
1866     ///
1867     void popFront()
1868     {
1869         assert(!this.empty, "Cannot pop empty range.");
1870 
1871         if(this._cursor >= this._input.length)
1872         {
1873             this._empty = true;
1874             return;
1875         }
1876 
1877         if((this._input.length - this._cursor) >= ANSI_CSI.length 
1878         && this._input[this._cursor..this._cursor + ANSI_CSI.length] == ANSI_CSI)
1879             this.readSequence();
1880         else
1881             this.readText();
1882     }
1883 
1884     private void readText()
1885     {
1886         const start = this._cursor;
1887         
1888         while(this._cursor < this._input.length)
1889         {
1890             if((this._input.length - this._cursor) >= 2 && this._input[this._cursor..this._cursor+2] == ANSI_CSI)
1891                 break;
1892 
1893             this._cursor++;
1894         }
1895 
1896         this._front.isAnsiSequence = false;
1897         this._front.slice = this._input[start..this._cursor];
1898     }
1899 
1900     private void readSequence()
1901     {
1902         const start = this._cursor;
1903         this._cursor += ANSI_CSI.length; // Already validated by popFront.
1904 
1905         while(this._cursor < this._input.length 
1906            && this.isValidAnsiChar(this._input[this._cursor]))
1907            this._cursor++;
1908 
1909         if(this._cursor < this._input.length)
1910             this._cursor++; // We've hit a non-ansi character, so we increment to include it in the output.
1911 
1912         this._front.isAnsiSequence = true;
1913         this._front.slice = this._input[start..this._cursor];
1914     }
1915 
1916     private bool isValidAnsiChar(char ch)
1917     {
1918         return (
1919             (ch >= '0' && ch <= '9')
1920          || ch == ';'
1921         );
1922     }
1923 }
1924 ///
1925 @("AnsiSectionRange")
1926 unittest
1927 {
1928     assert(AnsiSectionRange.init.empty);
1929     assert("".asAnsiSections.empty);
1930 
1931     auto r = "No Ansi".asAnsiSections;
1932     assert(!r.empty);
1933     assert(!r.front.isAnsiSequence);
1934     assert(r.front.slice == "No Ansi");
1935 
1936     r = "\033[m".asAnsiSections;
1937     assert(!r.empty);
1938     assert(r.front.isAnsiSequence);
1939     assert(r.front.slice == "\033[m");
1940 
1941     r = "\033[38;2;255;128;64;1;4;48;5;2m".asAnsiSections;
1942     assert(!r.empty);
1943     assert(r.front.isAnsiSequence);
1944     assert(r.front.slice == "\033[38;2;255;128;64;1;4;48;5;2m");
1945 
1946     r = "\033[mABC\033[m".asAnsiSections;
1947     assert(r.front.isAnsiSequence);
1948     assert(r.front.slice == "\033[m", r.front.slice);
1949     r.popFront();
1950     assert(!r.empty);
1951     assert(!r.front.isAnsiSequence);
1952     assert(r.front.slice == "ABC", r.front.slice);
1953     r.popFront();
1954     assert(!r.empty);
1955     assert(r.front.isAnsiSequence);
1956     assert(r.front.slice == "\033[m");
1957     r.popFront();
1958     assert(r.empty);
1959 
1960     r = "ABC\033[mDEF".asAnsiSections;
1961     assert(!r.front.isAnsiSequence);
1962     assert(r.front.slice == "ABC");
1963     r.popFront();
1964     assert(r.front.isAnsiSequence);
1965     assert(r.front.slice == "\033[m");
1966     r.popFront();
1967     assert(!r.front.isAnsiSequence);
1968     assert(r.front.slice == "DEF");
1969     r.popFront();
1970     assert(r.empty);
1971 }
1972 
1973 /+++ PUBLIC HELPERS +++/
1974 
1975 /// Determines if `CT` is a valid RGB data type.
1976 enum isUserDefinedRgbType(CT) =
1977 (
1978     __traits(hasMember, CT, "r")
1979  && __traits(hasMember, CT, "g")
1980  && __traits(hasMember, CT, "b")
1981 );
1982 
1983 /++
1984  + Converts any suitable data type into an `AnsiColour`.
1985  +
1986  + Params:
1987  +  colour = The colour to convert.
1988  +
1989  + Returns:
1990  +  An `AnsiColour` created from the given `colour`.
1991  +
1992  + See_Also:
1993  +  `isUserDefinedRgbType`
1994  + ++/
1995 AnsiColour to(T : AnsiColour, CT)(CT colour)
1996 if(isUserDefinedRgbType!CT)
1997 {
1998     return AnsiColour(colour.r, colour.g, colour.b);
1999 }
2000 ///
2001 @("to!AnsiColour(User defined)")
2002 @safe @nogc nothrow pure
2003 unittest
2004 {
2005     static struct RGB
2006     {
2007         ubyte r;
2008         ubyte g;
2009         ubyte b;
2010     }
2011 
2012     assert(RGB(255, 128, 64).to!AnsiColour == AnsiColour(255, 128, 64));
2013 }
2014 
2015 /// ditto.
2016 AnsiColour toBg(T)(T c)
2017 {
2018     auto colour = to!AnsiColour(c);
2019     colour.isBg = IsBgColour.yes;
2020     return colour;
2021 }
2022 ///
2023 @("toBg")
2024 @safe @nogc nothrow pure
2025 unittest
2026 {
2027     static struct RGB
2028     {
2029         ubyte r;
2030         ubyte g;
2031         ubyte b;
2032     }
2033 
2034     assert(RGB(255, 128, 64).toBg == AnsiColour(255, 128, 64, IsBgColour.yes));
2035 }
2036 
2037 /++
2038  + Creates an `AnsiTextLite` from the given `text`. This function is mostly used when using
2039  + the fluent UFCS chaining pattern.
2040  +
2041  + Params:
2042  +  text = The text to use.
2043  +
2044  + Returns:
2045  +  An `AnsiTextLite` from the given `text`.
2046  + ++/
2047 @safe @nogc
2048 AnsiTextLite ansi(const(char)[] text) nothrow pure
2049 {
2050     return AnsiTextLite(text);
2051 }
2052 ///
2053 @("ansi")
2054 unittest
2055 {
2056     version(none)
2057     {
2058         import std.stdio;
2059         writeln("Hello, World!".ansi
2060                                .fg(Ansi4BitColour.red)
2061                                .bg(AnsiRgbColour(128, 128, 128))
2062                                .style(AnsiStyle.init.bold.underline)
2063         );
2064     }
2065 }
2066 
2067 /++
2068  + Constructs an `AnsiSectionRange` from the given `slice`.
2069  + ++/
2070 @safe @nogc
2071 AnsiSectionRange asAnsiSections(const(char)[] slice) nothrow pure
2072 {
2073     return AnsiSectionRange(slice);
2074 }
2075 
2076 /++
2077  + Enables ANSI support on windows via `SetConsoleMode`. This function is no-op on non-Windows platforms.
2078  + ++/
2079 void ansiEnableWindowsSupport() @nogc nothrow
2080 {
2081     version(Windows)
2082     {
2083         import core.sys.windows.windows : HANDLE, DWORD, GetStdHandle, STD_OUTPUT_HANDLE, GetConsoleMode, SetConsoleMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING;
2084         HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2085         DWORD mode = 0;
2086         GetConsoleMode(stdOut, &mode);
2087         mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
2088         SetConsoleMode(stdOut, mode);
2089     }
2090 }
2091 
2092 /+++ PRIVATE HELPERS +++/
2093 private char[] numToStrBase10(NumT)(char[] buffer, NumT num)
2094 {
2095     if(num == 0)
2096     {
2097         if(buffer.length > 0)
2098         {
2099             buffer[0] = '0';
2100             return buffer[0..1];
2101         }
2102         else
2103             return null;
2104     }
2105 
2106     const CHARS = "0123456789";
2107 
2108     ptrdiff_t i = buffer.length;
2109     while(i > 0 && num > 0)
2110     {
2111         buffer[--i] = CHARS[num % 10];
2112         num /= 10;
2113     }
2114 
2115     return buffer[i..$];
2116 }
2117 ///
2118 @("numToStrBase10")
2119 unittest
2120 {
2121     char[2] b;
2122     assert(numToStrBase10(b, 32) == "32");
2123 }
2124 
2125 private NumT strToNum(NumT)(const(char)[] slice)
2126 {
2127     NumT num;
2128 
2129     foreach(i, ch; slice)
2130     {
2131         const exponent = slice.length - (i + 1);
2132         const tens     = 10 ^^ exponent;
2133         const chNum    = cast(NumT)(ch - '0');
2134 
2135         if(tens == 0)
2136             num += chNum;
2137         else
2138             num += chNum * tens;
2139     }
2140 
2141     return num;
2142 }
2143 ///
2144 @("strToNum")
2145 unittest
2146 {
2147     assert("1".strToNum!int == 1);
2148     assert("11".strToNum!int == 11);
2149     assert("901".strToNum!int == 901);
2150 }