advgetopt 2.0.47
Parse complex command line arguments and configuration files in C++.
utils.cpp
Go to the documentation of this file.
1// Copyright (c) 2006-2024 Made to Order Software Corp. All Rights Reserved
2//
3// https://snapwebsites.org/project/advgetopt
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
28// self
29//
30#include "advgetopt/utils.h"
31
32#include "advgetopt/exception.h"
33
34
35// snapdev
36//
37#include <snapdev/glob_to_list.h>
38#include <snapdev/isatty.h>
39#include <snapdev/not_used.h>
40#include <snapdev/trim_string.h>
41
42
43// cppthread
44//
45#include <cppthread/guard.h>
46#include <cppthread/mutex.h>
47
48
49// C++
50//
51#include <cstring>
52#include <iomanip>
53#include <set>
54#include <sstream>
55
56
57// C
58//
59#include <sys/ioctl.h>
60#include <sys/stat.h>
61#include <unistd.h>
62
63
64
65
66// last include
67//
68#include <snapdev/poison.h>
69
70
71
72namespace advgetopt
73{
74
75
76
77namespace
78{
79
80
81
91cppthread::mutex * g_mutex;
92
93
94
95constexpr char const g_single_quote = '\'';
96constexpr char const * g_empty_string = "\"\"";
97constexpr char const * g_escaped_single_quotes = "'\\''";
98constexpr char const * g_simple_characters = "+-./0123456789=ABCEFGHIJKLMNOPQRSTUVWXYZabcefghijklmnopqrstuvwxyz_";
99
100
101
102}
103// no name namespace
104
105
106
123cppthread::mutex & get_global_mutex()
124{
125 {
126 cppthread::guard lock(*cppthread::g_system_mutex);
127
128 if(g_mutex == nullptr)
129 {
130 g_mutex = new cppthread::mutex();
131 }
132 }
133
134 return *g_mutex;
135}
136
137
138
168std::string unquote(std::string const & s, std::string const & pairs)
169{
170 if(s.length() >= 2)
171 {
172 std::string::size_type const max(pairs.length() - 1);
173 for(std::string::size_type pos(0); pos < max; pos += 2)
174 {
175 if(s.front() == pairs[pos + 0]
176 && s.back() == pairs[pos + 1])
177 {
178 return s.substr(1, s.length() - 2);
179 }
180 }
181 }
182
183 return s;
184}
185
186
201std::string quote(std::string const & s, char open, char close)
202{
203 std::string result;
204
205 if(close == '\0')
206 {
207 close = open;
208 }
209
210 result += open;
211 for(auto const c : s)
212 {
213 if(c == open
214 || c == close)
215 {
216 result += '\\';
217 }
218 result += c;
219 }
220 result += close;
221
222 return result;
223}
224
225
259std::string option_with_dashes(std::string const & s)
260{
261 std::string result;
262 result.reserve(s.length());
263 for(auto const & c : s)
264 {
265 if(c == '_')
266 {
267 result += '-';
268 }
269 else
270 {
271 result += c;
272 }
273 }
274 return result;
275}
276
277
288std::string option_with_underscores(std::string const & s)
289{
290 std::string result;
291 result.reserve(s.length());
292 for(auto const & c : s)
293 {
294 if(c == '-')
295 {
296 result += '_';
297 }
298 else
299 {
300 result += c;
301 }
302 }
303 return result;
304}
305
306
347void split_string(std::string const & str
349 , string_list_t const & separators)
350{
351 std::string::size_type pos(0);
352 std::string::size_type start(0);
353 while(pos < str.length())
354 {
355 if(str[pos] == '\'' || str[pos] == '"')
356 {
357 if(start < pos)
358 {
359 std::string const v(snapdev::trim_string(str.substr(start, pos - start)));
360 if(!v.empty())
361 {
362 result.push_back(v);
363 }
364 start = pos;
365 }
366
367 // quoted parameters are handled without the separators
368 //
369 char const quote(str[pos]);
370 for(++pos; pos < str.length() && str[pos] != quote; ++pos);
371
372 std::string const v(str.substr(start + 1, pos - (start + 1)));
373 if(!v.empty())
374 {
375 result.push_back(v);
376 }
377 if(pos < str.length())
378 {
379 // skip the closing quote
380 //
381 ++pos;
382 }
383 start = pos;
384 }
385 else
386 {
387 bool found(false);
388 for(auto const & sep : separators)
389 {
390 if(str.length() - pos >= sep.length())
391 {
392 if(str.compare(pos, sep.length(), sep) == 0)
393 {
394 // match! cut here
395 //
396 if(start < pos)
397 {
398 std::string const v(snapdev::trim_string(str.substr(start, pos - start)));
399 if(!v.empty())
400 {
401 result.push_back(v);
402 }
403 }
404 pos += sep.length();
405 start = pos;
406 found = true;
407 break;
408 }
409 }
410 }
411
412 if(!found)
413 {
414 ++pos;
415 }
416 }
417 }
418
419 if(start < pos)
420 {
421 std::string const v(snapdev::trim_string(str.substr(start, pos - start)));
422 if(!v.empty())
423 {
424 result.push_back(v);
425 }
426 }
427}
428
429
467 std::string const & filename
468 , char const * group_name
469 , char const * project_name
471{
472 if(filename.empty())
473 {
474 return string_list_t();
475 }
476
477 std::string name;
478 if(group_name == nullptr
479 || *group_name == '\0')
480 {
481 if(project_name == nullptr
482 || *project_name == '\0')
483 {
484 return string_list_t();
485 }
486 name = project_name;
487 }
488 else
489 {
490 name = group_name;
491 }
492
493 std::string pattern;
494 std::string::size_type const pos(filename.find_last_of('/'));
495 if(pos == 0)
496 {
498 "filename \""
499 + filename
500 + "\" last slash (/) is at the start, which is not allowed.");
501 }
502 if(pos != std::string::npos
503 && pos > 0)
504 {
505 pattern = filename.substr(0, pos + 1)
506 + name
507 + ".d/[0-9][0-9]-"
508 + filename.substr(pos + 1);
509 }
510 else
511 {
512 pattern = name
513 + ".d/[0-9][0-9]-"
514 + filename;
515 }
516
517 // we use an std::set so the resulting list is sorted
518 //
519 snapdev::glob_to_list<std::set<std::string>> glob;
520
521 // the glob() function is not thread safe
522 {
523 cppthread::guard lock(get_global_mutex());
524 snapdev::NOT_USED(glob.read_path<snapdev::glob_to_list_flag_t::GLOB_FLAG_IGNORE_ERRORS>(pattern));
525 }
526
527 // we add the default name if none other exists
528 //
530 && glob.empty())
531 {
534 , group_name
535 , project_name));
536 }
537
538 return string_list_t(glob.begin(), glob.end());
539}
540
541
586 std::string const & filename
587 , char const * group_name
588 , char const * project_name
589 , int priority)
590{
592 {
594 "priority must be a number between 0 and 99 inclusive; "
595 + std::to_string(priority)
596 + " is invalid.");
597 }
598
599 if(filename.empty())
600 {
601 return std::string();
602 }
603
604 char const * name(nullptr);
605 if(group_name == nullptr
606 || *group_name == '\0')
607 {
608 if(project_name == nullptr
609 || *project_name == '\0')
610 {
611 return std::string();
612 }
613 name = project_name;
614 }
615 else
616 {
617 name = group_name;
618 }
619
620 std::string::size_type const pos(filename.find_last_of('/'));
621 if(pos == 0)
622 {
623 throw getopt_root_filename("filename \"" + filename + "\" starts with a slash (/), which is not allowed.");
624 }
625
626 std::string result;
627 result.reserve(filename.length() + strlen(name) + 6);
628 if(pos != std::string::npos)
629 {
630 result = filename.substr(0, pos + 1);
631 }
632 result += name;
633 result += ".d/";
634 if(priority < 10)
635 {
636 result += '0';
637 }
638 result += std::to_string(priority);
639 result += '-';
640 if(pos == std::string::npos)
641 {
642 result += filename;
643 }
644 else
645 {
646 result += filename.substr(pos + 1);
647 }
648
649 return result;
650}
651
652
676std::string handle_user_directory(std::string const & filename)
677{
678 if(!filename.empty()
679 && filename[0] == '~'
680 && (filename.length() == 1 || filename[1] == '/'))
681 {
682 char const * const home(getenv("HOME"));
683 if(home != nullptr
684 && *home != '\0')
685 {
686 return home + filename.substr(1);
687 }
688 }
689
690 return filename;
691}
692
693
709bool is_true(std::string s)
710{
711 return s == "true" || s == "on" || s == "yes" | s == "1";
712}
713
714
730bool is_false(std::string s)
731{
732 return s == "false" || s == "off" || s == "no" || s == "0";
733}
734
735
747{
748 std::int64_t cols(80);
749
751 {
752// LCOV_EXCL_START
753 // when running coverage, the output is redirected for logging purposes
754 // which means that isatty() returns false -- so at this time I just
755 // exclude those since they are unreachable from my standard Unit Tests
756 //
757 winsize w;
758 if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1)
759 {
760 cols = std::max(static_cast<unsigned short>(40), w.ws_col);
761 }
762// LCOV_EXCL_STOP
763 }
764
765 return cols;
766}
767
768
791std::string breakup_line(
792 std::string line
793 , size_t const option_width
794 , size_t const line_width)
795{
796 std::stringstream ss;
797
798 size_t const width(line_width - option_width);
799
800 // TODO: once we have C++17, avoid substr() using std::string_view instead
801 //
802 for(;;)
803 {
804 std::string l;
805 std::string::size_type const nl(line.find('\n'));
806 if(nl != std::string::npos
807 && nl < width)
808 {
809 l = line.substr(0, nl);
810 line = line.substr(nl + 1);
811 }
812 else if(line.size() <= width)
813 {
814 break;
815 }
816 else if(std::isspace(line[width]))
817 {
818 // special case when the space is right at the edge
819 //
820 l = line.substr(0, width);
821 size_t pos(width);
822 do
823 {
824 ++pos;
825 }
826 while(std::isspace(line[pos]));
827 line = line.substr(pos);
828 }
829 else
830 {
831 // search for the last space before the edge of the screen
832 //
833 std::string::size_type pos(line.find_last_of(' ', width));
834 if(pos == std::string::npos)
835 {
836 // no space found, cut right at the edge...
837 // (this should be really rare)
838 //
839 l = line.substr(0, width);
840 line = line.substr(width);
841 }
842 else
843 {
844 // we found a space, write everything up to that space
845 //
846 l = line.substr(0, pos);
847
848 // remove additional spaces from the start of the next line
849 do // LCOV_EXCL_LINE
850 {
851 ++pos;
852 }
853 while(std::isspace(line[pos]));
854 line = line.substr(pos);
855 }
856 }
857
858 ss << l
859 << std::endl;
860
861 // more to print? if so we need the indentation
862 //
863 if(!line.empty()
864 && option_width > 0)
865 {
866 ss << std::setw(option_width) << " ";
867 }
868 }
869
870 // some leftover?
871 //
872 if(!line.empty())
873 {
874 ss << line << std::endl;
875 }
876
877 return ss.str();
878}
879
880
900 std::string const & argument
901 , std::string const & help
902 , size_t const option_width
903 , size_t const line_width)
904{
905 std::stringstream ss;
906
907 ss << " ";
908
909 if(argument.size() < option_width - 3)
910 {
911 // enough space on a single line
912 //
913 ss << argument
914 << std::setw(option_width - 3 - argument.size())
915 << " ";
916 }
917 else if(argument.size() >= line_width - 4)
918 {
919 // argument too long for even one line on the screen!?
920 // call the function to break it up with indentation of 3
921 //
923
924 if(!help.empty()
925 && option_width > 0)
926 {
927 ss << std::setw(option_width) << " ";
928 }
929 }
930 else
931 {
932 // argument too long for the help to follow immediately
933 //
934 ss << argument
935 << std::endl
936 << std::setw(option_width)
937 << " ";
938 }
939
941
942 return ss.str();
943}
944
945
959std::string escape_shell_argument(std::string const & arg)
960{
961 if(arg.empty())
962 {
963 return std::string(g_empty_string);
964 }
965
966 std::string::size_type const pos(arg.find_first_not_of(g_simple_characters));
967 if(pos == std::string::npos)
968 {
969 return arg;
970 }
971
972 std::string result;
973
974 result += g_single_quote;
975 std::string::size_type p1(0);
976 while(p1 < arg.length())
977 {
978 std::string::size_type const p2(arg.find('\'', p1));
979 if(p2 == std::string::npos)
980 {
981 result += arg.substr(p1);
982 break;
983 }
984 result += arg.substr(p1, p2 - p1);
985 result += g_escaped_single_quotes;
986 p1 = p2 + 1; // skip the '
987 }
988 result += g_single_quote;
989
990 return result;
991}
992
993
1006{
1007 std::string result;
1008#if defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__)
1009#if defined(__SANITIZE_ADDRESS__)
1010 result += "The address sanitizer is compiled in.\n";
1011#endif
1012#if defined(__SANITIZE_THREAD__)
1013 result += "The thread sanitizer is compiled in.\n";
1014#endif
1015#else
1016 result += "The address and thread sanitizers are not compiled in.\n";
1017#endif
1018 return result;
1019}
1020
1021
1036// LCOV_EXCL_START
1038{
1039 std::int64_t rows(25);
1040
1042 {
1043 // when running coverage, the output is redirected for logging purposes
1044 // which means that isatty() returns false -- so at this time I just
1045 // exclude those since they are unreachable from my standard Unit Tests
1046 //
1047 winsize w;
1048 if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1)
1049 {
1050 rows = std::max(static_cast<unsigned short>(2), w.ws_row);
1051 }
1052 }
1053
1054 return rows;
1055}
1056// LCOV_EXCL_STOP
1057
1058
1069void less(std::basic_ostream<char> & out, std::string const & data)
1070{
1071 if(snapdev::isatty(out))
1072 {
1073// LCOV_EXCL_START
1074 auto const lines(std::count(data.begin(), data.end(), '\n'));
1075 size_t const height(get_screen_height());
1076 if(lines > static_cast<std::remove_const_t<decltype(lines)>>(height))
1077 {
1078 struct stat s;
1079 if(stat("/bin/less", &s) == 0)
1080 {
1081 FILE * f(popen("/bin/less", "w"));
1082 if(f != nullptr)
1083 {
1084 fwrite(data.c_str(), sizeof(char), data.length(), f);
1085 pclose(f);
1086 return;
1087 }
1088 }
1089 else if(stat("/bin/more", &s) == 0)
1090 {
1091 FILE * f(popen("/bin/more", "w"));
1092 if(f != nullptr)
1093 {
1094 fwrite(data.c_str(), sizeof(char), data.length(), f);
1095 pclose(f);
1096 return;
1097 }
1098 }
1099 }
1100// LCOV_EXCL_STOP
1101 }
1102
1103 // fallback, just print everything to the console as is
1104 //
1105 out << data << std::endl;
1106}
1107
1108
1109
1110} // namespace advgetopt
1111// vim: ts=4 sw=4 et
Definitions of the advanced getopt exceptions.
constexpr char const * g_escaped_single_quotes
Definition utils.cpp:97
cppthread::mutex * g_mutex
The configuration file mutex.
Definition utils.cpp:91
constexpr char const * g_empty_string
Definition utils.cpp:96
constexpr char const g_single_quote
Definition utils.cpp:95
constexpr char const * g_simple_characters
Definition utils.cpp:98
The advgetopt environment to parse command line options.
size_t get_screen_width()
Retrieve the width of one line in your console.
Definition utils.cpp:746
string_list_t insert_group_name(std::string const &filename, char const *group_name, char const *project_name, bool add_default_on_empty)
Insert the group (or project) name in the filename.
Definition utils.cpp:466
void split_string(std::string const &str, string_list_t &result, string_list_t const &separators)
Split a string in sub-strings separated by separators.
Definition utils.cpp:347
bool is_false(std::string s)
Check whether a value represents "false".
Definition utils.cpp:730
std::string breakup_line(std::string line, size_t const option_width, size_t const line_width)
Breakup a string on multiple lines.
Definition utils.cpp:791
constexpr flag_t option_flags_merge()
Definition flags.h:87
void less(std::basic_ostream< char > &out, std::string const &data)
Print out a string to the console or use less.
Definition utils.cpp:1069
std::string format_usage_string(std::string const &argument, std::string const &help, size_t const option_width, size_t const line_width)
Format a help string to make it fit on a given width.
Definition utils.cpp:899
cppthread::mutex & get_global_mutex()
Get a global mutex.
Definition utils.cpp:123
bool is_true(std::string s)
Check whether a value represents "true".
Definition utils.cpp:709
std::string option_with_underscores(std::string const &s)
Converts an option back to using underscores.
Definition utils.cpp:288
std::string escape_shell_argument(std::string const &arg)
Escape special characters from a shell argument.
Definition utils.cpp:959
std::string quote(std::string const &s, char open, char close)
The converse of unquote.
Definition utils.cpp:201
std::string sanitizer_details()
Generate a string describing whether we're using the sanitizer.
Definition utils.cpp:1005
size_t get_screen_height()
Retrieve the height of your console.
Definition utils.cpp:1037
std::string unquote(std::string const &s, std::string const &pairs)
Remove single (') or double (") quotes from a string.
Definition utils.cpp:168
std::string option_with_dashes(std::string const &s)
Convert the _ found in a string to - instead.
Definition utils.cpp:259
std::vector< std::string > string_list_t
Definition utils.h:41
std::string handle_user_directory(std::string const &filename)
Replace a starting ~/... with the contents of the $HOME variable.
Definition utils.cpp:676
std::string default_group_name(std::string const &filename, char const *group_name, char const *project_name, int priority)
Generate the default filename (the ".../50-...")
Definition utils.cpp:585
A few utility functions that are not specific to an object.

This document is part of the Snap! Websites Project.

Copyright by Made to Order Software Corp.