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.descriptor.TextDescriptor;
13 
14 import std.string;
15 import std.conv;
16 import hunt.console.Console;
17 import hunt.console.command.Command;
18 import hunt.console.input.InputArgument;
19 import hunt.console.input.InputDefinition;
20 import hunt.console.input.InputOption;
21 import hunt.console.util.StringUtils;
22 
23 import hunt.collection.List;
24 import hunt.collection.Map;
25 import hunt.math.Helper;
26 import hunt.Integer;
27 import hunt.Boolean;
28 import hunt.console.descriptor.AbstractDescriptor;
29 import hunt.console.descriptor.DescriptorOptions;
30 import hunt.console.descriptor.ConsoleDescription;
31 
32 class TextDescriptor : AbstractDescriptor
33 {
34     override protected void describeInputArgument(InputArgument argument, DescriptorOptions options)
35     {
36         string defaultValue = "";
37         if (argument.getDefaultValue() !is null) {
38             defaultValue = format("<comment> (default: %s)</comment>", argument.getDefaultValue());
39         }
40 
41         int nameWidth = options.has("name_width") ? Integer.valueOf(options.get("name_width")).intValue() : cast(int)(argument.getName().length);
42 
43         writeText(format(" <info>%-" ~ nameWidth.to!string ~ "s</info> %s%s",
44                 argument.getName(),
45                 argument.getDescription() is null ? "" : argument.getDescription().replace("\\n", "\\n" ~ StringUtils.repeat(" ", nameWidth + 2)),
46                 defaultValue is null ? "" : defaultValue
47         ), options);
48     }
49 
50     override protected void describeInputOption(InputOption option, DescriptorOptions options)
51     {
52         string defaultValue = "";
53         if (option.getDefaultValue() !is null) {
54             defaultValue = format("<comment> (default: %s)</comment>", option.getDefaultValue());
55         }
56 
57         int nameWidth = options.has("name_width") ? Integer.valueOf(options.get("name_width")).intValue() : cast(int)(option.getName().length);
58         int nameWithShortcutWidth = nameWidth - cast(int)(option.getName().length) - 2;
59 
60         writeText(format(" <info>%s</info> %-" ~ nameWithShortcutWidth.to!string ~ "s%s%s%s",
61                 "--" ~ option.getName(),
62                 option.getShortcut() is null ? "" : format("(-%s) ", option.getShortcut()),
63                 option.getDescription().replace("\\n", "\\n" ~ StringUtils.repeat(" ", nameWidth + 2)),
64                 defaultValue,
65                 option.isArray() ? "<comment> (multiple values allowed)</comment>": ""
66         ), options);
67     }
68 
69     override protected void describeInputDefinition(InputDefinition definition, DescriptorOptions options)
70     {
71         int nameWidth = 0;
72         int nameLength;
73         foreach (InputOption option ; definition.getOptions()) {
74             nameLength = cast(int)(option.getName().length) + 2;
75             if (option.getShortcut() !is null) {
76                 nameLength += cast(int)(option.getShortcut().length) + 3;
77             }
78             nameWidth = MathHelper.max(nameWidth, nameLength);
79         }
80         foreach (InputArgument argument ; definition.getArguments()) {
81             nameWidth = MathHelper.max(nameWidth, cast(int)(argument.getName().length));
82         }
83         ++nameWidth;
84 
85         if (definition.getArgumentCount() > 0) {
86             writeText("<comment>Arguments:</comment>", options);
87             writeNewline();
88             foreach (InputArgument argument ; definition.getArguments()) {
89                 describeInputArgument(argument, (new DescriptorOptions()).set("name_width", nameWidth.to!string));
90                 writeNewline();
91             }
92             if (definition.getOptions().size() > 0) {
93                 writeNewline();
94             }
95         }
96 
97         if (definition.getOptions().size() > 0) {
98             writeText("<comment>Options:</comment>", options);
99             writeNewline();
100             foreach (InputOption option ; definition.getOptions()) {
101                 describeInputOption(option, (new DescriptorOptions()).set("name_width", nameWidth.to!string));
102                 writeNewline();
103             }
104         }
105     }
106 
107     override protected void describeCommand(Command command, DescriptorOptions options)
108     {
109         command.getSynopsis();
110         command.mergeConsoleDefinition();
111 
112         writeText("<comment>Usage:</comment>", options);
113         writeNewline();
114         writeText(" " ~ command.getSynopsis(), options);
115         writeNewline();
116 
117         if (command.getAliases().length > 0) {
118             writeNewline();
119             writeText("<comment>Aliases:</comment> <info>" ~ StringUtils.join(command.getAliases(), ", ") ~ "</info>", options);
120         }
121 
122         InputDefinition definition = command.getNativeDefinition();
123         if (definition !is null) {
124             writeNewline();
125             describeInputDefinition(definition, options);
126         }
127 
128         writeNewline();
129 
130         string help = command.getProcessedHelp();
131         if (help !is null && !(help.length == 0)) {
132             writeText("<comment>Help:</comment>", options);
133             writeNewline();
134             writeText(" " ~ help.replace("\\n", "\\\n "), options);
135             writeNewline();
136         }
137     }
138 
139     override protected void describeConsole(Console application, DescriptorOptions options)
140     {
141         string describedNamespace = options.has("namespace") ? options.get("namespace") : null;
142         ConsoleDescription description = new ConsoleDescription(application, describedNamespace);
143 
144         int width = getColumnWidth(description.getCommands());
145 
146         if (options.has("raw_text") && Boolean.parseBoolean(options.get("raw_text"))) {
147             foreach (Command command ; description.getCommands().values()) {
148                 writeText(format("%-" ~ width.to!string ~ "s %s", command.getName(), command.getDescription()), options);
149                 writeNewline();
150             }
151         } else {
152             writeText(application.getHelp(), options);
153             writeNewline();
154 
155             if (describedNamespace !is null) {
156                 writeText(format("<comment>Available commands for the '%s' namespace:</comment>", describedNamespace), options);
157             } else {
158                 writeText("<comment>Available commands:</comment>", options);
159             }
160 
161             // add commands by namespace
162             foreach (string k,List!(string) v ; description.getNamespaces()) {
163 
164                 if (describedNamespace is null && !(ConsoleDescription.GLOBAL_NAMESPACE == k)) {
165                     writeNewline();
166                     writeText("<comment>" ~ k ~ "</comment>", options);
167                 }
168 
169                 foreach (string name ; v) {
170                     writeNewline();
171                     writeText(format("  <info>%-" ~ width.to!string ~ "s</info> %s", name, description.getCommand(name).getDescription() is null ? "" : description.getCommand(name).getDescription()), options);
172                 }
173             }
174 
175             writeNewline();
176         }
177     }
178 
179     private void writeNewline()
180     {
181         writeText("\r\n"/* System.getProperty("line.separator") */, new DescriptorOptions());
182     }
183 
184     private void writeText(string content, DescriptorOptions options)
185     {
186         write(
187             options.has("raw_text") && Boolean.parseBoolean("raw_text") ? StringUtils.stripTags(content) : content,
188             !options.has("raw_output") || !Boolean.parseBoolean(options.get("raw_output"))
189         );
190     }
191 
192     private int getColumnWidth(Map!(string, Command) commands)
193     {
194         int width = 0;
195         foreach (Command command ; commands.values()) {
196             width = MathHelper.max(width, cast(int)(command.getName().length));
197         }
198 
199         return width;
200     }
201 }