source: trunk/lib/Configuration.cc @ 522

Last change on this file since 522 was 522, checked in by Peter Johansson, 14 years ago

improved error message when config file is invalid

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 14.1 KB
Line 
1// $Id: Configuration.cc 522 2007-12-25 00:51:27Z peter $
2
3/*
4  Copyright (C) 2007 Peter Johansson
5
6  This file is part of svndigest, http://trac.thep.lu.se/svndigest
7
8  svndigest is free software; you can redistribute it and/or modify it
9  under the terms of the GNU General Public License as published by
10  the Free Software Foundation; either version 2 of the License, or
11  (at your option) any later version.
12
13  svndigest is distributed in the hope that it will be useful, but
14  WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  General Public License for more details.
17
18  You should have received a copy of the GNU General Public License
19  along with this program; if not, write to the Free Software
20  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
21  02111-1307, USA.
22*/
23
24#include "Configuration.h"
25
26#include "Functor.h"
27
28#include <cassert>
29#include <fstream>
30#include <map>
31#include <string>
32#include <sstream>
33#include <stdexcept>
34#include <utility>
35
36namespace theplu{
37namespace svndigest{
38
39  Configuration* Configuration::instance_=NULL;
40
41
42  Configuration::Configuration(void)
43  {
44  }
45
46
47  void Configuration::add_codon(std::string key, std::string start, 
48                                std::string end)
49  {
50    std::pair<std::string, std::string> p(start,end);
51    String2Codons::iterator iter = string2codons_.end();
52    for (String2Codons::iterator i=string2codons_.begin();
53         i!=string2codons_.end(); ++i)
54      if (i->first == key)
55        iter = i;
56   
57    if (iter==string2codons_.end())
58      string2codons_.push_back(std::make_pair(key, VectorPair(1,p)));
59    else
60      iter->second.push_back(p);
61  }
62
63
64  const std::vector<std::pair<std::string, std::string> >* 
65  Configuration::codon(std::string file_name) const 
66  {
67    if (const std::pair<std::string,std::string>* dict=dictionary(file_name))
68      try {
69        file_name = translate(file_name, *dict);
70      }
71      catch (std::runtime_error& e) {
72        std::stringstream mess;
73        mess << "svndigest: invalid config file: "
74             << "expression " << dict->second << " is invalid";
75        if (e.what()[0])
76          mess << "because " << e.what() << " is a too large.";
77        else
78          mess << ".";
79        throw std::runtime_error(mess.str());
80      }
81    for (String2Codons::const_iterator i(string2codons_.begin());
82         i!=string2codons_.end(); ++i) {
83      if (svndigest::equal(file_name.begin(), file_name.end(), 
84                           i->first.begin(), i->first.end()) ) {
85        return &i->second;
86      }
87    }
88    return NULL;
89  }
90
91
92  const std::map<std::string,Alias>& Configuration::copyright_alias(void) const
93  {
94    return copyright_alias_;
95  }
96
97
98  const std::pair<std::string,std::string>* 
99  Configuration::dictionary(std::string lhs) const
100  {
101    for (size_t i=0; i<dictionary_.size(); ++i)
102      if (svndigest::equal(lhs.begin(), lhs.end(), 
103                           dictionary_[i].first.begin(),
104                           dictionary_[i].first.end()))
105        return &dictionary_[i];
106    return NULL;
107  }
108
109
110  bool Configuration::equal_false(const std::string& str) const
111  {
112    return str=="false" || str=="False" || str=="FALSE" ||
113      str=="no" || str=="No" || str=="NO";
114  }
115
116
117  bool Configuration::equal_true(const std::string& str) const
118  {
119    return str=="true" || str=="True" || str=="TRUE" ||
120      str=="yes" || str=="Yes" || str=="YES";
121  }
122
123
124  void Configuration::load(void)
125  {
126    set_default();
127  }
128
129
130  void Configuration::load(std::istream& is)
131  {
132    assert(is.good());
133    set_default();
134
135    bool parsing_found=false;
136    bool dictionary_found=false;
137    std::string line;
138    std::string section;
139    std::string tmp;
140    while (getline(is, line)) {
141      line = ltrim(line);
142      if (line.empty() || line[0]=='#')
143        continue;
144      std::stringstream ss(line);
145      if (line[0] == '[') {
146        getline(ss, tmp, '[');
147        getline(ss, section, ']');
148        continue;
149      }
150      std::string lhs;
151      getline(ss, lhs, '=');
152      lhs = trim(lhs);
153      std::string rhs;
154      getline(ss, rhs);
155      rhs = trim(rhs);
156      if (rhs.empty()){
157        throw Config_error(line, "expected format: <lhs> = <rhs>");
158      }
159      if (section == "copyright-alias"){
160        std::map<std::string,Alias>::iterator iter = 
161          copyright_alias_.lower_bound(lhs);
162        if (iter!=copyright_alias_.end() && iter->first==lhs){
163          std::stringstream mess;
164          mess << "in copright-alias section " << lhs << " defined twice.";
165          throw Config_error(line, mess.str());
166        }
167       
168        // insert alias
169        copyright_alias_.insert(iter,std::make_pair(lhs, Alias(rhs,copyright_alias_.size())));
170      }
171      else if (section == "trac"){
172        if (lhs=="trac-root")
173          trac_root_=rhs;
174        else {
175          std::stringstream mess;
176          mess << "in trac section" << lhs + " is invalid option.";
177          throw Config_error(line, mess.str());
178        }
179      }
180      else if (section == "copyright") {
181        if (lhs=="missing-copyright-warning")
182          if (equal_false(rhs))
183            missing_copyright_warning_ = false;
184          else if (equal_true(rhs))
185            missing_copyright_warning_ = true;
186          else {
187            throw Config_error(line, "");
188          }
189      }
190      else if (section == "parsing-codons") {
191        if (!parsing_found) {
192          parsing_found=true;
193          // clearing the default setting
194          string2codons_.clear();
195        }
196       
197        if (codon(lhs)) {
198          std::stringstream mess;
199          mess << "clashes with previous given file name pattern: ";
200          // find previous file-name-pattern
201          for (String2Codons::const_iterator i(string2codons_.begin());
202               i!=string2codons_.end(); ++i) {
203            if (svndigest::equal(lhs.begin(), lhs.end(), 
204                                 i->first.begin(), i->first.end()) ) {
205              mess << "`" << i->first << "'";
206              break;
207            }
208          }
209          throw Config_error(line, mess.str());
210        }
211        std::stringstream ss(rhs);
212        std::string start;
213        while (getline(ss, start, ':')) {
214          start = trim(start);
215          std::string end;
216          getline(ss, end, ';');
217          end = trim(end);
218          if (start.empty() && end.empty())
219            continue;
220          try {
221            if (start.empty() || start=="\"\"") {
222              throw std::runtime_error("start-code is empty");
223            }
224            else if (start.size()<3) {
225              std::stringstream mess;
226              mess << "start-code `" << start << "' is invalid";
227              throw std::runtime_error(mess.str());
228            }
229            start = trim(start, '"');
230            if (end.empty() || end=="\"\"") {
231              throw std::runtime_error("end-code is empty");
232            }
233            else if (end.size()<3) {
234              std::stringstream mess;
235              mess << "end-code `" << end << "' is invalid";
236              throw std::runtime_error(mess.str());
237            }
238            end = trim(end, '"');
239          }
240          catch (std::runtime_error& e){
241            throw Config_error(line, e.what());
242          }
243          replace(start, "\\n", "\n");
244          replace(end, "\\n", "\n");
245          add_codon(lhs, start, end);
246        }
247      } 
248      else if (section == "file-name-dictionary") {
249        if (!dictionary_found) {
250          dictionary_found=true;
251          // clearing the default setting
252          dictionary_.clear();
253        }
254       
255        if (const std::pair<std::string, std::string>* entry=dictionary(lhs)) {
256          std::stringstream mess;
257          mess << "clashes with previous given file name pattern: "
258               << "`" << entry->first << "'";
259          throw Config_error(line, mess.str());
260        }
261        lhs = trim(lhs);
262        rhs = trim(rhs);
263        if (!lhs.empty() && !rhs.empty()) 
264          dictionary_.push_back(std::make_pair(lhs, rhs));
265        else if (!lhs.empty() || !rhs.empty()) {
266          throw Config_error(line, "");
267        }
268      } 
269    }
270  }
271
272
273  Configuration& Configuration::instance(void)
274  {
275    if (!instance_)
276      instance_ = new Configuration;
277    return *instance_;
278  }
279
280
281  bool Configuration::missing_copyright_warning(void) const
282  {
283    return missing_copyright_warning_;
284  }
285
286
287  std::string
288  Configuration::translate(const std::string& str,
289                           const std::pair<std::string, std::string>& dic) const
290  {
291    assert(svndigest::equal(str.begin(), str.end(),
292                            dic.first.begin(), dic.first.end()));
293    std::string res;
294    std::vector<std::string> vec;
295    regexp(str.begin(), str.end(), dic.first.begin(), dic.first.end(), vec);
296    for (std::string::const_iterator i(dic.second.begin()); 
297         i!=dic.second.end(); ++i) {
298      if (*i == '$') {
299        std::stringstream ss(std::string(i+1, dic.second.end()));
300        size_t n = 0;
301        ss >> n;
302        if (n>vec.size()){
303          std::stringstream mess;
304          mess << n;
305          throw std::runtime_error(mess.str());
306        }
307        if (n) {
308          res += vec[n-1];
309          ++i;
310          if (n>9){
311            ++i;
312            if (n>99)
313              ++i;
314          }
315        }
316        else
317          throw std::runtime_error("");
318      }
319      else
320        res += *i;
321    }
322
323    return res;
324  }
325
326
327  std::string trans_end_code(std::string str)
328  {
329    if (str.size()>0 && str[str.size()-1]=='\n')
330      return str.substr(0, str.size()-1) + std::string("\\n");
331    return str;
332  }
333
334
335  std::string trans_beg_code(std::string str)
336  {
337    if (str.size()>0 && str[0]=='\n')
338      return std::string("\\n") + str.substr(1); 
339    return str;
340  }
341
342
343  std::string trim(std::string str, char c)
344  {
345    if (str.size()<2 || str[0]!=c || str[str.size()-1]!=c){
346      std::stringstream mess;
347      mess << "expected `" << str << "' to be surrounded by `" << c << "'";
348      throw std::runtime_error(mess.str());
349    }
350    return str.substr(1, str.size()-2);
351  }
352
353
354  void Configuration::set_default(void)
355  {
356    copyright_alias_.clear();
357    missing_copyright_warning_=false;
358    trac_root_ = "";
359
360    add_codon("*.ac", "#", "\n");
361    add_codon("*.ac", "dnl", "\n");
362    add_codon("*.am", "#", "\n");
363    add_codon("*.am", "dnl", "\n");
364    add_codon("*.m4", "#", "\n");
365    add_codon("*.m4", "dnl", "\n");
366    add_codon("*.c", "//", "\n");
367    add_codon("*.c", "/*", "*/");
368    add_codon("*.cc", "//", "\n");
369    add_codon("*.cc", "/*", "*/");
370    add_codon("*.cpp", "//", "\n");
371    add_codon("*.cpp", "/*", "*/");
372    add_codon("*.cxx", "//", "\n");
373    add_codon("*.cxx", "/*", "*/");
374    add_codon("*.h", "//", "\n");
375    add_codon("*.h", "/*", "*/");
376    add_codon("*.hh", "//", "\n");
377    add_codon("*.hh", "/*", "*/");
378    add_codon("*.hpp", "//", "\n");
379    add_codon("*.hpp", "/*", "*/");
380    add_codon("*.java", "//", "\n");
381    add_codon("*.java", "/*", "*/");
382    add_codon("*.pl", "#", "\n");
383    add_codon("*.pm", "#", "\n");
384    add_codon("*.sh", "#", "\n");
385    add_codon("*config", "#", "\n");
386    add_codon("bootstrap", "#", "\n");
387    add_codon("Makefile", "#", "\n");
388    add_codon("*.tex", "%", "\n");
389    add_codon("*.m", "%", "\n");
390    add_codon("*.jsp", "<!--", "-->");
391    add_codon("*.html", "<%--", "--%>");
392    add_codon("*.xml", "<!--", "-->");
393    add_codon("*.xsl", "<!--", "-->");
394    add_codon("*.xsd", "<!--", "-->");
395    add_codon("*.xhtml", "<!--", "-->");
396    add_codon("*.shtml", "<!--", "-->");
397    add_codon("*.xml", "<!--", "-->");
398    add_codon("*.css", "<!--", "-->");
399    add_codon("*.rss", "<!--", "-->");
400    add_codon("*.sgml", "<!--", "-->");
401    add_codon("*.bat", "\nREM", "\n");
402    add_codon("*.bat", "\nrem", "\n");
403
404    dictionary_ = VectorPair(1, std::make_pair("*.in", "$1"));
405  }
406
407
408  std::string Configuration::trac_root(void) const
409  {
410    return trac_root_;
411  }
412
413
414  std::ostream& operator<<(std::ostream& os, const Configuration& conf)
415  {
416    os << "### This file configures various behaviors for svndigest\n"
417       << "### The commented-out below are intended to demonstrate how to use\n"
418       << "### this file.\n"
419       << "\n"
420       << "### Section for setting behaviour of copyright update\n"
421       << "[copyright]\n"
422       << "# if true svndigest will warn if file has no copyright statement.\n"
423       << "missing-copyright-warning = ";
424   
425    if (conf.missing_copyright_warning())
426      os << "yes\n";
427    else
428      os << "no\n";
429
430    os << "\n"
431       << "### Section for setting aliases used in copyright update\n"
432       << "[copyright-alias]\n"
433       << "# jdoe = John Doe\n";
434
435    typedef std::vector<std::pair<std::string, Alias> > vector;
436    vector vec;
437    std::back_insert_iterator<vector> back_insert_iterator(vec);
438    vec.reserve(conf.copyright_alias().size());
439    std::copy(conf.copyright_alias().begin(), conf.copyright_alias().end(),
440              back_insert_iterator);
441    // sort with respect to Alias.id
442    IdCompare id;
443    PairSecondCompare<const std::string, Alias, IdCompare> comp(id);
444    std::sort(vec.begin(),vec.end(), comp);
445             
446
447    for (vector::const_iterator i(vec.begin()); i!=vec.end(); ++i) {
448      os << i->first << " = " << i->second.name() << " \n";
449    }
450
451    os << "\n"
452       << "### Section for setting trac environment\n"
453       << "[trac]\n"
454       << "# If trac-root is set, svndigest will create anchors to "
455       << "the Trac page.\n"
456       << "# trac-root = http://trac.thep.lu.se/svndigest/\n";
457    if (!conf.trac_root().empty())
458      os << "trac-root = " << conf.trac_root() << "\n";
459
460    if (!conf.dictionary_.empty()) {
461      os << "\n"
462         << "### Section for setting dictionary for file names.\n"
463         << "### Prior looking for file name pattern in section [codon],\n"
464         << "### the file name may be translated according to the rules \n"
465         << "### in this section. In default setting there is, for example,\n"
466         << "### a rule to translate `<FILENAME>.in' to `<FILENAME>'.\n"
467         << "### The format of the entries is:\n"
468         << "###    file-name-pattern = new-name\n"
469         << "### Left hand side may contain wildcards (such as '*' and '?').\n"
470         << "### Right hand side may contain \"$i\", which will be replaced \n"
471         << "### with the ith wild card in lhs string.\n"
472         << "[file-name-dictionary]\n";
473      for (size_t i=0; i<conf.dictionary_.size(); ++i)
474        os << conf.dictionary_[i].first << " = " 
475           << conf.dictionary_[i].second << "\n"; 
476    }
477    if (!conf.string2codons_.empty()) {
478      os << "\n"
479         << "### Section for setting parsing modes\n"
480         << "### The format of the entries is:\n"
481         << "###   file-name-pattern = \"start-code\" : \"end-code\"\n"
482         << "### The file-name-pattern may contain wildcards (such as '*' "
483         << "and '?').\n"
484         << "### String \"\\n\" can be used for codons containing newline"
485         << "\n### character.\n"
486         << "[parsing-codons]\n";
487      for (size_t i=0; i<conf.string2codons_.size(); ++i) {
488        os << conf.string2codons_[i].first << " = "; 
489        for (size_t j=0; j<conf.string2codons_[i].second.size(); ++j) {
490          if (j)
491            os << "  ;  ";
492          os << "\"" << trans_beg_code(conf.string2codons_[i].second[j].first) 
493             << "\":\"" 
494             << trans_end_code(conf.string2codons_[i].second[j].second) 
495             << "\""; 
496        }
497        os << "\n";
498      }
499    }
500    return os;
501  }
502
503 
504  Config_error::Config_error(const std::string& line,const std::string& message)
505    : std::runtime_error(std::string("line: `") + line + 
506                         std::string("' is invalid.\n") + message)
507  {}
508
509}} // end of namespace svndigest and namespace theplu
Note: See TracBrowser for help on using the repository browser.