refactor(3p/nix/libutil): Replace string2Int & trim functions

Replaces these functions with corresponding functions from Abseil,
namely absl::StripAsciiWhitespace and absl::SimpleAtoi.

In the course of doing this some minor things I encountered along the
way were also refactored.

This also changes the signatures of the various custom readFile
functions to use absl::string_view types.
This commit is contained in:
Vincent Ambo 2020-05-25 01:19:02 +01:00
parent b371821db5
commit 98299da0fd
19 changed files with 84 additions and 72 deletions

View file

@ -1,5 +1,7 @@
#include "attr-path.hh" #include "attr-path.hh"
#include <absl/strings/numbers.h>
#include "eval-inline.hh" #include "eval-inline.hh"
#include "util.hh" #include "util.hh"
@ -50,7 +52,7 @@ Value* findAlongAttrPath(EvalState& state, const std::string& attrPath,
/* Is i an index (integer) or a normal attribute name? */ /* Is i an index (integer) or a normal attribute name? */
enum { apAttr, apIndex } apType = apAttr; enum { apAttr, apIndex } apType = apAttr;
unsigned int attrIndex; unsigned int attrIndex;
if (string2Int(attr, attrIndex)) { if (absl::SimpleAtoi(attr, &attrIndex)) {
apType = apIndex; apType = apIndex;
} }

View file

@ -4,6 +4,7 @@
#include <regex> #include <regex>
#include <utility> #include <utility>
#include <absl/strings/numbers.h>
#include <glog/logging.h> #include <glog/logging.h>
#include "derivations.hh" #include "derivations.hh"
@ -243,7 +244,7 @@ NixInt DrvInfo::queryMetaInt(const std::string& name, NixInt def) {
/* Backwards compatibility with before we had support for /* Backwards compatibility with before we had support for
integer meta fields. */ integer meta fields. */
NixInt n; NixInt n;
if (string2Int(v->string.s, n)) { if (absl::SimpleAtoi(v->string.s, &n)) {
return n; return n;
} }
} }

View file

