2
* Copyright (C) 2003 Robert Collins <robertc@squid-cache.org>
5
* This program is free software; you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation; either version 2 of the License, or
8
* (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
* DO NOT ALTER THE NEXT LINE
20
* arch-tag: a0feccb0-784a-4c9e-8a61-41d248fed0c0
25
#include <getopt++/GetOption.h>
26
#include <getopt++/StringOption.h>
27
#include <getopt++/BoolOption.h>
31
#include "ConfigSource.h"
32
#include "ConfigArchSource.h"
33
#include "FileSystemVisitor.h"
34
#include "Directory.h"
35
#include "ConfigEntry.h"
37
/* TODO: generate separate OptionSets for the commands, and check parameter 2 for which option set to process - i.e. use the system. XXX This requires removing the use of getopt from libgetopt++, as we need more flexability that it offers. */
39
static BoolOption verboseOption(false, 'v', "verbose", "be verbose about operation.");
40
static StringOption outputOption(".", 'd', "dir", "location to output built tree in.");
41
static StringOption configSourceOption("",'s',"source", "The RCS source for the config itself");
42
static BoolOption pathsOption(false, 'p', "paths", "only list the paths from the config.");
43
static BoolOption releaseOption(false, 'r', "release-id", "Generate a ./=RELEASE-ID for this config.");
44
static BoolOption noSchemeOption(false, 'n', "no-url-schemes", "Strip url schemes from project locations.");
51
cout << "cm: config manager" << endl;
52
cout << "usage:" << endl;
53
cout << "cm [options] command config-file" << endl;
54
cout << "commands: build cat update changes missing examine" << endl;
55
cout << "valid options: " << endl;
56
GetOption::GetInstance().ParameterUsage (cout);
59
/* TODO: factor a generic version into libgetopt++ */
61
class StringCollector : public Option
67
virtual const std::string shortOption() const
71
virtual const std::string longOption() const
75
virtual const std::string shortHelp() const
79
virtual Option::Argument argument() const
84
virtual Option::Result Process(const char * value)
86
strings.push_back(value);
90
string operator () (int offset)
92
return strings[offset - 1];
97
return strings.size();
101
vector<string> strings;
104
class CommandCollector : public Option
108
CommandCollector () {}
109
virtual const std::string shortOption() const
113
virtual const std::string longOption() const
117
virtual const std::string shortHelp() const
121
virtual Option::Argument argument() const
126
virtual Option::Result Process(const char * value)
128
if (command.size() > 0)
134
string operator () ()
144
getStream(StringCollector &unnamedparams)
146
const char *filename;
147
char tempfile[] = "/tmp/cm.XXXXXX";
150
if (unnamedparams.size() == 0)
153
filename = unnamedparams(1).c_str();
154
if (unnamedparams(1).find("http://") == 0 || unnamedparams(1).find("ftp://") == 0)
156
int handle = mkstemp(tempfile);
159
string wgetcmd = string("wget -O ") + tempfile + " " + filename;
160
system(wgetcmd.c_str());
166
static ifstream input (filename, ifstream::in);
169
throw new runtime_error ("Could not open " + unnamedparams(1) + " for reading");
179
main (int argc, char ** argv)
181
StringCollector unnamedparams;
182
CommandCollector theCommand;
184
if (argc == 1 || !GetOption::GetInstance().Process (argc, argv, &theCommand)) {
185
// || unnamedparams.size() < 1) {
190
if (!GetOption::GetInstance().Process (GetOption::GetInstance().remainingArgv(), &unnamedparams))
197
cout << "Output files will be placed in : '" << (string)outputOption << "'" << endl;
202
Config config(getStream(unnamedparams));
203
/* need to refactor the creation - duplicate test here */
204
ConfigSource::Verbose(verboseOption);
205
config.verbose(verboseOption);
207
if (unnamedparams.size() > 0)
208
config.name(unnamedparams(1));
210
if (((string)configSourceOption).size())
211
config.setConfigSource(ConfigSource::Create(configSourceOption));
213
config.setConfigSource(new ConfigArchSource(""));
215
if (theCommand() == "build")
216
config.build((string)outputOption);
217
else if (theCommand() == "cat")
219
else if (theCommand() == "update")
220
config.update((string)outputOption);
221
else if (theCommand() == "changes")
222
result = config.changes((string)outputOption);
223
else if (theCommand() == "missing")
224
result = config.missing((string)outputOption);
225
else if (theCommand() == "examine")
226
config.examine((string)outputOption);
233
config.makeReleaseId(outputOption);
234
} catch (exception *e) {
235
cerr << "Fatal error: '" << e->what() << "'" << endl;
241
Config::Config (istream &aSource) : source (aSource), theSource(NULL), _verbose(false)
245
Config::verbose(bool const &aBool)
251
Config::verbose() const
257
getLineFromComment(istream &source)
262
while (source.good()) {
272
struct BuildConfigEntry : public unary_function<void, ConfigEntry &>
274
BuildConfigEntry(ostream &aStream, string const &aRelPath) : out (aStream), relPath(aRelPath)
277
void operator() (ConfigEntry &anEntry)
279
while (anEntry.path().size() && nested.size() &&
280
anEntry.path().find(nested.back()->path()) != 0)
281
/* this is not a sub tree */
283
out << "Building " << anEntry.source()->url(false) << " in ";
284
out << relPath + "/" + anEntry.path();
286
out << " with parent " << nested.back()->path();
289
string parentPath = Path(relPath + "/" + anEntry.path()).dirName();
290
if (!Directory::Exists(parentPath))
291
Directory::Create(parentPath);
293
anEntry.source()->get (relPath + "/" + anEntry.path());
295
nested.back()->source()->ignore(anEntry.source(), relPath + "/" + anEntry.path());
296
nested.push_back(&anEntry);
300
string const &relPath;
301
vector<ConfigEntry *> nested;
305
Config::build (string const &where) throw(exception *)
308
for_each(entries.begin(), entries.end(), BuildConfigEntry(cout, where));
312
/* bah uglification. tokenising and parsing mixed. bah. bah. */
314
Config::getToken(bool const &required) throw(exception *)
316
while (source.good()) {
320
if (token.size() != 0) {
324
string candidate = getLineFromComment(source);
325
string::size_type position = candidate.find("config-name:");
327
if (position != string::npos)
328
name(candidate.substr(position + 12));
337
throw new runtime_error("Could not get next token from config file: invalid config file");
341
Config::normalise(string const &aPath)
343
string::size_type pos = aPath.find_first_not_of("./");
345
if (pos != string::npos)
346
return aPath.substr(pos);
348
/* If solely composed of . and /, return an empty string */
349
if (aPath.find_first_of("./") != string::npos)
355
Config::parse(istream &source) throw(exception *)
357
while (source.good()) {
358
string path = getToken(false);
360
if (path.size() == 0)
363
string source = getToken(true);
366
cout << "Found config line path:'" << path << "'" <<
367
" source url:'" << source << "'" << endl;
369
entries.push_back(ConfigEntry(normalise(path), ConfigSource::Create(source)));
373
struct DumpConfigEntry : public unary_function<void, ConfigEntry &>
375
DumpConfigEntry(ostream &aStream, bool const &aBool, bool const &anotherBool) : out (aStream), pathsOnly (aBool), stripSchemes(anotherBool)
378
void operator() (ConfigEntry &anEntry)
380
/* insert ./ for root nodes */
381
if (anEntry.path().size())
382
out << anEntry.path();
387
out << " " << anEntry.source()->url(stripSchemes);
393
bool const pathsOnly;
394
bool const stripSchemes;
398
Config::cat () throw (exception *)
401
for_each(entries.begin(), entries.end(), DumpConfigEntry(cout, pathsOption, noSchemeOption));
404
struct UpdateConfigEntry : public unary_function<void, ConfigEntry &>
406
UpdateConfigEntry(ostream &aStream, string const &aRelPath) : out (aStream), relPath(aRelPath)
409
void operator() (ConfigEntry &anEntry)
411
out << "Updating " << anEntry.source()->url(false) << " in ";
412
out << relPath + "/" + anEntry.path() << endl;
414
anEntry.source()->update (relPath + "/" + anEntry.path());
418
string const &relPath;
422
Config::update (string const &where) throw (exception *)
425
for_each(entries.begin(), entries.end(), UpdateConfigEntry(cout, where));
428
struct ChangesConfigEntry : public unary_function<void, ConfigEntry &>
430
ChangesConfigEntry(ostream &aStream, string const &aRelPath) : out (aStream), relPath(aRelPath), result(0)
433
void operator() (ConfigEntry &anEntry)
437
out << "Getting changes for " << anEntry.source()->url(false) << " in ";
438
out << relPath + "/" + anEntry.path() << endl;
440
int holding = anEntry.source()->changes (relPath + "/" + anEntry.path());
441
if (holding > result)
446
string const &relPath;
451
Config::changes (string const &where) throw (exception *)
454
ChangesConfigEntry result = for_each(entries.begin(), entries.end(), ChangesConfigEntry(cout, where));
455
return result.result;
458
struct MissingConfigEntry : public unary_function<void, ConfigEntry &>
460
MissingConfigEntry(ostream &aStream, string const &aRelPath) : out (aStream), relPath(aRelPath), result(0)
463
void operator() (ConfigEntry &anEntry)
467
out << "Getting missing patches for " << anEntry.source()->url(false) << " in ";
468
out << relPath + "/" + anEntry.path() << endl;
470
int holding = anEntry.source()->missing (relPath + "/" + anEntry.path());
471
if (holding > result)
476
string const &relPath;
481
Config::missing (string const &where) throw (exception *)
484
MissingConfigEntry result = for_each(entries.begin(), entries.end(), MissingConfigEntry(cout, where));
485
return result.result;
488
class Examiner : public FileSystemVisitor
491
Examiner(Config &aConfig) : config(aConfig)
494
virtual bool visitDirectory(Directory &aDir)
497
std::cout << aDir.path().fullName() << endl;
498
bool newEntry (false);
499
while (nested.size() && aDir.path().fullName().find(nested.back().path()) != 0)
500
/* this is not a sub tree */
502
if (!config.entries.size())
504
config.entries.push_back(ConfigEntry(aDir.path().fullName(),
505
ConfigSource::Create(aDir.path(),NULL)));
506
if (!config.entries.back().source())
507
throw new runtime_error("Could not identify the RCS type for the supplied path (" + aDir.path().fullName() + ").");
512
ConfigSource *newSource = ConfigSource::Create(aDir.path(),nested.back().source());
515
config.entries.push_back(ConfigEntry(aDir.path().fullName(), newSource));
520
nested.push_back(config.entries.back());
524
vector<ConfigEntry> nested;
527
class NormaliseConfigEntry : public unary_function<void, ConfigEntry &>
530
NormaliseConfigEntry (string where)
532
path = Path(where).fullName();
534
void operator() (ConfigEntry &anEntry)
536
anEntry.path("." + anEntry.path().substr(path.size()));
542
Config::examine (string const &where) throw (exception *)
544
/* visit the tree starting at where */
545
Examiner examiner(*this);
546
Directory(where).visit(examiner);
547
for_each(entries.begin(), entries.end(), NormaliseConfigEntry(where));
548
for_each(entries.begin(), entries.end(), DumpConfigEntry(cout, false, false));
552
Config::name(string const &aName) throw (exception *)
554
if (theName.size() == 0)
559
Config::setConfigSource(ConfigSource *aSource) throw(exception *)
566
struct GenReleaseLines : public unary_function<void, ConfigEntry &>
568
GenReleaseLines(ostream &aStream, string const &aRelPath) : output(aStream), where(aRelPath)
571
void operator() (ConfigEntry &entry)
573
output << entry.path() << "\t" << entry.source()->treeVersion(where + "/" + entry.path()) << endl;
583
Config::makeReleaseId(string const &where) throw (exception *)
585
if (unlink((where + "/=RELEASE-ID").c_str()) && errno != ENOENT)
586
throw new runtime_error ("Could not unlink " + where + "/=RELEASE-ID.");
587
ofstream output((where + "/=RELEASE-ID").c_str() , ios::trunc);
588
output << "# automatically generated release id (by cm)" << endl;
589
output << "#" << endl;
590
string configRelease;
593
output << theSource->treeVersion(where);
595
output << "(" << theName << ")" << endl;
599
for_each(entries.begin(), entries.end(), GenReleaseLines(output, where));
601
/* generate config name (using '-' if stdin was used and no name detected)
602
output tree canonical precise id's from configentries