aboutsummaryrefslogtreecommitdiffstats
path: root/nihil.util
diff options
context:
space:
mode:
authorLexi Winter <lexi@le-fay.org>2025-07-01 17:07:04 +0100
committerLexi Winter <lexi@le-fay.org>2025-07-01 17:07:04 +0100
commit2e2d1bd3b6c7776b77c33b94f30ead89367a71e6 (patch)
tree54d37ffadf8e677938d9b7a28e4e9b71be1e75c1 /nihil.util
parent36427c0966faa7aecd586b397ed9b845f18172f5 (diff)
downloadnihil-2e2d1bd3b6c7776b77c33b94f30ead89367a71e6.tar.gz
nihil-2e2d1bd3b6c7776b77c33b94f30ead89367a71e6.tar.bz2
add nihil.std
Diffstat (limited to 'nihil.util')
-rw-r--r--nihil.util/CMakeLists.txt20
-rw-r--r--nihil.util/capture_stream.ccm19
-rw-r--r--nihil.util/capture_stream.test.cc (renamed from nihil.util/test_capture_stream.cc)10
-rw-r--r--nihil.util/ctype.ccm73
-rw-r--r--nihil.util/ctype.test.cc (renamed from nihil.util/test_ctype.cc)9
-rw-r--r--nihil.util/next_word.ccm37
-rw-r--r--nihil.util/next_word.test.cc (renamed from nihil.util/test_next_word.cc)12
-rw-r--r--nihil.util/nihil.util.ccm8
-rw-r--r--nihil.util/parse_size.ccm75
-rw-r--r--nihil.util/parse_size.test.cc (renamed from nihil.util/test_parse_size.cc)8
-rw-r--r--nihil.util/save_errno.ccm35
-rw-r--r--nihil.util/skipws.ccm29
-rw-r--r--nihil.util/skipws.test.cc (renamed from nihil.util/test_skipws.cc)15
-rw-r--r--nihil.util/tabulate.ccm55
-rw-r--r--nihil.util/tabulate.test.cc (renamed from nihil.util/test_tabulate.cc)9
15 files changed, 180 insertions, 234 deletions
diff --git a/nihil.util/CMakeLists.txt b/nihil.util/CMakeLists.txt
index 2ef916e..109e4d4 100644
--- a/nihil.util/CMakeLists.txt
+++ b/nihil.util/CMakeLists.txt
@@ -1,7 +1,12 @@
# This source code is released into the public domain.
add_library(nihil.util STATIC)
-target_link_libraries(nihil.util PRIVATE nihil.core nihil.error nihil.monad)
+target_link_libraries(nihil.util PRIVATE
+ nihil.std
+ nihil.core
+ nihil.error
+ nihil.monad
+)
target_sources(nihil.util
PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
nihil.util.ccm
@@ -10,6 +15,7 @@ target_sources(nihil.util
ctype.ccm
parse_size.ccm
next_word.ccm
+ save_errno.ccm
skipws.ccm
tabulate.ccm
)
@@ -18,12 +24,12 @@ if(NIHIL_TESTS)
enable_testing()
add_executable(nihil.util.test
- test_capture_stream.cc
- test_ctype.cc
- test_parse_size.cc
- test_next_word.cc
- test_skipws.cc
- test_tabulate.cc
+ capture_stream.test.cc
+ ctype.test.cc
+ parse_size.test.cc
+ next_word.test.cc
+ skipws.test.cc
+ tabulate.test.cc
)
target_link_libraries(nihil.util.test PRIVATE
nihil.util
diff --git a/nihil.util/capture_stream.ccm b/nihil.util/capture_stream.ccm
index 7ec39a9..f061558 100644
--- a/nihil.util/capture_stream.ccm
+++ b/nihil.util/capture_stream.ccm
@@ -1,20 +1,13 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <iostream>
-
+// This source code is released into the public domain.
export module nihil.util:capture_stream;
+import nihil.std;
+
namespace nihil {
-/*
- * Capture output written to a stream and redirect it to an internal string
- * buffer. Call .str() to get the data written. Call .release() to stop
- * capturing (or simply delete the capture_stream object).
- */
+// Capture output written to a stream and redirect it to an internal string
+// buffer. Call .str() to get the data written. Call .release() to stop
+// capturing (or simply delete the capture_stream object).
export template<typename Char, typename Traits>
struct capture_stream {
capture_stream(std::basic_ostream<Char, Traits> &stream)
diff --git a/nihil.util/test_capture_stream.cc b/nihil.util/capture_stream.test.cc
index 27c8596..a4821b7 100644
--- a/nihil.util/test_capture_stream.cc
+++ b/nihil.util/capture_stream.test.cc
@@ -1,13 +1,11 @@
-/*
- * This source code is released into the public domain.
- */
-
-#include <iostream>
+// This source code is released into the public domain.
#include <catch2/catch_test_macros.hpp>
+import nihil.std;
import nihil.util;
+namespace {
TEST_CASE("nihil.util: capture", "[nihil][nihil.util]")
{
SECTION("std::cout with release()") {
@@ -42,3 +40,5 @@ TEST_CASE("nihil.util: capture", "[nihil][nihil.util]")
REQUIRE(cap.str() == "1+1=2\n");
}
}
+
+} // anonymous namespace
diff --git a/nihil.util/ctype.ccm b/nihil.util/ctype.ccm
index 6d30c4f..8f5de27 100644
--- a/nihil.util/ctype.ccm
+++ b/nihil.util/ctype.ccm
@@ -1,14 +1,8 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <concepts>
-#include <locale>
-
+// This source code is released into the public domain.
export module nihil.util:ctype;
+import nihil.std;
+
namespace nihil {
/*
@@ -21,15 +15,16 @@ namespace nihil {
* ctype_is copies the locale, so passing a temporary is fine.
*/
-export struct ctype_is final {
- ctype_is(std::ctype_base::mask mask_,
- std::locale const &locale_ = std::locale())
- : m_mask(mask_)
- , m_locale(locale_)
- {}
+export struct ctype_is final
+{
+ explicit ctype_is(std::ctype_base::mask mask_,
+ std::locale const &locale_ = std::locale()) noexcept
+ : m_mask(mask_)
+ , m_locale(locale_)
+ {
+ }
- [[nodiscard]] auto operator()(this ctype_is const &self,
- std::integral auto c)
+ [[nodiscard]] auto operator()(this ctype_is const &self, std::integral auto c)
{
using ctype = std::ctype<decltype(c)>;
auto &facet = std::use_facet<ctype>(self.m_locale);
@@ -37,11 +32,11 @@ export struct ctype_is final {
}
private:
- std::ctype_base::mask m_mask;
- std::locale m_locale;
+ std::ctype_base::mask m_mask;
+ std::locale m_locale;
};
-// Predefined tests for the current global locale.
+// Predefined tests for the current global locale.
export inline auto is_space = ctype_is(std::ctype_base::space);
export inline auto is_print = ctype_is(std::ctype_base::print);
@@ -59,29 +54,19 @@ export inline auto is_graph = ctype_is(std::ctype_base::graph);
// Predefined tests for the C locale. The C locale is guaranteed to always be
// available, so this doesn't create lifetime issues.
-export inline auto is_c_space =
- ctype_is(std::ctype_base::space, std::locale::classic());
-export inline auto is_c_print =
- ctype_is(std::ctype_base::print, std::locale::classic());
-export inline auto is_c_cntrl =
- ctype_is(std::ctype_base::cntrl, std::locale::classic());
-export inline auto is_c_upper =
- ctype_is(std::ctype_base::upper, std::locale::classic());
-export inline auto is_c_lower =
- ctype_is(std::ctype_base::lower, std::locale::classic());
-export inline auto is_c_alpha =
- ctype_is(std::ctype_base::alpha, std::locale::classic());
-export inline auto is_c_digit =
- ctype_is(std::ctype_base::digit, std::locale::classic());
-export inline auto is_c_punct =
- ctype_is(std::ctype_base::punct, std::locale::classic());
-export inline auto is_c_xdigit =
- ctype_is(std::ctype_base::xdigit, std::locale::classic());
-export inline auto is_c_blank =
- ctype_is(std::ctype_base::blank, std::locale::classic());
-export inline auto is_c_alnum =
- ctype_is(std::ctype_base::alnum, std::locale::classic());
-export inline auto is_c_graph =
- ctype_is(std::ctype_base::graph, std::locale::classic());
+//NOLINTBEGIN: Technically, std::locale::classic() can throw. Assume it doesn't.
+export inline auto is_c_space = ctype_is(std::ctype_base::space, std::locale::classic());
+export inline auto is_c_print = ctype_is(std::ctype_base::print, std::locale::classic());
+export inline auto is_c_cntrl = ctype_is(std::ctype_base::cntrl, std::locale::classic());
+export inline auto is_c_upper = ctype_is(std::ctype_base::upper, std::locale::classic());
+export inline auto is_c_lower = ctype_is(std::ctype_base::lower, std::locale::classic());
+export inline auto is_c_alpha = ctype_is(std::ctype_base::alpha, std::locale::classic());
+export inline auto is_c_digit = ctype_is(std::ctype_base::digit, std::locale::classic());
+export inline auto is_c_punct = ctype_is(std::ctype_base::punct, std::locale::classic());
+export inline auto is_c_xdigit = ctype_is(std::ctype_base::xdigit, std::locale::classic());
+export inline auto is_c_blank = ctype_is(std::ctype_base::blank, std::locale::classic());
+export inline auto is_c_alnum = ctype_is(std::ctype_base::alnum, std::locale::classic());
+export inline auto is_c_graph = ctype_is(std::ctype_base::graph, std::locale::classic());
+//NOLINTEND
} // namespace nihil
diff --git a/nihil.util/test_ctype.cc b/nihil.util/ctype.test.cc
index 62721d1..d000b45 100644
--- a/nihil.util/test_ctype.cc
+++ b/nihil.util/ctype.test.cc
@@ -1,11 +1,12 @@
-/*
- * This source code is released into the public domain.
- */
+// This source code is released into the public domain.
#include <catch2/catch_test_macros.hpp>
+import nihil.std;
import nihil.util;
+namespace {
+
TEST_CASE("ctype: space", "[ctype]") {
auto is_utf8_space =
nihil::ctype_is(std::ctype_base::space,
@@ -371,3 +372,5 @@ TEST_CASE("ctype: graph", "[ctype]") {
REQUIRE(nihil::is_c_graph(L'\u0430') == false);
REQUIRE(is_utf8_graph(L'\u0430') == true);
}
+
+} // anonymous namespace
diff --git a/nihil.util/next_word.ccm b/nihil.util/next_word.ccm
index c5d3ad7..89eeaee 100644
--- a/nihil.util/next_word.ccm
+++ b/nihil.util/next_word.ccm
@@ -1,44 +1,27 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <algorithm>
-#include <locale>
-#include <ranges>
-#include <string>
-#include <utility>
-
+// This source code is released into the public domain.
export module nihil.util:next_word;
import :skipws;
namespace nihil {
-/*
- * Return the next word from a string_view. Skips leading whitespace, so
- * calling this repeatedly will return each word from the string.
- */
-
-export template<typename Char> [[nodiscard]]
-auto next_word(std::basic_string_view<Char> text,
- std::locale const &locale = std::locale())
- -> std::pair<std::basic_string_view<Char>,
- std::basic_string_view<Char>>
+// Return the next word from a string_view. Skips leading whitespace, so
+// calling this repeatedly will return each word from the string.
+export template <typename Char>
+[[nodiscard]]
+auto next_word(std::basic_string_view<Char> text, std::locale const &locale = std::locale())
+ -> std::pair<std::basic_string_view<Char>, std::basic_string_view<Char>>
{
text = skipws(text, locale);
auto is_space = ctype_is(std::ctype_base::space, locale);
auto split_pos = std::ranges::find_if(text, is_space);
- return {{std::ranges::begin(text), split_pos},
- {split_pos, std::ranges::end(text)}};
+ return {{std::ranges::begin(text), split_pos}, {split_pos, std::ranges::end(text)}};
}
-export template<typename Char>
-auto next_word(std::basic_string_view<Char> *text,
- std::locale const &locale = std::locale())
+export template <typename Char>
+auto next_word(std::basic_string_view<Char> *text, std::locale const &locale = std::locale())
-> std::basic_string_view<Char>
{
auto [word, rest] = next_word(*text, locale);
diff --git a/nihil.util/test_next_word.cc b/nihil.util/next_word.test.cc
index 7e61237..87d491a 100644
--- a/nihil.util/test_next_word.cc
+++ b/nihil.util/next_word.test.cc
@@ -1,14 +1,12 @@
-/*
- * This source code is released into the public domain.
- */
-
-#include <locale>
-#include <string>
+// This source code is released into the public domain.
#include <catch2/catch_test_macros.hpp>
+import nihil.std;
import nihil.util;
+namespace {
+
TEST_CASE("next_word: basic", "[next_word]")
{
using namespace std::literals;
@@ -63,3 +61,5 @@ TEST_CASE("next_word: locale", "[next_word]")
REQUIRE(words.first == L"foo");
REQUIRE(words.second == L"\u2003bar\u2003baz");
}
+
+} // anonymous namespace
diff --git a/nihil.util/nihil.util.ccm b/nihil.util/nihil.util.ccm
index 89510c9..d8628a4 100644
--- a/nihil.util/nihil.util.ccm
+++ b/nihil.util/nihil.util.ccm
@@ -1,14 +1,10 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
+// This source code is released into the public domain.
export module nihil.util;
export import :capture_stream;
export import :ctype;
export import :parse_size;
export import :next_word;
+export import :save_errno;
export import :skipws;
export import :tabulate;
diff --git a/nihil.util/parse_size.ccm b/nihil.util/parse_size.ccm
index c95ac50..7fc3fa4 100644
--- a/nihil.util/parse_size.ccm
+++ b/nihil.util/parse_size.ccm
@@ -1,20 +1,7 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <algorithm>
-#include <coroutine>
-#include <cstdint>
-#include <expected>
-#include <ranges>
-#include <string>
-#include <system_error>
-#include <utility>
-
+// This source code is released into the public domain.
export module nihil.util:parse_size;
+import nihil.std;
import nihil.core;
import nihil.error;
import nihil.monad;
@@ -23,53 +10,50 @@ import :ctype;
namespace nihil {
-template<typename Char>
+export template <typename Char>
auto get_multiplier(Char c) -> std::expected<std::uint64_t, error>
{
auto ret = std::uint64_t{1};
+ // clang-format off
switch (c) {
- case 'p': case 'P': ret *= 1024; //NOLINT
- case 't': case 'T': ret *= 1024; //NOLINT
- case 'g': case 'G': ret *= 1024; //NOLINT
- case 'm': case 'M': ret *= 1024; //NOLINT
- case 'k': case 'K': ret *= 1024; //NOLINT
+ case 'p': case 'P': ret *= 1024; // NOLINT
+ case 't': case 'T': ret *= 1024; // NOLINT
+ case 'g': case 'G': ret *= 1024; // NOLINT
+ case 'm': case 'M': ret *= 1024; // NOLINT
+ case 'k': case 'K': ret *= 1024; // NOLINT
return ret;
default:
- return std::unexpected(error(errc::invalid_unit));
+ return error(errc::invalid_unit);
}
+ // clang-format on
}
-/*
- * Parse a string containing a human-formatted size, such as "1024"
- * or "4g". Parsing is always done in the "C" locale and does not
- * recognise thousands separators or negative numbers.
- */
-export template<typename T, typename Char> [[nodiscard]]
-auto parse_size(std::basic_string_view<Char> str)
- -> std::expected<T, error>
+// Parse a string containing a human-formatted size, such as "1024"
+// or "4g". Parsing is always done in the "C" locale and does not
+// recognise thousands separators or negative numbers.
+export template <typename T, typename Char>
+[[nodiscard]]
+auto parse_size(std::basic_string_view<Char> str) -> std::expected<T, error>
{
// Extract the numeric part of the string.
auto it = std::ranges::find_if_not(str, is_c_digit);
- auto num_str = std::basic_string_view<Char>(
- std::ranges::begin(str), it);
+ auto num_str = std::basic_string_view<Char>(std::ranges::begin(str), it);
if (num_str.empty())
- co_return std::unexpected(error(errc::empty_string));
+ co_return error(errc::empty_string);
auto ret = T{0};
for (auto c : num_str) {
if (ret > (std::numeric_limits<T>::max() / 10))
- co_return std::unexpected(error(
- std::errc::result_out_of_range));
+ co_return error(std::errc::result_out_of_range);
ret *= 10;
auto digit = static_cast<T>(c - '0');
if ((std::numeric_limits<T>::max() - digit) < ret)
- co_return std::unexpected(error(
- std::errc::result_out_of_range));
+ co_return error(std::errc::result_out_of_range);
ret += digit;
}
@@ -81,27 +65,26 @@ auto parse_size(std::basic_string_view<Char> str)
if (it != str.end())
// Multiplier is more than one character.
- co_return std::unexpected(error(errc::invalid_unit));
+ co_return error(errc::invalid_unit);
auto mult = co_await get_multiplier(mchar);
if (std::cmp_greater(ret, std::numeric_limits<T>::max() / mult))
- co_return std::unexpected(error(
- std::errc::result_out_of_range));
+ co_return error(std::errc::result_out_of_range);
- co_return ret * mult;
+ co_return ret *mult;
}
-export template<typename T>
-[[nodiscard]] inline auto parse_size(char const *s)
+export template <typename T>
+[[nodiscard]] auto parse_size(char const *s)
{
return parse_size<T>(std::string_view(s));
}
-export template<typename T>
-[[nodiscard]] inline auto parse_size(wchar_t const *s)
+export template <typename T>
+[[nodiscard]] auto parse_size(wchar_t const *s)
{
return parse_size<T>(std::wstring_view(s));
}
-}
+} // namespace nihil
diff --git a/nihil.util/test_parse_size.cc b/nihil.util/parse_size.test.cc
index 692039b..d79912a 100644
--- a/nihil.util/test_parse_size.cc
+++ b/nihil.util/parse_size.test.cc
@@ -1,12 +1,8 @@
-/*
- * This source code is released into the public domain.
- */
-
-#include <cstdint>
-#include <system_error>
+// This source code is released into the public domain.
#include <catch2/catch_test_macros.hpp>
+import nihil.std;
import nihil.core;
import nihil.error;
import nihil.util;
diff --git a/nihil.util/save_errno.ccm b/nihil.util/save_errno.ccm
new file mode 100644
index 0000000..27567f8
--- /dev/null
+++ b/nihil.util/save_errno.ccm
@@ -0,0 +1,35 @@
+// This source code is released into the public domain.
+module;
+
+#include <cerrno>
+
+export module nihil.util:save_errno;
+
+// save_errno: save the current value of errno and restore it when we're destroyed.
+// this allows wrappers around C functions that use errno to preserve the caller's
+// errno value.
+
+namespace nihil {
+
+export struct save_errno final
+{
+ save_errno() : m_errno(errno) {}
+
+ ~save_errno()
+ {
+ errno = m_errno;
+ }
+
+ // Not copyable
+ save_errno(const save_errno&) = delete;
+ auto operator=(const save_errno&) -> save_errno & = delete;
+
+ // Not movable
+ save_errno(save_errno&&) = delete;
+ auto operator=(save_errno&&) -> save_errno & = delete;
+
+private:
+ int m_errno;
+};
+
+} // namespace nihil
diff --git a/nihil.util/skipws.ccm b/nihil.util/skipws.ccm
index 4813ae8..0a15775 100644
--- a/nihil.util/skipws.ccm
+++ b/nihil.util/skipws.ccm
@@ -1,27 +1,14 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <algorithm>
-#include <locale>
-#include <ranges>
-#include <string>
-
+// This source code is released into the public domain.
export module nihil.util:skipws;
import :ctype;
namespace nihil {
-/*
- * Remove leading whitespace from a string.
- */
-
-export template<typename Char> [[nodiscard]]
-auto skipws(std::basic_string_view<Char> text,
- std::locale const &locale = std::locale())
+// Remove leading whitespace from a string.
+export template <typename Char>
+[[nodiscard]]
+auto skipws(std::basic_string_view<Char> text, std::locale const &locale = std::locale())
-> std::basic_string_view<Char>
{
auto is_space = ctype_is(std::ctype_base::space, locale);
@@ -29,10 +16,8 @@ auto skipws(std::basic_string_view<Char> text,
return {nonws, std::ranges::end(text)};
}
-export template<typename Char>
-auto skipws(std::basic_string_view<Char> *text,
- std::locale const &locale = std::locale())
- -> void
+export template <typename Char>
+auto skipws(std::basic_string_view<Char> *text, std::locale const &locale = std::locale()) -> void
{
*text = skipws(*text, locale);
}
diff --git a/nihil.util/test_skipws.cc b/nihil.util/skipws.test.cc
index 837c1f3..0cb741c 100644
--- a/nihil.util/test_skipws.cc
+++ b/nihil.util/skipws.test.cc
@@ -1,17 +1,14 @@
-/*
- * This source code is released into the public domain.
- */
-
-#include <locale>
-#include <string>
-using namespace std::literals;
+// This source code is released into the public domain.
#include <catch2/catch_test_macros.hpp>
+import nihil.std;
import nihil.util;
TEST_CASE("skipws: basic", "[skipws]")
{
+ using namespace std::literals;
+
REQUIRE(nihil::skipws("foo"sv) == "foo");
REQUIRE(nihil::skipws(" foo"sv) == "foo");
REQUIRE(nihil::skipws("foo "sv) == "foo ");
@@ -20,6 +17,8 @@ TEST_CASE("skipws: basic", "[skipws]")
TEST_CASE("skipws: pointer", "[skipws]")
{
+ using namespace std::literals;
+
auto s = "foo"sv;
nihil::skipws(&s);
REQUIRE(s == "foo");
@@ -39,6 +38,8 @@ TEST_CASE("skipws: pointer", "[skipws]")
TEST_CASE("skipws: locale", "[skipws]")
{
+ using namespace std::literals;
+
// Assume the default locale is C.
REQUIRE(nihil::skipws(L"\u2003foo"sv) == L"\u2003foo");
REQUIRE(nihil::skipws(L"\u2003foo"sv, std::locale("C.UTF-8")) == L"foo");
diff --git a/nihil.util/tabulate.ccm b/nihil.util/tabulate.ccm
index 5998b24..8f5c22e 100644
--- a/nihil.util/tabulate.ccm
+++ b/nihil.util/tabulate.ccm
@@ -1,53 +1,38 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <algorithm>
-#include <cstdlib>
-#include <format>
-#include <ranges>
-#include <iterator>
-#include <vector>
-
+// This source code is released into the public domain.
export module nihil.util:tabulate;
+import nihil.std;
import nihil.error;
import :ctype;
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
- */
+// 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 {
- table_spec_error(std::string_view what)
+ explicit table_spec_error(std::string_view what)
: error(what)
{
}
};
-/*
- * The specification for a single field.
- */
+// The specification for a single field.
template<typename Char>
struct field_spec {
enum align_t { left, right };
diff --git a/nihil.util/test_tabulate.cc b/nihil.util/tabulate.test.cc
index 8dee796..408cc18 100644
--- a/nihil.util/test_tabulate.cc
+++ b/nihil.util/tabulate.test.cc
@@ -1,13 +1,8 @@
-/*
- * This source code is released into the public domain.
- */
-
-#include <iterator>
-#include <string>
-#include <vector>
+// This source code is released into the public domain.
#include <catch2/catch_test_macros.hpp>
+import nihil.std;
import nihil.util;
using namespace std::literals;