aboutsummaryrefslogtreecommitdiffstats
path: root/nihil.cli/dispatch_command.cc
blob: e9bf7799f037228d22c071c7f3422edcdc5a3ace (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// This source code is released into the public domain.
module;

#include <stdlib.h>   // getprogname, NOLINT
#include <sysexits.h> // EX_USAGE
#include <unistd.h>   // getopt

module nihil.cli;

import nihil.std;
import nihil.core;
import nihil.posix;

namespace nihil {

namespace {
// Print this node's children in a form useful to humans.
auto print_commands(command_tree_node const &node) -> void
{
	for (auto &&child : node.children())
		std::println(std::cerr, "  {}", child.command().path());
}
} // namespace

auto dispatch_command(int const argc, char **argv) -> int
{
	// Reset getopt(3) for the command, in case main() used it already.
	optreset = 1;
	optind = 1;

	// Node that tree.find() never fails, at worst it will return the root node.
	auto tree = build_command_tree();
	auto [node, rest] = tree.find(std::span(argv, argc));
	auto const &command = node->command();

	// 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 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.
	auto nrest = static_cast<int>(std::ranges::distance(rest));
	// Keep the first argument, because getopt() wants it
	if (nrest < argc)
		++nrest;
	auto ret = command.invoke(nrest, argv + (argc - nrest));

	// Restore the old progname.
	progname_guard.release();

	if (ret)
		return *ret;

	// Incomplete command: print the list of valid commands at this node.
	if (ret.error() == errc::incomplete_command) {
		std::println(std::cerr, "{}: usage:", ::getprogname());
		print_commands(*node);
		return EX_USAGE;
	}

	// We didn't recognise the error, so just print it and exit.
	std::println(std::cerr, "{}", ret.error());
	return 1;
}

} // namespace nihil