aboutsummaryrefslogtreecommitdiffstats
path: root/nihil.util/tabulate.ccm
diff options
context:
space:
mode:
Diffstat (limited to 'nihil.util/tabulate.ccm')
-rw-r--r--nihil.util/tabulate.ccm298
1 files changed, 0 insertions, 298 deletions
diff --git a/nihil.util/tabulate.ccm b/nihil.util/tabulate.ccm
deleted file mode 100644
index 762e2a3..0000000
--- a/nihil.util/tabulate.ccm
+++ /dev/null
@@ -1,298 +0,0 @@
-// This source code is released into the public domain.
-export module nihil.util:tabulate;
-
-import nihil.std;
-
-import :ctype;
-import :error;
-
-namespace nihil {
-
-// tabulate: format the given range in an ASCII table and write the output
-// to the given output iterator. The range's values will be converted to
-// strings as if by std::format.
-//
-// tabulate is implemented by copying the range; this allows it to work on
-// input/forward ranges at the cost of slightly increased memory use.
-//
-// The table spec is a string consisting of zero or more field formats,
-// formatted as {flags:fieldname}; both flags and fieldname are optional.
-// If there are fewer field formats than fields, the remaining fields
-// are formatted as if by {:}.
-//
-// The following flags are supported:
-//
-// < left-align this column (default)
-// > right-align this column
-
-// Exception thrown when a table spec is invalid.
-export struct table_spec_error : error {
- explicit table_spec_error(std::string_view what)
- : error(what)
- {
- }
-};
-
-// The specification for a single field.
-template<typename Char>
-struct field_spec {
- enum align_t { left, right };
-
- // Get the name of this field.
- auto name(this field_spec const &self)
- -> std::basic_string_view<Char>
- {
- return self.m_name;
- }
-
- // Set the name of this field.
- auto name(this field_spec &self,
- std::basic_string_view<Char> new_name)
- -> void
- {
- self.m_name = new_name;
- }
-
- // Set this field's alignment.
- auto align(this field_spec &self, align_t new_align) -> void
- {
- self.m_align = new_align;
- }
-
- // Ensure the length of this field is at least the given width.
- auto ensure_width(this field_spec &self, std::size_t newwidth)
- -> void
- {
- self.m_width = std::max(self.m_width, newwidth);
- }
-
- // Format an object to a string based on our field spec.
- [[nodiscard]] auto format(this field_spec const &, auto &&obj)
- -> std::basic_string<Char>
- {
- auto format_string = std::basic_string<Char>{'{', '}'};
- return std::format(std::runtime_format(format_string), obj);
- }
-
- // Print a column value to an output iterator according to our field
- // spec. If is_last is true, this is the last field on the line, so
- // we won't output any trailling padding.
- auto print(this field_spec const &self,
- std::basic_string_view<Char> value,
- std::output_iterator<Char> auto &out,
- bool is_last)
- -> void
- {
- auto padding = self.m_width - value.size();
-
- if (self.m_align == right)
- for (std::size_t i = 0; i < padding; ++i)
- *out++ = ' ';
-
- std::ranges::copy(value, out);
-
- if (!is_last && self.m_align == left)
- for (std::size_t i = 0; i < padding; ++i)
- *out++ = ' ';
- }
-
-private:
- std::basic_string_view<Char> m_name;
- std::size_t m_width = 0;
- align_t m_align = left;
-};
-
-/*
- * The specification for an entire table.
- */
-template<typename Char>
-struct table_spec {
- // Add a new field spec to this table.
- auto add(this table_spec &self, field_spec<Char> field) -> void
- {
- self.m_fields.emplace_back(std::move(field));
- }
-
- // Return the field spec for a given field. If the field doesn't
- // exist, this field and any intermediate fields will be created.
- [[nodiscard]] auto field(this table_spec &self, std::size_t fieldnr)
- -> field_spec<Char> &
- {
- if (fieldnr >= self.m_fields.size())
- self.m_fields.resize(fieldnr + 1);
- return self.m_fields.at(fieldnr);
- }
-
- // The number of columns in this table.
- [[nodiscard]] auto columns(this table_spec const &self) -> std::size_t
- {
- return self.m_fields.size();
- }
-
- // Return all the fields in this table.
- [[nodiscard]] auto fields(this table_spec const &self)
- -> std::vector<field_spec<Char>> const &
- {
- return self.m_fields;
- }
-
-private:
- std::vector<field_spec<Char>> m_fields;
-};
-
-// Parse the field flags, e.g. '<'.
-template<typename Char,
- std::input_iterator Iterator, std::sentinel_for<Iterator> Sentinel>
-auto parse_field_flags(field_spec<Char> &field, Iterator &pos, Sentinel end)
- -> void
-{
- while (pos < end) {
- switch (*pos) {
- case '<':
- field.align(field_spec<Char>::left);
- break;
- case '>':
- field.align(field_spec<Char>::right);
- break;
- case ':':
- ++pos;
- /*FALLTHROUGH*/
- case '}':
- return;
- default:
- throw table_spec_error("Invalid table spec: "
- "unknown flag character");
- }
-
- if (++pos == end)
- throw table_spec_error("Invalid table spec: "
- "unterminated field");
- }
-}
-
-// Parse a complete field spec, e.g. "{<:NAME}".
-template<typename Char,
- std::input_iterator Iterator, std::sentinel_for<Iterator> Sentinel>
-[[nodiscard]] auto parse_field(Iterator &pos, Sentinel end)
- -> field_spec<Char>
-{
- auto field = field_spec<Char>{};
-
- if (pos == end)
- throw table_spec_error("Invalid table spec: empty field");
-
- // The field spec should start with a '{'.
- if (*pos != '{')
- throw table_spec_error("Invalid table spec: expected '{'");
-
- if (++pos == end)
- throw table_spec_error("Invalid table spec: unterminated field");
-
- // This consumes 'pos' up to and including the ':'.
- parse_field_flags(field, pos, end);
-
- auto brace = std::ranges::find(pos, end, '}');
- if (brace == end)
- throw table_spec_error("Invalid table spec: expected '}'");
-
- field.name(std::basic_string_view<Char>(pos, brace));
- pos = std::next(brace);
-
- // The field must be at least as wide as its header.
- field.ensure_width(field.name().size());
-
- return field;
-}
-
-template<typename Char>
-[[nodiscard]] auto parse_table_spec(std::basic_string_view<Char> spec)
- -> table_spec<Char>
-{
- auto table = table_spec<Char>();
-
- auto pos = std::ranges::begin(spec);
- auto end = std::ranges::end(spec);
-
- for (;;) {
- // Skip leading whitespace
- while (pos < end && is_c_space(*pos))
- ++pos;
-
- if (pos == end)
- break;
-
- table.add(parse_field<Char>(pos, end));
- }
-
- return table;
-}
-
-export template<typename Char,
- std::ranges::range Range,
- std::output_iterator<Char> Iterator>
-auto basic_tabulate(std::basic_string_view<Char> table_spec,
- Range &&range,
- Iterator &&out)
- -> void
-{
- // Parse the table spec.
- auto table = parse_table_spec(table_spec);
-
- // Create our copy of the input data.
- auto data = std::vector<std::vector<std::basic_string<Char>>>();
- // Reserve the first row for the header.
- data.resize(1);
-
- // Find the required length of each field.
- for (auto &&row : range) {
- // LLVM doesn't have std::enumerate_view yet
- auto i = std::size_t{0};
- auto &this_row = data.emplace_back();
-
- for (auto &&column : row) {
- auto &field = table.field(i);
- auto &str = this_row.emplace_back(field.format(column));
- field.ensure_width(str.size());
- ++i;
- }
- }
-
- // Add the header row.
- for (auto &&field : table.fields())
- data.at(0).emplace_back(std::from_range, field.name());
-
- // Print the values.
- for (auto &&row : data) {
- for (std::size_t i = 0; i < row.size(); ++i) {
- auto &field = table.field(i);
- bool is_last = (i == row.size() - 1);
-
- field.print(row[i], out, is_last);
-
- if (!is_last)
- *out++ = ' ';
- }
-
- *out++ = '\n';
- }
-}
-
-export auto tabulate(std::string_view table_spec,
- std::ranges::range auto &&range,
- std::output_iterator<char> auto &&out)
-{
- return basic_tabulate<char>(table_spec,
- std::forward<decltype(range)>(range),
- std::forward<decltype(out)>(out));
-}
-
-export auto wtabulate(std::wstring_view table_spec,
- std::ranges::range auto &&range,
- std::output_iterator<wchar_t> auto &&out)
-{
- return basic_tabulate<wchar_t>(table_spec,
- std::forward<decltype(range)>(range),
- std::forward<decltype(out)>(out));
-}
-
-} // namespace nihil