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 : // boost lib
41 : //
42 : #include <boost/algorithm/string/trim.hpp>
43 :
44 :
45 : // last include
46 : //
47 : #include <snapdev/poison.h>
48 :
49 :
50 :
51 : namespace advgetopt
52 : {
53 :
54 :
55 :
56 : /** \brief Remove single (') or double (") quotes from a string.
57 : *
58 : * If a string starts and ends with the same quotation mark, then it
59 : * gets removed.
60 : *
61 : * If no quotes appear, then the function returns a copy of the input as is.
62 : *
63 : * The \p pairs parameter must have an even size (or the last character
64 : * gets ignored). By default, it is set to the double and single quotes:
65 : *
66 : * \code
67 : * "\"\"''"
68 : * \endcode
69 : *
70 : * To remove square, angle, curly brackets:
71 : *
72 : * \code
73 : * "[]<>{}"
74 : * \endcode
75 : *
76 : * \todo
77 : * Add support for UTF-8 quotes. Right now only quotes of 1 byte will
78 : * work.
79 : *
80 : * \param[in] s The string to unquote.
81 : * \param[in] pairs A list of accepted quotes.
82 : *
83 : * \return The unquoted string.
84 : */
85 174 : std::string unquote(std::string const & s, std::string const & pairs)
86 : {
87 174 : if(s.length() >= 2)
88 : {
89 138 : std::string::size_type const max(pairs.length() - 1);
90 398 : for(std::string::size_type pos(0); pos < max; pos += 2)
91 : {
92 582 : if(s.front() == pairs[pos + 0]
93 291 : && s.back() == pairs[pos + 1])
94 : {
95 31 : return s.substr(1, s.length() - 2);
96 : }
97 : }
98 : }
99 :
100 143 : return s;
101 : }
102 :
103 :
104 : /** \brief Split a string in sub-strings separated by \p separators.
105 : *
106 : * This function searches for any of the \p separators in \p str and
107 : * split at those locations.
108 : *
109 : * For example, to split a comma separated list of strings, use the
110 : * following:
111 : *
112 : * \code
113 : * string_list_t result;
114 : * option_info::split_string(string_to_split, result, {","});
115 : * \endcode
116 : *
117 : * If `string_to_split` is set to "a, b, c", then the `result` vector
118 : * will have three strings as a result: `a`, `b`, and `c`. Note that
119 : * the function automatically trims all strings and it never keeps
120 : * empty strings. So two separators one after another is accepted and
121 : * no empty string results.
122 : *
123 : * The trimming happens after the split occurs. This allows for the
124 : * list of separators to include spaces as separators.
125 : *
126 : * The function does not clear the result vector. This allows you to
127 : * call this function multiple times with various strings and the
128 : * results will be cumulated.
129 : *
130 : * \note
131 : * This function is a static so it can be used from anywhere to split
132 : * strings as required. You do not need to have an option_info instance.
133 : *
134 : * \todo
135 : * See to fix the fact that `a"b"c` becomes `{"a", "b", "c"}` when
136 : * there are not separators between `a`, `"b"`, and `c`. To the minimum
137 : * we may want to generate an error when such is found (i.e. when a
138 : * quote is found and `start < pos` is true.
139 : *
140 : * \param[in] str The string to split.
141 : * \param[in] result The vector where the split strings are saved.
142 : * \param[in] separators The vector of strings used as separators.
143 : */
144 91 : void split_string(std::string const & str
145 : , string_list_t & result
146 : , string_list_t const & separators)
147 : {
148 91 : std::string::size_type pos(0);
149 91 : std::string::size_type start(0);
150 11755 : while(pos < str.length())
151 : {
152 5832 : if(str[pos] == '\'' || str[pos] == '"')
153 : {
154 14 : if(start < pos)
155 : {
156 12 : std::string v(str.substr(start, pos - start));
157 6 : boost::trim(v);
158 6 : if(!v.empty())
159 : {
160 4 : result.push_back(v);
161 : }
162 6 : start = pos;
163 : }
164 :
165 : // quoted parameters are handled without the separators
166 : //
167 14 : char const quote(str[pos]);
168 14 : for(++pos; pos < str.length() && str[pos] != quote; ++pos);
169 :
170 28 : std::string const v(str.substr(start + 1, pos - (start + 1)));
171 14 : if(!v.empty())
172 : {
173 11 : result.push_back(v);
174 : }
175 14 : if(pos < str.length())
176 : {
177 : // skip the closing quote
178 : //
179 12 : ++pos;
180 : }
181 14 : start = pos;
182 : }
183 : else
184 : {
185 5818 : bool found(false);
186 11714 : for(auto const & sep : separators)
187 : {
188 6168 : if(str.length() - pos >= sep.length())
189 : {
190 6168 : if(str.compare(pos, sep.length(), sep) == 0)
191 : {
192 : // match! cut here
193 : //
194 272 : if(start < pos)
195 : {
196 500 : std::string v(str.substr(start, pos - start));
197 250 : boost::trim(v);
198 250 : if(!v.empty())
199 : {
200 250 : result.push_back(v);
201 : }
202 : }
203 272 : pos += sep.length();
204 272 : start = pos;
205 272 : found = true;
206 272 : break;
207 : }
208 : }
209 : }
210 :
211 5818 : if(!found)
212 : {
213 5546 : ++pos;
214 : }
215 : }
216 : }
217 :
218 91 : if(start < pos)
219 : {
220 168 : std::string v(str.substr(start, pos - start));
221 84 : boost::trim(v);
222 84 : if(!v.empty())
223 : {
224 84 : result.push_back(v);
225 : }
226 : }
227 91 : }
228 :
229 :
230 : /** \brief Insert the project name in the filename.
231 : *
232 : * This function inserts the name of the project in the specified full path
233 : * filename. It gets added right before the basename. So for example you
234 : * have a path such as:
235 : *
236 : * /etc/snapwebsites/advgetopt.conf
237 : *
238 : * and a project name such as:
239 : *
240 : * adventure
241 : *
242 : * The resulting path is:
243 : *
244 : * /etc/snapwebsites/adventure.d/advgetopt.conf
245 : *
246 : * Notice that the function adds a ".d" as well.
247 : *
248 : * \param[in] filename The filename where the project name gets injected.
249 : * \param[in] project_name The name of the project to inject in the filename.
250 : *
251 : * \return The new filename or an empty string if no project name or filename
252 : * are specified.
253 : */
254 509 : std::string insert_project_name(std::string const & filename
255 : , char const * project_name)
256 : {
257 509 : if(project_name == nullptr
258 508 : || *project_name == '\0'
259 1016 : || filename.empty())
260 : {
261 3 : return std::string();
262 : }
263 :
264 506 : std::string::size_type const pos(filename.find_last_of('/'));
265 506 : if(pos != std::string::npos
266 230 : && pos > 0)
267 : {
268 460 : return filename.substr(0, pos + 1)
269 460 : + project_name
270 460 : + ".d"
271 690 : + filename.substr(pos);
272 : }
273 :
274 276 : return project_name + (".d/" + filename);
275 : }
276 :
277 :
278 : /** \brief Replace a starting `~/...` with the contents of the \$HOME variable.
279 : *
280 : * This function checks the beginning of \p filename. If it starts with `'~/'`
281 : * then it replaces the `'~'` character with the contents of the \$HOME
282 : * environment variable.
283 : *
284 : * If \p filename is just `"~"`, then the function returns the contents of
285 : * the \$HOME environment variable by itself.
286 : *
287 : * If somehow the \$HOME environment variable is empty, the function does
288 : * nothing.
289 : *
290 : * \param[in] filename The filename to check for a tilde (~).
291 : *
292 : * \return The input as is unless the \$HOME path can be prepended to replace
293 : * the tilde (~) character.
294 : */
295 562 : std::string handle_user_directory(std::string const & filename)
296 : {
297 562 : char const * const home(getenv("HOME"));
298 562 : if(home != nullptr
299 404 : && *home != '\0')
300 : {
301 804 : if(!filename.empty()
302 402 : && filename[0] == '~'
303 458 : && (filename.length() == 1 || filename[1] == '/'))
304 : {
305 56 : return home + filename.substr(1);
306 : }
307 : }
308 :
309 506 : return filename;
310 : }
311 :
312 :
313 :
314 : } // namespace advgetopt
315 : // vim: ts=4 sw=4 et
|