Line data Source code
1 : // Copyright (c) 2006-2020 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 :
20 :
21 : /** \file
22 : * \brief Implementation of utility functions.
23 : *
24 : * This file includes various utility functions that are not specifically
25 : * attached to a class.
26 : */
27 :
28 : // self
29 : //
30 : #include "advgetopt/utils.h"
31 :
32 :
33 : // snapdev lib
34 : //
35 : #include <snapdev/glob_to_list.h>
36 : #include <snapdev/not_used.h>
37 :
38 :
39 : // cppthread lib
40 : //
41 : #include <cppthread/guard.h>
42 : #include <cppthread/mutex.h>
43 :
44 :
45 : // boost lib
46 : //
47 : #include <boost/algorithm/string/trim.hpp>
48 :
49 :
50 : // C++ lib
51 : //
52 : #include <set>
53 :
54 :
55 : // last include
56 : //
57 : #include <snapdev/poison.h>
58 :
59 :
60 :
61 : namespace advgetopt
62 : {
63 :
64 :
65 :
66 : namespace
67 : {
68 :
69 :
70 :
71 : /** \brief The configuration file mutex.
72 : *
73 : * This options are generally viewed as read-only global variables. They
74 : * get setup once early on and then used and reused as many times as
75 : * required.
76 : *
77 : * This mutex makes sure that access between multiple thread happens in
78 : * a safe manner.
79 : */
80 : cppthread::mutex * g_mutex;
81 :
82 :
83 :
84 : }
85 : // no name namespace
86 :
87 :
88 :
89 : /** \brief Get a global mutex.
90 : *
91 : * This function returns a global mutex we can use to lock the advgetopt
92 : * whenever multithread functionality is required (i.e. a global is used.)
93 : *
94 : * It is safe to call this function early (i.e. before main was ever
95 : * called.)
96 : *
97 : * Usage:
98 : *
99 : * \code
100 : * cppthread::guard lock(get_global_mutex());
101 : * \endcode
102 : *
103 : * \return A reference to our global mutex.
104 : */
105 7017 : cppthread::mutex & get_global_mutex()
106 : {
107 : {
108 14034 : cppthread::guard lock(*cppthread::g_system_mutex);
109 :
110 7017 : if(g_mutex == nullptr)
111 : {
112 1 : g_mutex = new cppthread::mutex();
113 : }
114 : }
115 :
116 7017 : return *g_mutex;
117 : }
118 :
119 :
120 :
121 : /** \brief Remove single (') or double (") quotes from a string.
122 : *
123 : * If a string starts and ends with the same quotation mark, then it
124 : * gets removed.
125 : *
126 : * If no quotes appear, then the function returns a copy of the input as is.
127 : *
128 : * The \p pairs parameter must have an even size (or the last character
129 : * gets ignored). By default, it is set to the double and single quotes:
130 : *
131 : * \code
132 : * "\"\"''"
133 : * \endcode
134 : *
135 : * To remove square, angle, curly brackets:
136 : *
137 : * \code
138 : * "[]<>{}"
139 : * \endcode
140 : *
141 : * \todo
142 : * Add support for UTF-8 quotes. Right now only quotes of 1 byte will
143 : * work.
144 : *
145 : * \param[in] s The string to unquote.
146 : * \param[in] pairs A list of accepted quotes.
147 : *
148 : * \return The unquoted string.
149 : */
150 845 : std::string unquote(std::string const & s, std::string const & pairs)
151 : {
152 845 : if(s.length() >= 2)
153 : {
154 775 : std::string::size_type const max(pairs.length() - 1);
155 2227 : for(std::string::size_type pos(0); pos < max; pos += 2)
156 : {
157 3046 : if(s.front() == pairs[pos + 0]
158 1523 : && s.back() == pairs[pos + 1])
159 : {
160 71 : return s.substr(1, s.length() - 2);
161 : }
162 : }
163 : }
164 :
165 774 : return s;
166 : }
167 :
168 :
169 : /** \brief Split a string in sub-strings separated by \p separators.
170 : *
171 : * This function searches for any of the \p separators in \p str and
172 : * split at those locations.
173 : *
174 : * For example, to split a comma separated list of strings, use the
175 : * following:
176 : *
177 : * \code
178 : * string_list_t result;
179 : * option_info::split_string(string_to_split, result, {","});
180 : * \endcode
181 : *
182 : * If `string_to_split` is set to "a, b, c", then the `result` vector
183 : * will have three strings as a result: `a`, `b`, and `c`. Note that
184 : * the function automatically trims all strings and it never keeps
185 : * empty strings. So two separators one after another is accepted and
186 : * no empty string results.
187 : *
188 : * The trimming happens after the split occurs. This allows for the
189 : * list of separators to include spaces as separators.
190 : *
191 : * The function does not clear the result vector. This allows you to
192 : * call this function multiple times with various strings and the
193 : * results will be cumulated.
194 : *
195 : * \note
196 : * This function is a static so it can be used from anywhere to split
197 : * strings as required. You do not need to have an option_info instance.
198 : *
199 : * \todo
200 : * See to fix the fact that `a"b"c` becomes `{"a", "b", "c"}` when
201 : * there are not separators between `a`, `"b"`, and `c`. To the minimum
202 : * we may want to generate an error when such is found (i.e. when a
203 : * quote is found and `start < pos` is true.
204 : *
205 : * \param[in] str The string to split.
206 : * \param[in] result The vector where the split strings are saved.
207 : * \param[in] separators The vector of strings used as separators.
208 : */
209 95 : void split_string(std::string const & str
210 : , string_list_t & result
211 : , string_list_t const & separators)
212 : {
213 95 : std::string::size_type pos(0);
214 95 : std::string::size_type start(0);
215 11341 : while(pos < str.length())
216 : {
217 5623 : if(str[pos] == '\'' || str[pos] == '"')
218 : {
219 14 : if(start < pos)
220 : {
221 12 : std::string v(str.substr(start, pos - start));
222 6 : boost::trim(v);
223 6 : if(!v.empty())
224 : {
225 4 : result.push_back(v);
226 : }
227 6 : start = pos;
228 : }
229 :
230 : // quoted parameters are handled without the separators
231 : //
232 14 : char const quote(str[pos]);
233 14 : for(++pos; pos < str.length() && str[pos] != quote; ++pos);
234 :
235 28 : std::string const v(str.substr(start + 1, pos - (start + 1)));
236 14 : if(!v.empty())
237 : {
238 11 : result.push_back(v);
239 : }
240 14 : if(pos < str.length())
241 : {
242 : // skip the closing quote
243 : //
244 12 : ++pos;
245 : }
246 14 : start = pos;
247 : }
248 : else
249 : {
250 5609 : bool found(false);
251 11308 : for(auto const & sep : separators)
252 : {
253 5964 : if(str.length() - pos >= sep.length())
254 : {
255 5964 : if(str.compare(pos, sep.length(), sep) == 0)
256 : {
257 : // match! cut here
258 : //
259 265 : if(start < pos)
260 : {
261 486 : std::string v(str.substr(start, pos - start));
262 243 : boost::trim(v);
263 243 : if(!v.empty())
264 : {
265 243 : result.push_back(v);
266 : }
267 : }
268 265 : pos += sep.length();
269 265 : start = pos;
270 265 : found = true;
271 265 : break;
272 : }
273 : }
274 : }
275 :
276 5609 : if(!found)
277 : {
278 5344 : ++pos;
279 : }
280 : }
281 : }
282 :
283 95 : if(start < pos)
284 : {
285 176 : std::string v(str.substr(start, pos - start));
286 88 : boost::trim(v);
287 88 : if(!v.empty())
288 : {
289 88 : result.push_back(v);
290 : }
291 : }
292 95 : }
293 :
294 :
295 : /** \brief Insert the group (or project) name in the filename.
296 : *
297 : * This function inserts the name of the group in the specified full path
298 : * filename. It gets added right before the basename. So for example you
299 : * have a path such as:
300 : *
301 : * /etc/snapwebsites/advgetopt.conf
302 : *
303 : * and a group name such as:
304 : *
305 : * adventure
306 : *
307 : * The resulting path is:
308 : *
309 : * /etc/snapwebsites/adventure.d/advgetopt.conf
310 : *
311 : * Notice that the function adds a ".d" as well.
312 : *
313 : * If the group name is empty or null, then the project name is used. If
314 : * both are empty, then nothing happens (the function returns an empty list).
315 : *
316 : * \param[in] filename The filename where the project name gets injected.
317 : * \param[in] group_name The name of the group to inject in the filename.
318 : * \param[in] project_name The name of the project to inject in the filename.
319 : *
320 : * \return The list of filenames or an empty list if no group or project name
321 : * or filename are specified.
322 : */
323 527 : string_list_t insert_group_name(
324 : std::string const & filename
325 : , char const * group_name
326 : , char const * project_name)
327 : {
328 527 : if(filename.empty())
329 : {
330 5 : return string_list_t();
331 : }
332 :
333 1044 : std::string name;
334 522 : if(group_name == nullptr
335 160 : || *group_name == '\0')
336 : {
337 366 : if(project_name == nullptr
338 364 : || *project_name == '\0')
339 : {
340 4 : return string_list_t();
341 : }
342 362 : name = project_name;
343 : }
344 : else
345 : {
346 156 : name = group_name;
347 : }
348 :
349 1036 : std::string pattern;
350 518 : std::string::size_type const pos(filename.find_last_of('/'));
351 518 : if(pos != std::string::npos
352 242 : && pos > 0)
353 : {
354 968 : pattern = filename.substr(0, pos + 1)
355 484 : + name
356 484 : + ".d/[0-9][0-9]-"
357 968 : + filename.substr(pos + 1);
358 : }
359 : else
360 : {
361 276 : pattern = name
362 552 : + (".d/[0-9][0-9]-" + filename);
363 : }
364 :
365 1036 : snap::glob_to_list<std::set<std::string>> glob;
366 :
367 : // the glob() function is not thread safe
368 : {
369 1036 : cppthread::guard lock(get_global_mutex());
370 518 : snap::NOTUSED(glob.read_path<snap::glob_to_list_flag_t::GLOB_FLAG_IGNORE_ERRORS>(pattern));
371 : }
372 :
373 : // we add the default name if none other exists
374 : //
375 518 : if(glob.empty())
376 : {
377 497 : if(pos != std::string::npos
378 221 : && pos > 0)
379 : {
380 884 : glob.insert(filename.substr(0, pos + 1)
381 442 : + name
382 442 : + ".d/50-"
383 884 : + filename.substr(pos + 1));
384 : }
385 : else
386 : {
387 276 : glob.insert(name
388 552 : + (".d/50-" + filename));
389 : }
390 : }
391 :
392 518 : return string_list_t(glob.begin(), glob.end());
393 : }
394 :
395 :
396 : /** \brief Replace a starting `~/...` with the contents of the \$HOME variable.
397 : *
398 : * This function checks the beginning of \p filename. If it starts with `'~/'`
399 : * then it replaces the `'~'` character with the contents of the \$HOME
400 : * environment variable.
401 : *
402 : * If \p filename is just `"~"`, then the function returns the contents of
403 : * the \$HOME environment variable by itself.
404 : *
405 : * If somehow the \$HOME environment variable is empty, the function does
406 : * nothing.
407 : *
408 : * \param[in] filename The filename to check for a tilde (~).
409 : *
410 : * \return The input as is unless the \$HOME path can be prepended to replace
411 : * the tilde (~) character.
412 : */
413 566 : std::string handle_user_directory(std::string const & filename)
414 : {
415 566 : char const * const home(getenv("HOME"));
416 566 : if(home != nullptr
417 407 : && *home != '\0')
418 : {
419 810 : if(!filename.empty()
420 405 : && filename[0] == '~'
421 462 : && (filename.length() == 1 || filename[1] == '/'))
422 : {
423 57 : return home + filename.substr(1);
424 : }
425 : }
426 :
427 509 : return filename;
428 : }
429 :
430 :
431 : /** \brief Check whether a value represents "true".
432 : *
433 : * This function checks a string to see whether it is one of:
434 : *
435 : * * "true"
436 : * * "on"
437 : * * "1"
438 : *
439 : * If so, then the function returns true.
440 : *
441 : * \param[in] s The string to be checked.
442 : *
443 : * \return true if the string represents "true".
444 : */
445 7 : bool is_true(std::string s)
446 : {
447 7 : return s == "true" || s == "on" || s == "1";
448 : }
449 :
450 :
451 : /** \brief Check whether a value represents "false".
452 : *
453 : * This function checks a string to see whether it is one of:
454 : *
455 : * * "false"
456 : * * "off"
457 : * * "0"
458 : *
459 : * If so, then the function returns true.
460 : *
461 : * \param[in] s The string to be checked.
462 : *
463 : * \return true if the string represents "false".
464 : */
465 7 : bool is_false(std::string s)
466 : {
467 7 : return s == "false" || s == "off" || s == "0";
468 : }
469 :
470 :
471 :
472 6 : } // namespace advgetopt
473 : // vim: ts=4 sw=4 et
|