~ubuntu-branches/ubuntu/quantal/config-manager/quantal

« back to all changes in this revision

Viewing changes to cm.cc

  • Committer: Bazaar Package Importer
  • Author(s): Anand Kumria
  • Date: 2004-07-19 22:27:50 UTC
  • mto: (3.1.1 dapper)
  • mto: This revision was merged to the branch mainline in revision 3.
  • Revision ID: james.westby@ubuntu.com-20040719222750-sztqdj1aoj2r6frr
Tags: upstream-0.1p83
ImportĀ upstreamĀ versionĀ 0.1p83

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
 * Copyright (C) 2003  Robert Collins  <robertc@squid-cache.org>
3
 
 * 
4
 
 * 
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.
9
 
 * 
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.
14
 
 * 
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
18
 
 * 
19
 
 * DO NOT ALTER THE NEXT LINE
20
 
 * arch-tag: a0feccb0-784a-4c9e-8a61-41d248fed0c0
21
 
 * 
22
 
 */
23
 
 
24
 
#include "Config.h"
25
 
#include <getopt++/GetOption.h>
26
 
#include <getopt++/StringOption.h>
27
 
#include <getopt++/BoolOption.h>
28
 
#include <cassert>
29
 
#include <sstream>
30
 
#include <fstream>
31
 
#include "ConfigSource.h"
32
 
#include "ConfigArchSource.h"
33
 
#include "FileSystemVisitor.h"
34
 
#include "Directory.h"
35
 
#include "ConfigEntry.h"
36
 
 
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. */
38
 
 
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.");
45
 
using namespace std;
46
 
 
47
 
 
48
 
void
49
 
usage()
50
 
{
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);
57
 
}
58
 
 
59
 
/* TODO: factor a generic version into libgetopt++ */
60
 
 
61
 
class StringCollector : public Option
62
 
{
63
 
 
64
 
public:
65
 
    StringCollector ()
66
 
    {}
67
 
     virtual const std::string shortOption() const
68
 
       {
69
 
         return "";
70
 
       }
71
 
     virtual const std::string longOption() const
72
 
       {
73
 
         return "";
74
 
       }
75
 
     virtual const std::string shortHelp() const
76
 
       {
77
 
         return "";
78
 
       }
79
 
     virtual Option::Argument argument() const
80
 
       {
81
 
         return Required;
82
 
       }
83
 
 
84
 
     virtual Option::Result Process(const char * value)
85
 
    {
86
 
            strings.push_back(value);
87
 
        return Ok;
88
 
    }
89
 
 
90
 
    string operator () (int offset)
91
 
    {
92
 
        return strings[offset - 1];
93
 
    }
94
 
 
95
 
    size_t size() const
96
 
    {
97
 
        return strings.size();
98
 
    }
99
 
 
100
 
private:
101
 
    vector<string> strings;
102
 
}
103
 
;
104
 
class CommandCollector : public Option
105
 
{
106
 
 
107
 
public:
108
 
  CommandCollector () {}
109
 
  virtual const std::string shortOption() const
110
 
    {
111
 
      return "";
112
 
    }
113
 
  virtual const std::string longOption() const
114
 
    {
115
 
      return "";
116
 
    }
117
 
  virtual const std::string shortHelp() const
118
 
    {
119
 
      return "";
120
 
    }
121
 
  virtual Option::Argument argument() const
122
 
    {
123
 
      return Required;
124
 
    }
125
 
  
126
 
  virtual Option::Result Process(const char * value)
127
 
    {
128
 
      if (command.size() > 0)
129
 
          return Failed;
130
 
      command = value;
131
 
      return Stop;
132
 
    }
133
 
  
134
 
  string operator () ()
135
 
    {
136
 
      return command;
137
 
    }
138
 
  
139
 
private:
140
 
    std::string command;
141
 
};
142
 
 
143
 
istream &
144
 
getStream(StringCollector &unnamedparams)
145
 
{
146
 
  const char *filename;
147
 
  char tempfile[] = "/tmp/cm.XXXXXX";
148
 
  int filemade=0;
149
 
 
150
 
  if (unnamedparams.size() == 0)
151
 
    return cin;
152
 
 
153
 
  filename = unnamedparams(1).c_str();
154
 
  if (unnamedparams(1).find("http://") == 0 || unnamedparams(1).find("ftp://") == 0)
155
 
  {
156
 
    int handle = mkstemp(tempfile);
157
 
    close (handle);
158
 
    
159
 
    string wgetcmd = string("wget -O ") + tempfile + " " + filename;
160
 
    system(wgetcmd.c_str());
161
 
 
162
 
    filename = tempfile;
163
 
    filemade=1;
164
 
  }
165
 
  
166
 
  static ifstream input (filename, ifstream::in);
167
 
  
168
 
  if (!input.good())
169
 
    throw new runtime_error ("Could not open " + unnamedparams(1) + " for reading");
170
 
 
171
 
  if (filemade)
172
 
    unlink(tempfile);
173
 
 
174
 
  return input;
175
 
 
176
 
}
177
 
 
178
 
