1 module dud.options;
2 
3 import std.array : empty;
4 import std.getopt;
5 import std.stdio;
6 import std.traits : FieldNameTuple;
7 
8 @safe:
9 
10 private struct OptionUDA {
11 	string s;
12 	string l;
13 	string documentation;
14 	string type;
15 }
16 
17 private template buildSelector(alias thing) {
18 	enum attr = __traits(getAttributes, thing);
19 	static if(attr.length == 1) {
20 		alias First = attr[0];
21 		alias FirstType = typeof(First);
22 		static if(is(FirstType == OptionUDA)) {
23 
24 			static assert(First.s.length == 1 || First.s.length == 0);
25 			static assert(First.l.length > 1 || First.l.length == 0);
26 
27 			static if(First.s.length > 0 && First.l.length > 0) {
28 				enum buildSelector = First.s ~ "|" ~ First.l;
29 			} else static if(First.s.length == 0 && First.l.length > 0) {
30 				enum buildSelector = First.l;
31 			} else static if(First.s.length > 0 && First.l.length == 0) {
32 				enum buildSelector = First.s;
33 			} else {
34 				enum buildSelector = "";
35 			}
36 		}
37 	} else {
38 		enum buildSelector = "";
39 	}
40 }
41 
42 private template optionUDASelector(alias thing) {
43 	enum attr = __traits(getAttributes, thing);
44 	static if(attr.length == 1) {
45 		alias First = attr[0];
46 		alias FirstType = typeof(First);
47 		static if(is(FirstType == OptionUDA)) {
48 			enum optionUDASelector = First;
49 		} else {
50 			enum optionUDASelector = OptionUDA.init;
51 		}
52 	} else {
53 		enum optionUDASelector = OptionUDA.init;
54 	}
55 }
56 
57 private OptionUDA[] allOptions(T)() {
58 	OptionUDA[] ret;
59 	static foreach(mem; FieldNameTuple!T) {{
60 		OptionUDA tmp = optionUDASelector!(__traits(getMember, T, mem));
61 		if(tmp != OptionUDA.init) {
62 			tmp.type = typeof(__traits(getMember, T, mem)).stringof;
63 			ret ~= tmp;
64 		}
65 	}}
66 	return ret;
67 }
68 
69 struct OptionReturn(Option) {
70 	const Option options;
71 	const CommonOptions common;
72 }
73 
74 void getOptions(T)(ref T t, ref string[] args) {
75 	static foreach(mem; FieldNameTuple!T) {
76 		getopt(args, config.passThrough,
77 			buildSelector!(__traits(getMember, T, mem)),
78 			&__traits(getMember, t, mem));
79 	}
80 }
81 
82 void writeOption(Out, Ops)(auto ref Out output, const ref Ops option) {
83 	import std.algorithm.searching : maxElement;
84 	import std.algorithm.iteration : map, each;
85 	import std.format : formattedWrite;
86 
87 	enum opsDoc = optionUDASelector!Ops;
88 	writeln(opsDoc.documentation);
89 
90 	enum OptionUDA[] ops = allOptions!(Ops)();
91 	const size_t sMax = ops.map!(it => it.s.length).maxElement + "-".length;
92 	const size_t lMax = ops.map!(it => it.l.length).maxElement + "--".length;
93 	const size_t tMax = ops.map!(it => it.type.length).maxElement;
94 
95 	ops.each!(op =>
96 		formattedWrite(output, "%*s %*s %*s: %s\n",
97 			sMax, (op.s.empty ? "" : "-" ~ op.s),
98 			lMax, (op.l.empty ? "" : "--" ~ op.l),
99 			tMax, op.type,
100 			op.documentation)
101 	);
102 }
103 
104 void writeOptions(Out, Ops)(auto ref Out output,
105 		const ref OptionReturn!Ops options)
106 {
107 	writeOption(output, options.options);
108 	writeOption(output, options.common);
109 }
110 
111 OptionReturn!ConvertOptions getConvertOptions(ref string[] args) {
112 	CommonOptions common;
113 	getOptions(common, args);
114 
115 	ConvertOptions conv;
116 	getOptions(conv, args);
117 
118 	return OptionReturn!(ConvertOptions)(conv, common);
119 }
120 
121 enum ConvertTargetFormat {
122 	undefined,
123 	sdl,
124 	json
125 }
126 
127 @OptionUDA("", "", `
128 Command specific options
129 ========================
130 
131 dud convert to convert $input.(sdl,json) to $output.$format files.
132 
133 If no $input filename is given the current working directory is search
134 for a file with name "dub.json", "dub.sdl", or "package.json".
135 
136 If no $output is given the output file name will be derived from $format.
137 If also no $format is given an error is printed.
138 
139 The option $keepInput will prevent dud convert from deleting $input.
140 The option $keepInput is false by default.
141 
142 dud will not override $output if it exists.
143 To override a file with name $output pass $override to convert.
144 `)
145 struct ConvertOptions {
146 	@OptionUDA("i", "input", "The filename of the dub file to convert")
147 	string inputFilename;
148 
149 	@OptionUDA("o", "output", "The filename of the output")
150 	string outputFilename;
151 
152 	@OptionUDA("f", "format", "The type to convert to")
153 	ConvertTargetFormat outputTargetType;
154 
155 	@OptionUDA("k", "keepInput", "Keep the input file")
156 	bool keepInput;
157 
158 	@OptionUDA("", "override", "Override output file if exists")
159 	bool override_;
160 }
161 
162 @OptionUDA("", "", `
163 Common Options
164 ==============
165 
166 General options that apply to all functions of dud`)
167 struct CommonOptions {
168 	@OptionUDA("h", "help", "Display general or command specific help")
169 	bool help;
170 
171 	@OptionUDA("v", "verbose", "Print diagnostic output")
172 	bool verbose;
173 
174 	@OptionUDA("", "vverbose", "Print debug output")
175 	bool vverbose;
176 }