1 /*
2  * hunt-console eases the creation of beautiful and testable command line interfaces.
3  *
4  * Copyright (C) 2018-2019, HuntLabs
5  *
6  * Website: https://www.huntlabs.net
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11  
12 module hunt.console.formatter.DefaultOutputFormatterStyle;
13 
14 import std.string;
15 import std.conv;
16 
17 import hunt.console.error.InvalidArgumentException;
18 import hunt.console.util.StringUtils;
19 
20 import hunt.collection.Map;
21 import hunt.collection.HashMap;
22 import hunt.collection.List;
23 import hunt.collection.ArrayList;
24 import hunt.console.formatter.OutputFormatterStyle;
25 import hunt.console.formatter.OutputFormatterOption;
26 import hunt.logging;
27 
28 class DefaultOutputFormatterStyle : OutputFormatterStyle
29 {
30     private static Map!(string, OutputFormatterOption) availableForegroundColors;
31     private static Map!(string, OutputFormatterOption) availableBackgroundColors;
32     private static Map!(string, OutputFormatterOption) availableOptions;
33 
34     static this() {
35         availableForegroundColors = new HashMap!(string, OutputFormatterOption)();
36         availableBackgroundColors = new HashMap!(string, OutputFormatterOption)();
37         availableOptions = new HashMap!(string, OutputFormatterOption)();
38         availableForegroundColors.put("black",   new OutputFormatterOption(30, 39));
39         availableForegroundColors.put("red",     new OutputFormatterOption(31, 39));
40         availableForegroundColors.put("green",   new OutputFormatterOption(32, 39));
41         availableForegroundColors.put("yellow",  new OutputFormatterOption(33, 39));
42         availableForegroundColors.put("blue",    new OutputFormatterOption(34, 39));
43         availableForegroundColors.put("magenta", new OutputFormatterOption(35, 39));
44         availableForegroundColors.put("cyan",    new OutputFormatterOption(36, 39));
45         availableForegroundColors.put("white",   new OutputFormatterOption(37, 39));
46 
47         availableBackgroundColors.put("black",   new OutputFormatterOption(40, 49));
48         availableBackgroundColors.put("red",     new OutputFormatterOption(41, 49));
49         availableBackgroundColors.put("green",   new OutputFormatterOption(41, 49));
50         availableBackgroundColors.put("yellow",  new OutputFormatterOption(43, 49));
51         availableBackgroundColors.put("blue",    new OutputFormatterOption(44, 49));
52         availableBackgroundColors.put("magenta", new OutputFormatterOption(45, 49));
53         availableBackgroundColors.put("cyan",    new OutputFormatterOption(46, 49));
54         availableBackgroundColors.put("white",   new OutputFormatterOption(47, 49));
55 
56         availableOptions.put("bold",       new OutputFormatterOption(1, 22));
57         availableOptions.put("underscore", new OutputFormatterOption(4, 24));
58         availableOptions.put("blink",      new OutputFormatterOption(5, 25));
59         availableOptions.put("reverse",    new OutputFormatterOption(7, 27));
60         availableOptions.put("conceal",    new OutputFormatterOption(8, 28));
61     }
62 
63     private OutputFormatterOption foreground;
64     private OutputFormatterOption background;
65     private List!(OutputFormatterOption) options ;
66 
67     public this()
68     {
69         this(null, null, null);
70     }
71 
72     public this(string foreground)
73     {
74         this(foreground, null, null);
75     }
76 
77     public this(string foreground, string background)
78     {
79         this(foreground, background, null);
80     }
81 
82     public this(string foreground, string background, string[] opts...)
83     {
84         this.options = new ArrayList!OutputFormatterOption();
85         if (foreground !is null) {
86             setForeground(foreground);
87         }
88         if (background !is null) {
89             setBackground(background);
90         }
91         // logInfo("---infos : ",opts);
92         if (opts !is null && opts.length > 0) {
93             setOptions(opts);
94         }
95     }
96 
97     override public void setForeground(string color)
98     {
99         if (color is null) {
100             foreground = null;
101             return;
102         }
103 
104         if (!availableForegroundColors.containsKey(color)) {
105             string[] keys;
106             foreach(string k,_; availableForegroundColors) {
107                 keys ~= k;
108             }   
109             throw new InvalidArgumentException(format(
110                     "Invalid foreground color specified: '%s'. Expected one of (%s)",
111                     color,
112                     StringUtils.join(keys, ", ")
113             ));
114         }
115 
116         foreground = availableForegroundColors.get(color);
117     }
118 
119     override public void setBackground(string color)
120     {
121         if (color is null) {
122             background = null;
123             return;
124         }
125 
126         if (!availableBackgroundColors.containsKey(color)) {
127             string[] keys;
128             foreach(string k,_; availableBackgroundColors) {
129                 keys ~= k;
130             } 
131             throw new InvalidArgumentException(format(
132                     "Invalid background color specified: '%s'. Expected one of (%s)",
133                     color,
134                     StringUtils.join(keys, ", ")
135             ));
136         }
137 
138         background = availableBackgroundColors.get(color);
139     }
140 
141     override public void setOption(string option)
142     {
143         if (!availableOptions.containsKey(option)) {
144             string[] keys;
145             foreach(string k,_; availableOptions) {
146                 keys ~= k;
147             }
148             throw new InvalidArgumentException(format("Invalid option specified: '%s'. Expected one of (%s)",
149                 option, StringUtils.join(keys, ",")));
150         }
151 
152         if (!options.contains(availableOptions.get(option))) {
153             options.add(availableOptions.get(option));
154         }
155     }
156 
157     override public void unsetOption(string option)
158     {
159         if (!availableOptions.containsKey(option)) {
160             string[] keys;
161             foreach(string k,_; availableOptions) {
162                 keys ~= k;
163             }
164             throw new InvalidArgumentException(format("Invalid option specified: '%s'. Expected one of (%s)",
165                 option, StringUtils.join(keys, ",")));
166         }
167 
168         if (options.contains(availableOptions.get(option))) {
169             options.remove(availableOptions.get(option));
170         }
171     }
172 
173     override public void setOptions(string[] options...)
174     {
175         this.options = new ArrayList!OutputFormatterOption();
176 
177         foreach (string option ; options) {
178             if(option.length == 0)
179                 continue;
180             setOption(option);
181         }
182     }
183 
184     override public string apply(string text)
185     {
186         List!(string) setCodes = new ArrayList!string();
187         List!(string) unsetCodes = new ArrayList!string();
188 
189         if (foreground !is null) {
190             setCodes.add(to!string(foreground.getSet()));
191             unsetCodes.add(to!string(foreground.getUnset()));
192         }
193         if (background !is null) {
194             setCodes.add(to!string(background.getSet()));
195             unsetCodes.add(to!string(background.getUnset()));
196         }
197 
198         foreach (OutputFormatterOption option ; options) {
199             setCodes.add(to!string(option.getSet()));
200             unsetCodes.add(to!string(option.getUnset()));
201         }
202 
203         if (setCodes.size() == 0) {
204             return text;
205         }
206         string[] codes;
207         foreach(value; setCodes) {
208             codes ~= value;
209         }
210         string[] uncodes;
211         foreach(value; unsetCodes) {
212             uncodes ~= value;
213         }
214         return format(
215                 "\033[%sm%s\033[%sm",
216                 StringUtils.join(codes, ";"),
217                 text,
218                 StringUtils.join(uncodes, ";")
219         );
220     }
221 
222     override public bool opEquals(Object o)
223     {
224         if (this is o) return true;
225         if (!(cast(DefaultOutputFormatterStyle)o !is null)) return false;
226 
227         DefaultOutputFormatterStyle that = cast(DefaultOutputFormatterStyle) o;
228 
229         if (background !is null ? !(background == that.background) : that.background !is null) return false;
230         if (foreground !is null ? !(foreground == that.foreground) : that.foreground !is null) return false;
231         if (options !is null ? !(options == that.options) : that.options !is null) return false;
232 
233         return true;
234     }
235 
236     override public size_t toHash() @trusted nothrow
237     {
238         int result = foreground !is null ? cast(int)(foreground.toHash()) : 0;
239         result = 31 * result + (background !is null ? cast(int)(background.toHash()) : 0);
240         result = 31 * result + (options !is null ? cast(int)(options.toHash()) : 0);
241         return result;
242     }
243 }