diff options
author | Eelco Dolstra <edolstra@gmail.com> | 2020-07-16 14:07:32 +0200 |
---|---|---|
committer | Eelco Dolstra <edolstra@gmail.com> | 2020-07-16 14:07:32 +0200 |
commit | 3f264916dbfe346a71fa4182c9037332ac54f9d9 (patch) | |
tree | 94819bfb342195743f63321ee22f516df8d1fd22 /src/libutil | |
parent | 36a124260361ba8dfa43bf43a067dcc48064c93f (diff) | |
parent | 2d6d53bc87ef7468ad73431cf76123316f4c82bf (diff) |
Merge remote-tracking branch 'origin/flakes'
Diffstat (limited to 'src/libutil')
-rw-r--r-- | src/libutil/args.cc | 139 | ||||
-rw-r--r-- | src/libutil/args.hh | 131 | ||||
-rw-r--r-- | src/libutil/error.hh | 1 | ||||
-rw-r--r-- | src/libutil/hash.cc | 3 | ||||
-rw-r--r-- | src/libutil/hash.hh | 3 | ||||
-rw-r--r-- | src/libutil/util.cc | 30 | ||||
-rw-r--r-- | src/libutil/util.hh | 10 |
7 files changed, 232 insertions, 85 deletions
diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 1f0e4f803..986c5d1cd 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -1,6 +1,8 @@ #include "args.hh" #include "hash.hh" +#include <glob.h> + namespace nix { void Args::addFlag(Flag && flag_) @@ -13,6 +15,20 @@ void Args::addFlag(Flag && flag_) if (flag->shortName) shortFlags[flag->shortName] = flag; } +bool pathCompletions = false; +std::shared_ptr<std::set<std::string>> completions; + +std::string completionMarker = "___COMPLETE___"; + +std::optional<std::string> needsCompletion(std::string_view s) +{ + if (!completions) return {}; + auto i = s.find(completionMarker); + if (i != std::string::npos) + return std::string(s.begin(), i); + return {}; +} + void Args::parseCmdline(const Strings & _cmdline) { Strings pendingArgs; @@ -20,6 +36,14 @@ void Args::parseCmdline(const Strings & _cmdline) Strings cmdline(_cmdline); + if (auto s = getEnv("NIX_GET_COMPLETIONS")) { + size_t n = std::stoi(*s); + assert(n > 0 && n <= cmdline.size()); + *std::next(cmdline.begin(), n - 1) += completionMarker; + completions = std::make_shared<decltype(completions)::element_type>(); + verbosity = lvlError; + } + for (auto pos = cmdline.begin(); pos != cmdline.end(); ) { auto arg = *pos; @@ -63,7 +87,7 @@ void Args::printHelp(const string & programName, std::ostream & out) for (auto & exp : expectedArgs) { std::cout << renderLabels({exp.label}); // FIXME: handle arity > 1 - if (exp.arity == 0) std::cout << "..."; + if (exp.handler.arity == ArityAny) std::cout << "..."; if (exp.optional) std::cout << "?"; } std::cout << "\n"; @@ -99,18 +123,32 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) auto process = [&](const std::string & name, const Flag & flag) -> bool { ++pos; std::vector<std::string> args; + bool anyCompleted = false; for (size_t n = 0 ; n < flag.handler.arity; ++n) { if (pos == end) { if (flag.handler.arity == ArityAny) break; throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); } + if (flag.completer) + if (auto prefix = needsCompletion(*pos)) { + anyCompleted = true; + flag.completer(n, *prefix); + } args.push_back(*pos++); } - flag.handler.fun(std::move(args)); + if (!anyCompleted) + flag.handler.fun(std::move(args)); return true; }; if (string(*pos, 0, 2) == "--") { + if (auto prefix = needsCompletion(*pos)) { + for (auto & [name, flag] : longFlags) { + if (!hiddenCategories.count(flag->category) + && hasPrefix(name, std::string(*prefix, 2))) + completions->insert("--" + name); + } + } auto i = longFlags.find(string(*pos, 2)); if (i == longFlags.end()) return false; return process("--" + i->first, *i->second); @@ -123,6 +161,14 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) return process(std::string("-") + c, *i->second); } + if (auto prefix = needsCompletion(*pos)) { + if (prefix == "-") { + completions->insert("--"); + for (auto & [flag, _] : shortFlags) + completions->insert(std::string("-") + flag); + } + } + return false; } @@ -138,12 +184,17 @@ bool Args::processArgs(const Strings & args, bool finish) bool res = false; - if ((exp.arity == 0 && finish) || - (exp.arity > 0 && args.size() == exp.arity)) + if ((exp.handler.arity == ArityAny && finish) || + (exp.handler.arity != ArityAny && args.size() == exp.handler.arity)) { std::vector<std::string> ss; - for (auto & s : args) ss.push_back(s); - exp.handler(std::move(ss)); + for (const auto &[n, s] : enumerate(args)) { + ss.push_back(s); + if (exp.completer) + if (auto prefix = needsCompletion(s)) + exp.completer(n, *prefix); + } + exp.handler.fun(ss); expectedArgs.pop_front(); res = true; } @@ -154,6 +205,13 @@ bool Args::processArgs(const Strings & args, bool finish) return res; } +static void hashTypeCompleter(size_t index, std::string_view prefix) +{ + for (auto & type : hashTypes) + if (hasPrefix(type, prefix)) + completions->insert(type); +} + Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) { return Flag { @@ -162,7 +220,8 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) .labels = {"hash-algo"}, .handler = {[ht](std::string s) { *ht = parseHashType(s); - }} + }}, + .completer = hashTypeCompleter }; } @@ -174,10 +233,42 @@ Args::Flag Args::Flag::mkHashTypeOptFlag(std::string && longName, std::optional< .labels = {"hash-algo"}, .handler = {[oht](std::string s) { *oht = std::optional<HashType> { parseHashType(s) }; - }} + }}, + .completer = hashTypeCompleter }; } +static void completePath(std::string_view prefix, bool onlyDirs) +{ + pathCompletions = true; + glob_t globbuf; + int flags = GLOB_NOESCAPE | GLOB_TILDE; + #ifdef GLOB_ONLYDIR + if (onlyDirs) + flags |= GLOB_ONLYDIR; + #endif + if (glob((std::string(prefix) + "*").c_str(), flags, nullptr, &globbuf) == 0) { + for (size_t i = 0; i < globbuf.gl_pathc; ++i) { + if (onlyDirs) { + auto st = lstat(globbuf.gl_pathv[i]); + if (!S_ISDIR(st.st_mode)) continue; + } + completions->insert(globbuf.gl_pathv[i]); + } + globfree(&globbuf); + } +} + +void completePath(size_t, std::string_view prefix) +{ + completePath(prefix, false); +} + +void completeDir(size_t, std::string_view prefix) +{ + completePath(prefix, true); +} + Strings argvToStrings(int argc, char * * argv) { Strings args; @@ -225,18 +316,26 @@ void Command::printHelp(const string & programName, std::ostream & out) MultiCommand::MultiCommand(const Commands & commands) : commands(commands) { - expectedArgs.push_back(ExpectedArg{"command", 1, true, [=](std::vector<std::string> ss) { - assert(!command); - auto cmd = ss[0]; - if (auto alias = get(deprecatedAliases, cmd)) { - warn("'%s' is a deprecated alias for '%s'", cmd, *alias); - cmd = *alias; - } - auto i = commands.find(cmd); - if (i == commands.end()) - throw UsageError("'%s' is not a recognised command", cmd); - command = {cmd, i->second()}; - }}); + expectArgs({ + .label = "command", + .optional = true, + .handler = {[=](std::string s) { + assert(!command); + if (auto alias = get(deprecatedAliases, s)) { + warn("'%s' is a deprecated alias for '%s'", s, *alias); + s = *alias; + } + if (auto prefix = needsCompletion(s)) { + for (auto & [name, command] : commands) + if (hasPrefix(name, *prefix)) + completions->insert(name); + } + auto i = commands.find(s); + if (i == commands.end()) + throw UsageError("'%s' is not a recognised command", s); + command = {s, i->second()}; + }} + }); categories[Command::catDefault] = "Available commands"; } diff --git a/src/libutil/args.hh b/src/libutil/args.hh index 433d26bba..97a517344 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -8,8 +8,6 @@ namespace nix { -MakeError(UsageError, Error); - enum HashType : char; class Args @@ -28,61 +26,67 @@ protected: static const size_t ArityAny = std::numeric_limits<size_t>::max(); + struct Handler + { + std::function<void(std::vector<std::string>)> fun; + size_t arity; + + Handler() {} + + Handler(std::function<void(std::vector<std::string>)> && fun) + : fun(std::move(fun)) + , arity(ArityAny) + { } + + Handler(std::function<void()> && handler) + : fun([handler{std::move(handler)}](std::vector<std::string>) { handler(); }) + , arity(0) + { } + + Handler(std::function<void(std::string)> && handler) + : fun([handler{std::move(handler)}](std::vector<std::string> ss) { + handler(std::move(ss[0])); + }) + , arity(1) + { } + + Handler(std::function<void(std::string, std::string)> && handler) + : fun([handler{std::move(handler)}](std::vector<std::string> ss) { + handler(std::move(ss[0]), std::move(ss[1])); + }) + , arity(2) + { } + + Handler(std::vector<std::string> * dest) + : fun([=](std::vector<std::string> ss) { *dest = ss; }) + , arity(ArityAny) + { } + + template<class T> + Handler(T * dest) + : fun([=](std::vector<std::string> ss) { *dest = ss[0]; }) + , arity(1) + { } + + template<class T> + Handler(T * dest, const T & val) + : fun([=](std::vector<std::string> ss) { *dest = val; }) + , arity(0) + { } + }; + /* Flags. */ struct Flag { typedef std::shared_ptr<Flag> ptr; - struct Handler - { - std::function<void(std::vector<std::string>)> fun; - size_t arity; - - Handler() {} - - Handler(std::function<void(std::vector<std::string>)> && fun) - : fun(std::move(fun)) - , arity(ArityAny) - { } - - Handler(std::function<void()> && handler) - : fun([handler{std::move(handler)}](std::vector<std::string>) { handler(); }) - , arity(0) - { } - - Handler(std::function<void(std::string)> && handler) - : fun([handler{std::move(handler)}](std::vector<std::string> ss) { - handler(std::move(ss[0])); - }) - , arity(1) - { } - - Handler(std::function<void(std::string, std::string)> && handler) - : fun([handler{std::move(handler)}](std::vector<std::string> ss) { - handler(std::move(ss[0]), std::move(ss[1])); - }) - , arity(2) - { } - - template<class T> - Handler(T * dest) - : fun([=](std::vector<std::string> ss) { *dest = ss[0]; }) - , arity(1) - { } - - template<class T> - Handler(T * dest, const T & val) - : fun([=](std::vector<std::string> ss) { *dest = val; }) - , arity(0) - { } - }; - std::string longName; char shortName = 0; std::string description; std::string category; Strings labels; Handler handler; + std::function<void(size_t, std::string_view)> completer; static Flag mkHashTypeFlag(std::string && longName, HashType * ht); static Flag mkHashTypeOptFlag(std::string && longName, std::optional<HashType> * oht); @@ -99,9 +103,9 @@ protected: struct ExpectedArg { std::string label; - size_t arity; // 0 = any - bool optional; - std::function<void(std::vector<std::string>)> handler; + bool optional = false; + Handler handler; + std::function<void(size_t, std::string_view)> completer; }; std::list<ExpectedArg> expectedArgs; @@ -175,20 +179,28 @@ public: }); } + void expectArgs(ExpectedArg && arg) + { + expectedArgs.emplace_back(std::move(arg)); + } + /* Expect a string argument. */ void expectArg(const std::string & label, string * dest, bool optional = false) { - expectedArgs.push_back(ExpectedArg{label, 1, optional, [=](std::vector<std::string> ss) { - *dest = ss[0]; - }}); + expectArgs({ + .label = label, + .optional = true, + .handler = {dest} + }); } /* Expect 0 or more arguments. */ void expectArgs(const std::string & label, std::vector<std::string> * dest) { - expectedArgs.push_back(ExpectedArg{label, 0, false, [=](std::vector<std::string> ss) { - *dest = std::move(ss); - }}); + expectArgs({ + .label = label, + .handler = {dest} + }); } friend class MultiCommand; @@ -259,4 +271,13 @@ typedef std::vector<std::pair<std::string, std::string>> Table2; void printTable(std::ostream & out, const Table2 & table); +extern std::shared_ptr<std::set<std::string>> completions; +extern bool pathCompletions; + +std::optional<std::string> needsCompletion(std::string_view s); + +void completePath(size_t, std::string_view prefix); + +void completeDir(size_t, std::string_view prefix); + } diff --git a/src/libutil/error.hh b/src/libutil/error.hh index f4b3f11bb..0daaf3be2 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -191,6 +191,7 @@ public: } MakeError(Error, BaseError); +MakeError(UsageError, Error); class SysError : public Error { diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index f0d7754d1..5578a618e 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -16,6 +16,9 @@ namespace nix { +std::set<std::string> hashTypes = { "md5", "sha1", "sha256", "sha512" }; + + void Hash::init() { assert(type); diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh index 23259dced..ad6093fca 100644 --- a/src/libutil/hash.hh +++ b/src/libutil/hash.hh @@ -18,6 +18,8 @@ const int sha1HashSize = 20; const int sha256HashSize = 32; const int sha512HashSize = 64; +extern std::set<std::string> hashTypes; + extern const string base32Chars; enum Base : int { Base64, Base32, Base16, SRI }; @@ -122,6 +124,7 @@ Hash compressHash(const Hash & hash, unsigned int newSize); /* Parse a string representing a hash type. */ HashType parseHashType(const string & s); + /* Will return nothing on parse error */ std::optional<HashType> parseHashTypeOpt(const string & s); diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 1268b146a..93798a765 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -23,6 +23,7 @@ #include <sys/types.h> #include <sys/socket.h> #include <sys/wait.h> +#include <sys/time.h> #include <sys/un.h> #include <unistd.h> @@ -79,7 +80,7 @@ void replaceEnv(std::map<std::string, std::string> newEnv) } -Path absPath(Path path, std::optional<Path> dir) +Path absPath(Path path, std::optional<Path> dir, bool resolveSymlinks) { if (path[0] != '/') { if (!dir) { @@ -100,7 +101,7 @@ Path absPath(Path path, std::optional<Path> dir) } path = *dir + "/" + path; } - return canonPath(path); + return canonPath(path, resolveSymlinks); } @@ -345,7 +346,6 @@ void writeFile(const Path & path, Source & source, mode_t mode) } } - string readLine(int fd) { string s; @@ -581,20 +581,31 @@ Paths createDirs(const Path & path) } -void createSymlink(const Path & target, const Path & link) +void createSymlink(const Path & target, const Path & link, + std::optional<time_t> mtime) { if (symlink(target.c_str(), link.c_str())) throw SysError("creating symlink from '%1%' to '%2%'", link, target); + if (mtime) { + struct timeval times[2]; + times[0].tv_sec = *mtime; + times[0].tv_usec = 0; + times[1].tv_sec = *mtime; + times[1].tv_usec = 0; + if (lutimes(link.c_str(), times)) + throw SysError("setting time of symlink '%s'", link); + } } -void replaceSymlink(const Path & target, const Path & link) +void replaceSymlink(const Path & target, const Path & link, + std::optional<time_t> mtime) { for (unsigned int n = 0; true; n++) { Path tmp = canonPath(fmt("%s/.%d_%s", dirOf(link), n, baseNameOf(link))); try { - createSymlink(target, tmp); + createSymlink(target, tmp, mtime); } catch (SysError & e) { if (e.errNo == EEXIST) continue; throw; @@ -1006,12 +1017,14 @@ std::vector<char *> stringsToCharPtrs(const Strings & ss) return res; } - +// Output = "standard out" output stream string runProgram(Path program, bool searchPath, const Strings & args, const std::optional<std::string> & input) { RunOptions opts(program, args); opts.searchPath = searchPath; + // This allows you to refer to a program with a pathname relative to the + // PATH variable. opts.input = input; auto res = runProgram(opts); @@ -1022,6 +1035,7 @@ string runProgram(Path program, bool searchPath, const Strings & args, return res.second; } +// Output = error code + "standard out" output stream std::pair<int, std::string> runProgram(const RunOptions & options_) { RunOptions options(options_); @@ -1094,6 +1108,8 @@ void runProgram2(const RunOptions & options) if (options.searchPath) execvp(options.program.c_str(), stringsToCharPtrs(args_).data()); + // This allows you to refer to a program with a pathname relative + // to the PATH variable. else execv(options.program.c_str(), stringsToCharPtrs(args_).data()); diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 3641daaec..42130f6dc 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -49,7 +49,9 @@ void clearEnv(); /* Return an absolutized path, resolving paths relative to the specified directory, or the current directory otherwise. The path is also canonicalised. */ -Path absPath(Path path, std::optional<Path> dir = {}); +Path absPath(Path path, + std::optional<Path> dir = {}, + bool resolveSymlinks = false); /* Canonicalise a path by removing all `.' or `..' components and double or trailing slashes. Optionally resolves all symlink @@ -147,10 +149,12 @@ Path getDataDir(); Paths createDirs(const Path & path); /* Create a symlink. */ -void createSymlink(const Path & target, const Path & link); +void createSymlink(const Path & target, const Path & link, + std::optional<time_t> mtime = {}); /* Atomically create or replace a symlink. */ -void replaceSymlink(const Path & target, const Path & link); +void replaceSymlink(const Path & target, const Path & link, + std::optional<time_t> mtime = {}); /* Wrappers arount read()/write() that read/write exactly the |