Console Library 8.0.0
A header-only library that makes C++ simple
Loading...
Searching...
No Matches
fmt.h
Go to the documentation of this file.
1
9
10/*
11Copyright (c) 2026 MrXie1109
12
13Permission is hereby granted, free of charge, to any person obtaining a copy
14of this software and associated documentation files (the "Software"), to deal
15in the Software without restriction, including without limitation the rights
16to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17copies of the Software, and to permit persons to whom the Software is
18furnished to do so, subject to the following conditions:
19
20The above copyright notice and this permission notice shall be included in all
21copies or substantial portions of the Software.
22
23THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29SOFTWARE.
30*/
31
32#pragma once
33#include <cctype>
34#include <iomanip>
35#include <iostream>
36#include <sstream>
37#include <stdexcept>
38#include <string>
39
40#include "../core/csexc.h"
41#include "../io/output.h"
42
43namespace console {
44 namespace details {
49 template <class CharT = char>
55 CharT fill = static_cast<CharT>(' ');
60 CharT align = static_cast<CharT>('>');
65 int width = 0;
70 int precision = -1;
77 CharT type = 0;
82 bool hasHash = false;
87 bool hasPlus = false;
92 bool hasSpace = false;
93 };
94
102 template <class CharT = char>
103 void parse_format_spec(const std::basic_string<CharT> &specStr,
105 size_t i = 0;
106 size_t len = specStr.size();
107 if (len == 0) return;
108
109 if (i + 1 < len) {
110 CharT first = specStr[i];
111 CharT second = specStr[i + 1];
112 if (first == static_cast<CharT>('0')
113 && std::isdigit(static_cast<unsigned char>(second))) {
114 spec.fill = first;
115 spec.align = static_cast<CharT>('>');
116 i += 1;
117 } else if (second == static_cast<CharT>('<')
118 || second == static_cast<CharT>('>')) {
119 if (first == static_cast<CharT>('{')
120 || first == static_cast<CharT>('}')) {
121 throw BadFormat("invalid fill character");
122 }
123 spec.fill = first;
124 spec.align = second;
125 i += 2;
126 } else if (first == static_cast<CharT>('<')
127 || first == static_cast<CharT>('>')) {
128 spec.align = first;
129 i += 1;
130 }
131 }
132
133 if (i < len
134 && std::isdigit(static_cast<unsigned char>(specStr[i]))) {
135 size_t start = i;
136 while (i < len
137 && std::isdigit(static_cast<unsigned char>(specStr[i])))
138 i++;
139 spec.width = std::stoi(
140 std::string(specStr.begin() + start, specStr.begin() + i));
141 }
142
143 if (i < len && specStr[i] == static_cast<CharT>(':')) {
144 i++;
145 while (i < len
146 && (specStr[i] == static_cast<CharT>('#')
147 || specStr[i] == static_cast<CharT>('+')
148 || specStr[i] == static_cast<CharT>(' '))) {
149 if (specStr[i] == static_cast<CharT>('#'))
150 spec.hasHash = true;
151 else if (specStr[i] == static_cast<CharT>('+'))
152 spec.hasPlus = true;
153 else if (specStr[i] == static_cast<CharT>(' '))
154 spec.hasSpace = true;
155 i++;
156 }
157 if (i < len && specStr[i] == static_cast<CharT>('.')) {
158 i++;
159 size_t start = i;
160 while (
161 i < len
162 && std::isdigit(static_cast<unsigned char>(specStr[i])))
163 i++;
164 if (start == i) throw BadFormat("missing precision");
165 spec.precision = std::stoi(std::string(
166 specStr.begin() + start, specStr.begin() + i));
167 }
168 if (i < len
169 && std::isalpha(static_cast<unsigned char>(specStr[i]))) {
170 spec.type = specStr[i];
171 i++;
172 }
173 if (i < len) {
174 throw BadFormat(
175 "unexpected characters at end: "
176 + std::string(specStr.begin() + i, specStr.end()));
177 }
178 } else if (i < len) {
179 std::basic_string<CharT> remaining = specStr.substr(i);
180 if (remaining[0] == static_cast<CharT>('.')) {
181 i++;
182 size_t start = i;
183 while (
184 i < len
185 && std::isdigit(static_cast<unsigned char>(specStr[i])))
186 i++;
187 if (start == i) throw BadFormat("missing precision");
188 spec.precision = std::stoi(std::string(
189 specStr.begin() + start, specStr.begin() + i));
190 if (i < len
191 && std::isalpha(
192 static_cast<unsigned char>(specStr[i]))) {
193 spec.type = specStr[i];
194 i++;
195 }
196 if (i < len) {
197 throw BadFormat(
198 "unexpected characters after precision: "
199 + std::string(specStr.begin() + i, specStr.end()));
200 }
201 } else {
202 // 标志 + [.精度] + [类型]
203 size_t j = 0;
204 while (j < remaining.size()
205 && (remaining[j] == static_cast<CharT>('#')
206 || remaining[j] == static_cast<CharT>('+')
207 || remaining[j] == static_cast<CharT>(' '))) {
208 if (remaining[j] == static_cast<CharT>('#'))
209 spec.hasHash = true;
210 else if (remaining[j] == static_cast<CharT>('+'))
211 spec.hasPlus = true;
212 else if (remaining[j] == static_cast<CharT>(' '))
213 spec.hasSpace = true;
214 j++;
215 }
216 if (j < remaining.size()
217 && remaining[j] == static_cast<CharT>('.')) {
218 j++;
219 size_t prec_start = j;
220 while (j < remaining.size()
221 && std::isdigit(
222 static_cast<unsigned char>(remaining[j])))
223 j++;
224 if (prec_start == j)
225 throw BadFormat("missing precision");
226 spec.precision = std::stoi(
227 std::string(remaining.begin() + prec_start,
228 remaining.begin() + j));
229 }
230 if (j < remaining.size()) {
231 if (remaining.size() - j == 1
232 && std::isalpha(
233 static_cast<unsigned char>(remaining[j]))) {
234 spec.type = remaining[j];
235 j++;
236 } else {
237 throw BadFormat("unexpected character '"
238 + std::string(1,
239 static_cast<char>(remaining[j]))
240 + "'");
241 }
242 }
243 if (j < remaining.size()) {
244 throw BadFormat("unexpected characters: "
245 + std::string(remaining.begin() + j,
246 remaining.end()));
247 }
248 }
249 }
250
251 if (spec.hasHash) {
252 char t = static_cast<char>(spec.type);
253 if (t != 0 && t != 'd' && t != 'x' && t != 'X' && t != 'o'
254 && t != 'f' && t != 'F' && t != 'g' && t != 'G') {
255 throw BadFormat("'#' not allowed with type '"
256 + std::string(1, t) + "'");
257 }
258 }
259 if (spec.hasPlus && spec.hasSpace) {
260 throw BadFormat("'+' and space cannot both appear");
261 }
262 if (spec.precision >= 0) {
263 char t = static_cast<char>(spec.type);
264 if (t != 0 && t != 'f' && t != 'F' && t != 'e' && t != 'E'
265 && t != 'g' && t != 'G' && t != 's') {
266 throw BadFormat("precision not allowed with type '"
267 + std::string(1, t) + "'");
268 }
269 }
270 }
271
278 template <class CharT = char>
280 std::basic_ostream<CharT> &os, const BasicFormatSpec<CharT> &spec) {
281 if (spec.width > 0) os << std::setw(spec.width);
282 if (spec.fill != static_cast<CharT>(' '))
283 os << std::setfill(spec.fill);
284 if (spec.align == static_cast<CharT>('<'))
285 os << std::left;
286 else if (spec.align == static_cast<CharT>('>'))
287 os << std::right;
288 if (spec.precision >= 0) os << std::setprecision(spec.precision);
289 if (spec.type != 0) {
290 switch (static_cast<char>(spec.type)) {
291 case 'd':
292 os << std::dec;
293 break;
294 case 'x':
295 os << std::hex << std::nouppercase;
296 break;
297 case 'X':
298 os << std::hex << std::uppercase;
299 break;
300 case 'o':
301 os << std::oct;
302 break;
303 case 'f':
304 case 'F':
305 os << std::fixed;
306 break;
307 case 'e':
308 os << std::scientific;
309 break;
310 case 'E':
311 os << std::scientific << std::uppercase;
312 break;
313 case 'g':
314 os << std::defaultfloat;
315 break;
316 case 'G':
317 os << std::defaultfloat << std::uppercase;
318 break;
319 default:
320 break;
321 }
322 }
323 if (spec.hasHash) os << std::showbase;
324 if (spec.hasPlus) os << std::showpos;
325 }
326
327 // 辅助:算术类型正数前加空格
328 template <class CharT, class T>
329 static typename std::enable_if<std::is_arithmetic<T>::value, void>::type
330 apply_space_if_nonneg(std::basic_ostream<CharT> &os, const T &value) {
331 if (value >= 0) os << static_cast<CharT>(' ');
332 }
333
334 template <class CharT, class T>
335 static
336 typename std::enable_if<!std::is_arithmetic<T>::value, void>::type
337 apply_space_if_nonneg(std::basic_ostream<CharT> &, const T &) {}
338
347 template <typename CharT = char, typename T>
348 void format_value(std::basic_ostream<CharT> &os,
349 const T &value,
350 const BasicFormatSpec<CharT> &spec) {
351 std::ios_base::fmtflags oldFlags = os.flags();
352 CharT oldFill = os.fill();
353 std::streamsize oldPrecision = os.precision();
354 apply_format(os, spec);
355 if (spec.hasSpace) apply_space_if_nonneg(os, value);
356 put(os, value);
357 os.flags(oldFlags);
358 os.fill(oldFill);
359 os.precision(oldPrecision);
360 }
361
370 template <class CharT = char>
371 void format_impl(std::basic_ostream<CharT> &os,
372 const std::basic_string<CharT> &fmt,
373 size_t &pos,
374 size_t) {
375 while (pos < fmt.size()) {
376 CharT ch = fmt[pos];
377 if (ch == static_cast<CharT>('{') && pos + 1 < fmt.size()
378 && fmt[pos + 1] == static_cast<CharT>('{')) {
379 os << static_cast<CharT>('{');
380 pos += 2;
381 continue;
382 }
383 if (ch == static_cast<CharT>('}') && pos + 1 < fmt.size()
384 && fmt[pos + 1] == static_cast<CharT>('}')) {
385 os << static_cast<CharT>('}');
386 pos += 2;
387 continue;
388 }
389 if (ch == static_cast<CharT>('{')) {
390 size_t start = pos;
391 pos++;
392 size_t closePos = fmt.find(static_cast<CharT>('}'), pos);
393 if (closePos == std::basic_string<CharT>::npos) {
394 throw BadFormat("Unmatched '{' at position "
395 + std::to_string(start));
396 }
397 throw BadFormat(
398 "Not enough arguments for placeholder at position "
399 + std::to_string(start));
400 } else if (ch == static_cast<CharT>('}')) {
401 size_t start = pos;
402 pos++;
403 throw BadFormat(
404 "Unmatched '}' at position " + std::to_string(start));
405 } else {
406 os << ch;
407 pos++;
408 }
409 }
410 }
411
424 template <class CharT = char, typename T, typename... Args>
425 void format_impl(std::basic_ostream<CharT> &os,
426 const std::basic_string<CharT> &fmt,
427 size_t &pos,
428 size_t argIndex,
429 const T &first,
430 const Args &...rest) {
431 while (pos < fmt.size()) {
432 CharT ch = fmt[pos];
433 if (ch == static_cast<CharT>('{') && pos + 1 < fmt.size()
434 && fmt[pos + 1] == static_cast<CharT>('{')) {
435 os << static_cast<CharT>('{');
436 pos += 2;
437 continue;
438 }
439 if (ch == static_cast<CharT>('}') && pos + 1 < fmt.size()
440 && fmt[pos + 1] == static_cast<CharT>('}')) {
441 os << static_cast<CharT>('}');
442 pos += 2;
443 continue;
444 }
445 if (ch == static_cast<CharT>('{')) {
446 size_t start = pos;
447 pos++;
448 size_t closePos = fmt.find(static_cast<CharT>('}'), pos);
449 if (closePos == std::basic_string<CharT>::npos) {
450 throw BadFormat("Unmatched '{' at position "
451 + std::to_string(start));
452 }
453 std::basic_string<CharT> placeholder
454 = fmt.substr(pos, closePos - pos);
455 if (!placeholder.empty()
456 && placeholder[0] == static_cast<CharT>(':')) {
457 placeholder = placeholder.substr(1);
458 }
459 pos = closePos + 1;
461 try {
462 parse_format_spec(placeholder, spec);
463 } catch (const BadFormat &e) {
464 throw BadFormat("Invalid format specifier '"
465 + std::string(placeholder.begin(),
466 placeholder.end())
467 + "' at position "
468 + std::to_string(start + 1) + ": "
469 + e.what());
470 }
471 format_value(os, first, spec);
472 format_impl(os, fmt, pos, argIndex + 1, rest...);
473 return;
474 } else {
475 os << ch;
476 pos++;
477 }
478 }
479 }
480 }
481
484
493 template <typename... Args>
494 std::string format(const std::string &fmt, const Args &...args) {
495 std::ostringstream oss;
496 size_t pos = 0;
497 try {
498 details::format_impl(oss, fmt, pos, 0, args...);
499 } catch (const BadFormat &) {
500 throw;
501 } catch (const std::exception &e) {
502 throw BadFormat(std::string("Internal error: ") + e.what());
503 }
504 return oss.str();
505 }
506
515 template <typename... Args>
516 std::wstring format(const std::wstring &fmt, const Args &...args) {
517 std::wostringstream oss;
518 size_t pos = 0;
519 try {
520 details::format_impl(oss, fmt, pos, 0, args...);
521 } catch (const BadFormat &) {
522 throw;
523 } catch (const std::exception &e) {
524 throw BadFormat(std::string("Internal error: ") + e.what());
525 }
526 return oss.str();
527 }
528
537 template <class CharT, class Traits>
538 template <typename... Args>
540 const std::basic_string<CharT, Traits> &format_str,
541 const Args &...args) {
542 size_t pos = 0;
543 details::format_impl(os, format_str, pos, 0, args...);
544 os << end;
545 if (isFlush) os << std::flush;
546 return *this;
547 }
548
557 template <class CharT, class Traits>
558 template <typename... Args>
560 const CharT *format_str, const Args &...args) {
561 return fmt(std::basic_string<CharT, Traits>(format_str), args...);
562 }
563}
表示格式化字符串错误,例如占位符与参数不匹配。
Definition csexc.h:68
BadFormat(const std::string &msg)
构造 BadFormat。
Definition csexc.h:74
BasicOutput & fmt(const std::basic_string< CharT, Traits > &format_str, const Args &...args)
string_type end
Definition output.h:425
BasicOutput()
Definition output.h:432
bool isFlush
Definition output.h:426
std::basic_ostream< char, std::char_traits< char > > & os
Definition output.h:423
定义 console 库使用的自定义异常类层次结构。
Definition fmt.h:44
static std::enable_if< std::is_arithmetic< T >::value, void >::type apply_space_if_nonneg(std::basic_ostream< CharT > &os, const T &value)
Definition fmt.h:330
void format_value(std::basic_ostream< CharT > &os, const T &value, const BasicFormatSpec< CharT > &spec)
格式化单个值。
Definition fmt.h:348
void format_impl(std::basic_ostream< CharT > &os, const std::basic_string< CharT > &fmt, size_t &pos, size_t)
格式化实现(无参数版本,递归终止)。
Definition fmt.h:371
void parse_format_spec(const std::basic_string< CharT > &specStr, BasicFormatSpec< CharT > &spec)
解析格式说明符。
Definition fmt.h:103
void apply_format(std::basic_ostream< CharT > &os, const BasicFormatSpec< CharT > &spec)
将格式说明符应用到输出流。
Definition fmt.h:279
本库所有组件所在的顶层命名空间。
@ T
Definition kb.h:99
details::BasicFormatSpec< wchar_t > WFormatSpec
Definition fmt.h:483
std::string format(const std::string &fmt, const Args &...args)
格式化字符串(char 版)。
Definition fmt.h:494
details::BasicFormatSpec< char > FormatSpec
Definition fmt.h:482
std::enable_if< is_string< typenamestd::decay< StrOrCharType >::type >::value||is_char< typenamestd::decay< StrOrCharType >::type >::value >::type put(std::basic_ostream< CharT, Traits > &os, StrOrCharType &&value)
Definition output.h:56
提供 STL 容器的格式化输出和灵活的输出控制工具。
格式说明符结构。
Definition fmt.h:50