1 module clid.help;
2 
3 import std.traits : hasUDA, getUDAs;
4 import std.meta : Alias;
5 import std..string : leftJustify;
6 
7 import clid.attributes;
8 import clid.util;
9 
10 /**
11  * Print help information for a given configuration struct.
12  * Params: c = The configuration struct to print help information for.
13  */
14 void printHelp(C)(C c)
15 {
16 	import std.stdio : stderr;
17 
18 	validateStruct!C();
19 	stderr.write(getHelp(c));
20 }
21 
22 private string getHelp(C)(C c) // @suppress(dscanner.suspicious.unused_parameter)
23 {
24 	import std.ascii : newline;
25 
26 	string str = usage() ~ newline ~ newline;
27 
28 	static foreach (member; __traits(allMembers, C))
29 	{
30 		str ~= Describe!(__traits(getMember, c, member)) ~ newline;
31 	}
32 	str ~= "\t-h, --help".leftJustify(30) ~ "Show this help\n";
33 	return str;
34 }
35 
36 private template GetFlagName(alias E)
37 {
38 	static assert(getUDAs!(E, Parameter).length == 1, "Must have at most 1 @Parameter attribute.");
39 	static if (getUDAs!(E, Parameter)[0].shortName == ' ')
40 	{
41 		alias GetFlagName = Alias!("    --" ~ getUDAs!(E, Parameter)[0].longName);
42 	}
43 	else
44 	{
45 		alias GetFlagName = Alias!("-" ~ getUDAs!(E,
46 				Parameter)[0].shortName ~ ", --" ~ getUDAs!(E, Parameter)[0].longName);
47 	}
48 }
49 
50 private template GetFlagValue(alias E)
51 {
52 	import std.uni : toUpper;
53 
54 	static if (hasUDA!(E, Description) && getUDAs!(E, Description)[0].optionType.length > 0)
55 	{
56 		alias GetFlagValue = Alias!(" " ~ getUDAs!(E, Description)[0].optionType);
57 	}
58 	else static if (is(typeof(E) : bool))
59 	{
60 		alias GetFlagValue = Alias!("");
61 	}
62 	else
63 	{
64 		alias GetFlagValue = Alias!(" " ~ typeof(E).stringof.toUpper);
65 	}
66 }
67 
68 private template GetFlag(alias E)
69 {
70 	alias GetFlag = Alias!(GetFlagName!(E) ~ GetFlagValue!(E));
71 }
72 
73 private template GetDescriptionOnly(alias E)
74 {
75 	static if (hasUDA!(E, Description))
76 		alias GetDescriptionOnly = Alias!(getUDAs!(E, Description)[0].description);
77 	else
78 		alias GetDescriptionOnly = Alias!("");
79 }
80 
81 private template GetDescription(alias E)
82 {
83 	static if (hasUDA!(E, Required))
84 		alias GetDescription = Alias!("[REQUIRED] " ~ GetDescriptionOnly!(E));
85 	else
86 		alias GetDescription = GetDescriptionOnly!(E);
87 }
88 
89 private template Describe(alias E)
90 {
91 	static if (!hasUDA!(E, Description) && !hasUDA!(E, Required))
92 	{
93 		alias Describe = Alias!('\t' ~ GetFlag!(E));
94 	}
95 	else
96 	{
97 		alias Describe = Alias!(leftJustify('\t' ~ GetFlag!(E), 30) ~ GetDescription!(E));
98 	}
99 }
100 
101 private string usage()
102 {
103 	import core.runtime : Runtime;
104 
105 	return "Usage: " ~ Runtime.args()[0] ~ " [OPTION...]";
106 }
107 
108 // ======================
109 // Unit tests start here.
110 // ======================
111 
112 unittest
113 {
114 	struct Config
115 	{
116 		@Parameter("foo", 'f')
117 		@Description("Foobar")
118 		int value;
119 
120 		@Parameter("str", 's')
121 		@Description("Just a string")
122 		string stringValue;
123 
124 		@Parameter("time", 't')
125 		@Description("Interval time", "secs")
126 		int time;
127 
128 		@Parameter("bar")
129 		@Description("A random description")
130 		@Required bool other;
131 	}
132 
133 	immutable Config config;
134 	static assert(GetFlag!(config.value) == "-f, --foo INT");
135 	static assert(GetDescription!(config.value) == "Foobar");
136 
137 	printHelp(config);
138 }
139 
140 unittest
141 {
142 	struct Config
143 	{
144 		@Parameter("foo")
145 		int value;
146 	}
147 
148 	immutable Config config;
149 	static assert(GetFlag!(config.value) == "    --foo INT");
150 	//static assert(Describe!(config.value) == "\t    --foo INT");
151 }