1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmCMakePathCommand.h"
14 #include <cm/string_view>
15 #include <cmext/string_view>
17 #include "cmArgumentParser.h"
18 #include "cmCMakePath.h"
19 #include "cmExecutionStatus.h"
20 #include "cmMakefile.h"
22 #include "cmStringAlgorithms.h"
23 #include "cmSubcommandTable.h"
24 #include "cmSystemTools.h"
28 // Helper classes for argument parsing
29 template <typename Result>
30 class CMakePathArgumentParser : public cmArgumentParser<Result>
33 CMakePathArgumentParser()
34 : cmArgumentParser<Result>()
39 CMakePathArgumentParser& Bind(cm::static_string_view name, T Result::*member)
41 this->cmArgumentParser<Result>::Bind(name, member);
45 template <int Advance = 2>
46 Result Parse(std::vector<std::string> const& args,
47 std::vector<std::string>* keywordsMissingValue = nullptr,
48 std::vector<std::string>* parsedKeywords = nullptr) const
52 return this->cmArgumentParser<Result>::Parse(
53 cmMakeRange(args).advance(Advance), &this->Inputs, keywordsMissingValue,
57 const std::vector<std::string>& GetInputs() const { return this->Inputs; }
60 mutable std::vector<std::string> Inputs;
63 // OUTPUT_VARIABLE is expected
64 template <typename Result>
65 class ArgumentParserWithOutputVariable : public CMakePathArgumentParser<Result>
68 ArgumentParserWithOutputVariable()
69 : CMakePathArgumentParser<Result>()
71 this->Bind("OUTPUT_VARIABLE"_s, &Result::Output);
75 ArgumentParserWithOutputVariable& Bind(cm::static_string_view name,
78 this->cmArgumentParser<Result>::Bind(name, member);
82 template <int Advance = 2>
83 Result Parse(std::vector<std::string> const& args) const
85 this->KeywordsMissingValue.clear();
86 this->ParsedKeywords.clear();
88 return this->CMakePathArgumentParser<Result>::template Parse<Advance>(
89 args, &this->KeywordsMissingValue, &this->ParsedKeywords);
92 const std::vector<std::string>& GetKeywordsMissingValue() const
94 return this->KeywordsMissingValue;
96 const std::vector<std::string>& GetParsedKeywords() const
98 return this->ParsedKeywords;
101 bool checkOutputVariable(const Result& arguments,
102 cmExecutionStatus& status) const
104 if (std::find(this->GetKeywordsMissingValue().begin(),
105 this->GetKeywordsMissingValue().end(),
106 "OUTPUT_VARIABLE"_s) !=
107 this->GetKeywordsMissingValue().end()) {
108 status.SetError("OUTPUT_VARIABLE requires an argument.");
112 if (std::find(this->GetParsedKeywords().begin(),
113 this->GetParsedKeywords().end(),
114 "OUTPUT_VARIABLE"_s) != this->GetParsedKeywords().end() &&
115 arguments.Output.empty()) {
116 status.SetError("Invalid name for output variable.");
124 mutable std::vector<std::string> KeywordsMissingValue;
125 mutable std::vector<std::string> ParsedKeywords;
128 struct OutputVariable
132 // Usable when OUTPUT_VARIABLE is the only option
133 class OutputVariableParser
134 : public ArgumentParserWithOutputVariable<OutputVariable>
138 struct NormalizeOption
140 bool Normalize = false;
142 // Usable when NORMALIZE is the only option
143 class NormalizeParser : public CMakePathArgumentParser<NormalizeOption>
146 NormalizeParser() { this->Bind("NORMALIZE"_s, &NormalizeOption::Normalize); }
149 // retrieve value of input path from specified variable
150 bool getInputPath(const std::string& arg, cmExecutionStatus& status,
153 cmValue def = status.GetMakefile().GetDefinition(arg);
155 status.SetError("undefined variable for input path.");
163 bool HandleGetCommand(std::vector<std::string> const& args,
164 cmExecutionStatus& status)
166 static std::map<cm::string_view,
167 std::function<cmCMakePath(const cmCMakePath&, bool)>> const
168 actions{ { "ROOT_NAME"_s,
169 [](const cmCMakePath& path, bool) -> cmCMakePath {
170 return path.GetRootName();
172 { "ROOT_DIRECTORY"_s,
173 [](const cmCMakePath& path, bool) -> cmCMakePath {
174 return path.GetRootDirectory();
177 [](const cmCMakePath& path, bool) -> cmCMakePath {
178 return path.GetRootPath();
181 [](const cmCMakePath& path, bool) -> cmCMakePath {
182 return path.GetFileName();
185 [](const cmCMakePath& path, bool last_only) -> cmCMakePath {
187 return path.GetExtension();
189 return path.GetWideExtension();
192 [](const cmCMakePath& path, bool last_only) -> cmCMakePath {
194 return path.GetStem();
196 return path.GetNarrowStem();
199 [](const cmCMakePath& path, bool) -> cmCMakePath {
200 return path.GetRelativePath();
203 [](const cmCMakePath& path, bool) -> cmCMakePath {
204 return path.GetParentPath();
207 if (args.size() < 4) {
208 status.SetError("GET must be called with at least three arguments.");
212 const auto& action = args[2];
214 if (actions.find(action) == actions.end()) {
216 cmStrCat("GET called with an unknown action: ", action, "."));
222 bool LastOnly = false;
225 CMakePathArgumentParser<Arguments> parser;
226 if ((action == "EXTENSION"_s || action == "STEM"_s)) {
227 parser.Bind("LAST_ONLY"_s, &Arguments::LastOnly);
230 Arguments const arguments = parser.Parse<3>(args);
232 if (parser.GetInputs().size() != 1) {
233 status.SetError("GET called with unexpected arguments.");
236 if (parser.GetInputs().front().empty()) {
237 status.SetError("Invalid name for output variable.");
242 if (!getInputPath(args[1], status, path)) {
246 auto result = actions.at(action)(path, arguments.LastOnly);
248 status.GetMakefile().AddDefinition(parser.GetInputs().front(),
254 bool HandleSetCommand(std::vector<std::string> const& args,
255 cmExecutionStatus& status)
257 if (args.size() < 3 || args.size() > 4) {
258 status.SetError("SET must be called with two or three arguments.");
262 if (args[1].empty()) {
263 status.SetError("Invalid name for path variable.");
267 static NormalizeParser const parser;
269 const auto arguments = parser.Parse(args);
271 if (parser.GetInputs().size() != 1) {
272 status.SetError("SET called with unexpected arguments.");
277 cmCMakePath(parser.GetInputs().front(), cmCMakePath::native_format);
279 if (arguments.Normalize) {
280 path = path.Normal();
283 status.GetMakefile().AddDefinition(args[1], path.GenericString());
288 bool HandleAppendCommand(std::vector<std::string> const& args,
289 cmExecutionStatus& status)
291 if (args[1].empty()) {
292 status.SetError("Invalid name for path variable.");
296 static OutputVariableParser const parser{};
298 const auto arguments = parser.Parse(args);
300 if (!parser.checkOutputVariable(arguments, status)) {
304 cmCMakePath path(status.GetMakefile().GetSafeDefinition(args[1]));
305 for (const auto& input : parser.GetInputs()) {
309 status.GetMakefile().AddDefinition(
310 arguments.Output.empty() ? args[1] : arguments.Output, path.String());
315 bool HandleAppendStringCommand(std::vector<std::string> const& args,
316 cmExecutionStatus& status)
318 static OutputVariableParser const parser{};
320 const auto arguments = parser.Parse(args);
322 if (!parser.checkOutputVariable(arguments, status)) {
326 std::string inputPath;
327 if (!getInputPath(args[1], status, inputPath)) {
331 cmCMakePath path(inputPath);
332 for (const auto& input : parser.GetInputs()) {
336 status.GetMakefile().AddDefinition(
337 arguments.Output.empty() ? args[1] : arguments.Output, path.String());
342 bool HandleRemoveFilenameCommand(std::vector<std::string> const& args,
343 cmExecutionStatus& status)
345 static OutputVariableParser const parser{};
347 const auto arguments = parser.Parse(args);
349 if (!parser.checkOutputVariable(arguments, status)) {
353 if (!parser.GetInputs().empty()) {
354 status.SetError("REMOVE_FILENAME called with unexpected arguments.");
358 std::string inputPath;
359 if (!getInputPath(args[1], status, inputPath)) {
363 cmCMakePath path(inputPath);
364 path.RemoveFileName();
366 status.GetMakefile().AddDefinition(
367 arguments.Output.empty() ? args[1] : arguments.Output, path.String());
372 bool HandleReplaceFilenameCommand(std::vector<std::string> const& args,
373 cmExecutionStatus& status)
375 static OutputVariableParser const parser{};
377 const auto arguments = parser.Parse(args);
379 if (!parser.checkOutputVariable(arguments, status)) {
383 if (parser.GetInputs().size() > 1) {
384 status.SetError("REPLACE_FILENAME called with unexpected arguments.");
388 std::string inputPath;
389 if (!getInputPath(args[1], status, inputPath)) {
393 cmCMakePath path(inputPath);
394 path.ReplaceFileName(
395 parser.GetInputs().empty() ? "" : parser.GetInputs().front());
397 status.GetMakefile().AddDefinition(
398 arguments.Output.empty() ? args[1] : arguments.Output, path.String());
403 bool HandleRemoveExtensionCommand(std::vector<std::string> const& args,
404 cmExecutionStatus& status)
409 bool LastOnly = false;
412 static auto const parser =
413 ArgumentParserWithOutputVariable<Arguments>{}.Bind("LAST_ONLY"_s,
414 &Arguments::LastOnly);
416 Arguments const arguments = parser.Parse(args);
418 if (!parser.checkOutputVariable(arguments, status)) {
422 if (!parser.GetInputs().empty()) {
423 status.SetError("REMOVE_EXTENSION called with unexpected arguments.");
427 std::string inputPath;
428 if (!getInputPath(args[1], status, inputPath)) {
432 cmCMakePath path(inputPath);
434 if (arguments.LastOnly) {
435 path.RemoveExtension();
437 path.RemoveWideExtension();
440 status.GetMakefile().AddDefinition(
441 arguments.Output.empty() ? args[1] : arguments.Output, path.String());
446 bool HandleReplaceExtensionCommand(std::vector<std::string> const& args,
447 cmExecutionStatus& status)
452 bool LastOnly = false;
455 static auto const parser =
456 ArgumentParserWithOutputVariable<Arguments>{}.Bind("LAST_ONLY"_s,
457 &Arguments::LastOnly);
459 Arguments const arguments = parser.Parse(args);
461 if (!parser.checkOutputVariable(arguments, status)) {
465 if (parser.GetInputs().size() > 1) {
466 status.SetError("REPLACE_EXTENSION called with unexpected arguments.");
470 std::string inputPath;
471 if (!getInputPath(args[1], status, inputPath)) {
475 cmCMakePath path(inputPath);
476 cmCMakePath extension(
477 parser.GetInputs().empty() ? "" : parser.GetInputs().front());
479 if (arguments.LastOnly) {
480 path.ReplaceExtension(extension);
482 path.ReplaceWideExtension(extension);
485 status.GetMakefile().AddDefinition(
486 arguments.Output.empty() ? args[1] : arguments.Output, path.String());
491 bool HandleNormalPathCommand(std::vector<std::string> const& args,
492 cmExecutionStatus& status)
494 static OutputVariableParser const parser{};
496 const auto arguments = parser.Parse(args);
498 if (!parser.checkOutputVariable(arguments, status)) {
502 if (!parser.GetInputs().empty()) {
503 status.SetError("NORMAL_PATH called with unexpected arguments.");
507 std::string inputPath;
508 if (!getInputPath(args[1], status, inputPath)) {
512 auto path = cmCMakePath(inputPath).Normal();
514 status.GetMakefile().AddDefinition(
515 arguments.Output.empty() ? args[1] : arguments.Output, path.String());
520 bool HandleTransformPathCommand(
521 std::vector<std::string> const& args, cmExecutionStatus& status,
522 const std::function<cmCMakePath(const cmCMakePath&,
523 const std::string& base)>& transform,
524 bool normalizeOption = false)
529 std::string BaseDirectory;
530 bool Normalize = false;
533 auto parser = ArgumentParserWithOutputVariable<Arguments>{}.Bind(
534 "BASE_DIRECTORY"_s, &Arguments::BaseDirectory);
535 if (normalizeOption) {
536 parser.Bind("NORMALIZE"_s, &Arguments::Normalize);
539 Arguments arguments = parser.Parse(args);
541 if (!parser.checkOutputVariable(arguments, status)) {
545 if (!parser.GetInputs().empty()) {
546 status.SetError(cmStrCat(args[0], " called with unexpected arguments."));
550 if (std::find(parser.GetKeywordsMissingValue().begin(),
551 parser.GetKeywordsMissingValue().end(), "BASE_DIRECTORY"_s) !=
552 parser.GetKeywordsMissingValue().end()) {
553 status.SetError("BASE_DIRECTORY requires an argument.");
557 if (std::find(parser.GetParsedKeywords().begin(),
558 parser.GetParsedKeywords().end(),
559 "BASE_DIRECTORY"_s) == parser.GetParsedKeywords().end()) {
560 arguments.BaseDirectory = status.GetMakefile().GetCurrentSourceDirectory();
563 std::string inputPath;
564 if (!getInputPath(args[1], status, inputPath)) {
568 auto path = transform(cmCMakePath(inputPath), arguments.BaseDirectory);
569 if (arguments.Normalize) {
570 path = path.Normal();
573 status.GetMakefile().AddDefinition(
574 arguments.Output.empty() ? args[1] : arguments.Output, path.String());
579 bool HandleRelativePathCommand(std::vector<std::string> const& args,
580 cmExecutionStatus& status)
582 return HandleTransformPathCommand(
584 [](const cmCMakePath& path, const std::string& base) -> cmCMakePath {
585 return path.Relative(base);
589 bool HandleAbsolutePathCommand(std::vector<std::string> const& args,
590 cmExecutionStatus& status)
592 return HandleTransformPathCommand(
594 [](const cmCMakePath& path, const std::string& base) -> cmCMakePath {
595 return path.Absolute(base);
600 bool HandleNativePathCommand(std::vector<std::string> const& args,
601 cmExecutionStatus& status)
603 if (args.size() < 3 || args.size() > 4) {
604 status.SetError("NATIVE_PATH must be called with two or three arguments.");
608 static NormalizeParser const parser;
610 const auto arguments = parser.Parse(args);
612 if (parser.GetInputs().size() != 1) {
613 status.SetError("NATIVE_PATH called with unexpected arguments.");
616 if (parser.GetInputs().front().empty()) {
617 status.SetError("Invalid name for output variable.");
621 std::string inputPath;
622 if (!getInputPath(args[1], status, inputPath)) {
626 cmCMakePath path(inputPath);
627 if (arguments.Normalize) {
628 path = path.Normal();
631 status.GetMakefile().AddDefinition(parser.GetInputs().front(),
632 path.NativeString());
637 bool HandleConvertCommand(std::vector<std::string> const& args,
638 cmExecutionStatus& status)
640 #if defined(_WIN32) && !defined(__CYGWIN__)
641 const auto pathSep = ";"_s;
643 const auto pathSep = ":"_s;
645 const auto cmakePath = "TO_CMAKE_PATH_LIST"_s;
646 const auto nativePath = "TO_NATIVE_PATH_LIST"_s;
648 if (args.size() < 4 || args.size() > 5) {
649 status.SetError("CONVERT must be called with three or four arguments.");
653 const auto& action = args[2];
655 if (action != cmakePath && action != nativePath) {
657 cmStrCat("CONVERT called with an unknown action: ", action, "."));
661 if (args[3].empty()) {
662 status.SetError("Invalid name for output variable.");
666 static NormalizeParser const parser;
668 const auto arguments = parser.Parse<4>(args);
670 if (!parser.GetInputs().empty()) {
671 status.SetError("CONVERT called with unexpected arguments.");
675 std::vector<std::string> paths;
677 if (action == cmakePath) {
678 paths = cmSystemTools::SplitString(args[1], pathSep.front());
680 cmExpandList(args[1], paths);
683 for (auto& path : paths) {
684 auto p = cmCMakePath(path,
685 action == cmakePath ? cmCMakePath::native_format
686 : cmCMakePath::generic_format);
687 if (arguments.Normalize) {
690 if (action == cmakePath) {
691 path = p.GenericString();
693 path = p.NativeString();
697 auto value = cmJoin(paths, action == cmakePath ? ";"_s : pathSep);
698 status.GetMakefile().AddDefinition(args[3], value);
703 bool HandleCompareCommand(std::vector<std::string> const& args,
704 cmExecutionStatus& status)
706 if (args.size() != 5) {
707 status.SetError("COMPARE must be called with four arguments.");
711 static std::map<cm::string_view,
712 std::function<bool(const cmCMakePath&,
713 const cmCMakePath&)>> const operators{
715 [](const cmCMakePath& path1, const cmCMakePath& path2) -> bool {
716 return path1 == path2;
719 [](const cmCMakePath& path1, const cmCMakePath& path2) -> bool {
720 return path1 != path2;
724 const auto op = operators.find(args[2]);
725 if (op == operators.end()) {
726 status.SetError(cmStrCat(
727 "COMPARE called with an unknown comparison operator: ", args[2], "."));
731 if (args[4].empty()) {
732 status.SetError("Invalid name for output variable.");
736 cmCMakePath path1(args[1]);
737 cmCMakePath path2(args[3]);
738 auto result = op->second(path1, path2);
740 status.GetMakefile().AddDefinitionBool(args[4], result);
745 bool HandleHasItemCommand(
746 std::vector<std::string> const& args, cmExecutionStatus& status,
747 const std::function<bool(const cmCMakePath&)>& has_item)
749 if (args.size() != 3) {
751 cmStrCat(args.front(), " must be called with two arguments."));
755 std::string inputPath;
756 if (!getInputPath(args[1], status, inputPath)) {
760 if (args[2].empty()) {
761 status.SetError("Invalid name for output variable.");
765 cmCMakePath path(inputPath);
766 auto result = has_item(path);
768 status.GetMakefile().AddDefinitionBool(args[2], result);
773 bool HandleHasRootNameCommand(std::vector<std::string> const& args,
774 cmExecutionStatus& status)
776 return HandleHasItemCommand(
778 [](const cmCMakePath& path) -> bool { return path.HasRootName(); });
781 bool HandleHasRootDirectoryCommand(std::vector<std::string> const& args,
782 cmExecutionStatus& status)
784 return HandleHasItemCommand(
786 [](const cmCMakePath& path) -> bool { return path.HasRootDirectory(); });
789 bool HandleHasRootPathCommand(std::vector<std::string> const& args,
790 cmExecutionStatus& status)
792 return HandleHasItemCommand(
794 [](const cmCMakePath& path) -> bool { return path.HasRootPath(); });
797 bool HandleHasFilenameCommand(std::vector<std::string> const& args,
798 cmExecutionStatus& status)
800 return HandleHasItemCommand(
802 [](const cmCMakePath& path) -> bool { return path.HasFileName(); });
805 bool HandleHasExtensionCommand(std::vector<std::string> const& args,
806 cmExecutionStatus& status)
808 return HandleHasItemCommand(
810 [](const cmCMakePath& path) -> bool { return path.HasExtension(); });
813 bool HandleHasStemCommand(std::vector<std::string> const& args,
814 cmExecutionStatus& status)
816 return HandleHasItemCommand(
818 [](const cmCMakePath& path) -> bool { return path.HasStem(); });
821 bool HandleHasRelativePartCommand(std::vector<std::string> const& args,
822 cmExecutionStatus& status)
824 return HandleHasItemCommand(
826 [](const cmCMakePath& path) -> bool { return path.HasRelativePath(); });
829 bool HandleHasParentPathCommand(std::vector<std::string> const& args,
830 cmExecutionStatus& status)
832 return HandleHasItemCommand(
834 [](const cmCMakePath& path) -> bool { return path.HasParentPath(); });
837 bool HandleIsAbsoluteCommand(std::vector<std::string> const& args,
838 cmExecutionStatus& status)
840 if (args.size() != 3) {
841 status.SetError("IS_ABSOLUTE must be called with two arguments.");
845 std::string inputPath;
846 if (!getInputPath(args[1], status, inputPath)) {
850 if (args[2].empty()) {
851 status.SetError("Invalid name for output variable.");
855 bool isAbsolute = cmCMakePath(inputPath).IsAbsolute();
857 status.GetMakefile().AddDefinitionBool(args[2], isAbsolute);
862 bool HandleIsRelativeCommand(std::vector<std::string> const& args,
863 cmExecutionStatus& status)
865 if (args.size() != 3) {
866 status.SetError("IS_RELATIVE must be called with two arguments.");
870 std::string inputPath;
871 if (!getInputPath(args[1], status, inputPath)) {
875 if (args[2].empty()) {
876 status.SetError("Invalid name for output variable.");
880 bool isRelative = cmCMakePath(inputPath).IsRelative();
882 status.GetMakefile().AddDefinitionBool(args[2], isRelative);
887 bool HandleIsPrefixCommand(std::vector<std::string> const& args,
888 cmExecutionStatus& status)
890 if (args.size() < 4 || args.size() > 5) {
891 status.SetError("IS_PREFIX must be called with three or four arguments.");
895 static NormalizeParser const parser;
897 const auto arguments = parser.Parse(args);
899 if (parser.GetInputs().size() != 2) {
900 status.SetError("IS_PREFIX called with unexpected arguments.");
904 std::string inputPath;
905 if (!getInputPath(args[1], status, inputPath)) {
909 const auto& input = parser.GetInputs().front();
910 const auto& output = parser.GetInputs().back();
912 if (output.empty()) {
913 status.SetError("Invalid name for output variable.");
918 if (arguments.Normalize) {
920 cmCMakePath(inputPath).Normal().IsPrefix(cmCMakePath(input).Normal());
922 isPrefix = cmCMakePath(inputPath).IsPrefix(input);
925 status.GetMakefile().AddDefinitionBool(output, isPrefix);
930 bool HandleHashCommand(std::vector<std::string> const& args,
931 cmExecutionStatus& status)
933 if (args.size() != 3) {
934 status.SetError("HASH must be called with two arguments.");
938 std::string inputPath;
939 if (!getInputPath(args[1], status, inputPath)) {
943 const auto& output = args[2];
945 if (output.empty()) {
946 status.SetError("Invalid name for output variable.");
950 auto hash = hash_value(cmCMakePath(inputPath).Normal());
952 std::ostringstream out;
953 out << std::setbase(16) << hash;
955 status.GetMakefile().AddDefinition(output, out.str());
959 } // anonymous namespace
961 bool cmCMakePathCommand(std::vector<std::string> const& args,
962 cmExecutionStatus& status)
964 if (args.size() < 2) {
965 status.SetError("must be called with at least two arguments.");
969 static cmSubcommandTable const subcommand{
970 { "GET"_s, HandleGetCommand },
971 { "SET"_s, HandleSetCommand },
972 { "APPEND"_s, HandleAppendCommand },
973 { "APPEND_STRING"_s, HandleAppendStringCommand },
974 { "REMOVE_FILENAME"_s, HandleRemoveFilenameCommand },
975 { "REPLACE_FILENAME"_s, HandleReplaceFilenameCommand },
976 { "REMOVE_EXTENSION"_s, HandleRemoveExtensionCommand },
977 { "REPLACE_EXTENSION"_s, HandleReplaceExtensionCommand },
978 { "NORMAL_PATH"_s, HandleNormalPathCommand },
979 { "RELATIVE_PATH"_s, HandleRelativePathCommand },
980 { "ABSOLUTE_PATH"_s, HandleAbsolutePathCommand },
981 { "NATIVE_PATH"_s, HandleNativePathCommand },
982 { "CONVERT"_s, HandleConvertCommand },
983 { "COMPARE"_s, HandleCompareCommand },
984 { "HAS_ROOT_NAME"_s, HandleHasRootNameCommand },
985 { "HAS_ROOT_DIRECTORY"_s, HandleHasRootDirectoryCommand },
986 { "HAS_ROOT_PATH"_s, HandleHasRootPathCommand },
987 { "HAS_FILENAME"_s, HandleHasFilenameCommand },
988 { "HAS_EXTENSION"_s, HandleHasExtensionCommand },
989 { "HAS_STEM"_s, HandleHasStemCommand },
990 { "HAS_RELATIVE_PART"_s, HandleHasRelativePartCommand },
991 { "HAS_PARENT_PATH"_s, HandleHasParentPathCommand },
992 { "IS_ABSOLUTE"_s, HandleIsAbsoluteCommand },
993 { "IS_RELATIVE"_s, HandleIsRelativeCommand },
994 { "IS_PREFIX"_s, HandleIsPrefixCommand },
995 { "HASH"_s, HandleHashCommand }
998 return subcommand(args[0], args, status);