1 /++ 2 + Simple coloring module for strings 3 + 4 + Copyright: Copyright © 2017, Christian Köstlin 5 + Authors: Christian Koestlin 6 + License: MIT 7 +/ 8 module colored; 9 10 import std.string; 11 12 public import colored.packageversion; 13 14 /// Available Colors 15 enum AnsiColor 16 { 17 black = 30, 18 red = 31, 19 green = 32, 20 yellow = 33, 21 blue = 34, 22 magenta = 35, 23 cyan = 36, 24 lightGray = 37, 25 defaultColor = 39, 26 darkGray = 90, 27 lightRed = 91, 28 lightGreen = 92, 29 lightYellow = 93, 30 lightBlue = 94, 31 lightMagenta = 95, 32 lightCyan = 96, 33 white = 97 34 } 35 36 /// Available Styles 37 enum Style 38 { 39 bold = 1, 40 dim = 2, 41 underlined = 4, 42 blink = 5, 43 reverse = 7, 44 hidden = 8 45 } 46 47 /// Internal structure to style a string 48 struct StyledString 49 { 50 public string unformatted; 51 private int[] befores; 52 private int[] afters; 53 public this(string unformatted) 54 { 55 this.unformatted = unformatted; 56 } 57 58 private StyledString addPair(int before, int after) 59 { 60 befores ~= before; 61 afters ~= after; 62 return this; 63 } 64 65 StyledString setForeground(int color) 66 { 67 return addPair(color, 0); 68 } 69 70 StyledString setBackground(int color) 71 { 72 return addPair(color + 10, 0); 73 } 74 75 StyledString addStyle(int style) 76 { 77 return addPair(style, 0); 78 } 79 80 string toString() @safe 81 { 82 import std.algorithm; 83 84 auto prefix = befores.map!(a => "\033[%dm".format(a)).join(""); 85 auto suffix = afters.map!(a => "\033[%dm".format(a)).join(""); 86 return "%s%s%s".format(prefix, unformatted, suffix); 87 } 88 } 89 90 @("styledstring") unittest 91 { 92 import unit_threaded; 93 import std.stdio; 94 import std.traits; 95 96 foreach (immutable color; [EnumMembers!AnsiColor]) 97 { 98 auto colorName = "%s".format(color); 99 writeln(StyledString(colorName).setForeground(color)); 100 } 101 foreach (immutable color; [EnumMembers!AnsiColor]) 102 { 103 auto colorName = "bg%s".format(color); 104 writeln(StyledString(colorName).setBackground(color)); 105 } 106 foreach (immutable style; [EnumMembers!Style]) 107 { 108 auto styleName = "%s".format(style); 109 writeln(StyledString(styleName).addStyle(style)); 110 } 111 } 112 113 auto colorMixin(T)() 114 { 115 import std.traits; 116 117 string res = ""; 118 foreach (immutable color; [EnumMembers!T]) 119 { 120 auto t = typeof(T.init).stringof; 121 auto c = "%s".format(color); 122 res ~= "auto %1$s(string s) { return StyledString(s).setForeground(%2$s.%1$s); }\n".format(c, 123 t); 124 res ~= "auto %1$s(StyledString s) { return s.setForeground(%2$s.%1$s); }\n".format(c, t); 125 string name = c[0 .. 1].toUpper ~ c[1 .. $]; 126 res ~= "auto on%3$s(string s) { return StyledString(s).setBackground(%2$s.%1$s); }\n".format(c, 127 t, name); 128 res ~= "auto on%3$s(StyledString s) { return s.setBackground(%2$s.%1$s); }\n".format(c, 129 t, name); 130 } 131 return res; 132 } 133 134 auto styleMixin(T)() 135 { 136 import std.traits; 137 138 string res = ""; 139 foreach (immutable style; [EnumMembers!T]) 140 { 141 auto t = typeof(T.init).stringof; 142 auto s = "%s".format(style); 143 res ~= "auto %1$s(string s) { return StyledString(s).addStyle(%2$s.%1$s); }\n".format(s, t); 144 res ~= "auto %1$s(StyledString s) { return s.addStyle(%2$s.%1$s); }\n".format(s, t); 145 } 146 return res; 147 } 148 149 mixin(colorMixin!AnsiColor); 150 mixin(styleMixin!Style); 151 152 @("api") unittest 153 { 154 import std.stdio; 155 156 "redOnGreen".red.onGreen.writeln; 157 "redOnYellowBoldUnderlined".red.onYellow.bold.underlined.writeln; 158 "bold".bold.writeln; 159 "test".writeln; 160 } 161 162 /// Calculate length of string excluding all formatting escapes 163 ulong unformattedLength(string s) 164 { 165 enum State 166 { 167 NORMAL, 168 ESCAPED, 169 } 170 171 auto state = State.NORMAL; 172 ulong count = 0; 173 foreach (c; s) 174 { 175 switch (state) 176 { 177 case State.NORMAL: 178 if (c == 0x1b) 179 { 180 state = State.ESCAPED; 181 } 182 else 183 { 184 count++; 185 } 186 break; 187 case State.ESCAPED: 188 if (c == 'm') 189 { 190 state = State.NORMAL; 191 } 192 break; 193 default: 194 throw new Exception("Illegal state"); 195 } 196 } 197 return count; 198 } 199 200 auto leftJustifyFormattedString(string s, ulong width, dchar fillChar = ' ') 201 { 202 auto res = s; 203 auto currentWidth = s.unformattedLength; 204 for (auto i = currentWidth; i < width; ++i) 205 { 206 res ~= fillChar; 207 } 208 return res; 209 }