int
179
 
main (int argc, char ** argv)
180
 
{
181
 
    StringCollector unnamedparams;
182
 
    CommandCollector theCommand;
183
 
 
184
 
    if (argc == 1 || !GetOption::GetInstance().Process (argc, argv, &theCommand)) {
185
 
        // || unnamedparams.size() < 1) {
186
 
        usage();
187
 
        return 1;
188
 
    }
189
 
 
190
 
    if (!GetOption::GetInstance().Process (GetOption::GetInstance().remainingArgv(), &unnamedparams))
191
 
      {
192
 
        usage();
193
 
        return 1;
194
 
      }
195
 
 
196
 
    if (verboseOption)
197
 
        cout << "Output files will be placed in : '" << (string)outputOption << "'" << endl;
198
 
 
199
 
    int result = 0;
200
 
 
201
 
    try {
202
 
        Config config(getStream(unnamedparams));
203
 
        /* need to refactor the creation - duplicate test here */
204
 
        ConfigSource::Verbose(verboseOption);
205
 
        config.verbose(verboseOption);
206
 
 
207
 
        if (unnamedparams.size() > 0)
208
 
            config.name(unnamedparams(1));
209
 
 
210
 
        if (((string)configSourceOption).size())
211
 
            config.setConfigSource(ConfigSource::Create(configSourceOption));
212
 
        else
213
 
            config.setConfigSource(new ConfigArchSource(""));
214
 
 
215
 
        if (theCommand() == "build")
216
 
            config.build((string)outputOption);
217
 
        else if (theCommand() == "cat")
218
 
            config.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);
227
 
        else {
228
 
            usage();
229
 
            return 1;
230
 
        }
231
 
 
232
 
        if (releaseOption)
233
 
            config.makeReleaseId(outputOption);
234
 
    } catch (exception *e) {
235
 
        cerr << "Fatal error: '" << e->what() << "'" << endl;
236
 
    }
237
 
 
238
 
    return result;
239
 
}
240
 
 
241
 
Config::Config (istream &aSource) : source (aSource), theSource(NULL), _verbose(false)
242
 
{}
243
 
 
244
 
void
245
 
Config::verbose(bool const &aBool)
246
 
{
247
 
    _verbose = aBool;
248
 
}
249
 
 
250
 
bool
251
 
Config::verbose() const
252
 
{
253
 
    return _verbose;
254
 
}
255
 
 
256
 
string
257
 
getLineFromComment(istream &source)
258
 
{
259
 
    char c;
260
 
    string result;
261
 
 
262
 
    while (source.good()) {
263
 
        source.get(c);
264
 
 
265
 
        if (c == '\n')
266
 
            return result ;
267
 
 
268
 
        result += c;
269
 
    }
270
 
}
271
 
 
272
 
struct BuildConfigEntry : public unary_function<void, ConfigEntry &>
273
 
{
274
 
    BuildConfigEntry(ostream &aStream, string const &aRelPath) : out (aStream), relPath(aRelPath)
275
 
    {}
276
 
 
277
 
    void operator() (ConfigEntry &anEntry)
278
 
    {
279
 
        while (anEntry.path().size() && nested.size() && 
280
 
               anEntry.path().find(nested.back()->path()) != 0)
281
 
            /* this is not a sub tree */
282
 
            nested.pop_back();
283
 
        out << "Building " << anEntry.source()->url(false) << " in ";
284
 
        out << relPath + "/" + anEntry.path();
285
 
        if (nested.size())
286
 
            out << " with parent " << nested.back()->path();
287
 
        out << endl;
288
 
 
289
 
        string parentPath = Path(relPath + "/" + anEntry.path()).dirName();
290
 
        if (!Directory::Exists(parentPath))
291
 
            Directory::Create(parentPath);
292
 
            
293
 
        anEntry.source()->get (relPath + "/" + anEntry.path());
294
 
        if (nested.size())
295
 
            nested.back()->source()->ignore(anEntry.source(), relPath + "/" + anEntry.path());
296
 
        nested.push_back(&anEntry);
297
 
    }
298
 
 
299
 
    ostream &out;
300
 
    string const &relPath;
301
 
    vector<ConfigEntry *> nested;
302
 
};
303
 
 
304
 
void
305
 
Config::build (string const &where) throw(exception *)
306
 
{
307
 
    parse(source);
308
 
    for_each(entries.begin(), entries.end(), BuildConfigEntry(cout, where));
309
 
}
310
 
 
311
 
 
312
 
/* bah uglification. tokenising and parsing mixed. bah. bah. */
313
 
