]> git.ipfire.org Git - thirdparty/ccache.git/blob - src/util/string.cpp
Move exceptions.hpp to core
[thirdparty/ccache.git] / src / util / string.cpp
1 // Copyright (C) 2021 Joel Rosdahl and other contributors
2 //
3 // See doc/AUTHORS.adoc for a complete list of contributors.
4 //
5 // This program is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU General Public License as published by the Free
7 // Software Foundation; either version 3 of the License, or (at your option)
8 // any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but WITHOUT
11 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 // more details.
14 //
15 // You should have received a copy of the GNU General Public License along with
16 // this program; if not, write to the Free Software Foundation, Inc., 51
17 // Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
19 #include "string.hpp"
20
21 #include <fmtmacros.hpp>
22
23 #include <cctype>
24
25 namespace util {
26
27 nonstd::expected<int64_t, std::string>
28 parse_signed(const std::string& value,
29 const nonstd::optional<int64_t> min_value,
30 const nonstd::optional<int64_t> max_value,
31 const nonstd::string_view description)
32 {
33 const std::string stripped_value = strip_whitespace(value);
34
35 size_t end = 0;
36 long long result = 0;
37 bool failed = false;
38 try {
39 // Note: sizeof(long long) is guaranteed to be >= sizeof(int64_t)
40 result = std::stoll(stripped_value, &end, 10);
41 } catch (std::exception&) {
42 failed = true;
43 }
44 if (failed || end != stripped_value.size()) {
45 return nonstd::make_unexpected(
46 FMT("invalid integer: \"{}\"", stripped_value));
47 }
48
49 const int64_t min = min_value ? *min_value : INT64_MIN;
50 const int64_t max = max_value ? *max_value : INT64_MAX;
51 if (result < min || result > max) {
52 return nonstd::make_unexpected(
53 FMT("{} must be between {} and {}", description, min, max));
54 } else {
55 return result;
56 }
57 }
58
59 nonstd::expected<mode_t, std::string>
60 parse_umask(const std::string& value)
61 {
62 return util::parse_unsigned(value, 0, 0777, "umask", 8);
63 }
64
65 nonstd::expected<uint64_t, std::string>
66 parse_unsigned(const std::string& value,
67 const nonstd::optional<uint64_t> min_value,
68 const nonstd::optional<uint64_t> max_value,
69 const nonstd::string_view description,
70 const int base)
71 {
72 const std::string stripped_value = strip_whitespace(value);
73
74 size_t end = 0;
75 unsigned long long result = 0;
76 bool failed = false;
77 if (starts_with(stripped_value, "-")) {
78 failed = true;
79 } else {
80 try {
81 // Note: sizeof(unsigned long long) is guaranteed to be >=
82 // sizeof(uint64_t)
83 result = std::stoull(stripped_value, &end, base);
84 } catch (std::exception&) {
85 failed = true;
86 }
87 }
88 if (failed || end != stripped_value.size()) {
89 const auto base_info = base == 8 ? "octal " : "";
90 return nonstd::make_unexpected(
91 FMT("invalid unsigned {}integer: \"{}\"", base_info, stripped_value));
92 }
93
94 const uint64_t min = min_value ? *min_value : 0;
95 const uint64_t max = max_value ? *max_value : UINT64_MAX;
96 if (result < min || result > max) {
97 return nonstd::make_unexpected(
98 FMT("{} must be between {} and {}", description, min, max));
99 } else {
100 return result;
101 }
102 }
103
104 nonstd::expected<std::string, std::string>
105 percent_decode(nonstd::string_view string)
106 {
107 const auto from_hex = [](const char digit) {
108 return static_cast<uint8_t>(
109 std::isdigit(digit) ? digit - '0' : std::tolower(digit) - 'a' + 10);
110 };
111
112 std::string result;
113 result.reserve(string.size());
114 for (size_t i = 0; i < string.size(); ++i) {
115 if (string[i] != '%') {
116 result += string[i];
117 } else if (i + 2 >= string.size() || !std::isxdigit(string[i + 1])
118 || !std::isxdigit(string[i + 2])) {
119 return nonstd::make_unexpected(
120 FMT("invalid percent-encoded string at position {}: {}", i, string));
121 } else {
122 const char ch = static_cast<char>(from_hex(string[i + 1]) << 4
123 | from_hex(string[i + 2]));
124 result += ch;
125 i += 2;
126 }
127 }
128
129 return result;
130 }
131
132 std::pair<nonstd::string_view, nonstd::optional<nonstd::string_view>>
133 split_once(const nonstd::string_view string, const char split_char)
134 {
135 const size_t sep_pos = string.find(split_char);
136 if (sep_pos == nonstd::string_view::npos) {
137 return std::make_pair(string, nonstd::nullopt);
138 } else {
139 return std::make_pair(string.substr(0, sep_pos),
140 string.substr(sep_pos + 1));
141 }
142 }
143
144 std::string
145 strip_whitespace(const nonstd::string_view string)
146 {
147 const auto is_space = [](const int ch) { return std::isspace(ch); };
148 const auto start = std::find_if_not(string.begin(), string.end(), is_space);
149 const auto end =
150 std::find_if_not(string.rbegin(), string.rend(), is_space).base();
151 return start < end ? std::string(start, end) : std::string();
152 }
153
154 } // namespace util