From f70434b1fbbdb0e188718f0c55a8156a7aa08744 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 22 Nov 2018 16:03:31 +0100 Subject: Move Command and MultiCommand to libutil --- src/libutil/args.cc | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/libutil/args.hh | 41 +++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) (limited to 'src/libutil') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 7af2a1bf7..2837dacc9 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -200,4 +200,73 @@ void printTable(std::ostream & out, const Table2 & table) } } +void Command::printHelp(const string & programName, std::ostream & out) +{ + Args::printHelp(programName, out); + + auto exs = examples(); + if (!exs.empty()) { + out << "\n"; + out << "Examples:\n"; + for (auto & ex : exs) + out << "\n" + << " " << ex.description << "\n" // FIXME: wrap + << " $ " << ex.command << "\n"; + } +} + +MultiCommand::MultiCommand(const std::vector> & _commands) +{ + for (auto & command : _commands) + commands.emplace(command->name(), command); + + expectedArgs.push_back(ExpectedArg{"command", 1, true, [=](std::vector ss) { + assert(!command); + auto i = commands.find(ss[0]); + if (i == commands.end()) + throw UsageError("'%s' is not a recognised command", ss[0]); + command = i->second; + }}); +} + +void MultiCommand::printHelp(const string & programName, std::ostream & out) +{ + if (command) { + command->printHelp(programName + " " + command->name(), out); + return; + } + + out << "Usage: " << programName << " ... ...\n"; + + out << "\n"; + out << "Common flags:\n"; + printFlags(out); + + out << "\n"; + out << "Available commands:\n"; + + Table2 table; + for (auto & command : commands) { + auto descr = command.second->description(); + if (!descr.empty()) + table.push_back(std::make_pair(command.second->name(), descr)); + } + printTable(out, table); +} + +bool MultiCommand::processFlag(Strings::iterator & pos, Strings::iterator end) +{ + if (Args::processFlag(pos, end)) return true; + if (command && command->processFlag(pos, end)) return true; + return false; +} + +bool MultiCommand::processArgs(const Strings & args, bool finish) +{ + if (command) + return command->processArgs(args, finish); + else + return Args::processArgs(args, finish); +} + } diff --git a/src/libutil/args.hh b/src/libutil/args.hh index ad5fcca39..bf69bf4b6 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -188,6 +188,47 @@ public: friend class MultiCommand; }; +/* A command is an argument parser that can be executed by calling its + run() method. */ +struct Command : virtual Args +{ + virtual std::string name() = 0; + virtual void prepare() { }; + virtual void run() = 0; + + struct Example + { + std::string description; + std::string command; + }; + + typedef std::list Examples; + + virtual Examples examples() { return Examples(); } + + void printHelp(const string & programName, std::ostream & out) override; +}; + +typedef std::map> Commands; + +/* An argument parser that supports multiple subcommands, + i.e. ‘ ’. */ +class MultiCommand : virtual Args +{ +public: + Commands commands; + + std::shared_ptr command; + + MultiCommand(const std::vector> & commands); + + void printHelp(const string & programName, std::ostream & out) override; + + bool processFlag(Strings::iterator & pos, Strings::iterator end) override; + + bool processArgs(const Strings & args, bool finish) override; +}; + Strings argvToStrings(int argc, char * * argv); /* Helper function for rendering argument labels. */ -- cgit v1.2.3 From c8a0b9d5cbfe6619f8b38118f5b1d1875d1c5309 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 12 Feb 2019 13:43:32 +0100 Subject: experimental/optional -> optional --- src/libutil/lru-cache.hh | 4 ++-- src/libutil/serialise.cc | 2 +- src/libutil/util.cc | 2 +- src/libutil/util.hh | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/lru-cache.hh b/src/libutil/lru-cache.hh index 9b8290e63..8b83f842c 100644 --- a/src/libutil/lru-cache.hh +++ b/src/libutil/lru-cache.hh @@ -2,7 +2,7 @@ #include #include -#include +#include namespace nix { @@ -64,7 +64,7 @@ public: /* Look up an item in the cache. If it exists, it becomes the most recently used item. */ - std::experimental::optional get(const Key & key) + std::optional get(const Key & key) { auto i = data.find(key); if (i == data.end()) return {}; diff --git a/src/libutil/serialise.cc b/src/libutil/serialise.cc index 0e75eeec2..8201549fd 100644 --- a/src/libutil/serialise.cc +++ b/src/libutil/serialise.cc @@ -171,7 +171,7 @@ std::unique_ptr sinkToSource( std::function fun; std::function eof; - std::experimental::optional coro; + std::optional coro; bool started = false; SinkToSource(std::function fun, std::function eof) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 7eca35577..e3dcd246c 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -965,7 +965,7 @@ std::vector stringsToCharPtrs(const Strings & ss) string runProgram(Path program, bool searchPath, const Strings & args, - const std::experimental::optional & input) + const std::optional & input) { RunOptions opts(program, args); opts.searchPath = searchPath; diff --git a/src/libutil/util.hh b/src/libutil/util.hh index bda87bee4..9f239bff3 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #ifndef HAVE_STRUCT_DIRENT_D_TYPE @@ -259,14 +259,14 @@ pid_t startProcess(std::function fun, const ProcessOptions & options = P shell backtick operator). */ string runProgram(Path program, bool searchPath = false, const Strings & args = Strings(), - const std::experimental::optional & input = {}); + const std::optional & input = {}); struct RunOptions { Path program; bool searchPath = true; Strings args; - std::experimental::optional input; + std::optional input; Source * standardIn = nullptr; Sink * standardOut = nullptr; bool _killStderr = false; -- cgit v1.2.3 From d4ee8afd59cd7935f59b730c432cf58460af8a84 Mon Sep 17 00:00:00 2001 From: Nick Van den Broeck Date: Thu, 21 Feb 2019 06:53:01 +0100 Subject: Implemented --flake flag for nix build Also fixed Eelco's PR comments --- src/libutil/util.cc | 1 - 1 file changed, 1 deletion(-) (limited to 'src/libutil') diff --git a/src/libutil/util.cc b/src/libutil/util.cc index e3dcd246c..b0a2b853e 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -344,7 +344,6 @@ void writeFile(const Path & path, Source & source, mode_t mode) } } - string readLine(int fd) { string s; -- cgit v1.2.3 From b42ba08fc8a291c549c1f9f92457d72639fac995 Mon Sep 17 00:00:00 2001 From: Nick Van den Broeck Date: Thu, 21 Mar 2019 09:30:16 +0100 Subject: Add command `flake clone` --- src/libutil/util.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/libutil') diff --git a/src/libutil/util.cc b/src/libutil/util.cc index b0a2b853e..f4f86c5c8 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -962,12 +962,14 @@ std::vector stringsToCharPtrs(const Strings & ss) return res; } - +// Output = "standard out" output stream string runProgram(Path program, bool searchPath, const Strings & args, const std::optional & 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); @@ -978,6 +980,7 @@ string runProgram(Path program, bool searchPath, const Strings & args, return res.second; } +// Output = error code + "standard out" output stream std::pair runProgram(const RunOptions & options_) { RunOptions options(options_); @@ -1028,6 +1031,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()); -- cgit v1.2.3 From 6960ee929dcf95c24e0db761fd4bc46c3749abb2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 19 Apr 2019 11:34:23 +0200 Subject: Clean up exportGit argument handling --- src/libutil/hash.hh | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src/libutil') diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh index 2dbc3b630..edede8ace 100644 --- a/src/libutil/hash.hh +++ b/src/libutil/hash.hh @@ -80,6 +80,18 @@ struct Hash or base-64. By default, this is prefixed by the hash type (e.g. "sha256:"). */ std::string to_string(Base base = Base32, bool includeType = true) const; + + std::string gitRev() const + { + assert(type == htSHA1); + return to_string(Base16, false); + } + + std::string gitShortRev() const + { + assert(type == htSHA1); + return std::string(to_string(Base16, false), 0, 7); + } }; -- cgit v1.2.3 From 7dcf5b011a0942ecf953f2b607c4c8d0e9e652c7 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 2 May 2019 21:09:52 +0200 Subject: Add function for quoting strings --- src/libutil/util.cc | 22 ---------------------- src/libutil/util.hh | 22 ++++++++++++++++++++-- 2 files changed, 20 insertions(+), 24 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/util.cc b/src/libutil/util.cc index f4f86c5c8..5598415f5 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1167,28 +1167,6 @@ template StringSet tokenizeString(const string & s, const string & separators); template vector tokenizeString(const string & s, const string & separators); -string concatStringsSep(const string & sep, const Strings & ss) -{ - string s; - for (auto & i : ss) { - if (s.size() != 0) s += sep; - s += i; - } - return s; -} - - -string concatStringsSep(const string & sep, const StringSet & ss) -{ - string s; - for (auto & i : ss) { - if (s.size() != 0) s += sep; - s += i; - } - return s; -} - - string chomp(const string & s) { size_t i = s.find_last_not_of(" \n\r\t"); diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 9f239bff3..8bd57d2e4 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -334,8 +334,26 @@ template C tokenizeString(const string & s, const string & separators = /* Concatenate the given strings with a separator between the elements. */ -string concatStringsSep(const string & sep, const Strings & ss); -string concatStringsSep(const string & sep, const StringSet & ss); +template +string concatStringsSep(const string & sep, const C & ss) +{ + string s; + for (auto & i : ss) { + if (s.size() != 0) s += sep; + s += i; + } + return s; +} + + +/* Add quotes around a collection of strings. */ +template Strings quoteStrings(const C & c) +{ + Strings res; + for (auto & s : c) + res.push_back("'" + s + "'"); + return res; +} /* Remove trailing whitespace from a string. */ -- cgit v1.2.3 From 8ec77614f63e14d1869734b0d21a646667bbf88b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 2 May 2019 21:28:41 +0200 Subject: Move createTempFile to libutil --- src/libutil/util.cc | 11 +++++++++++ src/libutil/util.hh | 12 ++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 5598415f5..75b73fcfa 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -461,6 +461,17 @@ Path createTempDir(const Path & tmpRoot, const Path & prefix, } +std::pair createTempFile(const Path & prefix) +{ + Path tmpl(getEnv("TMPDIR", "/tmp") + "/" + prefix + ".XXXXXX"); + // Strictly speaking, this is UB, but who cares... + AutoCloseFD fd(mkstemp((char *) tmpl.c_str())); + if (!fd) + throw SysError("creating temporary file '%s'", tmpl); + return {std::move(fd), tmpl}; +} + + static Lazy getHome2([]() { Path homeDir = getEnv("HOME"); if (homeDir.empty()) { diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 8bd57d2e4..6c9d7c2eb 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -118,10 +118,6 @@ void deletePath(const Path & path); void deletePath(const Path & path, unsigned long long & bytesFreed); -/* Create a temporary directory. */ -Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix", - bool includePid = true, bool useGlobalCounter = true, mode_t mode = 0755); - /* Return $HOME or the user's home directory from /etc/passwd. */ Path getHome(); @@ -199,6 +195,14 @@ public: }; +/* Create a temporary directory. */ +Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix", + bool includePid = true, bool useGlobalCounter = true, mode_t mode = 0755); + +/* Create a temporary file, returning a file handle and its path. */ +std::pair createTempFile(const Path & prefix = "nix"); + + class Pipe { public: -- cgit v1.2.3 From ae7b56cd9a5ed8810828736fbb930a7c14ea44ca Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 28 May 2019 22:35:41 +0200 Subject: Get last commit time of github flakes --- src/libutil/util.cc | 18 +++++++++++++++--- src/libutil/util.hh | 6 ++++-- 2 files changed, 19 insertions(+), 5 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/util.cc b/src/libutil/util.cc index f82f902fc..92c8957ff 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #ifdef __APPLE__ @@ -552,20 +553,31 @@ Paths createDirs(const Path & path) } -void createSymlink(const Path & target, const Path & link) +void createSymlink(const Path & target, const Path & link, + std::optional mtime) { if (symlink(target.c_str(), link.c_str())) throw SysError(format("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 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; diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 35f9169f6..e05ef1e7d 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -142,10 +142,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 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 mtime = {}); /* Wrappers arount read()/write() that read/write exactly the -- cgit v1.2.3 From 615a9d031d22a6aee64f8511e15685e47b6f8796 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 17 Jun 2019 09:12:03 +0200 Subject: Add "warning" verbosity level This ensures that "nix" shows warnings. Previously these were hidden because they were at "info" level. --- src/libutil/logging.cc | 3 ++- src/libutil/logging.hh | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'src/libutil') diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc index 799c6e1ae..b379306f6 100644 --- a/src/libutil/logging.cc +++ b/src/libutil/logging.cc @@ -21,7 +21,7 @@ Logger * logger = makeDefaultLogger(); void Logger::warn(const std::string & msg) { - log(lvlInfo, ANSI_RED "warning:" ANSI_NORMAL " " + msg); + log(lvlWarn, ANSI_RED "warning:" ANSI_NORMAL " " + msg); } class SimpleLogger : public Logger @@ -46,6 +46,7 @@ public: char c; switch (lvl) { case lvlError: c = '3'; break; + case lvlWarn: c = '4'; break; case lvlInfo: c = '5'; break; case lvlTalkative: case lvlChatty: c = '6'; break; default: c = '7'; diff --git a/src/libutil/logging.hh b/src/libutil/logging.hh index 678703102..5f2219445 100644 --- a/src/libutil/logging.hh +++ b/src/libutil/logging.hh @@ -6,6 +6,7 @@ namespace nix { typedef enum { lvlError = 0, + lvlWarn, lvlInfo, lvlTalkative, lvlChatty, -- cgit v1.2.3 From a0de58f471c9087d8e6cc60a6078f9940a125b15 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Jun 2019 16:01:35 +0200 Subject: Make subcommand construction in MultiCommand lazy --- src/libutil/args.cc | 15 +++++++-------- src/libutil/args.hh | 11 ++++++++--- 2 files changed, 15 insertions(+), 11 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 2837dacc9..217495c26 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -215,17 +215,15 @@ void Command::printHelp(const string & programName, std::ostream & out) } } -MultiCommand::MultiCommand(const std::vector> & _commands) +MultiCommand::MultiCommand(const Commands & commands) + : commands(commands) { - for (auto & command : _commands) - commands.emplace(command->name(), command); - expectedArgs.push_back(ExpectedArg{"command", 1, true, [=](std::vector ss) { assert(!command); auto i = commands.find(ss[0]); if (i == commands.end()) throw UsageError("'%s' is not a recognised command", ss[0]); - command = i->second; + command = i->second(); }}); } @@ -246,10 +244,11 @@ void MultiCommand::printHelp(const string & programName, std::ostream & out) out << "Available commands:\n"; Table2 table; - for (auto & command : commands) { - auto descr = command.second->description(); + for (auto & i : commands) { + auto command = i.second(); + auto descr = command->description(); if (!descr.empty()) - table.push_back(std::make_pair(command.second->name(), descr)); + table.push_back(std::make_pair(command->name(), descr)); } printTable(out, table); } diff --git a/src/libutil/args.hh b/src/libutil/args.hh index bf69bf4b6..8497eaf71 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -192,7 +192,12 @@ public: run() method. */ struct Command : virtual Args { - virtual std::string name() = 0; +private: + std::string _name; + +public: + std::string name() { return _name; } + virtual void prepare() { }; virtual void run() = 0; @@ -209,7 +214,7 @@ struct Command : virtual Args void printHelp(const string & programName, std::ostream & out) override; }; -typedef std::map> Commands; +typedef std::map()>> Commands; /* An argument parser that supports multiple subcommands, i.e. ‘ ’. */ @@ -220,7 +225,7 @@ public: std::shared_ptr command; - MultiCommand(const std::vector> & commands); + MultiCommand(const Commands & commands); void printHelp(const string & programName, std::ostream & out) override; -- cgit v1.2.3 From d0a769cb061a13ad880c76e5ea69a76150439853 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Jun 2019 23:37:40 +0200 Subject: Initialize Command::_name --- src/libutil/args.cc | 2 ++ src/libutil/args.hh | 3 +++ 2 files changed, 5 insertions(+) (limited to 'src/libutil') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 217495c26..ba15ea571 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -224,6 +224,7 @@ MultiCommand::MultiCommand(const Commands & commands) if (i == commands.end()) throw UsageError("'%s' is not a recognised command", ss[0]); command = i->second(); + command->_name = ss[0]; }}); } @@ -246,6 +247,7 @@ void MultiCommand::printHelp(const string & programName, std::ostream & out) Table2 table; for (auto & i : commands) { auto command = i.second(); + command->_name = i.first; auto descr = command->description(); if (!descr.empty()) table.push_back(std::make_pair(command->name(), descr)); diff --git a/src/libutil/args.hh b/src/libutil/args.hh index 8497eaf71..a083c4ce8 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -195,7 +195,10 @@ struct Command : virtual Args private: std::string _name; + friend class MultiCommand; + public: + std::string name() { return _name; } virtual void prepare() { }; -- cgit v1.2.3 From a67cf5a3585c41dd9f219a2c7aa9cf67fa69520b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 24 Jun 2019 21:48:52 +0200 Subject: Fix 'error 9 while decompressing xz file' Once we've started writing data to a Sink, we can't restart a download request, because then we end up writing duplicate data to the Sink. Therefore we shouldn't handle retries in Downloader but at a higher level (in particular, in copyStorePath()). Fixes #2952. --- src/libutil/retry.hh | 38 ++++++++++++++++++++++++++++++++++++++ src/libutil/types.hh | 2 ++ 2 files changed, 40 insertions(+) create mode 100644 src/libutil/retry.hh (limited to 'src/libutil') diff --git a/src/libutil/retry.hh b/src/libutil/retry.hh new file mode 100644 index 000000000..b45cb37f7 --- /dev/null +++ b/src/libutil/retry.hh @@ -0,0 +1,38 @@ +#pragma once + +#include "logging.hh" + +#include +#include +#include +#include + +namespace nix { + +inline unsigned int retrySleepTime(unsigned int attempt) +{ + std::random_device rd; + std::mt19937 mt19937; + return 250.0 * std::pow(2.0f, + attempt - 1 + std::uniform_real_distribution<>(0.0, 0.5)(mt19937)); +} + +template +C retry(unsigned int attempts, std::function && f) +{ + unsigned int attempt = 0; + while (true) { + try { + return f(); + } catch (BaseError & e) { + ++attempt; + if (attempt >= attempts || !e.isTransient()) + throw; + auto ms = retrySleepTime(attempt); + warn("%s; retrying in %d ms", e.what(), ms); + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); + } + } +} + +} diff --git a/src/libutil/types.hh b/src/libutil/types.hh index 92bf469b5..88e3243f4 100644 --- a/src/libutil/types.hh +++ b/src/libutil/types.hh @@ -109,6 +109,8 @@ public: const string & msg() const { return err; } const string & prefix() const { return prefix_; } BaseError & addPrefix(const FormatOrString & fs); + + virtual bool isTransient() { return false; } }; #define MakeError(newClass, superClass) \ -- cgit v1.2.3 From c693f80b814c244dcdae7a2e87fb9e444d9d1ca5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 2 Sep 2019 17:43:27 +0200 Subject: Shut up some clang warnings --- src/libutil/args.hh | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/libutil') diff --git a/src/libutil/args.hh b/src/libutil/args.hh index a083c4ce8..b960a55a8 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -199,6 +199,8 @@ private: public: + virtual ~Command() { } + std::string name() { return _name; } virtual void prepare() { }; -- cgit v1.2.3 From aeb695c0074b52772057b36f442a054f8d1a856d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 2 Sep 2019 17:50:44 +0200 Subject: Remove obsolete OpenSSL locking code OpenSSL 1.1.1 no longer needs this (https://github.com/openssl/openssl/commit/2e52e7df518d80188c865ea3f7bb3526d14b0c08). This shuts up a clang warning about opensslLockCallback being unused. --- src/libutil/hash.cc | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/libutil') diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index 1c14ebb18..362c537fe 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -13,6 +13,10 @@ #include #include +#if OPENSSL_VERSION_NUMBER < 0x10101000L +#error "Unsupported version of OpenSSL, you need at least 1.1.1" +#endif + namespace nix { -- cgit v1.2.3 From 4caeefaf004c1a4fdd67485f0328a6741a9640fb Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 4 Sep 2019 14:06:52 +0200 Subject: Revert "Remove obsolete OpenSSL locking code" This reverts commit aeb695c0074b52772057b36f442a054f8d1a856d. --- src/libutil/hash.cc | 4 ---- 1 file changed, 4 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index 362c537fe..1c14ebb18 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -13,10 +13,6 @@ #include #include -#if OPENSSL_VERSION_NUMBER < 0x10101000L -#error "Unsupported version of OpenSSL, you need at least 1.1.1" -#endif - namespace nix { -- cgit v1.2.3 From 90df25ef7eb5157868a744906eaf6401430bb93d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Oct 2019 15:07:50 +0200 Subject: Fix build --- src/libutil/util.hh | 4 ---- 1 file changed, 4 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 9f8c7092d..814028442 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -122,10 +122,6 @@ void deletePath(const Path & path); void deletePath(const Path & path, unsigned long long & bytesFreed); -/* Create a temporary directory. */ -Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix", - bool includePid = true, bool useGlobalCounter = true, mode_t mode = 0755); - std::string getUserName(); /* Return $HOME or the user's home directory from /etc/passwd. */ -- cgit v1.2.3 From 1bf9eb21b75f0d93d9c1633ea2e6fdf840047e79 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 8 Jan 2020 15:34:06 +0100 Subject: absPath(): Use std::optional --- src/libutil/util.cc | 6 +++--- src/libutil/util.hh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/util.cc b/src/libutil/util.cc index dda0950a1..ab63ceb10 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -98,10 +98,10 @@ void replaceEnv(std::map newEnv) } -Path absPath(Path path, Path dir) +Path absPath(Path path, std::optional dir) { if (path[0] != '/') { - if (dir == "") { + if (!dir) { #ifdef __GNU__ /* GNU (aka. GNU/Hurd) doesn't have any limitation on path lengths and doesn't define `PATH_MAX'. */ @@ -117,7 +117,7 @@ Path absPath(Path path, Path dir) free(buf); #endif } - path = dir + "/" + path; + path = *dir + "/" + path; } return canonPath(path); } diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 5026db7cc..02c505cbc 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -46,7 +46,7 @@ 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, Path dir = ""); +Path absPath(Path path, std::optional dir = {}); /* Canonicalise a path by removing all `.' or `..' components and double or trailing slashes. Optionally resolves all symlink -- cgit v1.2.3 From 9f4d8c6170517c9452e25dc29c56a6fbb43d40a1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 21 Jan 2020 16:27:53 +0100 Subject: Pluggable fetchers Flakes are now fetched using an extensible mechanism. Also lots of other flake cleanups. --- src/libutil/types.hh | 8 ++++++++ src/libutil/util.cc | 4 ++-- src/libutil/util.hh | 4 +++- 3 files changed, 13 insertions(+), 3 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/types.hh b/src/libutil/types.hh index 20b96a85c..a1ce7b372 100644 --- a/src/libutil/types.hh +++ b/src/libutil/types.hh @@ -157,4 +157,12 @@ typedef list Paths; typedef set PathSet; +/* Helper class to run code at startup. */ +template +struct OnStartup +{ + OnStartup(T && t) { t(); } +}; + + } diff --git a/src/libutil/util.cc b/src/libutil/util.cc index ab63ceb10..4266a5474 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -98,7 +98,7 @@ void replaceEnv(std::map newEnv) } -Path absPath(Path path, std::optional dir) +Path absPath(Path path, std::optional dir, bool resolveSymlinks) { if (path[0] != '/') { if (!dir) { @@ -119,7 +119,7 @@ Path absPath(Path path, std::optional dir) } path = *dir + "/" + path; } - return canonPath(path); + return canonPath(path, resolveSymlinks); } diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 02c505cbc..2f1613ac1 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -46,7 +46,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 dir = {}); +Path absPath(Path path, + std::optional dir = {}, + bool resolveSymlinks = false); /* Canonicalise a path by removing all `.' or `..' components and double or trailing slashes. Optionally resolves all symlink -- cgit v1.2.3 From 678301072f05b650dc15c5edb4c25f08f0d6cace Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 31 Jan 2020 14:01:46 +0100 Subject: nix flake list-inputs: Pretty-print the tree --- src/libutil/util.hh | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'src/libutil') diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 2f1613ac1..5d44b92c3 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -458,6 +458,13 @@ void ignoreException(); #define ANSI_BLUE "\e[34;1m" +/* Tree formatting. */ +constexpr char treeConn[] = "╠═══"; +constexpr char treeLast[] = "╚═══"; +constexpr char treeLine[] = "║ "; +constexpr char treeNull[] = " "; + + /* Truncate a string to 'width' printable characters. If 'filterAll' is true, all ANSI escape sequences are filtered out. Otherwise, some escape sequences (such as colour setting) are copied but not @@ -583,4 +590,31 @@ extern PathFilter defaultPathFilter; AutoCloseFD createUnixDomainSocket(const Path & path, mode_t mode); +// A Rust/Python-like enumerate() iterator adapter. +// Borrowed from http://reedbeta.com/blog/python-like-enumerate-in-cpp17. +template ())), + typename = decltype(std::end(std::declval()))> +constexpr auto enumerate(T && iterable) +{ + struct iterator + { + size_t i; + TIter iter; + bool operator != (const iterator & other) const { return iter != other.iter; } + void operator ++ () { ++i; ++iter; } + auto operator * () const { return std::tie(i, *iter); } + }; + + struct iterable_wrapper + { + T iterable; + auto begin() { return iterator{ 0, std::begin(iterable) }; } + auto end() { return iterator{ 0, std::end(iterable) }; } + }; + + return iterable_wrapper{ std::forward(iterable) }; +} + + } -- cgit v1.2.3 From e91f32f2b58eda31fa8d8206ce9d0a8aef52ad13 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 31 Jan 2020 14:06:26 +0100 Subject: Use light box drawing symbols --- src/libutil/util.hh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 5d44b92c3..320590836 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -459,9 +459,9 @@ void ignoreException(); /* Tree formatting. */ -constexpr char treeConn[] = "╠═══"; -constexpr char treeLast[] = "╚═══"; -constexpr char treeLine[] = "║ "; +constexpr char treeConn[] = "├───"; +constexpr char treeLast[] = "└───"; +constexpr char treeLine[] = "│ "; constexpr char treeNull[] = " "; -- cgit v1.2.3 From 8414685c0f0c0d744c70d438894e4ecd7a078808 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 31 Jan 2020 19:16:40 +0100 Subject: Change lock file format to use an attribute representation of flake refs rather than URLs --- src/libutil/util.hh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 320590836..0aee67008 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -483,10 +483,11 @@ string base64Decode(const string & s); /* Get a value for the specified key from an associate container, or a default value if the key doesn't exist. */ template -std::optional get(const T & map, const std::string & key) +std::optional get(const T & map, const typename T::key_type & key) { auto i = map.find(key); - return i == map.end() ? std::optional() : i->second; + if (i == map.end()) return {}; + return std::optional(i->second); } -- cgit v1.2.3 From 8451298b35353abafe385124cb55e8d4911032ad Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 1 Feb 2020 16:41:54 +0100 Subject: Factor out TreeInfo --- src/libutil/logging.cc | 2 +- src/libutil/util.hh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src/libutil') diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc index b379306f6..fa5c84a27 100644 --- a/src/libutil/logging.cc +++ b/src/libutil/logging.cc @@ -21,7 +21,7 @@ Logger * logger = makeDefaultLogger(); void Logger::warn(const std::string & msg) { - log(lvlWarn, ANSI_RED "warning:" ANSI_NORMAL " " + msg); + log(lvlWarn, ANSI_YELLOW "warning:" ANSI_NORMAL " " + msg); } class SimpleLogger : public Logger diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 0aee67008..9a6268346 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -455,6 +455,7 @@ void ignoreException(); #define ANSI_FAINT "\e[2m" #define ANSI_RED "\e[31;1m" #define ANSI_GREEN "\e[32;1m" +#define ANSI_YELLOW "\e[33;1m" #define ANSI_BLUE "\e[34;1m" -- cgit v1.2.3 From e0a0ae0467fa8cdcc542f593b9d94283f04508ff Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 30 Mar 2020 14:03:28 +0200 Subject: Move fetchers from libstore to libfetchers --- src/libutil/url.cc | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/libutil/url.hh | 62 ++++++++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 src/libutil/url.cc create mode 100644 src/libutil/url.hh (limited to 'src/libutil') diff --git a/src/libutil/url.cc b/src/libutil/url.cc new file mode 100644 index 000000000..5d5328e5d --- /dev/null +++ b/src/libutil/url.cc @@ -0,0 +1,137 @@ +#include "url.hh" +#include "util.hh" + +namespace nix { + +std::regex refRegex(refRegexS, std::regex::ECMAScript); +std::regex revRegex(revRegexS, std::regex::ECMAScript); +std::regex flakeIdRegex(flakeIdRegexS, std::regex::ECMAScript); + +ParsedURL parseURL(const std::string & url) +{ + static std::regex uriRegex( + "((" + schemeRegex + "):" + + "(?:(?://(" + authorityRegex + ")(" + absPathRegex + "))|(/?" + pathRegex + ")))" + + "(?:\\?(" + queryRegex + "))?" + + "(?:#(" + queryRegex + "))?", + std::regex::ECMAScript); + + std::smatch match; + + if (std::regex_match(url, match, uriRegex)) { + auto & base = match[1]; + std::string scheme = match[2]; + auto authority = match[3].matched + ? std::optional(match[3]) : std::nullopt; + std::string path = match[4].matched ? match[4] : match[5]; + auto & query = match[6]; + auto & fragment = match[7]; + + auto isFile = scheme.find("file") != std::string::npos; + + if (authority && *authority != "" && isFile) + throw Error("file:// URL '%s' has unexpected authority '%s'", + url, *authority); + + if (isFile && path.empty()) + path = "/"; + + return ParsedURL{ + .url = url, + .base = base, + .scheme = scheme, + .authority = authority, + .path = path, + .query = decodeQuery(query), + .fragment = percentDecode(std::string(fragment)) + }; + } + + else + throw BadURL("'%s' is not a valid URL", url); +} + +std::string percentDecode(std::string_view in) +{ + std::string decoded; + for (size_t i = 0; i < in.size(); ) { + if (in[i] == '%') { + if (i + 2 >= in.size()) + throw BadURL("invalid URI parameter '%s'", in); + try { + decoded += std::stoul(std::string(in, i + 1, 2), 0, 16); + i += 3; + } catch (...) { + throw BadURL("invalid URI parameter '%s'", in); + } + } else + decoded += in[i++]; + } + return decoded; +} + +std::map decodeQuery(const std::string & query) +{ + std::map result; + + for (auto s : tokenizeString(query, "&")) { + auto e = s.find('='); + if (e != std::string::npos) + result.emplace( + s.substr(0, e), + percentDecode(std::string_view(s).substr(e + 1))); + } + + return result; +} + +std::string percentEncode(std::string_view s) +{ + std::string res; + for (auto & c : s) + if ((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || strchr("-._~!$&'()*+,;=:@", c)) + res += c; + else + res += fmt("%%%02x", (unsigned int) c); + return res; +} + +std::string encodeQuery(const std::map & ss) +{ + std::string res; + bool first = true; + for (auto & [name, value] : ss) { + if (!first) res += '&'; + first = false; + res += percentEncode(name); + res += '='; + res += percentEncode(value); + } + return res; +} + +std::string ParsedURL::to_string() const +{ + return + scheme + + ":" + + (authority ? "//" + *authority : "") + + path + + (query.empty() ? "" : "?" + encodeQuery(query)) + + (fragment.empty() ? "" : "#" + percentEncode(fragment)); +} + +bool ParsedURL::operator ==(const ParsedURL & other) const +{ + return + scheme == other.scheme + && authority == other.authority + && path == other.path + && query == other.query + && fragment == other.fragment; +} + +} diff --git a/src/libutil/url.hh b/src/libutil/url.hh new file mode 100644 index 000000000..1503023a2 --- /dev/null +++ b/src/libutil/url.hh @@ -0,0 +1,62 @@ +#pragma once + +#include "types.hh" + +#include + +namespace nix { + +struct ParsedURL +{ + std::string url; + std::string base; // URL without query/fragment + std::string scheme; + std::optional authority; + std::string path; + std::map query; + std::string fragment; + + std::string to_string() const; + + bool operator ==(const ParsedURL & other) const; +}; + +MakeError(BadURL, Error); + +std::string percentDecode(std::string_view in); + +std::map decodeQuery(const std::string & query); + +ParsedURL parseURL(const std::string & url); + +// URI stuff. +const static std::string pctEncoded = "(?:%[0-9a-fA-F][0-9a-fA-F])"; +const static std::string schemeRegex = "(?:[a-z+]+)"; +const static std::string ipv6AddressRegex = "(?:\\[[0-9a-fA-F:]+\\])"; +const static std::string unreservedRegex = "(?:[a-zA-Z0-9-._~])"; +const static std::string subdelimsRegex = "(?:[!$&'\"()*+,;=])"; +const static std::string hostnameRegex = "(?:(?:" + unreservedRegex + "|" + pctEncoded + "|" + subdelimsRegex + ")*)"; +const static std::string hostRegex = "(?:" + ipv6AddressRegex + "|" + hostnameRegex + ")"; +const static std::string userRegex = "(?:(?:" + unreservedRegex + "|" + pctEncoded + "|" + subdelimsRegex + "|:)*)"; +const static std::string authorityRegex = "(?:" + userRegex + "@)?" + hostRegex + "(?::[0-9]+)?"; +const static std::string pcharRegex = "(?:" + unreservedRegex + "|" + pctEncoded + "|" + subdelimsRegex + "|[:@])"; +const static std::string queryRegex = "(?:" + pcharRegex + "|[/? \"])*"; +const static std::string segmentRegex = "(?:" + pcharRegex + "+)"; +const static std::string absPathRegex = "(?:(?:/" + segmentRegex + ")*/?)"; +const static std::string pathRegex = "(?:" + segmentRegex + "(?:/" + segmentRegex + ")*/?)"; + +// A Git ref (i.e. branch or tag name). +const static std::string refRegexS = "[a-zA-Z0-9][a-zA-Z0-9_.-]*"; // FIXME: check +extern std::regex refRegex; + +// A Git revision (a SHA-1 commit hash). +const static std::string revRegexS = "[0-9a-fA-F]{40}"; +extern std::regex revRegex; + +// A ref or revision, or a ref followed by a revision. +const static std::string refAndOrRevRegex = "(?:(" + revRegexS + ")|(?:(" + refRegexS + ")(?:/(" + revRegexS + "))?))"; + +const static std::string flakeIdRegexS = "[a-zA-Z][a-zA-Z0-9_-]*"; +extern std::regex flakeIdRegex; + +} -- cgit v1.2.3 From 696c026006a6ac46adc990ed5cb0f31535bac076 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 16 Apr 2020 13:12:58 +0200 Subject: Logger: Add method for writing to stdout Usually this just writes to stdout, but for ProgressBar, we need to clear the current line, write the line to stdout, and then redraw the progress bar. --- src/libutil/logging.cc | 6 ++++++ src/libutil/logging.hh | 10 ++++++++++ 2 files changed, 16 insertions(+) (limited to 'src/libutil') diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc index bb437cf1c..3cc4ef8f1 100644 --- a/src/libutil/logging.cc +++ b/src/libutil/logging.cc @@ -3,6 +3,7 @@ #include #include +#include namespace nix { @@ -24,6 +25,11 @@ void Logger::warn(const std::string & msg) log(lvlWarn, ANSI_YELLOW "warning:" ANSI_NORMAL " " + msg); } +void Logger::writeToStdout(std::string_view s) +{ + std::cout << s << "\n"; +} + class SimpleLogger : public Logger { public: diff --git a/src/libutil/logging.hh b/src/libutil/logging.hh index 108b5dcb0..18c24d508 100644 --- a/src/libutil/logging.hh +++ b/src/libutil/logging.hh @@ -78,6 +78,16 @@ public: virtual void stopActivity(ActivityId act) { }; virtual void result(ActivityId act, ResultType type, const Fields & fields) { }; + + virtual void writeToStdout(std::string_view s); + + template + inline void stdout(const std::string & fs, const Args & ... args) + { + boost::format f(fs); + formatHelper(f, args...); + writeToStdout(f.str()); + } }; ActivityId getCurActivity(); -- cgit v1.2.3 From 91ddee6bf045b1c6144d14233abdb96127186ec3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 10 May 2020 20:32:21 +0200 Subject: nix: Implement basic bash completion --- src/libutil/args.cc | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++-- src/libutil/args.hh | 4 ++++ src/libutil/hash.cc | 3 +++ src/libutil/hash.hh | 2 ++ 4 files changed, 62 insertions(+), 2 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index f829415d1..ceaabcfca 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -13,6 +13,19 @@ void Args::addFlag(Flag && flag_) if (flag->shortName) shortFlags[flag->shortName] = flag; } +std::shared_ptr> completions; + +std::string completionMarker = "___COMPLETE___"; + +std::optional 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 +33,13 @@ 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(); + } + for (auto pos = cmdline.begin(); pos != cmdline.end(); ) { auto arg = *pos; @@ -99,18 +119,32 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) auto process = [&](const std::string & name, const Flag & flag) -> bool { ++pos; std::vector args; + bool anyNeedsCompletion = 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 (anyNeedsCompletion) + args.push_back(""); + else + throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); + } else { + if (needsCompletion(*pos)) + anyNeedsCompletion = true; + args.push_back(*pos++); } - args.push_back(*pos++); } 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 +157,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; } @@ -161,6 +203,10 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) .description = "hash algorithm ('md5', 'sha1', 'sha256', or 'sha512')", .labels = {"hash-algo"}, .handler = {[ht](std::string s) { + if (auto prefix = needsCompletion(s)) + for (auto & type : hashTypes) + if (hasPrefix(type, *prefix)) + completions->insert(type); *ht = parseHashType(s); if (*ht == htUnknown) throw UsageError("unknown hash type '%1%'", s); @@ -217,6 +263,11 @@ MultiCommand::MultiCommand(const Commands & commands) { expectedArgs.push_back(ExpectedArg{"command", 1, true, [=](std::vector ss) { assert(!command); + if (auto prefix = needsCompletion(ss[0])) { + for (auto & [name, command] : commands) + if (hasPrefix(name, *prefix)) + completions->insert(name); + } auto i = commands.find(ss[0]); if (i == commands.end()) throw UsageError("'%s' is not a recognised command", ss[0]); diff --git a/src/libutil/args.hh b/src/libutil/args.hh index 1932e6a8a..ae2875e72 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -256,4 +256,8 @@ typedef std::vector> Table2; void printTable(std::ostream & out, const Table2 & table); +extern std::shared_ptr> completions; + +std::optional needsCompletion(std::string_view s); + } diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index 7caee1da7..606c78ed7 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -16,6 +16,9 @@ namespace nix { +std::set hashTypes = { "md5", "sha1", "sha256", "sha512" }; + + void Hash::init() { if (type == htMD5) hashSize = md5HashSize; diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh index ea9fca3e7..e1a16ba22 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 hashTypes; + extern const string base32Chars; enum Base : int { Base64, Base32, Base16, SRI }; -- cgit v1.2.3 From e0c19ee620c53b52ca7cf69c19d414d782338be1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 10 May 2020 21:35:07 +0200 Subject: Add completion for paths --- src/libutil/args.cc | 48 +++++++++++++++++++++++++++++++++++++++++++++++- src/libutil/args.hh | 12 ++++++++++-- 2 files changed, 57 insertions(+), 3 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index ceaabcfca..320b1b4b2 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -1,6 +1,8 @@ #include "args.hh" #include "hash.hh" +#include + namespace nix { void Args::addFlag(Flag && flag_) @@ -13,6 +15,7 @@ void Args::addFlag(Flag && flag_) if (flag->shortName) shortFlags[flag->shortName] = flag; } +bool pathCompletions = false; std::shared_ptr> completions; std::string completionMarker = "___COMPLETE___"; @@ -128,8 +131,11 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) else throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); } else { - if (needsCompletion(*pos)) + if (needsCompletion(*pos)) { + if (flag.completer) + flag.completer(n, *pos); anyNeedsCompletion = true; + } args.push_back(*pos++); } } @@ -214,6 +220,46 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) }; } +void completePath(size_t, std::string_view s) +{ + if (auto prefix = needsCompletion(s)) { + pathCompletions = true; + glob_t globbuf; + if (glob((*prefix + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE, nullptr, &globbuf) == 0) { + for (size_t i = 0; i < globbuf.gl_pathc; ++i) + completions->insert(globbuf.gl_pathv[i]); + globfree(&globbuf); + } + } +} + +void Args::expectPathArg(const std::string & label, string * dest, bool optional) +{ + expectedArgs.push_back({ + .label = label, + .arity = 1, + .optional = optional, + .handler = {[=](std::vector ss) { + completePath(0, ss[0]); + *dest = ss[0]; + }} + }); +} + +void Args::expectPathArgs(const std::string & label, std::vector * dest) +{ + expectedArgs.push_back({ + .label = label, + .arity = 0, + .optional = false, + .handler = {[=](std::vector ss) { + for (auto & s : ss) + completePath(0, s); + *dest = std::move(ss); + }} + }); +} + Strings argvToStrings(int argc, char * * argv) { Strings args; diff --git a/src/libutil/args.hh b/src/libutil/args.hh index ae2875e72..f93459c96 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -83,6 +83,7 @@ protected: std::string category; Strings labels; Handler handler; + std::function completer; static Flag mkHashTypeFlag(std::string && longName, HashType * ht); }; @@ -98,8 +99,8 @@ protected: struct ExpectedArg { std::string label; - size_t arity; // 0 = any - bool optional; + size_t arity = 0; // 0 = any + bool optional = false; std::function)> handler; }; @@ -182,6 +183,8 @@ public: }}); } + void expectPathArg(const std::string & label, string * dest, bool optional = false); + /* Expect 0 or more arguments. */ void expectArgs(const std::string & label, std::vector * dest) { @@ -190,6 +193,8 @@ public: }}); } + void expectPathArgs(const std::string & label, std::vector * dest); + friend class MultiCommand; }; @@ -257,7 +262,10 @@ typedef std::vector> Table2; void printTable(std::ostream & out, const Table2 & table); extern std::shared_ptr> completions; +extern bool pathCompletions; std::optional needsCompletion(std::string_view s); +void completePath(size_t, std::string_view s); + } -- cgit v1.2.3 From 0884f180f5ca8a864e6db5256eaa10646e87d671 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 10 May 2020 21:50:32 +0200 Subject: Simplify --- src/libutil/args.cc | 28 +++++++++++----------------- src/libutil/util.cc | 2 +- src/libutil/util.hh | 2 +- 3 files changed, 13 insertions(+), 19 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 320b1b4b2..257b4b6c4 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -122,22 +122,15 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) auto process = [&](const std::string & name, const Flag & flag) -> bool { ++pos; std::vector args; - bool anyNeedsCompletion = false; for (size_t n = 0 ; n < flag.handler.arity; ++n) { if (pos == end) { if (flag.handler.arity == ArityAny) break; - if (anyNeedsCompletion) - args.push_back(""); - else - throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); - } else { - if (needsCompletion(*pos)) { - if (flag.completer) - flag.completer(n, *pos); - anyNeedsCompletion = true; - } - args.push_back(*pos++); + throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); } + if (auto prefix = needsCompletion(*pos)) + if (flag.completer) + flag.completer(n, *prefix); + args.push_back(*pos++); } flag.handler.fun(std::move(args)); return true; @@ -209,14 +202,15 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) .description = "hash algorithm ('md5', 'sha1', 'sha256', or 'sha512')", .labels = {"hash-algo"}, .handler = {[ht](std::string s) { - if (auto prefix = needsCompletion(s)) - for (auto & type : hashTypes) - if (hasPrefix(type, *prefix)) - completions->insert(type); *ht = parseHashType(s); if (*ht == htUnknown) throw UsageError("unknown hash type '%1%'", s); - }} + }}, + .completer = [](size_t index, std::string_view prefix) { + for (auto & type : hashTypes) + if (hasPrefix(type, prefix)) + completions->insert(type); + } }; } diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 4c8e2b26d..f2782ce69 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1330,7 +1330,7 @@ bool statusOk(int status) } -bool hasPrefix(const string & s, const string & prefix) +bool hasPrefix(std::string_view s, std::string_view prefix) { return s.compare(0, prefix.size(), prefix) == 0; } diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 4be1d4580..a861d5aa6 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -431,7 +431,7 @@ template bool string2Float(const string & s, N & n) /* Return true iff `s' starts with `prefix'. */ -bool hasPrefix(const string & s, const string & prefix); +bool hasPrefix(std::string_view s, std::string_view prefix); /* Return true iff `s' ends in `suffix'. */ -- cgit v1.2.3 From 4c3c638a05e52cdc3bd96255873b711a28630288 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 11 May 2020 15:46:18 +0200 Subject: Cleanup --- src/libutil/args.cc | 90 +++++++++++++++----------------------- src/libutil/args.hh | 123 ++++++++++++++++++++++++++++------------------------ 2 files changed, 101 insertions(+), 112 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 257b4b6c4..c389090ae 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -86,7 +86,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"; @@ -127,8 +127,8 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) if (flag.handler.arity == ArityAny) break; throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); } - if (auto prefix = needsCompletion(*pos)) - if (flag.completer) + if (flag.completer) + if (auto prefix = needsCompletion(*pos)) flag.completer(n, *prefix); args.push_back(*pos++); } @@ -179,12 +179,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 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; } @@ -214,46 +219,17 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) }; } -void completePath(size_t, std::string_view s) +void completePath(size_t, std::string_view prefix) { - if (auto prefix = needsCompletion(s)) { - pathCompletions = true; - glob_t globbuf; - if (glob((*prefix + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE, nullptr, &globbuf) == 0) { - for (size_t i = 0; i < globbuf.gl_pathc; ++i) - completions->insert(globbuf.gl_pathv[i]); - globfree(&globbuf); - } + pathCompletions = true; + glob_t globbuf; + if (glob((std::string(prefix) + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE, nullptr, &globbuf) == 0) { + for (size_t i = 0; i < globbuf.gl_pathc; ++i) + completions->insert(globbuf.gl_pathv[i]); + globfree(&globbuf); } } -void Args::expectPathArg(const std::string & label, string * dest, bool optional) -{ - expectedArgs.push_back({ - .label = label, - .arity = 1, - .optional = optional, - .handler = {[=](std::vector ss) { - completePath(0, ss[0]); - *dest = ss[0]; - }} - }); -} - -void Args::expectPathArgs(const std::string & label, std::vector * dest) -{ - expectedArgs.push_back({ - .label = label, - .arity = 0, - .optional = false, - .handler = {[=](std::vector ss) { - for (auto & s : ss) - completePath(0, s); - *dest = std::move(ss); - }} - }); -} - Strings argvToStrings(int argc, char * * argv) { Strings args; @@ -301,18 +277,22 @@ 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 ss) { - assert(!command); - if (auto prefix = needsCompletion(ss[0])) { - for (auto & [name, command] : commands) - if (hasPrefix(name, *prefix)) - completions->insert(name); - } - auto i = commands.find(ss[0]); - if (i == commands.end()) - throw UsageError("'%s' is not a recognised command", ss[0]); - command = {ss[0], i->second()}; - }}); + expectArgs({ + .label = "command", + .optional = true, + .handler = {[=](std::string s) { + assert(!command); + 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 f93459c96..250244162 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -28,55 +28,60 @@ protected: static const size_t ArityAny = std::numeric_limits::max(); + struct Handler + { + std::function)> fun; + size_t arity; + + Handler() {} + + Handler(std::function)> && fun) + : fun(std::move(fun)) + , arity(ArityAny) + { } + + Handler(std::function && handler) + : fun([handler{std::move(handler)}](std::vector) { handler(); }) + , arity(0) + { } + + Handler(std::function && handler) + : fun([handler{std::move(handler)}](std::vector ss) { + handler(std::move(ss[0])); + }) + , arity(1) + { } + + Handler(std::function && handler) + : fun([handler{std::move(handler)}](std::vector ss) { + handler(std::move(ss[0]), std::move(ss[1])); + }) + , arity(2) + { } + + Handler(std::vector * dest) + : fun([=](std::vector ss) { *dest = ss; }) + , arity(ArityAny) + { } + + template + Handler(T * dest) + : fun([=](std::vector ss) { *dest = ss[0]; }) + , arity(1) + { } + + template + Handler(T * dest, const T & val) + : fun([=](std::vector ss) { *dest = val; }) + , arity(0) + { } + }; + /* Flags. */ struct Flag { typedef std::shared_ptr ptr; - struct Handler - { - std::function)> fun; - size_t arity; - - Handler() {} - - Handler(std::function)> && fun) - : fun(std::move(fun)) - , arity(ArityAny) - { } - - Handler(std::function && handler) - : fun([handler{std::move(handler)}](std::vector) { handler(); }) - , arity(0) - { } - - Handler(std::function && handler) - : fun([handler{std::move(handler)}](std::vector ss) { - handler(std::move(ss[0])); - }) - , arity(1) - { } - - Handler(std::function && handler) - : fun([handler{std::move(handler)}](std::vector ss) { - handler(std::move(ss[0]), std::move(ss[1])); - }) - , arity(2) - { } - - template - Handler(T * dest) - : fun([=](std::vector ss) { *dest = ss[0]; }) - , arity(1) - { } - - template - Handler(T * dest, const T & val) - : fun([=](std::vector ss) { *dest = val; }) - , arity(0) - { } - }; - std::string longName; char shortName = 0; std::string description; @@ -99,9 +104,9 @@ protected: struct ExpectedArg { std::string label; - size_t arity = 0; // 0 = any bool optional = false; - std::function)> handler; + Handler handler; + std::function completer; }; std::list expectedArgs; @@ -175,26 +180,30 @@ 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 ss) { - *dest = ss[0]; - }}); + expectArgs({ + .label = label, + .optional = true, + .handler = {dest} + }); } - void expectPathArg(const std::string & label, string * dest, bool optional = false); - /* Expect 0 or more arguments. */ void expectArgs(const std::string & label, std::vector * dest) { - expectedArgs.push_back(ExpectedArg{label, 0, false, [=](std::vector ss) { - *dest = std::move(ss); - }}); + expectArgs({ + .label = label, + .handler = {dest} + }); } - void expectPathArgs(const std::string & label, std::vector * dest); - friend class MultiCommand; }; @@ -266,6 +275,6 @@ extern bool pathCompletions; std::optional needsCompletion(std::string_view s); -void completePath(size_t, std::string_view s); +void completePath(size_t, std::string_view prefix); } -- cgit v1.2.3 From e917332d6396fe9808519e7a87916f789b71392a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 11 May 2020 21:38:17 +0200 Subject: Shut up warnings while running completers --- src/libutil/args.cc | 1 + 1 file changed, 1 insertion(+) (limited to 'src/libutil') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index c389090ae..ee0c99c2a 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -41,6 +41,7 @@ void Args::parseCmdline(const Strings & _cmdline) assert(n > 0 && n <= cmdline.size()); *std::next(cmdline.begin(), n - 1) += completionMarker; completions = std::make_shared(); + verbosity = lvlError; } for (auto pos = cmdline.begin(); pos != cmdline.end(); ) { -- cgit v1.2.3 From 27d34ef770356f86823ca832f278e72bb0a07982 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 11 May 2020 22:04:13 +0200 Subject: When completing flakerefs, only return directories --- src/libutil/args.cc | 14 ++++++++++++-- src/libutil/args.hh | 2 ++ 2 files changed, 14 insertions(+), 2 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index ee0c99c2a..f6740e076 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -220,17 +220,27 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) }; } -void completePath(size_t, std::string_view prefix) +static void completePath(std::string_view prefix, int flags) { pathCompletions = true; glob_t globbuf; - if (glob((std::string(prefix) + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE, nullptr, &globbuf) == 0) { + if (glob((std::string(prefix) + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE | flags, nullptr, &globbuf) == 0) { for (size_t i = 0; i < globbuf.gl_pathc; ++i) completions->insert(globbuf.gl_pathv[i]); globfree(&globbuf); } } +void completePath(size_t, std::string_view prefix) +{ + completePath(prefix, 0); +} + +void completeDir(size_t, std::string_view prefix) +{ + completePath(prefix, GLOB_ONLYDIR); +} + Strings argvToStrings(int argc, char * * argv) { Strings args; diff --git a/src/libutil/args.hh b/src/libutil/args.hh index 250244162..405ec3d47 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -277,4 +277,6 @@ std::optional needsCompletion(std::string_view s); void completePath(size_t, std::string_view prefix); +void completeDir(size_t, std::string_view prefix); + } -- cgit v1.2.3 From 437614b479dd30200dcdc1950f8d4fdddfef7a61 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 12 May 2020 11:08:59 +0200 Subject: Fix macOS build macOS doesn't have GLOB_ONLYDIR. --- src/libutil/args.cc | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index f6740e076..8667bd450 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -220,25 +220,35 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) }; } -static void completePath(std::string_view prefix, int flags) +static void completePath(std::string_view prefix, bool onlyDirs) { pathCompletions = true; glob_t globbuf; - if (glob((std::string(prefix) + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE | flags, nullptr, &globbuf) == 0) { - for (size_t i = 0; i < globbuf.gl_pathc; ++i) + 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, 0); + completePath(prefix, false); } void completeDir(size_t, std::string_view prefix) { - completePath(prefix, GLOB_ONLYDIR); + completePath(prefix, true); } Strings argvToStrings(int argc, char * * argv) -- cgit v1.2.3 From 6470450ab48196668099eeca2f171fec110d647b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 8 Jun 2020 16:20:00 +0200 Subject: Add completion for --update-input --- src/libutil/args.hh | 2 -- src/libutil/types.hh | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/args.hh b/src/libutil/args.hh index 59541df99..2d49dbf66 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 diff --git a/src/libutil/types.hh b/src/libutil/types.hh index 250c9581d..f70590b44 100644 --- a/src/libutil/types.hh +++ b/src/libutil/types.hh @@ -131,6 +131,7 @@ public: } MakeError(Error, BaseError); +MakeError(UsageError, Error); class SysError : public Error { -- cgit v1.2.3 From 6ff9aa8df7ce8266147f74c65e2cc529a1e72ce0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 1 Jul 2020 20:31:39 +0200 Subject: Don't process an option if any of its arguments need completion --- src/libutil/args.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index b16a2e213..986c5d1cd 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -123,17 +123,21 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) auto process = [&](const std::string & name, const Flag & flag) -> bool { ++pos; std::vector 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)) + 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; }; -- cgit v1.2.3 From 64f03635d763bd627f4fb8516c04eb64e881c902 Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Sun, 12 Jul 2020 16:50:22 +0200 Subject: Fix ANSI color constants The `m` acts as termination-symbol when declaring graphics. Because of this, the `;1m` doesn't have any effect and is directly printed to the console: ``` $ nix repl > builtins.fetchGit { /* ... */ } { outPath = "/nix/store/s0f0iz4a41cxx2h055lmh6p2d5k5bc6r-source"; rev = "e73e45b723a9a6eecb98bd5f3df395d9ab3633b6"; revCount = ;1m428; shortRev = "e73e45b"; submodules = ;1mfalse; } ``` Introduced by 6403508f5a2fcf073b2a0d4e5fcf5f5ebb890384. --- src/libutil/ansicolor.hh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/ansicolor.hh b/src/libutil/ansicolor.hh index a38c2d798..ae741f867 100644 --- a/src/libutil/ansicolor.hh +++ b/src/libutil/ansicolor.hh @@ -11,7 +11,7 @@ namespace nix { #define ANSI_GREEN "\e[32;1m" #define ANSI_YELLOW "\e[33;1m" #define ANSI_BLUE "\e[34;1m" -#define ANSI_MAGENTA "\e[35m;1m" -#define ANSI_CYAN "\e[36m;1m" +#define ANSI_MAGENTA "\e[35;1m" +#define ANSI_CYAN "\e[36;1m" } -- cgit v1.2.3 From fc84c358d9e55e9ba1d939d8974f6deef629848e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 10 Jul 2020 20:58:02 +0200 Subject: Make 'nix copy' to file:// binary caches run in constant memory --- src/libutil/archive.hh | 4 ++-- src/libutil/serialise.hh | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/archive.hh b/src/libutil/archive.hh index 768fe2536..795e9ce02 100644 --- a/src/libutil/archive.hh +++ b/src/libutil/archive.hh @@ -63,11 +63,11 @@ struct ParseSink virtual void createSymlink(const Path & path, const string & target) { }; }; -struct TeeSink : ParseSink +struct TeeParseSink : ParseSink { TeeSource source; - TeeSink(Source & source) : source(source) { } + TeeParseSink(Source & source) : source(source) { } }; void parseDump(ParseSink & sink, Source & source); diff --git a/src/libutil/serialise.hh b/src/libutil/serialise.hh index a04118512..bd651fb7d 100644 --- a/src/libutil/serialise.hh +++ b/src/libutil/serialise.hh @@ -166,6 +166,19 @@ struct StringSource : Source }; +/* A sink that writes all incoming data to two other sinks. */ +struct TeeSink : Sink +{ + Sink & sink1, & sink2; + TeeSink(Sink & sink1, Sink & sink2) : sink1(sink1), sink2(sink2) { } + virtual void operator () (const unsigned char * data, size_t len) + { + sink1(data, len); + sink2(data, len); + } +}; + + /* Adapter class of a Source that saves all data read to `s'. */ struct TeeSource : Source { -- cgit v1.2.3 From 0a9da00a10fa27a3e3b98439cb0a7d5e79135b58 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 13 Jul 2020 17:30:42 +0200 Subject: NarAccessor: Run in constant memory --- src/libutil/archive.hh | 3 ++- src/libutil/serialise.hh | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'src/libutil') diff --git a/src/libutil/archive.hh b/src/libutil/archive.hh index 795e9ce02..302b1bb18 100644 --- a/src/libutil/archive.hh +++ b/src/libutil/archive.hh @@ -65,9 +65,10 @@ struct ParseSink struct TeeParseSink : ParseSink { + StringSink saved; TeeSource source; - TeeParseSink(Source & source) : source(source) { } + TeeParseSink(Source & source) : source(source, saved) { } }; void parseDump(ParseSink & sink, Source & source); diff --git a/src/libutil/serialise.hh b/src/libutil/serialise.hh index bd651fb7d..84a4eb001 100644 --- a/src/libutil/serialise.hh +++ b/src/libutil/serialise.hh @@ -179,17 +179,17 @@ struct TeeSink : Sink }; -/* Adapter class of a Source that saves all data read to `s'. */ +/* Adapter class of a Source that saves all data read to a sink. */ struct TeeSource : Source { Source & orig; - ref data; - TeeSource(Source & orig) - : orig(orig), data(make_ref()) { } + Sink & sink; + TeeSource(Source & orig, Sink & sink) + : orig(orig), sink(sink) { } size_t read(unsigned char * data, size_t len) { size_t n = orig.read(data, len); - this->data->append((const char *) data, n); + sink(data, len); return n; } }; -- cgit v1.2.3 From 493961b6899e7f3471e7efa24ed251c7723adbcd Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 13 Jul 2020 18:22:56 +0200 Subject: Remove istringstream_nocopy --- src/libutil/hash.cc | 1 - src/libutil/istringstream_nocopy.hh | 92 ------------------------------------- 2 files changed, 93 deletions(-) delete mode 100644 src/libutil/istringstream_nocopy.hh (limited to 'src/libutil') diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index 1a3e7c5d8..f0d7754d1 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -8,7 +8,6 @@ #include "hash.hh" #include "archive.hh" #include "util.hh" -#include "istringstream_nocopy.hh" #include #include diff --git a/src/libutil/istringstream_nocopy.hh b/src/libutil/istringstream_nocopy.hh deleted file mode 100644 index f7beac578..000000000 --- a/src/libutil/istringstream_nocopy.hh +++ /dev/null @@ -1,92 +0,0 @@ -/* This file provides a variant of std::istringstream that doesn't - copy its string argument. This is useful for large strings. The - caller must ensure that the string object is not destroyed while - it's referenced by this object. */ - -#pragma once - -#include -#include - -template , class Allocator = std::allocator> -class basic_istringbuf_nocopy : public std::basic_streambuf -{ -public: - typedef std::basic_string string_type; - - typedef typename std::basic_streambuf::off_type off_type; - - typedef typename std::basic_streambuf::pos_type pos_type; - - typedef typename std::basic_streambuf::int_type int_type; - - typedef typename std::basic_streambuf::traits_type traits_type; - -private: - const string_type & s; - - off_type off; - -public: - basic_istringbuf_nocopy(const string_type & s) : s{s}, off{0} - { - } - -private: - pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which) - { - if (which & std::ios_base::in) { - this->off = dir == std::ios_base::beg - ? off - : (dir == std::ios_base::end - ? s.size() + off - : this->off + off); - } - return pos_type(this->off); - } - - pos_type seekpos(pos_type pos, std::ios_base::openmode which) - { - return seekoff(pos, std::ios_base::beg, which); - } - - std::streamsize showmanyc() - { - return s.size() - off; - } - - int_type underflow() - { - if (typename string_type::size_type(off) == s.size()) - return traits_type::eof(); - return traits_type::to_int_type(s[off]); - } - - int_type uflow() - { - if (typename string_type::size_type(off) == s.size()) - return traits_type::eof(); - return traits_type::to_int_type(s[off++]); - } - - int_type pbackfail(int_type ch) - { - if (off == 0 || (ch != traits_type::eof() && ch != s[off - 1])) - return traits_type::eof(); - - return traits_type::to_int_type(s[--off]); - } - -}; - -template , class Allocator = std::allocator> -class basic_istringstream_nocopy : public std::basic_iostream -{ - typedef basic_istringbuf_nocopy buf_type; - buf_type buf; -public: - basic_istringstream_nocopy(const typename buf_type::string_type & s) : - std::basic_iostream(&buf), buf(s) {}; -}; - -typedef basic_istringstream_nocopy istringstream_nocopy; -- cgit v1.2.3 From 7c2fef0a819481058d49c469c115bb0668b7016b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 13 Jul 2020 20:07:19 +0200 Subject: Make 'nix copy' to s3:// binary caches run in constant memory --- src/libutil/serialise.hh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'src/libutil') diff --git a/src/libutil/serialise.hh b/src/libutil/serialise.hh index 84a4eb001..8386a4991 100644 --- a/src/libutil/serialise.hh +++ b/src/libutil/serialise.hh @@ -349,4 +349,27 @@ Source & operator >> (Source & in, bool & b) } +/* An adapter that converts a std::basic_istream into a source. */ +struct StreamToSourceAdapter : Source +{ + std::shared_ptr> istream; + + StreamToSourceAdapter(std::shared_ptr> istream) + : istream(istream) + { } + + size_t read(unsigned char * data, size_t len) override + { + if (!istream->read((char *) data, len)) { + if (istream->eof()) { + if (istream->gcount() == 0) + throw EndOfFile("end of file"); + } else + throw Error("I/O error in StreamToSourceAdapter"); + } + return istream->gcount(); + } +}; + + } -- cgit v1.2.3