string
314
 
Config::getToken(bool const &required) throw(exception *)
315
 
{
316
 
    while (source.good()) {
317
 
        string token;
318
 
        source >> token;
319
 
 
320
 
        if (token.size() != 0) {
321
 
            if (token[0] != '#')
322
 
                return token;
323
 
            else {
324
 
                string candidate = getLineFromComment(source);
325
 
                string::size_type position = candidate.find("config-name:");
326
 
 
327
 
                if (position != string::npos)
328
 
                    name(candidate.substr(position + 12));
329
 
            }
330
 
 
331
 
        }
332
 
    }
333
 
 
334
 
    if (!required)
335
 
        return string();
336
 
 
337
 
    throw new runtime_error("Could not get next token from config file: invalid config file");
338
 
}
339
 
 
340
 
string
341
 
Config::normalise(string const &aPath)
342
 
{
343
 
    string::size_type pos = aPath.find_first_not_of("./");
344
 
 
345
 
    if (pos != string::npos)
346
 
        return aPath.substr(pos);
347
 
 
348
 
    /* If solely composed of . and /, return an empty string */
349
 
    if (aPath.find_first_of("./") != string::npos)
350
 
      return string();
351
 
    return aPath;
352
 
}
353
 
 
354
 
void
355
 
Config::parse(istream &source) throw(exception *)
356
 
{
357
 
    while (source.good()) {
358
 
        string path = getToken(false);
359
 
 
360
 
        if (path.size() == 0)
361
 
            return ;
362
 
 
363
 
        string source = getToken(true);
364
 
 
365
 
        if (verbose())
366
 
            cout << "Found config line path:'" << path << "'" <<
367
 
            " source url:'" << source << "'" << endl;
368
 
 
369
 
        entries.push_back(ConfigEntry(normalise(path), ConfigSource::Create(source)));
370
 
    }
371
 
}
372
 
 
373
 
struct DumpConfigEntry : public unary_function<void, ConfigEntry &>
374
 
{
375
 
    DumpConfigEntry(ostream &aStream, bool const &aBool, bool const &anotherBool) : out (aStream), pathsOnly (aBool), stripSchemes(anotherBool)
376
 
    {}
377
 
 
378
 
    void operator() (ConfigEntry &anEntry)
379
 
    {
380
 
      /* insert ./ for root nodes */
381
 
      if (anEntry.path().size())
382
 
          out << anEntry.path();
383
 
      else
384
 
          out << ".";
385
 
 
386
 
        if (!pathsOnly)
387
 
            out << " " << anEntry.source()->url(stripSchemes);
388
 
 
389
 
        out << endl;
390
 
    }
391
 
    
392
 
    ostream &out;
393
 
    bool const pathsOnly;
394
 
    bool const stripSchemes;
395
 
};
396
 
 
397
 
void
398
 
Config::cat () throw (exception *)
399
 
{
400
 
    parse(source);
401
 
    for_each(entries.begin(), entries.end(), DumpConfigEntry(cout, pathsOption, noSchemeOption));
402
 
}
403
 
 
404
 
struct UpdateConfigEntry : public unary_function<void, ConfigEntry &>
405
 
{
406
 
    UpdateConfigEntry(ostream &aStream, string const &aRelPath) : out (aStream), relPath(aRelPath)
407
 
    {}
408
 
 
409
 
    void operator() (ConfigEntry &anEntry)
410
 
    {
411
 
        out << "Updating " << anEntry.source()->url(false) << " in ";
412
 
        out << relPath + "/" + anEntry.path() << endl;
413
 
 
414
 
        anEntry.source()->update (relPath + "/" + anEntry.path());
415
 
    }
416
 
 
417
 
    ostream &out;
418
 
    string const &relPath;
419
 
};
420
 
 
421
 
void
422
 
Config::update (string const &where) throw (exception *)
423
 
{
424
 
    parse(source);
425
 
    for_each(entries.begin(), entries.end(), UpdateConfigEntry(cout, where));
426
 
}
427
 
 
428
 
struct ChangesConfigEntry : public unary_function<void, ConfigEntry &>
429
 
{
430
 
    ChangesConfigEntry(ostream &aStream, string const &aRelPath) : out (aStream), relPath(aRelPath), result(0)
431
 
    {}
432
 
 
433
 
    void operator() (ConfigEntry &anEntry)
434
 
    {
435
 
        if (verboseOption)
436
 
          {
437
 
            out << "Getting changes for " << anEntry.source()->url(false) << " in ";
438
 
            out << relPath + "/" + anEntry.path() << endl;
439
 
          }
440
 
        int holding = anEntry.source()->changes (relPath + "/" + anEntry.path());
441
 
        if (holding > result)
442
 
            result = holding;
443
 
    }
444
 
 
445
 
    ostream &out;
446
 
    string const &relPath;
447
 
    int result;
448
 
};
449
 
 
450
 
