Line data Source code
1 : /*
2 : * License:
3 : * Copyright (c) 2006-2019 Made to Order Software Corp. All Rights Reserved
4 : *
5 : * https://snapwebsites.org/
6 : * contact@m2osw.com
7 : *
8 : * This program is free software; you can redistribute it and/or modify
9 : * it under the terms of the GNU General Public License as published by
10 : * the Free Software Foundation; either version 2 of the License, or
11 : * (at your option) any later version.
12 : *
13 : * This program is distributed in the hope that it will be useful,
14 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : * GNU General Public License for more details.
17 : *
18 : * You should have received a copy of the GNU General Public License along
19 : * with this program; if not, write to the Free Software Foundation, Inc.,
20 : * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 : *
22 : * Authors:
23 : * Alexis Wilke alexis@m2osw.com
24 : * Doug Barbieri doug@m2osw.com
25 : */
26 :
27 :
28 : /** \file
29 : * \brief Implementation of utility functions.
30 : *
31 : * This file includes various utility functions that are not specifically
32 : * attached to a class.
33 : */
34 :
35 : // self
36 : //
37 : #include "advgetopt/utils.h"
38 :
39 :
40 : // snapdev lib
41 : //
42 : #include <snapdev/glob_to_list.h>
43 : #include <snapdev/not_used.h>
44 :
45 :
46 : // boost lib
47 : //
48 : #include <boost/algorithm/string/trim.hpp>
49 :
50 :
51 : // last include
52 : //
53 : #include <snapdev/poison.h>
54 :
55 :
56 :
57 : namespace advgetopt
58 : {
59 :
60 :
61 :
62 : /** \brief Remove single (') or double (") quotes from a string.
63 : *
64 : * If a string starts and ends with the same quotation mark, then it
65 : * gets removed.
66 : *
67 : * If no quotes appear, then the function returns a copy of the input as is.
68 : *
69 : * The \p pairs parameter must have an even size (or the last character
70 : * gets ignored). By default, it is set to the double and single quotes:
71 : *
72 : * \code
73 : * "\"\"''"
74 : * \endcode
75 : *
76 : * To remove square, angle, curly brackets:
77 : *
78 : * \code
79 : * "[]<>{}"
80 : * \endcode
81 : *
82 : * \todo
83 : * Add support for UTF-8 quotes. Right now only quotes of 1 byte will
84 : * work.
85 : *
86 : * \param[in] s The string to unquote.
87 : * \param[in] pairs A list of accepted quotes.
88 : *
89 : * \return The unquoted string.
90 : */
91 174 : std::string unquote(std::string const & s, std::string const & pairs)
92 : {
93 174 : if(s.length() >= 2)
94 : {
95 138 : std::string::size_type const max(pairs.length() - 1);
96 398 : for(std::string::size_type pos(0); pos < max; pos += 2)
97 : {
98 582 : if(s.front() == pairs[pos + 0]
99 291 : && s.back() == pairs[pos + 1])
100 : {
101 31 : return s.substr(1, s.length() - 2);
102 : }
103 : }
104 : }
105 :
106 143 : return s;
107 : }
108 :
109 :
110 : /** \brief Split a string in sub-strings separated by \p separators.
111 : *
112 : * This function searches for any of the \p separators in \p str and
113 : * split at those locations.
114 : *
115 : * For example, to split a comma separated list of strings, use the
116 : * following:
117 : *
118 : * \code
119 : * string_list_t result;
120 : * option_info::split_string(string_to_split, result, {","});
121 : * \endcode
122 : *
123 : * If `string_to_split` is set to "a, b, c", then the `result` vector
124 : * will have three strings as a result: `a`, `b`, and `c`. Note that
125 : * the function automatically trims all strings and it never keeps
126 : * empty strings. So two separators one after another is accepted and
127 : * no empty string results.
128 : *
129 : * The trimming happens after the split occurs. This allows for the
130 : * list of separators to include spaces as separators.
131 : *
132 : * The function does not clear the result vector. This allows you to
133 : * call this function multiple times with various strings and the
134 : * results will be cumulated.
135 : *
136 : * \note
137 : * This function is a static so it can be used from anywhere to split
138 : * strings as required. You do not need to have an option_info instance.
139 : *
140 : * \todo
141 : * See to fix the fact that `a"b"c` becomes `{"a", "b", "c"}` when
142 : * there are not separators between `a`, `"b"`, and `c`. To the minimum
143 : * we may want to generate an error when such is found (i.e. when a
144 : * quote is found and `start < pos` is true.
145 : *
146 : * \param[in] str The string to split.
147 : * \param[in] result The vector where the split strings are saved.
148 : * \param[in] separators The vector of strings used as separators.
149 : */
150 91 : void split_string(std::string const & str
151 : , string_list_t & result
152 : , string_list_t const & separators)
153 : {
154 91 : std::string::size_type pos(0);
155 91 : std::string::size_type start(0);
156 11399 : while(pos < str.length())
157 : {
158 5654 : if(str[pos] == '\'' || str[pos] == '"')
159 : {
160 14 : if(start < pos)
161 : {
162 12 : std::string v(str.substr(start, pos - start));
163 6 : boost::trim(v);
164 6 : if(!v.empty())
165 : {
166 4 : result.push_back(v);
167 : }
168 6 : start = pos;
169 : }
170 :
171 : // quoted parameters are handled without the separators
172 : //
173 14 : char const quote(str[pos]);
174 14 : for(++pos; pos < str.length() && str[pos] != quote; ++pos);
175 :
176 28 : std::string const v(str.substr(start + 1, pos - (start + 1)));
177 14 : if(!v.empty())
178 : {
179 11 : result.push_back(v);
180 : }
181 14 : if(pos < str.length())
182 : {
183 : // skip the closing quote
184 : //
185 12 : ++pos;
186 : }
187 14 : start = pos;
188 : }
189 : else
190 : {
191 5640 : bool found(false);
192 11365 : for(auto const & sep : separators)
193 : {
194 5990 : if(str.length() - pos >= sep.length())
195 : {
196 5990 : if(str.compare(pos, sep.length(), sep) == 0)
197 : {
198 : // match! cut here
199 : //
200 265 : if(start < pos)
201 : {
202 486 : std::string v(str.substr(start, pos - start));
203 243 : boost::trim(v);
204 243 : if(!v.empty())
205 : {
206 243 : result.push_back(v);
207 : }
208 : }
209 265 : pos += sep.length();
210 265 : start = pos;
211 265 : found = true;
212 265 : break;
213 : }
214 : }
215 : }
216 :
217 5640 : if(!found)
218 : {
219 5375 : ++pos;
220 : }
221 : }
222 : }
223 :
224 91 : if(start < pos)
225 : {
226 168 : std::string v(str.substr(start, pos - start));
227 84 : boost::trim(v);
228 84 : if(!v.empty())
229 : {
230 84 : result.push_back(v);
231 : }
232 : }
233 91 : }
234 :
235 :
236 : /** \brief Insert the project name in the filename.
237 : *
238 : * This function inserts the name of the project in the specified full path
239 : * filename. It gets added right before the basename. So for example you
240 : * have a path such as:
241 : *
242 : * /etc/snapwebsites/advgetopt.conf
243 : *
244 : * and a project name such as:
245 : *
246 : * adventure
247 : *
248 : * The resulting path is:
249 : *
250 : * /etc/snapwebsites/adventure.d/advgetopt.conf
251 : *
252 : * Notice that the function adds a ".d" as well.
253 : *
254 : * \param[in] filename The filename where the project name gets injected.
255 : * \param[in] project_name The name of the project to inject in the filename.
256 : *
257 : * \return The new filename or an empty string if no project name or filename
258 : * are specified.
259 : */
260 509 : string_list_t insert_project_name(
261 : std::string const & filename
262 : , char const * project_name)
263 : {
264 509 : if(project_name == nullptr
265 508 : || *project_name == '\0'
266 1016 : || filename.empty())
267 : {
268 3 : return string_list_t();
269 : }
270 :
271 1012 : std::string pattern;
272 :
273 506 : std::string::size_type const pos(filename.find_last_of('/'));
274 506 : if(pos != std::string::npos
275 230 : && pos > 0)
276 : {
277 460 : pattern = filename.substr(0, pos + 1)
278 460 : + project_name
279 460 : + ".d/[0-9][0-9]-"
280 690 : + filename.substr(pos + 1);
281 : }
282 : else
283 : {
284 : pattern = project_name
285 276 : + (".d/[0-9][0-9]-" + filename);
286 : }
287 :
288 1012 : snap::glob_to_list<string_list_t> glob;
289 506 : snap::NOTUSED(glob.read_path<snap::glob_to_list_flag_t::GLOB_FLAG_IGNORE_ERRORS>(pattern));
290 :
291 : // we add the default name if none other exists
292 : //
293 506 : if(glob.empty())
294 : {
295 487 : if(pos != std::string::npos
296 211 : && pos > 0)
297 : {
298 422 : glob.push_back(filename.substr(0, pos + 1)
299 422 : + project_name
300 422 : + ".d/50-"
301 633 : + filename.substr(pos + 1));
302 : }
303 : else
304 : {
305 : glob.push_back(project_name
306 276 : + (".d/50-" + filename));
307 : }
308 : }
309 :
310 506 : return glob;
311 : }
312 :
313 :
314 : /** \brief Replace a starting `~/...` with the contents of the \$HOME variable.
315 : *
316 : * This function checks the beginning of \p filename. If it starts with `'~/'`
317 : * then it replaces the `'~'` character with the contents of the \$HOME
318 : * environment variable.
319 : *
320 : * If \p filename is just `"~"`, then the function returns the contents of
321 : * the \$HOME environment variable by itself.
322 : *
323 : * If somehow the \$HOME environment variable is empty, the function does
324 : * nothing.
325 : *
326 : * \param[in] filename The filename to check for a tilde (~).
327 : *
328 : * \return The input as is unless the \$HOME path can be prepended to replace
329 : * the tilde (~) character.
330 : */
331 562 : std::string handle_user_directory(std::string const & filename)
332 : {
333 562 : char const * const home(getenv("HOME"));
334 562 : if(home != nullptr
335 404 : && *home != '\0')
336 : {
337 804 : if(!filename.empty()
338 402 : && filename[0] == '~'
339 458 : && (filename.length() == 1 || filename[1] == '/'))
340 : {
341 56 : return home + filename.substr(1);
342 : }
343 : }
344 :
345 506 : return filename;
346 : }
347 :
348 :
349 :
350 6 : } // namespace advgetopt
351 : // vim: ts=4 sw=4 et
|