1 // license:GPL-2.0+
2 // copyright-holders:Couriersud
3 
4 #include "poptions.h"
5 #include "pexception.h"
6 #include "pstrutil.h"
7 #include "ptypes.h"
8 
9 namespace plib {
10 
option_base(options & parent,const pstring & help)11 	option_base::option_base(options &parent, const pstring &help)
12 	: m_help(help)
13 	{
14 		parent.register_option(this);
15 	}
16 
option(options & parent,const pstring & ashort,const pstring & along,const pstring & help,bool has_argument)17 	option::option(options &parent, const pstring &ashort, const pstring &along, const pstring &help, bool has_argument)
18 	: option_base(parent, help), m_short(ashort), m_long(along),
19 	  m_has_argument(has_argument), m_specified(false)
20 	{
21 	}
22 
parse(const pstring & argument)23 	int option_str::parse(const pstring &argument)
24 	{
25 		m_val = argument;
26 		return 0;
27 	}
28 
parse(const pstring & argument)29 	int option_bool::parse(const pstring &argument)
30 	{
31 		unused_var(argument);
32 		m_val = true;
33 		return 0;
34 	}
35 
parse(const pstring & argument)36 	int option_vec::parse(const pstring &argument)
37 	{
38 		bool err = false;
39 		m_val.push_back(argument);
40 		return (err ? 1 : 0);
41 	}
42 
options()43 	options::options()
44 	: m_other_args(nullptr)
45 	{
46 	}
47 
options(option ** o)48 	options::options(option **o)
49 	: m_other_args(nullptr)
50 	{
51 		int i=0;
52 		while (o[i] != nullptr)
53 		{
54 			register_option(o[i]);
55 			i++;
56 		}
57 	}
58 
register_option(option_base * opt)59 	void options::register_option(option_base *opt)
60 	{
61 		m_opts.push_back(opt);
62 	}
63 
check_consistency()64 	void options::check_consistency()
65 	{
66 		for (auto &opt : m_opts)
67 		{
68 			auto *o = dynamic_cast<option *>(opt);
69 			if (o != nullptr)
70 			{
71 				if (o->short_opt().empty() && o->long_opt().empty())
72 				{
73 					auto *ov = dynamic_cast<option_args *>(o);
74 					if (ov != nullptr)
75 					{
76 						if (m_other_args != nullptr)
77 						{
78 							throw pexception("other args can only be specified once!");
79 						}
80 
81 						m_other_args = ov;
82 					}
83 					else
84 						throw pexception("found option with neither short or long tag!" );
85 				}
86 			}
87 		}
88 	}
89 
parse(const std::vector<putf8string> & argv)90 	std::size_t options::parse(const std::vector<putf8string> &argv)
91 	{
92 		check_consistency();
93 		m_app = argv[0];
94 		bool seen_other_args = false;
95 
96 		for (std::size_t i=1; i < argv.size(); )
97 		{
98 			pstring arg(argv[i]);
99 			option *opt = nullptr;
100 			pstring opt_arg;
101 			bool has_equal_arg = false;
102 
103 			if (!seen_other_args && plib::startsWith(arg, "--"))
104 			{
105 				auto v = psplit(arg.substr(2),'=');
106 				if (!v.empty() && !v[0].empty())
107 				{
108 					opt = getopt_long(v[0]);
109 					has_equal_arg = (v.size() > 1);
110 					if (has_equal_arg)
111 					{
112 						for (std::size_t j = 1; j < v.size() - 1; j++)
113 							opt_arg += (v[j] + "=");
114 						opt_arg += v[v.size()-1];
115 					}
116 				}
117 				else
118 				{
119 					opt = m_other_args;
120 					seen_other_args = true;
121 				}
122 			}
123 			else if (!seen_other_args && plib::startsWith(arg, "-"))
124 			{
125 				std::size_t p = 1;
126 				opt = getopt_short(arg.substr(p, 1));
127 				++p;
128 				if (p < arg.length())
129 				{
130 					has_equal_arg = true;
131 					opt_arg = arg.substr(p);
132 				}
133 			}
134 			else
135 			{
136 				seen_other_args = true;
137 				if (m_other_args == nullptr)
138 					return i;
139 				opt = m_other_args;
140 				i--; // we haven't had an option specifier;
141 			}
142 			if (opt == nullptr)
143 				return i;
144 			if (opt->has_argument())
145 			{
146 				if (has_equal_arg)
147 				{
148 					if (opt->do_parse(opt_arg) != 0)
149 						return i;
150 				}
151 				else
152 				{
153 					i++;
154 					if (i >= argv.size())
155 						return i - 1;
156 					if (opt->do_parse(pstring(argv[i])) != 0)
157 						return i - 1;
158 				}
159 			}
160 			else
161 			{
162 				if (has_equal_arg)
163 					return i;
164 				opt->do_parse("");
165 			}
166 			i++;
167 		}
168 		return argv.size();
169 	}
170 
split_paragraphs(const pstring & text,unsigned width,unsigned indent,unsigned firstline_indent,const pstring & line_end)171 	pstring options::split_paragraphs(const pstring &text, unsigned width, unsigned indent,
172 			unsigned firstline_indent, const pstring &line_end)
173 	{
174 		auto paragraphs = psplit(text,'\n');
175 		pstring ret("");
176 
177 		for (auto &p : paragraphs)
178 		{
179 			pstring line = plib::rpad(pstring(""), pstring(" "), firstline_indent);
180 			for (auto &s : psplit(p, ' '))
181 			{
182 				if (line.length() + s.length() > width)
183 				{
184 					ret += line + line_end;
185 					line = plib::rpad(pstring(""), pstring(" "), indent);
186 				}
187 				line += s + " ";
188 			}
189 			ret += line;
190 			if (p != paragraphs.back())
191 				ret += line_end;
192 		}
193 		return ret;
194 	}
195 
help(const pstring & description,const pstring & usage,unsigned width,unsigned indent) const196 	pstring options::help(const pstring &description, const pstring &usage,
197 			unsigned width, unsigned indent) const
198 	{
199 		pstring ret;
200 
201 		ret = split_paragraphs(description, width, 0, 0) + "\n\n";
202 		ret += "Usage:\t" + usage + "\n\nOptions:\n\n";
203 
204 		for (const auto & optbase : m_opts )
205 		{
206 			// Skip anonymous inputs which are collected in option_args
207 			if (dynamic_cast<option_args *>(optbase) != nullptr)
208 				continue;
209 
210 			if (auto * const opt = dynamic_cast<option *>(optbase))
211 			{
212 				pstring line = "";
213 				if (!opt->short_opt().empty())
214 					line += "  -" + opt->short_opt();
215 				if (!opt->long_opt().empty())
216 				{
217 					if (!line.empty())
218 						line += ", ";
219 					else
220 						line = "      ";
221 					line += "--" + opt->long_opt();
222 					if (opt->has_argument())
223 					{
224 						line += "=";
225 						auto *ol = dynamic_cast<option_str_limit_base *>(opt);
226 						if (ol != nullptr)
227 						{
228 							for (const auto &v : ol->limit())
229 							{
230 								line += v + "|";
231 							}
232 							line = plib::left(line, line.length() - 1);
233 						}
234 						else
235 							line += "Value";
236 					}
237 				}
238 				line = plib::rpad(line, pstring(" "), indent - 2) + "  ";
239 				if (line.length() > indent)
240 				{
241 					//ret += "TestGroup abc\n  def gef\nxyz\n\n" ;
242 					ret += line + "\n";
243 					ret += split_paragraphs(opt->help(), width, indent, indent) + "\n";
244 				}
245 				else
246 					ret += split_paragraphs(line + opt->help(), width, indent, 0) + "\n";
247 			}
248 			else if (auto *grp = dynamic_cast<option_group *>(optbase))
249 			{
250 				ret += "\n" + grp->group() + ":\n";
251 				if (!grp->help().empty())
252 					ret += split_paragraphs(grp->help(), width, 4, 4) + "\n\n";
253 			}
254 		}
255 		// FIXME: other help ...
256 		pstring ex("");
257 		for (const auto & optbase : m_opts )
258 		{
259 			if (auto *example = dynamic_cast<option_example *>(optbase))
260 			{
261 				// help2man doesn't like \\ line continuation in output
262 				ex += split_paragraphs(example->example(), width, 8, 0, "\n") + "\n\n";
263 				ex += split_paragraphs(example->help(), width, 4, 4) + "\n\n";
264 			}
265 		}
266 		if (!ex.empty())
267 		{
268 			ret += "\n\nExamples:\n\n" + ex;
269 		}
270 		return ret;
271 	}
272 
getopt_short(const pstring & arg) const273 	option *options::getopt_short(const pstring &arg) const
274 	{
275 		for (const auto & optbase : m_opts)
276 		{
277 			auto *opt = dynamic_cast<option *>(optbase);
278 			if (opt != nullptr && !arg.empty() && opt->short_opt() == arg)
279 				return opt;
280 		}
281 		return nullptr;
282 	}
getopt_long(const pstring & arg) const283 	option *options::getopt_long(const pstring &arg) const
284 	{
285 		for (const auto & optbase : m_opts)
286 		{
287 			auto *opt = dynamic_cast<option *>(optbase);
288 			if (opt != nullptr && !arg.empty() && opt->long_opt() == arg)
289 				return opt;
290 		}
291 		return nullptr;
292 	}
293 
294 } // namespace plib
295 
296