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