Sese Framework  2.3.0
A cross-platform framework
Loading...
Searching...
No Matches
Format.h
Go to the documentation of this file.
1// Copyright 2024 libsese
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
19
20#pragma once
21
22#include <sese/Config.h>
24#include <sese/text/Number.h>
27#include <sese/types/is_pair.h>
28#include <sese/Util.h>
29
30#include <cassert>
31#include <cmath>
32
33namespace sese::text {
34
35struct FmtCtx {
37 std::string_view pattern;
38 std::string_view::const_iterator pos;
39
40 explicit FmtCtx(std::string_view p);
41
42 bool parsing(std::string &args);
43};
44
49bool FormatOption_StringParse(FormatOption &opt, const std::string &opt_str);
50
55void FormatOption_StringFormat(FmtCtx &ctx, FormatOption &opt, const std::string &value);
56
61bool FormatOption_NumberParse(FormatOption &opt, const std::string &opt_str);
62
63template<typename T>
64SESE_ALWAYS_INLINE void FormatOption_NumberFormatAlgin(FmtCtx &ctx, FormatOption &opt, T number, int radix, bool upper_case) {
65 StringBuilder &builder = ctx.builder;
66 auto len = number2StringLength(number, radix);
67 if (opt.wide <= len) {
68 Number::toString(builder, number, radix, upper_case);
69 return;
70 }
71 auto diff = opt.wide - len;
72 switch (opt.align) {
73 case Align::LEFT:
74 Number::toString(builder, number, radix, upper_case);
75 builder << std::string(diff, opt.wide_char); // GCOVR_EXCL_LINE
76 break;
77 case Align::RIGHT:
78 builder << std::string(diff, opt.wide_char); // GCOVR_EXCL_LINE
79 Number::toString(builder, number, radix, upper_case);
80 break;
81 case Align::CENTER:
82 builder << std::string(diff / 2, opt.wide_char); // GCOVR_EXCL_LINE
83 Number::toString(builder, number, radix, upper_case);
84 builder << std::string((diff % 2 == 1 ? (diff / 2 + 1) : (diff / 2)), opt.wide_char); // GCOVR_EXCL_LINE
85 break;
86 }
87}
88
94template<typename T>
95void FormatOption_NumberFormat(FmtCtx &ctx, FormatOption &opt, T number) {
96 auto radix = opt.ext_type;
97 if (radix == 'X') {
98 FormatOption_NumberFormatAlgin(ctx, opt, number, 16, true);
99 } else if (radix == 'x') {
100 FormatOption_NumberFormatAlgin(ctx, opt, number, 16, false);
101 } else if (radix == 'o') {
102 FormatOption_NumberFormatAlgin(ctx, opt, number, 8, true);
103 } else if (radix == 'b') {
104 FormatOption_NumberFormatAlgin(ctx, opt, number, 2, true);
105 } else {
106 FormatOption_NumberFormatAlgin(ctx, opt, number, 10, true);
107 }
108}
109
115template<typename T>
117 if (opt.float_placeholder == 0) {
118 opt.float_placeholder = 1;
119 }
120 StringBuilder &builder = ctx.builder;
121 size_t len;
122 if (opt.ext_type == '%') {
123 number *= 100;
124 len = floating2StringLength(number, opt.float_placeholder);
125 len += 1;
126 } else {
127 len = floating2StringLength(number, opt.float_placeholder);
128 }
129 if (opt.wide <= len) {
130 Number::toString(builder, number, opt.float_placeholder);
131 if (opt.ext_type == '%') {
132 builder.append('%');
133 }
134 return;
135 }
136 auto diff = opt.wide - len;
137 switch (opt.align) {
138 case Align::LEFT:
139 Number::toString(builder, number, opt.float_placeholder);
140 if (opt.ext_type == '%') {
141 builder.append('%');
142 }
143 builder << std::string(diff, opt.wide_char); // GCOVR_EXCL_LINE
144 break;
145 case Align::RIGHT:
146 builder << std::string(diff, opt.wide_char); // GCOVR_EXCL_LINE
147 Number::toString(builder, number, opt.float_placeholder);
148 if (opt.ext_type == '%') {
149 builder.append('%');
150 }
151 break;
152 case Align::CENTER:
153 builder << std::string(diff / 2, opt.wide_char); // GCOVR_EXCL_LINE
154 Number::toString(builder, number, opt.float_placeholder);
155 if (opt.ext_type == '%') {
156 builder.append('%');
157 }
158 builder << std::string((diff % 2 == 1 ? (diff / 2 + 1) : (diff / 2)), opt.wide_char); // GCOVR_EXCL_LINE
159 break;
160 }
161}
162
163namespace overload {
164 template<typename VALUE, typename ENABLE = void>
165 struct Formatter {
166 bool parse(const std::string &) { return false; }
167
168 void format(FmtCtx &, const VALUE &) {}
169 };
170
171 template<>
172 struct Formatter<std::string> {
174
175 bool parse(const std::string &opt_str) { return FormatOption_StringParse(option, opt_str); }
176
177 void format(FmtCtx &ctx, const std::string &value) { FormatOption_StringFormat(ctx, option, value); }
178 };
179
180 template<>
181 struct Formatter<const char *> {
183
184 bool parse(const std::string &opt_str) {
185 return FormatOption_StringParse(option, opt_str);
186 }
187
188 void format(FmtCtx &ctx, const char *value) {
189 FormatOption_StringFormat(ctx, option, value);
190 }
191 };
192
193 template<typename VALUE>
194 struct Formatter<VALUE, std::enable_if_t<std::is_integral_v<VALUE>>> {
196
197 bool parse(const std::string &opt_str) {
198 return FormatOption_NumberParse(option, opt_str);
199 }
200
201 void format(FmtCtx &ctx, const VALUE &value) {
202 FormatOption_NumberFormat(ctx, option, value);
203 }
204 };
205
206 template<typename VALUE>
207 struct Formatter<VALUE, std::enable_if_t<std::is_floating_point_v<VALUE>>> {
209
210 bool parse(const std::string &opt_str) {
211 return FormatOption_NumberParse(option, opt_str);
212 }
213
214 void format(FmtCtx &ctx, const VALUE &value) {
215 if (std::isnan(value)) {
216 ctx.builder << "NaN";
217 } else {
218 FormatOption_FloatNumberFormat<VALUE>(ctx, option, value);
219 }
220 }
221 };
222
223 template<typename VALUE>
224 struct Formatter<VALUE, std::enable_if_t<is_iterable_v<VALUE>>> {
225 char begin_ch = '[';
226 char end_ch = ']';
227 char split_ch = ',';
228
229 bool parse(const std::string &args) {
230 if (args.size() == 3) {
231 begin_ch = args[0];
232 split_ch = args[1];
233 end_ch = args[2];
234 return true;
235 }
236 return false;
237 }
238
239 void format(FmtCtx &ctx, VALUE &value) {
240 StringBuilder &builder = ctx.builder;
241 builder << begin_ch;
242 bool first = true;
243 for (auto &&item: value) {
245 if (first) {
246 first = false;
247 } else {
248 builder << split_ch;
249 }
250 formatter.format(ctx, item);
251 }
252 builder << end_ch;
253 }
254 };
255
256} // namespace overload
257
258template<typename C, std::enable_if_t<!is_pair<typename C::value_type>::value, int> = 0>
259std::string for_each(const C &container) {
260 constexpr char SPLIT_CH = ',';
261 constexpr char BEGIN_CH = '[';
262 constexpr char END_CH = ']';
263 if (container.empty()) {
264 return "[]";
265 }
266 FmtCtx ctx("");
268 bool first = true;
269 ctx.builder << BEGIN_CH;
270 for (const auto &item: container) {
271 if (first) {
272 first = false;
273 } else {
274 ctx.builder << SPLIT_CH << ' ';
275 }
276 formatter.format(ctx, item);
277 }
278 ctx.builder << END_CH;
279 return ctx.builder.toString();
280}
281
282template<typename C, std::enable_if_t<is_pair<typename C::value_type>::value, int> = 0>
283std::string for_each(const C &container) {
284 constexpr char SPLIT_CH = ',';
285 constexpr char BEGIN_CH = '[';
286 constexpr char END_CH = ']';
287 constexpr char PAIR_BEGIN_CH = '{';
288 constexpr char PAIR_END_CH = '}';
289 if (container.empty()) {
290 return "[]";
291 }
292 FmtCtx ctx("");
293 auto key_formatter = overload::Formatter<std::decay_t<typename C::key_type>>();
294 auto value_formatter = overload::Formatter<std::decay_t<typename C::mapped_type>>();
295 bool first = true;
296 ctx.builder << BEGIN_CH;
297 for (auto &&[key, value]: container) {
298 if (first) {
299 first = false;
300 } else {
301 ctx.builder << SPLIT_CH << ' ';
302 }
303 ctx.builder << PAIR_BEGIN_CH;
304 key_formatter.format(ctx, key);
305 ctx.builder << SPLIT_CH << ' ';
306 value_formatter.format(ctx, value);
307 ctx.builder << PAIR_END_CH;
308 }
309 ctx.builder << END_CH;
310 return ctx.builder.toString();
311}
312
313constexpr size_t FormatParameterCounter(const char *pattern) {
314 size_t count = 0;
315 const char *p = pattern;
316 if (*p == '{')
317 count += 1;
318 p++;
319 while (*p != 0) {
320 if (*p == '{' && *(p - 1) != '\\') {
321 count += 1;
322 }
323 p++;
324 }
325 return count;
326}
327
328template<typename T>
329void Format(FmtCtx &ctx, T &&arg) {
330 std::string parsing_args;
331 auto status = ctx.parsing(parsing_args);
332 if (!status) {
333 return;
334 }
335 auto formatter = overload::Formatter<std::decay_t<T>>();
336 if (!parsing_args.empty()) {
337 if (formatter.parse(parsing_args)) {
338 formatter.format(ctx, std::forward<T>(arg));
339 } else {
340 ctx.builder << "!{parsing failed}";
341 }
342 ctx.parsing(parsing_args);
343 } else {
344 formatter.format(ctx, std::forward<T>(arg));
345 ctx.parsing(parsing_args);
346 }
347}
348
349template<typename T, typename... ARGS>
350void Format(FmtCtx &ctx, T &&arg, ARGS &&...args) {
351 std::string parsing_args;
352 auto status = ctx.parsing(parsing_args);
353 if (!status) {
354 return;
355 }
356 auto formatter = overload::Formatter<std::decay_t<T>>();
357 if (!parsing_args.empty()) {
358 if (formatter.parse(parsing_args)) {
359 formatter.format(ctx, std::forward<T>(arg));
360 } else {
361 ctx.builder << "!{parsing failed}";
362 }
363 Format(ctx, std::forward<ARGS>(args)...);
364 } else {
365 formatter.format(ctx, std::forward<T>(arg));
366 Format(ctx, std::forward<ARGS>(args)...);
367 }
368}
369
374template<typename... ARGS, std::enable_if_t<sizeof...(ARGS) == 0, int> = 0>
375std::string fmt(std::string_view pattern, ARGS &&...) {
376 if (FormatParameterCounter(pattern.data())) {
377 return "!{Mismatch in number of parameters}";
378 }
379 return {pattern.begin(), pattern.end()};
380}
381
387template<typename... ARGS, std::enable_if_t<sizeof...(ARGS) != 0, int> = 0>
388std::string fmt(std::string_view pattern, ARGS &&...args) {
389 auto param = FormatParameterCounter(pattern.data());
390 if (param != sizeof...(args)) {
391 return "!{Mismatch in number of parameters}";
392 }
393 FmtCtx ctx(pattern);
394 Format(ctx, std::forward<ARGS>(args)...);
395 return ctx.builder.toString();
396}
397
398} // namespace sese::text