1+ /* *
2+ * @file extensions.hpp
3+ * @author Simon Cahill (simon@simonc.eu)
4+ * @brief Contains useful extension methods that aren't provided by C++ out of the box.
5+ * @version 0.1
6+ * @date 2022-10-15
7+ *
8+ * @copyright Copyright (c) 2022 Simon Cahill
9+ */
10+
11+ #ifndef ENDLESSH_REPORT_INCLUDE_EXTENSIONS_HPP
12+ #define ENDLESSH_REPORT_INCLUDE_EXTENSIONS_HPP
13+
14+ // stl
15+ #include < chrono>
16+ #include < cmath>
17+ #include < functional>
18+ #include < string>
19+ #include < vector>
20+
21+ // libc
22+ #include < regex.h>
23+
24+ // date
25+ #include < date/date.h>
26+
27+ // fmt
28+ #include < fmt/format.h>
29+
30+ using std::chrono::seconds;
31+ using std::chrono::system_clock;
32+ using std::function;
33+ using std::string;
34+ using std::vector;
35+
36+ /* *
37+ * @brief Splits a given string by the passed delimiters in to a vector.
38+ *
39+ * @param str string& The string to split.
40+ * @param delimiters string& A string containing the delimiters to split by (chars).
41+ * @param tokens vector<string>& A vector that will contain the elements.
42+ *
43+ * @return true If tokens were found.
44+ * @return false Otherwise.
45+ */
46+ inline bool splitString (const string& str, const string& delimiters, vector<string> &tokens) {
47+ // Skip delimiters at beginning.
48+ string::size_type lastPos = str.find_first_not_of (delimiters, 0 );
49+ // Find first "non-delimiter".
50+ string::size_type pos = str.find_first_of (delimiters, lastPos);
51+
52+ while (string::npos != pos || string::npos != lastPos) {
53+ // Found a token, add it to the vector.
54+ tokens.push_back (str.substr (lastPos, pos - lastPos));
55+ // Skip delimiters.
56+ lastPos = string::npos == pos ? string::npos : pos + 1 ;
57+ // Find next "non-delimiter"
58+ pos = str.find_first_of (delimiters, lastPos);
59+ }
60+
61+ return tokens.size () > 0 ;
62+ }
63+
64+ /* *
65+ * @brief Matches a string against a regular expression.
66+ *
67+ * @param haystack The string to compare
68+ * @param needle The expression to compare against
69+ *
70+ * @return true If the string matches the expression
71+ * @return false Otherwise
72+ */
73+ inline bool regexMatch (const char * haystack, const char * needle) {
74+ int nRet;
75+ regex_t sRegEx ;
76+
77+ if (regcomp (&sRegEx , needle, REG_EXTENDED | REG_NOSUB) != 0 ) {
78+ return false ;
79+ }
80+
81+ nRet = regexec (&sRegEx , haystack, (size_t ) 0 , NULL , 0 );
82+ regfree (&sRegEx );
83+
84+ if (nRet != 0 ) {
85+ return false ;
86+ }
87+
88+ return true ;
89+ }
90+
91+ /* *
92+ * @brief Rounds a given double to the desired amount of decimal places.
93+ *
94+ * @param x The double to round
95+ * @param decimalPlaces The decimal places the double should be rounded to
96+ *
97+ * @return double The resulting double.
98+ */
99+ inline double roundNumber (const double x, const uint32_t decimalPlaces) {
100+ const double multiplificationFaktor = std::pow (10.0 , decimalPlaces);
101+ return std::ceil (x * multiplificationFaktor) / multiplificationFaktor;
102+ }
103+
104+ /* *
105+ * @brief Gets the current timestamp as an ISO timestamp, accurate to the nearest second.
106+ *
107+ * @return string A string containing the current timestamp in ISO format.
108+ */
109+ inline string getCurrentIsoTimestamp () {
110+ auto timeNow = system_clock::now ();
111+ return date::format (" %FT%TZ" , date::floor<seconds>(timeNow));
112+ }
113+
114+ /* *
115+ * @brief Converts a number of bytes to a human-readable format, such as KiB, MiB, GiB, ...
116+ *
117+ * @param bytes The bytes to convert.
118+ *
119+ * @return string The human-readable string.
120+ */
121+ inline string getHumanReadableBytes (const size_t bytes) {
122+ const static size_t ONE_KIB = 1024 ;
123+ const static size_t ONE_MIB = ONE_KIB * ONE_KIB;
124+ const static size_t ONE_GIB = ONE_MIB * ONE_MIB;
125+
126+ if (bytes > ONE_GIB) { return fmt::format (" {0:.2f}GiB" , static_cast <double >(bytes / ONE_GIB)); }
127+ if (bytes > ONE_MIB) { return fmt::format (" {0:.2f}MiB" , static_cast <double >(bytes / ONE_MIB)); }
128+ if (bytes > ONE_KIB) { return fmt::format (" {0:.2f}KiB" , static_cast <double >(bytes / ONE_KIB)); }
129+
130+ // We're likely less than a KiB
131+ return fmt::format (" {0:d}B" , bytes);
132+ }
133+
134+ /* *
135+ * @brief Converts n seconds into a human-readable form. E.g. 300s would become 5 minutes
136+ *
137+ * @param seconds The amount of seconds to convert.
138+ *
139+ * @return string The human-readable string
140+ */
141+ inline string getHumanReadableTime (const double seconds) {
142+ if (seconds < 60 ) { return fmt::format (" {0:.0f}s" , floor (seconds)); }
143+
144+ string result{};
145+ time_t secondsAsTime = static_cast <time_t >(seconds);
146+ struct tm timeStruct = {0 };
147+ gmtime_r (&secondsAsTime, &timeStruct);
148+
149+ if (timeStruct.tm_yday > 0 ) {
150+ result += fmt::format (" {0:d}d " , timeStruct.tm_yday );
151+ } if (timeStruct.tm_hour > 0 ) {
152+ result += fmt::format (" {0:d}h " , timeStruct.tm_hour );
153+ } if (timeStruct.tm_min > 0 ) {
154+ result += fmt::format (" {0:d}m " , timeStruct.tm_min );
155+ } if (timeStruct.tm_sec > 0 ) {
156+ result += fmt::format (" {0:d}s" , timeStruct.tm_sec );
157+ }
158+
159+ return result;
160+ }
161+
162+ /* *
163+ * @brief Gets a string containing whitespace to centre text
164+ *
165+ * @param totalWidth The total width of the area where the string should be centred
166+ * @param strLength The length of the string to be centre-printed
167+ *
168+ * @return string The spacer string
169+ */
170+ inline string getSpacerString (const uint32_t totalWidth, const uint32_t strLength) {
171+ return string (totalWidth / 2 - strLength / 2 , ' ' );
172+ }
173+
174+ /* *
175+ * @brief Trims the beginning of a given string.
176+ *
177+ * @param nonTrimmed The non-trimmed string.
178+ * @param trimChar The character to trim off the string. (default: space)
179+ *
180+ * @return The trimmed string.
181+ */
182+ inline string trimStart (string nonTrimmed, const string& trimChar) {
183+ // nonTrimmed.erase(nonTrimmed.begin(), find_if(nonTrimmed.begin(), nonTrimmed.end(), not1(ptr_fun<int32_t, int32_t>(isspace))));
184+
185+ function<bool (char )> shouldTrimChar = [=](char c) -> bool { return trimChar.size () == 0 ? isspace (c) : trimChar.find (c) != string::npos; };
186+
187+ nonTrimmed.erase (nonTrimmed.begin (), find_if (nonTrimmed.begin (), nonTrimmed.end (), not1 (shouldTrimChar)));
188+
189+ return nonTrimmed;
190+ }
191+
192+ /* *
193+ * @brief Trims the end of a given string.
194+ * @param nonTrimmed The non-trimmed string.
195+ * @param trimChar The character to trim off the string. (default: space)
196+ *
197+ * @return The trimmed string.
198+ */
199+ inline string trimEnd (string nonTrimmed, const string& trimChar) {
200+ // nonTrimmed.erase(find_if(nonTrimmed.rbegin(), nonTrimmed.rend(), not1(ptr_fun<int32_t, int32_t>(isspace))).base(), nonTrimmed.end());
201+
202+ function<bool (char )> shouldTrimChar = [=](char c) -> bool { return trimChar.size () == 0 ? isspace (c) : trimChar.find (c) != string::npos; };
203+ nonTrimmed.erase (find_if (nonTrimmed.rbegin (), nonTrimmed.rend (), not1 (shouldTrimChar)).base (), nonTrimmed.end ());
204+
205+ return nonTrimmed;
206+ }
207+
208+ /* *
209+ * @brief Trims both the beginning and the end of a given string.
210+ *
211+ * @param nonTrimmed The non-trimmed string.
212+ * @param trimChar The character to trim off the string. (default: space)
213+ *
214+ * @return The trimmed string.
215+ */
216+ inline string trim (const string& nonTrimmed, const string& trimChar) { return trimStart (trimEnd (nonTrimmed, trimChar), trimChar); }
217+
218+
219+ #endif // ENDLESSH_REPORT_INCLUDE_EXTENSIONS_HPP
0 commit comments