@ -2,6 +2,8 @@
#include <memory> #include <memory>
#include <absl/strings/numbers.h>
#include "util.hh" #include "util.hh"
namespace nix { namespace nix {
@ -68,8 +70,8 @@ std::string nextComponent(std::string::const_iterator& p,
static bool componentsLT(const std::string& c1, const std::string& c2) { static bool componentsLT(const std::string& c1, const std::string& c2) {
int n1; int n1;
int n2; int n2;
bool c1Num = string2Int(c1, n1); bool c1Num = absl::SimpleAtoi(c1, &n1);
bool c2Num = string2Int(c2, n2); bool c2Num = absl::SimpleAtoi(c2, &n2);
if (c1Num && c2Num) { if (c1Num && c2Num) {
return n1 < n2; return n1 < n2;

View file

@ -2,6 +2,7 @@
#include <locale> #include <locale>
#include <absl/strings/numbers.h>
#include <signal.h> #include <signal.h>
#include "args.hh" #include "args.hh"
@ -78,7 +79,7 @@ N getIntArg(const std::string& opt, Strings::iterator& i,
} }
} }
N n; N n;
if (!string2Int(s, n)) if (!absl::SimpleAtoi(s, &n))
throw UsageError(format("'%1%' requires an integer argument") % opt); throw UsageError(format("'%1%' requires an integer argument") % opt);
return n * multiplier; return n * multiplier;
} }

View file

@ -4,6 +4,9 @@
#include <future> #include <future>
#include <memory> #include <memory>
#include <absl/strings/ascii.h>
#include <absl/strings/numbers.h>
#include "archive.hh" #include "archive.hh"
#include "compression.hh" #include "compression.hh"
#include "derivations.hh" #include "derivations.hh"
@ -21,7 +24,8 @@ namespace nix {
BinaryCacheStore::BinaryCacheStore(const Params& params) : Store(params) { BinaryCacheStore::BinaryCacheStore(const Params& params) : Store(params) {
if (secretKeyFile != "") { if (secretKeyFile != "") {
secretKey = std::make_unique<SecretKey>(readFile(secretKeyFile)); const std::string& secret_key_file = secretKeyFile;
secretKey = std::make_unique<SecretKey>(readFile(secret_key_file));
} }
StringSink sink; StringSink sink;
@ -43,7 +47,8 @@ void BinaryCacheStore::init() {
continue; continue;
} }
auto name = line.substr(0, colon); auto name = line.substr(0, colon);
auto value = trim(line.substr(colon + 1, std::string::npos)); auto value =
absl::StripAsciiWhitespace(line.substr(colon + 1, std::string::npos));
if (name == "StoreDir") { if (name == "StoreDir") {
if (value != storeDir) { if (value != storeDir) {
throw Error(format("binary cache '%s' is for Nix stores with prefix " throw Error(format("binary cache '%s' is for Nix stores with prefix "
@ -53,7 +58,9 @@ void BinaryCacheStore::init() {
} else if (name == "WantMassQuery") { } else if (name == "WantMassQuery") {
wantMassQuery_ = value == "1"; wantMassQuery_ = value == "1";
} else if (name == "Priority") { } else if (name == "Priority") {
string2Int(value, priority); if (!absl::SimpleAtoi(value, &priority)) {
LOG(WARNING) << "Invalid 'Priority' value: " << value;
}
} }
} }
} }

View file

@ -13,6 +13,7 @@
#include <thread> #include <thread>
#include <absl/strings/ascii.h> #include <absl/strings/ascii.h>
#include <absl/strings/numbers.h>
#include <fcntl.h> #include <fcntl.h>
#include <grp.h> #include <grp.h>
#include <netdb.h> #include <netdb.h>
@ -2412,7 +2413,7 @@ void DerivationGoal::startBuilder() {
userNamespaceSync.readSide = -1; userNamespaceSync.readSide = -1;
pid_t tmp; pid_t tmp;
if (!string2Int<pid_t>(readLine(builderOut.readSide.get()), tmp)) { if (!absl::SimpleAtoi(readLine(builderOut.readSide.get()), &tmp)) {
abort(); abort();
} }
pid = tmp; pid = tmp;
@ -2805,7 +2806,8 @@ void DerivationGoal::runChild() {
std::string netrcData; std::string netrcData;
try { try {
if (drv->isBuiltin() && drv->builder == "builtin:fetchurl") { if (drv->isBuiltin() && drv->builder == "builtin:fetchurl") {
netrcData = readFile(settings.netrcFile); const std::string& netrc_file = settings.netrcFile;
netrcData = readFile(netrc_file);
} }
} catch (SysError&) { } catch (SysError&) {
} }

View file

@ -1,6 +1,7 @@
#include "download.hh" #include "download.hh"
#include <absl/strings/ascii.h> #include <absl/strings/ascii.h>
#include <absl/strings/numbers.h>
#include "archive.hh" #include "archive.hh"
#include "compression.hh" #include "compression.hh"
@ -174,7 +175,8 @@ struct CurlDownloader : public Downloader {
size_t headerCallback(void* contents, size_t size, size_t nmemb) { size_t headerCallback(void* contents, size_t size, size_t nmemb) {
size_t realSize = size * nmemb; size_t realSize = size * nmemb;
std::string line((char*)contents, realSize); std::string line((char*)contents, realSize);
DLOG(INFO) << "got header for '" << request.uri << "': " << trim(line); DLOG(INFO) << "got header for '" << request.uri
<< "': " << absl::StripAsciiWhitespace(line);
if (line.compare(0, 5, "HTTP/") == 0) { // new response starts if (line.compare(0, 5, "HTTP/") == 0) { // new response starts
result.etag = ""; result.etag = "";
auto ss = tokenizeString<std::vector<std::string>>(line, " "); auto ss = tokenizeString<std::vector<std::string>>(line, " ");
@ -186,9 +188,10 @@ struct CurlDownloader : public Downloader {
} else { } else {
auto i = line.find(':'); auto i = line.find(':');
if (i != std::string::npos) { if (i != std::string::npos) {
std::string name = toLower(trim(std::string(line, 0, i))); std::string name = absl::AsciiStrToLower(
absl::StripAsciiWhitespace(std::string(line, 0, i)));
if (name == "etag") { if (name == "etag") {
result.etag = trim(std::string(line, i + 1)); result.etag = absl::StripAsciiWhitespace(std::string(line, i + 1));
/* Hack to work around a GitHub bug: it sends /* Hack to work around a GitHub bug: it sends
ETags, but ignores If-None-Match. So if we get ETags, but ignores If-None-Match. So if we get
the expected ETag on a 200 response, then shut the expected ETag on a 200 response, then shut
@ -200,9 +203,10 @@ struct CurlDownloader : public Downloader {
return 0; return 0;
} }
} else if (name == "content-encoding") { } else if (name == "content-encoding") {
encoding = trim(std::string(line, i + 1)); encoding = absl::StripAsciiWhitespace(std::string(line, i + 1));
} else if (name == "accept-ranges" && } else if (name == "accept-ranges" &&
toLower(trim(std::string(line, i + 1))) == "bytes") { absl::AsciiStrToLower(absl::StripAsciiWhitespace(
std::string(line, i + 1))) == "bytes") {
acceptRanges = true; acceptRanges = true;
} }
} }
@ -893,7 +897,7 @@ CachedDownloadResult Downloader::downloadCached(
tokenizeString<std::vector<std::string>>(readFile(dataFile), "\n"); tokenizeString<std::vector<std::string>>(readFile(dataFile), "\n");
if (ss.size() >= 3 && ss[0] == url) { if (ss.size() >= 3 && ss[0] == url) {
time_t lastChecked; time_t lastChecked;
if (string2Int(ss[2], lastChecked) && if (absl::SimpleAtoi(ss[2], &lastChecked) &&
(uint64_t)lastChecked + request.ttl >= (uint64_t)time(nullptr)) { (uint64_t)lastChecked + request.ttl >= (uint64_t)time(nullptr)) {
skip = true; skip = true;
result.effectiveUri = request.uri; result.effectiveUri = request.uri;

View file

@ -4,6 +4,7 @@
#include <map> #include <map>
#include <thread> #include <thread>
#include <absl/strings/numbers.h>
#include <dlfcn.h> #include <dlfcn.h>
#include "archive.hh" #include "archive.hh"
@ -163,7 +164,7 @@ void BaseSetting<SandboxMode>::convertToArg(Args& args,
void MaxBuildJobsSetting::set(const std::string& str) { void MaxBuildJobsSetting::set(const std::string& str) {
if (str == "auto") { if (str == "auto") {
value = std::max(1U, std::thread::hardware_concurrency()); value = std::max(1U, std::thread::hardware_concurrency());
} else if (!string2Int(str, value)) { } else if (!absl::SimpleAtoi(str, &value)) {
throw UsageError( throw UsageError(
"configuration setting '%s' should be 'auto' or an integer", name); "configuration setting '%s' should be 'auto' or an integer", name);
} }

View file

@ -7,6 +7,7 @@
#include <ctime> #include <ctime>
#include <iostream> #include <iostream>
#include <absl/strings/numbers.h>
#include <fcntl.h> #include <fcntl.h>
#include <glog/logging.h> #include <glog/logging.h>
#include <grp.h> #include <grp.h>
@ -295,7 +296,7 @@ int LocalStore::getSchema() {
int curSchema = 0; int curSchema = 0;
if (pathExists(schemaPath)) { if (pathExists(schemaPath)) {
std::string s = readFile(schemaPath); std::string s = readFile(schemaPath);
if (!string2Int(s, curSchema)) { if (!absl::SimpleAtoi(s, &curSchema)) {
throw Error(format("'%1%' is corrupt") % schemaPath); throw Error(format("'%1%' is corrupt") % schemaPath);
} }
} }

View file

@ -2,6 +2,8 @@
#include <algorithm> #include <algorithm>
#include <absl/strings/ascii.h>
#include <absl/strings/string_view.h>
#include <glog/logging.h> #include <glog/logging.h>
#include "globals.hh" #include "globals.hh"
@ -48,14 +50,13 @@ bool Machine::mandatoryMet(const std::set<std::string>& features) const {
void parseMachines(const std::string& s, Machines& machines) { void parseMachines(const std::string& s, Machines& machines) {
for (auto line : tokenizeString<std::vector<std::string>>(s, "\n;")) { for (auto line : tokenizeString<std::vector<std::string>>(s, "\n;")) {
trim(line);
line.erase(std::find(line.begin(), line.end(), '#'), line.end()); line.erase(std::find(line.begin(), line.end(), '#'), line.end());
if (line.empty()) { if (line.empty()) {
continue; continue;
} }
if (line[0] == '@') { if (line[0] == '@') {
auto file = trim(std::string(line, 1)); auto file = absl::StripAsciiWhitespace(std::string(line, 1));
try { try {
parseMachines(readFile(file), machines); parseMachines(readFile(file), machines);
} catch (const SysError& e) { } catch (const SysError& e) {

View file

@ -1,5 +1,7 @@
#include "nar-info.hh" #include "nar-info.hh"
#include <absl/strings/numbers.h>
#include "globals.hh" #include "globals.hh"
namespace nix { namespace nix {
@ -47,13 +49,13 @@ NarInfo::NarInfo(const Store& store, const std::string& s,
} else if (name == "FileHash") { } else if (name == "FileHash") {
fileHash = parseHashField(value); fileHash = parseHashField(value);
} else if (name == "FileSize") { } else if (name == "FileSize") {
if (!string2Int(value, fileSize)) { if (!absl::SimpleAtoi(value, &fileSize)) {
corrupt(); corrupt();
} }
} else if (name == "NarHash") { } else if (name == "NarHash") {
narHash = parseHashField(value); narHash = parseHashField(value);
} else if (name == "NarSize") { } else if (name == "NarSize") {
if (!string2Int(value, narSize)) { if (!absl::SimpleAtoi(value, &narSize)) {
corrupt(); corrupt();
} }
} else if (name == "References") { } else if (name == "References") {

View file

@ -3,6 +3,9 @@
#include <cerrno> #include <cerrno>
#include <cstdio> #include <cstdio>
#include <absl/strings/numbers.h>
#include <absl/strings/string_view.h>
#include <absl/strings/strip.h>
#include <glog/logging.h> #include <glog/logging.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
@ -17,24 +20,24 @@ static bool cmpGensByNumber(const Generation& a, const Generation& b) {
return a.number < b.number; return a.number < b.number;
} }
/* Parse a generation name of the format // Parse a generation out of the format
`<profilename>-<number>-link'. */ // `<profilename>-<generation>-link'.
static int parseName(const std::string& profileName, const std::string& name) { static int parseName(absl::string_view profileName, absl::string_view name) {
if (std::string(name, 0, profileName.size() + 1) != profileName + "-") { // Consume the `<profilename>-' prefix and and `-link' suffix.
return -1; if (!(absl::ConsumePrefix(&name, profileName) &&
} absl::ConsumePrefix(&name, "-") &&
std::string s = std::string(name, profileName.size() + 1); absl::ConsumeSuffix(&name, "-link"))) {
std::string::size_type p = s.find("-link");
if (p == std::string::npos) {
return -1; return -1;
} }
int n; int n;
if (string2Int(std::string(s, 0, p), n) && n >= 0) { if (!absl::SimpleAtoi(name, &n) && n < 0) {
return n;
}
return -1; return -1;
} }
return n;
}
Generations findGenerations(const Path& profile, int& curGen) { Generations findGenerations(const Path& profile, int& curGen) {
Generations gens; Generations gens;
@ -218,7 +221,7 @@ void deleteGenerationsOlderThan(const Path& profile,
std::string strDays = std::string(timeSpec, 0, timeSpec.size() - 1); std::string strDays = std::string(timeSpec, 0, timeSpec.size() - 1);
int days; int days;
if (!string2Int(strDays, days) || days < 1) { if (!absl::SimpleAtoi(strDays, &days) || days < 1) {
throw Error(format("invalid number of days specifier '%1%'") % timeSpec); throw Error(format("invalid number of days specifier '%1%'") % timeSpec);
} }

View file

@ -3,6 +3,7 @@
#include <future> #include <future>
#include <utility> #include <utility>
#include <absl/strings/numbers.h>
#include <glog/logging.h> #include <glog/logging.h>
#include "crypto.hh" #include "crypto.hh"
@ -718,7 +719,7 @@ ValidPathInfo decodeValidPathInfo(std::istream& str, bool hashGiven) {
getline(str, s); getline(str, s);
info.narHash = Hash(s, htSHA256); info.narHash = Hash(s, htSHA256);
getline(str, s); getline(str, s);
if (!string2Int(s, info.narSize)) { if (!absl::SimpleAtoi(s, &info.narSize)) {
throw Error("number expected"); throw Error("number expected");
} }
} }
@ -726,7 +727,7 @@ ValidPathInfo decodeValidPathInfo(std::istream& str, bool hashGiven) {
std::string s; std::string s;
int n; int n;
getline(str, s); getline(str, s);
if (!string2Int(s, n)) { if (!absl::SimpleAtoi(s, &n)) {
throw Error("number expected"); throw Error("number expected");
} }
while ((n--) != 0) { while ((n--) != 0) {

View file

@ -4,6 +4,8 @@
#include <map> #include <map>
#include <memory> #include <memory>
#include <absl/strings/numbers.h>
#include "util.hh" #include "util.hh"
namespace nix { namespace nix {
@ -179,7 +181,7 @@ class Args {
.arity(1) .arity(1)
.handler([=](std::vector<std::string> ss) { .handler([=](std::vector<std::string> ss) {
I n; I n;
if (!string2Int(ss[0], n)) if (!absl::SimpleAtoi(ss[0], &n))
throw UsageError("flag '--%s' requires a integer argument", throw UsageError("flag '--%s' requires a integer argument",
longName); longName);
fun(n); fun(n);

View file

@ -3,6 +3,7 @@
#include <utility> #include <utility>
#include <absl/strings/numbers.h>
#include <glog/logging.h> #include <glog/logging.h>
#include "args.hh" #include "args.hh"
@ -214,7 +215,7 @@ std::string BaseSetting<std::string>::to_string() {
template <typename T> template <typename T>
void BaseSetting<T>::set(const std::string& str) { void BaseSetting<T>::set(const std::string& str) {
static_assert(std::is_integral<T>::value, "Integer required."); static_assert(std::is_integral<T>::value, "Integer required.");
if (!string2Int(str, value)) { if (!absl::SimpleAtoi(str, &value)) {
throw UsageError("setting '%s' has invalid value '%s'", name, str); throw UsageError("setting '%s' has invalid value '%s'", name, str);
} }
} }

View file

@ -47,7 +47,7 @@ libutil_dep_list = [
openssl_dep, openssl_dep,
pthread_dep, pthread_dep,
libsodium_dep, libsodium_dep,
] ] + absl_deps
libutil_link_list = [] libutil_link_list = []
libutil_link_args = [] libutil_link_args = []

View file

@ -12,6 +12,7 @@
#include <thread> #include <thread>
#include <utility> #include <utility>
#include <absl/strings/string_view.h>
#include <fcntl.h> #include <fcntl.h>
#include <grp.h> #include <grp.h>
#include <pwd.h> #include <pwd.h>
@ -306,16 +307,17 @@ std::string readFile(int fd) {
return std::string((char*)buf.data(), st.st_size); return std::string((char*)buf.data(), st.st_size);
} }
std::string readFile(const Path& path, bool drain) { std::string readFile(absl::string_view path, bool drain) {
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); AutoCloseFD fd = open(std::string(path).c_str(), O_RDONLY | O_CLOEXEC);
if (!fd) { if (!fd) {
throw SysError(format("opening file '%1%'") % path); throw SysError(format("opening file '%1%'") % path);
} }
return drain ? drainFD(fd.get()) : readFile(fd.get()); return drain ? drainFD(fd.get()) : readFile(fd.get());
} }
void readFile(const Path& path, Sink& sink) { void readFile(absl::string_view path, Sink& sink) {
AutoCloseFD fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); // TODO(tazjin): use stdlib functions for this stuff
AutoCloseFD fd = open(std::string(path).c_str(), O_RDONLY | O_CLOEXEC);
if (!fd) { if (!fd) {
throw SysError("opening file '%s'", path); throw SysError("opening file '%s'", path);
} }
@ -1213,15 +1215,6 @@ std::string concatStringsSep(const std::string& sep, const StringSet& ss) {
return s; return s;
} }
std::string trim(const std::string& s, const std::string& whitespace) {
auto i = s.find_first_not_of(whitespace);
if (i == std::string::npos) {
return "";
}
auto j = s.find_last_not_of(whitespace);
return std::string(s, i, j == std::string::npos ? j : j - i + 1);
}
std::string replaceStrings(const std::string& s, const std::string& from, std::string replaceStrings(const std::string& s, const std::string& from,
const std::string& to) { const std::string& to) {
if (from.empty()) { if (from.empty()) {

View file

@ -8,6 +8,7 @@
#include <optional> #include <optional>
#include <sstream> #include <sstream>
#include <absl/strings/string_view.h>
#include <dirent.h> #include <dirent.h>
#include <signal.h> #include <signal.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -97,8 +98,8 @@ unsigned char getFileType(const Path& path);
/* Read the contents of a file into a string. */ /* Read the contents of a file into a string. */
std::string readFile(int fd); std::string readFile(int fd);
std::string readFile(const Path& path, bool drain = false); std::string readFile(absl::string_view path, bool drain = false);
void readFile(const Path& path, Sink& sink); void readFile(absl::string_view path, Sink& sink);
/* Write a string to a file. */ /* Write a string to a file. */
void writeFile(const Path& path, const std::string& s, mode_t mode = 0666); void writeFile(const Path& path, const std::string& s, mode_t mode = 0666);
@ -325,10 +326,6 @@ C tokenizeString(const std::string& s,
std::string concatStringsSep(const std::string& sep, const Strings& ss); std::string concatStringsSep(const std::string& sep, const Strings& ss);
std::string concatStringsSep(const std::string& sep, const StringSet& ss); std::string concatStringsSep(const std::string& sep, const StringSet& ss);
/* Remove whitespace from the start and end of a string. */
std::string trim(const std::string& s,
const std::string& whitespace = " \n\r\t");
/* Replace all occurrences of a string inside another string. */ /* Replace all occurrences of a string inside another string. */
std::string replaceStrings(const std::string& s, const std::string& from, std::string replaceStrings(const std::string& s, const std::string& from,
const std::string& to); const std::string& to);
@ -339,16 +336,6 @@ std::string statusToString(int status);
bool statusOk(int status); bool statusOk(int status);
/* Parse a string into an integer. */
template <class N>
bool string2Int(const std::string& s, N& n) {
if (std::string(s, 0, 1) == "-" && !std::numeric_limits<N>::is_signed)
return false;
std::istringstream str(s);
str >> n;
return str && str.get() == EOF;
}
/* Parse a string into a float. */ /* Parse a string into a float. */
template <class N> template <class N>
bool string2Float(const std::string& s, N& n) { bool string2Float(const std::string& s, N& n) {

View file

@ -4,6 +4,7 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <absl/strings/numbers.h>
#include <glog/logging.h> #include <glog/logging.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
@ -1303,7 +1304,7 @@ static void opSwitchGeneration(Globals& globals, Strings opFlags,
} }
int dstGen; int dstGen;
if (!string2Int(opArgs.front(), dstGen)) { if (!absl::SimpleAtoi(opArgs.front(), &dstGen)) {
throw UsageError(format("expected a generation number")); throw UsageError(format("expected a generation number"));
} }
@ -1369,7 +1370,7 @@ static void opDeleteGenerations(Globals& globals, Strings opFlags,
} }
std::string str_max = std::string(opArgs.front(), 1, opArgs.front().size()); std::string str_max = std::string(opArgs.front(), 1, opArgs.front().size());
int max; int max;
if (!string2Int(str_max, max) || max == 0) { if (!absl::SimpleAtoi(str_max, &max) || max == 0) {
throw Error(format("invalid number of generations to keep %1%") % throw Error(format("invalid number of generations to keep %1%") %
opArgs.front()); opArgs.front());
} }
@ -1378,7 +1379,7 @@ static void opDeleteGenerations(Globals& globals, Strings opFlags,
std::set<unsigned int> gens; std::set<unsigned int> gens;
for (auto& i : opArgs) { for (auto& i : opArgs) {
unsigned int n; unsigned int n;
if (!string2Int(i, n)) { if (!absl::SimpleAtoi(i, &n)) {
throw UsageError(format("invalid generation number '%1%'") % i); throw UsageError(format("invalid generation number '%1%'") % i);
} }
gens.insert(n); gens.insert(n);