1 module colored;
2 
3 import std.string;
4 
5 enum AnsiColor
6 {
7     black = 30,
8     red = 31,
9     green = 32,
10     yellow = 33,
11     blue = 34,
12     magenta = 35,
13     cyan = 36,
14     lightGray = 37,
15     defaultColor = 39,
16     darkGray = 90,
17     lightRed = 91,
18     lightGreen = 92,
19     lightYellow = 93,
20     lightBlue = 94,
21     lightMagenta = 95,
22     lightCyan = 96,
23     white = 97
24 }
25 
26 enum Style
27 {
28     bold = 1,
29     dim = 2,
30     underlined = 4,
31     blink = 5,
32     reverse = 7,
33     hidden = 8
34 }
35 
36 struct StyledString
37 {
38     string s;
39     int[] befores;
40     int[] afters;
41     this(string s)
42     {
43         this.s = s;
44     }
45 
46     StyledString addStyle(int before, int after)
47     {
48         befores ~= before;
49         afters ~= after;
50         return this;
51     }
52 
53     string toString()
54     {
55         import std.algorithm;
56 
57         auto prefix = befores.map!(a => "\033[%dm".format(a)).join("");
58         auto suffix = afters.map!(a => "\033[%dm".format(a)).join("");
59         return "%s%s%s".format(prefix, s, suffix);
60     }
61 }
62 
63 @("styledstring") unittest
64 {
65     import unit_threaded;
66     import std.stdio;
67     import std.traits;
68 
69     foreach (immutable color; [EnumMembers!AnsiColor])
70     {
71         auto colorName = "%s".format(color);
72         writeln(StyledString(colorName).addStyle(color, 0));
73     }
74     foreach (immutable color; [EnumMembers!AnsiColor])
75     {
76         auto colorName = "bg%s".format(color);
77         writeln(StyledString(colorName).addStyle(color + 10, 0));
78     }
79     foreach (immutable style; [EnumMembers!Style])
80     {
81         auto styleName = "%s".format(style);
82         writeln(StyledString(styleName).addStyle(style, style + 20));
83     }
84 
85     writeln(StyledString("test").addStyle(AnsiColor.red, 0)
86             .addStyle(Style.underlined, Style.underlined + 20));
87 }
88 
89 auto colorMixin(T)()
90 {
91     import std.traits;
92 
93     string res = "";
94     foreach (immutable color; [EnumMembers!T])
95     {
96         auto t = typeof(T.init).stringof;
97         auto c = "%s".format(color);
98         res ~= "auto %1$s(string s) { return StyledString(s).addStyle(%2$s.%1$s, 0); }\n".format(c,
99                 t);
100         res ~= "auto %1$s(StyledString s) { return s.addStyle(%2$s.%1$s, 0); }\n".format(c, t);
101         string name = c[0 .. 1].toUpper ~ c[1 .. $];
102         res ~= "auto on%3$s(string s) { return StyledString(s).addStyle(%2$s.%1$s+10, 0); }\n".format(c,
103                 t, name);
104         res ~= "auto on%3$s(StyledString s) { return s.addStyle(%2$s.%1$s+10, 0); }\n".format(c,
105                 t, name);
106     }
107     return res;
108 }
109 
110 auto styleMixin(T)()
111 {
112     import std.traits;
113 
114     string res = "";
115     foreach (immutable style; [EnumMembers!T])
116     {
117         auto t = typeof(T.init).stringof;
118         auto s = "%s".format(style);
119         res ~= "auto %1$s(string s) { return StyledString(s).addStyle(%2$s.%1$s, %2$s.%1$s+20); }\n".format(s,
120                 t);
121         res ~= "auto %1$s(StyledString s) { return s.addStyle(%2$s.%1$s, %2$s.%1$s+20); }\n".format(s,
122                 t);
123     }
124     return res;
125 }
126 
127 mixin(colorMixin!AnsiColor);
128 mixin(styleMixin!Style);
129 
130 @("api") unittest
131 {
132     import std.stdio;
133 
134     writeln("red".red.onGreen);
135     writeln("red".red.onYellow.bold.underlined);
136 }