New command line parsing infrastructure
This commit is contained in:
		
							parent
							
								
									c780c1124e
								
							
						
					
					
						commit
						0db9e6cd1a
					
				
					 6 changed files with 472 additions and 81 deletions
				
			
		
							
								
								
									
										37
									
								
								src/libmain/common-args.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/libmain/common-args.cc
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,37 @@
 | 
				
			||||||
 | 
					#include "common-args.hh"
 | 
				
			||||||
 | 
					#include "globals.hh"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace nix {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MixCommonArgs::MixCommonArgs(const string & programName)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    mkFlag('v', "verbose", "increase verbosity level", []() {
 | 
				
			||||||
 | 
					        verbosity = (Verbosity) (verbosity + 1);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mkFlag(0, "quiet", "decrease verbosity level", []() {
 | 
				
			||||||
 | 
					        verbosity = verbosity > lvlError ? (Verbosity) (verbosity - 1) : lvlError;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mkFlag(0, "debug", "enable debug output", []() {
 | 
				
			||||||
 | 
					        verbosity = lvlDebug;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mkFlag1(0, "log-type", "type", "set logging format ('pretty', 'flat', 'systemd')",
 | 
				
			||||||
 | 
					        [](std::string s) {
 | 
				
			||||||
 | 
					            if (s == "pretty") logType = ltPretty;
 | 
				
			||||||
 | 
					            else if (s == "escapes") logType = ltEscapes;
 | 
				
			||||||
 | 
					            else if (s == "flat") logType = ltFlat;
 | 
				
			||||||
 | 
					            else if (s == "systemd") logType = ltSystemd;
 | 
				
			||||||
 | 
					            else throw UsageError("unknown log type");
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mkFlag(0, "option", {"name", "value"}, "set a Nix configuration option (overriding nix.conf)", 2,
 | 
				
			||||||
 | 
					        [](Strings ss) {
 | 
				
			||||||
 | 
					            auto name = ss.front(); ss.pop_front();
 | 
				
			||||||
 | 
					            auto value = ss.front();
 | 
				
			||||||
 | 
					            settings.set(name, value);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/libmain/common-args.hh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/libmain/common-args.hh
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,22 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "args.hh"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace nix {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct MixCommonArgs : virtual Args
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    MixCommonArgs(const string & programName);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct MixDryRun : virtual Args
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    bool dryRun;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    MixDryRun()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        mkFlag(0, "dry-run", "show what this command would do without doing it", &dryRun);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,8 @@
 | 
				
			||||||
#include "config.h"
 | 
					#include "config.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "shared.hh"
 | 
					#include "common-args.hh"
 | 
				
			||||||
#include "globals.hh"
 | 
					#include "globals.hh"
 | 
				
			||||||
 | 
					#include "shared.hh"
 | 
				
			||||||
#include "store-api.hh"
 | 
					#include "store-api.hh"
 | 
				
			||||||
#include "util.hh"
 | 
					#include "util.hh"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -84,16 +85,6 @@ void printMissing(ref<Store> store, const PathSet & willBuild,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void setLogType(string lt)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    if (lt == "pretty") logType = ltPretty;
 | 
					 | 
				
			||||||
    else if (lt == "escapes") logType = ltEscapes;
 | 
					 | 
				
			||||||
    else if (lt == "flat") logType = ltFlat;
 | 
					 | 
				
			||||||
    else if (lt == "systemd") logType = ltSystemd;
 | 
					 | 
				
			||||||
    else throw UsageError("unknown log type");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
string getArg(const string & opt,
 | 
					string getArg(const string & opt,
 | 
				
			||||||
    Strings::iterator & i, const Strings::iterator & end)
 | 
					    Strings::iterator & i, const Strings::iterator & end)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					@ -164,77 +155,80 @@ void initNix()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct LegacyArgs : public MixCommonArgs
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    std::function<bool(Strings::iterator & arg, const Strings::iterator & end)> parseArg;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LegacyArgs(const std::string & programName,
 | 
				
			||||||
 | 
					        std::function<bool(Strings::iterator & arg, const Strings::iterator & end)> parseArg)
 | 
				
			||||||
 | 
					        : MixCommonArgs(programName), parseArg(parseArg)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        mkFlag('Q', "no-build-output", "do not show build output",
 | 
				
			||||||
 | 
					            &settings.buildVerbosity, lvlVomit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mkFlag(0, "print-build-trace", "emit special build trace message",
 | 
				
			||||||
 | 
					            &settings.printBuildTrace);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mkFlag('K', "keep-failed", "keep temporary directories of failed builds",
 | 
				
			||||||
 | 
					            &settings.keepFailed);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mkFlag('k', "keep-going", "keep going after a build fails",
 | 
				
			||||||
 | 
					            &settings.keepGoing);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mkFlag(0, "fallback", "build from source if substitution fails", []() {
 | 
				
			||||||
 | 
					            settings.set("build-fallback", "true");
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        auto intSettingAlias = [&](char shortName, const std::string & longName,
 | 
				
			||||||
 | 
					            const std::string & description, const std::string & dest) {
 | 
				
			||||||
 | 
					            mkFlag<unsigned int>(shortName, longName, description, [=](unsigned int n) {
 | 
				
			||||||
 | 
					                settings.set(dest, std::to_string(n));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        intSettingAlias('j', "max-jobs", "maximum number of parallel builds", "build-max-jobs");
 | 
				
			||||||
 | 
					        intSettingAlias(0, "cores", "maximum number of CPU cores to use inside a build", "build-cores");
 | 
				
			||||||
 | 
					        intSettingAlias(0, "max-silent-time", "number of seconds of silence before a build is killed", "build-max-silent-time");
 | 
				
			||||||
 | 
					        intSettingAlias(0, "timeout", "number of seconds before a build is killed", "build-timeout");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mkFlag(0, "readonly-mode", "do not write to the Nix store",
 | 
				
			||||||
 | 
					            &settings.readOnlyMode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mkFlag(0, "no-build-hook", "disable use of the build hook mechanism",
 | 
				
			||||||
 | 
					            &settings.useBuildHook, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mkFlag(0, "show-trace", "show Nix expression stack trace in evaluation errors",
 | 
				
			||||||
 | 
					            &settings.showTrace);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mkFlag(0, "no-gc-warning", "disable warning about not using ‘--add-root’",
 | 
				
			||||||
 | 
					            &gcWarning, false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool processFlag(Strings::iterator & pos, Strings::iterator end) override
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (MixCommonArgs::processFlag(pos, end)) return true;
 | 
				
			||||||
 | 
					        bool res = parseArg(pos, end);
 | 
				
			||||||
 | 
					        if (res) ++pos;
 | 
				
			||||||
 | 
					        return res;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool processArgs(const Strings & args, bool finish) override
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (args.empty()) return true;
 | 
				
			||||||
 | 
					        assert(args.size() == 1);
 | 
				
			||||||
 | 
					        Strings ss(args);
 | 
				
			||||||
 | 
					        auto pos = ss.begin();
 | 
				
			||||||
 | 
					        if (!parseArg(pos, ss.end()))
 | 
				
			||||||
 | 
					            throw UsageError(format("unexpected argument ‘%1%’") % args.front());
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void parseCmdLine(int argc, char * * argv,
 | 
					void parseCmdLine(int argc, char * * argv,
 | 
				
			||||||
    std::function<bool(Strings::iterator & arg, const Strings::iterator & end)> parseArg)
 | 
					    std::function<bool(Strings::iterator & arg, const Strings::iterator & end)> parseArg)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /* Put the arguments in a vector. */
 | 
					    LegacyArgs(baseNameOf(argv[0]), parseArg).parseCmdline(argvToStrings(argc, argv));
 | 
				
			||||||
    Strings args;
 | 
					 | 
				
			||||||
    argc--; argv++;
 | 
					 | 
				
			||||||
    while (argc--) args.push_back(*argv++);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /* Process default options. */
 | 
					 | 
				
			||||||
    for (Strings::iterator i = args.begin(); i != args.end(); ++i) {
 | 
					 | 
				
			||||||
        string arg = *i;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /* Expand compound dash options (i.e., `-qlf' -> `-q -l -f'). */
 | 
					 | 
				
			||||||
        if (arg.length() > 2 && arg[0] == '-' && arg[1] != '-' && isalpha(arg[1])) {
 | 
					 | 
				
			||||||
            *i = (string) "-" + arg[1];
 | 
					 | 
				
			||||||
            auto next = i; ++next;
 | 
					 | 
				
			||||||
            for (unsigned int j = 2; j < arg.length(); j++)
 | 
					 | 
				
			||||||
                if (isalpha(arg[j]))
 | 
					 | 
				
			||||||
                    args.insert(next, (string) "-" + arg[j]);
 | 
					 | 
				
			||||||
                else {
 | 
					 | 
				
			||||||
                    args.insert(next, string(arg, j));
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            arg = *i;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (arg == "--verbose" || arg == "-v") verbosity = (Verbosity) (verbosity + 1);
 | 
					 | 
				
			||||||
        else if (arg == "--quiet") verbosity = verbosity > lvlError ? (Verbosity) (verbosity - 1) : lvlError;
 | 
					 | 
				
			||||||
        else if (arg == "--log-type") {
 | 
					 | 
				
			||||||
            string s = getArg(arg, i, args.end());
 | 
					 | 
				
			||||||
            setLogType(s);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else if (arg == "--no-build-output" || arg == "-Q")
 | 
					 | 
				
			||||||
            settings.buildVerbosity = lvlVomit;
 | 
					 | 
				
			||||||
        else if (arg == "--print-build-trace")
 | 
					 | 
				
			||||||
            settings.printBuildTrace = true;
 | 
					 | 
				
			||||||
        else if (arg == "--keep-failed" || arg == "-K")
 | 
					 | 
				
			||||||
            settings.keepFailed = true;
 | 
					 | 
				
			||||||
        else if (arg == "--keep-going" || arg == "-k")
 | 
					 | 
				
			||||||
            settings.keepGoing = true;
 | 
					 | 
				
			||||||
        else if (arg == "--fallback")
 | 
					 | 
				
			||||||
            settings.set("build-fallback", "true");
 | 
					 | 
				
			||||||
        else if (arg == "--max-jobs" || arg == "-j")
 | 
					 | 
				
			||||||
            settings.set("build-max-jobs", getArg(arg, i, args.end()));
 | 
					 | 
				
			||||||
        else if (arg == "--cores")
 | 
					 | 
				
			||||||
            settings.set("build-cores", getArg(arg, i, args.end()));
 | 
					 | 
				
			||||||
        else if (arg == "--readonly-mode")
 | 
					 | 
				
			||||||
            settings.readOnlyMode = true;
 | 
					 | 
				
			||||||
        else if (arg == "--max-silent-time")
 | 
					 | 
				
			||||||
            settings.set("build-max-silent-time", getArg(arg, i, args.end()));
 | 
					 | 
				
			||||||
        else if (arg == "--timeout")
 | 
					 | 
				
			||||||
            settings.set("build-timeout", getArg(arg, i, args.end()));
 | 
					 | 
				
			||||||
        else if (arg == "--no-build-hook")
 | 
					 | 
				
			||||||
            settings.useBuildHook = false;
 | 
					 | 
				
			||||||
        else if (arg == "--show-trace")
 | 
					 | 
				
			||||||
            settings.showTrace = true;
 | 
					 | 
				
			||||||
        else if (arg == "--no-gc-warning")
 | 
					 | 
				
			||||||
            gcWarning = false;
 | 
					 | 
				
			||||||
        else if (arg == "--option") {
 | 
					 | 
				
			||||||
            ++i; if (i == args.end()) throw UsageError("‘--option’ requires two arguments");
 | 
					 | 
				
			||||||
            string name = *i;
 | 
					 | 
				
			||||||
            ++i; if (i == args.end()) throw UsageError("‘--option’ requires two arguments");
 | 
					 | 
				
			||||||
            string value = *i;
 | 
					 | 
				
			||||||
            settings.set(name, value);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else {
 | 
					 | 
				
			||||||
            if (!parseArg(i, args.end()))
 | 
					 | 
				
			||||||
                throw UsageError(format("unrecognised option ‘%1%’") % *i);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    settings.update();
 | 
					    settings.update();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "util.hh"
 | 
					#include "util.hh"
 | 
				
			||||||
 | 
					#include "args.hh"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <signal.h>
 | 
					#include <signal.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,8 +10,6 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace nix {
 | 
					namespace nix {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MakeError(UsageError, nix::Error);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Exit : public std::exception
 | 
					class Exit : public std::exception
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										179
									
								
								src/libutil/args.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								src/libutil/args.cc
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,179 @@
 | 
				
			||||||
 | 
					#include "args.hh"
 | 
				
			||||||
 | 
					#include "hash.hh"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace nix {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Args::parseCmdline(const Strings & _cmdline)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    Strings pendingArgs;
 | 
				
			||||||
 | 
					    bool dashDash = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Strings cmdline(_cmdline);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (auto pos = cmdline.begin(); pos != cmdline.end(); ) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        auto arg = *pos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* Expand compound dash options (i.e., `-qlf' -> `-q -l -f',
 | 
				
			||||||
 | 
					           `-j3` -> `-j 3`). */
 | 
				
			||||||
 | 
					        if (!dashDash && arg.length() > 2 && arg[0] == '-' && arg[1] != '-' && isalpha(arg[1])) {
 | 
				
			||||||
 | 
					            *pos = (string) "-" + arg[1];
 | 
				
			||||||
 | 
					            auto next = pos; ++next;
 | 
				
			||||||
 | 
					            for (unsigned int j = 2; j < arg.length(); j++)
 | 
				
			||||||
 | 
					                if (isalpha(arg[j]))
 | 
				
			||||||
 | 
					                    cmdline.insert(next, (string) "-" + arg[j]);
 | 
				
			||||||
 | 
					                else {
 | 
				
			||||||
 | 
					                    cmdline.insert(next, string(arg, j));
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            arg = *pos;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!dashDash && arg == "--") {
 | 
				
			||||||
 | 
					            dashDash = true;
 | 
				
			||||||
 | 
					            ++pos;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (!dashDash && std::string(arg, 0, 1) == "-") {
 | 
				
			||||||
 | 
					            if (!processFlag(pos, cmdline.end()))
 | 
				
			||||||
 | 
					                throw UsageError(format("unrecognised flag ‘%1%’") % arg);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            pendingArgs.push_back(*pos++);
 | 
				
			||||||
 | 
					            if (processArgs(pendingArgs, false))
 | 
				
			||||||
 | 
					                pendingArgs.clear();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    processArgs(pendingArgs, true);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Args::printHelp(const string & programName, std::ostream & out)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    std::cout << "Usage: " << programName << " <FLAGS>...";
 | 
				
			||||||
 | 
					    for (auto & exp : expectedArgs) {
 | 
				
			||||||
 | 
					        std::cout << renderLabels({exp.label});
 | 
				
			||||||
 | 
					        // FIXME: handle arity > 1
 | 
				
			||||||
 | 
					        if (exp.arity == 0) std::cout << "...";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    std::cout << "\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto s = description();
 | 
				
			||||||
 | 
					    if (s != "")
 | 
				
			||||||
 | 
					        std::cout << "\nSummary: " << s << ".\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (longFlags.size()) {
 | 
				
			||||||
 | 
					        std::cout << "\n";
 | 
				
			||||||
 | 
					        std::cout << "Flags:\n";
 | 
				
			||||||
 | 
					        printFlags(out);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Args::printFlags(std::ostream & out)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    Table2 table;
 | 
				
			||||||
 | 
					    for (auto & flags : longFlags)
 | 
				
			||||||
 | 
					        table.push_back(std::make_pair(
 | 
				
			||||||
 | 
					                "--" + flags.first + renderLabels(flags.second.labels),
 | 
				
			||||||
 | 
					                flags.second.description));
 | 
				
			||||||
 | 
					    printTable(out, table);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    assert(pos != end);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto process = [&](const std::string & name, const Flag & flag) -> bool {
 | 
				
			||||||
 | 
					        ++pos;
 | 
				
			||||||
 | 
					        Strings args;
 | 
				
			||||||
 | 
					        for (size_t n = 0 ; n < flag.arity; ++n) {
 | 
				
			||||||
 | 
					            if (pos == end)
 | 
				
			||||||
 | 
					                throw UsageError(format("flag ‘%1%’ requires %2% argument(s)")
 | 
				
			||||||
 | 
					                    % name % flag.arity);
 | 
				
			||||||
 | 
					            args.push_back(*pos++);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        flag.handler(args);
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (string(*pos, 0, 2) == "--") {
 | 
				
			||||||
 | 
					        auto i = longFlags.find(string(*pos, 2));
 | 
				
			||||||
 | 
					        if (i == longFlags.end()) return false;
 | 
				
			||||||
 | 
					        return process("--" + i->first, i->second);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (string(*pos, 0, 1) == "-" && pos->size() == 2) {
 | 
				
			||||||
 | 
					        auto c = (*pos)[1];
 | 
				
			||||||
 | 
					        auto i = shortFlags.find(c);
 | 
				
			||||||
 | 
					        if (i == shortFlags.end()) return false;
 | 
				
			||||||
 | 
					        return process(std::string("-") + c, i->second);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Args::processArgs(const Strings & args, bool finish)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (expectedArgs.empty()) {
 | 
				
			||||||
 | 
					        if (!args.empty())
 | 
				
			||||||
 | 
					            throw UsageError(format("unexpected argument ‘%1%’") % args.front());
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto & exp = expectedArgs.front();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool res = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ((exp.arity == 0 && finish) ||
 | 
				
			||||||
 | 
					        (exp.arity > 0 && args.size() == exp.arity))
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        exp.handler(args);
 | 
				
			||||||
 | 
					        expectedArgs.pop_front();
 | 
				
			||||||
 | 
					        res = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (finish && !expectedArgs.empty())
 | 
				
			||||||
 | 
					        throw UsageError("more arguments are required");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return res;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Args::mkHashTypeFlag(const std::string & name, HashType * ht)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    mkFlag1(0, name, "TYPE", "hash algorithm (‘md5’, ‘sha1’, ‘sha256’, or ‘sha512’)", [=](std::string s) {
 | 
				
			||||||
 | 
					        *ht = parseHashType(s);
 | 
				
			||||||
 | 
					        if (*ht == htUnknown)
 | 
				
			||||||
 | 
					            throw UsageError(format("unknown hash type ‘%1%’") % s);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Strings argvToStrings(int argc, char * * argv)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    Strings args;
 | 
				
			||||||
 | 
					    argc--; argv++;
 | 
				
			||||||
 | 
					    while (argc--) args.push_back(*argv++);
 | 
				
			||||||
 | 
					    return args;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string renderLabels(const Strings & labels)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    std::string res;
 | 
				
			||||||
 | 
					    for (auto label : labels) {
 | 
				
			||||||
 | 
					        for (auto & c : label) c = std::toupper(c);
 | 
				
			||||||
 | 
					        res += " <" + label + ">";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return res;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void printTable(std::ostream & out, const Table2 & table)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    size_t max = 0;
 | 
				
			||||||
 | 
					    for (auto & row : table)
 | 
				
			||||||
 | 
					        max = std::max(max, row.first.size());
 | 
				
			||||||
 | 
					    for (auto & row : table) {
 | 
				
			||||||
 | 
					        out << "  " << row.first
 | 
				
			||||||
 | 
					            << std::string(max - row.first.size() + 2, ' ')
 | 
				
			||||||
 | 
					            << row.second << "\n";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										160
									
								
								src/libutil/args.hh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								src/libutil/args.hh
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,160 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <map>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "util.hh"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace nix {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MakeError(UsageError, nix::Error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum HashType : char;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Args
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Parse the command line, throwing a UsageError if something goes
 | 
				
			||||||
 | 
					       wrong. */
 | 
				
			||||||
 | 
					    void parseCmdline(const Strings & cmdline);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    virtual void printHelp(const string & programName, std::ostream & out);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    virtual std::string description() { return ""; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Flags. */
 | 
				
			||||||
 | 
					    struct Flag
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::string description;
 | 
				
			||||||
 | 
					        Strings labels;
 | 
				
			||||||
 | 
					        size_t arity;
 | 
				
			||||||
 | 
					        std::function<void(Strings)> handler;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::map<std::string, Flag> longFlags;
 | 
				
			||||||
 | 
					    std::map<char, Flag> shortFlags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    virtual bool processFlag(Strings::iterator & pos, Strings::iterator end);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void printFlags(std::ostream & out);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Positional arguments. */
 | 
				
			||||||
 | 
					    struct ExpectedArg
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::string label;
 | 
				
			||||||
 | 
					        size_t arity; // 0 = any
 | 
				
			||||||
 | 
					        std::function<void(Strings)> handler;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::list<ExpectedArg> expectedArgs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    virtual bool processArgs(const Strings & args, bool finish);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Helper functions for constructing flags / positional
 | 
				
			||||||
 | 
					       arguments. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void mkFlag(char shortName, const std::string & longName,
 | 
				
			||||||
 | 
					        const Strings & labels, const std::string & description,
 | 
				
			||||||
 | 
					        size_t arity, std::function<void(Strings)> handler)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        auto flag = Flag{description, labels, arity, handler};
 | 
				
			||||||
 | 
					        if (shortName) shortFlags[shortName] = flag;
 | 
				
			||||||
 | 
					        longFlags[longName] = flag;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void mkFlag(char shortName, const std::string & longName,
 | 
				
			||||||
 | 
					        const std::string & description, std::function<void()> fun)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        mkFlag(shortName, longName, {}, description, 0, std::bind(fun));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void mkFlag1(char shortName, const std::string & longName,
 | 
				
			||||||
 | 
					        const std::string & label, const std::string & description,
 | 
				
			||||||
 | 
					        std::function<void(std::string)> fun)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        mkFlag(shortName, longName, {label}, description, 1, [=](Strings ss) {
 | 
				
			||||||
 | 
					            fun(ss.front());
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void mkFlag(char shortName, const std::string & name,
 | 
				
			||||||
 | 
					        const std::string & description, bool * dest)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        mkFlag(0, name, description, dest, true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void mkFlag(char shortName, const std::string & longName,
 | 
				
			||||||
 | 
					        const std::string & label, const std::string & description,
 | 
				
			||||||
 | 
					        string * dest)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        mkFlag1(shortName, longName, label, description, [=](std::string s) {
 | 
				
			||||||
 | 
					            *dest = s;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void mkHashTypeFlag(const std::string & name, HashType * ht);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    template<class T>
 | 
				
			||||||
 | 
					    void mkFlag(char shortName, const std::string & longName, const std::string & description,
 | 
				
			||||||
 | 
					        T * dest, const T & value)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        mkFlag(shortName, longName, {}, description, 0, [=](Strings ss) {
 | 
				
			||||||
 | 
					            *dest = value;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    template<class I>
 | 
				
			||||||
 | 
					    void mkIntFlag(char shortName, const std::string & longName,
 | 
				
			||||||
 | 
					        const std::string & description, I * dest)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        mkFlag<I>(shortName, longName, description, [=](I n) {
 | 
				
			||||||
 | 
					            *dest = n;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    template<class I>
 | 
				
			||||||
 | 
					    void mkFlag(char shortName, const std::string & longName,
 | 
				
			||||||
 | 
					        const std::string & description, std::function<void(I)> fun)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        mkFlag(shortName, longName, {"N"}, description, 1, [=](Strings ss) {
 | 
				
			||||||
 | 
					            I n;
 | 
				
			||||||
 | 
					            if (!string2Int(ss.front(), n))
 | 
				
			||||||
 | 
					                throw UsageError(format("flag ‘--%1%’ requires a integer argument") % longName);
 | 
				
			||||||
 | 
					            fun(n);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Expect a string argument. */
 | 
				
			||||||
 | 
					    void expectArg(const std::string & label, string * dest)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        expectedArgs.push_back(ExpectedArg{label, 1, [=](Strings ss) {
 | 
				
			||||||
 | 
					            *dest = ss.front();
 | 
				
			||||||
 | 
					        }});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Expect 0 or more arguments. */
 | 
				
			||||||
 | 
					    void expectArgs(const std::string & label, Strings * dest)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        expectedArgs.push_back(ExpectedArg{label, 0, [=](Strings ss) {
 | 
				
			||||||
 | 
					            *dest = ss;
 | 
				
			||||||
 | 
					        }});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Strings argvToStrings(int argc, char * * argv);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Helper function for rendering argument labels. */
 | 
				
			||||||
 | 
					std::string renderLabels(const Strings & labels);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Helper function for printing 2-column tables. */
 | 
				
			||||||
 | 
					typedef std::vector<std::pair<std::string, std::string>> Table2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void printTable(std::ostream & out, const Table2 & table);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue