microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
markdown.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5#pragma once
6
10#include "microfmt.hpp"
11#include <algorithm>
12#include <cstddef>
13#include <cstdint>
14#include <reloco/type_id.hpp>
15#include <string_view>
16#include <utility>
17
18namespace microfmt::md {
19
20// ============================================================================
21// Table Column Specifications
22// ============================================================================
23
24enum class align : uint8_t { left, center, right };
25
26struct MICROFMT_API_CLASS RELOCO_POINTER column {
27 microfmt::string_view title{};
28 size_t min_width{0};
29 align alignment{align::left};
30};
31
32enum class admonition : uint8_t { note, tip, important, warning, caution };
33
34class writer;
35
36template <typename Callable>
37writer &details(writer &w, microfmt::string_view summary, Callable &&body, bool open = false) noexcept;
38
39// ============================================================================
40// Markdown Document Writer (Zero Allocation)
41// ============================================================================
42
44public:
45 explicit constexpr writer(const sink &out) noexcept : m_sink(out) {}
46
47 // ------------------------------------------------------------------------
48 // Raw Output & Formatting Primitives
49 // ------------------------------------------------------------------------
50
51 writer &write(microfmt::string_view sv) & noexcept {
52 m_sink.write(sv);
53 return *this;
54 }
55
56 writer &put(char c) & noexcept {
57 m_sink.put(c);
58 return *this;
59 }
60
61 writer &newline() & noexcept {
62 m_sink.put('\n');
63 return *this;
64 }
65
66 writer &pad(char c, size_t count) & noexcept {
67 for (size_t i = 0; i < count; ++i) {
68 m_sink.put(c);
69 }
70 return *this;
71 }
72
73 template <typename... Args> writer &print(microfmt::string_view fmt, const Args &...args) & noexcept {
74 format_to(m_sink, fmt, args...);
75 return *this;
76 }
77
78 template <typename... Args> writer &println(microfmt::string_view fmt, const Args &...args) & noexcept {
79 format_to(m_sink, fmt, args...);
80 m_sink.put('\n');
81 return *this;
82 }
83
84 // ------------------------------------------------------------------------
85 // Headings & Dividers
86 // ------------------------------------------------------------------------
87
88 template <typename... Args>
89 writer &heading(uint8_t level, microfmt::string_view fmt, const Args &...args) & noexcept {
90 const uint8_t clamped = std::clamp<uint8_t>(level, 1, 6);
91 pad('#', clamped);
92 put(' ');
93 format_to(m_sink, fmt, args...);
94 return newline();
95 }
96
97 writer &h1(microfmt::string_view title) & noexcept { return heading(1, "{}", title); }
98 writer &h2(microfmt::string_view title) & noexcept { return heading(2, "{}", title); }
99 writer &h3(microfmt::string_view title) & noexcept { return heading(3, "{}", title); }
100 writer &h4(microfmt::string_view title) & noexcept { return heading(4, "{}", title); }
101
102 writer &horizontal_rule() & noexcept { return write("---\n"); }
103
104 // ------------------------------------------------------------------------
105 // Code Blocks
106 // ------------------------------------------------------------------------
107
108 writer &code_block_begin(microfmt::string_view lang = "") & noexcept {
109 write("```");
110 if (!lang.empty()) {
111 write(lang);
112 }
113 return newline();
114 }
115
116 writer &code_block_end() & noexcept { return write("```\n"); }
117
118 template <typename Callable> writer &code_block(microfmt::string_view lang, Callable &&body) & noexcept {
119 code_block_begin(lang);
120 body(*this);
121 return code_block_end();
122 }
123
124 // ------------------------------------------------------------------------
125 // Blockquotes & Lists
126 // ------------------------------------------------------------------------
127
128 template <typename... Args> writer &blockquote(microfmt::string_view fmt, const Args &...args) & noexcept {
129 write("> ");
130 format_to(m_sink, fmt, args...);
131 return newline();
132 }
133
134 template <typename... Args> writer &list_item(microfmt::string_view fmt, const Args &...args) & noexcept {
135 write("- ");
136 format_to(m_sink, fmt, args...);
137 return newline();
138 }
139
140 template <typename... Args>
141 writer &numbered_item(size_t index, microfmt::string_view fmt, const Args &...args) & noexcept {
142 format_to(m_sink, "{}. ", index);
143 format_to(m_sink, fmt, args...);
144 return newline();
145 }
146
147 // ------------------------------------------------------------------------
148 // Tables
149 // ------------------------------------------------------------------------
150
151 writer &table_header(span<const column> cols) & noexcept {
152 if (cols.empty())
153 return *this;
154
155 // Row 1: Header names
156 put('|');
157 for (const auto &col : cols) {
158 put(' ');
159 const size_t width = std::max(col.min_width, col.title.size());
160 write(col.title);
161 pad(' ', width - col.title.size());
162 write(" |");
163 }
164 newline();
165
166 // Row 2: Alignment delimiters
167 put('|');
168 for (const auto &col : cols) {
169 put(' ');
170 const size_t width = std::max(col.min_width, col.title.size());
171 switch (col.alignment) {
172 case align::center:
173 put(':');
174 pad('-', width >= 2 ? width - 2 : 1);
175 put(':');
176 break;
177 case align::right:
178 pad('-', width >= 1 ? width - 1 : 1);
179 put(':');
180 break;
181 case align::left:
182 default:
183 put(':');
184 pad('-', width >= 1 ? width - 1 : 1);
185 break;
186 }
187 write(" |");
188 }
189 return newline();
190 }
191
192 writer &table_row_begin() & noexcept {
193 put('|');
194 return *this;
195 }
196
197 writer &table_cell(microfmt::string_view content, const column &col) & noexcept {
198 put(' ');
199 const size_t width = std::max(col.min_width, col.title.size());
200 const size_t len = content.size();
201
202 if (len >= width) {
203 write(content);
204 } else {
205 const size_t pad_total = width - len;
206 switch (col.alignment) {
207 case align::right:
208 pad(' ', pad_total);
209 write(content);
210 break;
211 case align::center: {
212 const size_t left = pad_total / 2;
213 const size_t right = pad_total - left;
214 pad(' ', left);
215 write(content);
216 pad(' ', right);
217 break;
218 }
219 case align::left:
220 default:
221 write(content);
222 pad(' ', pad_total);
223 break;
224 }
225 }
226 write(" |");
227 return *this;
228 }
229
230 template <size_t ScratchSize = 128, typename... Args>
231 writer &table_cell_fmt(const column &col, microfmt::string_view fmt, const Args &...args) & noexcept {
233 format_to(scratch.as_sink(), fmt, args...);
234 return table_cell(scratch.view(), col);
235 }
236
237 writer &table_row_end() & noexcept { return newline(); }
238
239 [[nodiscard]] constexpr sink get_sink() const & noexcept RELOCO_LIFETIMEBOUND { return m_sink; }
240
241 [[nodiscard]] constexpr sink get_sink() const && = delete;
242
243 template <typename... Args>
244 writer &task_item(bool checked, microfmt::string_view fmt, const Args &...args) & noexcept {
245 write(checked ? "- [x] " : "- [ ] ");
246 format_to(m_sink, fmt, args...);
247 return newline();
248 }
249
250 template <typename... Args>
251 writer &nested_list_item(uint8_t indent_level, microfmt::string_view fmt, const Args &...args) & noexcept {
252 pad(' ', static_cast<size_t>(indent_level) * 2);
253 write("- ");
254 format_to(m_sink, fmt, args...);
255 return newline();
256 }
257
258 template <typename Callable> writer &alert(admonition type, Callable &&body) & noexcept {
259 switch (type) {
260 case admonition::note:
261 write("> [!NOTE]\n");
262 break;
263 case admonition::tip:
264 write("> [!TIP]\n");
265 break;
267 write("> [!IMPORTANT]\n");
268 break;
270 write("> [!WARNING]\n");
271 break;
273 write("> [!CAUTION]\n");
274 break;
275 }
276 // Custom indented blockquote writer proxy
277 body(*this);
278 return newline();
279 }
280
281 template <typename... Args>
282 writer &hexdump_block(microfmt::string_view summary, span<const uint8_t> data) & noexcept {
283 return details(*this, summary, [&data](writer &w) {
284 w.code_block("text", [&data](writer &cw) { format_to(cw.get_sink(), "{}", microfmt::hexdump(data)); });
285 });
286 }
287
288private:
289 sink m_sink;
290};
291
292// Bold, Italic, Strikethrough, Inline Code, Hyperlink, and Badges
293struct MICROFMT_API_CLASS RELOCO_POINTER bold_view {
294 microfmt::string_view text;
295};
296struct MICROFMT_API_CLASS RELOCO_POINTER italic_view {
297 microfmt::string_view text;
298};
299struct MICROFMT_API_CLASS RELOCO_POINTER strike_view {
300 microfmt::string_view text;
301};
302struct MICROFMT_API_CLASS RELOCO_POINTER code_view {
303 microfmt::string_view text;
304};
305struct MICROFMT_API_CLASS RELOCO_POINTER link_view {
306 microfmt::string_view label;
307 microfmt::string_view url;
308};
309struct MICROFMT_API_CLASS RELOCO_POINTER image_view {
310 microfmt::string_view alt;
311 microfmt::string_view url;
312};
313
314[[nodiscard]] constexpr bold_view bold(microfmt::string_view s) noexcept { return {s}; }
315[[nodiscard]] constexpr italic_view italic(microfmt::string_view s) noexcept { return {s}; }
316[[nodiscard]] constexpr strike_view strike(microfmt::string_view s) noexcept { return {s}; }
317[[nodiscard]] constexpr code_view code(microfmt::string_view s) noexcept { return {s}; }
318[[nodiscard]] constexpr link_view link(microfmt::string_view label, microfmt::string_view url) noexcept {
319 return {label, url};
320}
321[[nodiscard]] constexpr image_view image(microfmt::string_view alt, microfmt::string_view url) noexcept {
322 return {alt, url};
323}
324
325} // namespace microfmt::md
326
328 constexpr void parse(format_parse_context &) noexcept {}
329 void format(const microfmt::md::bold_view &v, const sink &out) const noexcept {
330 out.write("**");
331 out.write(v.text);
332 out.write("**");
333 }
334};
335
337 constexpr void parse(format_parse_context &) noexcept {}
338 void format(const microfmt::md::code_view &v, const sink &out) const noexcept {
339 out.put('`');
340 out.write(v.text);
341 out.put('`');
342 }
343};
344
346 constexpr void parse(format_parse_context &) noexcept {}
347 void format(const microfmt::md::italic_view &v, const sink &out) const noexcept {
348 out.put('_');
349 out.write(v.text);
350 out.put('_');
351 }
352};
353
355 constexpr void parse(format_parse_context &) noexcept {}
356 void format(const microfmt::md::strike_view &v, const sink &out) const noexcept {
357 out.write("~~");
358 out.write(v.text);
359 out.write("~~");
360 }
361};
362
364 constexpr void parse(format_parse_context &) noexcept {}
365 void format(const microfmt::md::link_view &v, const sink &out) const noexcept {
366 out.put('[');
367 out.write(v.label);
368 out.write("](");
369 out.write(v.url);
370 out.put(')');
371 }
372};
373
375 constexpr void parse(format_parse_context &) noexcept {}
376 void format(const microfmt::md::image_view &v, const sink &out) const noexcept {
377 out.write("![");
378 out.write(v.alt);
379 out.write("](");
380 out.write(v.url);
381 out.put(')');
382 }
383};
384
385namespace microfmt::md {
386
387// ============================================================================
388// Collapsible Details Guard (RAII)
389// ============================================================================
390
391class MICROFMT_API_CLASS RELOCO_POINTER details_guard {
392public:
393 explicit details_guard(writer &w RELOCO_LIFETIMEBOUND, microfmt::string_view summary, bool open = false) noexcept;
394 ~details_guard() noexcept;
395
396 details_guard(const details_guard &) = delete;
397 details_guard &operator=(const details_guard &) = delete;
398 details_guard(details_guard &&o) noexcept : m_writer(std::exchange(o.m_writer, nullptr)) {}
399
400private:
401 writer *m_writer{nullptr};
402};
403
404// ============================================================================
405// details_guard Implementation
406// ============================================================================
407
408inline details_guard::details_guard(writer &w, microfmt::string_view summary, bool open) noexcept : m_writer(&w) {
409 if (open) {
410 m_writer->write("<details open>\n<summary>");
411 } else {
412 m_writer->write("<details>\n<summary>");
413 }
414 m_writer->write(summary);
415 m_writer->write("</summary>\n\n");
416}
417
419 if (m_writer != nullptr) {
420 m_writer->write("\n</details>\n\n");
421 }
422}
423
424template <typename Callable>
425inline writer &details(writer &w, microfmt::string_view summary, Callable &&body, bool open) noexcept {
426 details_guard guard(w, summary, open);
427 body(w);
428 return w;
429}
430
431template <size_t NumCols> class RELOCO_POINTER table_writer {
432public:
433 table_writer(writer &w RELOCO_LIFETIMEBOUND, span<const column> cols) noexcept : m_writer(w), m_cols(cols) {
434 m_writer.table_header(m_cols);
435 }
436
437 template <typename... Cells> table_writer &row(const Cells &...cells) & noexcept {
438 m_writer.table_row_begin();
439 size_t col_idx = 0;
440 auto format_cell = [this, &col_idx](const auto &cell) {
441 if constexpr (std::is_convertible_v<decltype(cell), microfmt::string_view>) {
442 m_writer.table_cell(static_cast<microfmt::string_view>(cell), m_cols[col_idx++]);
443 } else {
444 m_writer.table_cell_fmt(m_cols[col_idx++], "{}", cell);
445 }
446 };
447 (format_cell(cells), ...);
448 m_writer.table_row_end();
449 return *this;
450 }
451
452private:
453 writer &m_writer;
454 span<const column> m_cols;
455};
456
457} // namespace microfmt::md
458
459// See <reloco/type_id.hpp> for the full RELOCO_TYPE_ID_NAME rationale;
460// defined here, alongside column's/align's/admonition's own definitions.
Output sink that stores formatted characters in an internal fixed-size stack/inline buffer.
Definition microfmt.hpp:185
constexpr microfmt::string_view view() const &noexcept RELOCO_LIFETIMEBOUND
Returns a string view over the characters written so far.
Definition microfmt.hpp:245
RELOCO_END_UNSAFE_BUFFER_USAGE RELOCO_CONSTEXPR20 sink as_sink() &noexcept RELOCO_LIFETIMEBOUND
Creates a type-erased sink adapter pointing to this instance.
Definition microfmt.hpp:236
Definition microfmt.hpp:881
Definition markdown.hpp:391
~details_guard() noexcept
Definition markdown.hpp:418
details_guard(writer &w RELOCO_LIFETIMEBOUND, microfmt::string_view summary, bool open=false) noexcept
Definition markdown.hpp:431
table_writer(writer &w RELOCO_LIFETIMEBOUND, span< const column > cols) noexcept
Definition markdown.hpp:433
table_writer & row(const Cells &...cells) &noexcept
Definition markdown.hpp:437
Definition markdown.hpp:43
writer & code_block_begin(microfmt::string_view lang="") &noexcept
Definition markdown.hpp:108
constexpr sink get_sink() const &noexcept RELOCO_LIFETIMEBOUND
Definition markdown.hpp:239
writer & numbered_item(size_t index, microfmt::string_view fmt, const Args &...args) &noexcept
Definition markdown.hpp:141
writer & heading(uint8_t level, microfmt::string_view fmt, const Args &...args) &noexcept
Definition markdown.hpp:89
writer & alert(admonition type, Callable &&body) &noexcept
Definition markdown.hpp:258
writer & code_block_end() &noexcept
Definition markdown.hpp:116
writer & nested_list_item(uint8_t indent_level, microfmt::string_view fmt, const Args &...args) &noexcept
Definition markdown.hpp:251
writer & put(char c) &noexcept
Definition markdown.hpp:56
writer & table_cell(microfmt::string_view content, const column &col) &noexcept
Definition markdown.hpp:197
writer & pad(char c, size_t count) &noexcept
Definition markdown.hpp:66
writer & write(microfmt::string_view sv) &noexcept
Definition markdown.hpp:51
writer & table_cell_fmt(const column &col, microfmt::string_view fmt, const Args &...args) &noexcept
Definition markdown.hpp:231
constexpr writer(const sink &out) noexcept
Definition markdown.hpp:45
writer & newline() &noexcept
Definition markdown.hpp:61
writer & table_header(span< const column > cols) &noexcept
Definition markdown.hpp:151
writer & h3(microfmt::string_view title) &noexcept
Definition markdown.hpp:99
writer & table_row_end() &noexcept
Definition markdown.hpp:237
writer & blockquote(microfmt::string_view fmt, const Args &...args) &noexcept
Definition markdown.hpp:128
writer & h2(microfmt::string_view title) &noexcept
Definition markdown.hpp:98
writer & horizontal_rule() &noexcept
Definition markdown.hpp:102
writer & list_item(microfmt::string_view fmt, const Args &...args) &noexcept
Definition markdown.hpp:134
writer & code_block(microfmt::string_view lang, Callable &&body) &noexcept
Definition markdown.hpp:118
writer & h1(microfmt::string_view title) &noexcept
Definition markdown.hpp:97
constexpr sink get_sink() const &&=delete
writer & hexdump_block(microfmt::string_view summary, span< const uint8_t > data) &noexcept
Definition markdown.hpp:282
writer & println(microfmt::string_view fmt, const Args &...args) &noexcept
Definition markdown.hpp:78
writer & h4(microfmt::string_view title) &noexcept
Definition markdown.hpp:100
writer & print(microfmt::string_view fmt, const Args &...args) &noexcept
Definition markdown.hpp:73
writer & table_row_begin() &noexcept
Definition markdown.hpp:192
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Direct and fault-checked memory hex-dump views.
Core formatting primitives, sinks, and customization point.
Definition fmt.hpp:17
Definition metadata_map.hpp:116
constexpr italic_view italic(microfmt::string_view s) noexcept
Definition markdown.hpp:315
constexpr bold_view bold(microfmt::string_view s) noexcept
Definition markdown.hpp:314
constexpr image_view image(microfmt::string_view alt, microfmt::string_view url) noexcept
Definition markdown.hpp:321
writer & details(writer &w, microfmt::string_view summary, Callable &&body, bool open=false) noexcept
Definition markdown.hpp:425
constexpr code_view code(microfmt::string_view s) noexcept
Definition markdown.hpp:317
constexpr strike_view strike(microfmt::string_view s) noexcept
Definition markdown.hpp:316
constexpr link_view link(microfmt::string_view label, microfmt::string_view url) noexcept
Definition markdown.hpp:318
admonition
Definition markdown.hpp:32
align
Definition markdown.hpp:24
Definition assert.hpp:19
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
RELOCO_CONSTEXPR20 void format_to(const sink &out, microfmt::string_view fmt, const Args &...args) noexcept
Definition microfmt.hpp:1766
constexpr styled_str_view pad(microfmt::string_view s, size_t width, text_align a=text_align::left, char fill=' ') noexcept
Definition styled.hpp:58
constexpr hexdump_view hexdump(span< const uint8_t > data, uintptr_t base_addr=0, uint8_t bytes_per_line=16, bool show_ascii=true) noexcept
Helper for dumping safe, direct in-memory buffers (zero overhead).
Definition hexdump.hpp:52
RELOCO_TYPE_ID_NAME(microfmt::semver, "microfmt::semver")
constexpr void parse(format_parse_context &) noexcept
Definition markdown.hpp:328
void format(const microfmt::md::bold_view &v, const sink &out) const noexcept
Definition markdown.hpp:329
constexpr void parse(format_parse_context &) noexcept
Definition markdown.hpp:337
void format(const microfmt::md::code_view &v, const sink &out) const noexcept
Definition markdown.hpp:338
constexpr void parse(format_parse_context &) noexcept
Definition markdown.hpp:375
void format(const microfmt::md::image_view &v, const sink &out) const noexcept
Definition markdown.hpp:376
void format(const microfmt::md::italic_view &v, const sink &out) const noexcept
Definition markdown.hpp:347
constexpr void parse(format_parse_context &) noexcept
Definition markdown.hpp:346
void format(const microfmt::md::strike_view &v, const sink &out) const noexcept
Definition markdown.hpp:356
constexpr void parse(format_parse_context &) noexcept
Definition markdown.hpp:355
Definition microfmt.hpp:985
Definition markdown.hpp:293
microfmt::string_view text
Definition markdown.hpp:294
Definition markdown.hpp:302
microfmt::string_view text
Definition markdown.hpp:303
Definition markdown.hpp:26
Definition markdown.hpp:309
microfmt::string_view alt
Definition markdown.hpp:310
microfmt::string_view url
Definition markdown.hpp:311
Definition markdown.hpp:296
microfmt::string_view text
Definition markdown.hpp:297
Definition markdown.hpp:299
microfmt::string_view text
Definition markdown.hpp:300