int
451
 
Config::changes (string const &where) throw (exception *)
452
 
{
453
 
    parse(source);
454
 
    ChangesConfigEntry result = for_each(entries.begin(), entries.end(), ChangesConfigEntry(cout, where));
455
 
    return result.result;
456
 
}
457
 
 
458
 
struct MissingConfigEntry : public unary_function<void, ConfigEntry &>
459
 
{
460
 
    MissingConfigEntry(ostream &aStream, string const &aRelPath) : out (aStream), relPath(aRelPath), result(0)
461
 
    {}
462
 
 
463
 
    void operator() (ConfigEntry &anEntry)
464
 
    {
465
 
        if (verboseOption)
466
 
          {
467
 
            out << "Getting missing patches for " << anEntry.source()->url(false) << " in ";
468
 
            out << relPath + "/" + anEntry.path() << endl;
469
 
          }
470
 
        int holding = anEntry.source()->missing (relPath + "/" + anEntry.path());
471
 
        if (holding > result)
472
 
            result = holding;
473
 
    }
474
 
 
475
 
    ostream &out;
476
 
    string const &relPath;
477
 
    int result;
478
 
};
479
 
 
480
 
int
481
 
Config::missing (string const &where) throw (exception *)
482
 
{
483
 
    parse(source);
484
 
    MissingConfigEntry result = for_each(entries.begin(), entries.end(), MissingConfigEntry(cout, where));
485
 
    return result.result;
486
 
}
487
 
 
488
 
class Examiner : public FileSystemVisitor
489
 
{
490
 
  public:
491
 
    Examiner(Config &aConfig) : config(aConfig)
492
 
      {
493
 
      }
494
 
  virtual bool visitDirectory(Directory &aDir)
495
 
    {
496
 
      if (verboseOption)
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 */
501
 
          nested.pop_back();
502
 
      if (!config.entries.size())
503
 
        {
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() + ").");
508
 
          newEntry = true;
509
 
        }
510
 
      else
511
 
        {
512
 
          ConfigSource *newSource = ConfigSource::Create(aDir.path(),nested.back().source());
513
 
          if (newSource)
514
 
            {
515
 
              config.entries.push_back(ConfigEntry(aDir.path().fullName(), newSource));
516
 
              newEntry = true;
517
 
            }
518
 
        }
519
 
      if (newEntry)
520
 
          nested.push_back(config.entries.back());
521
 
      return true;
522
 
    }
523
 
    Config &config;
524
 
    vector<ConfigEntry> nested;
525
 
};
526
 
 
527
 
class NormaliseConfigEntry : public unary_function<void, ConfigEntry &>
528
 
{
529
 
  public:
530
 
    NormaliseConfigEntry (string where)
531
 
      {
532
 
        path = Path(where).fullName();
533
 
      }
534
 
    void operator() (ConfigEntry &anEntry)
535
 
    {
536
 
      anEntry.path("." + anEntry.path().substr(path.size()));
537
 
    }
538
 
    string path;
539
 
};
540
 
 
541
 
void
542
 
Config::examine (string const &where) throw (exception *)
543
 
{
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));
549
 
}
550
 
 
551
 
void
552
 
Config::name(string const &aName) throw (exception *)
553
 
{
554
 
    if (theName.size() == 0)
555
 
        theName = aName;
556
 
}
557
 
 
558
 
void
559
 
Config::setConfigSource(ConfigSource *aSource) throw(exception *)
560
 
{
561
 
    if (!theSource)
562
 
        theSource = aSource;
563
 
}
564
 
 
565
 
 
566
 
struct GenReleaseLines : public unary_function<void, ConfigEntry &>
567
 
{
568
 
    GenReleaseLines(ostream &aStream, string const &aRelPath) : output(aStream), where(aRelPath)
569
 
    {}
570
 
 
571
 
    void operator() (ConfigEntry &entry)
572
 
    {
573
 
        output << entry.path() << "\t" << entry.source()->treeVersion(where + "/" + entry.path()) << endl;
574
 
    }
575
 
 
576
 
private:
577
 
    ostream &output;
578
 
    string const &where;
579
 
 
580
 
};
581
 
 
582
 
void
583
 
Config::makeReleaseId(string const &where) throw (exception *)
584
 
{
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;
591
 
 
592
 
    if (theSource)
593
 
        output << theSource->treeVersion(where);
594
 
 
595
 
    output << "(" << theName << ")" << endl;
596
 
 
597
 
    output << endl;
598
 
 
599
 
    for_each(entries.begin(), entries.end(), GenReleaseLines(output, where));
600
 
 
601
 
    /*  generate config name (using '-' if stdin was used and no name detected)
602
 
        output tree canonical precise id's from configentries
603
 
    */
604
 
}