diff options
| -rw-r--r-- | CMakeLists.txt | 2 | ||||
| -rw-r--r-- | nihil.cli/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | nihil.cli/command.ccm | 17 | ||||
| -rw-r--r-- | nihil.cli/command_tree.ccm | 2 | ||||
| -rw-r--r-- | nihil.cli/dispatch_command.cc | 15 | ||||
| -rw-r--r-- | nihil.cli/registry.cc | 43 | ||||
| -rw-r--r-- | nihil.cli/registry.ccm | 27 | ||||
| -rw-r--r-- | nihil.flagset/flagset.ccm | 107 | ||||
| -rw-r--r-- | nihil.posix/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | nihil.posix/execv.ccm | 6 | ||||
| -rw-r--r-- | nihil.posix/open.ccm | 16 | ||||
| -rw-r--r-- | nihil.posix/posix.ccm | 1 | ||||
| -rw-r--r-- | nihil.posix/progname.ccm | 68 | ||||
| -rw-r--r-- | nihil.std/nihil.std.ccm | 32 |
14 files changed, 206 insertions, 133 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index c51c2aa..a2c9187 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ find_package(PkgConfig REQUIRED) # clang-tidy support find_program(CLANG_TIDY clang-tidy) -if(NOT (CLANG_TIDY STREQUAL "")) +if (NOT (CLANG_TIDY STREQUAL "CLANG_TIDY-NOTFOUND")) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) file(GLOB_RECURSE NIHIL_SOURCES "*.cc" "*.ccm") diff --git a/nihil.cli/CMakeLists.txt b/nihil.cli/CMakeLists.txt index 091c34f..859fea5 100644 --- a/nihil.cli/CMakeLists.txt +++ b/nihil.cli/CMakeLists.txt @@ -4,6 +4,7 @@ add_library(nihil.cli STATIC) target_link_libraries(nihil.cli PRIVATE nihil.std nihil.generator + nihil.posix; nihil.util ) target_sources(nihil.cli @@ -18,7 +19,6 @@ target_sources(nihil.cli PRIVATE dispatch_command.cc - registry.cc ) if(NIHIL_TESTS) diff --git a/nihil.cli/command.ccm b/nihil.cli/command.ccm index dc3f29a..ea52bbf 100644 --- a/nihil.cli/command.ccm +++ b/nihil.cli/command.ccm @@ -31,15 +31,24 @@ export struct command final { using command_handler_t = std::function<int(int, char **)>; - command(std::string_view const path, std::string_view const usage, command_handler_t handler) noexcept - : m_path(path) + // Construct a new command with a handler and register it. Since this is usually invoked + // at global scope by static object construction, we handle exception internally. + command(std::string_view const path, std::string_view const usage, auto &&handler) noexcept + try : m_path(path) , m_usage(usage) - , m_handler(std::move(handler)) + , m_handler(std::forward<decltype(handler)>(handler)) { register_command(this); + } catch (std::exception const &exc) { + std::println(std::cerr, "register_command(): {}", exc.what()); + std::quick_exit(1); + } catch (...) { + std::println(std::cerr, "register_command(): unknown error"); + std::quick_exit(1); } - explicit command(std::string_view const path) noexcept + // Create a stub command which doesn't have a handler. + explicit command(std::string_view const path) : m_path(path) { } diff --git a/nihil.cli/command_tree.ccm b/nihil.cli/command_tree.ccm index 7399357..8ed16c7 100644 --- a/nihil.cli/command_tree.ccm +++ b/nihil.cli/command_tree.ccm @@ -166,7 +166,7 @@ private: { auto tree = command_tree(); - for (auto &&command : get_registered_commands()) { + for (auto &&command : get_registry()) { // Throws std::logic_error on duplicates. tree.insert(command); } diff --git a/nihil.cli/dispatch_command.cc b/nihil.cli/dispatch_command.cc index 6e3a757..e9bf779 100644 --- a/nihil.cli/dispatch_command.cc +++ b/nihil.cli/dispatch_command.cc @@ -9,6 +9,7 @@ module nihil.cli; import nihil.std; import nihil.core; +import nihil.posix; namespace nihil { @@ -17,11 +18,11 @@ namespace { auto print_commands(command_tree_node const &node) -> void { for (auto &&child : node.children()) - std::print(std::cerr, " {}\n", child.command().path()); + std::println(std::cerr, " {}", child.command().path()); } } // namespace -auto dispatch_command(int argc, char **argv) -> int +auto dispatch_command(int const argc, char **argv) -> int { // Reset getopt(3) for the command, in case main() used it already. optreset = 1; @@ -35,12 +36,8 @@ auto dispatch_command(int argc, char **argv) -> int // Set the program name to the existing progname plus the full path to the command being // invoked; this makes error messages nicer. Save the old progname so we can restore it // after invoking the command. - auto const *old_progname = ::getprogname(); - - { - auto cprogname = std::format("{} {}", ::getprogname(), command.path()); - ::setprogname(cprogname.c_str()); - } + auto progname_guard = + setprogname(std::format("{} {}", getprogname().value_or(""), command.path())); // Invoke the command see what it returns. If it's an exit code, just return it. // Otherwise, handle the error. @@ -51,7 +48,7 @@ auto dispatch_command(int argc, char **argv) -> int auto ret = command.invoke(nrest, argv + (argc - nrest)); // Restore the old progname. - ::setprogname(old_progname); + progname_guard.release(); if (ret) return *ret; diff --git a/nihil.cli/registry.cc b/nihil.cli/registry.cc deleted file mode 100644 index a972a65..0000000 --- a/nihil.cli/registry.cc +++ /dev/null @@ -1,43 +0,0 @@ -// This source code is released into the public domain. -module nihil.cli; - -import nihil.std; - -namespace nihil { - -// Get the registry storage. Because this is called from global ctors, -// it handles exceptions itself. -auto get_registry() noexcept -> std::vector<std::shared_ptr<command>> & -try { - static auto commands = std::vector<std::shared_ptr<command>>(); - return commands; -} catch (std::exception const &exc) { - std::println(std::cerr, "{}", exc.what()); - std::exit(1); // NOLINT -} catch (...) { - std::println(std::cerr, "get_registered_commands(): unknown error\n"); - std::exit(1); // NOLINT -} - -// Register a new command. -auto register_command(command *cmd) noexcept -> void -try { - auto null_deleter = [] (command const *) -> void {}; - - auto &commands = get_registry(); - commands.emplace_back(cmd, null_deleter); -} catch (std::exception const &exc) { - std::println(std::cerr, "{}", exc.what()); - std::exit(1); // NOLINT -} catch (...) { - std::println(std::cerr, "get_registered_commands(): unknown error\n"); - std::exit(1); // NOLINT -} - -// Get the list of registered commands. -auto get_registered_commands() -> std::span<std::shared_ptr<command>> -{ - return {get_registry()}; -} - -} // namespace nihil diff --git a/nihil.cli/registry.ccm b/nihil.cli/registry.ccm index e5a7e29..5e31195 100644 --- a/nihil.cli/registry.ccm +++ b/nihil.cli/registry.ccm @@ -7,11 +7,28 @@ namespace nihil { export struct command; -// Register a command. This is guaranteed not to throw; errors will print -// a diagnostic and exit. -auto register_command(command *cmd) noexcept -> void; +/////////////////////////////////////////////////////////////////////// +// Command registry storage. This is where command::command() registers +// itself when the global command objects are constructed at startup. +// +// Because we sometimes create stub commands dynamically, the registry +// storage is a list of shared_ptr<command>. The "real" commands (which +// refer to global objects) will be created with a null deleter so they +// are never deleted. This allows real and stub commands to mix in the +// same command_tree. -// Get previously registered commands. -auto get_registered_commands() -> std::span<std::shared_ptr<command>>; +// Get the current registry. +auto get_registry() -> std::vector<std::shared_ptr<command>> & +{ + static auto commands = std::vector<std::shared_ptr<command>>(); + return commands; +} + +// Register a new command. +auto register_command(command *cmd) noexcept -> void +{ + auto constexpr null_deleter = [] (command const *) -> void {}; + get_registry().emplace_back(cmd, null_deleter); +} } // namespace nihil diff --git a/nihil.flagset/flagset.ccm b/nihil.flagset/flagset.ccm index 5bd7720..796a2c2 100644 --- a/nihil.flagset/flagset.ccm +++ b/nihil.flagset/flagset.ccm @@ -9,8 +9,9 @@ import nihil.std; namespace nihil { -export template<std::integral base_type, typename Tag> -struct flagset final { +export template <std::integral base_type, typename Tag> +struct flagset final +{ using underlying_type = base_type; /* @@ -23,12 +24,13 @@ struct flagset final { */ flagset(flagset const &other) noexcept : m_value(other.m_value) - {} + { + } /* * Create flags from an integer mask. */ - template<base_type flag> + template <base_type flag> [[nodiscard]] static constexpr auto mask() noexcept -> flagset { return flagset(flag); @@ -37,7 +39,7 @@ struct flagset final { /* * Create flags for a specific bit. */ - template<unsigned bitnr> + template <unsigned bitnr> [[nodiscard]] static constexpr auto bit() noexcept -> flagset { static_assert(bitnr < std::numeric_limits<base_type>::digits); @@ -47,8 +49,7 @@ struct flagset final { /* * Create flags from a runtime value. */ - [[nodiscard]] static auto from_int(base_type value) noexcept - -> flagset + [[nodiscard]] static auto from_int(base_type value) noexcept -> flagset { return flagset(value); } @@ -56,8 +57,7 @@ struct flagset final { /* * Assign this flagset. */ - auto operator=(this flagset &lhs, flagset rhs) noexcept - -> flagset & + auto operator=(this flagset &lhs, flagset rhs) noexcept -> flagset & { if (&lhs != &rhs) lhs.m_value = rhs.m_value; @@ -67,8 +67,7 @@ struct flagset final { /* * The integer value of this flagset. */ - [[nodiscard]] constexpr auto value(this flagset self) noexcept - -> base_type + [[nodiscard]] constexpr auto value(this flagset self) noexcept -> base_type { return self.m_value; } @@ -76,8 +75,7 @@ struct flagset final { /* * True if this flagset has any bits set. */ - [[nodiscard]] explicit constexpr operator bool(this flagset self) - noexcept + [[nodiscard]] explicit constexpr operator bool(this flagset self) noexcept { return self.m_value != 0; } @@ -85,8 +83,7 @@ struct flagset final { /* * Set bits. */ - constexpr auto operator|= (this flagset &lhs, flagset rhs) noexcept - -> flagset & + constexpr auto operator|=(this flagset &lhs, flagset rhs) noexcept -> flagset & { lhs.m_value |= rhs.value(); return lhs; @@ -95,8 +92,7 @@ struct flagset final { /* * Mask bits. */ - constexpr auto operator&= (this flagset &lhs, flagset rhs) noexcept - -> flagset & + constexpr auto operator&=(this flagset &lhs, flagset rhs) noexcept -> flagset & { lhs.m_value &= rhs.value(); return lhs; @@ -105,8 +101,7 @@ struct flagset final { /* * Invert bits. */ - [[nodiscard]] constexpr auto operator~ (this flagset self) noexcept - -> flagset + [[nodiscard]] constexpr auto operator~(this flagset self) noexcept -> flagset { return flagset(~self.m_value); } @@ -114,8 +109,7 @@ struct flagset final { /* * xor bits. */ - constexpr auto operator^= (this flagset &lhs, flagset rhs) - noexcept -> flagset + constexpr auto operator^=(this flagset &lhs, flagset rhs) noexcept -> flagset { lhs.m_value ^= rhs.value(); return lhs; @@ -126,68 +120,55 @@ private: explicit constexpr flagset(base_type mask) noexcept : m_value(mask) - {} -}; + { + } -export template<std::integral base_type, typename Tag> -[[nodiscard]] auto operator| (flagset<base_type, Tag> lhs, - flagset<base_type, Tag> rhs) noexcept - -> flagset<base_type, Tag> -{ - return (lhs |= rhs); -} + [[nodiscard]] friend auto operator|(flagset lhs, flagset rhs) noexcept -> flagset + { + return (lhs |= rhs); + } -export template<std::integral base_type, typename Tag> -[[nodiscard]] auto operator& (flagset<base_type, Tag> lhs, - flagset<base_type, Tag> rhs) noexcept - -> flagset<base_type, Tag> -{ - return (lhs &= rhs); -} + [[nodiscard]] friend auto operator&(flagset lhs, flagset rhs) noexcept -> flagset + { + return (lhs &= rhs); + } -export template<std::integral base_type, typename Tag> -[[nodiscard]] auto operator^ (flagset<base_type, Tag> lhs, - flagset<base_type, Tag> rhs) noexcept - -> flagset<base_type, Tag> -{ - return (lhs ^= rhs); -} + [[nodiscard]] friend auto operator^(flagset lhs, flagset rhs) noexcept -> flagset + { + return (lhs ^= rhs); + } -export template<std::integral base_type, typename Tag> -[[nodiscard]] auto operator== (flagset<base_type, Tag> lhs, - flagset<base_type, Tag> rhs) noexcept - -> bool -{ - return lhs.value() == rhs.value(); -} + [[nodiscard]] friend auto + operator==(flagset lhs, flagset rhs) noexcept -> bool + { + return lhs.value() == rhs.value(); + } -export template<std::integral base_type, typename Tag> -auto operator<<(std::ostream &strm, flagset<base_type, Tag> flags) - -> std::ostream & -{ - std::print(strm, "{}", flags); - return strm; -} + friend auto operator<<(std::ostream &strm, flagset<base_type, Tag> flags) -> std::ostream & + { + std::print(strm, "{}", flags); + return strm; + } +}; } // namespace nihil /* * Formatting for flagset. */ -export template<std::integral base_type, typename Tag, typename Char> +export template <std::integral base_type, typename Tag, typename Char> struct std::formatter<nihil::flagset<base_type, Tag>, Char> { using flags_t = nihil::flagset<base_type, Tag>; - template<typename ParseContext> + template <typename ParseContext> constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return ctx.begin(); } - template<typename FmtContext> - auto format(flags_t flags, FmtContext& ctx) const - -> FmtContext::iterator + template <typename FmtContext> + auto format(flags_t flags, FmtContext &ctx) const -> FmtContext::iterator { auto constexpr digits = std::numeric_limits<base_type>::digits; auto value = flags.value(); diff --git a/nihil.posix/CMakeLists.txt b/nihil.posix/CMakeLists.txt index d76ae4d..25f13f1 100644 --- a/nihil.posix/CMakeLists.txt +++ b/nihil.posix/CMakeLists.txt @@ -30,6 +30,7 @@ target_sources(nihil.posix open_in_path.ccm paths.ccm process.ccm + progname.ccm read_file.ccm rename.ccm spawn.ccm diff --git a/nihil.posix/execv.ccm b/nihil.posix/execv.ccm index d598d94..cd501f7 100644 --- a/nihil.posix/execv.ccm +++ b/nihil.posix/execv.ccm @@ -3,6 +3,10 @@ module; #include <unistd.h> // execv() +#include "nihil.hh" + +extern char **environ; // NOLINT + export module nihil.posix:execv; import nihil.std; @@ -56,7 +60,7 @@ export struct execv final [&] (fd const &file) { #if NIHIL_HAVE_FEXECVE == 1 - ::fexecv(file.get(), self.m_args.data()); + ::fexecve(file.get(), self.m_args.data(), environ); return std::unexpected(error("execve failed", error(sys_error()))); #else std::ignore = file; diff --git a/nihil.posix/open.ccm b/nihil.posix/open.ccm index f2f5ecd..a5a9e5d 100644 --- a/nihil.posix/open.ccm +++ b/nihil.posix/open.ccm @@ -39,9 +39,6 @@ export inline constexpr auto open_shared_lock = open_flags::mask<O_SHLOCK>(); export inline constexpr auto open_exclusive_lock = open_flags::mask<O_EXLOCK>(); export inline constexpr auto open_directory = open_flags::mask<O_DIRECTORY>(); export inline constexpr auto open_nofollow = open_flags::mask<O_NOFOLLOW>(); -export inline constexpr auto open_nofollow_any = open_flags::mask<O_NOFOLLOW_ANY>(); -export inline constexpr auto open_symlink = open_flags::mask<O_SYMLINK>(); -export inline constexpr auto open_eventonly = open_flags::mask<O_EVTONLY>(); export inline constexpr auto open_close_on_exec = open_flags::mask<O_CLOEXEC>(); export inline constexpr auto open_resolve_beneath = open_flags::mask<O_RESOLVE_BENEATH>(); @@ -62,6 +59,19 @@ export inline constexpr auto open_path = open_flags::mask<O_PATH>(); export inline constexpr auto open_empty_path = open_flags::mask<O_EMPTY_PATH>(); #endif +// macOS +#ifdef O_NOFOLLOW_ANY +export inline constexpr auto open_nofollow_any = open_flags::mask<O_NOFOLLOW_ANY>(); +#endif + +#ifdef O_SYMLINK +export inline constexpr auto open_symlink = open_flags::mask<O_SYMLINK>(); +#endif + +#ifdef O_EVTONLY +export inline constexpr auto open_eventonly = open_flags::mask<O_EVTONLY>(); +#endif + // Open the given file and return an fd for it. export [[nodiscard]] auto open(std::filesystem::path const &filename, open_flags flags, int mode = 0777) -> std::expected<fd, error> diff --git a/nihil.posix/posix.ccm b/nihil.posix/posix.ccm index aa21649..c80724d 100644 --- a/nihil.posix/posix.ccm +++ b/nihil.posix/posix.ccm @@ -19,6 +19,7 @@ export import :open; export import :open_in_path; export import :paths; export import :process; +export import :progname; export import :read_file; export import :rename; export import :spawn; diff --git a/nihil.posix/progname.ccm b/nihil.posix/progname.ccm new file mode 100644 index 0000000..127b972 --- /dev/null +++ b/nihil.posix/progname.ccm @@ -0,0 +1,68 @@ +// This source code is released into the public domain. +module; + +// progname: wrappers for getprogname and setprogname. +// +// setprogname() doesn't copy the provided name, which makes it awkward to provide +// dynamic values (e.g., std::string). We solve this by making setprogname a guard +// object which resets the previous progname when it's destroyed. This also happens +// to be useful in cases like nihil.cli where we want to temporarily set the progname. + +#include <stdlib.h> // NOLINT + +export module nihil.posix:progname; + +import nihil.std; + +namespace nihil { + +// Get the current program name. We could return std::string_view here, but since +// the current program name can change unexpectedly, we don't. +export [[nodiscard]] auto getprogname() -> std::optional<std::string> +{ + if (auto const *progname = ::getprogname(); progname != nullptr) + return {progname}; + return {}; +} + +// Set the progname name to a new value for the lifetime of this object. +// Reset it to the previous value when the object is destroyed. +export struct setprogname final +{ + // Set the program name. + explicit setprogname(std::string_view const progname) + : m_progname(progname) + , m_old_progname(::getprogname()) + { + ::setprogname(m_progname.data()); + } + + // Restore the old name on destruction. + ~setprogname() + { + if (m_old_progname != nullptr) + ::setprogname(m_old_progname); + } + + // Restore the old program name immediately. + auto release() -> void + { + if (m_old_progname != nullptr) { + ::setprogname(m_old_progname); + m_old_progname = nullptr; + } + } + + // Not copyable. + setprogname(setprogname const &) = delete; + auto operator=(setprogname const &) -> setprogname & = delete; + + // Not movable. + setprogname(setprogname &&) = delete; + auto operator=(setprogname &&) -> setprogname & = delete; + +private: + std::string m_progname; + char const *m_old_progname; +}; +} // namespace nihil diff --git a/nihil.std/nihil.std.ccm b/nihil.std/nihil.std.ccm index 80ca3c9..d0a20b1 100644 --- a/nihil.std/nihil.std.ccm +++ b/nihil.std/nihil.std.ccm @@ -1,4 +1,5 @@ // This source code is released into the public domain. + module; // Export the parts of std that nihil uses. This is technically undefined behaviour since we're @@ -165,6 +166,7 @@ using std::uint64_t; // <cstdlib> using std::exit; using std::quick_exit; +using std::abort; // <cstdio> using std::FILE; @@ -196,11 +198,35 @@ using std::filesystem::exists; } // <format> +using std::basic_format_context; +using std::format_context; +using std::wformat_context; +using std::basic_format_args; +using std::format_args; +using std::wformat_args; +using std::basic_format_string; +using std::format_string; +using std::wformat_string; +using std::runtime_format; using std::format; -using std::formatter; using std::format_to; +using std::vformat; +using std::vformat_to; using std::format_to_n; -using std::runtime_format; +using std::format_to_n_result; +using std::formatted_size; +using std::formatter; +using std::formattable; +using std::basic_format_parse_context; +using std::format_parse_context; +using std::wformat_parse_context; +using std::format_kind; +using std::range_format; +using std::range_formatter; +using std::basic_format_arg; +using std::visit_format_arg; +using std::make_format_args; +using std::make_wformat_args; using std::format_error; // <functional> @@ -289,6 +315,8 @@ using std::optional; // <print> using std::print; using std::println; +using std::vprint_nonunicode; +using std::vprint_unicode; // <random> using std::default_random_engine; |
